Transfer to English

This commit is contained in:
Thanh Quy - wolf
2026-05-06 00:55:06 +07:00
parent 40b673b9bb
commit cd2de11da0
28 changed files with 337 additions and 186 deletions
+12 -12
View File
@@ -110,7 +110,7 @@ export default function ManagerSignupPage() {
href="/login"
className="flex w-full items-center justify-center rounded-xl border-2 border-(--color-primary) bg-white py-3 font-semibold text-(--color-primary) no-underline transition-all duration-150 hover:bg-(--color-primary) hover:text-white"
>
Quay lại đăng nhập
Back to Sign In
</Link>
</div>
)}
@@ -122,13 +122,13 @@ export default function ManagerSignupPage() {
<i className="fa-solid fa-triangle-exclamation text-2xl text-yellow-500"></i>
</div>
<div className="text-center">
<h2 className="mb-2 text-lg font-semibold text-(--color-text-primary)">Không thể kết nối</h2>
<p className="text-sm text-(--color-text-muted)">Không thể kiểm tra trạng thái đăng . Vui lòng thử lại.</p>
<h2 className="mb-2 text-lg font-semibold text-(--color-text-primary)">Cannot Connect</h2>
<p className="text-sm text-(--color-text-muted)">Unable to check registration status. Please try again.</p>
</div>
<Button variant="primaryNoBorder" style="login" size="lg" onClick={() => { setPageState("checking"); fetch("/api/manager/signup").then((r) => { if (r.ok) setPageState("available"); else if (r.status === 403) setPageState("closed"); else setPageState("error"); }).catch(() => setPageState("error")); }}>
Thử lại
Retry
</Button>
<Link href="/login" className="text-sm text-(--color-primary) underline">Quay lại đăng nhập</Link>
<Link href="/login" className="text-sm text-(--color-primary) underline">Back to Sign In</Link>
</div>
)}
@@ -136,10 +136,10 @@ export default function ManagerSignupPage() {
{pageState === "available" && (
<form onSubmit={handleSubmit} className="space-y-4">
{[
{ id: "name", label: "Họ tên", icon: "fa-user", placeholder: "Nguyễn Văn A", field: "name" as const, type: "text" },
{ id: "phone", label: "Số điện thoại", icon: "fa-phone", placeholder: "0987654321", field: "phone" as const, type: "tel" },
{ id: "password", label: "Mật khẩu", icon: "fa-lock", placeholder: "Ít nhất 6 ký tự", field: "password" as const, type: "password" },
{ id: "eateryName", label: "Tên nhà hàng", icon: "fa-store", placeholder: "Coffee & More", field: "eateryName" as const, type: "text" },
{ id: "name", label: "Full Name", icon: "fa-user", placeholder: "John Doe", field: "name" as const, type: "text" },
{ id: "phone", label: "Phone Number", icon: "fa-phone", placeholder: "0987654321", field: "phone" as const, type: "tel" },
{ id: "password", label: "Password", icon: "fa-lock", placeholder: "At least 6 characters", field: "password" as const, type: "password" },
{ id: "eateryName", label: "Restaurant Name", icon: "fa-store", placeholder: "Coffee & More", field: "eateryName" as const, type: "text" },
].map(({ id, label, icon, placeholder, field, type }) => (
<div key={id}>
<label htmlFor={id} className="mb-2 block text-sm font-medium text-(--color-text-secondary)">
@@ -176,16 +176,16 @@ export default function ManagerSignupPage() {
<div className="space-y-3 pt-2">
<Button variant="primaryNoBorder" type="submit" style="login" size="lg" disabled={isLoading}>
{isLoading ? (
<><i className="fa-solid fa-spinner fa-spin mr-2"></i>Đang xử ...</>
<><i className="fa-solid fa-spinner fa-spin mr-2"></i>Processing...</>
) : (
"Đăng ký"
"Sign Up"
)}
</Button>
<Link
href="/login"
className="flex w-full items-center justify-center rounded-xl border-2 border-(--color-primary) bg-white py-3 font-semibold text-(--color-primary) no-underline transition-all duration-150 hover:bg-(--color-primary) hover:text-white"
>
Đã tài khoản? Đăng nhập
Already have an account? Sign In
</Link>
</div>
</form>
+7 -7
View File
@@ -8,7 +8,7 @@ import { useCart } from "@/lib/cart-context";
import { useState } from "react";
const formatPrice = (value: number) =>
value.toLocaleString("vi-VN", { style: "currency", currency: "VND" });
value.toLocaleString("en-US", { style: "currency", currency: "VND" });
export default function PaymentPage() {
const {
@@ -32,13 +32,13 @@ export default function PaymentPage() {
<div className="bg-card overflow-hidden rounded-2xl border border-(--color-border-light)">
<div className="border-b border-(--color-border-light) px-4 py-3">
<h1 className="text-foreground text-lg font-bold md:text-xl">
Trang thanh toán
Payment page
</h1>
</div>
{items.length === 0 ? (
<div className="px-4 py-10 text-center text-(--color-text-muted)">
Chưa sản phẩm nào trong giỏ hàng.
There are no products in the shopping cart yet.
</div>
) : (
<div className="overflow-x-auto">
@@ -85,7 +85,7 @@ export default function PaymentPage() {
<button
onClick={() => decreaseQty(item.id)}
className="inline-flex h-8 w-8 items-center justify-center rounded-lg border border-(--color-border) hover:bg-(--color-border-light)"
aria-label={`Giảm số lượng ${item.name}`}
aria-label={`Decrease quantity of ${item.name}`}
>
-
</button>
@@ -97,12 +97,12 @@ export default function PaymentPage() {
setQuantity(item.id, Number(e.target.value))
}
className="h-8 w-16 rounded-lg border border-(--color-border) bg-transparent text-center"
title="Nhập số lượng"
title="Enter quantity"
/>
<button
onClick={() => increaseQty(item.id)}
className="inline-flex h-8 w-8 items-center justify-center rounded-lg border border-(--color-border) hover:bg-(--color-border-light)"
aria-label={`Tăng số lượng ${item.name}`}
aria-label={`Increase quantity of ${item.name}`}
>
+
</button>
@@ -114,7 +114,7 @@ export default function PaymentPage() {
variant="danger"
size="md"
style="payment"
aria-label={`Xóa ${item.name} khỏi giỏ hàng`}
aria-label={`Remove ${item.name} from cart`}
>
Delete product
</Button>
+155 -4
View File
@@ -9,11 +9,13 @@ import {
import { useAuth } from "@/lib/auth-context";
import { useManager } from "@/lib/manager-context";
import Link from "next/link";
import { useState } from "react";
export default function ManagerPage() {
const { user, logout } = useAuth();
const { activeTab, setActiveTab, products, combos, categories } =
useManager();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const tabs = [
{
@@ -163,8 +165,8 @@ export default function ManagerPage() {
</p>
</div>
{/* Mobile tabs */}
<div className="flex items-center gap-1 lg:hidden">
{/* smlg: icon row */}
<div className="hidden items-center gap-1 sm:flex lg:hidden">
{tabs.map((tab) => (
<button
key={tab.id}
@@ -176,7 +178,7 @@ export default function ManagerPage() {
}`}
>
<i className={tab.icon}></i>
<span className="hidden sm:inline">{tab.label}</span>
<span>{tab.label}</span>
</button>
))}
<Link
@@ -184,8 +186,157 @@ export default function ManagerPage() {
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">Finance</span>
<span>Finance</span>
</Link>
{/* User menu dropdown (smlg) */}
<div className="relative">
<button
onClick={() => setMobileMenuOpen((o) => !o)}
className={`flex cursor-pointer items-center gap-1.5 rounded-xl border-none px-3 py-2 text-xs font-medium transition ${
mobileMenuOpen
? "bg-(--color-primary) text-white"
: "bg-background text-(--color-text-secondary) hover:text-(--color-primary)"
}`}
>
<i className="fa-solid fa-user-tie"></i>
</button>
{mobileMenuOpen && (
<div className="absolute right-0 top-full z-50 mt-1 w-44 rounded-xl border border-(--color-border-light) bg-white p-2 shadow-lg">
<div className="flex items-center gap-2 px-3 py-2">
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-(--color-accent-light)">
<i className="fa-solid fa-user-tie text-xs text-(--color-primary)"></i>
</div>
<div className="min-w-0">
<p className="text-foreground truncate text-xs font-semibold">
{user?.name ?? "Manager"}
</p>
<p className="text-[11px] text-(--color-text-muted)">
Store Manager
</p>
</div>
</div>
<div className="my-1 border-t border-(--color-border-light)" />
<Link
href="/"
onClick={() => setMobileMenuOpen(false)}
className="hover:bg-background flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium text-(--color-text-secondary) no-underline transition"
>
<i className="fa-solid fa-house w-4 text-center"></i>
Return to home
</Link>
<button
onClick={() => {
logout();
setMobileMenuOpen(false);
}}
className="flex w-full cursor-pointer items-center gap-2 rounded-lg border-none bg-transparent px-3 py-2 text-xs font-medium text-red-500 transition hover:bg-red-50"
>
<i className="fa-solid fa-right-from-bracket w-4 text-center"></i>
Log out
</button>
</div>
)}
</div>
</div>
{/* xs (< sm): single hamburger → full dropdown */}
<div className="relative sm:hidden">
<button
onClick={() => setMobileMenuOpen((o) => !o)}
className={`flex cursor-pointer items-center gap-1.5 rounded-xl border-none px-3 py-2 text-sm font-medium transition ${
mobileMenuOpen
? "bg-(--color-primary) text-white"
: "bg-background text-(--color-text-secondary) hover:text-(--color-primary)"
}`}
>
<i
className={
mobileMenuOpen ? "fa-solid fa-xmark" : "fa-solid fa-bars"
}
></i>
</button>
{mobileMenuOpen && (
<div className="absolute right-0 top-full z-50 mt-1 w-52 rounded-xl border border-(--color-border-light) bg-white p-2 shadow-lg">
{/* User info */}
<div className="flex items-center gap-2 px-3 py-2">
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-(--color-accent-light)">
<i className="fa-solid fa-user-tie text-xs text-(--color-primary)"></i>
</div>
<div className="min-w-0">
<p className="text-foreground truncate text-xs font-semibold">
{user?.name ?? "Manager"}
</p>
<p className="text-[11px] text-(--color-text-muted)">
Store Manager
</p>
</div>
</div>
<div className="my-1 border-t border-(--color-border-light)" />
{/* Tabs */}
<p className="px-3 py-1 text-[11px] font-semibold tracking-wider text-(--color-text-muted) uppercase">
Menu Management
</p>
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id);
setMobileMenuOpen(false);
}}
className={`flex w-full cursor-pointer items-center gap-2 rounded-lg border-none px-3 py-2 text-xs font-medium transition ${
activeTab === tab.id
? "bg-(--color-primary) text-white"
: "bg-transparent text-(--color-text-secondary) hover:bg-(--color-background)"
}`}
>
<i className={`${tab.icon} w-4 text-center`}></i>
<span className="flex-1 text-left">{tab.label}</span>
</button>
))}
<div className="my-1 border-t border-(--color-border-light)" />
{/* Analytics */}
<p className="px-3 py-1 text-[11px] font-semibold tracking-wider text-(--color-text-muted) uppercase">
Analytics
</p>
<Link
href="/manager/analytics"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium text-(--color-primary) no-underline transition hover:bg-(--color-accent-light)"
>
<i className="fa-solid fa-chart-line w-4 text-center"></i>
Finance
</Link>
<Link
href="/staff/schedule"
onClick={() => setMobileMenuOpen(false)}
className="hover:bg-background flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium text-(--color-text-secondary) no-underline transition"
>
<i className="fa-solid fa-calendar-days w-4 text-center"></i>
Shifts
</Link>
<div className="my-1 border-t border-(--color-border-light)" />
{/* Actions */}
<Link
href="/"
onClick={() => setMobileMenuOpen(false)}
className="hover:bg-background flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium text-(--color-text-secondary) no-underline transition"
>
<i className="fa-solid fa-house w-4 text-center"></i>
Return to home
</Link>
<button
onClick={() => {
logout();
setMobileMenuOpen(false);
}}
className="flex w-full cursor-pointer items-center gap-2 rounded-lg border-none bg-transparent px-3 py-2 text-xs font-medium text-red-500 transition hover:bg-red-50"
>
<i className="fa-solid fa-right-from-bracket w-4 text-center"></i>
Log out
</button>
</div>
)}
</div>
{/* Desktop actions */}
+3 -3
View File
@@ -18,8 +18,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Coffee Shop — Hệ thống đặt món",
description: "Đặt món cà phê, trà, nước ép và nhiều hơn nữa tại Coffee Shop.",
title: "Coffee Shop — Order System",
description: "Order coffee, tea, juice and more at Coffee Shop.",
icons: {
icon: "/favicon/favicon.ico",
shortcut: "/favicon/favicon.ico",
@@ -33,7 +33,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="vi">
<html lang="en">
<head>
{/* FontAwesome 6 — icons used throughout the app */}
<link
+2 -2
View File
@@ -17,7 +17,7 @@ import type { ProductCardProps } from "./Card.types";
*/
export default function ProductCard({
image,
imageAlt = "Ảnh sản phẩm",
imageAlt = "Product image",
productName,
price,
description,
@@ -67,7 +67,7 @@ export default function ProductCard({
variant="primary"
size="sm"
icon="fa-cart-plus"
aria-label={`Mua ${productName}`}
aria-label={`Buy ${productName}`}
>
Buy
</Button>
+6 -6
View File
@@ -11,22 +11,22 @@ const STATUS_STYLES: Record<
available: {
bg: "bg-blue-50 border-blue-200",
text: "text-blue-700",
label: "Còn trống",
label: "Available",
},
registered: {
bg: "bg-blue-100 border-blue-400",
text: "text-blue-900",
label: "Đã đăng ký",
label: "Registered",
},
approved_leave: {
bg: "bg-purple-50 border-purple-300",
text: "text-purple-700",
label: "Nghỉ phép",
label: "On Leave",
},
absent: {
bg: "bg-red-50 border-red-300",
text: "text-red-700",
label: "Vắng mặt",
label: "Absent",
},
};
@@ -94,7 +94,7 @@ export default function ShiftCard({
{shift.registeredStaff.length > 0 && (
<div className="mt-2 border-t border-current/10 pt-2">
<p className="text-[10px] font-medium tracking-wide uppercase opacity-60">
Nhân viên ({shift.registeredStaff.length}/{shift.maxStaff})
Staff ({shift.registeredStaff.length}/{shift.maxStaff})
</p>
<div className="mt-1 flex flex-wrap gap-1">
{shift.registeredStaff.map((s) => (
@@ -111,7 +111,7 @@ export default function ShiftCard({
{shift.status === "available" && shift.registeredStaff.length === 0 && (
<p className="mt-2 text-[10px] italic opacity-50">
{shift.maxStaff} vị trí còn trống
{shift.maxStaff} spots available
</p>
)}
</button>
@@ -6,7 +6,7 @@ export default function SearchBar({
value,
onChange,
onClear,
placeholder = "Tìm kiếm...",
placeholder = "Search...",
className = "",
}: SearchBarProps) {
return (
@@ -17,14 +17,14 @@ export default function SearchBar({
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
aria-label="Tìm kiếm món ăn"
aria-label="Search items"
className="bg-card text-foreground border-border placeholder:text-muted-foreground focus:border-primary focus:ring-primary focus:ring-opacity-20 w-full rounded-xl border py-2 pr-9 pl-9 text-sm transition-all duration-150 outline-none focus:ring-2"
/>
{value && (
<button
onClick={onClear}
title="Xóa tìm kiếm"
aria-label="Xóa tìm kiếm"
title="Clear search"
aria-label="Clear search"
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent p-0 text-(--color-text-muted) transition-colors duration-150 hover:text-(--color-primary)"
>
<i className="fa-solid fa-xmark text-sm"></i>
+4 -4
View File
@@ -162,7 +162,7 @@ export function BarChart({ current, previous, height = 200 }: BarChartProps) {
fontSize="10"
fill="#F0D9A8"
>
{d.label} ({hovered.set === "cur" ? "Hiện tại" : "Trước"})
{d.label} ({hovered.set === "cur" ? "Current" : "Previous"})
</text>
<text
x={tipX + tipW / 2}
@@ -181,7 +181,7 @@ export function BarChart({ current, previous, height = 200 }: BarChartProps) {
fontSize="10"
fill="#A08060"
>
{d.orders} đơn hàng
{d.orders} orders
</text>
</g>
);
@@ -190,7 +190,7 @@ export function BarChart({ current, previous, height = 200 }: BarChartProps) {
{/* Legend */}
<rect x={padL} y={4} width={10} height={10} rx="2" fill="#6F4E37" />
<text x={padL + 13} y={13} fontSize="10" fill="#6F4E37">
Hiện tại
Current
</text>
<rect
x={padL + 65}
@@ -201,7 +201,7 @@ export function BarChart({ current, previous, height = 200 }: BarChartProps) {
fill="#E2C9A8"
/>
<text x={padL + 78} y={13} fontSize="10" fill="#A08060">
Kỳ trước
Previous
</text>
</svg>
</div>
+1 -1
View File
@@ -180,7 +180,7 @@ export function LineChart({ data, height = 200 }: LineChartProps) {
fontSize="10"
fill="#A08060"
>
{p.data.orders} đơn
{p.data.orders} orders
</text>
</g>
);
@@ -76,21 +76,21 @@ export function ProductTable({ data }: ProductTableProps) {
#
</th>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Sản phẩm
Product
</th>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Danh mục
Category
</th>
<SortTh col="unitsSold" label="Số lượng" className="text-right" />
<SortTh col="revenue" label="Doanh thu" className="text-right" />
<SortTh col="unitsSold" label="Units Sold" className="text-right" />
<SortTh col="revenue" label="Revenue" className="text-right" />
<th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)">
Giá nhập
Cost Price
</th>
<th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)">
Giá bán
Selling Price
</th>
<SortTh col="profit" label="Lợi nhuận" className="text-right" />
<SortTh col="profitMargin" label="Biên LN" className="text-right" />
<SortTh col="profit" label="Profit" className="text-right" />
<SortTh col="profitMargin" label="Margin" className="text-right" />
</tr>
</thead>
<tbody>
@@ -53,7 +53,7 @@ export function SummaryCard({
</span>
<span className="text-xs font-normal text-(--color-text-muted)">
({isPositive ? "+" : ""}
{formatCurrency(change)}) so với kỳ trước
{formatCurrency(change)}) vs previous period
</span>
</div>
</div>
+1 -1
View File
@@ -11,7 +11,7 @@ export default function CartFab() {
return (
<Link
href="/payment"
aria-label="Đi đến trang thanh toán"
aria-label="Go to payment page"
className="fixed right-5 bottom-6 z-70 flex h-14 w-14 items-center justify-center rounded-full bg-(--color-primary) text-white shadow-xl transition-all duration-150 hover:bg-(--color-primary-dark) active:scale-95"
>
<i className="fa-solid fa-cart-shopping text-lg"></i>
@@ -107,13 +107,13 @@ export default function CategoryModal({
onClick={onClose}
className="flex-1 cursor-pointer rounded-xl border border-(--color-border) bg-white px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-(--color-border-light)"
>
Hủy
Cancel
</button>
<button
type="submit"
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
>
{isEdit ? "Lưu thay đổi" : "Thêm danh mục"}
{isEdit ? "Save change" : "Add category"}
</button>
</div>
</form>
+17 -17
View File
@@ -6,7 +6,7 @@ import { useState } from "react";
import type { ComboModalProps } from "./Manager.types";
function formatPrice(price: number) {
return price.toLocaleString("vi-VN") + "đ";
return price.toLocaleString("en-US") + "";
}
export default function ComboModal({
@@ -74,7 +74,7 @@ export default function ComboModal({
<div className="flex max-h-[90vh] w-full max-w-xl flex-col rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-(--color-border-light) px-6 py-4">
<h2 className="text-foreground text-lg font-bold">
{isEdit ? "Chỉnh sửa combo" : "Thêm combo mới"}
{isEdit ? "Edit Combo" : "Add New Combo"}
</h2>
<button
title="Close"
@@ -92,7 +92,7 @@ export default function ComboModal({
<div className="flex-1 space-y-4 overflow-y-auto px-6 py-5">
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Tên combo <span className="text-red-500">*</span>
Combo Name <span className="text-red-500">*</span>
</label>
<input
required
@@ -100,16 +100,16 @@ export default function ComboModal({
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
className={inputCls}
placeholder="Ví dụ: Combo Cà Phê Đôi"
placeholder="e.g. Double Coffee Combo"
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Giá combo (đ) <span className="text-red-500">*</span>
Price (VND) <span className="text-red-500">*</span>
</label>
<input
title="Giá combo"
title="Price in Vietnamese đồng"
required
type="number"
min={0}
@@ -124,10 +124,10 @@ export default function ComboModal({
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
tả
Description
</label>
<textarea
title="Mô tả combo"
title="Description"
rows={2}
value={form.description}
onChange={(e) =>
@@ -139,10 +139,10 @@ export default function ComboModal({
<div>
<label className="mb-2 block text-sm font-medium text-(--color-text-secondary)">
Món trong combo{" "}
Items in the combo{" "}
{form.items.length === 0 && (
<span className="text-xs text-red-500">
(Chọn ít nhất 1 món)
(Choose at least one item.)
</span>
)}
</label>
@@ -162,7 +162,7 @@ export default function ComboModal({
</span>
<div className="flex items-center gap-1">
<button
title="Giảm"
title="Decrease"
type="button"
onClick={() => updateItemQty(p.id, qty - 1)}
disabled={qty === 0}
@@ -174,7 +174,7 @@ export default function ComboModal({
{qty}
</span>
<button
title="Tăng"
title="Increase"
type="button"
onClick={() => updateItemQty(p.id, qty + 1)}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border border-(--color-border) bg-white text-xs text-(--color-text-secondary) transition hover:border-(--color-primary) hover:text-(--color-primary)"
@@ -191,14 +191,14 @@ export default function ComboModal({
<div className="bg-background flex items-center justify-between rounded-xl border border-(--color-border-light) px-4 py-3">
<div>
<p className="text-foreground text-sm font-medium">
Trạng thái
Status
</p>
<p className="text-xs text-(--color-text-muted)">
{form.available ? "Còn hàng" : "Tạm hết"}
{form.available ? "In stock" : "Temporarily out of stock"}
</p>
</div>
<button
title="Chuyển đổi trạng thái"
title="Toggle status"
type="button"
onClick={() => setForm({ ...form, available: !form.available })}
className={`relative h-6 w-11 cursor-pointer rounded-full border-none transition-colors duration-200 ${
@@ -220,14 +220,14 @@ export default function ComboModal({
onClick={onClose}
className="flex-1 cursor-pointer rounded-xl border border-(--color-border) bg-white px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-(--color-border-light)"
>
Hủy
Cancel
</button>
<button
type="submit"
disabled={form.items.length === 0}
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95 disabled:cursor-not-allowed disabled:opacity-50"
>
{isEdit ? "Lưu thay đổi" : "Thêm combo"}
{isEdit ? "Save change" : "Add combo"}
</button>
</div>
</form>
+8 -8
View File
@@ -9,7 +9,7 @@ import DeleteConfirm from "./DeleteConfirm";
import StatusBadge from "./StatusBadge";
function formatPrice(price: number) {
return price.toLocaleString("vi-VN") + "đ";
return price.toLocaleString("en-US") + "";
}
export default function CombosTab() {
@@ -26,7 +26,7 @@ export default function CombosTab() {
const [deleteTarget, setDeleteTarget] = useState<Combo | null>(null);
const getProductName = (id: number) =>
products.find((p) => p.id === id)?.name ?? `Món #${id}`;
products.find((p) => p.id === id)?.name ?? `Item #${id}`;
return (
<div className="space-y-4">
@@ -39,7 +39,7 @@ export default function CombosTab() {
className="flex cursor-pointer items-center gap-2 rounded-xl border-none bg-(--color-primary) px-4 py-2 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
>
<i className="fa-solid fa-plus"></i>
<span className="hidden sm:inline">Thêm combo</span>
<span className="hidden sm:inline">Add new combo</span>
</button>
</div>
@@ -47,7 +47,7 @@ export default function CombosTab() {
{combos.length === 0 ? (
<div className="col-span-full flex flex-col items-center gap-3 py-16 text-(--color-text-muted)">
<i className="fa-solid fa-layer-group text-4xl opacity-30"></i>
<p className="text-sm">Chưa combo nào</p>
<p className="text-sm">No combos available yet.</p>
</div>
) : (
combos.map((combo) => (
@@ -69,7 +69,7 @@ export default function CombosTab() {
<button
onClick={() => toggleComboAvailability(combo.id)}
className="ml-3 shrink-0 cursor-pointer border-none bg-transparent"
title="Đổi trạng thái"
title="Toggle status"
>
<StatusBadge available={combo.available} />
</button>
@@ -77,7 +77,7 @@ export default function CombosTab() {
<div className="bg-background mx-4 mb-3 rounded-xl px-3 py-2">
<p className="mb-1 text-[11px] font-semibold tracking-wide text-(--color-text-muted) uppercase">
Bao gồm
Include
</p>
<ul className="space-y-0.5">
{combo.items.map((item) => (
@@ -99,14 +99,14 @@ export default function CombosTab() {
<div className="flex gap-1.5">
<button
onClick={() => setModalCombo(combo)}
title="Chỉnh sửa"
title="Edit"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg border border-(--color-border-light) bg-transparent text-(--color-text-muted) transition hover:border-(--color-primary-light) hover:text-(--color-primary)"
>
<i className="fa-solid fa-pen text-xs"></i>
</button>
<button
onClick={() => setDeleteTarget(combo)}
title="Xóa"
title="Delete"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-transparent text-(--color-text-muted) transition hover:border-red-200 hover:bg-red-50 hover:text-red-500"
>
<i className="fa-solid fa-trash text-xs"></i>
@@ -17,9 +17,9 @@ export default function DeleteConfirm({
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<i className="fa-solid fa-trash-can text-xl text-red-500"></i>
</div>
<h3 className="text-foreground text-base font-bold">Xóa "{name}"?</h3>
<h3 className="text-foreground text-base font-bold">Delete "{name}"?</h3>
<p className="text-sm text-(--color-text-muted)">
Hành đng này không thể hoàn tác.
This action cannot be undone.
</p>
</div>
<div className="flex gap-3">
@@ -27,13 +27,13 @@ export default function DeleteConfirm({
onClick={onClose}
className="flex-1 cursor-pointer rounded-xl border border-(--color-border) bg-white px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-(--color-border-light)"
>
Hủy
Cancel
</button>
<button
onClick={onConfirm}
className="flex-1 cursor-pointer rounded-xl border-none bg-red-500 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-red-600 active:scale-95"
>
Xóa
Delete
</button>
</div>
</div>
+18 -18
View File
@@ -16,7 +16,7 @@ interface Eatery {
}
function formatPrice(price: number) {
return price.toLocaleString("vi-VN") + "đ";
return price.toLocaleString("en-US") + "";
}
export default function MenuItemsTab() {
@@ -62,7 +62,7 @@ export default function MenuItemsTab() {
if (id) setMenuItems(await loadMenuItems(id));
else setMenuItems([]);
} catch {
setError("Không thể tải dữ liệu menu. Kiểm tra kết nối backend.");
setError("Cannot load menu data. Please check the backend connection.");
} finally {
setLoading(false);
}
@@ -82,7 +82,7 @@ export default function MenuItemsTab() {
setNewPrice("");
setShowForm(false);
} catch {
setSubmitError("Không thể thêm món. Vui lòng thử lại.");
setSubmitError("Cannot add dish. Please try again.");
} finally {
setSubmitting(false);
}
@@ -99,7 +99,7 @@ export default function MenuItemsTab() {
return (
<div className="flex items-center justify-center py-16 text-(--color-text-muted)">
<i className="fa-solid fa-spinner mr-2 animate-spin"></i>
Đang tải menu...
Loading menu...
</div>
);
}
@@ -117,7 +117,7 @@ export default function MenuItemsTab() {
return (
<div className="flex flex-col items-center justify-center py-16 text-(--color-text-muted)">
<i className="fa-solid fa-store mb-2 block text-3xl opacity-30"></i>
<p className="text-sm">Quán của bạn chưa đưc khởi tạo trong hệ thống.</p>
<p className="text-sm">Your eatery has not been initialized in the system.</p>
</div>
);
}
@@ -127,14 +127,14 @@ export default function MenuItemsTab() {
{/* Toolbar */}
<div className="flex items-center justify-between">
<p className="text-sm text-(--color-text-muted)">
<strong className="text-foreground">{menuItems.length}</strong> món trong menu
<strong className="text-foreground">{menuItems.length}</strong> items on the menu
</p>
<button
onClick={() => setShowForm(true)}
className="flex cursor-pointer items-center gap-2 rounded-xl border-none bg-(--color-primary) px-4 py-2 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
>
<i className="fa-solid fa-plus"></i>
<span className="hidden sm:inline">Thêm món</span>
<span className="hidden sm:inline">Add new dish</span>
</button>
</div>
@@ -144,10 +144,10 @@ export default function MenuItemsTab() {
<thead className="bg-background">
<tr>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Tên món
Dish name
</th>
<th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)">
Giá
Price
</th>
</tr>
</thead>
@@ -159,7 +159,7 @@ export default function MenuItemsTab() {
className="py-12 text-center text-(--color-text-muted)"
>
<i className="fa-solid fa-bowl-food mb-2 block text-3xl opacity-30"></i>
Chưa món nào trong menu
There are no items on the menu yet.
</td>
</tr>
) : (
@@ -186,7 +186,7 @@ export default function MenuItemsTab() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 backdrop-blur-sm">
<div className="w-full max-w-sm rounded-2xl bg-white p-6 shadow-lg">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-foreground font-bold">Thêm món mới</h2>
<h2 className="text-foreground font-bold">Add new dish</h2>
<button
onClick={closeForm}
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--color-text-muted) hover:text-(--color-primary)"
@@ -198,13 +198,13 @@ export default function MenuItemsTab() {
<form onSubmit={handleAdd} className="space-y-4">
<div>
<label className="mb-1.5 block text-sm font-medium text-(--color-text-secondary)">
Tên món <span className="text-red-500">*</span>
Dish name <span className="text-red-500">*</span>
</label>
<input
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder="Nhập tên món..."
placeholder="Enter dish name..."
required
className="text-foreground w-full rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm outline-none transition focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)/20"
/>
@@ -212,13 +212,13 @@ export default function MenuItemsTab() {
<div>
<label className="mb-1.5 block text-sm font-medium text-(--color-text-secondary)">
Giá (VNĐ) <span className="text-red-500">*</span>
Price (VND) <span className="text-red-500">*</span>
</label>
<input
type="number"
value={newPrice}
onChange={(e) => setNewPrice(e.target.value)}
placeholder="Nhập giá..."
placeholder="Enter price..."
required
min={0}
step={500}
@@ -239,7 +239,7 @@ export default function MenuItemsTab() {
onClick={closeForm}
className="flex-1 cursor-pointer rounded-xl border border-(--color-border-light) bg-transparent py-2 text-sm font-medium text-(--color-text-secondary) transition hover:bg-background"
>
Hủy
Cancel
</button>
<button
type="submit"
@@ -249,10 +249,10 @@ export default function MenuItemsTab() {
{submitting ? (
<>
<i className="fa-solid fa-spinner mr-1 animate-spin"></i>
Đang lưu...
Saving...
</>
) : (
"Thêm món"
"Add more dishes"
)}
</button>
</div>
+14 -14
View File
@@ -41,11 +41,11 @@ export default function ProductModal({
<div className="w-full max-w-lg rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-(--color-border-light) px-6 py-4">
<h2 className="text-foreground text-lg font-bold">
{isEdit ? "Chỉnh sửa món" : "Thêm món mới"}
{isEdit ? "Edit Item" : "Add New Item"}
</h2>
<button
onClick={onClose}
title="Đóng"
title="Close"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border-none bg-transparent text-(--color-text-muted) transition-colors hover:bg-(--color-border-light) hover:text-(--color-primary)"
>
<i className="fa-solid fa-xmark"></i>
@@ -55,7 +55,7 @@ export default function ProductModal({
<form onSubmit={handleSubmit} className="space-y-4 px-6 py-5">
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Tên món <span className="text-red-500">*</span>
Item Name <span className="text-red-500">*</span>
</label>
<input
required
@@ -63,18 +63,18 @@ export default function ProductModal({
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
className={inputCls}
placeholder="Ví dụ: Cà Phê Đen"
placeholder="e.g. Black Coffee"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Danh mục <span className="text-red-500">*</span>
Category <span className="text-red-500">*</span>
</label>
<select
required
title="Chọn danh mục"
title="Select category"
value={form.category}
onChange={(e) => setForm({ ...form, category: e.target.value })}
className={inputCls}
@@ -88,7 +88,7 @@ export default function ProductModal({
</div>
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Giá (đ) <span className="text-red-500">*</span>
Price () <span className="text-red-500">*</span>
</label>
<input
required
@@ -107,7 +107,7 @@ export default function ProductModal({
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
tả
Description
</label>
<textarea
rows={3}
@@ -116,19 +116,19 @@ export default function ProductModal({
setForm({ ...form, description: e.target.value })
}
className={`${inputCls} resize-none`}
placeholder="Mô tả ngắn về món..."
placeholder="Short description of the item..."
/>
</div>
<div className="bg-background flex items-center justify-between rounded-xl border border-(--color-border-light) px-4 py-3">
<div>
<p className="text-foreground text-sm font-medium">Trạng thái</p>
<p className="text-foreground text-sm font-medium">Status</p>
<p className="text-xs text-(--color-text-muted)">
{form.available ? "Còn hàng" : "Tạm hết"}
{form.available ? "In Stock" : "Temporarily Out"}
</p>
</div>
<button
title="Chuyển trạng thái"
title="Toggle status"
type="button"
onClick={() => setForm({ ...form, available: !form.available })}
className={`relative h-6 w-11 cursor-pointer rounded-full border-none transition-colors duration-200 ${
@@ -149,13 +149,13 @@ export default function ProductModal({
onClick={onClose}
className="flex-1 cursor-pointer rounded-xl border border-(--color-border) bg-white px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-(--color-border-light)"
>
Hủy
Cancel
</button>
<button
type="submit"
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
>
{isEdit ? "Lưu thay đổi" : "Thêm món"}
{isEdit ? "Save Changes" : "Add Item"}
</button>
</div>
</form>
+22 -22
View File
@@ -9,7 +9,7 @@ import ProductModal from "./ProductModal";
import StatusBadge from "./StatusBadge";
function formatPrice(price: number) {
return price.toLocaleString("vi-VN") + "đ";
return price.toLocaleString("en-US") + "";
}
export default function ProductsTab() {
@@ -59,12 +59,12 @@ export default function ProductsTab() {
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Tìm kiếm món..."
placeholder="Search dishes..."
className="text-foreground w-full rounded-xl border border-(--color-border) bg-white py-2 pr-9 pl-9 text-sm transition outline-none focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)/20"
/>
{search && (
<button
title="Xóa tìm kiếm"
title="Clear search"
onClick={() => setSearch("")}
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent text-(--color-text-muted) hover:text-(--color-primary)"
>
@@ -77,9 +77,9 @@ export default function ProductsTab() {
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="text-foreground cursor-pointer rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm transition outline-none focus:border-(--color-primary)"
title="Lọc theo danh mục"
title="Filter by category"
>
<option value="all">Tất cả danh mục</option>
<option value="all">All categories</option>
{categories.map((cat) => (
<option key={cat.id} value={cat.id}>
{cat.name}
@@ -95,26 +95,26 @@ export default function ProductsTab() {
)
}
className="text-foreground cursor-pointer rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm transition outline-none focus:border-(--color-primary)"
title="Lọc theo trạng thái"
title="Filter by status"
>
<option value="all">Tất cả trạng thái</option>
<option value="available">Còn hàng</option>
<option value="unavailable">Tạm hết</option>
<option value="all">All states</option>
<option value="available">In stock</option>
<option value="unavailable">Temporarily out of stock</option>
</select>
<button
title="Thêm món"
title="Add dish"
onClick={() => setModalProduct("new")}
className="flex cursor-pointer items-center gap-2 rounded-xl border-none bg-(--color-primary) px-4 py-2 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
>
<i className="fa-solid fa-plus"></i>
<span className="hidden sm:inline">Thêm món</span>
<span className="hidden sm:inline">Add new dish</span>
</button>
</div>
<p className="text-sm text-(--color-text-muted)">
Hiển thị <strong className="text-foreground">{filtered.length}</strong>{" "}
/ {products.length} món
Showing <strong className="text-foreground">{filtered.length}</strong>{" "}
/ {products.length} items
</p>
{/* Table */}
@@ -123,19 +123,19 @@ export default function ProductsTab() {
<thead className="bg-background">
<tr>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Tên món
Item Name
</th>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Danh mục
Category
</th>
<th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)">
Giá
Price
</th>
<th className="px-4 py-3 text-center font-semibold text-(--color-text-secondary)">
Trạng thái
Status
</th>
<th className="px-4 py-3 text-center font-semibold text-(--color-text-secondary)">
Thao tác
Actions
</th>
</tr>
</thead>
@@ -147,7 +147,7 @@ export default function ProductsTab() {
className="py-12 text-center text-(--color-text-muted)"
>
<i className="fa-solid fa-mug-hot mb-2 block text-3xl opacity-30"></i>
Không tìm thấy món nào
<span className="ml-3">No items found</span>
</td>
</tr>
) : (
@@ -180,7 +180,7 @@ export default function ProductsTab() {
<td className="px-4 py-3 text-center">
<button
onClick={() => toggleProductAvailability(p.id)}
title="Nhấn để đổi trạng thái"
title="Click to toggle status"
className="cursor-pointer border-none bg-transparent"
>
<StatusBadge available={p.available ?? true} />
@@ -190,14 +190,14 @@ export default function ProductsTab() {
<div className="flex items-center justify-center gap-1.5">
<button
onClick={() => setModalProduct(p)}
title="Chỉnh sửa"
title="Edit"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg border border-(--color-border-light) bg-transparent text-(--color-text-muted) transition hover:border-(--color-primary-light) hover:text-(--color-primary)"
>
<i className="fa-solid fa-pen text-xs"></i>
</button>
<button
onClick={() => setDeleteTarget(p)}
title="Xóa"
title="Delete"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-transparent text-(--color-text-muted) transition hover:border-red-200 hover:bg-red-50 hover:text-red-500"
>
<i className="fa-solid fa-trash text-xs"></i>
@@ -40,14 +40,14 @@ export default function CategorySidebar({
className={`text-xs font-bold tracking-widest whitespace-nowrap text-(--color-text-muted) uppercase ${isOpen ? "block" : "hidden"} xl:block`}
>
<i className="fa-solid fa-utensils mr-2 text-(--color-primary)"></i>
Thực Đơn
Menu
</span>
{/* Toggle button — hidden on xl+ (sidebar is always expanded there) */}
<button
onClick={onToggle}
title={isOpen ? "Thu gọn menu" : "Mở rộng menu"}
aria-label={isOpen ? "Thu gọn menu" : "Mở rộng menu"}
title={isOpen ? "Collapse menu" : "Expand menu"}
aria-label={isOpen ? "Collapse menu" : "Expand menu"}
className="flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--color-text-muted) transition-colors duration-150 hover:bg-(--color-border-light) hover:text-(--color-primary) xl:hidden"
>
<i
@@ -26,7 +26,7 @@ export default function ProductGrid({
});
const activeCategoryLabel =
MENU_CATEGORIES.find((c) => c.id === activeCategory)?.name ?? "Tất cả";
MENU_CATEGORIES.find((c) => c.id === activeCategory)?.name ?? "All";
const gridCols = isSidebarOpen
? "grid-cols-1 min-[480px]:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
@@ -80,8 +80,8 @@ export default function ProductGrid({
<i className="fa-solid fa-mug-hot text-5xl opacity-30"></i>
<p className="text-base font-medium">
{searchQuery
? `Không tìm thấy món nào cho "${searchQuery}"`
: "Chưa có món trong danh mục này"}
? `No items found for "${searchQuery}"`
: "No items in this category yet"}
</p>
</div>
)}
@@ -140,7 +140,7 @@ export default function ShiftCreateModal({
{/* Department */}
<div>
<label className="mb-1 block text-xs font-semibold text-(--color-text-secondary)">
Bộ phận
Department
</label>
<select
title="Department"
@@ -160,7 +160,7 @@ export default function ShiftCreateModal({
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 block text-xs font-semibold text-(--color-text-secondary)">
Số nhân viên tối đa
Max Staff
</label>
<input
title="Max Staff"
@@ -174,7 +174,7 @@ export default function ShiftCreateModal({
</div>
<div>
<label className="mb-1 block text-xs font-semibold text-(--color-text-secondary)">
Lương ca (VND)
Wage (VND)
</label>
<input
title="Wage"
@@ -203,14 +203,14 @@ export default function ShiftCreateModal({
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:opacity-90"
>
<i className="fa-solid fa-plus mr-2"></i>
Tạo ca làm
Create Shift
</button>
<button
type="button"
onClick={onClose}
className="cursor-pointer rounded-xl border border-(--color-border-light) bg-transparent px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-gray-50"
>
Hủy
Cancel
</button>
</div>
</form>
@@ -32,10 +32,10 @@ export default function ShiftDetailModal({
setSuccess(null);
const result = registerShift(shift.id, user.id, user.name);
if (result.success) {
setSuccess("Đăng ký ca thành công!");
setSuccess("Shift registered successfully!");
setTimeout(onClose, 1200);
} else {
setError(result.error ?? "Có lỗi xảy ra.");
setError(result.error ?? "An error occurred.");
}
};
@@ -43,13 +43,13 @@ export default function ShiftDetailModal({
if (!user) return;
setError(null);
unregisterShift(shift.id, user.id);
setSuccess("Đã hủy đăng ký ca.");
setSuccess("Shift registration cancelled.");
setTimeout(onClose, 1200);
};
const handleManagerUnregister = (staffId: number) => {
unregisterShift(shift.id, staffId);
setSuccess("Đã xóa nhân viên khỏi ca.");
setSuccess("Staff removed from shift.");
};
const handleDelete = () => {
@@ -58,10 +58,10 @@ export default function ShiftDetailModal({
};
const statusLabel = {
available: "Còn trống",
registered: "Đã đăng ký",
approved_leave: "Nghỉ phép",
absent: "Vắng mặt",
available: "Available",
registered: "Registered",
approved_leave: "On Leave",
absent: "Absent",
};
const statusColor = {
@@ -84,7 +84,7 @@ export default function ShiftDetailModal({
{dept && <i className={`${dept.icon} text-(--color-primary)`}></i>}
<div>
<h2 className="text-foreground text-base font-bold">
Chi tiết ca làm
Shift Details
</h2>
<p className="text-xs text-(--color-text-muted)">{dept?.name}</p>
</div>
@@ -114,11 +114,11 @@ export default function ShiftDetailModal({
<div className="grid grid-cols-2 gap-3">
<div className="rounded-xl bg-gray-50 p-3">
<p className="text-[10px] font-semibold text-(--color-text-muted) uppercase">
Ngày
Date
</p>
<p className="text-foreground mt-1 text-sm font-bold">
{new Date(shift.date + "T00:00:00").toLocaleDateString(
"vi-VN",
"en-US",
{
weekday: "long",
day: "2-digit",
@@ -130,7 +130,7 @@ export default function ShiftDetailModal({
</div>
<div className="rounded-xl bg-gray-50 p-3">
<p className="text-[10px] font-semibold text-(--color-text-muted) uppercase">
Giờ làm
Work Hours
</p>
<p className="text-foreground mt-1 text-sm font-bold">
{shift.startTime} {shift.endTime}
@@ -138,18 +138,18 @@ export default function ShiftDetailModal({
</div>
<div className="rounded-xl bg-gray-50 p-3">
<p className="text-[10px] font-semibold text-(--color-text-muted) uppercase">
Thời lượng
Duration
</p>
<p className="text-foreground mt-1 text-sm font-bold">
{shift.durationHours} giờ
{shift.durationHours} hrs
</p>
</div>
<div className="rounded-xl bg-gray-50 p-3">
<p className="text-[10px] font-semibold text-(--color-text-muted) uppercase">
Lương ca
Shift Wage
</p>
<p className="mt-1 text-sm font-bold text-(--color-primary)">
{shift.wage.toLocaleString("vi-VN")} VND
{shift.wage.toLocaleString("en-US")} VND
</p>
</div>
</div>
@@ -157,12 +157,12 @@ export default function ShiftDetailModal({
{/* Registered staff */}
<div>
<p className="mb-2 text-xs font-semibold text-(--color-text-secondary)">
Nhân viên đã đăng ({shift.registeredStaff.length}/
Registered Staff ({shift.registeredStaff.length}/
{shift.maxStaff})
</p>
{shift.registeredStaff.length === 0 ? (
<p className="text-xs text-(--color-text-muted) italic">
Chưa ai đăng
No one registered yet
</p>
) : (
<div className="space-y-2">
@@ -186,7 +186,7 @@ export default function ShiftDetailModal({
className="cursor-pointer rounded-lg border-none bg-transparent px-2 py-1 text-xs text-red-500 transition hover:bg-red-50"
>
<i className="fa-solid fa-user-minus mr-1"></i>
Xóa
Remove
</button>
)}
</div>
@@ -222,7 +222,7 @@ export default function ShiftDetailModal({
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:opacity-90"
>
<i className="fa-solid fa-calendar-plus mr-2"></i>
Đăng ca
Register Shift
</button>
)}
{isRegistered && (
@@ -232,7 +232,7 @@ export default function ShiftDetailModal({
className="flex-1 cursor-pointer rounded-xl border border-red-200 bg-transparent px-4 py-2.5 text-sm font-semibold text-red-600 transition hover:bg-red-50"
>
<i className="fa-solid fa-calendar-minus mr-2"></i>
Hủy đăng
Cancel Registration
</button>
)}
{isManager && (
@@ -242,7 +242,7 @@ export default function ShiftDetailModal({
className="cursor-pointer rounded-xl border border-red-200 bg-transparent px-4 py-2.5 text-sm font-semibold text-red-600 transition hover:bg-red-50"
>
<i className="fa-solid fa-trash mr-2"></i>
Xóa ca
Delete Shift
</button>
)}
<button
@@ -250,7 +250,7 @@ export default function ShiftDetailModal({
onClick={onClose}
className="cursor-pointer rounded-xl border border-(--color-border-light) bg-transparent px-4 py-2.5 text-sm font-medium text-(--color-text-secondary) transition hover:bg-gray-50"
>
Đóng
Close
</button>
</div>
</div>
@@ -27,12 +27,12 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
<div className="bg-background flex min-h-screen items-center justify-center">
<div className="flex flex-col items-center gap-4 text-(--color-text-muted)">
<i className="fa-solid fa-spinner fa-spin text-3xl text-(--color-primary)"></i>
<p className="text-sm">Đang kiểm tra quyền truy cập...</p>
<p className="text-sm">Checking access...</p>
<Link
href="/login"
className="text-sm font-medium text-(--color-primary) hover:underline"
>
Đăng nhập nếu chưa tài khoản
Sign in if you don&apos;t have an account
</Link>
</div>
</div>
@@ -27,12 +27,12 @@ export default function StaffLayout({ children }: StaffLayoutProps) {
<div className="bg-background flex min-h-screen items-center justify-center">
<div className="flex flex-col items-center gap-4 text-(--color-text-muted)">
<i className="fa-solid fa-spinner fa-spin text-3xl text-(--color-primary)"></i>
<p className="text-sm">Đang kiểm tra quyền truy cập...</p>
<p className="text-sm">Checking access...</p>
<Link
href="/login"
className="text-sm font-medium text-(--color-primary) hover:underline"
>
Đăng nhập nếu chưa tài khoản
Sign in if you don&apos;t have an account
</Link>
</div>
</div>
+4 -4
View File
@@ -5,10 +5,10 @@
* e.g. 1_500_000 → "1.5 tr", 25_000 → "25 k"
*/
export function formatCurrency(value: number): string {
if (value >= 1_000_000_000) return (value / 1_000_000_000).toFixed(1) + " tỷ";
if (value >= 1_000_000) return (value / 1_000_000).toFixed(1) + " tr";
if (value >= 1_000_000_000) return (value / 1_000_000_000).toFixed(1) + " B";
if (value >= 1_000_000) return (value / 1_000_000).toFixed(1) + " M";
if (value >= 1_000) return (value / 1_000).toFixed(0) + " k";
return value.toLocaleString("vi-VN") + "đ";
return value.toLocaleString() + "";
}
/**
@@ -16,7 +16,7 @@ export function formatCurrency(value: number): string {
* e.g. 25_000 → "25.000đ"
*/
export function formatCurrencyFull(value: number): string {
return value.toLocaleString("vi-VN") + "đ";
return value.toLocaleString("en-US") + "";
}
/**
+2 -2
View File
@@ -53,11 +53,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
localStorage.setItem("coffee-shop-user", JSON.stringify(userData));
return { ok: true };
} else {
console.error("Đăng nhập thất bại:", response.status);
console.error("Login failed:", response.status);
return { ok: false, status: response.status };
}
} catch (error) {
console.error("Lỗi kết nối API:", error);
console.error("API connection error:", error);
return { ok: false };
}
};
+4 -4
View File
@@ -204,14 +204,14 @@ export function ShiftProvider({ children }: { children: ReactNode }) {
staffName: string,
): { success: boolean; error?: string } => {
const shift = shifts.find((s) => s.id === shiftId);
if (!shift) return { success: false, error: "Ca làm không tồn tại." };
if (!shift) return { success: false, error: "Shift does not exist." };
if (shift.registeredStaff.length >= shift.maxStaff) {
return { success: false, error: "Ca làm đã đủ người." };
return { success: false, error: "Shift is already full." };
}
if (shift.registeredStaff.some((rs) => rs.id === staffId)) {
return { success: false, error: "Bạn đã đăng ký ca này rồi." };
return { success: false, error: "You have already registered for this shift." };
}
if (
@@ -225,7 +225,7 @@ export function ShiftProvider({ children }: { children: ReactNode }) {
) {
return {
success: false,
error: "Xung đột lịch! Bạn đã có ca làm trùng giờ trong ngày này.",
error: "Schedule conflict! You already have an overlapping shift on this day.",
};
}