);
@@ -309,30 +379,51 @@ 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.
{/* Summary row */}
- Tổng doanh thu:
- {formatCurrencyFull(filteredRevenue)}
+
+ Tổng doanh thu:{" "}
+
+
+ {formatCurrencyFull(filteredRevenue)}
+
- Tổng lợi nhuận:
- {formatCurrencyFull(filteredProfit)}
+
+ Tổng lợi nhuận:{" "}
+
+
+ {formatCurrencyFull(filteredProfit)}
+
- Tổng sản lượng:
- {filteredUnits.toLocaleString()} ly
+
+ Tổng sản lượng:{" "}
+
+
+ {filteredUnits.toLocaleString()} ly
+
- Biên LN trung bình:
- {avgMargin.toFixed(1)}%
+
+ Biên LN trung bình:{" "}
+
+
+ {avgMargin.toFixed(1)}%
+
diff --git a/components/COMPONENTS.md b/components/COMPONENTS.md
index 4973951..93b80dd 100644
--- a/components/COMPONENTS.md
+++ b/components/COMPONENTS.md
@@ -1,7 +1,7 @@
# Components Documentation
-> This project follows **Atomic Design** pattern.
-> See `Atomic.md` at project root for full structure guide.
+> This project follows **Atomic Design** pattern. See `Atomic.md` at project
+> root for full structure guide.
## Directory Structure
@@ -19,17 +19,21 @@ 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).
+**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.
+**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.
+**Description:** Search input with clear button. Controlled component — value
+and onChange come from parent.
---
@@ -37,35 +41,41 @@ components/
### CategorySidebar (`organisms/navigation/CategorySidebar.tsx`)
-**Description:** Left sidebar with collapsible category filter. Sticky below header, full viewport height minus header.
+**Description:** Left sidebar with collapsible category filter. Sticky below
+header, full viewport height minus header.
### CartFab (`organisms/cart/CartFab.tsx`)
-**Description:** Floating Action Button displaying cart item count. Links to /payment page.
+**Description:** Floating Action Button displaying cart item count. Links to
+/payment page.
### ProductGrid (`organisms/product-grid/ProductGrid.tsx`)
-**Description:** Full product grid organism with mobile category tabs, filtering, and empty state. Uses useCart and useMenu contexts.
+**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.
+**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.
+**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.
+**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 |
+| 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.
@@ -75,11 +85,13 @@ components/
### MainLayout (`templates/main-layout/MainLayout.tsx`)
-**Description:** Main layout template — wraps content with Header, Footer, and CartFab. Used by (main) route group.
+**Description:** Main layout template — wraps content with Header, Footer, and
+CartFab. Used by (main) route group.
### AuthLayout (`templates/auth-layout/AuthLayout.tsx`)
-**Description:** Auth layout template — centers content in screen. Used by login/register pages.
+**Description:** Auth layout template — centers content in screen. Used by
+login/register pages.
### FeedLayout (`templates/feed-layout/FeedLayout.tsx`)
@@ -87,7 +99,8 @@ components/
### ManagerLayout (`templates/manager-layout/ManagerLayout.tsx`)
-**Description:** Manager layout template — auth guard + ManagerProvider. Used by the manager route group.
+**Description:** Manager layout template — auth guard + ManagerProvider. Used by
+the manager route group.
---
@@ -95,16 +108,20 @@ components/
### AuthContext (`lib/auth-context.tsx`)
-Manages user authentication state including login, logout, and registration. Uses localStorage for persistence.
+Manages user authentication state including login, logout, and registration.
+Uses localStorage for persistence.
### CartContext (`lib/cart-context.tsx`)
-Manages shopping cart state with localStorage persistence. Tracks items, quantities, and totals.
+Manages shopping cart state with localStorage persistence. Tracks items,
+quantities, and totals.
### MenuContext (`lib/menu-context.tsx`)
-Provides shared activeCategory state across components. Synchronizes Header mobile menu and CategorySidebar selection.
+Provides shared activeCategory state across components. Synchronizes Header
+mobile menu and CategorySidebar selection.
### ManagerContext (`lib/manager-context.tsx`)
-Manages menu CRUD state (products, combos, categories) for the manager dashboard.
+Manages menu CRUD state (products, combos, categories) for the manager
+dashboard.
diff --git a/components/atoms/ATOMS.md b/components/atoms/ATOMS.md
index 5b55e07..e11486f 100644
--- a/components/atoms/ATOMS.md
+++ b/components/atoms/ATOMS.md
@@ -1,16 +1,18 @@
# Atoms Components Library
-**Status:** ✅ Phase 1 Implementation Complete
-**Created:** 2026-04-03
+**Status:** ✅ Phase 1 Implementation Complete **Created:** 2026-04-03
**Foundation:** Atomic Design Pattern
---
## 📚 Overview
-Atoms are the basic building blocks of your UI. They are small, focused, reusable components that have no dependencies on other components (except potentially other atoms).
+Atoms are the basic building blocks of your UI. They are small, focused,
+reusable components that have no dependencies on other components (except
+potentially other atoms).
**Key Principles:**
+
- ✅ No business logic
- ✅ No context/hooks (useAuth, useCart, etc.)
- ✅ Pure props-based
@@ -25,9 +27,11 @@ Atoms are the basic building blocks of your UI. They are small, focused, reusabl
### Buttons (`buttons/`)
#### **Button.tsx**
+
Main interactive button component with multiple variants and sizes.
**Props:**
+
- `variant` - "primary" | "secondary" | "danger" | "ghost" (default: "primary")
- `size` - "sm" | "md" | "lg" (default: "md")
- `icon` - FontAwesome icon class (optional)
@@ -38,6 +42,7 @@ Main interactive button component with multiple variants and sizes.
- All standard HTML button attributes
**Usage:**
+
```tsx
import { Button } from "@/components/atoms";
@@ -55,6 +60,7 @@ import { Button } from "@/components/atoms";
```
**Styling:**
+
- Primary: Branded color with dark hover
- Secondary: Border style with light background on hover
- Danger: Red for destructive actions
@@ -63,15 +69,18 @@ import { Button } from "@/components/atoms";
---
#### **IconButton.tsx**
+
Button designed specifically for icon-only interactions.
**Props:**
+
- `variant` - "primary" | "secondary" | "danger" | "ghost"
- `size` - "sm" (8x8) | "md" (10x10) | "lg" (12x12)
- `icon` - FontAwesome icon class (required)
- All standard HTML button attributes
**Usage:**
+
```tsx
import { IconButton } from "@/components/atoms";
@@ -84,9 +93,11 @@ import { IconButton } from "@/components/atoms";
### Inputs (`inputs/`)
#### **TextInput.tsx**
+
General text input field with optional label, error, and icon.
**Props:**
+
- `label` - string (optional)
- `error` - string (optional, shows red border and error text)
- `icon` - FontAwesome icon class (optional)
@@ -94,6 +105,7 @@ General text input field with optional label, error, and icon.
- All standard HTML input attributes
**Usage:**
+
```tsx
import { TextInput } from "@/components/atoms";
@@ -115,9 +127,11 @@ import { TextInput } from "@/components/atoms";
---
#### **SearchInput.tsx**
+
Search input with built-in search icon and clear button.
**Props:**
+
- `value` - string (controlled input)
- `onChange` - callback when text changes
- `onClear` - callback for clear button (required for button to show)
@@ -125,6 +139,7 @@ Search input with built-in search icon and clear button.
- All standard HTML input attributes
**Usage:**
+
```tsx
import { SearchInput } from "@/components/atoms";
@@ -135,20 +150,23 @@ const [query, setQuery] = useState("");
onChange={(e) => setQuery(e.target.value)}
onClear={() => setQuery("")}
placeholder="Search products..."
-/>
+/>;
```
---
#### **Textarea.tsx**
+
Multi-line text input with optional label and error.
**Props:**
+
- `label` - string (optional)
- `error` - string (optional)
- All standard HTML textarea attributes
**Usage:**
+
```tsx
import { Textarea } from "@/components/atoms";
@@ -158,7 +176,7 @@ import { Textarea } from "@/components/atoms";
rows={4}
value={review}
onChange={(e) => setReview(e.target.value)}
-/>
+/>;
```
---
@@ -166,14 +184,17 @@ import { Textarea } from "@/components/atoms";
### Typography (`typography/`)
#### **Heading.tsx**
+
Semantic heading component with level-based sizing.
**Props:**
+
- `level` - 1 | 2 | 3 | 4 | 5 | 6 (default: 2)
- `children` - ReactNode
- All standard HTML heading attributes
**Sizing:**
+
- Level 1: text-3xl font-bold
- Level 2: text-2xl font-bold
- Level 3: text-xl font-bold
@@ -182,6 +203,7 @@ Semantic heading component with level-based sizing.
- Level 6: text-sm font-semibold
**Usage:**
+
```tsx
import { Heading } from "@/components/atoms";
@@ -193,20 +215,24 @@ import { Heading } from "@/components/atoms";
---
#### **Text.tsx**
+
Paragraph text with semantic variants.
**Props:**
+
- `variant` - "body1" | "body2" | "caption" | "label" (default: "body1")
- `children` - ReactNode
- All standard HTML paragraph attributes
**Variants:**
+
- body1: text-base (main content)
- body2: text-sm (secondary content)
- caption: text-xs (smallest text)
- label: text-sm font-medium (form labels)
**Usage:**
+
```tsx
import { Text } from "@/components/atoms";
@@ -219,13 +245,16 @@ import { Text } from "@/components/atoms";
---
#### **Caption.tsx**
+
Small caption/note text component.
**Props:**
+
- `children` - ReactNode
- All standard HTML span attributes
**Usage:**
+
```tsx
import { Caption } from "@/components/atoms";
@@ -238,15 +267,19 @@ import { Caption } from "@/components/atoms";
### Badges (`badges/`)
#### **Badge.tsx**
+
Labeled badge component for highlighting information.
**Props:**
-- `variant` - "primary" | "secondary" | "success" | "danger" | "warning" (default: "primary")
+
+- `variant` - "primary" | "secondary" | "success" | "danger" | "warning"
+ (default: "primary")
- `size` - "sm" | "md" (default: "md")
- `children` - ReactNode
- All standard HTML span attributes
**Variants:**
+
- primary: Branded color
- secondary: Light gray
- success: Green
@@ -254,6 +287,7 @@ Labeled badge component for highlighting information.
- warning: Yellow
**Usage:**
+
```tsx
import { Badge } from "@/components/atoms";
@@ -265,14 +299,17 @@ import { Badge } from "@/components/atoms";
---
#### **PriceBadge.tsx**
+
Specialized badge for displaying formatted prices.
**Props:**
+
- `price` - number
- `currency` - string (default: "VND")
- All standard HTML span attributes
**Usage:**
+
```tsx
import { PriceBadge } from "@/components/atoms";
@@ -281,6 +318,7 @@ import { PriceBadge } from "@/components/atoms";
```
**Output:**
+
- VND: "50.000 ₫"
- USD: "$99.99"
@@ -289,13 +327,16 @@ import { PriceBadge } from "@/components/atoms";
### Dividers (`dividers/`)
#### **Divider.tsx**
+
Visual separator element.
**Props:**
+
- `orientation` - "horizontal" | "vertical" (default: "horizontal")
- All standard HTML hr attributes
**Usage:**
+
```tsx
import { Divider } from "@/components/atoms";
@@ -332,34 +373,41 @@ Change one variable in `globals.css` to update all atoms across the app.
## 🔄 Import Patterns
### Individual Imports
+
```tsx
-import { Button } from "@/components/atoms/buttons";
-import { TextInput, SearchInput } from "@/components/atoms/inputs";
-import { Heading, Text, Caption } from "@/components/atoms/typography";
import { Badge, PriceBadge } from "@/components/atoms/badges";
+import { Button } from "@/components/atoms/buttons";
import { Divider } from "@/components/atoms/dividers";
+import { SearchInput, TextInput } from "@/components/atoms/inputs";
+import { Caption, Heading, Text } from "@/components/atoms/typography";
```
### Barrel Exports (Recommended)
+
```tsx
import {
- Button,
- IconButton,
- TextInput,
- SearchInput,
- Textarea,
- Heading,
- Text,
- Caption,
Badge,
- PriceBadge,
+ Button,
+ Caption,
Divider,
+ Heading,
+ IconButton,
+ PriceBadge,
+ SearchInput,
+ Text,
+ TextInput,
+ Textarea,
} from "@/components/atoms";
```
### Type Imports
+
```tsx
-import type { ButtonProps, TextInputProps, HeadingProps } from "@/components/atoms";
+import type {
+ ButtonProps,
+ HeadingProps,
+ TextInputProps,
+} from "@/components/atoms";
```
---
@@ -367,13 +415,10 @@ import type { ButtonProps, TextInputProps, HeadingProps } from "@/components/ato
## ✅ Usage Examples
### Form Field
+
```tsx
-
+ Product NameProduct description
-
+
Add to Cart
@@ -397,6 +443,7 @@ import type { ButtonProps, TextInputProps, HeadingProps } from "@/components/ato
```
### Rating Display
+
```tsx
Reviews
@@ -410,6 +457,7 @@ import type { ButtonProps, TextInputProps, HeadingProps } from "@/components/ato
## 🧪 Testing Atoms
### Props Validation
+
```tsx
// ✅ Valid
Click
@@ -420,7 +468,9 @@ import type { ButtonProps, TextInputProps, HeadingProps } from "@/components/ato
```
### Accessibility
+
All atoms include:
+
- Semantic HTML
- ARIA labels where appropriate
- Keyboard navigation support
@@ -466,7 +516,8 @@ components/atoms/
## 🚀 Next Steps
-Once atoms are comfortable, the next phase is creating **Molecules** - combinations of atoms:
+Once atoms are comfortable, the next phase is creating **Molecules** -
+combinations of atoms:
- **ProductCard** - Image + Text + Badge + Button
- **FormField** - Label + Input + Error Text
@@ -489,6 +540,5 @@ See `OPTIMIZATION_PLAN.md` for the full roadmap.
---
-**Status:** ✅ Complete & Ready for Use
-**Last Updated:** 2026-04-03
-**Phase:** 1 of 5
+**Status:** ✅ Complete & Ready for Use **Last Updated:** 2026-04-03 **Phase:**
+1 of 5
diff --git a/components/atoms/buttons/Button.tsx b/components/atoms/buttons/Button.tsx
index ab7c405..8b42d2d 100644
--- a/components/atoms/buttons/Button.tsx
+++ b/components/atoms/buttons/Button.tsx
@@ -14,10 +14,12 @@ export default function Button({
}: ButtonProps) {
const styles = {
base: "font-semibold rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center justify-center gap-1.5",
- payment: "inline-flex cursor-pointer items-center justify-center gap-2 rounded-xl transition-colors",
- login: "w-full cursor-pointer rounded-xl py-3 font-semibold transition-all duration-150",
- }
-
+ payment:
+ "inline-flex cursor-pointer items-center justify-center gap-2 rounded-xl transition-colors",
+ login:
+ "w-full cursor-pointer rounded-xl py-3 font-semibold transition-all duration-150",
+ };
+
const variants = {
primary:
"bg-(--color-primary) text-white hover:bg-(--color-primary-dark) active:scale-95",
@@ -25,8 +27,10 @@ export default function Button({
"border border-(--color-border) hover:bg-(--color-border-light) active:scale-95",
danger: "bg-red-500 text-white hover:bg-red-600 active:scale-95",
ghost: "bg-transparent hover:bg-(--color-border-light) active:scale-95",
- primaryNoBorder: "border-none bg-(--color-primary) text-white hover:bg-(--color-primary-dark) active:scale-98",
- bgWhite: "border-2 border-(--color-primary) bg-white text-(--color-primary) hover:bg-(--color-primary) hover:text-white active:scale-98",
+ primaryNoBorder:
+ "border-none bg-(--color-primary) text-white hover:bg-(--color-primary-dark) active:scale-98",
+ bgWhite:
+ "border-2 border-(--color-primary) bg-white text-(--color-primary) hover:bg-(--color-primary) hover:text-white active:scale-98",
};
const sizes = {
diff --git a/components/atoms/buttons/Button.types.ts b/components/atoms/buttons/Button.types.ts
index cf24ac5..aa08ee3 100644
--- a/components/atoms/buttons/Button.types.ts
+++ b/components/atoms/buttons/Button.types.ts
@@ -1,8 +1,17 @@
import { ButtonHTMLAttributes } from "react";
-export interface ButtonProps extends Omit, 'style'> {
+export interface ButtonProps extends Omit<
+ ButtonHTMLAttributes,
+ "style"
+> {
style?: "base" | "payment" | "login";
- variant?: "primary" | "secondary" | "danger" | "ghost" | "primaryNoBorder" | "bgWhite";
+ variant?:
+ | "primary"
+ | "secondary"
+ | "danger"
+ | "ghost"
+ | "primaryNoBorder"
+ | "bgWhite";
size?: "sm" | "md" | "lg";
icon?: string; // FontAwesome class like "fa-solid fa-cart-plus"
iconPosition?: "left" | "right";
diff --git a/components/atoms/errors/Error.types.ts b/components/atoms/errors/Error.types.ts
index ba8026b..9374850 100644
--- a/components/atoms/errors/Error.types.ts
+++ b/components/atoms/errors/Error.types.ts
@@ -3,4 +3,4 @@ import { InputHTMLAttributes, TextareaHTMLAttributes } from "react";
export interface ErrorMessageLoginProps {
message: string;
type?: string;
- }
+}
diff --git a/components/atoms/errors/ErrorMessageLogin.tsx b/components/atoms/errors/ErrorMessageLogin.tsx
index a837fb1..250794a 100644
--- a/components/atoms/errors/ErrorMessageLogin.tsx
+++ b/components/atoms/errors/ErrorMessageLogin.tsx
@@ -26,7 +26,5 @@ export default function ErrorMessageLogin({
);
}
- return (
- type === "primary" ? primaryType() : secondaryType()
- );
+ return type === "primary" ? primaryType() : secondaryType();
}
diff --git a/components/atoms/errors/index.ts b/components/atoms/errors/index.ts
index 81c3cf8..e74b0ad 100644
--- a/components/atoms/errors/index.ts
+++ b/components/atoms/errors/index.ts
@@ -1,4 +1,2 @@
export { default as ErrorMessageLogin } from "./ErrorMessageLogin";
-export type {
- ErrorMessageLoginProps,
-} from "./Error.types";
+export type { ErrorMessageLoginProps } from "./Error.types";
diff --git a/components/atoms/inputs/LoginInput.tsx b/components/atoms/inputs/LoginInput.tsx
index 7984f03..60067df 100644
--- a/components/atoms/inputs/LoginInput.tsx
+++ b/components/atoms/inputs/LoginInput.tsx
@@ -46,7 +46,11 @@ export default function LoginInput({
type={showPassword ? "text" : type}
value={value}
onChange={onChange}
- placeholder= {type === "password" ? "Mật khẩu" : "admin / số điện thoại / tên nhân viên"}
+ placeholder={
+ type === "password"
+ ? "Mật khẩu"
+ : "admin / số điện thoại / tên nhân viên"
+ }
className={`text-foreground focus:ring-opacity-20 w-full rounded-xl border bg-white px-10 py-3 transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary) lg:pl-11 ${errors ? "border-red-400" : "border-(--color-border)"} `}
/>
{isPassword()}
diff --git a/components/molecules/cards/PaymentSummaryCard.tsx b/components/molecules/cards/PaymentSummaryCard.tsx
index 79214d0..adc8505 100644
--- a/components/molecules/cards/PaymentSummaryCard.tsx
+++ b/components/molecules/cards/PaymentSummaryCard.tsx
@@ -1,7 +1,6 @@
import Button from "@/components/atoms/buttons/Button";
-import Link from "next/link";
-
import { ReviewModal } from "@/components/organisms/modals";
+import Link from "next/link";
import { useState } from "react";
import type { PaymentSummaryCardProps } from "./Card.types";
@@ -14,14 +13,13 @@ export default function PaymentSummaryCard({
isCustomer = false,
backHref,
}: PaymentSummaryCardProps) {
-
- const [isReviewOpen, setIsReviewOpen] = useState(false);
- const handlePayment = () => {
+ const [isReviewOpen, setIsReviewOpen] = useState(false);
+ const handlePayment = () => {
// UI-only: open review modal after "payment"
if (isCustomer) {
- setIsReviewOpen(true);
- }
-};
+ setIsReviewOpen(true);
+ }
+ };
return (