Fix bug pull request #28

Merged
TakahashiNguyen merged 13 commits from develop into main 2026-04-06 14:09:48 +00:00
9 changed files with 1495 additions and 7 deletions
Showing only changes of commit e211ee5835 - Show all commits
+1 -1
View File
@@ -1,4 +1,4 @@
Rules:
## Rules:
- Khi làm một tính năng mới, trước khi hoàn thành phải update các file mark down mà thư mục đó được update.
- Khi có từ khóa "Yêu cầu" và một list các yêu cầu thì phải hoàn thành ĐÚNG yêu cầu, không thêm không bớt.
- Sử dụng thư viện tailwind CSS để code css cho project.
+2 -1
View File
@@ -2,7 +2,8 @@
"permissions": {
"allow": [
"Bash(npx next build)",
"Bash(npx tsc --noEmit)"
"Bash(npx tsc --noEmit)",
"Bash(npm run lint)"
]
}
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -1160,6 +1160,21 @@ export default function ManagerPage() {
</span>
</button>
))}
<div className="mt-3 border-t border-(--color-border-light) pt-3">
<p className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-(--color-text-muted)">
Phân tích
</p>
<Link
href="/manager/analytics"
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) no-underline transition-all hover:bg-background hover:text-(--color-primary-dark)"
>
<i className="fa-solid fa-chart-line w-4 text-center"></i>
<span className="flex-1 text-left">Tài chính</span>
<span className="rounded-full bg-(--color-accent-light) px-2 py-0.5 text-xs font-semibold text-(--color-primary)">
Mới
</span>
</Link>
</div>
</nav>
{/* User + logout */}
@@ -1230,10 +1245,24 @@ export default function ManagerPage() {
<span className="hidden sm:inline">{tab.label}</span>
</button>
))}
<Link
href="/manager/analytics"
className="flex items-center gap-1.5 rounded-xl border-none bg-(--color-accent-light) px-3 py-2 text-xs font-medium text-(--color-primary) no-underline transition hover:bg-(--color-accent-light)/70"
>
<i className="fa-solid fa-chart-line"></i>
<span className="hidden sm:inline">Tài chính</span>
</Link>
</div>
{/* Desktop: right-side actions */}
<div className="hidden items-center gap-2 lg:flex">
<Link
href="/manager/analytics"
className="flex items-center gap-1.5 rounded-xl bg-(--color-accent-light) px-3 py-2 text-xs font-medium text-(--color-primary) no-underline transition hover:opacity-80"
>
<i className="fa-solid fa-chart-line"></i>
Thống tài chính
</Link>
<Link
href="/"
className="flex items-center gap-1.5 rounded-xl border border-(--color-border-light) bg-transparent px-3 py-2 text-xs font-medium text-(--color-text-secondary) no-underline transition hover:bg-background"
+64 -5
View File
@@ -20,10 +20,16 @@ app/
│ │ └── page.tsx # Trang đăng ký (/register)
│ └── payment/
│ └── page.tsx # Trang thanh toán (/payment)
── (feed)/
├── layout.tsx # Feed layout
└── feed/
└── page.tsx # Trang khám phá quán (/feed)
── (feed)/
├── layout.tsx # Feed layout
└── feed/
└── page.tsx # Trang khám phá quán (/feed)
└── (manager)/
├── layout.tsx # Manager layout - auth guard + ManagerProvider
└── manager/
├── page.tsx # Manager Dashboard - quản lý thực đơn (/manager)
└── analytics/
└── page.tsx # Financial Analytics (/manager/analytics)
```
---
@@ -423,12 +429,65 @@ Defined at `:root` for light mode, with dark mode variants:
---
## (manager) Route Group
### Layout (app/(manager)/layout.tsx)
**Description:** Layout for manager routes. Guards access — redirects non-managers to `/`. Wraps children in `ManagerProvider`.
---
### Manager Dashboard (app/(manager)/manager/page.tsx)
**Route:** `/manager` **Type:** Client component **Description:** Full menu management interface for the shop owner.
#### Key Features
- **Sidebar (desktop):** Brand, tab navigation (Thực đơn / Combo / Danh mục), link to Financial Analytics, user info, logout
- **Top bar:** Page title, mobile tab switcher, analytics shortcut button
- **Tabs:** Products, Combos, Categories — each with CRUD modals
- **Analytics link:** `fa-chart-line` button → `/manager/analytics`
---
### Financial Analytics (app/(manager)/manager/analytics/page.tsx)
**Route:** `/manager/analytics` **Type:** Client component **Description:** Financial analytics and reporting dashboard for the shop manager.
#### Key Features
- **Summary cards:** Total revenue, orders, profit, average order value — each with period-over-period comparison %
- **Period selector:** Day / Week / Month / Year — switches revenue dataset
- **Chart switcher:** Line chart (trend), Bar chart (current vs previous half), Pie chart (revenue by category)
- **SVG charts:** Pure SVG, no external library — hover tooltips stay inside viewBox (auto-flip above/below for line points & bar tops), interactive dots/slices
- **Top 5 products:** Horizontal bar ranking filtered by category
- **Product detail table:** All 18 products sortable by units sold, revenue, profit, margin
- **Summary row:** Totals for filtered data (revenue, profit, volume, avg margin)
- **Category filter:** Dropdown to filter both top-5 and detail table
#### Data Sources
- `MOCK_REVENUE_DAILY/WEEKLY/MONTHLY/YEARLY` from `lib/constants.ts`
- `MOCK_PRODUCT_SALES` from `lib/constants.ts`
- `MENU_CATEGORIES` for category labels
#### State Management
```tsx
period: AnalyticsPeriod // "day" | "week" | "month" | "year"
activeChart: "line"|"bar"|"pie"
categoryFilter: string // category id or "all"
```
---
## Future Enhancements
- [x] Payment page implementation
- [x] Customer review modal (ReviewModal) with 5-star rating + textarea
- [x] Manager dashboard (menu management)
- [x] Financial Analytics dashboard (charts, profit analysis)
- [ ] Order history/tracking page
- [ ] Manager dashboard (menu management, order tracking)
- [ ] User profile page
- [ ] Cart checkout flow
- [ ] Real backend API integration
+69
View File
@@ -172,6 +172,54 @@ interface Shop {
---
### Financial Analytics Types
Types for the Financial Analytics module (`/manager/analytics`).
```typescript
type AnalyticsPeriod = "day" | "week" | "month" | "year";
interface RevenueDataPoint {
label: string; // display label (e.g. "01/04", "T3/W2")
revenue: number; // VND
orders: number;
}
interface ProductSalesStats {
productId: number;
name: string;
category: string;
unitsSold: number;
revenue: number;
costPrice: number; // giá nhập
sellingPrice: number; // giá bán
profit: number;
profitMargin: number; // %
}
interface PeriodComparison {
current: number;
previous: number;
change: number;
changePercent: number;
isPositive: boolean;
}
interface FinancialSummary {
totalRevenue: number;
totalOrders: number;
totalProfit: number;
averageOrderValue: number;
revenueComparison: PeriodComparison;
ordersComparison: PeriodComparison;
profitComparison: PeriodComparison;
}
```
**Usage:** `app/(manager)/manager/analytics/page.tsx`
---
## Constants (lib/constants.ts)
**Description:** Mock data used throughout the application. Replace with API
@@ -290,6 +338,27 @@ Array of 5 coffee shops for the feed/discovery page.
---
### MOCK_REVENUE_DAILY / WEEKLY / MONTHLY / YEARLY
Revenue time-series data for different periods. Used in the Financial Analytics page.
- `MOCK_REVENUE_DAILY` — 30 data points (April 2026, by day)
- `MOCK_REVENUE_WEEKLY` — 12 data points (last 12 weeks)
- `MOCK_REVENUE_MONTHLY` — 12 data points (last 12 months)
- `MOCK_REVENUE_YEARLY` — 5 data points (20222026)
Each `RevenueDataPoint`: `{ label, revenue (VND), orders }`.
---
### MOCK_PRODUCT_SALES
Array of 18 `ProductSalesStats` — sales statistics for each product including cost price, selling price, profit and margin. Used in the analytics table and top-products ranking.
**Usage:** `/manager/analytics` — product table, top-5 bar, pie chart
---
### MOCK_USERS
Pre-configured user accounts for authentication demo.
+281
View File
@@ -2,6 +2,8 @@ import type {
Combo,
MenuCategory,
Product,
ProductSalesStats,
RevenueDataPoint,
Shop,
ShopInfo,
SocialLinks,
@@ -312,6 +314,285 @@ export const MOCK_SHOPS: Shop[] = [
},
];
// ===== MOCK FINANCIAL DATA =====
// Daily revenue for the last 30 days (current month)
export const MOCK_REVENUE_DAILY: RevenueDataPoint[] = [
{ label: "01/04", revenue: 1250000, orders: 42 },
{ label: "02/04", revenue: 980000, orders: 35 },
{ label: "03/04", revenue: 1540000, orders: 55 },
{ label: "04/04", revenue: 1120000, orders: 40 },
{ label: "05/04", revenue: 1890000, orders: 68 },
{ label: "06/04", revenue: 2100000, orders: 74 },
{ label: "07/04", revenue: 2350000, orders: 82 },
{ label: "08/04", revenue: 1680000, orders: 60 },
{ label: "09/04", revenue: 1430000, orders: 50 },
{ label: "10/04", revenue: 1750000, orders: 63 },
{ label: "11/04", revenue: 1560000, orders: 56 },
{ label: "12/04", revenue: 1920000, orders: 70 },
{ label: "13/04", revenue: 2230000, orders: 80 },
{ label: "14/04", revenue: 2480000, orders: 90 },
{ label: "15/04", revenue: 1870000, orders: 66 },
{ label: "16/04", revenue: 1340000, orders: 48 },
{ label: "17/04", revenue: 1610000, orders: 58 },
{ label: "18/04", revenue: 1490000, orders: 53 },
{ label: "19/04", revenue: 1970000, orders: 71 },
{ label: "20/04", revenue: 2140000, orders: 77 },
{ label: "21/04", revenue: 2560000, orders: 93 },
{ label: "22/04", revenue: 1780000, orders: 64 },
{ label: "23/04", revenue: 1520000, orders: 54 },
{ label: "24/04", revenue: 1840000, orders: 66 },
{ label: "25/04", revenue: 1660000, orders: 60 },
{ label: "26/04", revenue: 2050000, orders: 74 },
{ label: "27/04", revenue: 2280000, orders: 82 },
{ label: "28/04", revenue: 2490000, orders: 89 },
{ label: "29/04", revenue: 1950000, orders: 70 },
{ label: "30/04", revenue: 1720000, orders: 62 },
];
// Weekly revenue (last 12 weeks)
export const MOCK_REVENUE_WEEKLY: RevenueDataPoint[] = [
{ label: "T1/W1", revenue: 8200000, orders: 295 },
{ label: "T1/W2", revenue: 9450000, orders: 340 },
{ label: "T1/W3", revenue: 10100000, orders: 362 },
{ label: "T1/W4", revenue: 8750000, orders: 315 },
{ label: "T2/W1", revenue: 9200000, orders: 330 },
{ label: "T2/W2", revenue: 10500000, orders: 378 },
{ label: "T2/W3", revenue: 11200000, orders: 400 },
{ label: "T2/W4", revenue: 9800000, orders: 352 },
{ label: "T3/W1", revenue: 10400000, orders: 374 },
{ label: "T3/W2", revenue: 11800000, orders: 424 },
{ label: "T3/W3", revenue: 12500000, orders: 448 },
{ label: "T3/W4", revenue: 10900000, orders: 392 },
];
// Monthly revenue (last 12 months)
export const MOCK_REVENUE_MONTHLY: RevenueDataPoint[] = [
{ label: "T4/2025", revenue: 42000000, orders: 1512 },
{ label: "T5/2025", revenue: 45500000, orders: 1638 },
{ label: "T6/2025", revenue: 48000000, orders: 1728 },
{ label: "T7/2025", revenue: 52000000, orders: 1872 },
{ label: "T8/2025", revenue: 49500000, orders: 1782 },
{ label: "T9/2025", revenue: 46800000, orders: 1685 },
{ label: "T10/2025", revenue: 51200000, orders: 1843 },
{ label: "T11/2025", revenue: 55000000, orders: 1980 },
{ label: "T12/2025", revenue: 62000000, orders: 2232 },
{ label: "T1/2026", revenue: 44000000, orders: 1584 },
{ label: "T2/2026", revenue: 47500000, orders: 1710 },
{ label: "T3/2026", revenue: 53500000, orders: 1926 },
];
// Yearly revenue (last 5 years)
export const MOCK_REVENUE_YEARLY: RevenueDataPoint[] = [
{ label: "2022", revenue: 420000000, orders: 15120 },
{ label: "2023", revenue: 485000000, orders: 17460 },
{ label: "2024", revenue: 530000000, orders: 19080 },
{ label: "2025", revenue: 598000000, orders: 21528 },
{ label: "2026", revenue: 180000000, orders: 6480 },
];
// Product sales statistics (with cost price for profit analysis)
export const MOCK_PRODUCT_SALES: ProductSalesStats[] = [
{
productId: 12,
name: "Latte Caramel",
category: "latte",
unitsSold: 487,
revenue: 21915000,
costPrice: 18000,
sellingPrice: 45000,
profit: 13185000,
profitMargin: 60.2,
},
{
productId: 6,
name: "Trà Xanh Matcha",
category: "tra",
unitsSold: 412,
revenue: 16480000,
costPrice: 15000,
sellingPrice: 40000,
profit: 10300000,
profitMargin: 62.5,
},
{
productId: 5,
name: "Trà Đào Cam Sả",
category: "tra",
unitsSold: 398,
revenue: 13930000,
costPrice: 12000,
sellingPrice: 35000,
profit: 9153000,
profitMargin: 65.7,
},
{
productId: 13,
name: "Latte Vanilla",
category: "latte",
unitsSold: 356,
revenue: 16020000,
costPrice: 18000,
sellingPrice: 45000,
profit: 9612000,
profitMargin: 60.0,
},
{
productId: 4,
name: "Cà Phê Trứng",
category: "cafe",
unitsSold: 340,
revenue: 15300000,
costPrice: 16000,
sellingPrice: 45000,
profit: 9860000,
profitMargin: 64.4,
},
{
productId: 2,
name: "Cà Phê Sữa",
category: "cafe",
unitsSold: 325,
revenue: 9750000,
costPrice: 10000,
sellingPrice: 30000,
profit: 6500000,
profitMargin: 66.7,
},
{
productId: 3,
name: "Bạc Xỉu",
category: "cafe",
unitsSold: 298,
revenue: 9536000,
costPrice: 11000,
sellingPrice: 32000,
profit: 6259000,
profitMargin: 65.6,
},
{
productId: 1,
name: "Cà Phê Đen",
category: "cafe",
unitsSold: 285,
revenue: 7125000,
costPrice: 8000,
sellingPrice: 25000,
profit: 4845000,
profitMargin: 68.0,
},
{
productId: 9,
name: "Sữa Chua Dâu",
category: "sua-chua",
unitsSold: 267,
revenue: 10680000,
costPrice: 15000,
sellingPrice: 40000,
profit: 6675000,
profitMargin: 62.5,
},
{
productId: 10,
name: "Nước Ép Cam",
category: "nuoc-ep",
unitsSold: 241,
revenue: 8435000,
costPrice: 12000,
sellingPrice: 35000,
profit: 5543000,
profitMargin: 65.7,
},
{
productId: 8,
name: "Sữa Chua Trân Châu",
category: "sua-chua",
unitsSold: 228,
revenue: 8664000,
costPrice: 14000,
sellingPrice: 38000,
profit: 5472000,
profitMargin: 63.2,
},
{
productId: 7,
name: "Trà Vải Hoa Nhài",
category: "tra",
unitsSold: 215,
revenue: 8170000,
costPrice: 13000,
sellingPrice: 38000,
profit: 5375000,
profitMargin: 65.8,
},
{
productId: 15,
name: "Bánh Flan",
category: "giai-khat",
unitsSold: 198,
revenue: 4950000,
costPrice: 8000,
sellingPrice: 25000,
profit: 3366000,
profitMargin: 68.0,
},
{
productId: 11,
name: "Nước Ép Dưa Hấu",
category: "nuoc-ep",
unitsSold: 182,
revenue: 5460000,
costPrice: 10000,
sellingPrice: 30000,
profit: 3640000,
profitMargin: 66.7,
},
{
productId: 14,
name: "Bánh Mì Nướng Bơ",
category: "giai-khat",
unitsSold: 175,
revenue: 3500000,
costPrice: 6000,
sellingPrice: 20000,
profit: 2450000,
profitMargin: 70.0,
},
{
productId: 16,
name: "Trân Châu Đen",
category: "topping",
unitsSold: 456,
revenue: 4560000,
costPrice: 2000,
sellingPrice: 10000,
profit: 3648000,
profitMargin: 80.0,
},
{
productId: 17,
name: "Thạch Cà Phê",
category: "topping",
unitsSold: 389,
revenue: 3890000,
costPrice: 2000,
sellingPrice: 10000,
profit: 3112000,
profitMargin: 80.0,
},
{
productId: 18,
name: "Trân Châu Trắng",
category: "topping",
unitsSold: 342,
revenue: 3420000,
costPrice: 2000,
sellingPrice: 10000,
profit: 2736000,
profitMargin: 80.0,
},
];
// ===== MOCK USERS (for UI demo replace with real auth) =====
export const MOCK_USERS: Record<string, User> = {
manager: {
+39
View File
@@ -60,6 +60,45 @@ export interface Shop {
image: string;
}
// ===== FINANCIAL ANALYTICS TYPES =====
export type AnalyticsPeriod = "day" | "week" | "month" | "year";
export interface RevenueDataPoint {
label: string; // e.g. "01/04", "Tuần 1", "Tháng 1"
revenue: number;
orders: number;
}
export interface ProductSalesStats {
productId: number;
name: string;
category: string;
unitsSold: number;
revenue: number;
costPrice: number; // giá nhập
sellingPrice: number; // giá bán
profit: number;
profitMargin: number; // %
}
export interface PeriodComparison {
current: number;
previous: number;
change: number; // absolute
changePercent: number; // %
isPositive: boolean;
}
export interface FinancialSummary {
totalRevenue: number;
totalOrders: number;
totalProfit: number;
averageOrderValue: number;
revenueComparison: PeriodComparison;
ordersComparison: PeriodComparison;
profitComparison: PeriodComparison;
}
// ===== COMBO TYPES =====
export interface ComboItem {
productId: number;