# Kế hoạch triển khai: Tính năng nâng cấp & Sửa lỗi hệ thống YoTrip Bản kế hoạch này phác thảo chi tiết phương án kỹ thuật để thực hiện các tính năng mới và khắc phục các lỗi tồn tại trong dự án theo yêu cầu. --- ## User Review Required > [!IMPORTANT] > - **Cập nhật database schema (Prisma Migrations)**: Chúng ta cần thêm bảng mới `TourNote` và `RecommendedLocation`, đồng thời bổ sung các trường `isDeleted` và `deletedAt` cho bảng `Tour` và `Photo` để phục vụ tính năng Thùng rác (Trash Bin). Việc này yêu cầu chạy prisma migrations (`npx prisma migrate dev`). > - **Thay đổi Nginx config**: Cấu hình Nginx trong Docker production cần bổ sung `client_max_body_size 50M;` để hỗ trợ tải ảnh lớn lên proxy trước khi nén trên client, tránh lỗi HTTP 413. > - **Cơ chế Hybrid Lưu trữ Ghi chú (Notes)**: Ghi chú sẽ được lưu trữ song song (hybrid) cả ở `localStorage` (để hiển thị tức thời và hoạt động offline) lẫn đồng bộ lên server (để lưu trữ lâu dài và cho phép Admin kiểm duyệt/moderation). Khi mở trang ghi chú, ứng dụng sẽ tải dữ liệu từ cache `localStorage` trước, sau đó fetch dữ liệu mới nhất từ server để đồng bộ và cập nhật lại cache. --- ## Proposed Changes ### 1. Cơ sở dữ liệu (Database Schema) #### [MODIFY] [schema.prisma](file:///home/locpham/travelplanning/backend/prisma/schema.prisma) - **Bảng `Photo` & `Tour`**: Bổ sung hai trường `isDeleted Boolean @default(false)` và `deletedAt DateTime?` để hỗ trợ cơ chế soft-delete (chuyển vào thùng rác). - **Bảng `ModerationSetting`**: Thêm trường `trashRetentionDays Int @default(30)` để lưu trữ số ngày lưu trữ trong thùng rác do admin quy định. - **Thêm bảng `TourNote`**: Lưu trữ ghi chú của Tour liên kết với `Tour` và `User` sở hữu: ```prisma model TourNote { id String @id @default(uuid()) tourId String tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) title String content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt isDeleted Boolean @default(false) deletedAt DateTime? } ``` - **Thêm bảng `RecommendedLocation`**: Lưu trữ các địa điểm đề xuất: ```prisma model RecommendedLocation { id String @id @default(uuid()) type String // e.g. "RESTAURANT", "HOTEL", "HOMESTAY" name String phone String? email String? address String? latitude Float? longitude Float? description String @db.Text stars Int @default(5) // Số sao đánh giá từ 1 đến 5 isApproved Boolean @default(false) createdAt DateTime @default(now()) } ``` --- ### 2. Backend API (`backend/src/main.ts`) - **Soft-Delete cho Tours, Photos, và Notes**: - Cập nhật hàm `deleteTour`, `deletePhoto` để chỉ set `isDeleted: true` và `deletedAt: new Date()` thay vì xóa vĩnh viễn. - Cập nhật tất cả các truy vấn tour, ảnh (public & private) để loại trừ các bản ghi có `isDeleted: true`. - **TourNote API**: - `GET /api/v1/tours/:tourId/notes`: Lấy danh sách ghi chú của một tour (phải là thành viên tour hoặc admin). - `POST /api/v1/tours/:tourId/notes`: Tạo ghi chú cho tour. - `PUT /api/v1/tours/:tourId/notes/:noteId`: Sửa ghi chú. - `DELETE /api/v1/tours/:tourId/notes/:noteId`: Soft-delete ghi chú của tour. - **RecommendedLocation API**: - `GET /api/v1/recommendations`: Lấy danh sách địa điểm đề xuất đã duyệt (`isApproved: true`). - `POST /api/v1/recommendations`: Người dùng gửi đề xuất địa điểm mới. - `GET /api/v1/admin/recommendations`: Admin xem toàn bộ danh sách đề xuất. - `PATCH /api/v1/admin/recommendations/:id/approve`: Admin phê duyệt địa điểm đề xuất. - `DELETE /api/v1/admin/recommendations/:id`: Admin xóa đề xuất. - **Admin Trash Bin API**: - `GET /api/v1/admin/trash`: Lấy danh sách các đối tượng trong thùng rác (phân loại Tours, Photos, Notes) kèm thông tin ngày xóa. - `POST /api/v1/admin/trash/restore`: Phục hồi các đối tượng được chọn (`isDeleted: false`). - `POST /api/v1/admin/trash/delete-permanent`: Xóa vĩnh viễn các đối tượng được chọn khỏi ổ đĩa và DB. - `PATCH /api/v1/admin/trash/retention-days`: Cập nhật cấu hình `trashRetentionDays`. - **Cronjob dọn dẹp tự động (Background Service)**: - Khởi tạo một task chạy định kỳ (24 giờ một lần) quét qua các bảng `Tour`, `Photo`, `TourNote` có `isDeleted: true` và `deletedAt` đã quá hạn so với cấu hình `trashRetentionDays` để tự động xóa vĩnh viễn bản ghi và tệp vật lý. --- ### 3. Frontend & UI #### [MODIFY] [nginx.conf](file:///home/locpham/travelplanning/frontend/nginx.conf) - Thêm dòng `client_max_body_size 50M;` vào cấu hình block `server` của Nginx để khắc phục triệt để lỗi `413 (Request Entity Too Large)`. #### [NEW] [image.ts](file:///home/locpham/travelplanning/frontend/src/utils/image.ts) - Viết hàm tiện ích nén ảnh trên Client sử dụng Canvas API, giảm kích thước ảnh về chiều dài nhất tối đa **2048px (2K)**, chuyển định dạng sang `image/jpeg` chất lượng `0.85` trước khi tải lên. - Áp dụng hàm nén này cho: - Chụp ảnh/Tải ảnh ẩn danh ở [LandingPage.tsx](file:///home/locpham/travelplanning/frontend/src/pages/LandingPage.tsx) - Tải ảnh lên trong Tour ở [AddPhotoModal.tsx](file:///home/locpham/travelplanning/frontend/src/components/AddPhotoModal.tsx) - Các vị trí upload ảnh khác. #### [MODIFY] [LoginModal.tsx](file:///home/locpham/travelplanning/frontend/src/components/LoginModal.tsx) - Thêm class CSS `max-h-[90vh] overflow-y-auto` vào container chính của modal để tránh bị che mất nút đăng nhập trên các trình duyệt di động có chiều cao màn hình nhỏ hoặc khi bàn phím ảo hiển thị. #### [MODIFY] [TourDetailPage.tsx](file:///home/locpham/travelplanning/frontend/src/pages/TourDetailPage.tsx) - **Khắc phục lỗi xuất PDF (`oklab` error)**: - Thay vì clone trực tiếp DOM timeline chứa màu oklab phức tạp của Tailwind v4, chúng ta sẽ xây dựng trực tiếp một phần tử HTML `div` đơn giản, dùng cấu trúc bảng hoặc danh sách căn lề cơ bản, sử dụng màu HEX tiêu chuẩn (`#ffffff`, `#3b82f6`, v.v.). - Trước khi gọi `html2pdf().from(element).save()`, tạm thời đặt `.disabled = true` cho tất cả các thẻ `