diff --git a/PHASE_1_SUMMARY.md b/PHASE_1_SUMMARY.md index 086467a..541f6ff 100644 --- a/PHASE_1_SUMMARY.md +++ b/PHASE_1_SUMMARY.md @@ -1,9 +1,7 @@ # Phase 1 Implementation Summary: Atomic Design Foundation -**Status:** ✅ COMPLETE -**Date:** 2026-04-03 -**Commit:** eca619b -**Branch:** atomic_design +**Status:** ✅ COMPLETE **Date:** 2026-04-03 **Commit:** eca619b **Branch:** +atomic_design --- @@ -12,6 +10,7 @@ ### ✨ Created 21 Atom Components #### Buttons (2 components) + ``` components/atoms/buttons/ ├── Button.tsx (4 variants: primary, secondary, danger, ghost) @@ -20,12 +19,14 @@ components/atoms/buttons/ ``` **Key Features:** + - Multiple size options (sm, md, lg) - Icon support with positioning (left/right) - Full HTML button attribute support - Active/disabled states with visual feedback #### Inputs (3 components) + ``` components/atoms/inputs/ ├── TextInput.tsx (with label, error, icon support) @@ -35,12 +36,14 @@ components/atoms/inputs/ ``` **Key Features:** + - Error state handling with red border - Icon integration with callback support - Accessible form field structure - Controlled/uncontrolled patterns #### Typography (3 components) + ``` components/atoms/typography/ ├── Heading.tsx (h1-h6 with semantic sizing) @@ -50,11 +53,13 @@ components/atoms/typography/ ``` **Key Features:** + - Semantic HTML (`

` through `

