607 lines
25 KiB
Markdown
607 lines
25 KiB
Markdown
# AfterSales CRM - AI Agent Instructions
|
|
|
|
> **Trước khi làm việc, đọc các file quan trọng:**
|
|
> - `CODEBASE_SNAPSHOT.md` — Toàn bộ cấu trúc codebase (thay vì scan lại code)
|
|
> - `TASKS_ROADMAP.md` — Roadmap: đã làm, đang làm, sẽ làm
|
|
> - `docs/I18N_GUIDE.md` — Hướng dẫn đa ngôn ngữ
|
|
> - `docs/ENUM_GUIDE.md` — Hướng dẫn thêm Enum mới
|
|
> - `luutru/` — Archive các file đã lỗi thời (tham khảo nếu cần)
|
|
>
|
|
> **File đã lỗi thời trong luutru/:** PROGRESS.md, SOP_des.md, AI_01/02/03, CHANGELOG.md, README.md gốc, SYSTEM_ASSESSMENT_PROPOSALS.md
|
|
|
|
## Overview
|
|
AfterSales CRM is a real-estate after-sales customer care system built with **Laravel 13.6** + **Filament 5.6**.
|
|
Hệ thống có **2 Filament Panel**:
|
|
- `/admin` — Admin Panel: đầy đủ tất cả modules (Contract, Handover, Service, User/Role...)
|
|
- `/support` — Support Panel: rút gọn, tập trung Feedback (Feedback, Customer, Product, Channel, Tag, ActivityLog)
|
|
|
|
## How to Run
|
|
```bash
|
|
cd /home/phuongtc/vibecode/minicrm
|
|
php artisan migrate:fresh --seed
|
|
php artisan serve
|
|
# Admin panel: http://localhost:8000/admin
|
|
# Support panel: http://localhost:8000/support
|
|
```
|
|
|
|
### Test Accounts (seeded)
|
|
| Role | Email | Password |
|
|
|------|-------|----------|
|
|
| Admin | admin@minicrm.local | password |
|
|
| Manager | manager@minicrm.local | password |
|
|
| Staff | staff@minicrm.local | password |
|
|
|
|
## Tech Stack Versions
|
|
- PHP 8.3
|
|
- Laravel 13.6.0 (`vendor/laravel/framework`)
|
|
- Filament 5.6.1 (`vendor/filament/filament`)
|
|
- Database: SQLite (`database/database.sqlite`)
|
|
- Composer: `/home/phuongtc/composer`
|
|
- Spatie: laravel-permission (role-based auth)
|
|
- Testing: Pest PHP
|
|
|
|
## Running Commands
|
|
```bash
|
|
cd /home/phuongtc/vibecode/minicrm
|
|
php artisan make:model Foo
|
|
php artisan make:filament-resource Foo --generate
|
|
php artisan make:filament-relation-manager ResourceName relationship titleAttribute
|
|
php artisan make:migration create_foo_table
|
|
php artisan migrate
|
|
php artisan db:seed
|
|
php artisan route:list
|
|
php artisan storage:link # needed for file uploads
|
|
php artisan test # run pest tests
|
|
|
|
# Export database backup (JSON format, one file per table)
|
|
php artisan app:export-backup
|
|
php artisan app:export-backup --path=/custom/path --pretty
|
|
php artisan app:export-backup --tables=feedback,customers,products
|
|
|
|
# Import data from Excel
|
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx --dry-run
|
|
php artisan app:import-cskh ai_instructions/CSKH.xlsx
|
|
```
|
|
|
|
## Dual Panel Architecture
|
|
|
|
Hệ thống có 2 Filament Panel độc lập:
|
|
|
|
### Admin Panel (`/admin`) — `AdminPanelProvider.php`
|
|
Đầy đủ tất cả modules, bao gồm:
|
|
- Feedback, Customer, Product, Contract, FeedbackChannel, FeedbackTag
|
|
- User, Role (admin-only)
|
|
- 3 Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget
|
|
|
|
### Support Panel (`/support`) — `SupportPanelProvider.php`
|
|
Rút gọn, chỉ tập trung vào quy trình xử lý feedback:
|
|
- Feedback, Customer, Product, FeedbackChannel, FeedbackTag, ActivityLog
|
|
- 3 Dashboard widgets giống Admin panel
|
|
- Không có: Contracts, Users, Roles, Handovers, ProductServices
|
|
|
|
Cả 2 panel dùng chung: Models, Services, Policies, Database, Translations.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
minicrm/
|
|
├── app/
|
|
│ ├── Filament/
|
|
│ │ ├── Resources/
|
|
│ │ │ ├── ActivityLogs/ # NEW: Audit trail (read-only)
|
|
│ │ │ │ ├── ActivityLogResource.php
|
|
│ │ │ │ └── Pages/ListActivityLogs.php
|
|
│ │ │ ├── Feedback/
|
|
│ │ │ │ ├── FeedbackResource.php
|
|
│ │ │ │ ├── Schemas/FeedbackForm.php
|
|
│ │ │ │ ├── Tables/FeedbackTable.php
|
|
│ │ │ │ ├── Pages/{Create,Edit,List}Feedback.php
|
|
│ │ │ │ └── RelationManagers/InteractionsRelationManager.php
|
|
│ │ │ ├── Products/
|
|
│ │ │ │ └── RelationManagers/{Contracts,Handovers,ProductServices}RelationManager.php
|
|
│ │ │ ├── Customers/
|
|
│ │ │ │ └── RelationManagers/FeedbacksRelationManager.php
|
|
│ │ │ ├── Contracts/
|
|
│ │ │ ├── FeedbackChannels/
|
|
│ │ │ ├── FeedbackTags/
|
|
│ │ │ ├── Users/
|
|
│ │ │ └── Roles/
|
|
│ │ └── Widgets/
|
|
│ │ ├── ResolvedTicketsWidget.php # Dashboard: resolved tickets table
|
|
│ │ ├── AvgProcessingTimeWidget.php # Dashboard: stats overview
|
|
│ │ └── HandlerPerformanceWidget.php # Dashboard: bar chart per handler
|
|
│ ├── Console/Commands/
|
|
│ │ ├── ImportCskh.php # app:import-cskh
|
|
│ │ └── ExportBackup.php # app:export-backup
|
|
│ ├── Enums/
|
|
│ │ ├── ContractCategory.php # V2: ownership/business
|
|
│ │ ├── ContractType.php
|
|
│ │ ├── ContractStatus.php
|
|
│ │ ├── HandoverStatus.php
|
|
│ │ └── ServiceType.php
|
|
│ ├── Http/
|
|
│ │ ├── Controllers/Controller.php
|
|
│ │ └── Middleware/HandleUploadErrors.php
|
|
│ ├── Models/
|
|
│ │ ├── ActivityLog.php # NEW: Audit trail model
|
|
│ │ ├── Contract.php # V2: +category, +representative_*
|
|
│ │ ├── Customer.php # V2: ownedProducts() qua contracts
|
|
│ │ ├── Department.php
|
|
│ │ ├── Feedback.php # V2: bỏ customer_product_id
|
|
│ │ ├── FeedbackAttachment.php
|
|
│ │ ├── FeedbackChannel.php
|
|
│ │ ├── FeedbackInteraction.php
|
|
│ │ ├── FeedbackTag.php
|
|
│ │ ├── Handover.php
|
|
│ │ ├── Product.php
|
|
│ │ ├── ProductService.php
|
|
│ │ ├── TicketTransferLog.php
|
|
│ │ └── User.php
|
|
│ ├── Policies/
|
|
│ │ ├── ActivityLogPolicy.php # NEW
|
|
│ │ ├── ContractPolicy.php
|
|
│ │ ├── CustomerPolicy.php
|
|
│ │ ├── FeedbackChannelPolicy.php
|
|
│ │ ├── FeedbackPolicy.php
|
|
│ │ ├── ProductPolicy.php
|
|
│ │ ├── RolePolicy.php
|
|
│ │ └── UserPolicy.php
|
|
│ ├── Notifications/
|
|
│ │ ├── TicketTransferred.php
|
|
│ │ └── TicketClosed.php
|
|
│ ├── Providers/Filament/
|
|
│ │ ├── AdminPanelProvider.php # Panel đầy đủ (/admin)
|
|
│ │ └── SupportPanelProvider.php # Panel rút gọn (/support)
|
|
│ ├── Rules/
|
|
│ │ └── RequireProofOfResolution.php
|
|
│ └── Services/
|
|
│ ├── ActivityLogger.php # NEW: static log() cho audit trail
|
|
│ ├── ClosingService.php
|
|
│ ├── FileService.php
|
|
│ └── TransferService.php
|
|
├── lang/ # i18n translation files
|
|
│ ├── en/{app.php, feedback.php, enums.php}
|
|
│ └── vi/{app.php, feedback.php, enums.php}
|
|
├── resources/views/filament/resources/feedback/
|
|
│ ├── similar-cases.blade.php
|
|
│ └── attachment-links.blade.php
|
|
├── webappUI/aftersales_crm_core/ # Design tokens & system
|
|
│ └── DESIGN.md
|
|
├── database/
|
|
│ ├── migrations/ # 25 migrations
|
|
│ └── seeders/{AfterSalesSeeder,PermissionSeeder}.php
|
|
├── tests/Feature/
|
|
│ ├── ClosingPermissionTest.php
|
|
│ └── TransferFlowTest.php
|
|
├── luutru/ # Archive các file đã lỗi thời
|
|
├── UI_DESIGN_BRIEF.md # Thiết kế UI chi tiết
|
|
└── TASKS_ROADMAP.md
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### activity_logs (NEW)
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| user_id | fk -> users nullable | Who performed action |
|
|
| action | string | import, export, transfer, close, create, update, delete |
|
|
| description | text | Human-readable description |
|
|
| subject_type | string nullable | Related model class |
|
|
| subject_id | bigint nullable | Related model ID |
|
|
| metadata | json nullable | Extra data |
|
|
| created_at | timestamp | |
|
|
|
|
### contracts (V2: category + representative)
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| product_id | fk -> products | cascadeOnDelete |
|
|
| customer_id | fk -> customers | cascadeOnDelete |
|
|
| category | string | **(V2)** ownership \| business |
|
|
| type | string | **(V2)** deposit, purchase, transfer, rental, self_business, bcc |
|
|
| start_date, end_date | date nullable | |
|
|
| status | string | active, expired, liquidated |
|
|
| printed_at | date nullable | |
|
|
| signed_status | string nullable | |
|
|
| representative_name | string nullable | **(V2)** |
|
|
| representative_phone | string nullable | **(V2)** |
|
|
| representative_email | string nullable | **(V2)** |
|
|
| created_at, updated_at | timestamp | |
|
|
|
|
> **V2 Constraint:** Mỗi product chỉ có tối đa 1 contract active mỗi category. Validation ở cả Model level (`booted::saving`) và UI.
|
|
|
|
### departments (NEW)
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| name | string | |
|
|
| manager_id | fk -> users nullable | Department head |
|
|
| created_at, updated_at | timestamp | |
|
|
|
|
### ticket_transfer_logs
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| feedback_id | fk -> feedback | cascadeOnDelete |
|
|
| from_department_id | fk -> departments nullable | |
|
|
| to_department_id | fk -> departments nullable | |
|
|
| sender_id | fk -> users | Who initiated transfer |
|
|
| reason | text | Mandatory |
|
|
| created_at, updated_at | timestamp | |
|
|
|
|
### feedback (expanded)
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| customer_id | fk -> customers | cascadeOnDelete |
|
|
| contract_id | fk -> contracts nullable | **(V2)** Snapshot contract (thay thế customer_product_id) |
|
|
| feedback_channel_id | fk -> feedback_channels | |
|
|
| assigned_to | fk -> users nullable | Current handler |
|
|
| current_department_id | fk -> departments nullable | Current department |
|
|
| is_escalated | boolean default false | |
|
|
| title | string | |
|
|
| content | text | |
|
|
| status | string | pending, processing, resolved, closed |
|
|
| created_at, updated_at | timestamp | |
|
|
|
|
### feedback_attachments (expanded)
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | bigint PK | |
|
|
| uuid | string nullable unique | |
|
|
| feedback_id | fk -> feedback | |
|
|
| feedback_interaction_id | fk -> feedback_interactions nullable | |
|
|
| user_id | fk -> users | |
|
|
| name | string | |
|
|
| path | string | |
|
|
| disk | string default 'local' | |
|
|
| collection | string default 'general' | proof_of_resolution, customer_evidence, general |
|
|
| mime_type | string nullable | |
|
|
| size | bigint nullable | |
|
|
| created_at, updated_at | timestamp | |
|
|
|
|
### Other tables
|
|
feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers, feedback_channels, users, contracts, handovers, product_services, roles, permissions, model_has_roles (Spatie), notifications, sessions, cache
|
|
|
|
## Workflow
|
|
|
|
```
|
|
1. Record Feedback → Assign handler → Assign department
|
|
2. Handler contacts customer / does work
|
|
3. Handler updates → May forward to another user (assignment interaction)
|
|
4. OR: Transfer to another department → TransferDepartment Action (reason required, notification sent)
|
|
5. Loop until → Marked as "resolved" (any role)
|
|
6. Manager reviews → Close Ticket Action (requires role=admin/manager + proof_of_resolution evidence)
|
|
```
|
|
|
|
## Key Features
|
|
|
|
### Dual Panel System (NEW)
|
|
- **Admin Panel** (`/admin`): Đầy đủ modules, User/Role management
|
|
- **Support Panel** (`/support`): Rút gọn, tập trung Feedback. Dùng cho đội support không cần quản lý hệ thống
|
|
|
|
### ActivityLog — Audit Trail (NEW)
|
|
- Tự động ghi log các hành động: import, export, transfer, close, create, update, delete
|
|
- `ActivityLogger::log(action, description, subject, metadata, userId)`
|
|
- Xem tại `/admin/activity-logs` hoặc `/support/activity-logs`
|
|
- Admin-only access (ActivityLogPolicy)
|
|
|
|
### Transfer Department (SOP compliant)
|
|
- Action trên EditFeedback: TransferDepartment
|
|
- Modal: select target department + reason (required) + optional attachments
|
|
- Calls TransferService: creates TicketTransferLog, updates current_department_id, resets handler to dept manager
|
|
- Sends TicketTransferred notification to target department manager
|
|
|
|
### Close Ticket (SOP compliant)
|
|
- Action trên EditFeedback: CloseTicket
|
|
- Visible only to admin/manager, only when status != 'closed'
|
|
- Requires confirmation
|
|
- Staff (403) if tries to close
|
|
- 422 if no proof_of_resolution attachment exists
|
|
- Sends TicketClosed notification to handler and department manager
|
|
|
|
### Status Change in Interactions (Updated)
|
|
- Staff: only pending, processing, resolved (no "closed" option)
|
|
- Manager/Admin: all options including "closed"
|
|
- When "closed" selected: calls ClosingService which validates evidence
|
|
|
|
### File Management (SOP compliant)
|
|
- Files stored on `public` disk (`storage/app/public/feedback-attachments/`)
|
|
- Collection system: `proof_of_resolution`, `customer_evidence`, `general`
|
|
- FileService validates: jpg, png, pdf, mp4, mov, max 20MB
|
|
- Temporary signed URLs for private file access
|
|
- FileService::hasCollection() checks existence of required evidence
|
|
|
|
### Dashboard Widgets
|
|
- ResolvedTicketsWidget: table of resolved tickets awaiting closure (manager: filtered to own department)
|
|
- AvgProcessingTimeWidget: 4 stats (resolved count, avg time, pending count, escalated count)
|
|
- HandlerPerformanceWidget: bar chart of avg processing time per handler
|
|
|
|
## Internationalization (i18n)
|
|
|
|
### Configuration
|
|
- Default locale: `vi` (Vietnamese), fallback: `en` (English)
|
|
- Set in `.env`: `APP_LOCALE=vi`, `APP_FALLBACK_LOCALE=en`
|
|
- Filament uses `app()->getLocale()` — no extra panel config needed
|
|
|
|
### Translation Files
|
|
```
|
|
lang/
|
|
├── en/
|
|
│ ├── app.php # General UI: navigation, labels, status, widgets, blade strings
|
|
│ ├── feedback.php # Feedback domain: services, notifications, interactions
|
|
│ └── enums.php # Enum labels (HandoverStatus, ServiceType, ContractStatus, ContractType)
|
|
└── vi/
|
|
├── app.php
|
|
├── feedback.php
|
|
└── enums.php
|
|
```
|
|
|
|
### Translation Patterns
|
|
```php
|
|
// Resource labels — MUST override getPluralLabel() (not auto-translated)
|
|
public static function getPluralLabel(): ?string
|
|
{
|
|
return __('app.resource_feedbacks');
|
|
}
|
|
|
|
// Navigation group — MUST override getNavigationGroup() (not auto-translated)
|
|
public static function getNavigationGroup(): ?string
|
|
{
|
|
return __('app.nav_customer_care');
|
|
}
|
|
|
|
// Form fields — MUST add explicit ->label()
|
|
TextInput::make('name')->label(__('app.name'))
|
|
|
|
// Status options
|
|
'options' => [
|
|
'pending' => __('app.status_pending'),
|
|
'processing' => __('app.status_processing'),
|
|
]
|
|
|
|
// Enum labels
|
|
public function label(): string
|
|
{
|
|
return match ($this) {
|
|
self::ACTIVE => __('enums.contract_status.active'),
|
|
};
|
|
}
|
|
|
|
// Widget heading/description — override getHeading()/getDescription()
|
|
public function getHeading(): string | Htmlable | null
|
|
{
|
|
return __('app.widget_avg_time_by_handler');
|
|
}
|
|
```
|
|
|
|
### Key Gotchas
|
|
- `$pluralLabel` property does NOT auto-translate — must override `getPluralLabel()`
|
|
- `getNavigationGroup()` does NOT auto-translate — must call `__()` explicitly
|
|
- `$heading`/`$description` in ChartWidget are raw properties — must override `getHeading()`/`getDescription()`
|
|
- Form fields without `->label()` use Filament's `filament-panels::` namespace (may not resolve correctly)
|
|
- Always add `->label(__('key'))` to TextInput, Select, Textarea, etc.
|
|
|
|
## Navigation Structure
|
|
|
|
### Admin Panel (`/admin`)
|
|
```
|
|
Dashboard (AccountWidget + 3 custom widgets)
|
|
├── Customer Care / Chăm sóc khách hàng (group)
|
|
│ └── Feedbacks / Phản ánh (chat-bubble-left-right, sort=1)
|
|
└── Management / Quản lý (group)
|
|
├── Products / Sản phẩm (building-storefront, sort=1)
|
|
├── Customers / Khách hàng (user-group, sort=2)
|
|
├── Channels / Kênh phản ánh (inbox-stack, sort=3)
|
|
├── Contracts / Hợp đồng (document-text, sort=4)
|
|
├── Tags / Thẻ (tag, sort=default)
|
|
├── Activity Logs (clock, sort=7) — admin only
|
|
├── Users / Người dùng (users, sort=5) — admin only
|
|
└── Roles / Vai trò (shield-check, sort=6) — admin only
|
|
```
|
|
|
|
### Support Panel (`/support`)
|
|
```
|
|
Dashboard (3 custom widgets)
|
|
├── Customer Care / Chăm sóc khách hàng (group)
|
|
│ └── Feedbacks
|
|
└── Management / Quản lý (group)
|
|
├── Products
|
|
├── Customers
|
|
├── Channels
|
|
├── Tags
|
|
└── Activity Logs
|
|
```
|
|
|
|
Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget (admin/manager only)
|
|
|
|
## RBAC (Role-Based Access Control) - Spatie
|
|
|
|
Roles: `admin`, `manager`, `staff`
|
|
|
|
### Granular Permissions (Spatie Permission)
|
|
|
|
**Feedback:** `view-feedback`, `create-feedback`, `update-feedback`, `delete-feedback`, `add-interaction`, `close-ticket`, `transfer-department`
|
|
|
|
**Products:** `view-product`, `create-product`, `update-product`, `delete-product`
|
|
|
|
**Customers:** `view-customer`, `create-customer`, `update-customer`, `delete-customer`
|
|
|
|
**Contracts:** `view-contract`, `create-contract`, `update-contract`, `delete-contract`
|
|
|
|
**Channels:** `view-channel`, `create-channel`, `update-channel`, `delete-channel`
|
|
|
|
**Tags:** `view-tag`, `create-tag`, `update-tag`, `delete-tag`
|
|
|
|
**Dashboard:** `view-dashboard-widgets`, `export-data`
|
|
|
|
### Permission Matrix
|
|
|
|
| Permission | admin | manager | staff |
|
|
|-----------|-------|---------|-------|
|
|
| view-feedback | ✓ | ✓ | ✓ |
|
|
| create-feedback | ✓ | ✓ | ✓ |
|
|
| update-feedback | ✓ | ✓ | ✓ |
|
|
| delete-feedback | ✓ | ✓ | ✗ |
|
|
| add-interaction | ✓ | ✓ | ✓ |
|
|
| close-ticket | ✓ | ✓ | ✗ |
|
|
| transfer-department | ✓ | ✓ | ✗ |
|
|
| view-product | ✓ | ✓ | ✓ |
|
|
| create/update-product | ✓ | ✓ | ✗ |
|
|
| delete-product | ✓ | ✗ | ✗ |
|
|
| view-customer | ✓ | ✓ | ✓ |
|
|
| create/update-customer | ✓ | ✓ | ✗ |
|
|
| delete-customer | ✓ | ✗ | ✗ |
|
|
| view-contract | ✓ | ✓ | ✓ |
|
|
| create/update-contract | ✓ | ✓ | ✗ |
|
|
| delete-contract | ✓ | ✗ | ✗ |
|
|
| view-channel/tag | ✓ | ✓ | ✓ |
|
|
| create/update-channel/tag | ✓ | ✓ | ✗ |
|
|
| delete-channel/tag | ✓ | ✗ | ✗ |
|
|
| view-dashboard-widgets | ✓ | ✓ | ✗ |
|
|
| export-data | ✓ | ✗ | ✗ |
|
|
|
|
### Permission check patterns
|
|
```php
|
|
// In Policies
|
|
$user->hasPermissionTo('close-ticket');
|
|
$user->hasPermissionTo('view-feedback');
|
|
|
|
// In Filament
|
|
auth()->user()->hasPermissionTo('transfer-department');
|
|
auth()->user()->hasPermissionTo('view-dashboard-widgets');
|
|
|
|
// Role check (still used for admin-only guards)
|
|
$user->hasRole('admin');
|
|
```
|
|
|
|
## Filament Key Patterns (v5.6)
|
|
|
|
### Form components use `Filament\Forms\Components`
|
|
```php
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Forms\Components\FileUpload;
|
|
use Filament\Forms\Components\RichEditor;
|
|
```
|
|
|
|
### Schema/layout components use `Filament\Schemas\Components`
|
|
```php
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
```
|
|
|
|
### Services access pattern
|
|
```php
|
|
use Illuminate\Support\Facades\App;
|
|
$service = App::make(FileService::class);
|
|
|
|
// ActivityLogger (static)
|
|
\App\Services\ActivityLogger::log('transfer', '...', $feedback, ['dept' => 'CSKH']);
|
|
```
|
|
|
|
### Spatie role check
|
|
```php
|
|
$user->hasRole('admin');
|
|
$user->hasRole(['admin', 'manager']);
|
|
$user->assignRole('manager');
|
|
```
|
|
|
|
## Known Gotchas
|
|
|
|
1. **Navigation group icons**: Filament 5 raises exception if both a navigation group AND its child items have icons.
|
|
2. **Form component namespaces**: Form input components under `Filament\Forms\Components\*`, layout under `Filament\Schemas\Components\*`.
|
|
3. **Dynamic selects**: Use `$get` function injection, NOT `$component->getLivewire()->data[...]`.
|
|
4. **Table name**: Model `Feedback` maps to table `feedback` (singular).
|
|
5. **FileUpload with dehydrated(false)**: Attachments stored by Filament first; create FeedbackAttachment record directly in after()/afterSave()/afterCreate() — do NOT re-upload via FileService.
|
|
6. **Spatie roles in tests**: Create roles explicitly in each test (RefreshDatabase rolls back).
|
|
7. **Private disk**: Files use `local` disk (storage/app/private/); access via signed URLs from FileService.
|
|
8. **ChartWidget properties**: `$heading` and `$columnSpan` are non-static in Filament's ChartWidget.
|
|
9. **Manager closing permission**: Manager must be department head (`Department.manager_id`) to close tickets in that department.
|
|
10. **Docker**: Entrypoint auto-generates APP_KEY and runs migrations. SQLite persists via volume mount.
|
|
11. **i18n - pluralLabel**: `$pluralLabel` property does NOT auto-translate — must override `getPluralLabel()` and call `__()`.
|
|
12. **i18n - navigationGroup**: `getNavigationGroup()` does NOT auto-translate — must return `__('key')` explicitly.
|
|
13. **i18n - ChartWidget heading**: `$heading`/`$description` are raw properties — must override `getHeading()`/`getDescription()`.
|
|
14. **i18n - form labels**: Form fields without `->label()` use Filament's `filament-panels::` namespace — always add explicit `->label(__('key'))`.
|
|
15. **i18n - Filament vendor**: Filament ships 57 Vietnamese translation files — auto-activated when `APP_LOCALE=vi`.
|
|
16. **FileUpload disk**: Always use `disk('public')` for all FileUpload components. NEVER use `disk('local')` — causes file not found errors.
|
|
17. **FileUpload dehydrated**: Do NOT use `dehydrated(false)` — prevents file paths from being available in afterSave()/afterCreate(). Use property to store paths in mutateFormDataBeforeSave().
|
|
18. **File metadata safety**: Always check `$disk->exists($path)` before calling `$disk->mimeType()` or `$disk->size()` — prevents UnableToRetrieveMetadata error.
|
|
19. **FileUpload in Actions**: When handling file uploads in Action callbacks (e.g., transfer attachments), file paths are strings (not UploadedFile). Create records directly instead of using FileService::upload().
|
|
20. **Tailwind CSS in Filament**: Custom blade views render inside Filament layout which does NOT include full Tailwind CSS build. Must add needed utilities to `public/css/filament-custom.css` with `!important`.
|
|
21. **SVG icon sizing**: Use inline `width`/`height` attributes on SVG icons (e.g., `<svg width="14" height="14">`) instead of Tailwind classes (`w-3.5 h-3.5`) — prevents Filament CSS from overriding sizes.
|
|
22. **Dual Panel**: Support panel at `/support` is separate from Admin panel at `/admin`. Both share the same models/services/policies/database.
|
|
|
|
## File Upload Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ FileUpload Component (disk='public') │
|
|
│ → Filament uploads via AJAX to storage/app/public/ │
|
|
│ → File path stored in Livewire state │
|
|
│ │
|
|
│ Form Submit │
|
|
│ → mutateFormDataBeforeSave() stores paths in property │
|
|
│ → Removes 'attachments' from $data before saving model │
|
|
│ │
|
|
│ afterSave()/afterCreate() │
|
|
│ → Uses stored paths to create FeedbackAttachment records │
|
|
│ → Checks $disk->exists($path) before getting metadata │
|
|
│ → Uses asset('storage/' . $path) for URLs │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### FileUpload Pattern (correct):
|
|
```php
|
|
// In Form
|
|
FileUpload::make('attachments')
|
|
->disk('public') // ALWAYS public
|
|
->directory('feedback-attachments')
|
|
// Do NOT use ->dehydrated(false)
|
|
|
|
// In Page class
|
|
protected array $pendingAttachments = [];
|
|
|
|
protected function mutateFormDataBeforeSave(array $data): array
|
|
{
|
|
$this->pendingAttachments = $data['attachments'] ?? [];
|
|
unset($data['attachments']); // Remove before saving model
|
|
return $data;
|
|
}
|
|
|
|
protected function afterSave(): void
|
|
{
|
|
$disk = Storage::disk('public');
|
|
foreach ($this->pendingAttachments as $path) {
|
|
if (! $disk->exists($path)) continue; // Safety check
|
|
|
|
$this->getRecord()->attachments()->create([
|
|
'path' => $path,
|
|
'disk' => 'public',
|
|
'mime_type' => $disk->mimeType($path),
|
|
'size' => $disk->size($path),
|
|
// ...
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Tailwind CSS in Filament (correct):
|
|
```php
|
|
// Filament uses its own CSS build, NOT the project's Tailwind build
|
|
// Custom blade views need utilities added to public/css/filament-custom.css
|
|
|
|
// In filament-custom.css:
|
|
.flex { display: flex !important; }
|
|
.items-center { align-items: center !important; }
|
|
.gap-2 { gap: 0.5rem !important; }
|
|
// ... add all needed utilities with !important
|
|
|
|
// For SVG icons, use inline attributes:
|
|
<svg width="14" height="14" class="text-blue-600">...</svg>
|
|
// NOT: <svg class="w-3.5 h-3.5 text-blue-600">...</svg>
|
|
```
|