From 71007bd9a123f8bb5054f7b69d4a27334f180d85 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Sun, 14 Jun 2026 15:59:27 +0700 Subject: [PATCH] =?UTF-8?q?B=E1=BB=95=20sung=20ng=C6=B0=E1=BB=9Di=20thanh?= =?UTF-8?q?=20to=C3=A1n=20v=C3=A0o=20th=C3=AAm=20=C4=91=E1=BB=8Ba=20=C4=91?= =?UTF-8?q?i=E1=BB=83m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 + ARCHITECTURE.md | 120 +++++++++- AddLocationModal.tsx | 85 ++++--- ItineraryTimeline.tsx | 23 +- dist/AddLocationModal.js | 31 +-- dist/AddLocationModal.js.map | 2 +- dist/ItineraryTimeline.js | 2 +- dist/ItineraryTimeline.js.map | 2 +- dist/jwt.strategy.d.ts | 2 +- dist/main.js | 5 +- dist/main.js.map | 2 +- dist/seed.js | 5 +- dist/seed.js.map | 2 +- dist/tsconfig.build.tsbuildinfo | 2 +- main.ts | 6 +- .../migration.sql | 148 ------------ .../migration.sql | 14 -- migrations/20260613112158/migration.sql | 2 - migrations/20260613114646/migration.sql | 211 ------------------ .../migration.sql | 18 +- .../migrations}/migration_lock.toml | 0 prisma/schema.prisma | 4 + seed.ts | 5 +- 23 files changed, 245 insertions(+), 449 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 migrations/20260613030135_init_travel_planning_schema/migration.sql delete mode 100644 migrations/20260613091754_add_is_admin_field/migration.sql delete mode 100644 migrations/20260613112158/migration.sql delete mode 100644 migrations/20260613114646/migration.sql rename prisma/migrations/{0_init => 20260614085323_init}/migration.sql (88%) rename {migrations => prisma/migrations}/migration_lock.toml (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9a73469 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "continue.enableConsole": true +} \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3e95533..ec23865 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -3,8 +3,126 @@ Tài liệu này mô tả kiến trúc tổng thể, mô hình dữ liệu và các giải pháp kỹ thuật cho tính năng lập kế hoạch du lịch đa người dùng thuộc nền tảng **Travel**. --- + +## 8. Cấu Trúc Thư Mục (Directory Structure) + +```text +/home/locpham/travelplanning/ +├── admin.guard.ts # Middleware kiểm tra quyền admin +├── App.tsx # Component chính của ứng dụng frontend +├── ARCHITECTURE.md # Tài liệu kiến trúc hệ thống +├── CreateTourModal.tsx # Modal cho tạo tour +├── dist/ # Thư mục build output +├── .env # Biến môi trường +├── ExpenseManager.tsx # Component quản lý chi phí +├── ExploreMap.tsx # Component bản đồ khám phá +├── index.tsx # Điểm vào ứng dụng frontend +├── ItineraryTimeline.tsx # Component timeline lịch trình +├── jwt-auth.guard.ts # Middleware xác thực JWT +├── jwt.strategy.ts # Strategy xử lý JWT +├── LandingPage.tsx # Trang landing page +├── LoginModal.tsx # Modal đăng nhập +├── main.ts # Điểm vào ứng dụng backend +├── migrations/ # Migration cũ của ORM khác +│ ├── 20260613030135_init_travel_planning_schema/ +│ │ └── migration.sql +│ ├── 20260613091754_add_is_admin_field/ +│ │ └── migration.sql +│ ├── 20260613112158/ +│ │ └── migration.sql +│ ├── 20260613114646/ +│ │ └── migration.sql +│ └── migration_lock.toml +├── nest-cli.json # Cấu hình NestJS CLI +├── node_modules/ # Thư mục dependencies +├── package.json # Cấu hình project Node.js +├── package-lock.json # Lockfile dependencies +├── postcss.config.js # Cấu hình PostCSS +├── prisma/ # Thư mục Prisma ORM +│ ├── migrations/ +│ │ └── 0_init/ +│ │ └── migration.sql +│ ├── schema.prisma # Schema dữ liệu chính +│ └── prisma.service.ts # Service Prisma +├── rbac.middleware.ts # Middleware phân quyền RBAC +├── README.md # Tài liệu hướng dẫn dự án +├── schema.sql # File SQL schema +├── seed.ts # File seed dữ liệu mẫu +├── SignupPage.tsx # Trang đăng ký tài khoản +├── tailwind.config.ts # Cấu hình TailwindCSS +├── TourDetailPage.tsx # Trang chi tiết tour +├── tsconfig.build.json # Cấu hình TypeScript build +├── tsconfig.json # Cấu hình TypeScript +├── UITourDesign.md # Tài liệu thiết kế UI +├── useTourStore.ts # State management Zustand cho Tour +├── vite.config.ts # Cấu hình Vite bundler +├── .gitignore # Git ignore rules +├── .vscode/ +│ └── settings.json # Cấu hình VS Code +└── index.css # Styles CSS toàn cục +``` + +### Giải thích Cấu trúc Thư mục + +**Frontend Layer:** +- Các file `.tsx` (React components) xử lý giao diện người dùng: `LandingPage`, `TourDetailPage`, `CreateTourModal`, `ExpenseManager`, `ItineraryTimeline`, `ExploreMap`, `UserManagementModal`, `SignupPage`, `LoginModal`, `AddLocationModal` +- Cấu hình TailwindCSS (`tailwind.config.ts`, `index.css`) cho styling +- `useTourStore.ts` - State management với Zustand + +**Backend Layer:** +- `main.ts` - Entry point ứng dụng NestJS/Express +- `expense-engine.service.ts` - Service tính toán và chia chi phí +- `prisma.service.ts` - Service kết nối cơ sở dữ liệu +- Guards & Middleware: `admin.guard.ts`, `jwt-auth.guard.ts`, `rbac.middleware.ts` +- `jwt.strategy.ts` - Xác thực người dùng qua JWT + +**Database Layer:** +- `prisma/schema.prisma` - Định nghĩa schema dữ liệu +- `prisma/migrations/` - Lịch sử thay đổi schema của Prisma +- `migrations/` - Migration cũ (không dùng Prisma) +- `seed.ts` - Chèn dữ liệu mẫu vào cơ sở dữ liệu + +**Configuration:** +- `tsconfig.json`, `tsconfig.build.json` - Cấu hình TypeScript +- `vite.config.ts` - Config bundler Vite +- `nest-cli.json` - Cấu hình NestJS CLI +- `postcss.config.js` - Cấu hình PostCSS cho TailwindCSS +- `.env` - Biến môi trường ứng dụng + +**Documentation:** +- `ARCHITECTURE.md` - Tài liệu kiến trúc hệ thống +- `UITourDesign.md` - Tài liệu thiết kế giao diện người dùng +- `README.md` - Hướng dẫn cài đặt và chạy dự án + +**Tools & IDE:** +- `.vscode/settings.json` - Cấu hình VS Code workspace +- `.gitignore` - Quy tắc bỏ qua file Git + +### Công nghệ trong Dự án + +```json +{ + "frontend": { + "framework": "React + Vite", + "styling": "TailwindCSS + PostCSS", + "state_management": "Zustand", + "map_engine": "Yêu cầu tích hợp Leaflet/Google Maps API" + }, + "backend": { + "framework": "NestJS/Express với TypeScript", + "auth": "JWT Strategy + Guards", + "orm": "Prisma", + "database": "PostgreSQL + PostGIS" + }, + "build_tools": { + "bundler": "Vite", + "compiler": "TypeScript" + } +} +``` + +--- -## 1. Kiến Trúc Tổng Quan (System Overview) Hệ thống được thiết kế theo mô hình **Client-Server** kết hợp với kiến trúc **Modular Monolith** hoặc **Microservices** (tùy quy mô triển khai), chia làm 3 tầng chính: diff --git a/AddLocationModal.tsx b/AddLocationModal.tsx index 8c5b693..27ea3e8 100644 --- a/AddLocationModal.tsx +++ b/AddLocationModal.tsx @@ -70,13 +70,16 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin note: '', expenseAmount: '', expenseCategory: 'OTHER', + expenseDescription: '', + expenseNote: '', + paidById: '', plannedStart: '', plannedEnd: '' }); const [isLoading, setIsLoading] = useState(false); // Gom các selector lại để giảm số lượng Hook gọi nội bộ và tăng hiệu năng - const { legs, addLocation, updateLocation, mapCenter } = useTourStore(); + const { legs, addLocation, updateLocation, mapCenter, currentTour } = useTourStore(); // 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook useEffect(() => { @@ -95,6 +98,9 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin note: editingLocation.note || '', expenseAmount: expense?.amount?.toString() || '', expenseCategory: expense?.category || 'OTHER', + expenseDescription: expense?.description || '', + expenseNote: expense?.note || '', + paidById: expense?.paidById || '', plannedStart: editingLocation.plannedStart ? editingLocation.plannedStart.slice(0, 16) : '', plannedEnd: editingLocation.plannedEnd ? editingLocation.plannedEnd.slice(0, 16) : '' }); @@ -104,6 +110,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin name: '', address: '', legId: initialLegId || (legs.length > 0 ? legs[0].id : ''), note: '', expenseAmount: '', expenseCategory: 'OTHER', + expenseDescription: '', expenseNote: '', paidById: '', plannedStart: '', plannedEnd: '' })); } @@ -146,20 +153,17 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin e.preventDefault(); setIsLoading(true); try { + const payload: any = { + ...formData, + legId: currentLegId, + latitude: parseFloat(formData.latitude as any), + longitude: parseFloat(formData.longitude as any), + }; + if (editingLocation) { - await updateLocation(editingLocation.id, { - ...formData, - legId: currentLegId, - latitude: parseFloat(formData.latitude as any), - longitude: parseFloat(formData.longitude as any), - }); + await updateLocation(editingLocation.id, payload); } else { - await addLocation(tourId, { - ...formData, - legId: currentLegId, - latitude: parseFloat(formData.latitude as any), - longitude: parseFloat(formData.longitude as any), - }); + await addLocation(tourId, payload); } onClose(); } catch (error) { @@ -206,27 +210,52 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin value={formData.address} onChange={e => setFormData({...formData, address: e.target.value})} />
- +