Refactor: Update Vietnamese text to English across multiple components
Release package / release (pull_request) Successful in 3m23s

- Translated payment page text from Vietnamese to English.
- Updated staff creation page with English labels and messages.
- Changed staff schedule page labels and navigation items to English.
- Modified shift card and search bar components to use English text.
- Updated delete confirmation and product modal components with English messages.
- Translated product management tab and category sidebar to English.
- Changed shift schedule components to display English text for shifts and actions.
- Updated header logo alt text to improve accessibility.
This commit is contained in:
Thanh Quy - wolf
2026-05-14 21:09:18 +07:00
parent 36c63f9a08
commit 54d5d6150c
16 changed files with 144 additions and 146 deletions
+14 -15
View File
@@ -151,7 +151,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 login
</Link>
</div>
)}
@@ -164,10 +164,10 @@ export default function ManagerSignupPage() {
</div>
<div className="text-center">
<h2 className="mb-2 text-lg font-semibold text-(--color-text-primary)">
Không thể kết nối
Unable to connect
</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.
Unable to check registration status. Please try again.
</p>
</div>
<Button
@@ -185,13 +185,13 @@ export default function ManagerSignupPage() {
.catch(() => setPageState("error"));
}}
>
Thử lại
Retry
</Button>
<Link
href="/login"
className="text-sm text-(--color-primary) underline"
>
Quay lại đăng nhập
Back to login
</Link>
</div>
)}
@@ -202,15 +202,15 @@ export default function ManagerSignupPage() {
{[
{
id: "name",
label: "Họ tên",
label: "Full name",
icon: "fa-user",
placeholder: "Nguyễn Văn A",
placeholder: "John Doe",
field: "name" as const,
type: "text",
},
{
id: "phone",
label: "Số điện thoại",
label: "Phone number",
icon: "fa-phone",
placeholder: "0987654321",
field: "phone" as const,
@@ -218,15 +218,15 @@ export default function ManagerSignupPage() {
},
{
id: "password",
label: "Mật khẩu",
label: "Password",
icon: "fa-lock",
placeholder: "Ít nhất 6 ký tự",
placeholder: "At least 6 characters",
field: "password" as const,
type: "password",
},
{
id: "eateryName",
label: "Tên nhà hàng",
label: "Restaurant name",
icon: "fa-store",
placeholder: "Coffee & More",
field: "eateryName" as const,
@@ -280,18 +280,17 @@ export default function ManagerSignupPage() {
>
{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ý"
"Register"
)}
</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>
+6 -6
View File
@@ -41,13 +41,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
</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.
Your cart is empty.
</div>
) : (
<div className="overflow-x-auto">
@@ -102,7 +102,7 @@ export default function PaymentPage() {
<button
onClick={() => decreaseQty(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 ${name}`}
aria-label={`Decrease quantity of ${name}`}
>
-
</button>
@@ -114,12 +114,12 @@ export default function PaymentPage() {
setQuantity(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(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 ${name}`}
aria-label={`Increase quantity of ${name}`}
>
+
</button>
@@ -131,7 +131,7 @@ export default function PaymentPage() {
variant="danger"
size="md"
style="payment"
aria-label={`Xóa ${name} khỏi giỏ hàng`}
aria-label={`Remove ${name} from cart`}
>
Delete product
</Button>
+20 -21
View File
@@ -27,13 +27,13 @@ export default function CreateStaffPage() {
const validate = () => {
const next = { name: "", phone: "", password: "", submit: "" };
if (!form.name.trim()) next.name = "Vui lòng nhập họ tên";
if (!form.phone.trim()) next.phone = "Vui lòng nhập số điện thoại";
if (!form.name.trim()) next.name = "Please enter full name";
if (!form.phone.trim()) next.phone = "Please enter phone number";
else if (!validatePhone(form.phone))
next.phone = "Số điện thoại không hợp lệ (vd: 0987654321)";
if (!form.password.trim()) next.password = "Vui lòng nhập mật khẩu";
next.phone = "Invalid phone number (e.g. 0987654321)";
if (!form.password.trim()) next.password = "Please enter password";
else if (form.password.length < 6)
next.password = "Mật khẩu phải có ít nhất 6 ký tự";
next.password = "Password must be at least 6 characters";
setErrors(next);
return !next.name && !next.phone && !next.password;
};
@@ -55,17 +55,17 @@ export default function CreateStaffPage() {
} else {
const errorCode = (await res.text().catch(() => "")).trim();
const errorMap: Record<string, string> = {
ExistedUser: "Số điện thoại đã được đăng ký",
InvalidPhoneNumber: "Số điện thoại không hợp lệ",
InvalidManager: "Không tìm thấy tài khoản manager",
ExistedUser: "Phone number already registered",
InvalidPhoneNumber: "Invalid phone number",
InvalidManager: "Manager account not found",
};
setErrors({
...errors,
submit: errorMap[errorCode] ?? "Đã có lỗi xảy ra, vui lòng thử lại",
submit: errorMap[errorCode] ?? "An error occurred, please try again",
});
}
} catch {
setErrors({ ...errors, submit: "Không thể kết nối, vui lòng thử lại" });
setErrors({ ...errors, submit: "Unable to connect, please try again" });
} finally {
setIsLoading(false);
}
@@ -79,10 +79,10 @@ export default function CreateStaffPage() {
<i className="fa-solid fa-user-plus text-2xl text-(--color-primary)"></i>
</div>
<h1 className="mb-1 text-2xl font-bold text-(--color-primary-dark)">
Tạo tài khoản nhân viên
Create Staff Account
</h1>
<p className="text-sm text-(--color-text-muted)">
Thêm nhân viên mới cho nhà hàng
Add a new staff member to the restaurant
</p>
</div>
@@ -90,15 +90,15 @@ export default function CreateStaffPage() {
{[
{
id: "name",
label: "Họ tên",
label: "Full name",
icon: "fa-user",
placeholder: "Nguyễn Văn A",
placeholder: "John Doe",
field: "name" as const,
type: "text",
},
{
id: "phone",
label: "Số điện thoại",
label: "Phone number",
icon: "fa-phone",
placeholder: "0987654321",
field: "phone" as const,
@@ -106,9 +106,9 @@ export default function CreateStaffPage() {
},
{
id: "password",
label: "Mật khẩu",
label: "Password",
icon: "fa-lock",
placeholder: "Ít nhất 6 ký tự",
placeholder: "At least 6 characters",
field: "password" as const,
type: "password",
},
@@ -160,18 +160,17 @@ export default function CreateStaffPage() {
>
{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...
</>
) : (
"Tạo tài khoản"
"Create account"
)}
</Button>
<Link
href="/manager"
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 Dashboard
Back to Dashboard
</Link>
</div>
</form>
+18 -18
View File
@@ -96,7 +96,7 @@ export default function StaffSchedulePage() {
<nav className="flex-1 space-y-0.5 overflow-y-auto p-3">
{/* View toggle */}
<p className="mb-1 px-3 pt-2 text-[10px] font-bold tracking-widest text-(--color-text-muted) uppercase">
Chế đ xem
View
</p>
<button
type="button"
@@ -108,7 +108,7 @@ export default function StaffSchedulePage() {
}`}
>
<i className={`fa-solid fa-table-columns w-4 text-center ${view === "week" ? "text-(--color-primary)" : ""}`}></i>
<span className="flex-1 text-left">Theo tuần</span>
<span className="flex-1 text-left">Weekly</span>
{view === "week" && (
<span className="h-1.5 w-1.5 rounded-full bg-(--color-primary)"></span>
)}
@@ -123,7 +123,7 @@ export default function StaffSchedulePage() {
}`}
>
<i className={`fa-solid fa-calendar w-4 text-center ${view === "month" ? "text-(--color-primary)" : ""}`}></i>
<span className="flex-1 text-left">Theo tháng</span>
<span className="flex-1 text-left">Monthly</span>
{view === "month" && (
<span className="h-1.5 w-1.5 rounded-full bg-(--color-primary)"></span>
)}
@@ -132,7 +132,7 @@ export default function StaffSchedulePage() {
{/* Navigation */}
<div className="mt-2 pt-2">
<p className="mb-1 px-3 text-[10px] font-bold tracking-widest text-(--color-text-muted) uppercase">
Điều hướng
Navigation
</p>
<button
type="button"
@@ -140,7 +140,7 @@ export default function StaffSchedulePage() {
className="flex w-full cursor-pointer items-center gap-3 rounded-xl border-none bg-transparent px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) transition-all hover:bg-gray-50 hover:text-(--color-primary-dark)"
>
<i className="fa-solid fa-crosshairs w-4 text-center"></i>
<span className="flex-1 text-left">Hôm nay</span>
<span className="flex-1 text-left">Today</span>
</button>
</div>
@@ -148,7 +148,7 @@ export default function StaffSchedulePage() {
{isManager && (
<div className="mt-2 pt-2">
<p className="mb-1 px-3 text-[10px] font-bold tracking-widest text-(--color-text-muted) uppercase">
Quản
Manager
</p>
<Link
href="/manager"
@@ -165,7 +165,7 @@ export default function StaffSchedulePage() {
className="mt-1 flex w-full cursor-pointer items-center gap-3 rounded-xl border-none bg-(--color-primary)/10 px-3 py-2.5 text-sm font-semibold text-(--color-primary) transition-all hover:bg-(--color-primary)/20"
>
<i className="fa-solid fa-plus w-4 text-center"></i>
<span className="flex-1 text-left">Tạo ca mới</span>
<span className="flex-1 text-left">Create shift</span>
</button>
</div>
)}
@@ -175,7 +175,7 @@ export default function StaffSchedulePage() {
{isManager && (
<div className="rounded-xl bg-gradient-to-br from-(--color-primary)/10 to-(--color-primary)/5 p-3">
<p className="text-[10px] font-bold tracking-wider text-(--color-text-muted) uppercase">
Ngân sách tuần
Weekly budget
</p>
<p className="mt-1 text-xl font-bold text-(--color-primary)">
{weeklyBudget >= 1_000_000
@@ -187,12 +187,12 @@ export default function StaffSchedulePage() {
)}
<div className="rounded-xl bg-gray-50 p-3">
<p className="text-[10px] font-bold tracking-wider text-(--color-text-muted) uppercase">
Ca tuần này
Shifts this week
</p>
<p className="mt-1 text-xl font-bold text-(--color-text-secondary)">
{totalShiftsThisWeek}
</p>
<p className="text-[10px] text-(--color-text-muted)">ca làm</p>
<p className="text-[10px] text-(--color-text-muted)">shifts</p>
</div>
</div>
</nav>
@@ -242,12 +242,12 @@ export default function StaffSchedulePage() {
{/* Title */}
<div>
<h1 className="text-foreground text-base font-bold md:text-lg">
{isManager ? "Quản lý ca làm" : "Đăng ký ca làm"}
{isManager ? "Manage Shifts" : "Register Shifts"}
</h1>
<p className="text-xs text-(--color-text-muted)">
{view === "week"
? `Tuần: ${weekLabel}`
: `Tháng: ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`}
? `Week: ${weekLabel}`
: `Month: ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`}
</p>
</div>
@@ -263,7 +263,7 @@ export default function StaffSchedulePage() {
: "bg-transparent text-(--color-text-secondary)"
}`}
>
Tuần
Week
</button>
<button
type="button"
@@ -274,7 +274,7 @@ export default function StaffSchedulePage() {
: "bg-transparent text-(--color-text-secondary)"
}`}
>
Tháng
Month
</button>
</div>
@@ -293,7 +293,7 @@ export default function StaffSchedulePage() {
onClick={goToToday}
className="cursor-pointer rounded-lg border border-(--color-border-light) bg-white px-3 py-1.5 text-xs font-semibold text-(--color-text-secondary) transition hover:border-(--color-primary)/30 hover:bg-(--color-primary)/5 hover:text-(--color-primary)"
>
Hôm nay
Today
</button>
<button
title="Next"
@@ -313,7 +313,7 @@ export default function StaffSchedulePage() {
className="hidden cursor-pointer items-center gap-2 rounded-xl border-none bg-(--color-primary) px-4 py-2 text-xs font-bold text-white shadow-sm transition hover:opacity-90 md:flex"
>
<i className="fa-solid fa-plus"></i>
Tạo ca mới
Create shift
</button>
)}
@@ -373,7 +373,7 @@ export default function StaffSchedulePage() {
{/* FAB for mobile manager */}
{isManager && (
<button
title="Tạo ca mới"
title="Create shift"
type="button"
onClick={() => { setCreateDate(undefined); setCreateOpen(true); }}
className="fixed right-4 bottom-6 z-30 flex h-14 w-14 cursor-pointer items-center justify-center rounded-full border-none bg-(--color-primary) text-white shadow-xl transition hover:opacity-90 md:hidden"
+2 -2
View File
@@ -83,14 +83,14 @@ export default function ShiftCard({ shift, compact = false, onClick }: ShiftCard
</p>
</div>
<span className={`shrink-0 rounded-full px-2.5 py-1 text-xs font-semibold ${s.badge}`}>
{registeredCount}/{shift.maxStaff} người
{registeredCount}/{shift.maxStaff} staff
</span>
</div>
{registeredCount > 0 && (
<div className="mt-3 border-t border-gray-100 pt-3">
<p className="mb-2 text-[10px] font-semibold tracking-wider text-(--color-text-muted) uppercase">
Nhân viên đăng
Registered staff
</p>
<div className="flex flex-wrap gap-1.5">
{shift.registeredStaff!.map((staff) => (
@@ -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>
@@ -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
@@ -39,7 +39,7 @@ export default function ProductModal({
});
if (!res.ok) {
throw new Error("Upload ảnh thất bại.");
throw new Error("Image upload failed.");
}
const raw = await res.text();
@@ -54,12 +54,12 @@ export default function ProductModal({
}
if (!filename) {
throw new Error("Không nhận được filename ảnh từ server.");
throw new Error("No image filename received from server.");
}
setForm((prev) => ({ ...prev, imageUrl: filename }));
} catch (error: any) {
setUploadError(error?.message || "Không thể upload ảnh.");
setUploadError(error?.message || "Unable to upload image.");
} finally {
setUploading(false);
}
@@ -93,11 +93,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>
@@ -107,7 +107,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
@@ -115,13 +115,13 @@ 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>
<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
@@ -139,7 +139,7 @@ export default function ProductModal({
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
nh món
Item image
</label>
<div className="space-y-2">
@@ -154,7 +154,7 @@ export default function ProductModal({
{uploading && (
<p className="text-sm text-(--color-text-muted)">
<i className="fa-solid fa-spinner mr-1 animate-spin"></i>
Đang upload nh...
Uploading image...
</p>
)}
</div>
@@ -163,7 +163,7 @@ export default function ProductModal({
<div className="mt-2">
<img
src={previewUrl}
alt="Preview ảnh món"
alt="Item image preview"
className="h-24 w-24 rounded-lg border border-(--color-border-light) object-cover"
/>
</div>
@@ -179,7 +179,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}
@@ -188,19 +188,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..."
/>
</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" : "Out of stock"}
</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 ${
@@ -221,14 +221,14 @@ 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"
disabled={uploading}
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:opacity-60"
>
{isEdit ? "Lưu thay đổi" : "Thêm món"}
{isEdit ? "Save changes" : "Add item"}
</button>
</div>
</form>
+19 -19
View File
@@ -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 items..."
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)"
>
@@ -81,26 +81,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 statuses</option>
<option value="available">In stock</option>
<option value="unavailable">Out of stock</option>
</select>
<button
title="Thêm món"
title="Add item"
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 item</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 */}
@@ -109,19 +109,19 @@ export default function ProductsTab() {
<thead className="bg-background">
<tr>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
nh
Image
</th>
<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-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>
@@ -133,7 +133,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>
<span className="ml-3">Không tìm thấy món nào</span>
<span className="ml-3">No items found</span>
</td>
</tr>
) : (
@@ -165,7 +165,7 @@ export default function ProductsTab() {
<td className="px-4 py-3 text-center">
<button
onClick={() => toggleProductAvailability(p)}
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} />
@@ -175,14 +175,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>
@@ -37,14 +37,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
@@ -144,15 +144,15 @@ export default function MobileShiftView({ onShiftClick }: MobileShiftViewProps)
? "bg-(--color-primary)/10 text-(--color-primary)"
: "bg-gray-100 text-(--color-text-muted)"
}`}>
{selectedShifts.length} ca làm
{selectedShifts.length} shifts
</span>
</div>
{selectedShifts.length === 0 ? (
<div className="rounded-2xl border border-dashed border-(--color-border-light) py-10 text-center">
<i className="fa-regular fa-calendar-xmark mb-3 text-3xl text-gray-200"></i>
<p className="text-sm font-medium text-(--color-text-muted)">Không ca làm</p>
<p className="mt-0.5 text-xs text-(--color-text-muted)">Chưa ca nào đưc tạo hôm này</p>
<p className="text-sm font-medium text-(--color-text-muted)">No shifts</p>
<p className="mt-0.5 text-xs text-(--color-text-muted)">No shifts created for this day</p>
</div>
) : (
<div className="space-y-2.5">
@@ -102,7 +102,7 @@ export default function MonthlyCalendar({ onShiftClick, onDateSelect }: MonthlyC
<div className="mt-1.5 space-y-1">
<span className="inline-flex items-center gap-1 rounded-full bg-(--color-primary)/10 px-1.5 py-0.5 text-[10px] font-semibold text-(--color-primary)">
<i className="fa-solid fa-clock text-[8px]"></i>
{shiftCount} ca
{shiftCount} shifts
</span>
</div>
)}
@@ -110,7 +110,7 @@ export default function MonthlyCalendar({ onShiftClick, onDateSelect }: MonthlyC
{/* Add indicator for manager */}
{shiftCount === 0 && onDateSelect && (
<p className="mt-1 text-[10px] text-(--color-text-muted) opacity-0 transition group-hover:opacity-100">
+ Thêm ca
+ Add shift
</p>
)}
</button>
@@ -185,7 +185,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"
@@ -205,7 +205,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"
@@ -219,7 +219,7 @@ export default function ShiftCreateModal({
</div>
<div>
<label className="mb-1 block text-xs font-semibold text-(--color-text-secondary)">
Lương ca (VND)
Shift wage (VND)
</label>
<input
title="Wage"
@@ -248,14 +248,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>
@@ -41,10 +41,10 @@ export default function ShiftDetailModal({
setSuccess(null);
const result = await 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.");
}
};
@@ -52,13 +52,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: string) => {
unregisterShift(shift.id, staffId);
setSuccess("Đã xóa nhân viên khỏi ca.");
setSuccess("Staff removed from shift.");
};
const handleDelete = async () => {
@@ -67,10 +67,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 = {
@@ -93,7 +93,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>
@@ -123,7 +123,7 @@ 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">
{parseShiftDate(shift.date as Date | string | undefined)?.toLocaleDateString(
@@ -139,7 +139,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
Working hours
</p>
<p className="text-foreground mt-1 text-sm font-bold">
{shift.startTime} {shift.endTime}
@@ -147,13 +147,13 @@ 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">{""} giờ</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 ?? 0).toLocaleString("vi-VN")} VND
@@ -164,12 +164,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 ({registeredStaff.length}/
Registered staff ({registeredStaff.length}/
{shift.maxStaff})
</p>
{registeredStaff.length === 0 ? (
<p className="text-xs text-(--color-text-muted) italic">
Chưa ai đăng
No staff registered yet
</p>
) : (
<div className="space-y-2">
@@ -193,7 +193,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>
@@ -226,7 +226,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
</button>
)}
{isRegistered && (
@@ -236,7 +236,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
Unregister
</button>
)}
{isManager && (
@@ -246,7 +246,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
@@ -254,7 +254,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>
@@ -116,14 +116,14 @@ export default function WeeklySchedule({
{formatDateShort(new Date(selectedDateRef))}
</h3>
<span className="rounded-full bg-(--color-primary)/10 px-2.5 py-1 text-xs font-semibold text-(--color-primary)">
{dayShifts.length} ca
{dayShifts.length} shifts
</span>
</div>
{dayShifts.length === 0 && !onCreateShift ? (
<div className="rounded-2xl border border-dashed border-(--color-border-light) py-10 text-center">
<i className="fa-regular fa-calendar-xmark mb-2 text-3xl text-gray-200"></i>
<p className="text-sm text-(--color-text-muted)">Không ca làm hôm nay</p>
<p className="text-sm text-(--color-text-muted)">No shifts today</p>
</div>
) : (
<div className="space-y-2.5">
@@ -137,7 +137,7 @@ export default function WeeklySchedule({
className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-2xl border border-dashed border-(--color-border-light) bg-transparent py-3 text-sm font-medium text-(--color-text-muted) transition hover:border-(--color-primary) hover:text-(--color-primary)"
>
<i className="fa-solid fa-plus"></i>
Thêm ca làm
Add shift
</button>
)}
</div>
@@ -156,7 +156,7 @@ export default function WeeklySchedule({
<thead>
<tr>
<th className="w-28 border-r border-b border-(--color-border-light) bg-gray-50/80 px-4 py-3 text-left text-[11px] font-semibold tracking-wider text-(--color-text-muted) uppercase">
Bộ phận
Department
</th>
{weekDates.map((date, i) => {
const today = isToday(new Date(date));
@@ -221,7 +221,7 @@ export default function WeeklySchedule({
className="mt-auto flex cursor-pointer items-center justify-center gap-1 rounded-lg border border-dashed border-gray-200 bg-transparent py-1.5 text-[10px] font-medium text-gray-300 transition hover:border-(--color-primary)/50 hover:text-(--color-primary)"
>
<i className="fa-solid fa-plus"></i>
Thêm
Add
</button>
)}
</div>
+1 -1
View File
@@ -47,7 +47,7 @@ export default function Header() {
<div className="relative h-10 w-10 shrink-0 md:h-11 md:w-11">
<Image
src={SHOP_INFO.logo}
alt={`Logo ${SHOP_INFO.name}`}
alt={`${SHOP_INFO.name} logo`}
fill
className="object-contain transition-transform duration-200 group-hover:scale-105"
sizes="44px"