Files
travelplanning/NOTE_EDIT.md
T

7.6 KiB

NOTE_EDIT.md

MyNotePage.tsx

Đường dẫn: frontend/src/pages/MyNotePage.tsx

Mục đích

Trang quản lý ghi chú cá nhân (My Notes) của người dùng trong một tour. Hỗ trợ tạo, sửa, xóa, tìm kiếm ghi chú và đồng bộ dữ liệu giữa client và server.

Cấu trúc & Luồng hoạt động chính

  • State quản lý:
    • notes: danh sách ghi chú hiện tại
    • isCreating: điều khiển hiển thị form tạo/sửa ghi chú
    • editingNoteId: xác định đang sửa ghi chú nào
    • noteForm: lưu trữ form data { title, content }
    • searchQuery: từ khóa tìm kiếm
  • Render có 3 nhánh:
    1. Không tạo ghi chú + không có ghi chú: hiển thị màn hình trống + nút "Tạo ghi chú mới"
    2. isCreating = true: hiển thị form nhập tiêu đề + ReactQuill + nút Lưu/Hủy
    3. filteredNotes.length > 0: hiển thị danh sách ghi chú dạng grid card
  • Lưu ghi chú (handleSaveNote):
    • Nếu đang edit: cập nhật note trong notes gọi API PUT
    • Nếu create mới: tạo temp_ id, thêm vào đầu mảng notes, gọi API POST để lấy id thật
    • Sau khi API thành công: cập nhật id thật và đánh dấu synced: true
  • Xóa ghi chú: hỏi confirm → đánh dấu deletedLocally: true → gọi API DELETE → xóa khỏi UI
  • Đồng bộ offline (syncLocalNotesToServer):
    • Xử lý tuần tự 3 nhóm: deletedLocallytemp_ (chưa đồng bộ) → edited (synced: false)
    • Mỗi nhóm có retry riêng, lỗi chỉ log console
  • Fetch & Cache (fetchServerNotes + useEffect):
    • Khi load trang: đọc localStorage key my_journey_notes_{tourId} làm cache ban đầu
    • Gọi API GET /api/v1/tours/${tourId}/notes để lấy dữ liệu server
    • Merge: giữ unsyncedNotes (temp_, synced:false, deletedLocally) + lọc khỏi server
    • Sort theo createdAt giảm dần (mới nhất lên đầu)
    • Lưu lại localStorage sau mỗi thay đổi
  • Tìm kiếm (filteredNotes):
    • Lọc theo title HOẶC content (strip HTML tag)
    • Không lọc note đã deletedLocally

ReactQuill configuration

  • Module: table, header(1,2), bold/italic/underline/strike, align, indent, bullet/check list, link/image/table/clean
  • Custom Icons: gán SVG từ lucide-react vào Quill.import('ui/icons')
  • Table handler: dùng prompt nhập số hàng/cột → gọi quill.getModule('table').insertTable()

Theme/Class cần lưu ý

  • Wrapper ReactQuill: bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm min-h-[500px] flex flex-col
  • CSS class ở ReactQuill đã fix: .ql-container lấy h-full, min-h-0; .ql-editor lấy flex-1, overflow-y-auto để nội dung dài cuộn trong khung
  • Note card: bg-[var(--surface)] dark:bg-[var(--surface-muted)] p-5 rounded-3xl
  • Màu chính: text-amber-500 cho buttons, header

TourDetailPage.tsx

Đường dẫn: frontend/src/pages/TourDetailPage.tsx

Mục đích

Trang chi tiết tour, tổng hợp toàn bộ thông tin: bản đồ interactive, lộ trình, gallery ảnh, manage members, quản lý chi phí, chat, và chức năng share. Trang này rất nặng (~3400 dòng) nên cần chú ý các captured props và callback pattern.

Cấu trúc chính

  • Header: sticky top bar có các tab: Timeline | Spot / Location | Expenses | Chat
  • Tabs switch: điều khiển hiển thị ItineraryTimeline, ExpenseManager, TourChat
  • Map section: leaflet map kết hợp MarkerCluster, Polyline vẽ lộ trình OSRM, Recenter button

