Cho phép chọn trực tiếp điểm trên bản đồ

This commit is contained in:
2026-06-13 21:34:07 +07:00
parent 25fcd5d926
commit 578b03d808
12 changed files with 489 additions and 248 deletions
+152 -67
View File
@@ -113,30 +113,36 @@ Lưu trữ tài nguyên đa phương tiện gắn với bối cảnh chuyến đ
* Hệ thống check `tour_members`.
* Nếu thuộc diện *External Viewer* hoặc *Photo Only*: Ẩn hoàn toàn tab "Kế hoạch chặng hành trình" và "Tổng số tiền", chỉ hiển thị giao diện Grid hình ảnh (`album`).
## 6. Thiết kế UI Layout: Màn hình Tổng quan Chuyến đi (Tour Dashboard)
# 6. THIẾT KẾ GIAO DIỆN NGƯỜI DÙNG (TOUR DASHBOARD & MAP ITINERARY)
Đây là màn hình Hub chính sau khi người dùng truy cập vào một Tour cụ thể. Giao diện được thiết kế để tối ưu hóa trải nghiệm trên thiết bị di động và kiểm soát hiển thị nội dung động dựa trên vai trò của thành viên (`ParticipantRole`).
Đây là màn hình Hub chính sau khi người dùng truy cập vào một Tour cụ thể. Giao diện được thiết kế để tối ưu hóa trải nghiệm trên thiết bị di động (Mobile-first) và kiểm soát hiển thị nội dung động dựa trên vai trò của thành viên (`ParticipantRole`).
### 6.1. Thành phần Giao diện chính
## 6.1. Thành phần Giao diện chính
* **Top Banner (Khu vực tiêu đề):**
* **Cover Image:** Ảnh bìa của tour (lấy từ ảnh đầu tiên trong Album hoặc ảnh phong cảnh mặc định).
* **Thông tin Tour:** Hiển thị tên Tour và khoảng thời gian diễn ra (`startDate` -> `endDate`).
* **Member Avatars:** Danh sách avatar các thành viên tham gia (`TourParticipant`). Cho phép nhấn vào để xem chi tiết danh sách hoặc mời thêm người (nếu có quyền).
* **Top Banner (Khu vực tiêu đề):**
* **Cover Image:** Ảnh bìa của tour (lấy từ ảnh đầu tiên trong Album hoặc ảnh phong cảnh mặc định).
* **Thông tin Tour:** Hiển thị tên Tour và khoảng thời gian diễn ra (`startDate` -> `endDate`).
* **Member Avatars:** Danh sách avatar các thành viên tham gia (`TourParticipant`). Cho phép nhấn vào để xem chi tiết hoặc mời thêm người (nếu có quyền).
* **Financial Quick-View Widget (Khối tài chính nhanh):**
* Hiển thị `totalCost` hiện tại của toàn bộ chuyến đi.
* **Logic Ẩn/Hiện động (RBAC):**
* *Hiển thị:* Với các role `OWNER`, `MANAGER`, `MEMBER`.
* *Ẩn:* Với các role `MEMBER_NO_FINANCE` hoặc `VIEWER_ONLY`. Thay thế khối này bằng một câu quote truyền cảm hứng du lịch (Ví dụ: *"Đừng nghe họ nói, hãy tự mình đi xem"*).
* **Bottom Navigation Bar hoặc Tab View:** Hệ thống chia làm 4 tab chính để tránh nhồi nhét dữ liệu:
1. **Lộ trình (Itinerary):** Hiển thị danh sách `Leg` & `Location`. Đây là tab mặc định khi vào Tour.
2. **Chi phí (Finance):** Quản lý các `Expense`. Chỉ hiển thị cho các role có quyền truy cập tài chính.
3. **Album ảnh (Gallery):** Hiển thị lưới hình ảnh `Photo` được chia sẻ trong chuyến đi.
4. **Thành viên & Cài đặt (Settings):** Khu vực dành riêng cho `OWNER` hoặc `MANAGER` để quản lý thành viên, phân quyền và cấu hình tour.
* **Financial Quick-View Widget (Khối tài chính nhanh):**
* Hiển thị `totalCost` hiện tại của toàn bộ chuyến đi.
* **Logic Ẩn/Hiện động (RBAC):**
* *Hiển thị:* Với các role `OWNER`, `MANAGER`, `MEMBER`.
* *Ẩn:* Với các role `MEMBER_NO_FINANCE` hoặc `VIEWER_ONLY`. Thay thế khối này bằng một câu quote truyền cảm hứng du lịch (Ví dụ: *"Đừng nghe họ nói, hãy tự mình đi xem"*).
* **Bottom Navigation Bar hoặc Tab View:** Hệ thống chia làm 4 tab chính để tránh nhồi nhét dữ liệu:
1. **Lộ trình (Itinerary):** Hiển thị danh sách `Leg` & `Location` tích hợp Bản đồ tương tác. *Đây là tab mặc định.*
2. **Chi phí (Finance):** Quản lý các `Expense`. Chỉ hiển thị cho các role có quyền truy cập tài chính.
3. **Album ảnh (Gallery):** Hiển thị lưới hình ảnh `Photo` được chia sẻ trong chuyến đi.
4. **Thành viên & Cài đặt (Settings):** Khu vực dành riêng cho `OWNER` hoặc `MANAGER` để quản lý thành viên, phân quyền và cấu hình tour.
---
## 6.2. Sơ đồ bố cục tổng thể (Wireframe mô phỏng)
### 6.2. Sơ đồ bố cục (Wireframe mô phỏng)
```text
+---------------------------------------+
| [ < ] Tên Chuyến Đi [ . . ] | <-- Header bar
@@ -147,82 +153,161 @@ Lưu trữ tài nguyên đa phương tiện gắn với bối cảnh chuyến đ
| [Avatar][Avatar][Avatar] +3 |
+---------------------------------------+
| |
| { TỔNG CHI PHÍ: 2.500.000 VND } | <-- Finance Widget (Hoặc Quote)
| { TỔNG CHI PHÍ: 2.500.000 VND } | <-- Finance Widget (Hoặc Quote)
| |
+---------------------------------------+
| [ Lộ trình ] [ Chi phí ] [ Ảnh ] [S] | <-- Tab Navigation
+---------------------------------------+
| |
| (Nội dung tab hiện tại) |
| TAB LỘ TRÌNH (BẢN ĐỒ + DANH SÁCH) |
| |
+---------------------------------------+
```
+---------------------------------------+
### 6.3. Tab Lộ Trình: Cấu trúc Cây Phân Cấp (Timeline View)
---
Đây là phần phức tạp nhất vì dữ liệu có cấu trúc lồng nhau: **Tour ➔ Leg (Chặng) ➔ Location (Địa điểm)**. Giao diện sử dụng mô hình Timeline đứng (Vertical Timeline) để hiển thị lịch trình di chuyển một cách chuẩn mực.
## 6.3. Tab Lộ Trình: Bản Đồ Tương Tác & Cấu Trúc Chặng (Map-Driven Timeline)
#### A. Tầng 1 - Chọn Chặng (Leg Picker)
* **UI Component:** Sử dụng **Horizontal Tab Scroller** đặt ở phía trên cùng của tab.
* **Chức năng:** Thay vì kéo một danh sách dài vô tận, người dùng có thể chọn nhanh các Chặng (ví dụ: Chặng 1, Chặng 2, Chặng 3 hoặc Ngày 1, Ngày 2...).
* **Logic:** Khi người dùng nhấn vào một `Leg`, danh sách các `Location` tương ứng sẽ được hiển thị phía dưới dựa vào trường `sequence`.
Để tối ưu hóa UX trên di động, Tab Lộ trình được chia thành 2 chế độ hiển thị bằng một nút bấm chuyển đổi nhanh (Toggle Switch): **Chế độ Bản đồ (Map View)** và **Chế độ Danh sách (Timeline View)**. Khi chỉnh sửa/khởi tạo, Chế độ Bản đồ sẽ làm chủ đạo.
### A. Luồng Khởi Tạo & Định Vị Lộ Trình (Google Maps Style)
Đối với các tài khoản có quyền chỉnh sửa (`OWNER`, `MANAGER`), luồng thiết lập lộ trình không gian được thực hiện qua các bước tương tác trực quan:
#### Giai đoạn 1: Đóng khung điểm Đầu - Cuối
1. **Khai báo Tour:** User nhập thông tin cơ bản ở bảng cấu hình.
2. **Xác định Điểm Đầu:** Tìm kiếm trên ô Search của bản đồ hoặc rê chuột/di tâm màn hình đến vị trí mong muốn ➔ **Click chuột phải** (hoặc **Nhấn giữ** nếu dùng mobile) ➔ Chọn **"Bắt đầu từ đây"**.
* *Phản hồi UI:* Xuất hiện Marker màu Xanh lá `[S]`. Tạo bản ghi `Location` đầu tiên.
3. **Xác định Điểm Cuối:** Di chuyển đến điểm đích ➔ **Click chuột phải / Nhấn giữ** ➔ Chọn **"Kết thúc ở đây"**.
* *Phản hồi UI:* Xuất hiện Marker màu Đỏ `[E]`.
4. **Khai báo số lượng Chặng:** Một ô thông báo nổi (Pop-up) yêu cầu nhập: *"Chuyến đi này chia làm bao nhiêu chặng?"*. Khi nhập số $N$, hệ thống tự động sinh ra $N$ bản ghi `Leg` với `sequence` từ $1$ đến $N$ ở trạng thái chờ rỗng.
#### Giai đoạn 2: Ghim điểm tham quan vào Chặng (Map-to-Leg)
1. **Chấm điểm:** Người dùng gõ tìm kiếm tọa độ/địa danh hoặc click trực tiếp lên bản đồ.
2. **Phân bổ vào chặng:** Click chuột phải / Nhấn giữ vào điểm vừa chọn ➔ Menu ngữ cảnh hiện ra danh sách các chặng đã khai báo ở Giai đoạn 1 ➔ Chọn số thứ tự chặng (Ví dụ: `Thêm vào Chặng 1`).
3. **Tự động sắp xếp & Vẽ tuyến:** * Dữ liệu địa điểm lập tức được xếp vào mảng con của `Leg` có `sequence: 1`.
* Hệ thống tự động sắp xếp vị trí hiển thị theo thứ tự thêm vào (có thể kéo thả đổi thứ tự thủ công sau).
* Bản đồ tự động gọi API Routing để vẽ đường nối liền mạch: **`[S]` ➔ Các điểm Chặng 1 ➔ Các điểm Chặng 2 ➔ `[E]**`.
#### Giai đoạn 3: Tối ưu Lịch trình & Thời gian (Data Refinement)
Sau khi "bộ khung" không gian trên bản đồ đã hoàn tất, người dùng chuyển sang giao diện danh sách để cấu hình chi tiết tuyến tính thời gian:
* Bổ sung ngày giờ kế hoạch (`plannedStart`, `plannedEnd`) cho từng `Location`.
* Chọn loại địa điểm (`LocationType`: `MOVE`, `VISIT`, `REST`, `EAT`) để hệ thống đồng bộ Icon hiển thị.
* Thêm ghi chú (`note`) tổng quan cho từng `Leg`.
```text
WIREFRAME CHẾ ĐỘ BẢN ĐỒ (MAP VIEW MODE):
+---------------------------------------+
| [ Tìm kiếm địa điểm trên bản đồ... ] |
+---------------------------------------+
| |
| [S] (Điểm xuất phát) |
| \ |
| \____ [Icon: EAT] Nhà hàng A |
| \ |
| \____ [E] (Điểm cuối) |
| |
| CONTEXT MENU KHI NHẤN GIỮ/CHUỘT PHẢI: |
| +---------------------------------+ |
| | o Bắt đầu từ đây | |
| | o Kết thúc ở đây | |
| | o Thêm vào lộ trình > [Chặng 1]| |
| +------------------------[Chặng 2]| |
+---------------------------------------+
```
---
### B. Chế Độ Hiển Thị Timeline Đứng (Dành cho việc Theo Dõi Tiến Độ)
Sau khi lưu lộ trình, khi đi du lịch thực tế, người dùng sẽ chủ yếu xem ở giao diện **Timeline đứng** để kiểm tra tiến độ:
#### Tầng 1 - Chọn Chặng (Leg Picker)
* **UI Component:** Sử dụng **Horizontal Tab Scroller** đặt ở phía trên cùng của tab.
* **Chức năng:** Người dùng có thể chọn nhanh các Chặng (ví dụ: Chặng 1, Chặng 2, Chặng 3...) để lọc dữ liệu hiển thị, tránh kéo màn hình quá dài.
* **Logic:** Nhấn vào `Leg` nào, danh sách các `Location` được xếp theo đúng `sequence` của chặng đó mới hiển thị phía dưới.
#### Tầng 2 - Danh sách Địa điểm (Location Cards)
#### B. Tầng 2 - Danh sách Địa điểm (Location Cards)
Mỗi địa điểm là một thẻ (card) nằm trên đường timeline đứng, bao gồm các đặc điểm nhận diện:
* **Icon nhận diện theo loại (LocationType):**
* `MOVE`: Icon Phương tiện (Xe bus, Máy bay).
* `VISIT`: Icon Tham quan (Kính thiên văn, Lá cờ).
* `REST`: Icon Nghỉ ngơi (Giường ngủ).
* `EAT`: Icon Ẩm thực (Dao dĩa).
* **Icon nhận diện theo loại (LocationType):**
* `MOVE`: Icon Phương tiện (Xe bus, Máy bay).
* `VISIT`: Icon Tham quan (Kính thiên văn, Lá cờ).
* `REST`: Icon Nghỉ ngơi (Giường ngủ).
* `EAT`: Icon Ẩm thực (Dao dĩa).
* **Trạng thái & Tiến độ (LocationStatus):**
* **Trạng thái PENDING:**
* *UI:* Card có viền nét đứt (dashed), màu xám nhạt. Hiển thị giờ dự kiến (`plannedStart`).
* *Hành động:* Nút "Bắt đầu" (Chỉ hiển thị cho vai trò `OWNER` hoặc `MANAGER`).
* **Trạng thái Đang diễn ra (Active):**
* *UI:* Card được làm sáng (highlight), hiển thị giờ bắt đầu thực tế (`actualStart`).
* *Hành động:* Nút "Hoàn thành".
* **Trạng thái COMPLETED:**
* *UI:* Card có dấu tích xanh, màu nền chuyển sang tone dịu (ví dụ: xanh lá nhạt), hiển thị giờ kết thúc thực tế (`actualEnd`).
### 6.4. Tab Chi Phí (Expense Management)
* **Trạng thái & Tiến độ (LocationStatus):**
* **Trạng thái PENDING:** * *UI:* Card có viền nét đứt (dashed), màu xám nhạt. Hiển thị giờ dự kiến (`plannedStart`).
* *Hành động:* Nút "Bắt đầu" (Chỉ hiển thị cho vai trò `OWNER` hoặc `MANAGER`).
* **Trạng thái Đang diễn ra (Active):**
* *UI:* Card được làm sáng (highlight), hiển thị giờ bắt đầu thực tế (`actualStart`).
* *Hành động:* Nút "Hoàn thành".
* **Trạng thái COMPLETED:**
* *UI:* Card có dấu tích xanh, màu nền chuyển sang tone dịu (ví dụ: xanh lá nhạt), hiển thị giờ kết thúc thực tế (`actualEnd`).
---
## 6.4. Tab Chi Phí (Expense Management)
Cấu trúc dữ liệu của hệ thống cho phép gắn `Expense` (Chi phí) vào một `Leg` (Chặng) hoặc gắn trực tiếp vào một `Location` (Địa điểm) cụ thể.
#### A. Phân tích trực quan
* **Biểu đồ tròn (Donut Chart):** Hiển thị ở đầu tab để phân tích chi phí theo danh mục (`category`: FOOD, TRANSPORT, ACCOMMODATION, TICKET...). Giúp người dùng nhìn trực quan ngân sách đang đổ vào đâu nhiều nhất.
### A. Phân tích trực quan
#### B. Danh sách hiển thị (List View)
* **Hiển thị dòng tiền:** Liệt kê các khoản chi tiêu kèm mô tả (`description`) và số tiền chính xác (`amount`).
* **Nhãn bối cảnh (Context Tag):** Mỗi dòng chi phí có một nhãn nhỏ đi kèm giúp người dùng xác định bối cảnh chi tiêu:
* Nếu `location_id` có dữ liệu: Hiển thị nhãn **“Tại: [Tên địa điểm]”**.
* Nếu chỉ có `leg_id`: Hiển thị nhãn **“Thuộc: [Tên Chặng]”**.
* Điều này giúp người dùng nắm bắt nhanh khoản tiền này được tiêu dùng tại vị trí nào trong hành trình.
* **Biểu đồ tròn (Donut Chart):** Hiển thị ở đầu tab để phân tích chi phí theo danh mục (`category`: FOOD, TRANSPORT, ACCOMMODATION, TICKET...). Giúp người dùng nhìn trực quan ngân sách đang đổ vào đâu nhiều nhất.
#### C. Nút thêm nhanh (Floating Action Button)
* **Nút +:** Nằm ở góc dưới màn hình để thêm mới chi phí.
* **Logic thông minh:** Khi nhấn vào, hệ thống sẽ tự động chọn sẵn `Leg` hoặc `Location` dựa trên ngữ cảnh người dùng đang xem để giảm thiểu các bước nhập liệu thủ công.
### B. Danh sách hiển thị (List View)
### 6.5. Tab Album Ảnh (Contextual Photo Gallery)
* **Hiển thị dòng tiền:** Liệt kê các khoản chi tiêu kèm mô tả (`description`) và số tiền chính xác (`amount`).
* **Nhãn bối cảnh (Context Tag):** Mỗi dòng chi phí có một nhãn nhỏ đi kèm giúp người dùng xác định bối cảnh chi tiêu:
* Nếu `location_id` có dữ liệu: Hiển thị nhãn **“Tại: [Tên địa điểm]”**.
* Nếu chỉ có `leg_id`: Hiển thị nhãn **“Thuộc: [Tên Chặng]”**.
Bảng `Photo` có trường `locationId` (nullable) và `privacy`. Giao diện cần tận dụng tốt hai trường này để tối ưu hóa việc lưu giữ và quản lý kỷ niệm chuyến đi.
#### A. Chế độ xem thông minh (Smart View)
### C. Nút thêm nhanh (Floating Action Button)
* **Nút +:** Nằm ở góc dưới màn hình để thêm mới chi phí.
* **Logic thông minh:** Khi nhấn vào, hệ thống sẽ tự động bắt bối cảnh (Context-aware). Nếu user đang đứng xem ở `Chặng 2`, form thêm mới sẽ tự động chọn sẵn `leg_id` của Chặng 2 để giảm thiểu các bước nhập liệu thủ công.
---
## 6.5. Tab Album Ảnh (Contextual Photo Gallery)
Bảng `Photo` có trường `locationId` (nullable) và `privacy`. Giao diện cần dạng hóa việc lọc để tối ưu trải nghiệm xem lại kỷ niệm.
### A. Chế độ xem thông minh (Smart View)
Cung cấp 2 chế độ lọc thông qua nút gạt (Toggle Switch):
* **Xem theo dòng thời gian (Timeline Photo):** Ảnh được gom nhóm (`group by`) theo từng `Location`. Đi tới địa điểm nào, ảnh chụp tại đó sẽ hiện ngay bên dưới địa điểm đó hoặc gom thành một cụm để dễ nhớ kỷ niệm gắn liền với lộ trình di chuyển.
* **Xem dạng lưới (Grid View):** Hiển thị dạng lưới ảnh tiêu chuẩn, tương tự các ứng dụng quản lý ảnh (Photos) thông thường trên điện thoại.
#### B. Bộ lọc Quyền riêng tư (PrivacyLevel)
Logic hiển thị tuân thủ nghiêm ngặt theo phân quyền:
* Các ảnh có tag `PRIVATE` chỉ hiển thị duy nhất với chính người tải lên (`uploaderId`).
* Ảnh `TOUR_ONLY` hiển thị cho mọi thành viên có trong Tour.
* **Xem theo dòng thời gian (Timeline Photo):** Ảnh được gom nhóm (`group by`) theo từng `Location`. Đi tới địa điểm nào trên bản đồ/timeline, ảnh chụp tại đó sẽ hiện ngay bên dưới địa điểm đó.
* **Xem dạng lưới (Grid View):** Hiển thị dạng lưới ảnh tiêu chuẩn (3x3 hoặc 4x4), tương tự các ứng dụng quản lý ảnh gốc trên điện thoại.
#### C. Chỉ báo UI (Privacy Indicators)
Trên góc mỗi ảnh hiển thị một icon nhỏ giúp người dùng dễ dàng kiểm soát trạng thái chia sẻ của mình:
* **Hình ổ khóa:** Dành cho ảnh ở chế độ `PRIVATE`.
* **Hình con mắt:** Dành cho ảnh ở chế độ `TOUR_ONLY` hoặc `PUBLIC`.
### B. Bộ lọc Quyền riêng tư (PrivacyLevel) & Chỉ báo UI
* Các ảnh có tag `PRIVATE` chỉ hiển thị duy nhất với chính người tải lên (`uploaderId`).
* Ảnh `TOUR_ONLY` hiển thị cho mọi thành viên có trong Tour.
* **Chỉ báo UI (Privacy Indicators):** Trên góc mỗi ảnh hiển thị một icon nhỏ (Hình ổ khóa cho `PRIVATE`, Hình con mắt cho `TOUR_ONLY` hoặc `PUBLIC`) giúp người dùng dễ dàng kiểm soát trạng thái chia sẻ.
---