`) - Consistent font sizing and weights - CSS variable color application #### Badges (2 components) + ``` components/atoms/badges/ ├── Badge.tsx (5 variants: primary, secondary, success, danger, warning) @@ -63,11 +68,13 @@ components/atoms/badges/ ``` **Key Features:** + - Multiple size options (sm, md) - Formatted price output (VND/USD) - Color-coded variants #### Dividers (1 component) + ``` components/atoms/dividers/ ├── Divider.tsx (horizontal/vertical separators) @@ -108,12 +115,14 @@ components/atoms/dividers/ ### 🔧 Updated Existing Components #### CartProduct.tsx + - ✅ Replaced inline button with ` +; ``` ### Form Field + ```tsx -import { TextInput, Button } from "@/components/atoms"; +import { Button, TextInput } from "@/components/atoms"; +/>; ``` ### Typography + ```tsx import { Heading, Text, Caption } from "@/components/atoms"; @@ -234,6 +250,7 @@ import { Heading, Text, Caption } from "@/components/atoms"; ``` ### Price Display + ```tsx import { PriceBadge } from "@/components/atoms"; @@ -275,6 +292,7 @@ import { PriceBadge } from "@/components/atoms"; ## 🚀 Next Steps (Phase 2) ### Molecules to Create + 1. **ProductCard** - Image + Text + Badge + Button 2. **FormField** - Label + Input + Error + Validation 3. **SearchBar** - SearchInput + Button @@ -288,6 +306,7 @@ import { PriceBadge } from "@/components/atoms"; ## 💡 Design Decisions ### Why Barrel Exports? + ```tsx // ❌ Bad: Scattered imports import Button from "@/components/atoms/buttons/Button"; @@ -298,12 +317,14 @@ import { Button, Text } from "@/components/atoms"; ``` ### Why TypeScript Types in Separate Files? + - Clear separation of concerns - Easier to maintain interfaces - Reusable types across modules - Better for large components ### Why CSS Variables? + - Single point for theme changes - Easy dark mode switching - Consistent across all atoms @@ -357,7 +378,5 @@ feat: Implement Atomic Design Phase 1 - Atoms Foundation --- -**Status:** Ready for Phase 2 🚀 -**Last Updated:** 2026-04-03 -**Contributor:** Claude Code + Anthropic AI - +**Status:** Ready for Phase 2 🚀 **Last Updated:** 2026-04-03 **Contributor:** +Claude Code + Anthropic AI diff --git a/README.md b/README.md index 209acc0..c355a6e 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,15 @@ Dành cho khách hàng: Dành cho quản lý: -- Sidebar desktop: Brand, tab navigation (Thực đơn / Combo / Danh mục), link tới Analytics +- Sidebar desktop: Brand, tab navigation (Thực đơn / Combo / Danh mục), link tới + Analytics - CRUD sản phẩm, combo, danh mục qua modals - Auth guard: tự động redirect non-manager về `/` #### 9. **Trang Phân Tích Tài Chính** (`app/(manager)/manager/analytics`) -- Summary cards: Doanh thu, đơn hàng, lợi nhuận, giá trị đơn trung bình (so sánh kỳ trước) +- Summary cards: Doanh thu, đơn hàng, lợi nhuận, giá trị đơn trung bình (so sánh + kỳ trước) - Bộ chọn kỳ: Ngày / Tuần / Tháng / Năm - Biểu đồ SVG thuần: Line, Bar, Pie — hover tooltips tương tác - Bảng top 5 sản phẩm và bảng chi tiết có thể sắp xếp @@ -196,18 +198,18 @@ frondend/ ## Công Nghệ Sử Dụng -| Công nghệ | Phiên bản | Mục đích | -| ------------ | --------- | ------------------------------------- | -| Next.js | 16.1.7 | React Framework (App Router) | -| React | 19.2.3 | Thư viện UI | -| TypeScript | ^5 | Kiểu dữ liệu tĩnh | -| Tailwind CSS | ^4.2.2 | Utility-first CSS framework | -| Geist Font | - | Font chữ (Google Fonts via next/font) | -| FontAwesome | 6.7.2 | Icon library (CDN) | -| pnpm | - | Package manager | -| ESLint | ^9 | Linting | -| Prettier | ^3 | Code formatting | -| semantic-release | ^25 | Automated versioning & release | +| Công nghệ | Phiên bản | Mục đích | +| ---------------- | --------- | ------------------------------------- | +| Next.js | 16.1.7 | React Framework (App Router) | +| React | 19.2.3 | Thư viện UI | +| TypeScript | ^5 | Kiểu dữ liệu tĩnh | +| Tailwind CSS | ^4.2.2 | Utility-first CSS framework | +| Geist Font | - | Font chữ (Google Fonts via next/font) | +| FontAwesome | 6.7.2 | Icon library (CDN) | +| pnpm | - | Package manager | +| ESLint | ^9 | Linting | +| Prettier | ^3 | Code formatting | +| semantic-release | ^25 | Automated versioning & release | --- @@ -225,11 +227,11 @@ frondend/ ### Tài Khoản Demo -| Loại tài khoản | Tên đăng nhập | Mật khẩu | -| -------------- | -------------- | -------------- | -| Manager | admin | admin | -| Staff | Nguyễn Văn An | Nguyễn Văn An | -| Customer | 0987654321 | user1 | +| Loại tài khoản | Tên đăng nhập | Mật khẩu | +| -------------- | ------------- | ------------- | +| Manager | admin | admin | +| Staff | Nguyễn Văn An | Nguyễn Văn An | +| Customer | 0987654321 | user1 | ### Design & Styling @@ -243,14 +245,18 @@ frondend/ Dự án tuân theo **Atomic Design** pattern — chi tiết tại `Atomic.md`: - **Atoms** - Nguyên tố cơ bản: Button, Input, Badge, Text, Heading, Divider -- **Molecules** - Nhóm atoms: ProductCard, ShopCard, SearchBar, PaymentSummaryCard -- **Organisms** - Phần UI phức tạp: CategorySidebar, CartFab, ProductGrid, ShopGrid, ReviewModal, analytics charts, manager tabs/modals -- **Templates** - Bố cục trang: MainLayout, AuthLayout, FeedLayout, ManagerLayout +- **Molecules** - Nhóm atoms: ProductCard, ShopCard, SearchBar, + PaymentSummaryCard +- **Organisms** - Phần UI phức tạp: CategorySidebar, CartFab, ProductGrid, + ShopGrid, ReviewModal, analytics charts, manager tabs/modals +- **Templates** - Bố cục trang: MainLayout, AuthLayout, FeedLayout, + ManagerLayout ### Data & Integration - Mock data nằm trong `lib/constants.ts` -- Context providers trong `app/providers.tsx`: AuthProvider, MenuProvider, CartProvider +- Context providers trong `app/providers.tsx`: AuthProvider, MenuProvider, + CartProvider - ManagerProvider được thêm bởi `app/(manager)/layout.tsx` - Thay bằng API calls khi backend sẵn sàng - Ảnh sản phẩm: thêm vào `public/imgs/products/` diff --git a/app/(main)/login/page.tsx b/app/(main)/login/page.tsx index b801294..9883080 100644 --- a/app/(main)/login/page.tsx +++ b/app/(main)/login/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { SHOP_INFO } from "@/lib/constants"; import LoginForm from "@/components/organisms/forms/LoginForm"; +import { SHOP_INFO } from "@/lib/constants"; import Image from "next/image"; export default function LoginPage() { @@ -28,7 +28,7 @@ export default function LoginPage() { Đăng nhập vào hệ thống

