From ff133edfda12ba4935b01b0bda9147efdd55171a Mon Sep 17 00:00:00 2001 From: Thanh Quy - wolf <524H0124@student.tdtu.edu.vn> Date: Fri, 3 Apr 2026 20:24:36 +0700 Subject: [PATCH] =?UTF-8?q?T=C3=A1i=20c=E1=BA=A5u=20tr=C3=BAc=20manager=20?= =?UTF-8?q?page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OPTIMIZATION_PLAN.md | 1184 ----------------- app/(manager)/manager/analytics/page.tsx | 1092 ++------------- app/layout.tsx | 2 +- components/COMPONENTS.md | 424 +----- components/CartFab.tsx | 23 - components/CartProduct.tsx | 78 -- components/Navbar.tsx | 124 -- components/ReviewModal.tsx | 160 --- components/organisms/analytics/BarChart.tsx | 110 ++ components/organisms/analytics/LineChart.tsx | 113 ++ components/organisms/analytics/PieChart.tsx | 109 ++ .../organisms/analytics/ProductTable.tsx | 101 ++ .../organisms/analytics/SummaryCard.tsx | 59 + components/organisms/analytics/index.ts | 7 + lib/analytics-utils.ts | 29 + 15 files changed, 698 insertions(+), 2917 deletions(-) delete mode 100644 OPTIMIZATION_PLAN.md delete mode 100644 components/CartFab.tsx delete mode 100644 components/CartProduct.tsx delete mode 100644 components/Navbar.tsx delete mode 100644 components/ReviewModal.tsx create mode 100644 components/organisms/analytics/BarChart.tsx create mode 100644 components/organisms/analytics/LineChart.tsx create mode 100644 components/organisms/analytics/PieChart.tsx create mode 100644 components/organisms/analytics/ProductTable.tsx create mode 100644 components/organisms/analytics/SummaryCard.tsx create mode 100644 components/organisms/analytics/index.ts create mode 100644 lib/analytics-utils.ts diff --git a/OPTIMIZATION_PLAN.md b/OPTIMIZATION_PLAN.md deleted file mode 100644 index 9b811b9..0000000 --- a/OPTIMIZATION_PLAN.md +++ /dev/null @@ -1,1184 +0,0 @@ -# Coffee Shop Frontend - Atomic Design Optimization Plan - -**Date:** 2026-04-03 **Current Branch:** `atomic_design` **Status:** Analysis & -Planning Phase - ---- - -## Executive Summary - -Your project has a solid foundation with well-organized contexts, utilities, and -components. However, the component structure doesn't follow the Atomic Design -pattern defined in `Atomic.md`. This document outlines how to restructure your -codebase to: - -✅ **Improve maintainability** - Clear separation of concerns ✅ **Increase -reusability** - Components organized by complexity level ✅ **Enable -scalability** - Easy to add features following established patterns ✅ **Better -testing** - Each level has specific responsibilities - ---- - -## Current State Analysis - -### ✅ What's Working Well - -1. **Context API Setup** (⭐⭐⭐⭐⭐) - - `auth-context.tsx` - Clean user authentication - - `cart-context.tsx` - Good shopping cart management - - `menu-context.tsx` - Simple category state - - `manager-context.tsx` - Manager dashboard state - - Proper TypeScript interfaces in `lib/types.ts` - -2. **Responsive Design** (⭐⭐⭐⭐) - - Mobile-first approach with Tailwind breakpoints - - CSS variables for theming (`--color-primary`, etc.) - - Good use of Next.js Image component - - Accessible ARIA attributes in modals and interactive components - -3. **Code Documentation** (⭐⭐⭐⭐) - - Comprehensive `COMPONENTS.md` with detailed prop tables - - App routing documented in `app/APP.md` - - Constants well-organized in `lib/constants.ts` - -4. **App Router Structure** (⭐⭐⭐⭐) - - Route groups: `(main)`, `(feed)`, `(manager)` - excellent organization - - Proper layout nesting in Next.js 16 - -### ❌ What Needs Improvement - -1. **Components Not Following Atomic Design** (🔴 HIGH PRIORITY) - - **Current:** All components in `components/` root directory - - **Problem:** No clear hierarchy between simple UI blocks and complex - sections - - **Current state:** - ``` - components/ - ├── CartProduct.tsx ← Should be: molecules/cards/ProductCard.tsx - ├── CartFab.tsx ← Should be: organisms/cart/CartFab.tsx - ├── Navbar.tsx ← Should be: organisms/navigation/Navbar.tsx - ├── ReviewModal.tsx ← Should be: organisms/modals/ReviewModal.tsx - ├── COMPONENTS.md - └── (no atoms/molecules/organisms directories) - ``` - -2. **Missing Atom Components** (🔴 CRITICAL) - - No `Button`, `TextInput`, `Text`, `Heading` atoms - - Buttons are hardcoded inline with ` -``` - -Should become: - -```tsx - -``` - -**Benefit:** - -- Reuse button styling across all components -- Consistent hover/active states -- Easy theme updates -- Better accessibility (built-in aria-labels) - ---- - -#### 2. Extract Search Component - -**Location:** `components/molecules/search-bar/SearchInput.tsx` - -Current inline from app/(main)/page.tsx: - -```tsx -
- - - {searchQuery && } -
-``` - -Should become: - -```tsx - -``` - -**Benefit:** - -- Consistent search UX across app -- Icon management centralized -- Clear button behavior standardized - ---- - -#### 3. Extract Category Menu - -**Location:** `components/organisms/navigation/CategoryMenu.tsx` - -Current inline from app/(main)/page.tsx:lines 131-153 - -This is UI logic that belongs in a component, not the page. - -**Benefit:** - -- Reusable in mobile view and elsewhere -- Easier to test category selection logic -- Decouples page from component internals - ---- - -#### 4. Extract Product Grid - -**Location:** `components/organisms/product-grid/ProductGrid.tsx` - -Current inline from app/(main)/page.tsx:lines 156-188 - -Move the grid rendering and product mapping logic. - -**Benefit:** - -- Page becomes 30 lines instead of 192 -- Grid responsive logic isolated -- Easy to add virtualization/pagination later - ---- - -#### 5. Rating Component Extraction - -**Location:** `components/molecules/ratings/RatingInput.tsx` - -Current embedded in ReviewModal.tsx:lines 82-125 - -Extract star rating logic to molecule. - -**Benefit:** - -- Reuse rating input in product reviews, manager forms -- Simpler ReviewModal component -- Easier to test rating logic - ---- - -#### 6. Form Field Molecule - -**Location:** `components/molecules/form-groups/FormField.tsx` - -Create generic FormField combining: - -- Label -- Input (text, email, password, etc.) -- Error message -- Validation state (success, error, loading) - -Used by: - -- LoginForm -- RegisterForm -- CheckoutForm -- ManagerForms - ---- - -### 📊 Component Reduction Targets - -**Current state:** - -- 5 components in `components/` root -- 1 template-like layout in `app/(main)/layout.tsx` -- Logic scattered across pages - -**Target state:** - -- 0 components in `components/` root -- 20+ atoms/molecules/organisms organized by type -- 5 template components -- Clean, focused pages (< 100 lines each) - ---- - -## Testing Strategy per Atomic Level - -### Atoms - -```bash -npm test components/atoms/ -# ✓ Button - all variants (primary, secondary, danger) -# ✓ Button - all sizes (sm, md, lg) -# ✓ Button - disabled state -# ✓ Button - with and without icon -# ✓ TextInput - focus/blur states -# ✓ TextInput - disabled/loading states -``` - -### Molecules - -```bash -npm test components/molecules/ -# ✓ ProductCard - renders image, name, price, button -# ✓ ProductCard - onAddToCart callback -# ✓ SearchInput - clear button appears/disappears -# ✓ FormField - error message displays -# ✓ RatingInput - click/hover behavior -``` - -### Organisms - -```bash -npm test components/organisms/ -# ✓ ProductGrid - filters by category -# ✓ ProductGrid - filters by search query -# ✓ Navbar - toggle expand/collapse -# ✓ CategoryMenu - category selection -# ✓ CartFab - shows/hides based on cart items -``` - -### Pages - -```bash -npm test app/ -# ✓ Main page - renders ProductGrid with Navbar -# ✓ Main page - category filter works end-to-end -# ✓ Payment page - ReviewModal appears -# ✓ Manager page - only accessible to managers -``` - ---- - -## Documentation Updates Required - -After implementation: - -1. **Update `components/COMPONENTS.md`** - - Add sections for atoms, molecules, organisms, templates - - Add migration examples - - Link to each component's purpose - -2. **Create `components/ATOMIC_DESIGN.md`** - - Summary of atomic structure - - When to use each level - - Import examples - - Common patterns - -3. **Update main `README.md`** - - Reference atomic design approach - - Point to component docs - -4. **Create component-level `.md` files** - - `components/atoms/buttons/Button.md` - - `components/molecules/cards/ProductCard.md` - - `components/organisms/product-grid/ProductGrid.md` - ---- - -## File Structure After Implementation - -``` -components/ -├── atoms/ -│ ├── buttons/ -│ │ ├── Button.tsx -│ │ ├── IconButton.tsx -│ │ ├── Button.types.ts -│ │ └── index.ts -│ ├── inputs/ -│ │ ├── TextInput.tsx -│ │ ├── SearchInput.tsx -│ │ ├── Textarea.tsx -│ │ ├── Input.types.ts -│ │ └── index.ts -│ ├── typography/ -│ │ ├── Heading.tsx -│ │ ├── Text.tsx -│ │ ├── Caption.tsx -│ │ ├── Typography.types.ts -│ │ └── index.ts -│ ├── badges/ -│ │ ├── Badge.tsx -│ │ ├── PriceBadge.tsx -│ │ ├── Badge.types.ts -│ │ └── index.ts -│ ├── icons/ -│ │ ├── CartIcon.tsx -│ │ ├── SearchIcon.tsx -│ │ ├── StarIcon.tsx -│ │ ├── Icon.types.ts -│ │ └── index.ts -│ ├── dividers/ -│ │ ├── Divider.tsx -│ │ ├── Divider.types.ts -│ │ └── index.ts -│ ├── loaders/ -│ │ ├── Spinner.tsx -│ │ ├── Skeleton.tsx -│ │ ├── Loader.types.ts -│ │ └── index.ts -│ ├── index.ts -│ └── README.md -├── molecules/ -│ ├── cards/ -│ │ ├── ProductCard.tsx -│ │ ├── ReviewCard.tsx -│ │ ├── Card.types.ts -│ │ └── index.ts -│ ├── form-groups/ -│ │ ├── FormField.tsx -│ │ ├── FormGroup.types.ts -│ │ └── index.ts -│ ├── ratings/ -│ │ ├── RatingStars.tsx -│ │ ├── RatingInput.tsx -│ │ ├── RatingLabel.tsx -│ │ ├── Rating.types.ts -│ │ └── index.ts -│ ├── price-display/ -│ │ ├── PriceTag.tsx -│ │ ├── PriceRange.tsx -│ │ ├── Price.types.ts -│ │ └── index.ts -│ ├── search-bar/ -│ │ ├── SearchBar.tsx -│ │ ├── Search.types.ts -│ │ └── index.ts -│ ├── breadcrumb/ -│ │ ├── Breadcrumb.tsx -│ │ ├── Breadcrumb.types.ts -│ │ └── index.ts -│ ├── tabs/ -│ │ ├── TabGroup.tsx -│ │ ├── Tab.tsx -│ │ ├── Tabs.types.ts -│ │ └── index.ts -│ ├── index.ts -│ └── README.md -├── organisms/ -│ ├── navigation/ -│ │ ├── Navbar.tsx -│ │ ├── CategoryMenu.tsx -│ │ ├── Navigation.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── cart/ -│ │ ├── CartFab.tsx -│ │ ├── CartSummary.tsx -│ │ ├── CartList.tsx -│ │ ├── Cart.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── product-grid/ -│ │ ├── ProductGrid.tsx -│ │ ├── ProductFilters.tsx -│ │ ├── ProductGrid.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── forms/ -│ │ ├── LoginForm.tsx -│ │ ├── RegisterForm.tsx -│ │ ├── CheckoutForm.tsx -│ │ ├── ReviewForm.tsx -│ │ ├── Forms.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── modals/ -│ │ ├── ReviewModal.tsx -│ │ ├── ConfirmModal.tsx -│ │ ├── Modal.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── shop-grid/ -│ │ ├── ShopGrid.tsx -│ │ ├── ShopFilters.tsx -│ │ ├── ShopGrid.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── hero-section/ -│ │ ├── HeroSection.tsx -│ │ ├── Hero.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── featured-section/ -│ │ ├── FeaturedProducts.tsx -│ │ ├── FeaturedShops.tsx -│ │ ├── Featured.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── index.ts -│ └── README.md -├── templates/ -│ ├── main-layout/ -│ │ ├── MainLayout.tsx -│ │ ├── MainLayout.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── feed-layout/ -│ │ ├── FeedLayout.tsx -│ │ ├── FeedLayout.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── manager-layout/ -│ │ ├── ManagerLayout.tsx -│ │ ├── ManagerLayout.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── checkout-layout/ -│ │ ├── CheckoutLayout.tsx -│ │ ├── CheckoutLayout.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── auth-layout/ -│ │ ├── AuthLayout.tsx -│ │ ├── AuthLayout.types.ts -│ │ ├── index.ts -│ │ └── README.md -│ ├── index.ts -│ └── README.md -├── ATOMIC_DESIGN.md -├── COMPONENTS.md (updated) -└── README.md (updated) -``` - ---- - -## Quick Wins (Implement First) - -These provide immediate value with minimal effort: - -### 1. Create Button Atom - -- 50 lines of code -- Removes ~100 lines of inline styling -- Unblocks other components -- **Est. 30 minutes** - -### 2. Create TextInput Atom - -- 40 lines of code -- Removes search input duplication -- **Est. 20 minutes** - -### 3. Create Typography Atoms - -- Heading, Text, Caption -- 60 lines of code -- Standardizes font sizes/weights -- **Est. 45 minutes** - -### 4. Move Existing Components - -- Navbar → `organisms/navigation/Navbar.tsx` -- CartFab → `organisms/cart/CartFab.tsx` -- ReviewModal → `organisms/modals/ReviewModal.tsx` -- Just directory restructuring -- Update imports -- **Est. 30 minutes** - -### 5. Rename CartProduct → ProductCard - -- Move to `molecules/cards/ProductCard.tsx` -- Extract interface to `Card.types.ts` -- **Est. 15 minutes** - -**Total: ~2.5 hours → 5 quick wins → solid foundation** - ---- - -## Migration Checklist - -### Phase 1: Atoms - -- [ ] Create `components/atoms/` directory structure -- [ ] Create Button.tsx, Button.types.ts -- [ ] Create TextInput.tsx, Input.types.ts -- [ ] Create Typography atoms (Heading, Text, Caption) -- [ ] Create Badge, PriceBadge atoms -- [ ] Create icon components (CartIcon, SearchIcon, StarIcon) -- [ ] Create Divider atom -- [ ] Create barrel exports (`atoms/index.ts`) -- [ ] Update `components/COMPONENTS.md` -- [ ] Verify all atoms have TypeScript interfaces -- [ ] Test: No console errors, styling matches - -### Phase 2: Molecules - -- [ ] Create `components/molecules/` directory structure -- [ ] Move CartProduct → ProductCard -- [ ] Extract interface to Card.types.ts -- [ ] Create FormField molecule -- [ ] Create RatingInput, RatingStars molecules -- [ ] Create PriceTag molecule -- [ ] Create SearchBar molecule -- [ ] Create Breadcrumb, Tabs molecules -- [ ] Create barrel exports -- [ ] Update imports in organisms -- [ ] Test: All molecules render with atom composition - -### Phase 3: Organisms - -- [ ] Create `components/organisms/` directory structure -- [ ] Move Navbar → organisms/navigation/ -- [ ] Extract CategoryMenu → organisms/navigation/ -- [ ] Move CartFab → organisms/cart/ -- [ ] Extract ProductGrid logic -- [ ] Move ReviewModal → organisms/modals/ -- [ ] Extract ReviewForm from ReviewModal -- [ ] Move form pages logic → organisms/forms/ -- [ ] Create barrel exports -- [ ] Update page imports -- [ ] Test: All organisms use molecule composition - -### Phase 4: Templates - -- [ ] Create `components/templates/` directory structure -- [ ] Create MainLayout template -- [ ] Create FeedLayout template -- [ ] Create AuthLayout template -- [ ] Create CheckoutLayout template -- [ ] Create ManagerLayout template -- [ ] Create barrel exports -- [ ] Update page imports -- [ ] Test: All templates wrap pages correctly - -### Phase 5: Pages & Testing - -- [ ] Update `app/(main)/page.tsx` -- [ ] Update `app/(main)/layout.tsx` -- [ ] Update `app/(feed)/feed/page.tsx` -- [ ] Update `app/(main)/login/page.tsx` -- [ ] Update `app/(main)/payment/page.tsx` -- [ ] Update `app/(manager)/manager/page.tsx` -- [ ] Run `npm run lint` -- [ ] Run `npm run format` -- [ ] Manual testing: responsive, accessibility, functionality -- [ ] Update `COMPONENTS.md` with new structure -- [ ] Commit all changes - ---- - -## Success Criteria - -✅ **After implementation, verify:** - -1. **Code Organization** - - [ ] All atoms in `components/atoms/` - - [ ] All molecules in `components/molecules/` - - [ ] All organisms in `components/organisms/` - - [ ] All templates in `components/templates/` - - [ ] Pages use only templates, organisms, molecules - -2. **Code Quality** - - [ ] No inline ` ))} @@ -989,31 +249,19 @@ export default function AnalyticsPage() { {activeChart === "line" && ( <> -