Key logic/Data flow

  • useTourStore là state chính: lưu currentTour, legs, locations, userRole, mapCenter
  • OSRM Routes: kết quả từ backend đa segment, combineSegmentRoutes() gộp thành 1 polyline
  • Sync data: addLocation, editLocation, deleteLocation gọi API và cập nhật store
  • PDF Export: dùng jsPDF + jspdf-autotable xuất hóa đơn/nhật ký tour
  • ShareJourney button: tạo public link hoặc mở share modal
  • Socket.IO: io() kết nối realtime cho chat và comment
  • Offline-first: nhiều actions gọi API nhưng không block UI; có try/catch và fallback

Các modal/components con được gọi

  • AddLocationModal, MembersTab, ExpenseManager, CommentModal, AddPhotoModal, TourChat
  • MapContextMenu: menu right-click trên map để bắt đầu/kết thúc/add to leg
  • MapRotationHandler: xoay map theo hướng di chuyển (cumulative rotation để tránh nhảy -360→0)
  • MapHoverTip: tooltip xuất hiện sau 3s khi hover trên map (cho user biết tip right-click)

Theme & Styling cần lưu ý

  • Map marker icon: dùng unpkg.com/leaflet@1.9.4 (fixed URL) do Vite bundler không nhận asset mặc định
  • Màu primary tour: text-blue-600, accent: text-amber-500
  • Sticky header: bg-[var(--surface)]/80 backdrop-blur-md

ItineraryTimeline.tsx

Đường dẫn: frontend/src/components/ItineraryTimeline.tsx

Mục đích

Component hiển thị lộ trình đa chặng (multi-leg itinerary) dạng timeline accordion. Mỗi leg là 1 section có thể expand/collapse độc lập (exclusive mode).

Cấu trúc chính

  • props:
    • onAddLocation(legId, isStart?, isEnd?): callback mở modal thêm địa điểm
    • onEditLocation(location): callback mở modal sửa địa điểm
    • onQuickNote(name): callback mở modal ghi chú nhanh
    • onNavigate(location): callback điều hướng trên map
    • onSuccess(): callback sau khi xóa/edit thành công
    • isPublicView: boolean, hide edit/delete buttons khi true
  • State:
    • expandedStageId: id của leg đang mở (chỉ 1 mở tại 1 thời điểm) → mặc định leg đầu tiên
    • isEditModalOpen, editingLegData: modal sửa chặng
    • isCommentModalOpen, commentLocationId, commentLocationName: modal comment

Logic đặc biệt

  • Exclusive expansion: toggleStageExpanded đảm bảo chỉ 1 leg mở tại 1 thời điểm (click lại để đóng)
  • Distance & Travel time:
    • Tính haversine giữa 2 điểm liên tiếp (allLocations flat)
    • Convert giữa các chặng: lấy prevLegLastLoc → tính khoảng cách → hiển thị "Tiếp nối từ ..."
    • leg.totalDistance + formatTravelTime() để đổi sang "X giờ Y phút"
  • Dwell time: thời gian dừng tại mỗi điểm = plannedEnd - plannedStart
  • Marker milestones:
    • Start point: plannedStart timestamp = 0
    • End point: plannedEnd timestamp = 0
    • TimeVariance: so sánh actual vs planned, hiển thị "Trễ X phút" / "Đúng giờ" / "Sớm X phút"
  • Accordion CSS: dùng grid-template-rows: 0fr → 1fr transition (folder-child-content-box) để animate mở/đóng mượt
  • Comment count: handleCommentIncrement/Decrement trực tiếp mutate useTourStore để cập nhật UI gần như tức thì mà không cần re-fetch API

Giao tiếp với cha (TourDetailPage)

  • onAddLocation?.(leg.id, isFirst, isLast) ↔ mở AddLocationModal
  • onEditLocation(loc) ↔ pre-fill modal edit
  • onNavigate(loc) ↔ mở notification/guide "Đang điều hướng" rồi gọi map navigate
  • onSuccess?.() gọi sau delete/edit thành công để refresh từ cha

Lưu ý chung

  • Cả 3 file đều dùng Tailwind CSS + theme variables từ index.css
  • Offline/sync-first: ghi chú lưu localStorage trước, API sau
  • Khi gọi API từ các hook, header gắn Authorization: Bearer ${localStorage.getItem('token')}
  • react-quill-new dùng thay cho react-quill do tương thích React 18+