diff --git a/README.md b/README.md index af7c72d..019fe74 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,24 @@ -# Hệ Thống Bán Đồ Ăn - Frontend +# Coffee Shop Frontend -Dự án Frontend cho một Hệ thống bán đồ ăn (Food Ordering System), được xây dựng bằng Next.js và React, với giao diện hiện đại sử dụng Tailwind CSS. +Dự án Frontend cho hệ thống đặt món cà phê, xây dựng bằng Next.js App Router, React 19, TypeScript và Tailwind CSS v4. --- ## Mô Tả Dự Án -Đây là giao diện người dùng (frontend) cho hệ thống đặt và bán đồ ăn trực tuyến. Hệ thống gồm 2 trang chính: +Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ uống trực tuyến. -### Trang Người Dùng (User Page) -Dành cho khách hàng với các chức năng: -- Chọn món ăn: Duyệt và thêm món vào giỏ hàng -- Xem hóa đơn: Xem chi tiết các món đã chọn và tổng tiền -- Thanh toán: Thực hiện thanh toán đơn hàng -- Đánh giá: Đánh giá chất lượng món ăn và dịch vụ +### Trang Người Dùng (User Page - /) +Dành cho khách hàng: +- Duyệt thực đơn theo danh mục (sidebar collapsible) +- Tìm kiếm món theo tên / mô tả +- Xem card sản phẩm với giá và nút Mua +- Lọc tự động theo trạng thái available -### Trang Quản Lý (Manager Page) -Dành cho chủ quán / nhân viên quản lý với các chức năng: -- Quản lý thực đơn (thêm, sửa, xóa món ăn) +### Trang Quản Lý (Manager Page - chưa triển khai) +Dành cho chủ quán / nhân viên: +- Quản lý thực đơn (thêm, sửa, xóa món) - Theo dõi và xử lý đơn hàng -- Quản lý thông tin quán --- @@ -27,56 +26,59 @@ Dành cho chủ quán / nhân viên quản lý với các chức năng: ### Yêu cầu hệ thống - Node.js >= 18 -- pnpm (khuyến nghị) hoặc npm / yarn / bun - -### Cài đặt dependencies +- pnpm (khuyến nghị) hoặc npm +### Cài đặt ```bash pnpm install -# hoặc -npm install ``` -### Chạy môi trường phát triển - +### Dev ```bash pnpm dev -# hoặc -npm run dev +``` +Mở trình duyệt tại http://localhost:3000 + +### Build +```bash +pnpm build && pnpm start ``` -Mở trình duyệt tại http://localhost:3000 để xem kết quả. - -### Build production - +### Lint ```bash -pnpm build -pnpm start +pnpm lint ``` --- ## Cấu Trúc Thư Mục -Dự án sử dụng cấu trúc mặc định của Next.js App Router: - ``` frondend/ -├── app/ # App Router (Next.js) -│ ├── layout.tsx # Root layout (font, metadata, global styles) -│ ├── page.tsx # Trang chủ (User Page) -│ ├── globals.css # CSS toàn cục (Tailwind import) -│ ├── favicon.ico # Icon trang web -│ └── components/ # Components dùng riêng trong app/ -├── components/ # Shared components dùng chung toàn dự án -│ └── CartProduct.tsx # Component card sản phẩm trong giỏ hàng -├── public/ # Static assets (ảnh, svg, ...) -├── next.config.ts # Cấu hình Next.js -├── tsconfig.json # Cấu hình TypeScript -├── postcss.config.mjs # Cấu hình PostCSS (Tailwind) -├── eslint.config.mjs # Cấu hình ESLint -├── package.json # Dependencies và scripts -└── pnpm-workspace.yaml # Cấu hình pnpm workspace ++-- app/ # Next.js App Router +| +-- layout.tsx # Root layout +| +-- page.tsx # Trang chủ - sidebar + product grid +| +-- globals.css # CSS design tokens + Tailwind import ++-- components/ # Shared UI components +| +-- Navbar.tsx # Sidebar danh mục (collapsible) +| +-- CartProduct.tsx # Card sản phẩm +| +-- COMPONENTS.md # Tài liệu component ++-- layouts/ # Layout-level components +| +-- header.tsx # Sticky top header +| +-- footer.tsx # Footer ++-- lib/ # Shared logic & data +| +-- constants.ts # Mock data +| +-- types.ts # TypeScript interfaces ++-- types/ # Global TypeScript declarations +| +-- css.d.ts # CSS module type shim ++-- public/ # Static assets ++-- WORKFLOW.md # Tài liệu kiến trúc & quy trình ++-- next.config.ts ++-- tsconfig.json ++-- postcss.config.mjs ++-- eslint.config.mjs ++-- package.json ++-- pnpm-workspace.yaml ``` --- @@ -89,19 +91,18 @@ frondend/ | React | 19.2.3 | Thư viện UI | | TypeScript | ^5 | Kiểu dữ liệu tĩnh | | Tailwind CSS | ^4 | Utility-first CSS framework | -| SCSS | - | CSS preprocessor | -| Geist Font | - | Font chữ mặc định (Google Fonts) | +| Geist Font | - | Font chữ (Google Fonts via next/font) | +| FontAwesome | 6.7.2 | Icon library (CDN) | | pnpm | - | Package manager | -| ESLint | ^9 | Linting code | - +| ESLint | ^9 | Linting | --- ## Ghi Chú Phát Triển -- Trang chủ () là điểm vào chính của **User Page** -- Chỉnh sửa để thay đổi giao diện trang chủ; trang tự động cập nhật khi lưu file -- Styles toàn cục được định nghĩa trong -- Tailwind CSS v4 được import trực tiếp qua trong CSS -- Hỗ trợ **Dark Mode** tự động theo cài đặt hệ thống () -- Font chữ sử dụng **Geist Sans** và **Geist Mono** từ Google Fonts thông qua +- Trang chủ (app/page.tsx) là điểm vào chính của User Page +- Design tokens định nghĩa trong app/globals.css dưới dạng CSS custom properties +- Mock data nằm trong lib/constants.ts - thay bằng API calls khi backend sẵn sàng +- Dark mode: biến CSS đã chuẩn bị sẵn trong globals.css nhưng chưa kích hoạt +- Ảnh sản phẩm: thêm ảnh thực vào public/imgs/products/ +- Xem WORKFLOW.md để hiểu kiến trúc tổng thể và quy trình mở rộng dự án diff --git a/TODO.md b/TODO.md index 9159d78..39ec01b 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +1,52 @@ -# Food Ordering Page - Implementation TODO +# Coffee Shop Frontend - TODO + +## Completed Optimizations + +### A. Dead Code Removed +- [x] lib/constants.ts - Removed unused NAV_LINKS export +- [x] lib/types.ts - Removed unused NavLink interface +- [x] components/Navbar.tsx - Removed trivial handleClick wrapper; inlined onCategoryChange call +- [x] components/Navbar.tsx - Removed unused Link import + +### B. Bugs / Inaccuracies Fixed +- [x] layouts/header.tsx - Fixed JSDoc: 3-column -> 2-column layout (no center section exists) +- [x] app/page.tsx - Added available !== false filter to product list +- [x] app/page.tsx - Fixed setState-in-effect lint error: moved initial sidebar state to lazy useState initializer +- [x] next.config.ts - Added explanatory JSDoc comment + +### C. Documentation Updated +- [x] README.md - Fixed file structure tree, removed SCSS, fixed dark mode note, updated tech table +- [x] components/COMPONENTS.md - Fixed CartProduct styling (was outdated w-64/text-red-500/bg-blue-600); added Navbar, Header, Footer sections + +### D. New Documentation Created +- [x] WORKFLOW.md - Architecture, data flow, design token system, how-to guides, dev workflow + +### E. Mini-test Results +- [x] npm run lint - PASSED (0 errors, 0 warnings) +- [x] npm run build - PASSED (Compiled successfully, TypeScript clean, static pages generated) + +--- + +## Pending Features (Future Work) + +### Cart & Ordering +- [ ] Implement add-to-cart logic (onBuy callback in CartProduct) +- [ ] Cart sidebar or modal with item list and total +- [ ] Order submission flow +- [ ] Payment page + +### Backend Integration +- [ ] Replace MOCK_PRODUCTS with real API calls (lib/api.ts) +- [ ] Replace MOCK_USERS with real authentication +- [ ] Product images: replace placeholder with real images in public/imgs/products/ + +### Manager Page +- [ ] Create app/manager/page.tsx +- [ ] Menu management (add/edit/delete products) +- [ ] Order tracking dashboard + +### UX Improvements +- [ ] Dark mode toggle (CSS variables already prepared in globals.css) +- [ ] Loading skeleton for product grid +- [ ] Toast notifications for cart actions +- [ ] Product detail modal/page diff --git a/app/page.tsx b/app/page.tsx index 4ccb517..de4dd52 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -20,27 +20,34 @@ import { MENU_CATEGORIES, MOCK_PRODUCTS } from "@/lib/constants"; * Expanded sidebar: 1 → sm:2 → lg:2 → xl:3 → 2xl:4 */ export default function Home() { + /* Start collapsed (false) so SSR and client initial render match. + * useEffect sets the correct value after hydration completes. */ const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [activeCategory, setActiveCategory] = useState("all"); const [searchQuery, setSearchQuery] = useState(""); - /* Default: expanded on desktop, collapsed on mobile */ + /* After mount: sync sidebar with viewport width, then subscribe to changes. + * The initial setIsSidebarOpen call is intentional (post-hydration only) + * and does not cause cascading renders — suppress the lint rule here. */ useEffect(() => { const mq = window.matchMedia("(min-width: 1024px)"); + // eslint-disable-next-line react-hooks/set-state-in-effect setIsSidebarOpen(mq.matches); const handler = (e: MediaQueryListEvent) => setIsSidebarOpen(e.matches); mq.addEventListener("change", handler); return () => mq.removeEventListener("change", handler); }, []); - /* Filter products by active category + search query */ + /* Filter products by availability, active category, and search query. + * p.available defaults to true when undefined (opt-in unavailability). */ const filteredProducts = MOCK_PRODUCTS.filter((p) => { + const isAvailable = p.available !== false; const matchesCategory = activeCategory === "all" || p.category === activeCategory; const matchesSearch = searchQuery.trim() === "" || p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.description.toLowerCase().includes(searchQuery.toLowerCase()); - return matchesCategory && matchesSearch; + return isAvailable && matchesCategory && matchesSearch; }); /* Active category label */ @@ -59,8 +66,7 @@ export default function Home() { return ( /* Outer wrapper: flex row, align-items: flex-start so sidebar sticks */
{/* ── Sidebar ── */} {/* Title + count */}
-