- Doanh thu theo thời gian — {PERIOD_LABELS[period]} -

+

Doanh thu theo thời gian — {PERIOD_LABELS[period]}

)} - {activeChart === "bar" && ( <> -

- So sánh doanh thu nửa đầu và nửa sau kỳ hiện tại -

- +

So sánh doanh thu nửa đầu và nửa sau kỳ hiện tại

+ )} - {activeChart === "pie" && ( <> -

- Tỷ trọng doanh thu theo danh mục sản phẩm -

+

Tỷ trọng doanh thu theo danh mục sản phẩm

)} @@ -1026,57 +274,27 @@ export default function AnalyticsPage() { Top sản phẩm bán chạy - {/* Category filter */} -
- - -
+ - - {/* Top 5 bar visual */}
{top5.map((p, i) => { - const maxRev = top5[0].revenue; - const pct = (p.revenue / maxRev) * 100; + const pct = (p.revenue / top5[0].revenue) * 100; return ( -
+
{i + 1} - - {p.name} - + {p.name}
- - {p.unitsSold} ly - - - {formatCurrency(p.revenue)} - + {p.unitsSold} ly + {formatCurrency(p.revenue)}
-
+
); @@ -1091,78 +309,30 @@ export default function AnalyticsPage() { Phân tích chi tiết sản phẩm -
- - -
+
-

- Click vào tiêu đề cột để sắp xếp. Hiển thị {filteredSales.length}{" "} - sản phẩm. + Click vào tiêu đề cột để sắp xếp. Hiển thị {filteredSales.length} sản phẩm.

- {/* Totals row */} + {/* Summary row */}
- - Tổng doanh thu:{" "} - - - {formatCurrencyFull( - filteredSales.reduce((s, d) => s + d.revenue, 0), - )} - + Tổng doanh thu: + {formatCurrencyFull(filteredRevenue)}
- - Tổng lợi nhuận:{" "} - - - {formatCurrencyFull( - filteredSales.reduce((s, d) => s + d.profit, 0), - )} - + Tổng lợi nhuận: + {formatCurrencyFull(filteredProfit)}
- - Tổng sản lượng:{" "} - - - {filteredSales - .reduce((s, d) => s + d.unitsSold, 0) - .toLocaleString()}{" "} - ly - + Tổng sản lượng: + {filteredUnits.toLocaleString()} ly
- - Biên LN trung bình:{" "} - - - {filteredSales.length > 0 - ? ( - filteredSales.reduce((s, d) => s + d.profitMargin, 0) / - filteredSales.length - ).toFixed(1) - : 0} - % - + Biên LN trung bình: + {avgMargin.toFixed(1)}%
diff --git a/app/layout.tsx b/app/layout.tsx index b7b188a..60ad434 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,4 @@ -import CartFab from "@/components/CartFab"; +import { CartFab } from "@/components/organisms/cart"; import Footer from "@/layouts/footer"; import Header from "@/layouts/header"; import type { Metadata } from "next"; diff --git a/components/COMPONENTS.md b/components/COMPONENTS.md index d4232f4..4973951 100644 --- a/components/COMPONENTS.md +++ b/components/COMPONENTS.md @@ -9,12 +9,8 @@ components/ ├── atoms/ # Basic building blocks (Button, Input, Text, Badge...) ├── molecules/ # Groups of atoms (ProductCard, SearchBar...) -├── organisms/ # Complex UI sections (CategorySidebar, CartFab, ProductGrid, ReviewModal...) -├── templates/ # Page layout structures (MainLayout, AuthLayout...) -├── CartProduct.tsx # Legacy — use molecules/cards/ProductCard instead -├── Navbar.tsx # Legacy — use organisms/navigation/CategorySidebar instead -├── CartFab.tsx # Legacy — use organisms/cart/CartFab instead -└── ReviewModal.tsx # Legacy — use organisms/modals/ReviewModal instead +├── organisms/ # Complex UI sections (CategorySidebar, CartFab, ProductGrid, ReviewModal, analytics charts...) +└── templates/ # Page layout structures (MainLayout, AuthLayout...) ``` --- @@ -23,11 +19,14 @@ components/ ### ProductCard (`molecules/cards/ProductCard.tsx`) -**Description:** Product card molecule. Displays product image, name, description, formatted price, and a Buy button. -Width is controlled by the parent grid (w-full), not the card itself. +**Description:** Product card molecule. Displays product image, name, description, formatted price, and a Buy button. Width is controlled by the parent grid (w-full). **Atomic level:** Molecule (composed of Button, Caption, Text atoms) +### ShopCard (`molecules/cards/ShopCard.tsx`) + +**Description:** Shop discovery card. Displays shop image, name, address, and a link to the menu. + ### SearchBar (`molecules/search-bar/SearchBar.tsx`) **Description:** Search input with clear button. Controlled component — value and onChange come from parent. @@ -48,10 +47,28 @@ Width is controlled by the parent grid (w-full), not the card itself. **Description:** Full product grid organism with mobile category tabs, filtering, and empty state. Uses useCart and useMenu contexts. +### ShopGrid (`organisms/shop-grid/ShopGrid.tsx`) + +**Description:** Responsive grid of ShopCard molecules for the feed/discovery page. + ### ReviewModal (`organisms/modals/ReviewModal.tsx`) **Description:** Modal for customer reviews. Shows 5-star rating and textarea. After submission, displays thank-you message. +### Analytics Charts (`organisms/analytics/`) + +**Description:** Pure-SVG chart and table components for the Financial Analytics dashboard. All components are interactive with hover tooltips. + +| Component | File | Description | +|-----------|------|-------------| +| LineChart | LineChart.tsx | Revenue trend line chart with area fill and hover tooltips | +| BarChart | BarChart.tsx | Grouped bar chart comparing current vs previous period | +| PieChart | PieChart.tsx | Pie chart with interactive legend for category breakdown | +| SummaryCard | SummaryCard.tsx | Metric card with period-over-period comparison indicator | +| ProductTable | ProductTable.tsx | Sortable product sales table with profit margin badges | + +**Usage:** Imported via `@/components/organisms/analytics` barrel index. + --- ## TEMPLATES @@ -64,395 +81,30 @@ Width is controlled by the parent grid (w-full), not the card itself. **Description:** Auth layout template — centers content in screen. Used by login/register pages. ---- +### FeedLayout (`templates/feed-layout/FeedLayout.tsx`) -## LEGACY COMPONENTS (root level — kept for backward compatibility) +**Description:** Feed layout template. Used by the feed/discovery route group. -### CartProduct +### ManagerLayout (`templates/manager-layout/ManagerLayout.tsx`) -**File:** components/CartProduct.tsx **Description:** Product card component. -Displays product image, name, description, formatted price, and a Buy button. -Width is controlled by the parent grid (w-full), not the card itself. - -### Props - -| Prop | Type | Required | Default | Description | -| ----------- | ---------------- | -------- | ------------ | ----------------------------------------------------------- | -| image | string | yes | - | URL/path to product image (Next.js Image) | -| imageAlt | string | no | Anh san pham | Alt text for accessibility | -| productName | string | yes | - | Product display name | -| price | number or string | yes | - | If number: auto-formatted to VND. If string: rendered as-is | -| description | string | yes | - | Short description, clamped to 2 lines | -| onBuy | () => void | no | undefined | Callback when Buy button is clicked | - -### Internal Logic - -- formattedPrice: number -> toLocaleString(vi-VN, { style: currency, currency: - VND }) -- Image fallback: fa-solid fa-mug-hot icon shown behind image; if image fails - onError hides the img element - -### Styling (CSS variables) - -| Element | Key classes | -| ------------ | ------------------------------------------------------------------ | -| Card wrapper | flex flex-col w-full rounded-2xl, shadow uses --color-shadow-sm/md | -| Image area | relative w-full h-36, bg --color-border-light | -| Product name | font-bold text-sm, color --color-text-primary, line-clamp-1 | -| Description | text-xs, color --color-text-muted, line-clamp-2 | -| Price | text-sm font-bold, color --color-primary | -| Buy button | bg --color-primary, hover --color-primary-dark, active:scale-95 | - -### Dependencies - -- next/image -- Tailwind CSS + CSS custom properties from globals.css -- FontAwesome (fa-solid fa-mug-hot fallback, fa-cart-plus button icon) - -### Notes - -- Card width is w-full - controlled by parent grid in page.tsx -- available field on Product is checked in page.tsx before rendering -- onBuy currently logs to console - TODO: implement cart logic +**Description:** Manager layout template — auth guard + ManagerProvider. Used by the manager route group. --- -## Navbar +## Contexts Documentation -**File:** components/Navbar.tsx **Description:** Left sidebar with collapsible -category filter. Sticky below header, full viewport height minus header. +### AuthContext (`lib/auth-context.tsx`) -### Props +Manages user authentication state including login, logout, and registration. Uses localStorage for persistence. -| Prop | Type | Required | Default | Description | -| ---------------- | -------------------- | -------- | --------- | ------------------------------------------------- | -| isOpen | boolean | yes | - | true = expanded (240px), false = collapsed (64px) | -| onToggle | () => void | yes | - | Toggle expand/collapse | -| activeCategory | string | no | all | Currently selected category id | -| onCategoryChange | (id: string) => void | no | undefined | Fired when user clicks a category | +### CartContext (`lib/cart-context.tsx`) -### Behavior +Manages shopping cart state with localStorage persistence. Tracks items, quantities, and totals. -- Collapsed: 64px wide, icon only (w-16) -- Expanded: 240px wide, icon + label (w-60) -- Width transition: transition-all duration-250ms -- Active category: highlighted with --color-primary background -- Footer shows SHOP_INFO.openHours (icon only when collapsed) +### MenuContext (`lib/menu-context.tsx`) -### Styling +Provides shared activeCategory state across components. Synchronizes Header mobile menu and CategorySidebar selection. -| Element | Key classes | -| ------------- | ----------------------------------------------------------- | -| Aside | sticky, border-r --color-border, bg --color-bg-sidebar | -| Toggle button | w-8 h-8 rounded-lg, hover bg --color-border-light | -| Active item | bg --color-primary text-white shadow-sm | -| Inactive item | hover bg --color-border-light, color --color-text-secondary | +### ManagerContext (`lib/manager-context.tsx`) -### Dependencies - -- next/link -- lib/constants: MENU_CATEGORIES, SHOP_INFO -- lib/types: MenuCategory -- FontAwesome icons - ---- - -## Header (layouts/header.tsx) - -**File:** layouts/header.tsx **Description:** Sticky top bar. 2-column layout: -Brand (left) + Auth button (right). Auth cycles Guest -> Manager -> Staff -> -Guest for UI demo. - -### Props - -None - reads SHOP_INFO and MOCK_USERS from lib/constants directly. - -### Internal State - -| State | Type | Description | -| ----- | ------------ | ------------------------------- | -| user | User or null | Current demo user. null = guest | - -### Auth States - -| State | Appearance | -| ------------ | ------------------------------------- | -| Guest (null) | Brown primary button, Dang nhap label | -| Manager | Gold/caramel badge with user-tie icon | -| Staff | Avatar circle + name, bordered button | - -### Responsive - -- Logo + shop name: always visible -- Tagline: hidden < md, shown md+ -- Button label: hidden < sm, shown sm+ - -### Dependencies - -- next/image, next/link -- lib/constants: SHOP_INFO, MOCK_USERS -- lib/types: User -- FontAwesome icons - ---- - -## Footer (layouts/footer.tsx) - -**File:** layouts/footer.tsx **Description:** Site footer with 12-column grid. 3 -sections: Brand info, Social links, WiFi card. - -### Props - -None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly. - -### Layout - -| Section | Mobile | md | lg/xl | -| ------------- | ----------- | ---------- | ------------ | -| Brand info | col-span-12 | col-span-6 | col-span-8/6 | -| Social + WiFi | col-span-12 | col-span-6 | col-span-4/6 | - -### Sections - -1. Brand: logo, name, tagline, address, phone, email, open hours -2. Social: Facebook, TikTok, Website links -3. WiFi: network name + password in monospace styled box -4. Bottom bar: copyright + Made with heart in Vietnam - -### Dependencies - -- next/image, next/link -- lib/constants: SHOP_INFO, SOCIAL_LINKS -- FontAwesome icons - ---- - -## CartFab - -**File:** components/CartFab.tsx **Description:** Floating Action Button -displaying cart item count. Shows badge with number of items and total price on -hover. - -### Props - -| Prop | Type | Required | Default | Description | -| ------- | ---------- | -------- | --------- | ---------------------------- | -| onClick | () => void | no | undefined | Callback when FAB is clicked | - -### Features - -- Displays cart icon with item count badge -- Shows total price on hover in tooltip -- Sticky position (bottom-right) -- Uses cart context to get items and total - -### Styling - -| Element | Key classes | -| ---------- | ----------------------------------------------- | -| FAB button | fixed bottom-6 right-6, rounded-full, shadow-lg | -| Badge | absolute top-0 right-0, red bg, small font | -| Tooltip | appears on hover, shows total price | - -### Dependencies - -- lib/cart-context: useCart() -- FontAwesome icons - ---- - -## ReviewModal - -**File:** components/ReviewModal.tsx **Description:** Modal for customer -reviews. Shows a 5-star rating selector and a textarea for written feedback. -After submission, displays a thank-you message. Appears on the payment page for -customers (via a dedicated "Đánh giá" button) and also opens automatically after -a successful payment action. - -### Props - -| Prop | Type | Required | Default | Description | -| ------- | ---------- | -------- | ------- | ----------------------------------------- | -| isOpen | boolean | yes | - | Controls modal visibility | -| onClose | () => void | yes | - | Callback to close the modal & reset state | - -### Behavior - -- **Star rating:** 5 interactive stars; hover highlights up to hovered star, - click locks selection. Disabled submit button until at least 1 star selected. -- **Star labels:** 1 = Rất tệ, 2 = Tệ, 3 = Bình thường, 4 = Tốt, 5 = Xuất sắc -- **Textarea:** Optional free-text review (placeholder in Vietnamese). -- **Footer buttons:** - - "Quay lại" — closes modal without submitting; resets all state. - - "Xác nhận" — submits (UI-only); transitions to thank-you state. -- **Thank-you state:** Replaces form with "Cảm ơn quý khách" message + close - button. -- **Backdrop click:** Also closes the modal (same as "Quay lại"). -- **State reset:** All state (rating, hover, review text, submitted) resets on - close. - -### Styling - -| Element | Key classes | -| --------------- | ----------------------------------------------------------------------- | -| Backdrop | fixed inset-0, bg-black/50 backdrop-blur-sm, z-50 | -| Modal panel | max-w-md, rounded-2xl, border --color-border-light, white bg, shadow-xl | -| Stars | text-3xl sm:text-4xl, hover:scale-110, active:scale-95 | -| Active star | fa-solid fa-star text-yellow-400 | -| Inactive star | fa-regular fa-star text-(--color-border) | -| Textarea | rounded-xl, focus:ring with --color-primary/20 | -| Quay lại button | border --color-border, hover --color-border-light | -| Xác nhận button | bg --color-primary, disabled:opacity-50 | -| Thank-you icon | fa-solid fa-heart text-(--color-accent), --color-accent-light bg | - -### Responsive - -- Padding: `p-6 sm:p-8` — larger on sm+ screens -- Stars: `text-3xl sm:text-4xl` -- Modal width: `w-full max-w-md` with `p-4` page padding — works on all screen - sizes - -### Dependencies - -- React useState -- FontAwesome icons (fa-star, fa-regular fa-star, fa-heart, fa-arrow-left, - fa-check) -- Tailwind CSS + CSS custom properties from globals.css - -### Usage in Payment Page - -```tsx -// Only shown for customers (user.role === "customer") -const [isReviewOpen, setIsReviewOpen] = useState(false); - -// Button in payment aside (only visible to customers) -; - -// Payment buttons (Tiền mặt / QR Code) also open the modal for customers -const handlePayment = () => { - if (isCustomer) setIsReviewOpen(true); -}; - - setIsReviewOpen(false)} />; -``` - ---- - -# Contexts Documentation - -## AuthContext (lib/auth-context.tsx) - -**File:** lib/auth-context.tsx **Description:** Manages user authentication -state including login, logout, and registration. Uses localStorage for -persistence. - -### Provider Props - -| Prop | Type | Required | Description | -| -------- | --------------- | -------- | ---------------- | -| children | React.ReactNode | yes | Child components | - -### Hook: useAuth() - -Returns `AuthContextType` with: - -| Property | Type | Description | -| -------------------- | ----------------------------------------------- | ------------------------------------------------- | -| user | User \| null | Current logged-in user or null | -| login | (username: string, password: string) => boolean | Login function; returns success status | -| logout | () => void | Logout function; clears user and localStorage | -| registerPhone | string \| null | Phone number during registration flow | -| setRegisterPhone | (phone: string \| null) => void | Update registerPhone state | -| completeRegistration | (phone: string) => void | Complete registration and create customer account | - -### Mock Database - -Pre-configured accounts: - -- Manager: `admin / admin` -- Staff: `Nguyễn Văn An / Nguyễn Văn An`, `Trần Thị Bình / Trần Thị Bình`, etc. -- Customer: Phone number as username, `user1` as password - -### Storage - -- Key: `coffee-shop-user` -- Format: JSON serialized User object - ---- - -## CartContext (lib/cart-context.tsx) - -**File:** lib/cart-context.tsx **Description:** Manages shopping cart state with -localStorage persistence. Tracks items, quantities, and totals. - -### Provider Props - -| Prop | Type | Required | Description | -| -------- | --------------- | -------- | ---------------- | -| children | React.ReactNode | yes | Child components | - -### Hook: useCart() - -Returns `CartContextValue` with: - -| Property | Type | Description | -| -------------- | -------------------------------------- | --------------------------------------------------------- | -| items | CartItem[] | Array of items in cart | -| totalItems | number | Total quantity of items | -| totalPrice | number | Total price in VND | -| addToCart | (product: Product) => void | Add or increase product quantity | -| increaseQty | (id: number) => void | Increase product quantity by 1 | -| decreaseQty | (id: number) => void | Decrease product quantity by 1 (removes if qty reaches 0) | -| removeFromCart | (id: number) => void | Remove product from cart | -| setQuantity | (id: number, quantity: number) => void | Set exact quantity (removes if 0) | - -### CartItem Interface - -```typescript -interface CartItem { - id: number; - name: string; - description: string; - price: number; - quantity: number; -} -``` - -### Storage - -- Key: `coffee-shop-cart` -- Format: JSON serialized CartItem[] -- Auto-loads on mount and auto-saves on change - ---- - -## MenuContext (lib/menu-context.tsx) - -**File:** lib/menu-context.tsx **Description:** Provides shared category/menu -state across components. Synchronizes Header mobile menu and Navbar sidebar -selection. - -### Provider Props - -| Prop | Type | Required | Description | -| -------- | --------------- | -------- | ---------------- | -| children | React.ReactNode | yes | Child components | - -### Hook: useMenu() - -Returns `MenuContextType` with: - -| Property | Type | Description | -| ----------------- | -------------------- | ----------------------------------------------- | -| activeCategory | string | Currently selected category id (default: "all") | -| setActiveCategory | (id: string) => void | Update active category | - -### Use Cases - -- Sync Navbar sidebar and Header mobile menu category selection -- Clear search query when category changes (implemented in main page) -- Pass selected category to product filter logic - -### Default Value - -- `activeCategory: "all"` - Show all products by default +Manages menu CRUD state (products, combos, categories) for the manager dashboard. diff --git a/components/CartFab.tsx b/components/CartFab.tsx deleted file mode 100644 index 1e488eb..0000000 --- a/components/CartFab.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import { useCart } from "@/lib/cart-context"; -import Link from "next/link"; - -export default function CartFab() { - const { totalItems } = useCart(); - - if (totalItems <= 0) return null; - - return ( - - - - {totalItems} - - - ); -} diff --git a/components/CartProduct.tsx b/components/CartProduct.tsx deleted file mode 100644 index ff3a570..0000000 --- a/components/CartProduct.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client"; - -import { Button, Caption, Text } from "@/components/atoms"; -import Image from "next/image"; - -interface CartProductProps { - image: string; - imageAlt?: string; - productName: string; - price: number | string; - description: string; - onBuy?: () => void; -} - -/** - * Product card — fills the parent grid cell width (w-full). - * - * Layout (top → bottom): - * 1. Image area (fixed height h-36) with coffee-mug fallback icon - * 2. Name + description (flex-1, grows to fill space) - * 3. Price + Buy button row (pinned to bottom) - * - * Responsive: card width is controlled by the parent grid, not the card itself. - */ -export default function CartProduct({ - image, - imageAlt = "Ảnh sản phẩm", - productName, - price, - description, - onBuy, -}: CartProductProps) { - const formattedPrice = - typeof price === "number" - ? price.toLocaleString("vi-VN", { style: "currency", currency: "VND" }) - : price; - - return ( -
- {/* ── Image area ── */} -
- {/* Fallback icon (shown when image fails or is missing) */} -
- -
- {/* Product image */} - {imageAlt} { - (e.currentTarget as HTMLImageElement).style.display = "none"; - }} - /> -
- - {/* ── Name + description ── */} -
-

- {productName} -

- {description} -
- - {/* ── Price + Buy button ── */} -
- - {formattedPrice} - - -
-
- ); -} diff --git a/components/Navbar.tsx b/components/Navbar.tsx deleted file mode 100644 index 72f0a14..0000000 --- a/components/Navbar.tsx +++ /dev/null @@ -1,124 +0,0 @@ -"use client"; - -import { MENU_CATEGORIES, SHOP_INFO } from "@/lib/constants"; -import type { MenuCategory } from "@/lib/types"; - -interface NavbarProps { - /** Whether the sidebar is expanded (true) or icon-only (false) */ - isOpen: boolean; - /** Toggle expand / collapse */ - onToggle: () => void; - /** Currently selected category id */ - activeCategory?: string; - /** Fired when user clicks a category */ - onCategoryChange?: (id: string) => void; -} - -/** - * Left sidebar — always visible, collapsible on all screen sizes. - * - * Collapsed → 64 px wide, icon only - * Expanded → 240 px wide, icon + label - * - * Width transition is handled by Tailwind w-16 / w-60 + transition-all. - * Parent controls open/close state via isOpen + onToggle props. - */ -export default function Navbar({ - isOpen, - onToggle, - activeCategory = "all", - onCategoryChange, -}: NavbarProps) { - return ( - - ); -} diff --git a/components/ReviewModal.tsx b/components/ReviewModal.tsx deleted file mode 100644 index 8c843ce..0000000 --- a/components/ReviewModal.tsx +++ /dev/null @@ -1,160 +0,0 @@ -"use client"; - -import { Button, Caption, Heading, Text, Textarea } from "@/components/atoms"; -import { useState } from "react"; - -interface ReviewModalProps { - isOpen: boolean; - onClose: () => void; -} - -export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) { - const [rating, setRating] = useState(0); - const [hovered, setHovered] = useState(0); - const [review, setReview] = useState(""); - const [submitted, setSubmitted] = useState(false); - - if (!isOpen) return null; - - const handleSubmit = () => { - setSubmitted(true); - }; - - const handleClose = () => { - // Reset state when closing - setRating(0); - setHovered(0); - setReview(""); - setSubmitted(false); - onClose(); - }; - - return ( -
- {/* Backdrop */} -