Files
goodgo-platform/docs/explorations/from-desktop/ARCHITECTURE_OVERVIEW.txt
Ho Ngoc Hai 08b96f9c2d docs: consolidate exploration & audit reports under docs/ (TEC-3094)
- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/
- Reorganize 27 .md/.txt at workspace root:
  - audit reports -> docs/audits/
  - exploration reports -> docs/explorations/
  - design system -> docs/design-system/
- Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root
- Refresh docs/README.md as canonical index with links to all groups
- Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were
  overwritten by the newer root-level versions during the move

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 16:29:24 +07:00

380 lines
24 KiB
Plaintext

╔══════════════════════════════════════════════════════════════════════════════╗
║ GOODGO FRONTEND ARCHITECTURE & DATA FLOW DIAGRAM ║
╚══════════════════════════════════════════════════════════════════════════════╝
┌─────────────────────────────────────────────────────────────────────────────┐
│ ROUTING & LAYOUT HIERARCHY │
└─────────────────────────────────────────────────────────────────────────────┘
app/
└── layout.tsx (global)
└── [locale]/
├── layout.tsx (i18n wrapper)
├── (public)/ ─────────────────── PUBLIC ROUTES
│ ├── listings/
│ │ ├── page.tsx ────────────────────────────────┐
│ │ └── [id]/page.tsx (Server RSC) ────────────┐ │
│ │ └── <ListingDetailClient/> (Client) ─┐ │ │
│ │ ├── <NeighborhoodRadarChart/> ──┐├─┼─┤ Neighborhood
│ │ ├── <NeighborhoodPOIMap/> │ │ │
│ │ └── <PriceHistoryChart/> ├─┘ │
│ ├── search/
│ ├── khu-cong-nghiep/
│ └── du-an/
├── (auth)/ ──────────────────── AUTH ROUTES
│ ├── login/
│ └── register/
├── (dashboard)/ ──────────────── PROTECTED ROUTES
│ ├── dashboard/
│ ├── analytics/
│ ├── valuation/
│ ├── industrial-parks/
│ └── reports/
└── (admin)/ ──────────────────── ADMIN ROUTES
└── admin/
├── users/
├── kyc/
└── moderation/
┌─────────────────────────────────────────────────────────────────────────────┐
│ LISTING DETAIL PAGE FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ /[locale]/(public)/listings/[id]/page.tsx (Server RSC) │
│ ────────────────────────────────────────────────────────── │
│ • generateMetadata() → SEO, OG, schema.json │
│ • await fetchListingById(id) → Backend API │
│ • Returns: <ListingDetailClient data={...} /> │
└────┬───────────────────────────────────────────────────────┬┘
│ SSR data injection (props) │
▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ <ListingDetailClient/> (Client Component, 39 KB) │
│ ───────────────────────────────────────────────────────── │
│ Layout Sections (top to bottom): │
│ │
│ 1. IMAGE GALLERY ─────────── <ImageGallery /> │
│ │
│ 2. KPI STRIP ─────────────── Price | m² | DOM | Views │
│ │
│ 3. CORE DETAILS ──────────── Address, Bedrooms, etc. │
│ │
│ 4. PRICE HISTORY ─────────── <PriceHistoryChart /> │
│ (Recharts AreaChart) │
│ │
│ 5. NEIGHBORHOOD SECTION ──── │
│ ├─ <NeighborhoodRadarChart/> │
│ │ (6 categories, 0-10 scores) │
│ │ Dynamically loaded, SSR: false │
│ │ │
│ └─ <NeighborhoodPOIMap/> │
│ (Mapbox GL with 6 POI types) │
│ Dynamically loaded, SSR: false │
│ • Schools, Hospitals, Transit, Shopping, etc. │
│ • Category filters │
│ • Distance display │
│ │
│ 6. AI ADVICE CARDS ────────── <AiAdviceCards /> │
│ (Personas) │
│ │
│ 7. SIMILAR LISTINGS ───────── Recommendations │
│ │
│ 8. AGENT CARD ────────────── Agent quality score │
│ │
│ 9. CTA SECTION ────────────── Inquiry | Compare | Estimate │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ API & STATE MANAGEMENT DATA FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────┐
│ Backend API │
│ (api/v1/...) │
└────────┬────────────┘
│ HTTPS + CSRF Token
┌──────────────────────────────────────────┐
│ lib/api-client.ts │
│ ───────────────────────────────────── │
│ • Base URL: NEXT_PUBLIC_API_URL │
│ • CSRF handling (from cookies) │
│ • 401 auto-refresh + retry │
│ • Type-safe generics: <T> │
└────┬──────────────────────────────────┬──┘
│ │
├─ apiClient.get<T>() │ HTTP Methods
├─ apiClient.post<T>() │
├─ apiClient.patch<T>() │
└─ apiClient.delete<T>() │
┌──────────────────────────────────────────────┐
│ Domain-Specific API Clients │
│ ───────────────────────────────────────── │
│ • listings-api.ts → ListingDetail │
│ • analytics-api.ts → MarketReport │
│ • valuation-api.ts → AVM estimates │
│ • agents-api.ts → AgentQualityScore │
│ • khu-cong-nghiep-api.ts │
│ • du-an-api.ts │
│ • inquiries-api.ts │
└────┬─────────────────────────────────────────┘
│ Returns typed data (interfaces)
┌────────────────────────────────────────────────┐
│ React Query (lib/hooks/) │
│ ───────────────────────────────────────── │
│ useQuery() with query keys: │
│ │
│ • useMarketReport(city, period) │
│ • usePriceTrend(district, city, type) │
│ • useListing(id) │
│ • useIndustrialParks() │
│ • useValuation() │
│ │
│ Features: │
│ ✓ Automatic caching + deduplication │
│ ✓ Background refetch intervals │
│ ✓ Error boundaries │
│ ✓ Loading states │
└────┬──────────────────────────────────────────┬┘
│ Provides data │
│ & loading/error states │
│ │
├──────────────────┬───────────────────────┤
│ │ │
▼ ▼ ▼
Components Zustand Stores React Context
(render data) (client state) (global features)
• Listings • auth-store • QueryProvider
• Charts • comparison-store • ThemeProvider
• Maps • preferences-store • AuthProvider
• Neighborhood • NotificationsProvider
┌─────────────────────────────────────────────────────────────────────────────┐
│ NEIGHBORHOOD COMPONENTS DETAIL │
└─────────────────────────────────────────────────────────────────────────────┘
API Response (from backend)
├─ NeighborhoodScoreData
│ ├─ overallScore: number (0-100)
│ ├─ categories: NeighborhoodCategory[]
│ │ └─ [
│ │ { category: "education", label: "Giáo dục", score: 7.5 },
│ │ { category: "healthcare", label: "Y tế", score: 8.2 },
│ │ { category: "transport", label: "Giao thông", score: 6.8 },
│ │ { category: "shopping", label: "Mua sắm", score: 7.9 },
│ │ { category: "dining", label: "Ẩm thực", score: 8.1 },
│ │ { category: "environment", label: "Môi trường", score: 7.3 },
│ │ ]
│ ├─ pois: POIItem[]
│ │ └─ [
│ │ { id: "1", name: "School X", category: "school", lat: 10.123, lng: 105.456, distance: 500 },
│ │ { id: "2", name: "Hospital Y", category: "hospital", lat: 10.124, lng: 105.457, distance: 800 },
│ │ ...
│ │ ]
│ └─ center: { lat: 10.123, lng: 105.456 }
└─ Rendered as:
├─────────────────────────────────────────────┐
│ <NeighborhoodRadarChart/> │
│ ───────────────────────────────────────── │
│ Recharts RadarChart Component │
│ │
│ Input: categories (0-10 scores) │
│ Output: │
│ • Radar polygon visualization │
│ • Badge strips below (Tốt/TB/Yếu) │
│ • Dark/light theme CSS variables │
└─────────────────────────────────────────────┘
├─────────────────────────────────────────────┐
│ <NeighborhoodPOIMap/> │
│ ───────────────────────────────────────── │
│ Mapbox GL Component (Client) │
│ │
│ Features: │
│ • Displays 6 POI category types │
│ • SVG icon markers (school, hospital, etc.)│
│ • Category filter toggles │
│ • Distance display on hover │
│ • Responsive map container │
│ • Theme-aware styling │
│ • Click handlers for POI selection │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ CHART COMPONENTS STACK │
└─────────────────────────────────────────────────────────────────────────────┘
Recharts Library
├─ PriceAreaChart (price-area-chart.tsx)
│ ├─ Input: PriceAreaChartPoint[] (period, avgPriceM2)
│ ├─ Renders: AreaChart with:
│ │ • Gradient fill (green/red based on trend)
│ │ • Responsive container
│ │ • XAxis: period labels
│ │ • YAxis: formatted Vietnamese currency (tr/k)
│ │ • Tooltip: formatted prices
│ └─ Used in: Listing detail page
├─ DistrictHeatmap (district-heatmap.tsx - 9 KB)
│ ├─ Input: HeatmapDataPoint[] (district, avgPrice, totalListings)
│ ├─ Possibly uses Mapbox layers for geo-visualization
│ └─ Used in: Analytics page
├─ PriceTrendChart (price-trend-chart.tsx)
│ ├─ Multiple period comparison
│ └─ Used in: Analytics, comparative analysis
├─ DistrictBarChart (district-bar-chart.tsx)
│ ├─ Bar chart for district-level metrics
│ └─ Used in: Market comparisons
├─ AgentPerformance (agent-performance.tsx - 6 KB)
│ ├─ Agent metrics visualization
│ └─ Used in: Agent detail pages
└─ NeighborhoodRadarChart (neighborhood-radar-chart.tsx)
├─ Radar polygon for 6 categories (0-10 scores)
└─ Used in: Listing detail, neighborhood info
┌─────────────────────────────────────────────────────────────────────────────┐
│ COMPONENT IMPORT PATTERNS │
└─────────────────────────────────────────────────────────────────────────────┘
STATIC IMPORT (always loaded):
──────────────────────────────
import { Badge } from '@/components/ui/badge';
Used for: Base UI components, utilities, small components
Bundle impact: Included in main bundle
DYNAMIC IMPORT (lazy-loaded):
──────────────────────────────
const NeighborhoodRadarChart = dynamic(
() => import('@/components/neighborhood')
.then(m => m.NeighborhoodRadarChart),
{
ssr: false, // Don't server-render (client-only due to Recharts)
loading: () => <ChartSkeleton /> // Loading UI
}
);
Used for: Heavy components (maps, charts, 3D)
Bundle impact: Code-split into separate chunk, loaded on-demand
BARREL EXPORT (for organization):
─────────────────────────────────
// components/neighborhood/index.ts
export { NeighborhoodRadarChart } from './neighborhood-radar-chart';
export { NeighborhoodPOIMap } from './neighborhood-poi-map';
// Usage:
import { NeighborhoodRadarChart, NeighborhoodPOIMap } from '@/components/neighborhood';
Benefits: Cleaner imports, single export point
┌─────────────────────────────────────────────────────────────────────────────┐
│ MAPBOX GL INITIALIZATION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
'use client' ← Must be client component (Mapbox GL requires DOM)
├─ import mapboxgl from 'mapbox-gl'
├─ import 'mapbox-gl/dist/mapbox-gl.css' ← CSS styles
├─ const mapStyle = useMapboxStyle() ← Get current theme
│ └─ Returns: MAPBOX_STYLE_LIGHT or MAPBOX_STYLE_DARK
├─ new mapboxgl.Map({
│ container: mapContainerRef.current,
│ style: mapStyle,
│ center: [lng, lat],
│ zoom: 14
│ })
├─ On theme change:
│ └─ map.setStyle(newStyle) ← Update map style reactively
└─ Cleanup:
└─ map?.remove() ← On unmount
┌─────────────────────────────────────────────────────────────────────────────┐
│ INTERNATIONALIZATION (i18n) FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
Request: /vi/listings/123
├─ [locale] route parameter = "vi"
├─ Loaded from: messages/vi.json
└─ Component:
const t = useTranslations();
<span>{t('listings.detail.title')}</span>
└─ Output: Vietnamese text from translation file
Request: /en/listings/123
├─ [locale] route parameter = "en"
├─ Loaded from: messages/en.json
└─ Output: English text from translation file
┌─────────────────────────────────────────────────────────────────────────────┐
│ SEO & METADATA GENERATION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
Server Component:
generateMetadata({ params }) {
├─ await fetchListingById(params.id) ← Get listing data
├─ Construct title, description
├─ Get first image for OG
└─ Return Metadata object:
{
title: "Property Title - Price",
description: "Type | Area | Bedrooms | Address | Price",
openGraph: {
url: "https://goodgo.vn/vi/listings/123",
type: "article",
images: [{ url: "...", width: 1200, height: 630 }],
},
alternates: {
canonical: "...",
languages: { vi: "...", en: "..." }
}
}
Output: <meta> tags in <head>
═══════════════════════════════════════════════════════════════════════════════
END OF ARCHITECTURE
═══════════════════════════════════════════════════════════════════════════════