chore: update project documentation, audit reports, and initialize IDE configuration files
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Property Detail Page - Component Map & Architecture Diagram
|
||||
# Trang Chi Tiết Bất Động Sản - Bản Đồ Component & Sơ Đồ Kiến Trúc
|
||||
|
||||
## 🎯 Page Component Hierarchy
|
||||
## 🎯 Phân Cấp Component Của Trang
|
||||
|
||||
```
|
||||
PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/page.tsx]
|
||||
@@ -21,7 +21,7 @@ PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/p
|
||||
│ ├─ Price Display
|
||||
│ └─ AddToCompareButton
|
||||
│
|
||||
├─ ImageGallery [MAIN FEATURE]
|
||||
├─ ImageGallery [TÍNH NĂNG CHÍNH]
|
||||
│ [apps/web/components/listings/image-gallery.tsx]
|
||||
│ ├─ Main Image Display
|
||||
│ │ ├─ Previous Button
|
||||
@@ -59,9 +59,9 @@ PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/p
|
||||
│ │ ├─ InfoItem (Legal Status)
|
||||
│ │ └─ InfoItem (Project)
|
||||
│ │
|
||||
│ ├─ Amenities Card (conditional)
|
||||
│ ├─ Amenities Card (có điều kiện)
|
||||
│ │ ├─ CardHeader
|
||||
│ │ └─ Badge(s) for each amenity
|
||||
│ │ └─ Badge(s) cho mỗi tiện ích
|
||||
│ │
|
||||
│ └─ Map Card
|
||||
│ ├─ CardHeader
|
||||
@@ -74,7 +74,7 @@ PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/p
|
||||
│ ├─ Seller Phone
|
||||
│ ├─ Call Button
|
||||
│ ├─ Message Button
|
||||
│ └─ Agent Info (conditional)
|
||||
│ └─ Agent Info (có điều kiện)
|
||||
│
|
||||
├─ AiEstimateButton
|
||||
│
|
||||
@@ -87,9 +87,9 @@ PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/p
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Image Gallery Component Details
|
||||
## 🖼️ Chi Tiết Component Thư Viện Ảnh
|
||||
|
||||
### File: `apps/web/components/listings/image-gallery.tsx`
|
||||
### Tệp: `apps/web/components/listings/image-gallery.tsx`
|
||||
|
||||
```
|
||||
ImageGallery (Client Component)
|
||||
@@ -110,12 +110,12 @@ ImageGallery (Client Component)
|
||||
│ │ ├─ Overlay: Next Button
|
||||
│ │ └─ Overlay: Counter Badge
|
||||
│ │
|
||||
│ └─ Thumbnail Container (if images.length > 1)
|
||||
│ └─ Thumbnail Container (nếu images.length > 1)
|
||||
│ ├─ className: "flex gap-2 overflow-x-auto"
|
||||
│ └─ ThumbnailButton (for each image)
|
||||
│ └─ ThumbnailButton (cho mỗi ảnh)
|
||||
│ ├─ Image (Next.js)
|
||||
│ ├─ Border: selected ? primary : transparent
|
||||
│ ├─ Opacity: 70% (unselected)
|
||||
│ ├─ Opacity: 70% (chưa được chọn)
|
||||
│ └─ Hover: opacity 100%
|
||||
│
|
||||
└─ Handlers:
|
||||
@@ -124,26 +124,26 @@ ImageGallery (Client Component)
|
||||
└─ handleSelectIndex(index)
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
### Luồng Dữ Liệu
|
||||
```
|
||||
Property.media (from API)
|
||||
Property.media (từ API)
|
||||
↓
|
||||
Filter by type === 'image'
|
||||
Lọc theo type === 'image'
|
||||
↓
|
||||
Sort by order property
|
||||
Sắp xếp theo thuộc tính order
|
||||
↓
|
||||
[selectedIndex, setSelectedIndex] → selectedIndex
|
||||
↓
|
||||
Image.url at index → Main Display
|
||||
All images → Thumbnails
|
||||
selectedIndex → Highlighted Thumbnail
|
||||
Image.url tại index → Hiển Thị Chính
|
||||
Tất cả ảnh → Thumbnails
|
||||
selectedIndex → Thumbnail Được Làm Nổi Bật
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Image Upload Component
|
||||
## 📱 Component Tải Lên Ảnh
|
||||
|
||||
### File: `apps/web/components/listings/image-upload.tsx`
|
||||
### Tệp: `apps/web/components/listings/image-upload.tsx`
|
||||
|
||||
```
|
||||
ImageUpload (Client Component)
|
||||
@@ -151,13 +151,13 @@ ImageUpload (Client Component)
|
||||
├─ Props:
|
||||
│ ├─ images: ImageFile[]
|
||||
│ ├─ onChange: (images: ImageFile[]) => void
|
||||
│ ├─ maxFiles?: number (default: 20)
|
||||
│ ├─ maxFiles?: number (mặc định: 20)
|
||||
│ └─ className?: string
|
||||
│
|
||||
├─ State:
|
||||
│ └─ isDragging: boolean
|
||||
│
|
||||
├─ Drag & Drop Zone
|
||||
├─ Vùng Kéo & Thả
|
||||
│ ├─ onDragOver → setIsDragging(true)
|
||||
│ ├─ onDragLeave → setIsDragging(false)
|
||||
│ ├─ onDrop → addFiles()
|
||||
@@ -168,33 +168,33 @@ ImageUpload (Client Component)
|
||||
│ ├─ multiple: true
|
||||
│ └─ hidden: true
|
||||
│
|
||||
└─ Preview Grid
|
||||
└─ For each image:
|
||||
├─ Image preview (URL.createObjectURL)
|
||||
├─ Cover badge (first image)
|
||||
├─ Delete button on hover
|
||||
└─ Cleanup on unmount (URL.revokeObjectURL)
|
||||
└─ Lưới Xem Trước
|
||||
└─ Cho mỗi ảnh:
|
||||
├─ Xem trước ảnh (URL.createObjectURL)
|
||||
├─ Huy hiệu ảnh bìa (ảnh đầu tiên)
|
||||
├─ Nút xóa khi di chuột qua
|
||||
└─ Dọn dẹp khi unmount (URL.revokeObjectURL)
|
||||
|
||||
Validation:
|
||||
├─ Allowed types: JPEG, PNG, WebP
|
||||
├─ Max size: 10MB per file
|
||||
└─ Max count: 20 files
|
||||
Kiểm Tra Hợp Lệ:
|
||||
├─ Các loại được phép: JPEG, PNG, WebP
|
||||
├─ Kích thước tối đa: 10MB mỗi tệp
|
||||
└─ Số lượng tối đa: 20 tệp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Related Components
|
||||
## 🧩 Các Component Liên Quan
|
||||
|
||||
### SearchResults & PropertyCard
|
||||
|
||||
```
|
||||
SearchResults
|
||||
│
|
||||
└─ Grid of PropertyCards
|
||||
└─ Lưới PropertyCards
|
||||
│
|
||||
└─ PropertyCard (for each listing)
|
||||
└─ PropertyCard (cho mỗi listing)
|
||||
│
|
||||
├─ Link to /listings/{id}
|
||||
├─ Link đến /listings/{id}
|
||||
│
|
||||
└─ Card
|
||||
├─ Image Container
|
||||
@@ -208,17 +208,17 @@ SearchResults
|
||||
├─ Price
|
||||
├─ Title
|
||||
├─ Location
|
||||
└─ Badges (Area, Bedrooms, etc.)
|
||||
└─ Badges (Area, Bedrooms, v.v.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Data Flow & API Mapping
|
||||
## 🌐 Luồng Dữ Liệu & Ánh Xạ API
|
||||
|
||||
### Server to Client Flow
|
||||
### Luồng Từ Server Đến Client
|
||||
|
||||
```
|
||||
1. Browser Request
|
||||
1. Yêu Cầu Trình Duyệt
|
||||
URL: /vi/listings/abc123
|
||||
│
|
||||
↓
|
||||
@@ -231,39 +231,39 @@ SearchResults
|
||||
│ ListingDetail {
|
||||
│ id: string
|
||||
│ property: {
|
||||
│ media: PropertyMedia[] ← Images
|
||||
│ media: PropertyMedia[] ← Ảnh
|
||||
│ }
|
||||
│ seller: {...}
|
||||
│ agent: {...}
|
||||
│ }
|
||||
│
|
||||
├─ generateMetadata()
|
||||
│ └─ Uses property.media[0] for OG image
|
||||
│ └─ Sử dụng property.media[0] cho ảnh OG
|
||||
│
|
||||
├─ generateJsonLd()
|
||||
│ └─ Structured data for SEO
|
||||
│ └─ Dữ liệu có cấu trúc cho SEO
|
||||
│
|
||||
└─ <ListingDetailClient listing={data} />
|
||||
│
|
||||
↓
|
||||
3. Client Component (Hydrated)
|
||||
3. Client Component (Đã Hydrate)
|
||||
│
|
||||
├─ <ImageGallery media={property.media} />
|
||||
│ └─ Local state: selectedIndex
|
||||
│ └─ State cục bộ: selectedIndex
|
||||
│
|
||||
└─ Other interactive components
|
||||
└─ Các component tương tác khác
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Styling Architecture
|
||||
## 🎨 Kiến Trúc Giao Diện
|
||||
|
||||
### Tailwind CSS Structure
|
||||
### Cấu Trúc Tailwind CSS
|
||||
|
||||
```
|
||||
Root CSS Variables (globals.css)
|
||||
Biến CSS Gốc (globals.css)
|
||||
│
|
||||
├─ Colors (HSL format)
|
||||
├─ Màu sắc (định dạng HSL)
|
||||
│ ├─ --primary
|
||||
│ ├─ --secondary
|
||||
│ ├─ --background
|
||||
@@ -272,59 +272,59 @@ Root CSS Variables (globals.css)
|
||||
│ ├─ --accent
|
||||
│ └─ --card
|
||||
│
|
||||
├─ Spacing
|
||||
│ └─ Uses standard Tailwind scale
|
||||
├─ Khoảng cách
|
||||
│ └─ Sử dụng thang chuẩn Tailwind
|
||||
│
|
||||
└─ Radius
|
||||
└─ Bo góc
|
||||
└─ --radius
|
||||
|
||||
Tailwind Config (tailwind.config.ts)
|
||||
│
|
||||
├─ Extends theme
|
||||
│ ├─ Colors mapped from CSS variables
|
||||
│ └─ Border radius configuration
|
||||
├─ Mở rộng theme
|
||||
│ ├─ Màu sắc ánh xạ từ biến CSS
|
||||
│ └─ Cấu hình bo góc viền
|
||||
│
|
||||
└─ Plugins
|
||||
└─ tailwindcss-animate
|
||||
```
|
||||
|
||||
### Component-Level Patterns
|
||||
### Các Mẫu Ở Cấp Component
|
||||
|
||||
```
|
||||
ui/button.tsx
|
||||
├─ CVA (Class Variance Authority)
|
||||
│ ├─ Base classes
|
||||
│ ├─ Variants
|
||||
│ │ ├─ variant: default, outline, ghost, etc.
|
||||
│ ├─ Các class cơ sở
|
||||
│ ├─ Biến thể
|
||||
│ │ ├─ variant: default, outline, ghost, v.v.
|
||||
│ │ └─ size: sm, default, lg, icon
|
||||
│ └─ defaultVariants
|
||||
│
|
||||
└─ Usage: <Button variant="default" size="lg" />
|
||||
└─ Sử dụng: <Button variant="default" size="lg" />
|
||||
|
||||
ui/badge.tsx
|
||||
├─ CVA variants
|
||||
├─ Biến thể CVA
|
||||
│ ├─ default (primary)
|
||||
│ ├─ secondary
|
||||
│ ├─ outline
|
||||
│ └─ ...colors (success, warning, info)
|
||||
│ └─ ...màu sắc (success, warning, info)
|
||||
│
|
||||
└─ Usage: <Badge variant="secondary">Text</Badge>
|
||||
└─ Sử dụng: <Badge variant="secondary">Text</Badge>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 State Management Patterns
|
||||
## 📊 Các Mẫu Quản Lý State
|
||||
|
||||
### Gallery Local State
|
||||
### State Cục Bộ Của Gallery
|
||||
```
|
||||
Component: ImageGallery
|
||||
State: selectedIndex (number)
|
||||
├─ Initialize: 0
|
||||
├─ Update on: prev, next, thumbnail click
|
||||
└─ Use: Display main image, highlight thumbnail
|
||||
├─ Khởi tạo: 0
|
||||
├─ Cập nhật khi: prev, next, nhấn vào thumbnail
|
||||
└─ Sử dụng: Hiển thị ảnh chính, làm nổi bật thumbnail
|
||||
```
|
||||
|
||||
### Global State (Zustand)
|
||||
### State Toàn Cục (Zustand)
|
||||
```
|
||||
Auth Store
|
||||
├─ user: UserProfile | null
|
||||
@@ -334,30 +334,30 @@ Auth Store
|
||||
└─ Actions: login, logout, fetchProfile
|
||||
|
||||
Comparison Store
|
||||
├─ selectedIds: string[] (persisted)
|
||||
├─ selectedIds: string[] (được lưu trữ)
|
||||
├─ listings: ListingDetail[]
|
||||
├─ isLoading: boolean
|
||||
├─ error: string | null
|
||||
└─ Actions: addToCompare, removeFromCompare, etc.
|
||||
└─ Actions: addToCompare, removeFromCompare, v.v.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Import Map
|
||||
## 🔗 Bản Đồ Import
|
||||
|
||||
### File Structure References
|
||||
### Tham Chiếu Cấu Trúc Tệp
|
||||
|
||||
```
|
||||
apps/web/
|
||||
│
|
||||
├─ app/[locale]/(public)/listings/[id]/
|
||||
│ └─ page.tsx ──────────────────────────── ENTRY POINT
|
||||
│ └─ page.tsx ──────────────────────────── ĐIỂM VÀO
|
||||
│ │
|
||||
│ └─ imports:
|
||||
│ ├─ ListingDetailClient
|
||||
│ ├─ JsonLd
|
||||
│ ├─ fetchListingById
|
||||
│ └─ formatting utilities
|
||||
│ └─ các tiện ích định dạng
|
||||
│
|
||||
├─ components/
|
||||
│ │
|
||||
@@ -368,10 +368,10 @@ apps/web/
|
||||
│ │ │ ├─ imports: AiEstimateButton
|
||||
│ │ │ └─ dynamic: ListingMap
|
||||
│ │ │
|
||||
│ │ ├─ image-gallery.tsx ───────────────── MAIN IMAGE DISPLAY
|
||||
│ │ ├─ image-gallery.tsx ───────────────── HIỂN THỊ ẢNH CHÍNH
|
||||
│ │ │ └─ imports: Next.js Image
|
||||
│ │ │
|
||||
│ │ └─ image-upload.tsx ──────────────────── FILE UPLOAD
|
||||
│ │ └─ image-upload.tsx ──────────────────── TẢI LÊN TỆP
|
||||
│ │ └─ imports: Button component
|
||||
│ │
|
||||
│ ├─ ui/
|
||||
@@ -379,14 +379,14 @@ apps/web/
|
||||
│ │ ├─ badge.tsx
|
||||
│ │ ├─ card.tsx
|
||||
│ │ ├─ dialog.tsx
|
||||
│ │ └─ ...other UI components
|
||||
│ │ └─ ...các component UI khác
|
||||
│ │
|
||||
│ └─ search/
|
||||
│ └─ property-card.tsx ──────────────── THUMBNAIL VIEW
|
||||
│ └─ property-card.tsx ──────────────── DẠNG XEM THUMBNAIL
|
||||
│ └─ imports: ImageGallery pattern
|
||||
│
|
||||
└─ lib/
|
||||
├─ listings-api.ts ────────────────────── API TYPES & FUNCTIONS
|
||||
├─ listings-api.ts ────────────────────── KIỂU DỮ LIỆU & HÀM API
|
||||
│ └─ PropertyMedia interface
|
||||
│ └─ ListingDetail interface
|
||||
│
|
||||
@@ -397,205 +397,205 @@ apps/web/
|
||||
|
||||
---
|
||||
|
||||
## 📈 Component Complexity Levels
|
||||
## 📈 Các Mức Độ Phức Tạp Của Component
|
||||
|
||||
### Level 1: Simple UI Components
|
||||
### Cấp 1: Các Component UI Đơn Giản
|
||||
```
|
||||
Badge, Button, Input, Label
|
||||
├─ Props: basic props + variants
|
||||
├─ State: none
|
||||
└─ Interactions: click, focus, hover
|
||||
├─ Props: props cơ bản + biến thể
|
||||
├─ State: không có
|
||||
└─ Tương tác: click, focus, hover
|
||||
```
|
||||
|
||||
### Level 2: Composite Components
|
||||
### Cấp 2: Các Component Tổng Hợp
|
||||
```
|
||||
Card (Header + Content + Footer)
|
||||
ImageUpload (Drag-drop + Preview grid)
|
||||
├─ Props: content children
|
||||
├─ State: local state
|
||||
└─ Interactions: click, drag, input
|
||||
ImageUpload (Kéo-thả + Lưới xem trước)
|
||||
├─ Props: nội dung children
|
||||
├─ State: state cục bộ
|
||||
└─ Tương tác: click, kéo, nhập liệu
|
||||
```
|
||||
|
||||
### Level 3: Feature Components
|
||||
### Cấp 3: Các Component Tính Năng
|
||||
```
|
||||
ImageGallery (Main + Thumbnails)
|
||||
ImageGallery (Chính + Thumbnails)
|
||||
PropertyCard (Link + Image + Info)
|
||||
├─ Props: data + callbacks
|
||||
├─ State: selected index, UI state
|
||||
└─ Interactions: navigation, filtering
|
||||
├─ Props: dữ liệu + callbacks
|
||||
├─ State: chỉ mục được chọn, trạng thái UI
|
||||
└─ Tương tác: điều hướng, lọc
|
||||
```
|
||||
|
||||
### Level 4: Page Components
|
||||
### Cấp 4: Các Component Trang
|
||||
```
|
||||
ListingDetailClient (Full page layout)
|
||||
├─ Props: listing data
|
||||
├─ State: multiple features
|
||||
├─ Interactions: all user interactions
|
||||
└─ Children: multiple feature components
|
||||
ListingDetailClient (Bố cục trang đầy đủ)
|
||||
├─ Props: dữ liệu listing
|
||||
├─ State: nhiều tính năng
|
||||
├─ Tương tác: tất cả tương tác người dùng
|
||||
└─ Children: nhiều component tính năng
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Performance Considerations
|
||||
## 🚀 Cân Nhắc Về Hiệu Suất
|
||||
|
||||
### Image Optimization
|
||||
### Tối Ưu Hóa Ảnh
|
||||
|
||||
```
|
||||
Image Component Strategy:
|
||||
Chiến Lược Component Ảnh:
|
||||
├─ Next.js Image component
|
||||
│ ├─ Automatic format selection (WebP, AVIF)
|
||||
│ ├─ Responsive serving via srcset
|
||||
│ └─ On-demand resizing
|
||||
│ ├─ Tự động chọn định dạng (WebP, AVIF)
|
||||
│ ├─ Phục vụ đáp ứng qua srcset
|
||||
│ └─ Thay đổi kích thước theo yêu cầu
|
||||
│
|
||||
├─ Lazy Loading
|
||||
│ ├─ Main image: priority={selectedIndex === 0}
|
||||
│ └─ Thumbnails: no priority (lazy)
|
||||
│ ├─ Ảnh chính: priority={selectedIndex === 0}
|
||||
│ └─ Thumbnails: không có priority (lazy)
|
||||
│
|
||||
└─ Responsive Sizing
|
||||
├─ sizes prop: tells browser image dimensions
|
||||
└─ Prevents layout shift
|
||||
└─ Kích Thước Đáp Ứng
|
||||
├─ Thuộc tính sizes: cho trình duyệt biết kích thước ảnh
|
||||
└─ Ngăn chặn thay đổi bố cục
|
||||
```
|
||||
|
||||
### Code Splitting
|
||||
### Phân Tách Code
|
||||
|
||||
```
|
||||
Dynamic Imports:
|
||||
├─ ListingMap (heavy, maps library)
|
||||
│ ├─ ssr: false (client-only)
|
||||
│ └─ loading: placeholder component
|
||||
├─ ListingMap (nặng, thư viện bản đồ)
|
||||
│ ├─ ssr: false (chỉ dành cho client)
|
||||
│ └─ loading: component giữ chỗ
|
||||
│
|
||||
└─ Other components: bundled with page
|
||||
└─ Các component khác: đóng gói cùng trang
|
||||
```
|
||||
|
||||
### State Optimization
|
||||
### Tối Ưu Hóa State
|
||||
|
||||
```
|
||||
Zustand:
|
||||
├─ Selector pattern: useStore(state => state.field)
|
||||
├─ Only re-render on selected state change
|
||||
└─ Persist middleware: localStorage only on needed data
|
||||
├─ Mẫu Selector: useStore(state => state.field)
|
||||
├─ Chỉ render lại khi state được chọn thay đổi
|
||||
└─ Middleware Persist: localStorage chỉ cho dữ liệu cần thiết
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Navigation Flow
|
||||
## 🔄 Luồng Điều Hướng
|
||||
|
||||
### User Journey - Image Gallery
|
||||
### Hành Trình Người Dùng - Thư Viện Ảnh
|
||||
|
||||
```
|
||||
1. User visits /vi/listings/123
|
||||
└─ Page loads with first image (priority=true)
|
||||
1. Người dùng truy cập /vi/listings/123
|
||||
└─ Trang tải với ảnh đầu tiên (priority=true)
|
||||
|
||||
2. User interacts with gallery:
|
||||
├─ Click thumbnail
|
||||
│ └─ setSelectedIndex(index) → main image updates
|
||||
2. Người dùng tương tác với gallery:
|
||||
├─ Nhấn vào thumbnail
|
||||
│ └─ setSelectedIndex(index) → ảnh chính cập nhật
|
||||
│
|
||||
├─ Click next button
|
||||
│ └─ setSelectedIndex(i + 1) → wraps to 0
|
||||
├─ Nhấn nút tiếp theo
|
||||
│ └─ setSelectedIndex(i + 1) → quay về 0
|
||||
│
|
||||
└─ Click prev button
|
||||
└─ setSelectedIndex(i - 1) → wraps to last
|
||||
└─ Nhấn nút trước đó
|
||||
└─ setSelectedIndex(i - 1) → quay về cuối
|
||||
```
|
||||
|
||||
### User Journey - File Upload (Create/Edit Listing)
|
||||
### Hành Trình Người Dùng - Tải Lên Tệp (Tạo/Chỉnh Sửa Listing)
|
||||
|
||||
```
|
||||
1. User opens listing form
|
||||
└─ ImageUpload component mounts
|
||||
1. Người dùng mở biểu mẫu listing
|
||||
└─ Component ImageUpload được gắn kết
|
||||
|
||||
2. User adds images:
|
||||
├─ Drag & drop files
|
||||
│ └─ addFiles() → filter + validate → onChange()
|
||||
2. Người dùng thêm ảnh:
|
||||
├─ Kéo & thả tệp
|
||||
│ └─ addFiles() → lọc + kiểm tra → onChange()
|
||||
│
|
||||
└─ Click to browse
|
||||
└─ Select files → same as drag & drop
|
||||
└─ Nhấn để duyệt
|
||||
└─ Chọn tệp → giống như kéo & thả
|
||||
|
||||
3. User removes image:
|
||||
└─ removeImage(index) → cleanup URL → onChange()
|
||||
3. Người dùng xóa ảnh:
|
||||
└─ removeImage(index) → dọn dẹp URL → onChange()
|
||||
|
||||
4. User submits form:
|
||||
└─ Images uploaded via listingsApi.uploadMedia()
|
||||
4. Người dùng gửi biểu mẫu:
|
||||
└─ Ảnh được tải lên qua listingsApi.uploadMedia()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Component Checklist
|
||||
## 📋 Danh Sách Kiểm Tra Component
|
||||
|
||||
### Image Gallery Features
|
||||
- [x] Main image display (responsive)
|
||||
- [x] Previous/Next navigation
|
||||
- [x] Image counter badge
|
||||
- [x] Thumbnail navigation (scrollable)
|
||||
- [x] Selected thumbnail highlighting
|
||||
- [x] Empty state fallback
|
||||
- [ ] Lightbox/modal zoom (NOT implemented)
|
||||
- [ ] Keyboard navigation (NOT implemented)
|
||||
- [ ] Touch gestures (NOT implemented)
|
||||
### Tính Năng Thư Viện Ảnh
|
||||
- [x] Hiển thị ảnh chính (đáp ứng)
|
||||
- [x] Điều hướng Trước/Tiếp theo
|
||||
- [x] Huy hiệu đếm ảnh
|
||||
- [x] Điều hướng thumbnail (có thể cuộn)
|
||||
- [x] Làm nổi bật thumbnail được chọn
|
||||
- [x] Trạng thái dự phòng khi trống
|
||||
- [ ] Phóng to lightbox/modal (CHƯA triển khai)
|
||||
- [ ] Điều hướng bàn phím (CHƯA triển khai)
|
||||
- [ ] Cử chỉ chạm (CHƯA triển khai)
|
||||
|
||||
### Image Upload Features
|
||||
- [x] Drag & drop
|
||||
- [x] Click to browse
|
||||
- [x] File type validation
|
||||
- [x] File size validation
|
||||
- [x] Preview grid
|
||||
- [x] Delete button
|
||||
- [x] Cover photo indicator
|
||||
- [x] URL cleanup on unmount
|
||||
- [ ] Progress bar (NOT implemented)
|
||||
- [ ] Multiple upload progress (NOT implemented)
|
||||
### Tính Năng Tải Lên Ảnh
|
||||
- [x] Kéo & thả
|
||||
- [x] Nhấn để duyệt
|
||||
- [x] Kiểm tra loại tệp
|
||||
- [x] Kiểm tra kích thước tệp
|
||||
- [x] Lưới xem trước
|
||||
- [x] Nút xóa
|
||||
- [x] Chỉ báo ảnh bìa
|
||||
- [x] Dọn dẹp URL khi unmount
|
||||
- [ ] Thanh tiến trình (CHƯA triển khai)
|
||||
- [ ] Tiến trình tải lên nhiều tệp (CHƯA triển khai)
|
||||
|
||||
### SEO & Metadata
|
||||
- [x] Open Graph image
|
||||
- [x] Twitter Card image
|
||||
- [x] JSON-LD schema
|
||||
- [x] Canonical URL
|
||||
- [x] Alternate language links
|
||||
- [x] Descriptive alt text
|
||||
- [x] Ảnh Open Graph
|
||||
- [x] Ảnh Twitter Card
|
||||
- [x] Schema JSON-LD
|
||||
- [x] URL Canonical
|
||||
- [x] Liên kết ngôn ngữ thay thế
|
||||
- [x] Văn bản alt mô tả
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Maintenance Guide
|
||||
## 🛠️ Hướng Dẫn Bảo Trì
|
||||
|
||||
### Adding New Image Features
|
||||
### Thêm Tính Năng Ảnh Mới
|
||||
|
||||
**Add to ImageGallery:**
|
||||
1. Define new prop in interface
|
||||
2. Update state if needed
|
||||
3. Update render logic
|
||||
4. Test responsive behavior
|
||||
5. Update TypeScript types
|
||||
**Thêm vào ImageGallery:**
|
||||
1. Định nghĩa prop mới trong interface
|
||||
2. Cập nhật state nếu cần
|
||||
3. Cập nhật logic render
|
||||
4. Kiểm tra hành vi đáp ứng
|
||||
5. Cập nhật các kiểu TypeScript
|
||||
|
||||
**Example - Add zoom feature:**
|
||||
**Ví dụ - Thêm tính năng zoom:**
|
||||
```typescript
|
||||
interface ImageGalleryProps {
|
||||
media: PropertyMedia[];
|
||||
className?: string;
|
||||
onImageClick?: (index: number) => void; // NEW
|
||||
onImageClick?: (index: number) => void; // MỚI
|
||||
}
|
||||
|
||||
// In component:
|
||||
const [isZoomed, setIsZoomed] = useState(false); // NEW
|
||||
// Trong component:
|
||||
const [isZoomed, setIsZoomed] = useState(false); // MỚI
|
||||
|
||||
<Image
|
||||
onClick={() => setIsZoomed(true)} // NEW
|
||||
cursor={isZoomed ? 'zoom-out' : 'zoom-in'} // NEW
|
||||
onClick={() => setIsZoomed(true)} // MỚI
|
||||
cursor={isZoomed ? 'zoom-out' : 'zoom-in'} // MỚI
|
||||
/>
|
||||
```
|
||||
|
||||
### Updating Image Data Structure
|
||||
### Cập Nhật Cấu Trúc Dữ Liệu Ảnh
|
||||
|
||||
**If PropertyMedia changes:**
|
||||
1. Update interface in `lib/listings-api.ts`
|
||||
2. Update API response mapping
|
||||
3. Update gallery component to use new fields
|
||||
4. Update tests
|
||||
5. Update API documentation
|
||||
**Nếu PropertyMedia thay đổi:**
|
||||
1. Cập nhật interface trong `lib/listings-api.ts`
|
||||
2. Cập nhật ánh xạ phản hồi API
|
||||
3. Cập nhật component gallery để sử dụng các trường mới
|
||||
4. Cập nhật các bài kiểm thử
|
||||
5. Cập nhật tài liệu API
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documentation
|
||||
## 📚 Tài Liệu Liên Quan
|
||||
|
||||
See also:
|
||||
- `PROPERTY_DETAIL_PAGE_ANALYSIS.md` - Comprehensive analysis
|
||||
- `PROPERTY_DETAIL_QUICK_REFERENCE.md` - Code snippets & patterns
|
||||
Xem thêm:
|
||||
- `PROPERTY_DETAIL_PAGE_ANALYSIS.md` - Phân tích toàn diện
|
||||
- `PROPERTY_DETAIL_QUICK_REFERENCE.md` - Đoạn code & các mẫu
|
||||
|
||||
|
||||
Reference in New Issue
Block a user