Màn quản trị — Cài đặt giới hạn từ khóa theo cửa hàng
Tổng quan
Thiết kế UI cho màn quản trị mới sẽ làm trong #1-3 Mở rộng giới hạn từ khóa — Phase 2.
Sales / CS có thể điều chỉnh giới hạn từ khóa theo từng cửa hàng dưới quyền sở hữu của user, trong khoảng 8〜15. Thao tác trên UI thay thế cho việc INSERT bằng SQL của Phase 1.
| Mục | Nội dung |
|---|---|
| Vị trí | Thêm 1 tab "Giới hạn từ khóa" vào page MappyUserDetail có sẵn (URL: /admin/users/:userId/details) |
| Guard | auth:admin_api (đã có) |
| Bảng dữ liệu | mappy_keyword_limit_settings (scope_type='location') |
| Audit | Tích hợp ActivityLog |
Phương châm tích hợp vào màn hình hiện có
Bám sát navigation admin của mappy đang có (không tạo route mới):
/admin/login
↓
/admin/users [MappyUserList] ← sidebar "MAPPY管理"
↓ (click loginId)
/admin/users/:userId/details [MappyUserDetail] ← thêm 1 tab vào 2 tab đã có
├ Tab1: Thông tin tài khoản (đã có · không sửa)
├ Tab2: Cài đặt thông báo (đã có · không sửa)
└ Tab3: Giới hạn từ khóa ★ Mới ở Phase 2File cần sửa
| File | Nội dung sửa |
|---|---|
resources/js/mappy/views/admin/UserDetail.vue | Thêm tab 3. Mở rộng <v-tabs> từ 2 → 3 |
resources/js/mappy/views/admin/UserKeywordLimitTab.vue ★ Mới | Nội dung chính của màn (bảng shop + edit inline) |
resources/js/mappy/include/selectors/StoreAndGroupSelector.vue | Không sửa (tab 1 vẫn dùng) |
app/Http/Controllers/Mappy/Admin/LocationController.php | Bổ sung max_keywords vào response của getUserLocationsAndGroups() |
app/Http/Controllers/Mappy/Admin/KeywordLimitController.php ★ Mới | Implement PATCH / DELETE endpoint |
routes/mappy/api.php | Thêm 2 dòng /admin/locations/{loc}/keyword-limit |
Sidebar (không đổi)
Không cần thêm mục vào resources/js/mappy/_navigationAdmin.js. Tính năng nằm sâu trong "MAPPY管理" hiện có, không thêm top-level menu.
Mock màn hình
アカウント情報
| 店舗名 | プラン値 | 現在の上限 | 新しい上限 | メモ(課金根拠) | 有効期間 | 操作 |
|---|---|---|---|---|---|---|
渋谷本店 location_id: 8801 | 8 | 15 | 2026-06-01 〜 無期限 | |||
新宿西口店 location_id: 8802 | 8 | 8 | — 〜 無期限 | |||
池袋東口店 location_id: 8803 | 8 | 12 | 2026-05-15 〜 2026-12-31 | |||
横浜西口店 location_id: 8804 | 8 | 8 | — 〜 無期限 | |||
川崎駅前店 location_id: 8805 | 8 | 10 | 2026-06-01 〜 無期限 |
呼び出し API(参考)
| Method | Path | 役割 |
|---|---|---|
GET | /api/mappy/admin/users/{user}/locations-and-groups | 店舗一覧取得(既存 API を拡張:resolved + override を含める) |
PATCH | /api/mappy/admin/locations/{loc}/keyword-limit | 店舗別上限を upsert(新設) |
DELETE | /api/mappy/admin/locations/{loc}/keyword-limit | override 解除(新設) |
Lưu ý: nằm dưới dạng 1 tab trong UserDetail. Tab Thông tin tài khoản / Cài đặt thông báo chỉ hiển thị dummy (giữ nguyên implement cũ, không sửa trong case này).
Thành phần màn hình
Phần header (kế thừa từ MappyUserDetail)
| Thành phần | Vai trò |
|---|---|
| Breadcrumb | Quản trị / MAPPY管理 (danh sách user) / loginId user |
| Chip thông tin user | Plan / giới hạn plan / kỳ hợp đồng / số shop sở hữu |
| Tab bar | Thông tin tài khoản / Cài đặt thông báo / Giới hạn từ khóa (kèm badge số override) |
Toolbar trong tab "Giới hạn từ khóa"
| Thành phần | Vai trò |
|---|---|
| Mô tả | "Có thể đặt giới hạn từ khóa 8〜15 cho từng cửa hàng dưới user này…" |
| Toggle audit log | Hiển thị inline 5 thay đổi gần nhất |
| Nút lưu hàng loạt | Chỉ active khi có thay đổi, kèm badge số dòng đã đổi |
Bảng danh sách cửa hàng
| Cột | Nội dung | Có thể sửa |
|---|---|---|
| Tên cửa hàng | Tên + location_id | Không (link tới chi tiết shop) |
| Plan value | Giá trị mặc định từ user.plan.maxKeywords | Không |
| Giới hạn hiện tại | Giá trị resolved (badge màu thể hiện trạng thái) | Không |
| Giới hạn mới | Stepper input 8〜15 | Có |
| Memo | Ghi chú sales / cơ sở tính phí (255 ký tự) | Có |
| Hiệu lực | effective_from 〜 effective_to | Không (tự set) |
| Thao tác | Nút "Gỡ" → xoá override | Có |
Quy tắc màu badge
| Trạng thái | Màu | Ý nghĩa |
|---|---|---|
| Chỉ plan value | Xám | Không có override, max_keywords = NULL |
| Đang override | Vàng | Vượt plan value nhưng dưới 15 |
| Tối đa | Xanh lá | Đặt 15 (giới hạn hệ thống) |
Filter
- Tìm kiếm tên cửa hàng incremental
- Select: tất cả / có override / chỉ plan value
Flow thao tác
Case 1: Nâng 1 cửa hàng từ 8 → 15
Case 2: Gỡ override (trở về plan value)
| Bước | Thao tác | Kết quả |
|---|---|---|
| 1 | Nhấn nút "Gỡ" trên dòng đó | Input về 8 (plan default), dòng được highlight "changed" |
| 2 | Nhấn nút lưu hàng loạt | Gọi DELETE /api/mappy/admin/locations/{loc}/keyword-limit |
| 3 | API soft-delete bản ghi | Set effective_to = CURDATE() - 1 day |
| 4 | Ghi ActivityLog | "Shop XYZ: 12 → 8, by xxx" |
Case 3: Sửa hàng loạt
Edit nhiều input → nút lưu hàng loạt hiện "(N dòng)" → 1 transaction PATCH tất cả.
Validation
| Mục | Quy tắc | Hiển thị lỗi |
|---|---|---|
| max_keywords | Số nguyên, 8 ≤ N ≤ 15 | Viền ô đỏ + tooltip lý do |
| memo | 0〜255 ký tự | Vượt → disable nút lưu |
| effective_to | NULL hoặc ≥ effective_from | Backend trả 400 |
| override = plan value | Không warning, UI hiểu là DELETE | — |
Tích hợp ActivityLog
Khi PATCH / DELETE thành công, tự ghi vào ActivityLog. Cấu trúc:
{
"action": "keyword_limit.updated",
"actor_id": 42,
"target_type": "gbp_location",
"target_id": 8801,
"before": { "max_keywords": 8, "memo": null },
"after": { "max_keywords": 15, "memo": "Sales #4521 / +¥9,800/tháng" },
"created_at": "2026-06-01 14:32:00"
}Thông tin này là nguồn cho việc tích hợp nền tảng tính phí (case tiếp theo) mở rộng payload.
Responsive
| Width | Hành vi |
|---|---|
| PC (≥768px) | Hiển thị toàn bộ cột, không scroll ngang |
| Mobile (<768px) | Ẩn cột memo & effective, ưu tiên core (tên shop / sửa limit / lưu) |
Phân quyền
| Role | Xem | Sửa | Gỡ |
|---|---|---|---|
admin | ✅ | ✅ | ✅ |
cs_operator | ✅ | ✅ (chỉ 8〜12) | ✅ |
sales_viewer | ✅ | ❌ | ❌ |
Cài đặt max_keywords > 12 chỉ admin được làm (cần xác nhận sales / billing).
Accessibility
- Stepper là tổ hợp
<button>+<input type="number">(thao tác bàn phím được) - Dòng changed không chỉ đổi nền mà còn có
aria-labelcho screen reader - Không trap focus (do là inline edit, không phải modal)