feat: add i18n support for Vietnamese and English
This commit is contained in:
107
AGENTS.md
107
AGENTS.md
@@ -68,11 +68,19 @@ minicrm/
|
||||
│ │ │ ├── Customers/
|
||||
│ │ │ │ └── RelationManagers/FeedbacksRelationManager.php
|
||||
│ │ │ ├── FeedbackChannels/
|
||||
│ │ │ └── FeedbackTags/
|
||||
│ │ │ ├── FeedbackTags/
|
||||
│ │ │ ├── Contracts/
|
||||
│ │ │ ├── Users/
|
||||
│ │ │ └── Roles/
|
||||
│ │ └── Widgets/
|
||||
│ │ ├── ResolvedTicketsWidget.php # Dashboard: resolved tickets table
|
||||
│ │ ├── AvgProcessingTimeWidget.php # Dashboard: stats overview
|
||||
│ │ └── HandlerPerformanceWidget.php # Dashboard: bar chart per handler
|
||||
│ ├── Enums/
|
||||
│ │ ├── ContractType.php
|
||||
│ │ ├── ContractStatus.php
|
||||
│ │ ├── HandoverStatus.php
|
||||
│ │ └── ServiceType.php
|
||||
│ ├── Models/
|
||||
│ │ ├── User.php # HasRoles (Spatie), isAdmin(), isManager()
|
||||
│ │ ├── Department.php # NEW: name, manager_id → User
|
||||
@@ -101,6 +109,15 @@ minicrm/
|
||||
│ │ └── TicketClosed.php # DB + Mail for ticket closure
|
||||
│ └── Providers/Filament/
|
||||
│ └── AdminPanelProvider.php
|
||||
├── lang/ # i18n translation files
|
||||
│ ├── en/
|
||||
│ │ ├── app.php # General UI labels, navigation, status, widgets
|
||||
│ │ ├── feedback.php # Feedback domain: services, notifications, interactions
|
||||
│ │ └── enums.php # Enum labels
|
||||
│ └── vi/
|
||||
│ ├── app.php
|
||||
│ ├── feedback.php
|
||||
│ └── enums.php
|
||||
├── resources/views/filament/resources/feedback/
|
||||
│ ├── similar-cases.blade.php
|
||||
│ └── attachment-links.blade.php # Modal listing attachments with signed download URLs
|
||||
@@ -217,19 +234,84 @@ feedback_interactions, feedback_tags, feedback_feedback_tag, products, customers
|
||||
- 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
|
||||
|
||||
```
|
||||
Dashboard
|
||||
├── Customer Care (group)
|
||||
│ └── Feedbacks (chat-bubble-left-right, sort=1)
|
||||
└── Management (group)
|
||||
├── Products (building-storefront, sort=1)
|
||||
├── Customers (user-group, sort=2)
|
||||
├── Channels (inbox-stack, sort=3)
|
||||
├── Tags (tag, sort=default)
|
||||
├── Users (users, sort=5) — admin only
|
||||
└── Roles (shield-check, sort=6) — admin only
|
||||
├── 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)
|
||||
├── Tags / Thẻ (tag, sort=default)
|
||||
├── Users / Người dùng (users, sort=5) — admin only
|
||||
└── Roles / Vai trò (shield-check, sort=6) — admin only
|
||||
```
|
||||
|
||||
Dashboard widgets: ResolvedTicketsWidget, AvgProcessingTimeWidget, HandlerPerformanceWidget (admin/manager only)
|
||||
@@ -337,3 +419,8 @@ $user->assignRole('manager');
|
||||
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`.
|
||||
|
||||
15
PROGRESS.md
15
PROGRESS.md
@@ -137,6 +137,21 @@ Tham chiếu: `SOP_des.md` — Bản đặc tả hệ thống After-Sale CRM
|
||||
- [x] UserPolicy + RolePolicy (admin-only)
|
||||
- [x] Tests updated: 8 tests, 21 assertions pass
|
||||
|
||||
### Internationalization (i18n) — Vietnamese + English
|
||||
- [x] Config: `APP_LOCALE=vi`, `APP_FALLBACK_LOCALE=en` in `.env`
|
||||
- [x] Created `lang/en/` and `lang/vi/` with 3 files each: `app.php`, `feedback.php`, `enums.php`
|
||||
- [x] ~120 translation keys covering: navigation, labels, status, widgets, services, notifications, enums
|
||||
- [x] All Resources: override `getPluralLabel()` + `getNavigationGroup()` with `__()` calls
|
||||
- [x] All Form fields: explicit `->label(__('key'))` on TextInput, Select, Textarea, etc.
|
||||
- [x] All Table columns: explicit `->label(__('key'))` on TextColumn, IconColumn
|
||||
- [x] All RelationManagers: `$title` property uses translation key
|
||||
- [x] All Widgets: `getHeading()`/`getDescription()` methods return translated strings
|
||||
- [x] Enums: `ContractType`, `ContractStatus`, `HandoverStatus`, `ServiceType` use `__()` in `label()`
|
||||
- [x] Services/Notifications: all user-facing strings use translation keys
|
||||
- [x] Blade views: all hardcoded strings replaced with `__()` calls
|
||||
- [x] Filament vendor translations (57 Vietnamese files) auto-activated for UI chrome
|
||||
- [x] Tests: 8/8 pass (21 assertions)
|
||||
|
||||
---
|
||||
|
||||
## Database Schema (final — 23 migrations)
|
||||
|
||||
56
SYSTEM_ASSESSMENT_PROPOSALS.md
Normal file
56
SYSTEM_ASSESSMENT_PROPOSALS.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# AfterSales CRM - System Assessment & Proposals (Draft)
|
||||
|
||||
> **Ngày ghi nhận:** Monday, May 4, 2026
|
||||
> **Người thực hiện:** Gemini CLI Agent
|
||||
> **Mục đích:** Ghi lại các nhận định về hệ thống, rủi ro bảo mật và đề xuất nâng cấp để thảo luận sau.
|
||||
|
||||
---
|
||||
|
||||
## 1. Nhận định Hệ thống (Current Understanding)
|
||||
- **Kiến trúc:** Laravel 13.6 + Filament 5.6, PHP 8.3. Sử dụng Spatie Permissions cho RBAC.
|
||||
- **Dữ liệu:** Đã import ~3600 records từ Excel thành công. Sử dụng SQLite làm database.
|
||||
- **Workflow:** Quy trình Feedback -> Transfer -> Resolve -> Close đã được thiết lập chặt chẽ với các Service chuyên biệt (`ClosingService`, `TransferService`).
|
||||
- **Tài liệu:** Đã có `AGENTS.md`, `PROGRESS.md`, `TASKS_ROADMAP.md` và `CODEBASE_SNAPSHOT.md` giúp nắm bắt nhanh trạng thái dự án.
|
||||
|
||||
## 2. Rủi ro Bảo mật & An toàn (Security & Safety Risks)
|
||||
|
||||
### 2.1. Phạm vi truy cập dữ liệu (Data Visibility)
|
||||
- **Vấn đề:** `FeedbackPolicy` hiện tại cho phép mọi User có quyền `view-feedback` xem được toàn bộ các phản hồi của tất cả phòng ban.
|
||||
- **Rủi ro:** Lộ thông tin nhạy cảm giữa các phòng ban.
|
||||
- **Đề xuất:** Áp dụng Global Scope hoặc Scoping trong Resource để nhân viên chỉ thấy phiếu thuộc phòng ban mình hoặc được giao cho mình.
|
||||
|
||||
### 2.2. Bảo mật Backup
|
||||
- **Vấn đề:** Command `app:export-backup` lưu file JSON (bao gồm hash mật khẩu và thông tin khách hàng) trực tiếp trong `storage/app/backups`.
|
||||
- **Rủi ro:** Dễ bị đánh cắp nếu folder này bị lộ hoặc server bị chiếm quyền user.
|
||||
- **Đề xuất:** Mã hóa file backup hoặc đẩy thẳng lên S3 với chính sách bảo mật nghiêm ngặt.
|
||||
|
||||
### 2.3. Signed URL trên Local Disk
|
||||
- **Vấn đề:** `FileService` dùng `temporaryUrl` trên driver `local`.
|
||||
- **Rủi ro:** Laravel mặc định không hỗ trợ URL tạm thời cho local driver trừ khi được cấu hình thêm. Có thể gây lỗi không xem được ảnh/file trong thực tế.
|
||||
|
||||
### 2.4. Tính nhất quán của Logic Đóng phiếu
|
||||
- **Vấn đề:** Logic kiểm tra `proof_of_resolution` đang nằm rải rác ở Service và một số chỗ trong Filament UI.
|
||||
- **Rủi ro:** Khó bảo trì và dễ xảy ra sai sót khi cập nhật quy trình.
|
||||
|
||||
## 3. Đề xuất Nâng cấp (Proposed Upgrades)
|
||||
|
||||
### 3.1. Audit Trail (Lịch sử chi tiết)
|
||||
- **Mô tả:** Ghi lại chi tiết ai đã sửa cái gì, từ giá trị cũ sang giá trị mới cho tất cả các Model quan trọng (Contract, Feedback, Customer).
|
||||
- **Công cụ gợi ý:** `spatie/laravel-activitylog`.
|
||||
|
||||
### 3.2. Mã hóa dữ liệu (PII Encryption)
|
||||
- **Mô tả:** Mã hóa các trường thông tin cá nhân (Số điện thoại, Email) trong Database để đảm bảo an toàn nếu database bị leak.
|
||||
|
||||
### 3.3. Theo dõi SLA (SLA Tracking)
|
||||
- **Mô tả:** Thiết lập thời hạn xử lý cho từng loại phản hồi. Cảnh báo (Email/Notification) khi sắp đến hạn hoặc quá hạn.
|
||||
|
||||
### 3.4. Gợi ý chuyển phòng thông minh (Smart Suggestion)
|
||||
- **Mô tả:** Tự động phân tích loại Hợp đồng hoặc Tag để gợi ý "Nên chuyển cho phòng Pháp lý/Kế toán".
|
||||
|
||||
### 3.5. Cải tiến Bulk Actions
|
||||
- **Mô tả:** Cho phép xử lý hàng loạt (Chuyển phòng, Phân công nhân viên) để tối ưu năng suất cho Manager.
|
||||
|
||||
## 4. Các câu hỏi thảo luận sau
|
||||
1. Quy tắc phân quyền xem chéo giữa các phòng ban cụ thể là gì?
|
||||
2. Hệ thống sẽ chạy Production trên SQLite hay chuyển sang MySQL/PostgreSQL?
|
||||
3. Có cần tích hợp Chat trực tuyến (Zalo/Messenger) vào hệ thống không?
|
||||
@@ -11,9 +11,9 @@ enum ContractStatus: string
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::ACTIVE => 'Đang hiệu lực',
|
||||
self::EXPIRED => 'Hết hạn',
|
||||
self::LIQUIDATED => 'Đã thanh lý',
|
||||
self::ACTIVE => __('enums.contract_status.active'),
|
||||
self::EXPIRED => __('enums.contract_status.expired'),
|
||||
self::LIQUIDATED => __('enums.contract_status.liquidated'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ enum ContractType: string
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::SALE => 'HĐ Mua bán',
|
||||
self::LEASE => 'HĐ Thuê',
|
||||
self::BCC => 'HĐ Hợp tác kinh doanh (BCC)',
|
||||
self::SALE => __('enums.contract_type.sale'),
|
||||
self::LEASE => __('enums.contract_type.lease'),
|
||||
self::BCC => __('enums.contract_type.bcc'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ enum HandoverStatus: string
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::NOT_READY => 'Chưa đủ điều kiện',
|
||||
self::READY => 'Đủ điều kiện',
|
||||
self::HANDED_OVER => 'Đã bàn giao',
|
||||
self::SCHEDULED => 'Đã lên lịch',
|
||||
self::NOT_READY => __('enums.handover.not_ready'),
|
||||
self::READY => __('enums.handover.ready'),
|
||||
self::HANDED_OVER => __('enums.handover.handed_over'),
|
||||
self::SCHEDULED => __('enums.handover.scheduled'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ enum ServiceType: string
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::MANAGEMENT_FEE => 'Phí quản lý',
|
||||
self::GRATITUDE => 'Tri ân',
|
||||
self::SELF_BUSINESS => 'Tự doanh',
|
||||
self::MANAGEMENT_FEE => __('enums.service.management_fee'),
|
||||
self::GRATITUDE => __('enums.service.gratitude'),
|
||||
self::SELF_BUSINESS => __('enums.service.self_business'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,18 @@ class ContractResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
|
||||
|
||||
protected static ?string $pluralLabel = 'Contracts';
|
||||
protected static ?string $pluralLabel = 'app.resource_contracts';
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_contracts');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -16,33 +16,37 @@ class ContractForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Thông tin hợp đồng')
|
||||
Section::make(__('app.contract_info'))
|
||||
->schema([
|
||||
Select::make('product_id')
|
||||
->label(__('app.product'))
|
||||
->relationship('product', 'name')
|
||||
->searchable()
|
||||
->required(),
|
||||
Select::make('customer_id')
|
||||
->label(__('app.widget_customer'))
|
||||
->relationship('customer', 'name')
|
||||
->searchable()
|
||||
->required(),
|
||||
Select::make('type')
|
||||
->label(__('app.contract'))
|
||||
->options(ContractType::options())
|
||||
->required()
|
||||
->native(false),
|
||||
Select::make('status')
|
||||
->label(__('app.status'))
|
||||
->options(ContractStatus::ACTIVE->options())
|
||||
->default('active')
|
||||
->required()
|
||||
->native(false),
|
||||
DatePicker::make('start_date')
|
||||
->label('Ngày bắt đầu'),
|
||||
->label(__('app.start_date')),
|
||||
DatePicker::make('end_date')
|
||||
->label('Ngày kết thúc'),
|
||||
->label(__('app.end_date')),
|
||||
DatePicker::make('printed_at')
|
||||
->label('Ngày in HĐ'),
|
||||
->label(__('app.printed_at')),
|
||||
TextInput::make('signed_status')
|
||||
->label('Tình trạng ký')
|
||||
->label(__('app.signed_status'))
|
||||
->maxLength(255),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
@@ -46,7 +46,7 @@ class ContractsTable
|
||||
->toggleable(),
|
||||
TextColumn::make('feedbacks_count')
|
||||
->counts('feedbacks')
|
||||
->label('Tickets'),
|
||||
->label(__('app.tickets')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
@@ -21,13 +21,18 @@ class CustomerResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedUserGroup;
|
||||
|
||||
protected static ?string $pluralLabel = 'Customers';
|
||||
protected static ?string $pluralLabel = 'app.resource_customers';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_customers');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -12,7 +12,7 @@ class FeedbacksRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'feedbacks';
|
||||
|
||||
protected static ?string $title = 'Feedback History';
|
||||
protected static ?string $title = 'app.feedback_history';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
@@ -23,8 +23,8 @@ class FeedbacksRelationManager extends RelationManager
|
||||
->searchable()
|
||||
->url(fn ($record) => FeedbackResource::getUrl('edit', ['record' => $record])),
|
||||
TextColumn::make('customerProduct.product.name')
|
||||
->label('Product')
|
||||
->placeholder('General'),
|
||||
->label(__('app.product'))
|
||||
->placeholder(__('app.general')),
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
@@ -37,10 +37,10 @@ class FeedbacksRelationManager extends RelationManager
|
||||
TextColumn::make('tags.name')
|
||||
->badge(),
|
||||
TextColumn::make('feedbackChannel.name')
|
||||
->label('Channel'),
|
||||
->label(__('app.channel')),
|
||||
TextColumn::make('interactions_count')
|
||||
->counts('interactions')
|
||||
->label('Interactions'),
|
||||
->label(__('app.interactions')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime('d/m/Y')
|
||||
->sortable(),
|
||||
@@ -48,10 +48,10 @@ class FeedbacksRelationManager extends RelationManager
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'resolved' => 'Resolved',
|
||||
'closed' => 'Closed',
|
||||
'pending' => __('app.status_pending'),
|
||||
'processing' => __('app.status_processing'),
|
||||
'resolved' => __('app.status_resolved'),
|
||||
'closed' => __('app.status_closed'),
|
||||
]),
|
||||
SelectFilter::make('tags')
|
||||
->relationship('tags', 'name'),
|
||||
|
||||
@@ -14,32 +14,37 @@ class CustomerForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Customer Information')
|
||||
Section::make(__('app.customer_information'))
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('app.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->label(__('app.email'))
|
||||
->email()
|
||||
->maxLength(255),
|
||||
TextInput::make('phone')
|
||||
->label(__('app.phone'))
|
||||
->tel()
|
||||
->maxLength(255),
|
||||
Textarea::make('address')
|
||||
->label(__('app.address'))
|
||||
->columnSpanFull(),
|
||||
Select::make('status')
|
||||
->options([
|
||||
'active' => 'Active',
|
||||
'inactive' => 'Inactive',
|
||||
'active' => __('app.status_active'),
|
||||
'inactive' => __('app.status_inactive'),
|
||||
])
|
||||
->default('active')
|
||||
->required(),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Owned Products')
|
||||
Section::make(__('app.owned_products'))
|
||||
->schema([
|
||||
Select::make('products')
|
||||
->label(__('app.resource_products'))
|
||||
->relationship('products', 'name')
|
||||
->multiple()
|
||||
->preload()
|
||||
|
||||
@@ -33,10 +33,10 @@ class CustomersTable
|
||||
}),
|
||||
TextColumn::make('products_count')
|
||||
->counts('products')
|
||||
->label('Products'),
|
||||
->label(__('app.resource_products')),
|
||||
TextColumn::make('feedbacks_count')
|
||||
->counts('feedbacks')
|
||||
->label('Feedbacks'),
|
||||
->label(__('app.feedbacks')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
@@ -45,8 +45,8 @@ class CustomersTable
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'active' => 'Active',
|
||||
'inactive' => 'Inactive',
|
||||
'active' => __('app.status_active'),
|
||||
'inactive' => __('app.status_inactive'),
|
||||
]),
|
||||
])
|
||||
->recordActions([
|
||||
|
||||
@@ -21,13 +21,18 @@ class FeedbackResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChatBubbleLeftRight;
|
||||
|
||||
protected static ?string $pluralLabel = 'Feedbacks';
|
||||
protected static ?string $pluralLabel = 'app.resource_feedbacks';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_feedbacks');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Customer Care';
|
||||
return __('app.nav_customer_care');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -29,7 +29,7 @@ class CreateFeedback extends CreateRecord
|
||||
$feedback->interactions()->create([
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'note',
|
||||
'content' => 'Feedback created via ' . ($feedback->feedbackChannel?->name ?? 'unknown channel'),
|
||||
'content' => __('feedback.created_via', ['channel' => $feedback->feedbackChannel?->name ?? __('feedback.unknown_channel')]),
|
||||
'changes' => ['status' => ['old' => null, 'new' => $feedback->status]],
|
||||
]);
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ class EditFeedback extends EditRecord
|
||||
protected function transferDepartmentAction(): Action
|
||||
{
|
||||
return Action::make('transferDepartment')
|
||||
->label('Transfer Department')
|
||||
->label(__('feedback.transfer_department'))
|
||||
->icon('heroicon-o-arrows-right-left')
|
||||
->color('warning')
|
||||
->visible(fn (): bool => auth()->user()->hasPermissionTo('transfer-department'))
|
||||
->form([
|
||||
Select::make('to_department_id')
|
||||
->label('Target Department')
|
||||
->label(__('feedback.target_department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->required()
|
||||
->searchable(),
|
||||
Select::make('new_assignee_id')
|
||||
->label('Assign To (optional)')
|
||||
->label(__('feedback.assign_to_optional'))
|
||||
->options(function (\Filament\Forms\Get $get) {
|
||||
$deptId = $get('to_department_id');
|
||||
if (! $deptId) {
|
||||
@@ -57,11 +57,11 @@ class EditFeedback extends EditRecord
|
||||
->searchable()
|
||||
->nullable(),
|
||||
Textarea::make('reason')
|
||||
->label('Reason')
|
||||
->label(__('feedback.reason'))
|
||||
->required()
|
||||
->rows(3),
|
||||
FileUpload::make('transfer_attachments')
|
||||
->label('Attachments')
|
||||
->label(__('app.attachments'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
@@ -92,7 +92,7 @@ class EditFeedback extends EditRecord
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Transferred successfully')
|
||||
->title(__('feedback.transferred_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@@ -103,14 +103,14 @@ class EditFeedback extends EditRecord
|
||||
protected function closeTicketAction(): Action
|
||||
{
|
||||
return Action::make('closeTicket')
|
||||
->label('Close Ticket')
|
||||
->label(__('feedback.close_ticket'))
|
||||
->icon('heroicon-o-check-circle')
|
||||
->color('success')
|
||||
->visible(fn (): bool => $this->getRecord()->status !== 'closed' && auth()->user()->hasPermissionTo('close-ticket'))
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Close Ticket')
|
||||
->modalDescription('Bạn có chắc muốn đóng phiếu này? Hành động này KHÔNG thể hoàn tác.')
|
||||
->modalSubmitActionLabel('Close')
|
||||
->modalHeading(__('feedback.close_ticket'))
|
||||
->modalDescription(__('feedback.close_confirm'))
|
||||
->modalSubmitActionLabel(__('feedback.close_submit'))
|
||||
->action(function (): void {
|
||||
$feedback = $this->getRecord();
|
||||
$actor = auth()->user();
|
||||
@@ -121,7 +121,7 @@ class EditFeedback extends EditRecord
|
||||
$closingService->close($feedback, $actor);
|
||||
|
||||
Notification::make()
|
||||
->title('Ticket closed successfully')
|
||||
->title(__('feedback.closed_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'interactions';
|
||||
|
||||
protected static ?string $title = 'Interaction History';
|
||||
protected static ?string $title = 'feedback.interaction_history';
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
@@ -35,9 +35,9 @@ class InteractionsRelationManager extends RelationManager
|
||||
->components([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'note' => 'Note',
|
||||
'status_change' => 'Status Change',
|
||||
'assignment' => 'Assignment / Forward',
|
||||
'note' => __('feedback.interaction_type_note'),
|
||||
'status_change' => __('feedback.interaction_type_status_change'),
|
||||
'assignment' => __('feedback.interaction_type_assignment'),
|
||||
])
|
||||
->default('note')
|
||||
->required()
|
||||
@@ -52,28 +52,28 @@ class InteractionsRelationManager extends RelationManager
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('new_status')
|
||||
->label('New Status')
|
||||
->label(__('app.new_status'))
|
||||
->options(fn (): array => auth()->user()->hasPermissionTo('close-ticket')
|
||||
? ['pending' => 'Pending', 'processing' => 'Processing', 'resolved' => 'Resolved', 'closed' => 'Closed']
|
||||
: ['pending' => 'Pending', 'processing' => 'Processing', 'resolved' => 'Resolved'],
|
||||
? ['pending' => __('app.status_pending'), 'processing' => __('app.status_processing'), 'resolved' => __('app.status_resolved'), 'closed' => __('app.status_closed')]
|
||||
: ['pending' => __('app.status_pending'), 'processing' => __('app.status_processing'), 'resolved' => __('app.status_resolved')],
|
||||
)
|
||||
->visible(fn ($get): bool => $get('type') === 'status_change'),
|
||||
|
||||
Select::make('new_assignee')
|
||||
->label('Assign To')
|
||||
->label(__('app.assign_to'))
|
||||
->options(User::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
||||
|
||||
Select::make('department_id')
|
||||
->label('Target Department')
|
||||
->label(__('app.target_department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
||||
|
||||
FileUpload::make('attachments')
|
||||
->label('Attachments')
|
||||
->label(__('app.attachments'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
@@ -90,13 +90,13 @@ class InteractionsRelationManager extends RelationManager
|
||||
->dateTime('d/m/Y H:i')
|
||||
->sortable(),
|
||||
TextColumn::make('user.name')
|
||||
->label('By'),
|
||||
->label(__('app.by')),
|
||||
TextColumn::make('type')
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'note' => 'Note',
|
||||
'status_change' => 'Status Change',
|
||||
'assignment' => 'Assignment',
|
||||
'note' => __('feedback.interaction_type_note'),
|
||||
'status_change' => __('feedback.interaction_type_status_change'),
|
||||
'assignment' => __('feedback.interaction_type_assignment'),
|
||||
default => $state,
|
||||
})
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
@@ -110,7 +110,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
->limit(80)
|
||||
->wrap(),
|
||||
TextColumn::make('changes')
|
||||
->label('Details')
|
||||
->label(__('app.details'))
|
||||
->formatStateUsing(function (?array $state): string {
|
||||
if (! $state) return '';
|
||||
$lines = [];
|
||||
@@ -123,7 +123,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
})
|
||||
->html(),
|
||||
TextColumn::make('attachments_count')
|
||||
->label('Files')
|
||||
->label(__('app.files'))
|
||||
->counts('attachments')
|
||||
->badge()
|
||||
->color(fn (int $state): string => $state > 0 ? 'primary' : 'gray'),
|
||||
@@ -131,9 +131,9 @@ class InteractionsRelationManager extends RelationManager
|
||||
->defaultSort('created_at', 'desc')
|
||||
->recordActions([
|
||||
Action::make('viewAttachments')
|
||||
->label('View Attachments')
|
||||
->label(__('feedback.view_attachments'))
|
||||
->icon('heroicon-o-paper-clip')
|
||||
->modalHeading('Attachments')
|
||||
->modalHeading(__('app.attachments'))
|
||||
->modalContent(function (FeedbackInteraction $record): \Illuminate\Contracts\View\View {
|
||||
$fileService = App::make(FileService::class);
|
||||
$attachments = $record->attachments;
|
||||
@@ -155,7 +155,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->label('Add Interaction')
|
||||
->label(__('feedback.add_interaction'))
|
||||
->icon('heroicon-o-plus')
|
||||
->mutateFormDataUsing(function (array $data, $livewire): array {
|
||||
$data['user_id'] = auth()->id();
|
||||
@@ -177,8 +177,8 @@ class InteractionsRelationManager extends RelationManager
|
||||
|
||||
if ($data['type'] === 'assignment' && isset($data['new_assignee'])) {
|
||||
$feedback = $livewire->getOwnerRecord();
|
||||
$oldAssignee = $feedback->assignedTo?->name ?? 'Unassigned';
|
||||
$newAssignee = User::find($data['new_assignee'])?->name ?? 'Unknown';
|
||||
$oldAssignee = $feedback->assignedTo?->name ?? __('app.unassigned');
|
||||
$newAssignee = User::find($data['new_assignee'])?->name ?? __('app.widget_unknown');
|
||||
$data['changes'] = [
|
||||
'assignment' => ['old' => $oldAssignee, 'new' => $newAssignee],
|
||||
];
|
||||
@@ -221,13 +221,13 @@ class InteractionsRelationManager extends RelationManager
|
||||
|
||||
if ($record->type === 'assignment') {
|
||||
Notification::make()
|
||||
->title('Feedback assigned successfully')
|
||||
->title(__('feedback.assigned_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
if ($record->type === 'status_change') {
|
||||
Notification::make()
|
||||
->title('Status updated successfully')
|
||||
->title(__('feedback.status_updated'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ class FeedbackForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Customer & Product')
|
||||
->description('Select the customer and their associated product')
|
||||
Section::make(__('app.customer_product'))
|
||||
->description(__('app.select_customer_product'))
|
||||
->icon('heroicon-o-user-group')
|
||||
->schema([
|
||||
Select::make('customer_id')
|
||||
@@ -32,13 +32,13 @@ class FeedbackForm
|
||||
->columnSpan(1),
|
||||
|
||||
Toggle::make('is_general')
|
||||
->label('General feedback (not related to a product)')
|
||||
->label(__('app.general_feedback'))
|
||||
->default(false)
|
||||
->live()
|
||||
->columnSpan(1),
|
||||
|
||||
Select::make('customer_product_id')
|
||||
->label('Product')
|
||||
->label(__('app.product'))
|
||||
->searchable()
|
||||
->getSearchResultsUsing(function (string $search, $get): array {
|
||||
$customerId = $get('customer_id');
|
||||
@@ -62,7 +62,7 @@ class FeedbackForm
|
||||
->live(),
|
||||
|
||||
Select::make('contract_id')
|
||||
->label('Contract')
|
||||
->label(__('app.contract'))
|
||||
->visible(fn ($get): bool => ! $get('is_general') && filled($get('customer_product_id')))
|
||||
->options(function ($get): array {
|
||||
$customerProductId = $get('customer_product_id');
|
||||
@@ -110,8 +110,8 @@ class FeedbackForm
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Feedback Details')
|
||||
->description('Describe the customer feedback')
|
||||
Section::make(__('app.feedback_details'))
|
||||
->description(__('app.describe_feedback'))
|
||||
->icon('heroicon-o-chat-bubble-left-right')
|
||||
->schema([
|
||||
Select::make('feedback_channel_id')
|
||||
@@ -143,16 +143,16 @@ class FeedbackForm
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Assignment & Status')
|
||||
->description('Assign handler and set initial status')
|
||||
Section::make(__('app.assignment_status'))
|
||||
->description(__('app.assign_handler'))
|
||||
->icon('heroicon-o-arrow-path-rounded-square')
|
||||
->schema([
|
||||
Select::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'resolved' => 'Resolved',
|
||||
'closed' => 'Closed',
|
||||
'pending' => __('app.status_pending'),
|
||||
'processing' => __('app.status_processing'),
|
||||
'resolved' => __('app.status_resolved'),
|
||||
'closed' => __('app.status_closed'),
|
||||
])
|
||||
->default('pending')
|
||||
->required()
|
||||
@@ -165,35 +165,35 @@ class FeedbackForm
|
||||
->columnSpan(1),
|
||||
|
||||
Select::make('current_department_id')
|
||||
->label('Department')
|
||||
->label(__('app.department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->columnSpan(1),
|
||||
|
||||
Toggle::make('is_escalated')
|
||||
->label('Escalated')
|
||||
->label(__('app.escalated'))
|
||||
->visible(fn (): bool => auth()->user()?->hasPermissionTo('close-ticket') ?? false)
|
||||
->columnSpan(1),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Attachments')
|
||||
->description('Upload files related to this feedback')
|
||||
Section::make(__('app.attachments'))
|
||||
->description(__('app.upload_files'))
|
||||
->icon('heroicon-o-paper-clip')
|
||||
->schema([
|
||||
Select::make('attachment_collection')
|
||||
->label('Collection')
|
||||
->label(__('app.collection'))
|
||||
->options([
|
||||
'general' => 'General',
|
||||
'proof_of_resolution' => 'Proof of Resolution',
|
||||
'customer_evidence' => 'Customer Evidence',
|
||||
'general' => __('app.collection_general'),
|
||||
'proof_of_resolution' => __('app.collection_proof_of_resolution'),
|
||||
'customer_evidence' => __('app.collection_customer_evidence'),
|
||||
])
|
||||
->default('general')
|
||||
->columnSpan(1),
|
||||
|
||||
FileUpload::make('attachments')
|
||||
->label('Files')
|
||||
->label(__('app.files'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
|
||||
@@ -33,13 +33,13 @@ class FeedbackTable
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('customerProduct.product.name')
|
||||
->label('Product')
|
||||
->placeholder('General')
|
||||
->label(__('app.product'))
|
||||
->placeholder(__('app.general'))
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('contract.type')
|
||||
->label('Contract')
|
||||
->label(__('app.contract'))
|
||||
->badge()
|
||||
->formatStateUsing(fn (?string $state): string => $state ? \App\Enums\ContractType::from($state)->label() : '-')
|
||||
->color(fn (?string $state): string => match ($state) {
|
||||
@@ -52,7 +52,7 @@ class FeedbackTable
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('feedbackChannel.name')
|
||||
->label('Channel')
|
||||
->label(__('app.channel'))
|
||||
->badge()
|
||||
->color('gray'),
|
||||
|
||||
@@ -72,22 +72,22 @@ class FeedbackTable
|
||||
}),
|
||||
|
||||
TextColumn::make('currentDepartment.name')
|
||||
->label('Department')
|
||||
->label(__('app.department'))
|
||||
->badge()
|
||||
->color('info')
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('assignedTo.name')
|
||||
->label('Assigned To')
|
||||
->label(__('app.assigned_to'))
|
||||
->toggleable(),
|
||||
|
||||
IconColumn::make('is_escalated')
|
||||
->label('Escalated')
|
||||
->label(__('app.escalated'))
|
||||
->boolean()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->label(__('app.created'))
|
||||
->since()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
@@ -96,20 +96,20 @@ class FeedbackTable
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'resolved' => 'Resolved',
|
||||
'closed' => 'Closed',
|
||||
'pending' => __('app.status_pending'),
|
||||
'processing' => __('app.status_processing'),
|
||||
'resolved' => __('app.status_resolved'),
|
||||
'closed' => __('app.status_closed'),
|
||||
]),
|
||||
SelectFilter::make('feedback_channel_id')
|
||||
->relationship('feedbackChannel', 'name')
|
||||
->label('Channel'),
|
||||
->label(__('app.channel')),
|
||||
SelectFilter::make('current_department_id')
|
||||
->relationship('currentDepartment', 'name')
|
||||
->label('Department'),
|
||||
->label(__('app.department')),
|
||||
SelectFilter::make('assigned_to')
|
||||
->relationship('assignedTo', 'name')
|
||||
->label('Assigned To'),
|
||||
->label(__('app.assigned_to')),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
|
||||
@@ -20,13 +20,18 @@ class FeedbackChannelResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedInboxStack;
|
||||
|
||||
protected static ?string $pluralLabel = 'Channels';
|
||||
protected static ?string $pluralLabel = 'app.resource_channels';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_channels');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -17,18 +17,22 @@ class FeedbackChannelForm
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('app.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->label(__('app.slug'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
TextInput::make('icon')
|
||||
->label(__('app.icon'))
|
||||
->maxLength(255)
|
||||
->hint('Heroicon name or emoji'),
|
||||
->hint(__('app.icon_hint')),
|
||||
ColorPicker::make('color')
|
||||
->label('Color'),
|
||||
->label(__('app.color')),
|
||||
Toggle::make('is_active')
|
||||
->label(__('app.status_active'))
|
||||
->default(true),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
@@ -27,7 +27,7 @@ class FeedbackChannelsTable
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('color')
|
||||
->label('Color')
|
||||
->label(__('app.color'))
|
||||
->formatStateUsing(fn ($state) => sprintf(
|
||||
'<span style="display:inline-block;width:24px;height:24px;background:%s;border-radius:6px;border:1px solid #d1d5db;" title="%s"></span>',
|
||||
$state ?: '#6b7280',
|
||||
@@ -36,10 +36,10 @@ class FeedbackChannelsTable
|
||||
->html(),
|
||||
IconColumn::make('is_active')
|
||||
->boolean()
|
||||
->label('Active'),
|
||||
->label(__('app.status_active')),
|
||||
TextColumn::make('feedbacks_count')
|
||||
->counts('feedbacks')
|
||||
->label('Feedbacks'),
|
||||
->label(__('app.feedbacks')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
@@ -20,11 +20,14 @@ class FeedbackTagResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedTag;
|
||||
|
||||
protected static ?string $pluralLabel = 'Tags';
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_tags');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -16,13 +16,15 @@ class FeedbackTagForm
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('app.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->label(__('app.slug'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
ColorPicker::make('color')
|
||||
->label('Color')
|
||||
->label(__('app.color'))
|
||||
->default('#3b82f6'),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
@@ -26,7 +26,7 @@ class FeedbackTagsTable
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('color')
|
||||
->label('Color')
|
||||
->label(__('app.color'))
|
||||
->formatStateUsing(fn ($state) => sprintf(
|
||||
'<span style="display:inline-block;width:24px;height:24px;background:%s;border-radius:6px;border:1px solid #d1d5db;" title="%s"></span>',
|
||||
$state ?: '#6b7280',
|
||||
@@ -35,7 +35,7 @@ class FeedbackTagsTable
|
||||
->html(),
|
||||
TextColumn::make('feedbacks_count')
|
||||
->counts('feedbacks')
|
||||
->label('Used'),
|
||||
->label(__('app.used')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
@@ -23,13 +23,18 @@ class ProductResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedBuildingStorefront;
|
||||
|
||||
protected static ?string $pluralLabel = 'Products';
|
||||
protected static ?string $pluralLabel = 'app.resource_products';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_products');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -15,7 +15,7 @@ class ContractsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'contracts';
|
||||
|
||||
protected static ?string $title = 'Contracts';
|
||||
protected static ?string $title = 'app.contracts';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
@@ -26,7 +26,7 @@ class ContractsRelationManager extends RelationManager
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state): string => ContractType::from($state)->label()),
|
||||
TextColumn::make('customer.name')
|
||||
->label('Customer')
|
||||
->label(__('app.widget_customer'))
|
||||
->searchable(),
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
@@ -44,10 +44,10 @@ class ContractsRelationManager extends RelationManager
|
||||
->date('d/m/Y')
|
||||
->sortable(),
|
||||
TextColumn::make('signed_status')
|
||||
->label('Signed'),
|
||||
->label(__('app.signed')),
|
||||
TextColumn::make('feedbacks_count')
|
||||
->counts('feedbacks')
|
||||
->label('Tickets'),
|
||||
->label(__('app.tickets')),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('type')
|
||||
|
||||
@@ -12,7 +12,7 @@ class HandoversRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'handovers';
|
||||
|
||||
protected static ?string $title = 'Handovers';
|
||||
protected static ?string $title = 'app.handovers';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
@@ -20,11 +20,11 @@ class HandoversRelationManager extends RelationManager
|
||||
->recordTitleAttribute('status')
|
||||
->columns([
|
||||
TextColumn::make('customer.name')
|
||||
->label('Customer')
|
||||
->label(__('app.widget_customer'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('handover_date')
|
||||
->label('Ngày BG')
|
||||
->label(__('app.handover_date'))
|
||||
->date('d/m/Y')
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
@@ -38,7 +38,7 @@ class HandoversRelationManager extends RelationManager
|
||||
})
|
||||
->formatStateUsing(fn (string $state): string => HandoverStatus::from($state)->label()),
|
||||
TextColumn::make('remark')
|
||||
->label('Ghi chú')
|
||||
->label(__('app.remark'))
|
||||
->limit(50)
|
||||
->toggleable(),
|
||||
TextColumn::make('created_at')
|
||||
|
||||
@@ -12,7 +12,7 @@ class ProductServicesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'productServices';
|
||||
|
||||
protected static ?string $title = 'Services';
|
||||
protected static ?string $title = 'app.services';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
@@ -38,11 +38,11 @@ class ProductServicesRelationManager extends RelationManager
|
||||
default => 'gray',
|
||||
}),
|
||||
TextColumn::make('actual_collection_date')
|
||||
->label('Ngày thu')
|
||||
->label(__('app.collection_date'))
|
||||
->date('d/m/Y')
|
||||
->sortable(),
|
||||
TextColumn::make('remark')
|
||||
->label('Ghi chú')
|
||||
->label(__('app.remark'))
|
||||
->limit(50)
|
||||
->toggleable(),
|
||||
TextColumn::make('created_at')
|
||||
@@ -55,10 +55,10 @@ class ProductServicesRelationManager extends RelationManager
|
||||
->options(ServiceType::MANAGEMENT_FEE->options()),
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'active' => 'Active',
|
||||
'pending' => 'Pending',
|
||||
'completed' => 'Completed',
|
||||
'cancelled' => 'Cancelled',
|
||||
'active' => __('app.status_active'),
|
||||
'pending' => __('app.status_pending'),
|
||||
'completed' => __('app.status_completed'),
|
||||
'cancelled' => __('app.status_cancelled'),
|
||||
]),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
|
||||
@@ -17,15 +17,17 @@ class ProductForm
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('app.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Textarea::make('description')
|
||||
->label(__('app.description'))
|
||||
->columnSpanFull(),
|
||||
Select::make('status')
|
||||
->options([
|
||||
'active' => 'Active',
|
||||
'inactive' => 'Inactive',
|
||||
'sold_out' => 'Sold Out',
|
||||
'active' => __('app.status_active'),
|
||||
'inactive' => __('app.status_inactive'),
|
||||
'sold_out' => __('app.status_sold_out'),
|
||||
])
|
||||
->default('active')
|
||||
->required(),
|
||||
|
||||
@@ -31,7 +31,7 @@ class ProductsTable
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('customers_count')
|
||||
->counts('customers')
|
||||
->label('Owners'),
|
||||
->label(__('app.owners')),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
@@ -40,9 +40,9 @@ class ProductsTable
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'active' => 'Active',
|
||||
'inactive' => 'Inactive',
|
||||
'sold_out' => 'Sold Out',
|
||||
'active' => __('app.status_active'),
|
||||
'inactive' => __('app.status_inactive'),
|
||||
'sold_out' => __('app.status_sold_out'),
|
||||
]),
|
||||
])
|
||||
->recordActions([
|
||||
|
||||
@@ -20,15 +20,20 @@ class RoleResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedShieldCheck;
|
||||
|
||||
protected static ?string $pluralLabel = 'Roles';
|
||||
protected static ?string $pluralLabel = 'app.resource_roles';
|
||||
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_roles');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -17,14 +17,14 @@ class RoleForm
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Role Name')
|
||||
->label(__('app.role_name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
]),
|
||||
|
||||
Section::make('Permissions')
|
||||
->description('Chọn quyền cho role này')
|
||||
Section::make(__('app.permissions'))
|
||||
->description(__('app.permissions'))
|
||||
->schema([
|
||||
CheckboxList::make('permissions')
|
||||
->label('')
|
||||
|
||||
@@ -19,7 +19,7 @@ class RolesTable
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('name')
|
||||
->label('Role Name')
|
||||
->label(__('app.role_name'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->badge()
|
||||
@@ -31,13 +31,13 @@ class RolesTable
|
||||
}),
|
||||
|
||||
TextColumn::make('permissions_count')
|
||||
->label('Permissions')
|
||||
->label(__('app.permissions'))
|
||||
->counts('permissions')
|
||||
->badge()
|
||||
->color('primary'),
|
||||
|
||||
TextColumn::make('users_count')
|
||||
->label('Users')
|
||||
->label(__('app.resource_users'))
|
||||
->counts('users')
|
||||
->badge()
|
||||
->color('success'),
|
||||
|
||||
@@ -22,14 +22,18 @@ class ListUsers extends ListRecords
|
||||
public function getTabs(): array
|
||||
{
|
||||
return [
|
||||
'all' => Tab::make(),
|
||||
'all' => Tab::make()
|
||||
->label(__('app.tab_all')),
|
||||
'admin' => Tab::make()
|
||||
->label('Admin')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query->where('role', 'admin'))
|
||||
->badge(fn () => static::getResource()::getModel()::where('role', 'admin')->count()),
|
||||
'manager' => Tab::make()
|
||||
->label('Manager')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query->where('role', 'manager'))
|
||||
->badge(fn () => static::getResource()::getModel()::where('role', 'manager')->count()),
|
||||
'staff' => Tab::make()
|
||||
->label('Staff')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query->where('role', 'staff'))
|
||||
->badge(fn () => static::getResource()::getModel()::where('role', 'staff')->count()),
|
||||
];
|
||||
|
||||
@@ -14,16 +14,19 @@ class UserForm
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('app.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('email')
|
||||
->label(__('app.email'))
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
TextInput::make('password')
|
||||
->label(__('app.password'))
|
||||
->password()
|
||||
->revealable()
|
||||
->required(fn (string $operation): bool => $operation === 'create')
|
||||
@@ -33,7 +36,7 @@ class UserForm
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('role')
|
||||
->label('Role')
|
||||
->label(__('app.role'))
|
||||
->options(Role::pluck('name', 'name')->toArray())
|
||||
->required()
|
||||
->searchable(),
|
||||
|
||||
@@ -20,15 +20,20 @@ class UserResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
|
||||
|
||||
protected static ?string $pluralLabel = 'Users';
|
||||
protected static ?string $pluralLabel = 'app.resource_users';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_users');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Management';
|
||||
return __('app.nav_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -35,29 +35,29 @@ class AvgProcessingTimeWidget extends BaseWidget
|
||||
|
||||
$avgDisplay = $avgMinutes ? round($avgMinutes) . ' min' : 'N/A';
|
||||
$avgDescription = $avgMinutes
|
||||
? 'Avg time from creation to resolution'
|
||||
: 'No resolved tickets yet';
|
||||
? __('app.widget_avg_time_desc')
|
||||
: __('app.widget_no_resolved_tickets');
|
||||
|
||||
return [
|
||||
Stat::make('Resolved Tickets', (string) $totalResolved)
|
||||
->description('Total resolved/closed tickets')
|
||||
Stat::make(__('app.widget_resolved_tickets'), (string) $totalResolved)
|
||||
->description(__('app.widget_total_resolved_closed'))
|
||||
->descriptionIcon('heroicon-m-check-circle')
|
||||
->color('success')
|
||||
->chart([7, 3, 4, 5, 6, 8, $totalResolved]),
|
||||
|
||||
Stat::make('Avg Processing Time', $avgDisplay)
|
||||
Stat::make(__('app.widget_avg_processing_time'), $avgDisplay)
|
||||
->description($avgDescription)
|
||||
->descriptionIcon('heroicon-m-clock')
|
||||
->color('info'),
|
||||
|
||||
Stat::make('Pending Tickets', (string) $totalPending)
|
||||
->description('Still in progress')
|
||||
Stat::make(__('app.widget_pending_tickets'), (string) $totalPending)
|
||||
->description(__('app.widget_still_in_progress'))
|
||||
->descriptionIcon('heroicon-m-arrow-path')
|
||||
->color('warning')
|
||||
->chart([3, 5, 4, 6, 5, 4, $totalPending]),
|
||||
|
||||
Stat::make('Escalated', (string) $escalatedCount)
|
||||
->description('Tickets marked as escalated')
|
||||
Stat::make(__('app.widget_escalated'), (string) $escalatedCount)
|
||||
->description(__('app.widget_escalated_desc'))
|
||||
->descriptionIcon('heroicon-m-exclamation-triangle')
|
||||
->color('danger'),
|
||||
];
|
||||
|
||||
@@ -5,15 +5,22 @@ namespace App\Filament\Widgets;
|
||||
use App\Models\Department;
|
||||
use App\Models\Feedback;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class HandlerPerformanceWidget extends ChartWidget
|
||||
{
|
||||
protected ?string $heading = 'Avg Processing Time by Handler';
|
||||
|
||||
protected ?string $description = 'Average time from ticket creation to resolution, broken down by handler.';
|
||||
|
||||
protected int|string|array $columnSpan = 'full';
|
||||
|
||||
public function getHeading(): string | Htmlable | null
|
||||
{
|
||||
return __('app.widget_avg_time_by_handler');
|
||||
}
|
||||
|
||||
public function getDescription(): string | Htmlable | null
|
||||
{
|
||||
return __('app.widget_avg_time_by_handler_desc');
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
@@ -39,19 +46,18 @@ class HandlerPerformanceWidget extends ChartWidget
|
||||
->with('assignedTo')
|
||||
->get();
|
||||
|
||||
$labels = $handlers->map(fn ($h) => $h->assignedTo?->name ?? 'Unknown')->toArray();
|
||||
$labels = $handlers->map(fn ($h) => $h->assignedTo?->name ?? __('app.widget_unknown'))->toArray();
|
||||
$data = $handlers->map(fn ($h) => round($h->avg_minutes, 1))->toArray();
|
||||
|
||||
// Generate gradient colors based on performance
|
||||
$maxMinutes = max($data) ?: 1;
|
||||
$colors = collect($data)->map(function ($minutes) use ($maxMinutes) {
|
||||
$ratio = $minutes / $maxMinutes;
|
||||
if ($ratio > 0.8) {
|
||||
return 'rgba(220, 38, 38, 0.8)'; // Red for slow
|
||||
return 'rgba(220, 38, 38, 0.8)';
|
||||
} elseif ($ratio > 0.5) {
|
||||
return 'rgba(217, 119, 6, 0.8)'; // Amber for medium
|
||||
return 'rgba(217, 119, 6, 0.8)';
|
||||
}
|
||||
return 'rgba(26, 86, 219, 0.8)'; // Blue for fast
|
||||
return 'rgba(26, 86, 219, 0.8)';
|
||||
})->toArray();
|
||||
|
||||
$borderColors = collect($data)->map(function ($minutes) use ($maxMinutes) {
|
||||
@@ -67,7 +73,7 @@ class HandlerPerformanceWidget extends ChartWidget
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Avg Time (minutes)',
|
||||
'label' => __('app.widget_avg_time_minutes'),
|
||||
'data' => $data,
|
||||
'backgroundColor' => $colors,
|
||||
'borderColor' => $borderColors,
|
||||
|
||||
@@ -35,37 +35,37 @@ class ResolvedTicketsWidget extends BaseWidget
|
||||
->color('primary'),
|
||||
|
||||
TextColumn::make('title')
|
||||
->label('Ticket')
|
||||
->label(__('app.widget_ticket'))
|
||||
->searchable()
|
||||
->weight('semibold')
|
||||
->url(fn (Feedback $record): string => FeedbackResource::getUrl('edit', ['record' => $record]))
|
||||
->color('primary'),
|
||||
|
||||
TextColumn::make('customer.name')
|
||||
->label('Customer')
|
||||
->label(__('app.widget_customer'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('customerProduct.product.name')
|
||||
->label('Product')
|
||||
->placeholder('General'),
|
||||
->label(__('app.product'))
|
||||
->placeholder(__('app.general')),
|
||||
|
||||
TextColumn::make('assignedTo.name')
|
||||
->label('Handler'),
|
||||
->label(__('app.widget_handler')),
|
||||
|
||||
TextColumn::make('currentDepartment.name')
|
||||
->label('Department')
|
||||
->label(__('app.department'))
|
||||
->badge()
|
||||
->color('info'),
|
||||
|
||||
TextColumn::make('updated_at')
|
||||
->label('Resolved')
|
||||
->label(__('app.widget_resolved'))
|
||||
->since()
|
||||
->sortable()
|
||||
->color('secondary'),
|
||||
])
|
||||
->defaultSort('updated_at', 'desc')
|
||||
->heading('Tickets Awaiting Closure')
|
||||
->description('Tickets that have been resolved and are pending manager review for closure.')
|
||||
->heading(__('app.widget_tickets_awaiting_closure'))
|
||||
->description(__('app.widget_tickets_awaiting_desc'))
|
||||
->paginated([5]);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ class TicketClosed extends Notification
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(__('Ticket #{id} đã được đóng', ['id' => $this->feedback->id]))
|
||||
->line(__('Ticket: :title', ['title' => $this->feedback->title]))
|
||||
->line(__('Đóng bởi: :name', ['name' => $this->actor->name]))
|
||||
->action(__('Xem Ticket'), url('/admin/feedbacks/' . $this->feedback->id . '/edit'));
|
||||
->subject(__('feedback.notif_ticket_closed_subject', ['id' => $this->feedback->id]))
|
||||
->line(__('feedback.notif_ticket_closed_ticket', ['title' => $this->feedback->title]))
|
||||
->line(__('feedback.notif_ticket_closed_by', ['name' => $this->actor->name]))
|
||||
->action(__('feedback.notif_view_ticket'), url('/admin/feedbacks/' . $this->feedback->id . '/edit'));
|
||||
}
|
||||
|
||||
public function toDatabase(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'message' => __('Ticket #{id} ":title" đã được đóng bởi :actor.', [
|
||||
'message' => __('feedback.notif_ticket_closed_message', [
|
||||
'id' => $this->feedback->id,
|
||||
'title' => $this->feedback->title,
|
||||
'actor' => $this->actor->name,
|
||||
|
||||
@@ -27,17 +27,17 @@ class TicketTransferred extends Notification
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(__('Ticket #{id} đã được chuyển đến phòng của bạn', ['id' => $this->feedback->id]))
|
||||
->line(__('Ticket: :title', ['title' => $this->feedback->title]))
|
||||
->line(__('Người chuyển: :name', ['name' => $this->sender->name]))
|
||||
->line(__('Lý do: :reason', ['reason' => $this->transferLog->reason]))
|
||||
->action(__('Xem Ticket'), url('/admin/feedbacks/' . $this->feedback->id . '/edit'));
|
||||
->subject(__('feedback.notif_ticket_transferred_subject', ['id' => $this->feedback->id]))
|
||||
->line(__('feedback.notif_ticket_closed_ticket', ['title' => $this->feedback->title]))
|
||||
->line(__('feedback.notif_ticket_transferred_by', ['name' => $this->sender->name]))
|
||||
->line(__('feedback.notif_ticket_transferred_reason', ['reason' => $this->transferLog->reason]))
|
||||
->action(__('feedback.notif_view_ticket'), url('/admin/feedbacks/' . $this->feedback->id . '/edit'));
|
||||
}
|
||||
|
||||
public function toDatabase(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'message' => __('Ticket #{id} ":title" đã được chuyển đến phòng của bạn bởi :sender.', [
|
||||
'message' => __('feedback.notif_ticket_transferred_message', [
|
||||
'id' => $this->feedback->id,
|
||||
'title' => $this->feedback->title,
|
||||
'sender' => $this->sender->name,
|
||||
|
||||
@@ -56,10 +56,10 @@ class AdminPanelProvider extends PanelProvider
|
||||
])
|
||||
->navigationGroups([
|
||||
NavigationGroup::make()
|
||||
->label('Customer Care')
|
||||
->label(__('app.nav_customer_care'))
|
||||
->icon('heroicon-o-chat-bubble-left-right'),
|
||||
NavigationGroup::make()
|
||||
->label('Management')
|
||||
->label(__('app.nav_management'))
|
||||
->icon('heroicon-o-cog-6-tooth'),
|
||||
])
|
||||
->middleware([
|
||||
|
||||
@@ -26,7 +26,7 @@ class RequireProofOfResolution implements ValidationRule
|
||||
$fileService = App::make(FileService::class);
|
||||
|
||||
if (! $fileService->hasCollection($this->feedback, 'proof_of_resolution')) {
|
||||
$fail(__('Yêu cầu cung cấp tài liệu bằng chứng để hoàn tất quy trình.'));
|
||||
$fail(__('feedback.proof_required'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,25 +11,22 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class ClosingService
|
||||
{
|
||||
/**
|
||||
* Close a feedback. Throws if staff tries to close or no proof exists.
|
||||
*/
|
||||
public function close(Feedback $feedback, User $actor): void
|
||||
{
|
||||
if ($feedback->status === 'closed') {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => __('Phiếu này đã được đóng.'),
|
||||
'status' => __('feedback.already_closed'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $actor->hasPermissionTo('close-ticket')) {
|
||||
throw new HttpException(403, __('Chỉ lãnh đạo cấp phòng mới có quyền đóng phiếu.'));
|
||||
throw new HttpException(403, __('feedback.close_permission_denied'));
|
||||
}
|
||||
|
||||
if ($actor->hasRole('manager') && ! $actor->hasRole('admin')) {
|
||||
$managedDeptIds = Department::where('manager_id', $actor->id)->pluck('id');
|
||||
if (! $managedDeptIds->contains($feedback->current_department_id)) {
|
||||
throw new HttpException(403, __('Bạn chỉ có thể đóng phiếu thuộc phòng ban mình quản lý.'));
|
||||
throw new HttpException(403, __('feedback.close_department_only'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +34,7 @@ class ClosingService
|
||||
|
||||
if (! $fileService->hasCollection($feedback, 'proof_of_resolution')) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => __('Yêu cầu cung cấp tài liệu bằng chứng để hoàn tất quy trình.'),
|
||||
'status' => __('feedback.proof_required'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -46,14 +43,11 @@ class ClosingService
|
||||
$this->notifyStakeholders($feedback, $actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a feedback. Any role can resolve.
|
||||
*/
|
||||
public function resolve(Feedback $feedback, User $actor): void
|
||||
{
|
||||
if (in_array($feedback->status, ['closed', 'resolved'])) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => __('Phiếu này đã được xử lý hoặc đóng.'),
|
||||
'status' => __('feedback.already_resolved'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,11 +126,11 @@ class FileService
|
||||
$errors = [];
|
||||
|
||||
if (! in_array($file->getMimeType(), $this->allowedMimes)) {
|
||||
$errors['file'] = __('File type :type is not allowed. Allowed: jpg, png, pdf, mp4, mov.', ['type' => $file->getMimeType()]);
|
||||
$errors['file'] = __('feedback.file_type_not_allowed', ['type' => $file->getMimeType()]);
|
||||
}
|
||||
|
||||
if ($file->getSize() > $this->maxSize * 1024) {
|
||||
$errors['file'] = __('File size exceeds :max KB.', ['max' => $this->maxSize]);
|
||||
$errors['file'] = __('feedback.file_size_exceeds', ['max' => $this->maxSize]);
|
||||
}
|
||||
|
||||
if (! empty($errors)) {
|
||||
|
||||
@@ -10,9 +10,6 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
class TransferService
|
||||
{
|
||||
/**
|
||||
* Transfer a feedback to another department.
|
||||
*/
|
||||
public function transfer(
|
||||
Feedback $feedback,
|
||||
Department $toDepartment,
|
||||
@@ -22,7 +19,7 @@ class TransferService
|
||||
): TicketTransferLog {
|
||||
if (empty(trim($reason))) {
|
||||
throw ValidationException::withMessages([
|
||||
'reason' => __('Vui lòng nhập lý do chuyển tiếp.'),
|
||||
'reason' => __('feedback.transfer_reason_required'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
133
lang/en/app.php
Normal file
133
lang/en/app.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Navigation
|
||||
'nav_customer_care' => 'Customer Care',
|
||||
'nav_management' => 'Management',
|
||||
|
||||
// Resource labels
|
||||
'resource_feedbacks' => 'Feedbacks',
|
||||
'resource_products' => 'Products',
|
||||
'resource_customers' => 'Customers',
|
||||
'resource_contracts' => 'Contracts',
|
||||
'resource_channels' => 'Channels',
|
||||
'resource_tags' => 'Tags',
|
||||
'resource_roles' => 'Roles',
|
||||
'resource_users' => 'Users',
|
||||
|
||||
// Status
|
||||
'status_pending' => 'Pending',
|
||||
'status_processing' => 'Processing',
|
||||
'status_resolved' => 'Resolved',
|
||||
'status_closed' => 'Closed',
|
||||
'status_active' => 'Active',
|
||||
'status_inactive' => 'Inactive',
|
||||
'status_sold_out' => 'Sold Out',
|
||||
'status_completed' => 'Completed',
|
||||
'status_cancelled' => 'Cancelled',
|
||||
|
||||
// General labels
|
||||
'name' => 'Name',
|
||||
'email' => 'Email',
|
||||
'password' => 'Password',
|
||||
'phone' => 'Phone',
|
||||
'address' => 'Address',
|
||||
'slug' => 'Slug',
|
||||
'icon' => 'Icon',
|
||||
'description' => 'Description',
|
||||
'status' => 'Status',
|
||||
'customer_product' => 'Customer & Product',
|
||||
'select_customer_product' => 'Select the customer and their associated product',
|
||||
'general_feedback' => 'General feedback (not related to a product)',
|
||||
'product' => 'Product',
|
||||
'contract' => 'Contract',
|
||||
'feedback_details' => 'Feedback Details',
|
||||
'describe_feedback' => 'Describe the customer feedback',
|
||||
'assignment_status' => 'Assignment & Status',
|
||||
'assign_handler' => 'Assign handler and set initial status',
|
||||
'department' => 'Department',
|
||||
'escalated' => 'Escalated',
|
||||
'attachments' => 'Attachments',
|
||||
'upload_files' => 'Upload files related to this feedback',
|
||||
'collection' => 'Collection',
|
||||
'collection_general' => 'General',
|
||||
'collection_proof_of_resolution' => 'Proof of Resolution',
|
||||
'collection_customer_evidence' => 'Customer Evidence',
|
||||
'files' => 'Files',
|
||||
'channel' => 'Channel',
|
||||
'assigned_to' => 'Assigned To',
|
||||
'created' => 'Created',
|
||||
'general' => 'General',
|
||||
'new_status' => 'New Status',
|
||||
'assign_to' => 'Assign To',
|
||||
'target_department' => 'Target Department',
|
||||
'by' => 'By',
|
||||
'details' => 'Details',
|
||||
'unassigned' => 'Unassigned',
|
||||
'role' => 'Role',
|
||||
'role_name' => 'Role Name',
|
||||
'permissions' => 'Permissions',
|
||||
'color' => 'Color',
|
||||
'customer_information' => 'Customer Information',
|
||||
'owned_products' => 'Owned Products',
|
||||
'icon_hint' => 'Heroicon name or emoji',
|
||||
'feedbacks' => 'Feedbacks',
|
||||
'used' => 'Used',
|
||||
'owners' => 'Owners',
|
||||
'tickets' => 'Tickets',
|
||||
'signed' => 'Signed',
|
||||
'interactions' => 'Interactions',
|
||||
'services' => 'Services',
|
||||
'handovers' => 'Handovers',
|
||||
'contracts' => 'Contracts',
|
||||
'collection_date' => 'Collection Date',
|
||||
'remark' => 'Remark',
|
||||
'handover_date' => 'Handover Date',
|
||||
'start_date' => 'Start Date',
|
||||
'end_date' => 'End Date',
|
||||
'printed_at' => 'Print Date',
|
||||
'signed_status' => 'Signed Status',
|
||||
'contract_info' => 'Contract Information',
|
||||
'feedback_history' => 'Feedback History',
|
||||
'tab_all' => 'All',
|
||||
'min' => 'min',
|
||||
'na' => 'N/A',
|
||||
|
||||
// Widget labels
|
||||
'widget_tickets_awaiting_closure' => 'Tickets Awaiting Closure',
|
||||
'widget_tickets_awaiting_desc' => 'Tickets that have been resolved and are pending manager review for closure.',
|
||||
'widget_ticket' => 'Ticket',
|
||||
'widget_customer' => 'Customer',
|
||||
'widget_handler' => 'Handler',
|
||||
'widget_resolved' => 'Resolved',
|
||||
'widget_resolved_tickets' => 'Resolved Tickets',
|
||||
'widget_total_resolved_closed' => 'Total resolved/closed tickets',
|
||||
'widget_avg_processing_time' => 'Avg Processing Time',
|
||||
'widget_avg_time_desc' => 'Avg time from creation to resolution',
|
||||
'widget_no_resolved_tickets' => 'No resolved tickets yet',
|
||||
'widget_pending_tickets' => 'Pending Tickets',
|
||||
'widget_still_in_progress' => 'Still in progress',
|
||||
'widget_escalated' => 'Escalated',
|
||||
'widget_escalated_desc' => 'Tickets marked as escalated',
|
||||
'widget_avg_time_by_handler' => 'Avg Processing Time by Handler',
|
||||
'widget_avg_time_by_handler_desc' => 'Average time from ticket creation to resolution, broken down by handler.',
|
||||
'widget_avg_time_minutes' => 'Avg Time (minutes)',
|
||||
'widget_unknown' => 'Unknown',
|
||||
|
||||
// Blade views
|
||||
'blade_similar_cases' => 'Similar Cases',
|
||||
'blade_matching_tags' => 'Matching tags:',
|
||||
'blade_title' => 'Title',
|
||||
'blade_customer' => 'Customer',
|
||||
'blade_status' => 'Status',
|
||||
'blade_handler' => 'Handler',
|
||||
'blade_date' => 'Date',
|
||||
'blade_no_attachments' => 'No attachments.',
|
||||
'blade_proof_of_resolution' => 'Proof of Resolution',
|
||||
'blade_customer_evidence' => 'Customer Evidence',
|
||||
'blade_open' => 'Open',
|
||||
'blade_links_expire' => 'Click to open. Links expire after 60 minutes.',
|
||||
'blade_file_count' => ':count file(s)',
|
||||
'blade_proof' => 'Proof',
|
||||
'blade_evidence' => 'Evidence',
|
||||
];
|
||||
28
lang/en/enums.php
Normal file
28
lang/en/enums.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'handover' => [
|
||||
'not_ready' => 'Not ready',
|
||||
'ready' => 'Ready',
|
||||
'handed_over' => 'Handed over',
|
||||
'scheduled' => 'Scheduled',
|
||||
],
|
||||
|
||||
'service' => [
|
||||
'management_fee' => 'Management fee',
|
||||
'gratitude' => 'Gratitude',
|
||||
'self_business' => 'Self business',
|
||||
],
|
||||
|
||||
'contract_status' => [
|
||||
'active' => 'Active',
|
||||
'expired' => 'Expired',
|
||||
'liquidated' => 'Liquidated',
|
||||
],
|
||||
|
||||
'contract_type' => [
|
||||
'sale' => 'Sale contract',
|
||||
'lease' => 'Lease contract',
|
||||
'bcc' => 'Business cooperation contract (BCC)',
|
||||
],
|
||||
];
|
||||
54
lang/en/feedback.php
Normal file
54
lang/en/feedback.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// ClosingService
|
||||
'already_closed' => 'This ticket has already been closed.',
|
||||
'close_permission_denied' => 'Only department managers can close tickets.',
|
||||
'close_department_only' => 'You can only close tickets in your managed department.',
|
||||
'proof_required' => 'Proof of resolution is required to complete the process.',
|
||||
'already_resolved' => 'This ticket has already been resolved or closed.',
|
||||
|
||||
// TransferService
|
||||
'transfer_reason_required' => 'Please enter a transfer reason.',
|
||||
|
||||
// FileService
|
||||
'file_type_not_allowed' => 'File type :type is not allowed. Allowed: jpg, png, pdf, mp4, mov.',
|
||||
'file_size_exceeds' => 'File size exceeds :max KB.',
|
||||
|
||||
// EditFeedback page
|
||||
'transfer_department' => 'Transfer Department',
|
||||
'target_department' => 'Target Department',
|
||||
'assign_to_optional' => 'Assign To (optional)',
|
||||
'reason' => 'Reason',
|
||||
'transferred_successfully' => 'Transferred successfully',
|
||||
'close_ticket' => 'Close Ticket',
|
||||
'close_confirm' => 'Are you sure you want to close this ticket? This action CANNOT be undone.',
|
||||
'close_submit' => 'Close',
|
||||
'closed_successfully' => 'Ticket closed successfully',
|
||||
|
||||
// InteractionsRelationManager
|
||||
'interaction_history' => 'Interaction History',
|
||||
'interaction_type_note' => 'Note',
|
||||
'interaction_type_status_change' => 'Status Change',
|
||||
'interaction_type_assignment' => 'Assignment / Forward',
|
||||
'view_attachments' => 'View Attachments',
|
||||
'add_interaction' => 'Add Interaction',
|
||||
'assigned_successfully' => 'Feedback assigned successfully',
|
||||
'status_updated' => 'Status updated successfully',
|
||||
|
||||
// Notifications
|
||||
'notif_ticket_closed_subject' => 'Ticket #:id has been closed',
|
||||
'notif_ticket_closed_ticket' => 'Ticket: :title',
|
||||
'notif_ticket_closed_by' => 'Closed by: :name',
|
||||
'notif_view_ticket' => 'View Ticket',
|
||||
'notif_ticket_closed_message' => 'Ticket #:id ":title" has been closed by :actor.',
|
||||
|
||||
'notif_ticket_transferred_subject' => 'Ticket #:id has been transferred to your department',
|
||||
'notif_ticket_transferred_by' => 'Transferred by: :name',
|
||||
'notif_ticket_transferred_reason' => 'Reason: :reason',
|
||||
'notif_ticket_transferred_message' => 'Ticket #:id ":title" has been transferred to your department by :sender.',
|
||||
|
||||
// CreateFeedback
|
||||
'created_via' => 'Feedback created via :channel',
|
||||
'unknown_channel' => 'unknown channel',
|
||||
];
|
||||
133
lang/vi/app.php
Normal file
133
lang/vi/app.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Navigation
|
||||
'nav_customer_care' => 'Chăm sóc khách hàng',
|
||||
'nav_management' => 'Quản lý',
|
||||
|
||||
// Resource labels
|
||||
'resource_feedbacks' => 'Phản ánh',
|
||||
'resource_products' => 'Sản phẩm',
|
||||
'resource_customers' => 'Khách hàng',
|
||||
'resource_contracts' => 'Hợp đồng',
|
||||
'resource_channels' => 'Kênh phản ánh',
|
||||
'resource_tags' => 'Thẻ',
|
||||
'resource_roles' => 'Vai trò',
|
||||
'resource_users' => 'Người dùng',
|
||||
|
||||
// Status
|
||||
'status_pending' => 'Chờ xử lý',
|
||||
'status_processing' => 'Đang xử lý',
|
||||
'status_resolved' => 'Đã xử lý',
|
||||
'status_closed' => 'Đã đóng',
|
||||
'status_active' => 'Hoạt động',
|
||||
'status_inactive' => 'Không hoạt động',
|
||||
'status_sold_out' => 'Hết hàng',
|
||||
'status_completed' => 'Hoàn thành',
|
||||
'status_cancelled' => 'Đã hủy',
|
||||
|
||||
// General labels
|
||||
'name' => 'Tên',
|
||||
'email' => 'Email',
|
||||
'password' => 'Mật khẩu',
|
||||
'phone' => 'Điện thoại',
|
||||
'address' => 'Địa chỉ',
|
||||
'slug' => 'Slug',
|
||||
'icon' => 'Biểu tượng',
|
||||
'description' => 'Mô tả',
|
||||
'status' => 'Trạng thái',
|
||||
'customer_product' => 'Khách hàng & Sản phẩm',
|
||||
'select_customer_product' => 'Chọn khách hàng và sản phẩm liên quan',
|
||||
'general_feedback' => 'Phản ánh chung (không liên quan đến sản phẩm)',
|
||||
'product' => 'Sản phẩm',
|
||||
'contract' => 'Hợp đồng',
|
||||
'feedback_details' => 'Chi tiết phản ánh',
|
||||
'describe_feedback' => 'Mô tả phản ánh của khách hàng',
|
||||
'assignment_status' => 'Phân công & Trạng thái',
|
||||
'assign_handler' => 'Phân công người xử lý và trạng thái ban đầu',
|
||||
'department' => 'Phòng ban',
|
||||
'escalated' => 'Khẩn cấp',
|
||||
'attachments' => 'Tệp đính kèm',
|
||||
'upload_files' => 'Tải lên tệp liên quan đến phản ánh này',
|
||||
'collection' => 'Loại',
|
||||
'collection_general' => 'Chung',
|
||||
'collection_proof_of_resolution' => 'Bằng chứng giải quyết',
|
||||
'collection_customer_evidence' => 'Bằng chứng khách hàng',
|
||||
'files' => 'Tệp',
|
||||
'channel' => 'Kênh',
|
||||
'assigned_to' => 'Người xử lý',
|
||||
'created' => 'Ngày tạo',
|
||||
'general' => 'Chung',
|
||||
'new_status' => 'Trạng thái mới',
|
||||
'assign_to' => 'Chỉ định cho',
|
||||
'target_department' => 'Phòng ban đích',
|
||||
'by' => 'Người thực hiện',
|
||||
'details' => 'Chi tiết',
|
||||
'unassigned' => 'Chưa phân công',
|
||||
'role' => 'Vai trò',
|
||||
'role_name' => 'Tên vai trò',
|
||||
'permissions' => 'Quyền',
|
||||
'color' => 'Màu sắc',
|
||||
'customer_information' => 'Thông tin khách hàng',
|
||||
'owned_products' => 'Sản phẩm sở hữu',
|
||||
'icon_hint' => 'Tên Heroicon hoặc emoji',
|
||||
'feedbacks' => 'Phản ánh',
|
||||
'used' => 'Đã dùng',
|
||||
'owners' => 'Chủ sở hữu',
|
||||
'tickets' => 'Phiếu',
|
||||
'signed' => 'Đã ký',
|
||||
'interactions' => 'Tương tác',
|
||||
'services' => 'Dịch vụ',
|
||||
'handovers' => 'Bàn giao',
|
||||
'contracts' => 'Hợp đồng',
|
||||
'collection_date' => 'Ngày thu',
|
||||
'remark' => 'Ghi chú',
|
||||
'handover_date' => 'Ngày bàn giao',
|
||||
'start_date' => 'Ngày bắt đầu',
|
||||
'end_date' => 'Ngày kết thúc',
|
||||
'printed_at' => 'Ngày in HĐ',
|
||||
'signed_status' => 'Tình trạng ký',
|
||||
'contract_info' => 'Thông tin hợp đồng',
|
||||
'feedback_history' => 'Lịch sử phản ánh',
|
||||
'tab_all' => 'Tất cả',
|
||||
'min' => 'phút',
|
||||
'na' => 'N/A',
|
||||
|
||||
// Widget labels
|
||||
'widget_tickets_awaiting_closure' => 'Phiếu chờ đóng',
|
||||
'widget_tickets_awaiting_desc' => 'Các phiếu đã xử lý và đang chờ quản lý xem xét để đóng.',
|
||||
'widget_ticket' => 'Phiếu',
|
||||
'widget_customer' => 'Khách hàng',
|
||||
'widget_handler' => 'Người xử lý',
|
||||
'widget_resolved' => 'Đã xử lý',
|
||||
'widget_resolved_tickets' => 'Phiếu đã xử lý',
|
||||
'widget_total_resolved_closed' => 'Tổng phiếu đã xử lý/đã đóng',
|
||||
'widget_avg_processing_time' => 'Thời gian xử lý TB',
|
||||
'widget_avg_time_desc' => 'Thời gian TB từ khi tạo đến khi xử lý',
|
||||
'widget_no_resolved_tickets' => 'Chưa có phiếu đã xử lý',
|
||||
'widget_pending_tickets' => 'Phiếu đang chờ',
|
||||
'widget_still_in_progress' => 'Đang xử lý',
|
||||
'widget_escalated' => 'Khẩn cấp',
|
||||
'widget_escalated_desc' => 'Phiếu được đánh dấu khẩn cấp',
|
||||
'widget_avg_time_by_handler' => 'Thời gian xử lý TB theo người xử lý',
|
||||
'widget_avg_time_by_handler_desc' => 'Thời gian trung bình từ khi tạo phiếu đến khi xử lý, phân theo người xử lý.',
|
||||
'widget_avg_time_minutes' => 'Thời gian TB (phút)',
|
||||
'widget_unknown' => 'Không xác định',
|
||||
|
||||
// Blade views
|
||||
'blade_similar_cases' => 'Trường hợp tương tự',
|
||||
'blade_matching_tags' => 'Thẻ trùng khớp:',
|
||||
'blade_title' => 'Tiêu đề',
|
||||
'blade_customer' => 'Khách hàng',
|
||||
'blade_status' => 'Trạng thái',
|
||||
'blade_handler' => 'Người xử lý',
|
||||
'blade_date' => 'Ngày',
|
||||
'blade_no_attachments' => 'Không có tệp đính kèm.',
|
||||
'blade_proof_of_resolution' => 'Bằng chứng giải quyết',
|
||||
'blade_customer_evidence' => 'Bằng chứng khách hàng',
|
||||
'blade_open' => 'Mở',
|
||||
'blade_links_expire' => 'Nhấn để mở. Liên kết hết hạn sau 60 phút.',
|
||||
'blade_file_count' => ':count tệp',
|
||||
'blade_proof' => 'Bằng chứng',
|
||||
'blade_evidence' => 'Chứng cứ',
|
||||
];
|
||||
28
lang/vi/enums.php
Normal file
28
lang/vi/enums.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'handover' => [
|
||||
'not_ready' => 'Chưa đủ điều kiện',
|
||||
'ready' => 'Đủ điều kiện',
|
||||
'handed_over' => 'Đã bàn giao',
|
||||
'scheduled' => 'Đã lên lịch',
|
||||
],
|
||||
|
||||
'service' => [
|
||||
'management_fee' => 'Phí quản lý',
|
||||
'gratitude' => 'Tri ân',
|
||||
'self_business' => 'Tự doanh',
|
||||
],
|
||||
|
||||
'contract_status' => [
|
||||
'active' => 'Đang hiệu lực',
|
||||
'expired' => 'Hết hạn',
|
||||
'liquidated' => 'Đã thanh lý',
|
||||
],
|
||||
|
||||
'contract_type' => [
|
||||
'sale' => 'HĐ Mua bán',
|
||||
'lease' => 'HĐ Thuê',
|
||||
'bcc' => 'HĐ Hợp tác kinh doanh (BCC)',
|
||||
],
|
||||
];
|
||||
54
lang/vi/feedback.php
Normal file
54
lang/vi/feedback.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// ClosingService
|
||||
'already_closed' => 'Phiếu này đã được đóng.',
|
||||
'close_permission_denied' => 'Chỉ lãnh đạo cấp phòng mới có quyền đóng phiếu.',
|
||||
'close_department_only' => 'Bạn chỉ có thể đóng phiếu thuộc phòng ban mình quản lý.',
|
||||
'proof_required' => 'Yêu cầu cung cấp tài liệu bằng chứng để hoàn tất quy trình.',
|
||||
'already_resolved' => 'Phiếu này đã được xử lý hoặc đóng.',
|
||||
|
||||
// TransferService
|
||||
'transfer_reason_required' => 'Vui lòng nhập lý do chuyển tiếp.',
|
||||
|
||||
// FileService
|
||||
'file_type_not_allowed' => 'Loại file :type không được phép. Cho phép: jpg, png, pdf, mp4, mov.',
|
||||
'file_size_exceeds' => 'Kích thước file vượt quá :max KB.',
|
||||
|
||||
// EditFeedback page
|
||||
'transfer_department' => 'Chuyển phòng ban',
|
||||
'target_department' => 'Phòng ban đích',
|
||||
'assign_to_optional' => 'Chỉ định người xử lý (tùy chọn)',
|
||||
'reason' => 'Lý do',
|
||||
'transferred_successfully' => 'Chuyển tiếp thành công',
|
||||
'close_ticket' => 'Đóng phiếu',
|
||||
'close_confirm' => 'Bạn có chắc muốn đóng phiếu này? Hành động này KHÔNG thể hoàn tác.',
|
||||
'close_submit' => 'Đóng',
|
||||
'closed_successfully' => 'Đóng phiếu thành công',
|
||||
|
||||
// InteractionsRelationManager
|
||||
'interaction_history' => 'Lịch sử tương tác',
|
||||
'interaction_type_note' => 'Ghi chú',
|
||||
'interaction_type_status_change' => 'Thay đổi trạng thái',
|
||||
'interaction_type_assignment' => 'Phân công / Chuyển tiếp',
|
||||
'view_attachments' => 'Xem tệp đính kèm',
|
||||
'add_interaction' => 'Thêm tương tác',
|
||||
'assigned_successfully' => 'Phân công thành công',
|
||||
'status_updated' => 'Cập nhật trạng thái thành công',
|
||||
|
||||
// Notifications
|
||||
'notif_ticket_closed_subject' => 'Ticket #:id đã được đóng',
|
||||
'notif_ticket_closed_ticket' => 'Ticket: :title',
|
||||
'notif_ticket_closed_by' => 'Đóng bởi: :name',
|
||||
'notif_view_ticket' => 'Xem Ticket',
|
||||
'notif_ticket_closed_message' => 'Ticket #:id ":title" đã được đóng bởi :actor.',
|
||||
|
||||
'notif_ticket_transferred_subject' => 'Ticket #:id đã được chuyển đến phòng của bạn',
|
||||
'notif_ticket_transferred_by' => 'Người chuyển: :name',
|
||||
'notif_ticket_transferred_reason' => 'Lý do: :reason',
|
||||
'notif_ticket_transferred_message' => 'Ticket #:id ":title" đã được chuyển đến phòng của bạn bởi :sender.',
|
||||
|
||||
// CreateFeedback
|
||||
'created_via' => 'Phản ánh được tạo qua :channel',
|
||||
'unknown_channel' => 'kênh không xác định',
|
||||
];
|
||||
441
previews/dashboard.html
Normal file
441
previews/dashboard.html
Normal file
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AfterSales CRM — Dashboard Preview</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwindcss.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: { 50:'#eff6ff',100:'#dbeafe',200:'#bfdbfe',300:'#93c5fd',400:'#60a5fa',500:'#3b82f6',600:'#2563eb',700:'#1d4ed8',800:'#1e40af',900:'#1e3a8a' },
|
||||
brand: '#2563eb',
|
||||
},
|
||||
fontFamily: { sans: ['Inter','system-ui','sans-serif'] },
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
.stat-card { transition: all 0.2s; }
|
||||
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.1); }
|
||||
.trend-up { color: #10b981; }
|
||||
.trend-down { color: #ef4444; }
|
||||
.pulse-dot { animation: pulse 2s infinite; }
|
||||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
|
||||
.sidebar-link { transition: all 0.15s; }
|
||||
.sidebar-link:hover { background: #eff6ff; color: #2563eb; }
|
||||
.sidebar-link.active { background: #2563eb; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900">
|
||||
<div class="flex min-h-screen">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col">
|
||||
<!-- Logo -->
|
||||
<div class="h-16 flex items-center px-6 border-b border-gray-100">
|
||||
<div class="w-8 h-8 bg-brand rounded-lg flex items-center justify-center mr-3">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
||||
</div>
|
||||
<span class="font-bold text-lg text-gray-900">AfterSales</span>
|
||||
</div>
|
||||
|
||||
<!-- Nav -->
|
||||
<nav class="flex-1 px-3 py-4 space-y-1">
|
||||
<a href="#" class="sidebar-link active flex items-center px-3 py-2.5 rounded-lg text-sm font-medium">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a>
|
||||
|
||||
<div class="pt-4 pb-2 px-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Customer Care</p>
|
||||
</div>
|
||||
<a href="feedback-list.html" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
||||
Feedbacks
|
||||
<span class="ml-auto bg-red-100 text-red-700 text-xs font-semibold px-2 py-0.5 rounded-full">5</span>
|
||||
</a>
|
||||
|
||||
<div class="pt-4 pb-2 px-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Management</p>
|
||||
</div>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Products
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
Customers
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Contracts
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/></svg>
|
||||
Channels
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||
Tags
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- User -->
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
<div class="flex items-center">
|
||||
<div class="w-9 h-9 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
<span class="text-primary-700 font-semibold text-sm">AD</span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Admin User</p>
|
||||
<p class="text-xs text-gray-500">admin@minicrm.local</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<!-- Top Bar -->
|
||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-8">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-900">Dashboard</h1>
|
||||
<p class="text-sm text-gray-500">Welcome back, Admin</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="relative p-2 text-gray-400 hover:text-gray-600 transition">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
<span class="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full pulse-dot"></span>
|
||||
</button>
|
||||
<div class="w-8 h-8 bg-primary-600 rounded-full flex items-center justify-center">
|
||||
<span class="text-white font-semibold text-xs">AD</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
<div class="p-8 space-y-6">
|
||||
|
||||
<!-- Row 1: Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- Resolved -->
|
||||
<div class="stat-card bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Resolved Tickets</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">156</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-emerald-50 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center">
|
||||
<span class="trend-up text-sm font-medium flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/></svg>
|
||||
+12%
|
||||
</span>
|
||||
<span class="text-sm text-gray-500 ml-2">vs last week</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Avg Processing Time -->
|
||||
<div class="stat-card bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Avg Processing Time</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">45 <span class="text-lg font-medium text-gray-400">min</span></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-amber-50 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center">
|
||||
<span class="trend-down text-sm font-medium flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"/></svg>
|
||||
-8%
|
||||
</span>
|
||||
<span class="text-sm text-gray-500 ml-2">improved</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending -->
|
||||
<div class="stat-card bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Pending Tickets</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">23</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center">
|
||||
<span class="trend-down text-sm font-medium flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"/></svg>
|
||||
-3
|
||||
</span>
|
||||
<span class="text-sm text-gray-500 ml-2">from yesterday</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Escalated -->
|
||||
<div class="stat-card bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">Escalated</p>
|
||||
<p class="text-3xl font-bold text-red-600 mt-1">5</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-red-50 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center">
|
||||
<span class="trend-up text-sm font-medium flex items-center text-red-600">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/></svg>
|
||||
+2
|
||||
</span>
|
||||
<span class="text-sm text-gray-500 ml-2">needs attention</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Charts -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Handler Performance Chart -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">Handler Performance</h3>
|
||||
<p class="text-sm text-gray-500">Average processing time per handler</p>
|
||||
</div>
|
||||
<select class="text-sm border border-gray-200 rounded-lg px-3 py-1.5 text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<option>This Week</option>
|
||||
<option>This Month</option>
|
||||
<option>This Quarter</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-24 text-sm font-medium text-gray-700">Nguyễn A</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="h-8 bg-gray-100 rounded-lg overflow-hidden">
|
||||
<div class="h-full bg-emerald-500 rounded-lg flex items-center justify-end pr-3" style="width: 48%">
|
||||
<span class="text-xs font-semibold text-white">32 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-16 text-right">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-emerald-50 text-emerald-700">Fast</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-24 text-sm font-medium text-gray-700">Trần B</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="h-8 bg-gray-100 rounded-lg overflow-hidden">
|
||||
<div class="h-full bg-blue-500 rounded-lg flex items-center justify-end pr-3" style="width: 65%">
|
||||
<span class="text-xs font-semibold text-white">48 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-16 text-right">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700">Normal</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-24 text-sm font-medium text-gray-700">Lê C</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="h-8 bg-gray-100 rounded-lg overflow-hidden">
|
||||
<div class="h-full bg-amber-500 rounded-lg flex items-center justify-end pr-3" style="width: 85%">
|
||||
<span class="text-xs font-semibold text-white">65 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-16 text-right">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-50 text-amber-700">Slow</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-24 text-sm font-medium text-gray-700">Phạm D</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="h-8 bg-gray-100 rounded-lg overflow-hidden">
|
||||
<div class="h-full bg-emerald-500 rounded-lg flex items-center justify-end pr-3" style="width: 40%">
|
||||
<span class="text-xs font-semibold text-white">28 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-16 text-right">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-emerald-50 text-emerald-700">Fast</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-24 text-sm font-medium text-gray-700">Hoàng E</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="h-8 bg-gray-100 rounded-lg overflow-hidden">
|
||||
<div class="h-full bg-red-500 rounded-lg flex items-center justify-end pr-3" style="width: 95%">
|
||||
<span class="text-xs font-semibold text-white">82 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-16 text-right">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-50 text-red-700">Alert</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tickets by Status Donut -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">Tickets by Status</h3>
|
||||
<p class="text-sm text-gray-500">Current distribution</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<!-- SVG Donut Chart -->
|
||||
<div class="relative">
|
||||
<svg width="200" height="200" viewBox="0 0 200 200">
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="#fbbf24" stroke-width="32" stroke-dasharray="100.53 402.12" stroke-dashoffset="0" transform="rotate(-90 100 100)"/>
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="#3b82f6" stroke-width="32" stroke-dasharray="75.4 402.12" stroke-dashoffset="-100.53" transform="rotate(-90 100 100)"/>
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="#10b981" stroke-width="32" stroke-dasharray="150.8 402.12" stroke-dashoffset="-175.93" transform="rotate(-90 100 100)"/>
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="#9ca3af" stroke-width="32" stroke-dasharray="75.4 402.12" stroke-dashoffset="-326.73" transform="rotate(-90 100 100)"/>
|
||||
<text x="100" y="95" text-anchor="middle" class="text-3xl font-bold" fill="#111827">207</text>
|
||||
<text x="100" y="115" text-anchor="middle" class="text-sm" fill="#6b7280">Total</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-2 gap-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-3 h-3 rounded-full bg-amber-400 mr-2"></div>
|
||||
<span class="text-sm text-gray-600">Pending</span>
|
||||
<span class="ml-auto text-sm font-semibold text-gray-900">23</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-3 h-3 rounded-full bg-blue-500 mr-2"></div>
|
||||
<span class="text-sm text-gray-600">Processing</span>
|
||||
<span class="ml-auto text-sm font-semibold text-gray-900">18</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-3 h-3 rounded-full bg-emerald-500 mr-2"></div>
|
||||
<span class="text-sm text-gray-600">Resolved</span>
|
||||
<span class="ml-auto text-sm font-semibold text-gray-900">156</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-3 h-3 rounded-full bg-gray-400 mr-2"></div>
|
||||
<span class="text-sm text-gray-600">Closed</span>
|
||||
<span class="ml-auto text-sm font-semibold text-gray-900">10</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: Resolved Tickets Table -->
|
||||
<div class="bg-white rounded-xl border border-gray-200">
|
||||
<div class="px-6 py-5 border-b border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">Tickets Awaiting Closure</h3>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Resolved tickets pending manager review</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-amber-50 text-amber-700 border border-amber-200">
|
||||
4 tickets
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="bg-gray-50">
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">#</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Ticket</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Customer</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Handler</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Department</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Contract</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Resolved Date</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900">#001</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="edit-feedback.html" class="text-sm font-medium text-primary-600 hover:text-primary-800 hover:underline">Khách hàng phản ánh nước nóng</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">Nguyễn Văn A</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center mr-2"><span class="text-xs font-medium text-blue-700">S</span></div>
|
||||
<span class="text-sm text-gray-700">Staff User</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4"><span class="text-sm text-gray-700">CSKH</span></td>
|
||||
<td class="px-6 py-4"><span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-emerald-50 text-emerald-700 border border-emerald-200">SALE</span></td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">30/04/2026</td>
|
||||
<td class="px-6 py-4">
|
||||
<button class="inline-flex items-center px-3 py-1.5 bg-emerald-600 text-white text-xs font-medium rounded-lg hover:bg-emerald-700 transition">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Close
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900">#003</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="#" class="text-sm font-medium text-primary-600 hover:text-primary-800 hover:underline">Yêu cầu sửa chữa cửa sổ</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">Trần Thị B</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-purple-100 rounded-full flex items-center justify-center mr-2"><span class="text-xs font-medium text-purple-700">T</span></div>
|
||||
<span class="text-sm text-gray-700">Trần Staff</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4"><span class="text-sm text-gray-700">Kỹ thuật</span></td>
|
||||
<td class="px-6 py-4"><span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 border border-blue-200">LEASE</span></td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">29/04/2026</td>
|
||||
<td class="px-6 py-4">
|
||||
<button class="inline-flex items-center px-3 py-1.5 bg-emerald-600 text-white text-xs font-medium rounded-lg hover:bg-emerald-700 transition">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Close
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900">#005</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="#" class="text-sm font-medium text-primary-600 hover:text-primary-800 hover:underline">Hỗ trợ đăng ký cư trú</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">Lê Minh C</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-emerald-100 rounded-full flex items-center justify-center mr-2"><span class="text-xs font-medium text-emerald-700">L</span></div>
|
||||
<span class="text-sm text-gray-700">Lê Staff</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4"><span class="text-sm text-gray-700">Pháp lý</span></td>
|
||||
<td class="px-6 py-4"><span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-50 text-amber-700 border border-amber-200">BCC</span></td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">28/04/2026</td>
|
||||
<td class="px-6 py-4">
|
||||
<button class="inline-flex items-center px-3 py-1.5 bg-emerald-600 text-white text-xs font-medium rounded-lg hover:bg-emerald-700 transition">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Close
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
372
previews/feedback-list.html
Normal file
372
previews/feedback-list.html
Normal file
@@ -0,0 +1,372 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AfterSales CRM — Feedback List Preview</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwindcss.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: { 50:'#eff6ff',100:'#dbeafe',200:'#bfdbfe',300:'#93c5fd',400:'#60a5fa',500:'#3b82f6',600:'#2563eb',700:'#1d4ed8',800:'#1e40af',900:'#1e3a8a' },
|
||||
brand: '#2563eb',
|
||||
},
|
||||
fontFamily: { sans: ['Inter','system-ui','sans-serif'] },
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
.sidebar-link { transition: all 0.15s; }
|
||||
.sidebar-link:hover { background: #eff6ff; color: #2563eb; }
|
||||
.sidebar-link.active { background: #2563eb; color: white; }
|
||||
.row-pending { border-left: 3px solid #fbbf24; }
|
||||
.row-processing { border-left: 3px solid #3b82f6; }
|
||||
.row-resolved { border-left: 3px solid #10b981; }
|
||||
.row-closed { border-left: 3px solid #9ca3af; }
|
||||
.escalated-pulse { animation: pulse-red 2s infinite; }
|
||||
@keyframes pulse-red { 0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,0.4)} 50%{box-shadow:0 0 0 6px rgba(239,68,68,0)} }
|
||||
.tag { display:inline-flex; align-items:center; padding:2px 8px; border-radius:9999px; font-size:0.7rem; font-weight:600; }
|
||||
.filter-chip { transition: all 0.15s; cursor:pointer; }
|
||||
.filter-chip:hover { background: #eff6ff; }
|
||||
.filter-chip.active { background: #2563eb; color: white; border-color: #2563eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900">
|
||||
<div class="flex min-h-screen">
|
||||
|
||||
<!-- Sidebar (same as dashboard) -->
|
||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col">
|
||||
<div class="h-16 flex items-center px-6 border-b border-gray-100">
|
||||
<div class="w-8 h-8 bg-brand rounded-lg flex items-center justify-center mr-3">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
||||
</div>
|
||||
<span class="font-bold text-lg text-gray-900">AfterSales</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-3 py-4 space-y-1">
|
||||
<a href="dashboard.html" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a>
|
||||
<div class="pt-4 pb-2 px-3"><p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Customer Care</p></div>
|
||||
<a href="#" class="sidebar-link active flex items-center px-3 py-2.5 rounded-lg text-sm font-medium">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
||||
Feedbacks
|
||||
<span class="ml-auto bg-red-100 text-red-700 text-xs font-semibold px-2 py-0.5 rounded-full">5</span>
|
||||
</a>
|
||||
<div class="pt-4 pb-2 px-3"><p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Management</p></div>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Products
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
Customers
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Contracts
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/></svg>
|
||||
Channels
|
||||
</a>
|
||||
<a href="#" class="sidebar-link flex items-center px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||
Tags
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
<div class="flex items-center">
|
||||
<div class="w-9 h-9 bg-primary-100 rounded-full flex items-center justify-center"><span class="text-primary-700 font-semibold text-sm">AD</span></div>
|
||||
<div class="ml-3"><p class="text-sm font-medium text-gray-900">Admin User</p><p class="text-xs text-gray-500">admin@minicrm.local</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<!-- Top Bar -->
|
||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-8">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-900">Feedbacks</h1>
|
||||
<p class="text-sm text-gray-500">Manage customer feedback and tickets</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class="relative p-2 text-gray-400 hover:text-gray-600 transition">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
<span class="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full"></span>
|
||||
</button>
|
||||
<a href="#" class="inline-flex items-center px-4 py-2 bg-brand text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition shadow-sm">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||
New Feedback
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="p-8 space-y-5">
|
||||
|
||||
<!-- Quick Stats Bar -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<button class="filter-chip active bg-white border border-gray-200 rounded-xl px-4 py-3 text-center transition">
|
||||
<p class="text-2xl font-bold text-white">5</p>
|
||||
<p class="text-xs font-medium text-primary-100">All</p>
|
||||
</button>
|
||||
<button class="filter-chip bg-white border border-gray-200 rounded-xl px-4 py-3 text-center transition">
|
||||
<p class="text-2xl font-bold text-amber-600">2</p>
|
||||
<p class="text-xs font-medium text-gray-500">Pending</p>
|
||||
</button>
|
||||
<button class="filter-chip bg-white border border-gray-200 rounded-xl px-4 py-3 text-center transition">
|
||||
<p class="text-2xl font-bold text-blue-600">1</p>
|
||||
<p class="text-xs font-medium text-gray-500">Processing</p>
|
||||
</button>
|
||||
<button class="filter-chip bg-white border border-gray-200 rounded-xl px-4 py-3 text-center transition">
|
||||
<p class="text-2xl font-bold text-emerald-600">1</p>
|
||||
<p class="text-xs font-medium text-gray-500">Resolved</p>
|
||||
</button>
|
||||
<button class="filter-chip bg-white border border-gray-200 rounded-xl px-4 py-3 text-center transition">
|
||||
<p class="text-2xl font-bold text-gray-400">1</p>
|
||||
<p class="text-xs font-medium text-gray-500">Closed</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search & Filters -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Search -->
|
||||
<div class="flex-1 relative">
|
||||
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input type="text" placeholder="Search by title, customer, or ID..." class="w-full pl-10 pr-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent" value="">
|
||||
</div>
|
||||
<!-- Filter dropdowns -->
|
||||
<select class="border border-gray-200 rounded-lg px-3 py-2.5 text-sm text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<option>All Channels</option>
|
||||
<option>Email</option>
|
||||
<option>Zalo</option>
|
||||
<option>Phone</option>
|
||||
<option>Document</option>
|
||||
<option>In Person</option>
|
||||
</select>
|
||||
<select class="border border-gray-200 rounded-lg px-3 py-2.5 text-sm text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<option>All Departments</option>
|
||||
<option>CSKH</option>
|
||||
<option>Kỹ thuật</option>
|
||||
<option>Kế toán</option>
|
||||
<option>Pháp lý</option>
|
||||
</select>
|
||||
<select class="border border-gray-200 rounded-lg px-3 py-2.5 text-sm text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<option>All Handlers</option>
|
||||
<option>Staff User</option>
|
||||
<option>Manager User</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feedback Cards -->
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Card 1: Pending + Escalated -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 row-pending hover:shadow-md transition cursor-pointer overflow-hidden">
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-sm font-bold text-gray-400">#002</span>
|
||||
<h3 class="text-base font-semibold text-gray-900">Khách hàng phản ánh về chất lượng nước</h3>
|
||||
<span class="escalated-pulse inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold bg-red-100 text-red-700 border border-red-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
|
||||
ESCALATED
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Khách hàng phản ánh nước sinh hoạt có màu vàng, mùi lạ trong 3 ngày liên tiếp tại căn hộ A2-0805...</p>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<span class="tag bg-amber-50 text-amber-700 border border-amber-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2"/><path stroke-linecap="round" stroke-width="2" d="M12 6v6l4 2"/></svg>
|
||||
Pending
|
||||
</span>
|
||||
<span class="tag bg-gray-50 text-gray-600 border border-gray-200">Zalo</span>
|
||||
<span class="tag bg-blue-50 text-blue-700 border border-blue-200">CSKH</span>
|
||||
<span class="tag bg-emerald-50 text-emerald-700 border border-emerald-200">SALE</span>
|
||||
<span class="tag bg-purple-50 text-purple-700 border border-purple-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5"/></svg>
|
||||
Sunrise A2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-end gap-2">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Trần Thị B
|
||||
</div>
|
||||
<div class="flex items-center text-sm text-gray-400">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
28/04/2026
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-gray-200 rounded-full flex items-center justify-center mr-1"><span class="text-xs font-medium text-gray-600">?</span></div>
|
||||
<span class="text-xs text-gray-400">Unassigned</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Processing -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 row-processing hover:shadow-md transition cursor-pointer overflow-hidden">
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-sm font-bold text-gray-400">#001</span>
|
||||
<h3 class="text-base font-semibold text-gray-900">Khách hàng phản ánh hệ thống nước nóng không hoạt động</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Khách hàng phản ánh về việc hệ thống nước nóng không hoạt động trong căn hộ A1-1205. Đã kiểm tra nhưng chưa xác định được nguyên nhân...</p>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<span class="tag bg-blue-50 text-blue-700 border border-blue-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Processing
|
||||
</span>
|
||||
<span class="tag bg-gray-50 text-gray-600 border border-gray-200">Email</span>
|
||||
<span class="tag bg-blue-50 text-blue-700 border border-blue-200">Kỹ thuật</span>
|
||||
<span class="tag bg-emerald-50 text-emerald-700 border border-emerald-200">SALE</span>
|
||||
<span class="tag bg-purple-50 text-purple-700 border border-purple-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5"/></svg>
|
||||
Sunrise A1
|
||||
</span>
|
||||
<span class="tag bg-pink-50 text-pink-700 border border-pink-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||
Technical
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-end gap-2">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Nguyễn Văn A
|
||||
</div>
|
||||
<div class="flex items-center text-sm text-gray-400">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
30/04/2026
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center mr-1"><span class="text-xs font-medium text-blue-700">S</span></div>
|
||||
<span class="text-xs text-gray-600">Staff User</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Pending -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 row-pending hover:shadow-md transition cursor-pointer overflow-hidden">
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-sm font-bold text-gray-400">#004</span>
|
||||
<h3 class="text-base font-semibold text-gray-900">Yêu cầu hỗ trợ đăng ký cư trú</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Khách hàng cần hỗ trợ về thủ tục đăng ký cư trú cho căn hộ mới bàn giao...</p>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<span class="tag bg-amber-50 text-amber-700 border border-amber-200">Pending</span>
|
||||
<span class="tag bg-gray-50 text-gray-600 border border-gray-200">In Person</span>
|
||||
<span class="tag bg-violet-50 text-violet-700 border border-violet-200">Pháp lý</span>
|
||||
<span class="tag bg-amber-50 text-amber-700 border border-amber-200">BCC</span>
|
||||
<span class="tag bg-purple-50 text-purple-700 border border-purple-200">Green Valley</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-end gap-2">
|
||||
<div class="flex items-center text-sm text-gray-500">Lê Minh C</div>
|
||||
<div class="flex items-center text-sm text-gray-400">27/04/2026</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-gray-200 rounded-full flex items-center justify-center mr-1"><span class="text-xs font-medium text-gray-600">?</span></div>
|
||||
<span class="text-xs text-gray-400">Unassigned</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 4: Resolved -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 row-resolved hover:shadow-md transition cursor-pointer overflow-hidden">
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-sm font-bold text-gray-400">#003</span>
|
||||
<h3 class="text-base font-semibold text-gray-900">Yêu cầu sửa chữa hệ thống cửa sổ</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Cửa sổ phòng ngủ bị kẹt không đóng được. Đã cử kỹ thuật viên sửa chữa xong...</p>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<span class="tag bg-emerald-50 text-emerald-700 border border-emerald-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Resolved
|
||||
</span>
|
||||
<span class="tag bg-gray-50 text-gray-600 border border-gray-200">Phone</span>
|
||||
<span class="tag bg-blue-50 text-blue-700 border border-blue-200">Kỹ thuật</span>
|
||||
<span class="tag bg-blue-50 text-blue-700 border border-blue-200">LEASE</span>
|
||||
<span class="tag bg-purple-50 text-purple-700 border border-purple-200">Ocean Tower</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-end gap-2">
|
||||
<div class="flex items-center text-sm text-gray-500">Phạm Thị D</div>
|
||||
<div class="flex items-center text-sm text-gray-400">29/04/2026</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-purple-100 rounded-full flex items-center justify-center mr-1"><span class="text-xs font-medium text-purple-700">T</span></div>
|
||||
<span class="text-xs text-gray-600">Trần Staff</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 5: Closed -->
|
||||
<div class="bg-white rounded-xl border border-gray-200 row-closed hover:shadow-md transition cursor-pointer overflow-hidden opacity-75">
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-sm font-bold text-gray-400">#005</span>
|
||||
<h3 class="text-base font-semibold text-gray-700">Phản ánh về phí quản lý</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mb-3 line-clamp-2">Khách hàng thắc mắc về phí quản lý tháng 3/2026. Đã giải thích và khách hàng đồng ý...</p>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<span class="tag bg-gray-100 text-gray-500 border border-gray-200">Closed</span>
|
||||
<span class="tag bg-gray-50 text-gray-500 border border-gray-200">Email</span>
|
||||
<span class="tag bg-gray-50 text-gray-500 border border-gray-200">Kế toán</span>
|
||||
<span class="tag bg-gray-50 text-gray-500 border border-gray-200">SALE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-end gap-2">
|
||||
<div class="flex items-center text-sm text-gray-400">Hoàng Văn E</div>
|
||||
<div class="flex items-center text-sm text-gray-400">25/04/2026</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 bg-emerald-100 rounded-full flex items-center justify-center mr-1"><span class="text-xs font-medium text-emerald-700">L</span></div>
|
||||
<span class="text-xs text-gray-500">Lê Staff</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500">Showing <span class="font-medium">1-5</span> of <span class="font-medium">5</span> results</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<button class="px-3 py-2 text-sm text-gray-400 bg-white border border-gray-200 rounded-lg cursor-not-allowed">Previous</button>
|
||||
<button class="px-3 py-2 text-sm font-medium text-white bg-brand border border-brand rounded-lg">1</button>
|
||||
<button class="px-3 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="p-4">
|
||||
@if(empty($links) || count($links) === 0)
|
||||
<p class="text-sm text-gray-500 py-8 text-center">No attachments.</p>
|
||||
<p class="text-sm text-gray-500 py-8 text-center">{{ __('app.blade_no_attachments') }}</p>
|
||||
@else
|
||||
<div class="space-y-3">
|
||||
@foreach($links as $link)
|
||||
@@ -38,11 +38,11 @@
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-xs text-gray-500">{{ $link['size'] }}</span>
|
||||
@if(($link['collection'] ?? '') === 'proof_of_resolution')
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-700 border border-green-200">Proof of Resolution</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-700 border border-green-200">{{ __('app.blade_proof_of_resolution') }}</span>
|
||||
@elseif(($link['collection'] ?? '') === 'customer_evidence')
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200">Customer Evidence</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200">{{ __('app.blade_customer_evidence') }}</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">{{ $link['collection'] ?? 'General' }}</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">{{ $link['collection'] ?? __('app.collection_general') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,11 +51,11 @@
|
||||
<a href="{{ $link['url'] }}" target="_blank"
|
||||
onclick="event.stopPropagation();"
|
||||
class="flex-shrink-0 inline-flex items-center px-3 py-1.5 rounded text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 transition-colors shadow-sm no-underline">
|
||||
Open
|
||||
{{ __('app.blade_open') }}
|
||||
</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<p class="mt-4 text-xs text-gray-500">Click to open. Links expire after 60 minutes.</p>
|
||||
<p class="mt-4 text-xs text-gray-500">{{ __('app.blade_links_expire') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-lg text-gray-900 flex items-center gap-2" style="font-family: 'Manrope', sans-serif;">
|
||||
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
|
||||
Attachments
|
||||
{{ __('app.attachments') }}
|
||||
</h3>
|
||||
<span class="text-xs text-gray-500">{{ $attachments->count() }} file(s)</span>
|
||||
<span class="text-xs text-gray-500">{{ __('app.blade_file_count', ['count' => $attachments->count()]) }}</span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
@@ -45,11 +45,11 @@
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-xs text-gray-500">{{ $sizeKb }}</span>
|
||||
@if($attachment->collection === 'proof_of_resolution')
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-700 border border-green-200">Proof</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-700 border border-green-200">{{ __('app.blade_proof') }}</span>
|
||||
@elseif($attachment->collection === 'customer_evidence')
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200">Evidence</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200">{{ __('app.blade_evidence') }}</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">General</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">{{ __('app.collection_general') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-lg text-gray-900 flex items-center gap-2" style="font-family: 'Manrope', sans-serif;">
|
||||
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
Similar Cases
|
||||
{{ __('app.blade_similar_cases') }}
|
||||
</h3>
|
||||
<span class="text-xs text-gray-500">Matching tags: {{ $record->tags->pluck('name')->implode(', ') }}</span>
|
||||
<span class="text-xs text-gray-500">{{ __('app.blade_matching_tags') }} {{ $record->tags->pluck('name')->implode(', ') }}</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 border-b border-gray-200">
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Title</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Customer</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Handler</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Date</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">{{ __('app.blade_title') }}</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">{{ __('app.blade_customer') }}</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">{{ __('app.blade_status') }}</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">{{ __('app.blade_handler') }}</th>
|
||||
<th class="px-6 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">{{ __('app.blade_date') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@@ -47,21 +47,21 @@
|
||||
<td class="px-6 py-4">
|
||||
@switch($case->status)
|
||||
@case('pending')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-amber-50 text-amber-700 border border-amber-200">Pending</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-amber-50 text-amber-700 border border-amber-200">{{ __('app.status_pending') }}</span>
|
||||
@break
|
||||
@case('processing')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-50 text-blue-700 border border-blue-200">Processing</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-50 text-blue-700 border border-blue-200">{{ __('app.status_processing') }}</span>
|
||||
@break
|
||||
@case('resolved')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-green-50 text-green-700 border border-green-200">Resolved</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-green-50 text-green-700 border border-green-200">{{ __('app.status_resolved') }}</span>
|
||||
@break
|
||||
@case('closed')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">Closed</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600 border border-gray-200">{{ __('app.status_closed') }}</span>
|
||||
@break
|
||||
@endswitch
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600">
|
||||
{{ $case->assignedTo?->name ?? 'Unassigned' }}
|
||||
{{ $case->assignedTo?->name ?? __('app.unassigned') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
{{ $case->created_at->format('d/m/Y') }}
|
||||
|
||||
417
webappUI/dashboard.html
Normal file
417
webappUI/dashboard.html
Normal file
@@ -0,0 +1,417 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard — AfterSales CRM</title>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand-icon">
|
||||
<span class="material-symbols-outlined">support_agent</span>
|
||||
</div>
|
||||
<span class="sidebar-brand-text">AfterSales</span>
|
||||
<span class="sidebar-brand-badge">CRM</span>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Main</div>
|
||||
<a class="sidebar-item active" href="dashboard.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">dashboard</span>
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Customer Care</div>
|
||||
<a class="sidebar-item" href="feedback-list.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">chat_bubble_left_right</span>
|
||||
Feedbacks
|
||||
<span class="sidebar-item-badge">8</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Management</div>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">building_storefront</span>
|
||||
Products
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">group</span>
|
||||
Customers
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">inbox_stack</span>
|
||||
Channels
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">description</span>
|
||||
Contracts
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">label</span>
|
||||
Tags
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar">AD</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">Admin User</div>
|
||||
<div class="sidebar-user-role">admin</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:var(--crm-text-tertiary)">unfold_more</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Top Bar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<div class="breadcrumb">
|
||||
<a href="#">Home</a>
|
||||
<span class="breadcrumb-sep material-symbols-outlined">chevron_right</span>
|
||||
<span class="breadcrumb-current">Dashboard</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">search</span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">notifications</span>
|
||||
<span class="topbar-btn-badge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="page-content">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
<p class="page-subtitle">Overview of customer care operations</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card stat-card--success">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-icon">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
</div>
|
||||
<div class="stat-card-trend stat-card-trend--up">
|
||||
<span class="material-symbols-outlined" style="font-size:14px">trending_up</span>
|
||||
+12%
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card-value">42</div>
|
||||
<div class="stat-card-label">Resolved Tickets</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card stat-card--info">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-icon">
|
||||
<span class="material-symbols-outlined">schedule</span>
|
||||
</div>
|
||||
<div class="stat-card-trend stat-card-trend--down">
|
||||
<span class="material-symbols-outlined" style="font-size:14px">trending_down</span>
|
||||
-8%
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card-value">2.5h</div>
|
||||
<div class="stat-card-label">Avg Processing Time</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card stat-card--warning">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-icon">
|
||||
<span class="material-symbols-outlined">pending</span>
|
||||
</div>
|
||||
<div class="stat-card-trend stat-card-trend--up">
|
||||
<span class="material-symbols-outlined" style="font-size:14px">trending_up</span>
|
||||
+3
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card-value">8</div>
|
||||
<div class="stat-card-label">Pending Tickets</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card stat-card--danger">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-icon">
|
||||
<span class="material-symbols-outlined">warning</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card-value">3</div>
|
||||
<div class="stat-card-label">Escalated</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2 Column Layout -->
|
||||
<div style="display:grid; grid-template-columns: 1.2fr 1fr; gap:24px;">
|
||||
<!-- Chart -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon">
|
||||
<span class="material-symbols-outlined">bar_chart</span>
|
||||
</div>
|
||||
Avg Processing Time by Handler
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn--ghost btn--sm">Week</button>
|
||||
<button class="btn btn--secondary btn--sm">Month</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-placeholder" style="height:240px; padding-bottom:40px;">
|
||||
<div class="chart-bar" style="height:65%; background: linear-gradient(180deg, #3b82f6, #60a5fa);">
|
||||
<span class="chart-bar-value">45m</span>
|
||||
<span class="chart-bar-label">Staff A</span>
|
||||
</div>
|
||||
<div class="chart-bar" style="height:85%; background: linear-gradient(180deg, #3b82f6, #60a5fa);">
|
||||
<span class="chart-bar-value">62m</span>
|
||||
<span class="chart-bar-label">Staff B</span>
|
||||
</div>
|
||||
<div class="chart-bar" style="height:45%; background: linear-gradient(180deg, #3b82f6, #60a5fa);">
|
||||
<span class="chart-bar-value">31m</span>
|
||||
<span class="chart-bar-label">Staff C</span>
|
||||
</div>
|
||||
<div class="chart-bar" style="height:70%; background: linear-gradient(180deg, #3b82f6, #60a5fa);">
|
||||
<span class="chart-bar-value">52m</span>
|
||||
<span class="chart-bar-label">Staff D</span>
|
||||
</div>
|
||||
<div class="chart-bar" style="height:55%; background: linear-gradient(180deg, #3b82f6, #60a5fa);">
|
||||
<span class="chart-bar-value">38m</span>
|
||||
<span class="chart-bar-label">Staff E</span>
|
||||
</div>
|
||||
<div class="chart-bar" style="height:90%; background: linear-gradient(180deg, #f59e0b, #fbbf24);">
|
||||
<span class="chart-bar-value">68m</span>
|
||||
<span class="chart-bar-label">Staff F</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resolved Awaiting Closure -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon" style="background:var(--crm-resolved-bg); color:var(--crm-resolved);">
|
||||
<span class="material-symbols-outlined">task_alt</span>
|
||||
</div>
|
||||
Resolved — Awaiting Closure
|
||||
</div>
|
||||
<span class="badge badge--resolved badge--dot">5 tickets</span>
|
||||
</div>
|
||||
<div class="card-body card-body--compact">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Customer</th>
|
||||
<th>Dept</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="feedback-detail.html" class="table-link">#12 Leak issue</a></td>
|
||||
<td>Nguyễn A</td>
|
||||
<td><span class="badge badge--processing" style="font-size:11px;">CSKH</span></td>
|
||||
<td class="text-right"><span class="material-symbols-outlined" style="font-size:18px;color:var(--crm-text-tertiary)">chevron_right</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">#8 AC broken</a></td>
|
||||
<td>Trần B</td>
|
||||
<td><span class="badge badge--processing" style="font-size:11px;">Kỹ thuật</span></td>
|
||||
<td class="text-right"><span class="material-symbols-outlined" style="font-size:18px;color:var(--crm-text-tertiary)">chevron_right</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">#5 Door fix</a></td>
|
||||
<td>Lê C</td>
|
||||
<td><span class="badge badge--processing" style="font-size:11px;">CSKH</span></td>
|
||||
<td class="text-right"><span class="material-symbols-outlined" style="font-size:18px;color:var(--crm-text-tertiary)">chevron_right</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">#3 Paint crack</a></td>
|
||||
<td>Phạm D</td>
|
||||
<td><span class="badge badge--processing" style="font-size:11px;">Kỹ thuật</span></td>
|
||||
<td class="text-right"><span class="material-symbols-outlined" style="font-size:18px;color:var(--crm-text-tertiary)">chevron_right</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">#1 Window leak</a></td>
|
||||
<td>Hoàng E</td>
|
||||
<td><span class="badge badge--processing" style="font-size:11px;">CSKH</span></td>
|
||||
<td class="text-right"><span class="material-symbols-outlined" style="font-size:18px;color:var(--crm-text-tertiary)">chevron_right</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Feedbacks -->
|
||||
<div class="card mt-6">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon">
|
||||
<span class="material-symbols-outlined">history</span>
|
||||
</div>
|
||||
Recent Feedbacks
|
||||
</div>
|
||||
<a href="feedback-list.html" class="btn btn--secondary btn--sm">
|
||||
View All
|
||||
<span class="material-symbols-outlined" style="font-size:16px">arrow_forward</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body card-body--compact">
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Customer</th>
|
||||
<th>Product</th>
|
||||
<th>Contract</th>
|
||||
<th>Channel</th>
|
||||
<th>Status</th>
|
||||
<th>Department</th>
|
||||
<th>Assigned To</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="feedback-detail.html" class="table-link">Leak in bathroom</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">NA</div>
|
||||
Nguyễn A
|
||||
</div>
|
||||
</td>
|
||||
<td>Sunrise A1</td>
|
||||
<td><span class="badge badge--sale">Sale</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;"><span class="material-symbols-outlined" style="font-size:16px;color:#0066ff">chat</span> Zalo</span></td>
|
||||
<td><span class="badge badge--pending badge--dot">Pending</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">2 hours ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">AC not working</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--purple">TB</div>
|
||||
Trần B
|
||||
</div>
|
||||
</td>
|
||||
<td>Green Valley</td>
|
||||
<td><span class="badge badge--lease">Lease</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;"><span class="material-symbols-outlined" style="font-size:16px;color:#ea4335">mail</span> Email</span></td>
|
||||
<td><span class="badge badge--processing badge--dot">Processing</span></td>
|
||||
<td>Kỹ thuật</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber">MG</div>
|
||||
Manager User
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">5 hours ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<a href="#" class="table-link">Door lock broken</a>
|
||||
<span class="material-symbols-outlined" style="font-size:16px;color:var(--crm-escalated)" title="Escalated">priority_high</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">LC</div>
|
||||
Lê C
|
||||
</div>
|
||||
</td>
|
||||
<td>Ocean Tower</td>
|
||||
<td><span class="badge badge--bcc">BCC</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;"><span class="material-symbols-outlined" style="font-size:16px;color:#34a853">phone</span> Phone</span></td>
|
||||
<td><span class="badge badge--escalated badge--dot">Escalated</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">1 day ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">Paint crack in living room</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber">PD</div>
|
||||
Phạm D
|
||||
</div>
|
||||
</td>
|
||||
<td>Park Hill</td>
|
||||
<td><span class="badge badge--sale">Sale</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;"><span class="material-symbols-outlined" style="font-size:16px;color:#6b7280">description</span> Document</span></td>
|
||||
<td><span class="badge badge--resolved badge--dot">Resolved</span></td>
|
||||
<td>Kỹ thuật</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--purple">AD</div>
|
||||
Admin User
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">3 days ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#" class="table-link">Window seal broken</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">HE</div>
|
||||
Hoàng E
|
||||
</div>
|
||||
</td>
|
||||
<td>Sunrise A2</td>
|
||||
<td><span class="badge badge--lease">Lease</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;"><span class="material-symbols-outlined" style="font-size:16px;color:#9c27b0">person</span> In Person</span></td>
|
||||
<td><span class="badge badge--closed badge--dot">Closed</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">1 week ago</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
602
webappUI/feedback-detail.html
Normal file
602
webappUI/feedback-detail.html
Normal file
@@ -0,0 +1,602 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ticket #12 — AfterSales CRM</title>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand-icon">
|
||||
<span class="material-symbols-outlined">support_agent</span>
|
||||
</div>
|
||||
<span class="sidebar-brand-text">AfterSales</span>
|
||||
<span class="sidebar-brand-badge">CRM</span>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Main</div>
|
||||
<a class="sidebar-item" href="dashboard.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">dashboard</span>
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Customer Care</div>
|
||||
<a class="sidebar-item active" href="feedback-list.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">chat_bubble_left_right</span>
|
||||
Feedbacks
|
||||
<span class="sidebar-item-badge">8</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Management</div>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">building_storefront</span>
|
||||
Products
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">group</span>
|
||||
Customers
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">inbox_stack</span>
|
||||
Channels
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">description</span>
|
||||
Contracts
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">label</span>
|
||||
Tags
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar">AD</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">Admin User</div>
|
||||
<div class="sidebar-user-role">admin</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:var(--crm-text-tertiary)">unfold_more</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<div class="breadcrumb">
|
||||
<a href="dashboard.html">Home</a>
|
||||
<span class="breadcrumb-sep material-symbols-outlined">chevron_right</span>
|
||||
<a href="feedback-list.html" style="color:var(--crm-text-tertiary); text-decoration:none;">Feedbacks</a>
|
||||
<span class="breadcrumb-sep material-symbols-outlined">chevron_right</span>
|
||||
<span class="breadcrumb-current">#12 Leak in bathroom</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">search</span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">notifications</span>
|
||||
<span class="topbar-btn-badge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-content">
|
||||
<!-- Page Header -->
|
||||
<div style="display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:28px;">
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:12px; margin-bottom:8px;">
|
||||
<a href="feedback-list.html" class="btn btn--ghost btn--sm" style="padding:6px;">
|
||||
<span class="material-symbols-outlined">arrow_back</span>
|
||||
</a>
|
||||
<h1 class="page-title" style="margin:0;">Leak in bathroom</h1>
|
||||
<span class="badge badge--pending badge--dot">Pending</span>
|
||||
<span class="badge badge--escalated" style="font-size:11px;">
|
||||
<span class="material-symbols-outlined" style="font-size:14px;">warning</span>
|
||||
Escalated
|
||||
</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:16px; margin-left:52px; font-size:13px; color:var(--crm-text-secondary);">
|
||||
<span>Ticket <strong>#12</strong></span>
|
||||
<span>•</span>
|
||||
<span>Created <strong>2 hours ago</strong></span>
|
||||
<span>•</span>
|
||||
<span>By <strong>Nguyễn A</strong></span>
|
||||
<span>•</span>
|
||||
<span>Via <strong>Zalo</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn--secondary btn--sm">
|
||||
<span class="material-symbols-outlined" style="font-size:16px;">edit</span>
|
||||
Edit
|
||||
</button>
|
||||
<button class="btn btn--secondary btn--sm">
|
||||
<span class="material-symbols-outlined" style="font-size:16px;">print</span>
|
||||
Print
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Grid -->
|
||||
<div class="detail-grid">
|
||||
<!-- Left Column -->
|
||||
<div class="detail-main">
|
||||
<!-- Feedback Content -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon">
|
||||
<span class="material-symbols-outlined">description</span>
|
||||
</div>
|
||||
Feedback Details
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div style="margin-bottom:20px;">
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-text-tertiary); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:8px;">Content</div>
|
||||
<div style="font-size:15px; line-height:1.7; color:var(--crm-text-primary); background:var(--crm-surface-dim); padding:16px 20px; border-radius:var(--crm-radius-md); border-left:3px solid var(--crm-primary);">
|
||||
Water is leaking from the bathroom ceiling. The leak started 3 days ago and has gotten worse. There's water damage on the ceiling and water dripping onto the floor. I've placed a bucket but need urgent repair. The apartment is Sunrise A1, Floor 12, Unit 1205.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:20px;">
|
||||
<div>
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-text-tertiary); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:8px;">Customer</div>
|
||||
<div style="display:flex; align-items:center; gap:10px; padding:10px 14px; background:var(--crm-surface-dim); border-radius:var(--crm-radius-md);">
|
||||
<div class="table-avatar table-avatar--blue" style="width:36px;height:36px;font-size:13px;">NA</div>
|
||||
<div>
|
||||
<div style="font-size:14px; font-weight:600; color:var(--crm-text-primary);">Nguyễn A</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary);">nguyen.a@email.com</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-text-tertiary); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:8px;">Product</div>
|
||||
<div style="display:flex; align-items:center; gap:10px; padding:10px 14px; background:var(--crm-surface-dim); border-radius:var(--crm-radius-md);">
|
||||
<div style="width:36px;height:36px;border-radius:var(--crm-radius-sm);background:linear-gradient(135deg,#3b82f6,#6366f1);display:flex;align-items:center;justify-content:center;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:white;">apartment</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:14px; font-weight:600; color:var(--crm-text-primary);">Sunrise A1</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary);">Floor 12, Unit 1205</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-text-tertiary); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:8px;">Tags</div>
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<span class="badge" style="background:#ede9fe;color:#7c3aed;border:1px solid #c4b5fd;">Plumbing</span>
|
||||
<span class="badge" style="background:#fee2e2;color:#991b1b;border:1px solid #fecaca;">Urgent</span>
|
||||
<span class="badge" style="background:#dbeafe;color:#1e40af;border:1px solid #bfdbfe;">Water Damage</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interaction History -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon" style="background:var(--crm-processing-bg); color:var(--crm-processing);">
|
||||
<span class="material-symbols-outlined">forum</span>
|
||||
</div>
|
||||
Interaction History
|
||||
</div>
|
||||
<button class="btn btn--primary btn--sm">
|
||||
<span class="material-symbols-outlined" style="font-size:16px;">add</span>
|
||||
Add Note
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="timeline">
|
||||
<!-- Note -->
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot timeline-dot--note">
|
||||
<span class="material-symbols-outlined">edit_note</span>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-author">Staff User</span>
|
||||
<span class="timeline-type">Note</span>
|
||||
<span class="timeline-time">2 hours ago</span>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Contacted customer to schedule inspection. Customer available tomorrow 2-4 PM. Will send maintenance team to check the ceiling leak. Need to coordinate with building management for access to the unit above.
|
||||
</div>
|
||||
<a href="#" class="timeline-attachment">
|
||||
<span class="material-symbols-outlined">attach_file</span>
|
||||
inspection_photo.jpg (2.3 MB)
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Change -->
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot timeline-dot--status">
|
||||
<span class="material-symbols-outlined">sync</span>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-author">Manager User</span>
|
||||
<span class="timeline-type">Status Change</span>
|
||||
<span class="timeline-time">5 hours ago</span>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<span class="badge badge--pending" style="font-size:11px;">Pending</span>
|
||||
<span class="material-symbols-outlined" style="font-size:16px; color:var(--crm-text-tertiary);">arrow_forward</span>
|
||||
<span class="badge badge--processing" style="font-size:11px;">Processing</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assignment -->
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot timeline-dot--assignment">
|
||||
<span class="material-symbols-outlined">person_add</span>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-author">Manager User</span>
|
||||
<span class="timeline-type">Assignment</span>
|
||||
<span class="timeline-time">5 hours ago</span>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Assigned to <strong>Staff User</strong> for handling
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transfer -->
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot timeline-dot--transfer">
|
||||
<span class="material-symbols-outlined">swap_horiz</span>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-author">Admin User</span>
|
||||
<span class="timeline-type">Transfer</span>
|
||||
<span class="timeline-time">1 day ago</span>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
|
||||
<span class="badge" style="background:var(--crm-processing-bg); color:var(--crm-processing); border:1px solid var(--crm-processing-border);">Kỹ thuật</span>
|
||||
<span class="material-symbols-outlined" style="font-size:16px; color:var(--crm-text-tertiary);">arrow_forward</span>
|
||||
<span class="badge" style="background:var(--crm-resolved-bg); color:var(--crm-resolved); border:1px solid var(--crm-resolved-border);">CSKH</span>
|
||||
</div>
|
||||
<div style="font-size:13px; color:var(--crm-text-secondary); font-style:italic;">
|
||||
"Đã kiểm tra kỹ thuật, cần CSKH liên hệ khách hàng để sắp lịch sửa chữa"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Initial Note -->
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot timeline-dot--note">
|
||||
<span class="material-symbols-outlined">edit_note</span>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="timeline-author">System</span>
|
||||
<span class="timeline-type">Created</span>
|
||||
<span class="timeline-time">2 days ago</span>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
Feedback created via <strong>Zalo</strong> channel. Auto-assigned to <strong>CSKH</strong> department.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attachments -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon" style="background:var(--crm-accent-light); color:var(--crm-accent);">
|
||||
<span class="material-symbols-outlined">attach_file</span>
|
||||
</div>
|
||||
Attachments
|
||||
</div>
|
||||
<span style="font-size:13px; color:var(--crm-text-tertiary);">3 files</span>
|
||||
</div>
|
||||
<div class="card-body card-body--compact">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File</th>
|
||||
<th>Collection</th>
|
||||
<th>Size</th>
|
||||
<th>Uploaded By</th>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div style="width:36px;height:36px;border-radius:var(--crm-radius-sm);background:#fee2e2;display:flex;align-items:center;justify-content:center;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:#dc2626;">image</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:14px; font-weight:600; color:var(--crm-text-primary);">inspection_photo.jpg</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary);">image/jpeg</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge" style="background:var(--crm-surface-dim); color:var(--crm-text-secondary); border:1px solid var(--crm-border); font-size:11px;">customer_evidence</span></td>
|
||||
<td style="font-size:13px; color:var(--crm-text-secondary);">2.3 MB</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green" style="width:24px;height:24px;font-size:10px;">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:13px; color:var(--crm-text-tertiary);">2h ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">download</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div style="width:36px;height:36px;border-radius:var(--crm-radius-sm);background:#dbeafe;display:flex;align-items:center;justify-content:center;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:#2563eb;">description</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:14px; font-weight:600; color:var(--crm-text-primary);">repair_estimate.pdf</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary);">application/pdf</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge" style="background:var(--crm-surface-dim); color:var(--crm-text-secondary); border:1px solid var(--crm-border); font-size:11px;">general</span></td>
|
||||
<td style="font-size:13px; color:var(--crm-text-secondary);">1.1 MB</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber" style="width:24px;height:24px;font-size:10px;">MG</div>
|
||||
Manager User
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:13px; color:var(--crm-text-tertiary);">1d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">download</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div style="width:36px;height:36px;border-radius:var(--crm-radius-sm);background:#ecfdf5;display:flex;align-items:center;justify-content:center;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:#059669;">check_circle</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:14px; font-weight:600; color:var(--crm-text-primary);">proof_fixed.jpg</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary);">image/jpeg</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge" style="background:var(--crm-resolved-bg); color:var(--crm-resolved); border:1px solid var(--crm-resolved-border); font-size:11px;">proof_of_resolution</span></td>
|
||||
<td style="font-size:13px; color:var(--crm-text-secondary);">3.8 MB</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green" style="width:24px;height:24px;font-size:10px;">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:13px; color:var(--crm-text-tertiary);">3d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">download</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transfer History -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="card-title-icon" style="background:var(--crm-pending-bg); color:var(--crm-pending);">
|
||||
<span class="material-symbols-outlined">swap_horiz</span>
|
||||
</div>
|
||||
Transfer History
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="transfer-log">
|
||||
<div class="transfer-item">
|
||||
<span class="transfer-dept">Kỹ thuật</span>
|
||||
<span class="transfer-arrow material-symbols-outlined">arrow_forward</span>
|
||||
<span class="transfer-dept">CSKH</span>
|
||||
<span style="font-size:13px; color:var(--crm-text-secondary);">— Admin User</span>
|
||||
<div class="transfer-meta">
|
||||
<div>"Đã kiểm tra kỹ thuật, cần CSKH liên hệ"</div>
|
||||
<div style="margin-top:2px;">1 day ago</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transfer-item">
|
||||
<span class="transfer-dept">CSKH</span>
|
||||
<span class="transfer-arrow material-symbols-outlined">arrow_forward</span>
|
||||
<span class="transfer-dept">Kỹ thuật</span>
|
||||
<span style="font-size:13px; color:var(--crm-text-secondary);">— Manager User</span>
|
||||
<div class="transfer-meta">
|
||||
<div>"Cần kiểm tra kỹ thuật"</div>
|
||||
<div style="margin-top:2px;">2 days ago</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar -->
|
||||
<div class="detail-sidebar">
|
||||
<!-- Actions -->
|
||||
<div class="action-card">
|
||||
<div class="action-card-title">
|
||||
<span class="material-symbols-outlined">bolt</span>
|
||||
Quick Actions
|
||||
</div>
|
||||
<button class="action-btn">
|
||||
<span class="material-symbols-outlined">swap_horiz</span>
|
||||
Transfer Department
|
||||
</button>
|
||||
<button class="action-btn">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
Mark as Resolved
|
||||
</button>
|
||||
<button class="action-btn action-btn--danger">
|
||||
<span class="material-symbols-outlined">lock</span>
|
||||
Close Ticket
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Ticket Info -->
|
||||
<div class="action-card">
|
||||
<div class="action-card-title">
|
||||
<span class="material-symbols-outlined">info</span>
|
||||
Ticket Information
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">sync</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Status</div>
|
||||
<div class="info-item-value"><span class="badge badge--pending badge--dot" style="font-size:12px;">Pending</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">apartment</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Department</div>
|
||||
<div class="info-item-value">CSKH</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">person</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Assigned To</div>
|
||||
<div class="info-item-value">
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<div class="table-avatar table-avatar--green" style="width:24px;height:24px;font-size:10px;">ST</div>
|
||||
Staff User
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">description</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Contract</div>
|
||||
<div class="info-item-value"><span class="badge badge--sale" style="font-size:12px;">Sale</span> Active</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">inbox_stack</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Channel</div>
|
||||
<div class="info-item-value" style="display:flex; align-items:center; gap:6px;">
|
||||
<span class="material-symbols-outlined" style="font-size:16px; color:#0066ff;">chat</span>
|
||||
Zalo
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">warning</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Escalated</div>
|
||||
<div class="info-item-value"><span style="color:var(--crm-escalated); font-weight:600;">Yes</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">calendar_today</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Created</div>
|
||||
<div class="info-item-value">Jan 15, 2026 10:30 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-icon">
|
||||
<span class="material-symbols-outlined">update</span>
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<div class="info-item-label">Last Updated</div>
|
||||
<div class="info-item-value">Jan 15, 2026 12:45 PM</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Feedbacks -->
|
||||
<div class="action-card">
|
||||
<div class="action-card-title">
|
||||
<span class="material-symbols-outlined">lightbulb</span>
|
||||
Similar Cases
|
||||
</div>
|
||||
<div style="display:flex; flex-direction:column; gap:10px;">
|
||||
<a href="#" style="display:block; padding:10px 12px; background:var(--crm-surface-dim); border-radius:var(--crm-radius-sm); border:1px solid var(--crm-border); text-decoration:none; transition:all 0.15s;">
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-primary);">Water leak in kitchen</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary); margin-top:2px;">Sunrise A2 • Resolved • 2 weeks ago</div>
|
||||
</a>
|
||||
<a href="#" style="display:block; padding:10px 12px; background:var(--crm-surface-dim); border-radius:var(--crm-radius-sm); border:1px solid var(--crm-border); text-decoration:none; transition:all 0.15s;">
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-primary);">Ceiling water damage</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary); margin-top:2px;">Green Valley • Closed • 1 month ago</div>
|
||||
</a>
|
||||
<a href="#" style="display:block; padding:10px 12px; background:var(--crm-surface-dim); border-radius:var(--crm-radius-sm); border:1px solid var(--crm-border); text-decoration:none; transition:all 0.15s;">
|
||||
<div style="font-size:13px; font-weight:600; color:var(--crm-primary);">Bathroom pipe burst</div>
|
||||
<div style="font-size:12px; color:var(--crm-text-tertiary); margin-top:2px;">Ocean Tower • Resolved • 3 weeks ago</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
429
webappUI/feedback-list.html
Normal file
429
webappUI/feedback-list.html
Normal file
@@ -0,0 +1,429 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Feedbacks — AfterSales CRM</title>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand-icon">
|
||||
<span class="material-symbols-outlined">support_agent</span>
|
||||
</div>
|
||||
<span class="sidebar-brand-text">AfterSales</span>
|
||||
<span class="sidebar-brand-badge">CRM</span>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Main</div>
|
||||
<a class="sidebar-item" href="dashboard.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">dashboard</span>
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Customer Care</div>
|
||||
<a class="sidebar-item active" href="feedback-list.html">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">chat_bubble_left_right</span>
|
||||
Feedbacks
|
||||
<span class="sidebar-item-badge">8</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<div class="sidebar-group-label">Management</div>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">building_storefront</span>
|
||||
Products
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">group</span>
|
||||
Customers
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">inbox_stack</span>
|
||||
Channels
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">description</span>
|
||||
Contracts
|
||||
</a>
|
||||
<a class="sidebar-item" href="#">
|
||||
<span class="sidebar-item-icon material-symbols-outlined">label</span>
|
||||
Tags
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar">AD</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">Admin User</div>
|
||||
<div class="sidebar-user-role">admin</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:20px;color:var(--crm-text-tertiary)">unfold_more</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<div class="breadcrumb">
|
||||
<a href="dashboard.html">Home</a>
|
||||
<span class="breadcrumb-sep material-symbols-outlined">chevron_right</span>
|
||||
<span style="color:var(--crm-text-tertiary)">Customer Care</span>
|
||||
<span class="breadcrumb-sep material-symbols-outlined">chevron_right</span>
|
||||
<span class="breadcrumb-current">Feedbacks</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">search</span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">notifications</span>
|
||||
<span class="topbar-btn-badge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-content">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
|
||||
<div>
|
||||
<h1 class="page-title">Feedbacks</h1>
|
||||
<p class="page-subtitle">Manage customer feedback and support tickets</p>
|
||||
</div>
|
||||
<button class="btn btn--primary">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
New Feedback
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div style="display:flex; gap:16px; margin-bottom:24px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-surface); border:1px solid var(--crm-border); border-radius:var(--crm-radius-full);">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-text-tertiary)">list</span>
|
||||
<span style="font-size:13px; font-weight:600; color:var(--crm-text-primary);">All</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-primary-light); color:var(--crm-primary); padding:1px 8px; border-radius:var(--crm-radius-full);">24</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-surface); border:1px solid var(--crm-border); border-radius:var(--crm-radius-full); cursor:pointer;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-pending)">schedule</span>
|
||||
<span style="font-size:13px; font-weight:500; color:var(--crm-text-secondary);">Pending</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-pending-bg); color:var(--crm-pending); padding:1px 8px; border-radius:var(--crm-radius-full);">8</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-surface); border:1px solid var(--crm-border); border-radius:var(--crm-radius-full); cursor:pointer;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-processing)">autorenew</span>
|
||||
<span style="font-size:13px; font-weight:500; color:var(--crm-text-secondary);">Processing</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-processing-bg); color:var(--crm-processing); padding:1px 8px; border-radius:var(--crm-radius-full);">6</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-surface); border:1px solid var(--crm-border); border-radius:var(--crm-radius-full); cursor:pointer;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-resolved)">check_circle</span>
|
||||
<span style="font-size:13px; font-weight:500; color:var(--crm-text-secondary);">Resolved</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-resolved-bg); color:var(--crm-resolved); padding:1px 8px; border-radius:var(--crm-radius-full);">5</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-surface); border:1px solid var(--crm-border); border-radius:var(--crm-radius-full); cursor:pointer;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-closed)">cancel</span>
|
||||
<span style="font-size:13px; font-weight:500; color:var(--crm-text-secondary);">Closed</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-closed-bg); color:var(--crm-closed); padding:1px 8px; border-radius:var(--crm-radius-full);">5</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:8px 16px; background:var(--crm-escalated-bg); border:1px solid rgba(220,38,38,0.2); border-radius:var(--crm-radius-full); cursor:pointer;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-escalated)">warning</span>
|
||||
<span style="font-size:13px; font-weight:600; color:var(--crm-escalated);">Escalated</span>
|
||||
<span style="font-size:12px; font-weight:700; background:var(--crm-escalated); color:white; padding:1px 8px; border-radius:var(--crm-radius-full);">3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="filter-bar">
|
||||
<div class="search-input">
|
||||
<span class="material-symbols-outlined">search</span>
|
||||
<input type="text" placeholder="Search feedbacks by title, customer...">
|
||||
</div>
|
||||
<div class="filter-select">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">filter_list</span>
|
||||
All Status
|
||||
<span class="material-symbols-outlined" style="font-size:18px">expand_more</span>
|
||||
</div>
|
||||
<div class="filter-select">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">inbox_stack</span>
|
||||
All Channels
|
||||
<span class="material-symbols-outlined" style="font-size:18px">expand_more</span>
|
||||
</div>
|
||||
<div class="filter-select">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">apartment</span>
|
||||
All Departments
|
||||
<span class="material-symbols-outlined" style="font-size:18px">expand_more</span>
|
||||
</div>
|
||||
<div class="filter-select">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">person</span>
|
||||
All Handlers
|
||||
<span class="material-symbols-outlined" style="font-size:18px">expand_more</span>
|
||||
</div>
|
||||
<button class="btn btn--ghost btn--sm" style="margin-left:auto;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">download</span>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card">
|
||||
<div class="card-body card-body--compact">
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40px;"><input type="checkbox" style="accent-color:var(--crm-primary);"></th>
|
||||
<th>Title</th>
|
||||
<th>Customer</th>
|
||||
<th>Product</th>
|
||||
<th>Contract</th>
|
||||
<th>Channel</th>
|
||||
<th>Tags</th>
|
||||
<th>Status</th>
|
||||
<th>Department</th>
|
||||
<th>Assigned To</th>
|
||||
<th>Created</th>
|
||||
<th style="width:60px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="feedback-detail.html" class="table-link">Leak in bathroom</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">NA</div>
|
||||
Nguyễn A
|
||||
</div>
|
||||
</td>
|
||||
<td>Sunrise A1</td>
|
||||
<td><span class="badge badge--sale">Sale</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#0066ff">chat</span> Zalo</span></td>
|
||||
<td><span class="badge" style="background:#ede9fe;color:#7c3aed;border:1px solid #c4b5fd;font-size:11px;">Plumbing</span></td>
|
||||
<td><span class="badge badge--pending badge--dot">Pending</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">2h ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="#" class="table-link">AC not working</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--purple">TB</div>
|
||||
Trần B
|
||||
</div>
|
||||
</td>
|
||||
<td>Green Valley</td>
|
||||
<td><span class="badge badge--lease">Lease</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#ea4335">mail</span> Email</span></td>
|
||||
<td><span class="badge" style="background:#fef3c7;color:#92400e;border:1px solid #fde68a;font-size:11px;">HVAC</span></td>
|
||||
<td><span class="badge badge--processing badge--dot">Processing</span></td>
|
||||
<td>Kỹ thuật</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber">MG</div>
|
||||
Manager
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">5h ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="background:var(--crm-escalated-bg);">
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<a href="#" class="table-link">Door lock broken</a>
|
||||
<span class="material-symbols-outlined" style="font-size:16px;color:var(--crm-escalated)" title="Escalated">priority_high</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">LC</div>
|
||||
Lê C
|
||||
</div>
|
||||
</td>
|
||||
<td>Ocean Tower</td>
|
||||
<td><span class="badge badge--bcc">BCC</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#34a853">phone</span> Phone</span></td>
|
||||
<td><span class="badge" style="background:#fee2e2;color:#991b1b;border:1px solid #fecaca;font-size:11px;">Urgent</span></td>
|
||||
<td><span class="badge badge--escalated badge--dot">Escalated</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">1d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="#" class="table-link">Paint crack in living room</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber">PD</div>
|
||||
Phạm D
|
||||
</div>
|
||||
</td>
|
||||
<td>Park Hill</td>
|
||||
<td><span class="badge badge--sale">Sale</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#6b7280">description</span> Doc</span></td>
|
||||
<td><span class="badge" style="background:#dbeafe;color:#1e40af;border:1px solid #bfdbfe;font-size:11px;">Painting</span></td>
|
||||
<td><span class="badge badge--resolved badge--dot">Resolved</span></td>
|
||||
<td>Kỹ thuật</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--purple">AD</div>
|
||||
Admin
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">3d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="#" class="table-link">Window seal broken</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">HE</div>
|
||||
Hoàng E
|
||||
</div>
|
||||
</td>
|
||||
<td>Sunrise A2</td>
|
||||
<td><span class="badge badge--lease">Lease</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#9c27b0">person</span> In Person</span></td>
|
||||
<td><span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;font-size:11px;">Window</span></td>
|
||||
<td><span class="badge badge--closed badge--dot">Closed</span></td>
|
||||
<td>CSKH</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">1w ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="#" class="table-link">Elevator noise complaint</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--purple">TB</div>
|
||||
Trần B
|
||||
</div>
|
||||
</td>
|
||||
<td>Green Valley</td>
|
||||
<td><span class="badge badge--lease">Lease</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#0066ff">chat</span> Zalo</span></td>
|
||||
<td><span class="badge" style="background:#fef3c7;color:#92400e;border:1px solid #fde68a;font-size:11px;">Noise</span></td>
|
||||
<td><span class="badge badge--pending badge--dot">Pending</span></td>
|
||||
<td>Kỹ thuật</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--amber">MG</div>
|
||||
Manager
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">2d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" style="accent-color:var(--crm-primary);"></td>
|
||||
<td><a href="#" class="table-link">Billing discrepancy</a></td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--blue">NA</div>
|
||||
Nguyễn A
|
||||
</div>
|
||||
</td>
|
||||
<td>Sunrise A1</td>
|
||||
<td><span class="badge badge--sale">Sale</span></td>
|
||||
<td><span style="display:inline-flex;align-items:center;gap:4px;font-size:13px;"><span class="material-symbols-outlined" style="font-size:16px;color:#ea4335">mail</span> Email</span></td>
|
||||
<td><span class="badge" style="background:#ede9fe;color:#7c3aed;border:1px solid #c4b5fd;font-size:11px;">Billing</span></td>
|
||||
<td><span class="badge badge--processing badge--dot">Processing</span></td>
|
||||
<td>Kế toán</td>
|
||||
<td>
|
||||
<div class="table-meta">
|
||||
<div class="table-avatar table-avatar--green">ST</div>
|
||||
Staff
|
||||
</div>
|
||||
</td>
|
||||
<td style="color:var(--crm-text-tertiary); font-size:13px;">4d ago</td>
|
||||
<td>
|
||||
<button class="btn btn--ghost btn--sm" style="padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:18px;">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination">
|
||||
<div class="pagination-info">Showing <strong>1-7</strong> of <strong>24</strong> feedbacks</div>
|
||||
<div class="pagination-buttons">
|
||||
<button class="pagination-btn">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">chevron_left</span>
|
||||
</button>
|
||||
<button class="pagination-btn active">1</button>
|
||||
<button class="pagination-btn">2</button>
|
||||
<button class="pagination-btn">3</button>
|
||||
<button class="pagination-btn">4</button>
|
||||
<button class="pagination-btn">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
127
webappUI/login.html
Normal file
127
webappUI/login.html
Normal file
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login — AfterSales CRM</title>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-layout">
|
||||
<!-- Left Panel -->
|
||||
<div class="login-left">
|
||||
<div class="login-left-content">
|
||||
<div class="login-left-icon">
|
||||
<span class="material-symbols-outlined">support_agent</span>
|
||||
</div>
|
||||
<h1>AfterSales CRM</h1>
|
||||
<p>Real-estate after-sales customer care system. Manage feedbacks, track tickets, and deliver exceptional service.</p>
|
||||
|
||||
<div style="margin-top:48px; display:flex; gap:32px; justify-content:center;">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:32px; font-weight:800; font-family:var(--font-heading);">3,658</div>
|
||||
<div style="font-size:13px; opacity:0.8; margin-top:4px;">Records Imported</div>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:32px; font-weight:800; font-family:var(--font-heading);">4</div>
|
||||
<div style="font-size:13px; opacity:0.8; margin-top:4px;">Departments</div>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:32px; font-weight:800; font-family:var(--font-heading);">8</div>
|
||||
<div style="font-size:13px; opacity:0.8; margin-top:4px;">Tests Passing</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:48px; padding:16px 24px; background:rgba(255,255,255,0.15); border-radius:var(--crm-radius-lg); backdrop-filter:blur(10px);">
|
||||
<div style="display:flex; align-items:center; gap:12px; margin-bottom:12px;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;">format_quote</span>
|
||||
<span style="font-size:13px; font-weight:600;">Customer Feedback</span>
|
||||
</div>
|
||||
<p style="font-size:14px; line-height:1.6; opacity:0.9; font-style:italic;">
|
||||
"The support team resolved my issue within 24 hours. Excellent service and very professional follow-up."
|
||||
</p>
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-top:12px;">
|
||||
<div style="width:28px; height:28px; border-radius:50%; background:rgba(255,255,255,0.3); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700;">NA</div>
|
||||
<div>
|
||||
<div style="font-size:13px; font-weight:600;">Nguyễn A</div>
|
||||
<div style="font-size:11px; opacity:0.7;">Sunrise A1 Resident</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel - Login Form -->
|
||||
<div class="login-right">
|
||||
<div class="login-form-container">
|
||||
<div class="login-form-header">
|
||||
<h2>Welcome back</h2>
|
||||
<p>Sign in to your account to continue</p>
|
||||
</div>
|
||||
|
||||
<form class="login-form" onsubmit="return false;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" class="form-input" placeholder="Enter your email" value="admin@minicrm.local">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Password</label>
|
||||
<div style="position:relative;">
|
||||
<input type="password" class="form-input" placeholder="Enter your password" value="password" style="padding-right:44px;">
|
||||
<button type="button" style="position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; cursor:pointer; color:var(--crm-text-tertiary); padding:4px;">
|
||||
<span class="material-symbols-outlined" style="font-size:20px;">visibility_off</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
|
||||
<label class="form-toggle">
|
||||
<div class="toggle-switch active"></div>
|
||||
<span class="toggle-label" style="font-size:13px;">Remember me</span>
|
||||
</label>
|
||||
<a href="#" style="font-size:13px; font-weight:600; color:var(--crm-primary); text-decoration:none;">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary btn--lg" style="width:100%;">
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-divider">or continue with</div>
|
||||
|
||||
<div class="login-demo">
|
||||
<div class="login-demo-title">Demo Accounts</div>
|
||||
<div class="login-demo-accounts">
|
||||
<div class="login-demo-account">
|
||||
<div>
|
||||
<div class="login-demo-role">Admin</div>
|
||||
<div class="login-demo-email">admin@minicrm.local</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-text-tertiary);">arrow_forward</span>
|
||||
</div>
|
||||
<div class="login-demo-account">
|
||||
<div>
|
||||
<div class="login-demo-role">Manager</div>
|
||||
<div class="login-demo-email">manager@minicrm.local</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-text-tertiary);">arrow_forward</span>
|
||||
</div>
|
||||
<div class="login-demo-account">
|
||||
<div>
|
||||
<div class="login-demo-role">Staff</div>
|
||||
<div class="login-demo-email">staff@minicrm.local</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined" style="font-size:18px; color:var(--crm-text-tertiary);">arrow_forward</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; margin-top:24px; font-size:12px; color:var(--crm-text-tertiary);">
|
||||
Laravel 13.6 • Filament 5.6 • Spatie RBAC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1363
webappUI/shared.css
Normal file
1363
webappUI/shared.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user