+

{activeCategoryLabel}

-

+

{filteredProducts.length} món

@@ -92,7 +98,7 @@ export default function Home() {
setSearchQuery(e.target.value)} placeholder="Tìm kiếm món..." className="w-full pl-9 pr-9 py-2 text-sm rounded-xl border outline-none - bg-[var(--color-bg-card)] text-[var(--color-text-primary)] - border-[var(--color-border)] placeholder:text-[var(--color-text-muted)] - focus:border-[var(--color-primary)] focus:ring-2 - focus:ring-[var(--color-primary)] focus:ring-opacity-20 + bg-card text-foreground + border-border placeholder:text-muted-foreground + focus:border-primary focus:ring-2 + focus:ring-primary focus:ring-opacity-20 transition-all duration-150" /> {/* Clear button */} @@ -112,8 +118,8 @@ export default function Home() { onClick={() => setSearchQuery("")} title="Xóa tìm kiếm" aria-label="Xóa tìm kiếm" - className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] - hover:text-[var(--color-primary)] transition-colors duration-150 + className="absolute right-3 top-1/2 -translate-y-1/2 text-(--color-text-muted) + hover:text-(--color-primary) transition-colors duration-150 cursor-pointer border-none bg-transparent p-0" > @@ -142,7 +148,7 @@ export default function Home() {
) : ( /* Empty state */ -
+

{searchQuery @@ -152,7 +158,7 @@ export default function Home() { {searchQuery && (