- + {/* Login Form */} diff --git a/app/(main)/page.tsx b/app/(main)/page.tsx index 705cfbc..604f55c 100644 --- a/app/(main)/page.tsx +++ b/app/(main)/page.tsx @@ -1,10 +1,10 @@ "use client"; +import { SearchBar } from "@/components/molecules/search-bar"; import { CategorySidebar } from "@/components/organisms/navigation"; import { ProductGrid } from "@/components/organisms/product-grid"; -import { SearchBar } from "@/components/molecules/search-bar"; -import { useMenu } from "@/lib/menu-context"; import { MENU_CATEGORIES } from "@/lib/constants"; +import { useMenu } from "@/lib/menu-context"; import { useEffect, useState } from "react"; /** @@ -72,10 +72,7 @@ export default function Home() { {/* ── Product grid (organism handles mobile category menu + grid) ── */} - + ); diff --git a/app/(main)/payment/page.tsx b/app/(main)/payment/page.tsx index f9377f4..b6d9b35 100644 --- a/app/(main)/payment/page.tsx +++ b/app/(main)/payment/page.tsx @@ -75,7 +75,7 @@ export default function PaymentPage() {
@@ -227,12 +232,22 @@ export default function RegisterPage() { {/* Buttons */}
{/* Submit Button */} - {/* Back Button */} -
diff --git a/app/(manager)/manager/analytics/page.tsx b/app/(manager)/manager/analytics/page.tsx index 77041a3..c0d738e 100644 --- a/app/(manager)/manager/analytics/page.tsx +++ b/app/(manager)/manager/analytics/page.tsx @@ -8,7 +8,11 @@ import { SummaryCard, } from "@/components/organisms/analytics"; import type { PieSlice } from "@/components/organisms/analytics"; -import { calcChange, formatCurrency, formatCurrencyFull } from "@/lib/analytics-utils"; +import { + calcChange, + formatCurrency, + formatCurrencyFull, +} from "@/lib/analytics-utils"; import { MENU_CATEGORIES, MOCK_PRODUCT_SALES, @@ -31,8 +35,15 @@ const PERIOD_LABELS: Record = { }; const CATEGORY_COLORS = [ - "#6F4E37", "#C8973A", "#A0785A", "#8B6914", "#D4A96A", - "#4A3728", "#F0D9A8", "#A08060", "#3D2B1F", + "#6F4E37", + "#C8973A", + "#A0785A", + "#8B6914", + "#D4A96A", + "#4A3728", + "#F0D9A8", + "#A08060", + "#3D2B1F", ]; const REVENUE_MAP: Record = { @@ -47,8 +58,8 @@ type ChartType = (typeof CHART_TYPES)[number]; const CHART_META: Record = { line: { icon: "fa-chart-line", label: "Line" }, - bar: { icon: "fa-chart-bar", label: "Bar" }, - pie: { icon: "fa-chart-pie", label: "Pie" }, + bar: { icon: "fa-chart-bar", label: "Bar" }, + pie: { icon: "fa-chart-pie", label: "Pie" }, }; // ─── Category filter select ─────────────────────────────────────────────────── @@ -74,7 +85,9 @@ function CategorySelect({ > {categories.map((c) => ( - + ))}
@@ -93,27 +106,28 @@ export default function AnalyticsPage() { // Split into halves for bar comparison const half = Math.floor(revenueData.length / 2); - const barCurrent = revenueData.slice(half); + const barCurrent = revenueData.slice(half); const barPrevious = revenueData.slice(0, half).slice(0, barCurrent.length); // Filtered product sales const filteredSales = useMemo( - () => categoryFilter === "all" - ? MOCK_PRODUCT_SALES - : MOCK_PRODUCT_SALES.filter((p) => p.category === categoryFilter), + () => + categoryFilter === "all" + ? MOCK_PRODUCT_SALES + : MOCK_PRODUCT_SALES.filter((p) => p.category === categoryFilter), [categoryFilter], ); // Summary stats const totalRevenue = revenueData.reduce((s, d) => s + d.revenue, 0); - const totalOrders = revenueData.reduce((s, d) => s + d.orders, 0); - const totalProfit = filteredSales.reduce((s, d) => s + d.profit, 0); + const totalOrders = revenueData.reduce((s, d) => s + d.orders, 0); + const totalProfit = filteredSales.reduce((s, d) => s + d.profit, 0); const avgOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0; // Period-over-period comparisons const curRevenue = barCurrent.reduce((s, d) => s + d.revenue, 0); const prevRevenue = barPrevious.reduce((s, d) => s + d.revenue, 0); - const curOrders = barCurrent.reduce((s, d) => s + d.orders, 0); + const curOrders = barCurrent.reduce((s, d) => s + d.orders, 0); const prevOrders = barPrevious.reduce((s, d) => s + d.orders, 0); const revComp = calcChange(curRevenue, prevRevenue); const ordComp = calcChange(curOrders, prevOrders); @@ -142,11 +156,13 @@ export default function AnalyticsPage() { // Totals for summary row const filteredRevenue = filteredSales.reduce((s, d) => s + d.revenue, 0); - const filteredProfit = filteredSales.reduce((s, d) => s + d.profit, 0); - const filteredUnits = filteredSales.reduce((s, d) => s + d.unitsSold, 0); - const avgMargin = filteredSales.length > 0 - ? filteredSales.reduce((s, d) => s + d.profitMargin, 0) / filteredSales.length - : 0; + const filteredProfit = filteredSales.reduce((s, d) => s + d.profit, 0); + const filteredUnits = filteredSales.reduce((s, d) => s + d.unitsSold, 0); + const avgMargin = + filteredSales.length > 0 + ? filteredSales.reduce((s, d) => s + d.profitMargin, 0) / + filteredSales.length + : 0; return (
@@ -167,7 +183,9 @@ export default function AnalyticsPage() {

Thống kê & Phân tích tài chính

-

Financial Analytics Dashboard

+

+ Financial Analytics Dashboard +

@@ -192,8 +210,12 @@ export default function AnalyticsPage() { onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)} className="text-foreground block rounded-lg border border-(--color-border) bg-(--color-bg-card) px-2 py-1.5 text-xs sm:hidden" > - {(Object.entries(PERIOD_LABELS) as [AnalyticsPeriod, string][]).map(([k, v]) => ( - + {( + Object.entries(PERIOD_LABELS) as [AnalyticsPeriod, string][] + ).map(([k, v]) => ( + ))} @@ -207,18 +229,42 @@ export default function AnalyticsPage() { Tổng quan
- - - - + + + +
@@ -241,7 +287,9 @@ export default function AnalyticsPage() { }`} > - {CHART_META[t].label} + + {CHART_META[t].label} + ))} @@ -249,19 +297,29 @@ 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 +

)} @@ -274,7 +332,10 @@ export default function AnalyticsPage() { Top sản phẩm bán chạy - +
{top5.map((p, i) => { @@ -286,15 +347,24 @@ export default function AnalyticsPage() { {i + 1} - {p.name} + + {p.name} +
- {p.unitsSold} ly - {formatCurrency(p.revenue)} + + {p.unitsSold} ly + + + {formatCurrency(p.revenue)} +
-
+
); @@ -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 Name Product description -
+
@@ -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 @@ -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 (
); diff --git a/components/organisms/analytics/PieChart.tsx b/components/organisms/analytics/PieChart.tsx index 29da56f..e096084 100644 --- a/components/organisms/analytics/PieChart.tsx +++ b/components/organisms/analytics/PieChart.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useMemo } from "react"; +import { useMemo, useState } from "react"; export interface PieSlice { label: string; @@ -78,7 +78,14 @@ export function PieChart({ data }: PieChartProps) { /> ))} {hovered !== null && ( - + {slices[hovered].percent.toFixed(1)}% )} @@ -93,14 +100,22 @@ export function PieChart({ data }: PieChartProps) { onMouseEnter={() => setHovered(s.index)} onMouseLeave={() => setHovered(null)} > - + {s.label} - {s.percent.toFixed(1)}% + + {s.percent.toFixed(1)}% +
))}
diff --git a/components/organisms/analytics/ProductTable.tsx b/components/organisms/analytics/ProductTable.tsx index 44b12af..d9f9a12 100644 --- a/components/organisms/analytics/ProductTable.tsx +++ b/components/organisms/analytics/ProductTable.tsx @@ -32,18 +32,33 @@ export function ProductTable({ data }: ProductTableProps) { const handleSort = (key: keyof ProductSalesStats) => { if (key === sortKey) setSortDir((d) => (d === "desc" ? "asc" : "desc")); - else { setSortKey(key); setSortDir("desc"); } + else { + setSortKey(key); + setSortDir("desc"); + } }; const sortIcon = (col: keyof ProductSalesStats) => ( - + ); - const SortTh = ({ col, label, className = "" }: { col: keyof ProductSalesStats; label: string; className?: string }) => ( + const SortTh = ({ + col, + label, + className = "", + }: { + col: keyof ProductSalesStats; + label: string; + className?: string; + }) => ( handleSort(col)} @@ -57,38 +72,67 @@ export function ProductTable({ data }: ProductTableProps) { - - - + + + - - + + {sorted.map((row, i) => ( - + - + - - - - - + + + + + diff --git a/components/organisms/analytics/SummaryCard.tsx b/components/organisms/analytics/SummaryCard.tsx index 3f18dd3..bded36a 100644 --- a/components/organisms/analytics/SummaryCard.tsx +++ b/components/organisms/analytics/SummaryCard.tsx @@ -29,7 +29,9 @@ export function SummaryCard({ - {title} + + {title} +

{value}

{subtitle && ( diff --git a/components/organisms/forms/LoginForm.tsx b/components/organisms/forms/LoginForm.tsx index 87899c1..28606a8 100644 --- a/components/organisms/forms/LoginForm.tsx +++ b/components/organisms/forms/LoginForm.tsx @@ -1,10 +1,10 @@ +import Button from "@/components/atoms/buttons/Button"; import ErrorMessageLogin from "@/components/atoms/errors/ErrorMessageLogin"; import LoginInput from "@/components/atoms/inputs/LoginInput"; import { useAuth } from "@/lib/auth-context"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { FormEvent, useState } from "react"; -import Button from "@/components/atoms/buttons/Button"; export default function LoginForm() { const router = useRouter(); @@ -102,7 +102,12 @@ export default function LoginForm() { {/* Buttons */}
{/* Login Button */} - diff --git a/components/organisms/manager/ProductsTab.tsx b/components/organisms/manager/ProductsTab.tsx index 9d5c27c..12f7fbf 100644 --- a/components/organisms/manager/ProductsTab.tsx +++ b/components/organisms/manager/ProductsTab.tsx @@ -152,7 +152,10 @@ export default function ProductsTab() {
) : ( filtered.map((p) => ( - +
#Sản phẩmDanh mục + # + + Sản phẩm + + Danh mục + Giá nhậpGiá bán + Giá nhập + + Giá bán +
{i + 1}{row.name} + {row.name} + {categoryName(row.category)} {row.unitsSold.toLocaleString()}{formatCurrencyFull(row.revenue)}{formatCurrencyFull(row.costPrice)}{formatCurrencyFull(row.sellingPrice)}{formatCurrencyFull(row.profit)} + {row.unitsSold.toLocaleString()} + + {formatCurrencyFull(row.revenue)} + + {formatCurrencyFull(row.costPrice)} + + {formatCurrencyFull(row.sellingPrice)} + + {formatCurrencyFull(row.profit)} + - = 70 ? "bg-green-100 text-green-700" - : row.profitMargin >= 60 ? "bg-yellow-100 text-yellow-700" - : "bg-red-100 text-red-600" - }`}> + = 70 + ? "bg-green-100 text-green-700" + : row.profitMargin >= 60 + ? "bg-yellow-100 text-yellow-700" + : "bg-red-100 text-red-600" + }`} + > {row.profitMargin.toFixed(1)}%

{p.name}

diff --git a/k8s.yaml b/k8s.yaml index 6351d40..f8b68c6 100644 --- a/k8s.yaml +++ b/k8s.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: frontend-container - image: git.demonkernel.io.vn/foodsurf/frontend:1.0.10 + image: git.demonkernel.io.vn/foodsurf/frontend:1.1.0 ports: - containerPort: 3000 resources: diff --git a/package-lock.json b/package-lock.json index 220c0d0..279ef79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "temp", - "version": "1.0.10", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "temp", - "version": "1.0.10", + "version": "1.1.0", "dependencies": { "@tailwindcss/postcss": "^4.2.2", "@types/node": "^20.19.37", diff --git a/package.json b/package.json index 935619f..7b1cef8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "temp", - "version": "1.0.10", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev", @@ -12,7 +12,7 @@ }, "dependencies": { "@tailwindcss/postcss": "^4.2.2", - "@types/node": "^20.19.37", + "@types/node": "^20.19.39", "@types/react": "^19.2.14", "next": "16.1.7", "react": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2729bff..f3b8fdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^4.2.2 version: 4.2.2 '@types/node': - specifier: ^20.19.37 - version: 20.19.37 + specifier: ^20.19.39 + version: 20.19.39 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -171,14 +171,14 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@emnapi/core@1.9.1': - resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.9.1': - resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} @@ -749,8 +749,8 @@ packages: resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. - '@types/node@20.19.37': - resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/node@20.19.39': + resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1092,8 +1092,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.12: - resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==} + baseline-browser-mapping@2.10.15: + resolution: {integrity: sha512-1nfKCq9wuAZFTkA2ey/3OXXx7GzFjLdkTiFVNwlJ9WqdI706CZRIhEqjuwanjMIja+84jDLa9rcyZDPDiVkASQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1171,8 +1171,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001782: - resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} + caniuse-lite@1.0.30001786: + resolution: {integrity: sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==} chalk@2.4.1: resolution: {integrity: sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==} @@ -1463,8 +1463,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.329: - resolution: {integrity: sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==} + electron-to-chromium@1.5.331: + resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1567,8 +1567,8 @@ packages: typescript: optional: true - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} eslint-import-resolver-typescript@3.10.1: resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} @@ -2424,8 +2424,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.23: - resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} lodash.capitalize@4.2.1: resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} @@ -2463,12 +2463,12 @@ packages: lodash@4.17.11: resolution: {integrity: sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - lodash@4.17.5: resolution: {integrity: sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2480,8 +2480,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.7: - resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + lru-cache@11.3.0: + resolution: {integrity: sha512-sr8xPKE25m6vJVcrdn6NxtC0fVfuPowbscLypegRgOm0yXSqr5JNHCAY3hnusdJ7HRBW04j6Ip4khvHU778DuQ==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -2660,8 +2660,8 @@ packages: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} node-rsa@0.4.2: resolution: {integrity: sha512-Bvso6Zi9LY4otIZefYrscsUpo2mUpiAVIEmSZV2q41sP8tHZoert3Yu6zv4f/RXJqMNZQKCtnhDugIuCma23YA==} @@ -3192,11 +3192,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@2.0.0-next.6: resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} engines: {node: '>= 0.4'} @@ -3643,8 +3638,8 @@ packages: resolution: {integrity: sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==} engines: {node: '>=18.17'} - undici@7.24.6: - resolution: {integrity: sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==} + undici@7.24.7: + resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} unicode-emoji-modifier-base@1.0.0: @@ -3971,18 +3966,18 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@emnapi/core@1.9.1': + '@emnapi/core@1.9.2': dependencies: - '@emnapi/wasi-threads': 1.2.0 + '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.1': + '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true @@ -4129,7 +4124,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.9.1 + '@emnapi/runtime': 1.9.2 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -4162,8 +4157,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 '@tybys/wasm-util': 0.10.1 optional: true @@ -4295,7 +4290,7 @@ snapshots: fs-extra: 8.1.0 globby: 10.0.2 got: 10.7.0 - lodash: 4.17.23 + lodash: 4.18.1 querystring: 0.2.1 url-join: 4.0.1 transitivePeerDependencies: @@ -4311,7 +4306,7 @@ snapshots: conventional-commits-parser: 6.4.0 debug: 4.4.3 import-from-esm: 2.0.0 - lodash-es: 4.17.23 + lodash-es: 4.18.1 micromatch: 4.0.8 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -4327,7 +4322,7 @@ snapshots: aggregate-error: 3.1.0 debug: 4.4.3 execa: 9.6.1 - lodash-es: 4.17.23 + lodash-es: 4.18.1 parse-json: 8.3.0 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -4346,12 +4341,12 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 issue-parser: 7.0.1 - lodash-es: 4.17.23 + lodash-es: 4.18.1 mime: 4.1.0 p-filter: 4.1.0 semantic-release: 25.0.3(typescript@5.9.3) tinyglobby: 0.2.15 - undici: 7.24.6 + undici: 7.24.7 url-join: 5.0.0 transitivePeerDependencies: - supports-color @@ -4364,7 +4359,7 @@ snapshots: env-ci: 11.2.0 execa: 9.6.1 fs-extra: 11.3.4 - lodash-es: 4.17.23 + lodash-es: 4.18.1 nerf-dart: 1.0.0 normalize-url: 9.0.0 npm: 11.12.1 @@ -4385,7 +4380,7 @@ snapshots: get-stream: 7.0.1 import-from-esm: 2.0.0 into-stream: 7.0.0 - lodash-es: 4.17.23 + lodash-es: 4.18.1 read-package-up: 11.0.0 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -4483,7 +4478,7 @@ snapshots: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 - lodash-es: 4.17.23 + lodash-es: 4.18.1 minimatch: 9.0.9 parse-imports-exports: 0.2.4 prettier: 3.8.1 @@ -4499,7 +4494,7 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.2.0 '@types/keyv': 3.1.4 - '@types/node': 20.19.37 + '@types/node': 20.19.39 '@types/responselike': 1.0.3 '@types/estree@1.0.8': {} @@ -4507,7 +4502,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 20.19.37 + '@types/node': 20.19.39 '@types/http-cache-semantics@4.2.0': {} @@ -4517,13 +4512,13 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.19.37 + '@types/node': 20.19.39 '@types/minimatch@6.0.0': dependencies: minimatch: 10.2.5 - '@types/node@20.19.37': + '@types/node@20.19.39': dependencies: undici-types: 6.21.0 @@ -4539,7 +4534,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 20.19.37 + '@types/node': 20.19.39 '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -4879,7 +4874,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.12: {} + baseline-browser-mapping@2.10.15: {} basic-auth@2.0.1: dependencies: @@ -4929,10 +4924,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.12 - caniuse-lite: 1.0.30001782 - electron-to-chromium: 1.5.329 - node-releases: 2.0.36 + baseline-browser-mapping: 2.10.15 + caniuse-lite: 1.0.30001786 + electron-to-chromium: 1.5.331 + node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-equal-constant-time@1.0.1: {} @@ -4975,7 +4970,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001782: {} + caniuse-lite@1.0.30001786: {} chalk@2.4.1: dependencies: @@ -5260,7 +5255,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.329: {} + electron-to-chromium@1.5.331: {} emoji-regex@10.6.0: {} @@ -5412,7 +5407,7 @@ snapshots: dependencies: '@next/eslint-plugin-next': 16.1.7 eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) @@ -5428,11 +5423,11 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 is-core-module: 2.16.1 - resolve: 1.22.11 + resolve: 2.0.0-next.6 transitivePeerDependencies: - supports-color @@ -5451,13 +5446,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -5472,8 +5467,8 @@ snapshots: debug: 3.2.7 doctrine: 2.1.0 eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6018,7 +6013,7 @@ snapshots: hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.7 + lru-cache: 11.3.0 http-cache-semantics@4.2.0: {} @@ -6420,7 +6415,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.23: {} + lodash-es@4.18.1: {} lodash.capitalize@4.2.1: {} @@ -6446,10 +6441,10 @@ snapshots: lodash@4.17.11: {} - lodash@4.17.23: {} - lodash@4.17.5: {} + lodash@4.18.1: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -6458,7 +6453,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.7: {} + lru-cache@11.3.0: {} lru-cache@5.1.1: dependencies: @@ -6585,8 +6580,8 @@ snapshots: dependencies: '@next/env': 16.1.7 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.12 - caniuse-lite: 1.0.30001782 + baseline-browser-mapping: 2.10.15 + caniuse-lite: 1.0.30001786 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -6621,7 +6616,7 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 - node-releases@2.0.36: {} + node-releases@2.0.37: {} node-rsa@0.4.2: dependencies: @@ -7047,12 +7042,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.6: dependencies: es-errors: 1.3.0 @@ -7120,7 +7109,7 @@ snapshots: hook-std: 4.0.0 hosted-git-info: 9.0.2 import-from-esm: 2.0.0 - lodash-es: 4.17.23 + lodash-es: 4.18.1 marked: 15.0.12 marked-terminal: 7.3.0(marked@15.0.12) micromatch: 4.0.8 @@ -7631,7 +7620,7 @@ snapshots: undici@6.24.1: {} - undici@7.24.6: {} + undici@7.24.7: {} unicode-emoji-modifier-base@1.0.0: {}