chore: updat

This commit is contained in:
TakahashiNg
2026-05-13 03:56:42 +00:00
parent 02a09a51d1
commit 65b9387105
4 changed files with 56 additions and 63 deletions
+19 -40
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import type { Product } from "@/lib/types"; import type { MenuItemEntity } from "@/lib/types";
import { useState } from "react"; import { useState } from "react";
import type { ProductModalProps } from "./Manager.types"; import type { ProductModalProps } from "./Manager.types";
@@ -12,11 +12,10 @@ export default function ProductModal({
onClose, onClose,
}: ProductModalProps) { }: ProductModalProps) {
const isEdit = product !== null; const isEdit = product !== null;
const [form, setForm] = useState<Omit<Product, "id">>({ const [form, setForm] = useState<Omit<MenuItemEntity, "id">>({
name: product?.name ?? "", name: product?.name ?? "",
category: product?.category ?? categories[0]?.id ?? "",
price: product?.price ?? 0, price: product?.price ?? 0,
image: product?.image ?? "/imgs/products/placeholder.jpg", imageUrl: product?.image ?? "/imgs/products/placeholder.jpg",
description: product?.description ?? "", description: product?.description ?? "",
available: product?.available ?? true, available: product?.available ?? true,
}); });
@@ -67,42 +66,22 @@ export default function ProductModal({
/> />
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div>
<div> <label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)"> Giá (đ) <span className="text-red-500">*</span>
Danh mục <span className="text-red-500">*</span> </label>
</label> <input
<select required
required type="number"
title="Chọn danh mục" min={0}
value={form.category} step={1000}
onChange={(e) => setForm({ ...form, category: e.target.value })} value={form.price}
className={inputCls} onChange={(e) =>
> setForm({ ...form, price: Number(e.target.value) })
{categories.map((cat) => ( }
<option key={cat.id} value={cat.id}> className={inputCls}
{cat.name} placeholder="25000"
</option> />
))}
</select>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
Giá (đ) <span className="text-red-500">*</span>
</label>
<input
required
type="number"
min={0}
step={1000}
value={form.price}
onChange={(e) =>
setForm({ ...form, price: Number(e.target.value) })
}
className={inputCls}
placeholder="25000"
/>
</div>
</div> </div>
<div> <div>
+7 -19
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useManager } from "@/lib/manager-context"; import { useManager } from "@/lib/manager-context";
import type { Product } from "@/lib/types"; import type { MenuItemEntity } from "@/lib/types";
import { useState } from "react"; import { useState } from "react";
import DeleteConfirm from "./DeleteConfirm"; import DeleteConfirm from "./DeleteConfirm";
@@ -27,13 +27,12 @@ export default function ProductsTab() {
"all" | "available" | "unavailable" "all" | "available" | "unavailable"
>("all"); >("all");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [modalProduct, setModalProduct] = useState<Product | null | "new">( const [modalProduct, setModalProduct] = useState<
null, MenuItemEntity | null | "new"
); >(null);
const [deleteTarget, setDeleteTarget] = useState<Product | null>(null); const [deleteTarget, setDeleteTarget] = useState<MenuItemEntity | null>(null);
const filtered = products.filter((p) => { const filtered = products.filter((p) => {
if (filterCategory !== "all" && p.category !== filterCategory) return false;
if (filterStatus === "available" && p.available === false) return false; if (filterStatus === "available" && p.available === false) return false;
if (filterStatus === "unavailable" && p.available !== false) return false; if (filterStatus === "unavailable" && p.available !== false) return false;
if ( if (
@@ -125,9 +124,6 @@ export default function ProductsTab() {
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)"> <th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Tên món Tên món
</th> </th>
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
Danh mục
</th>
<th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)"> <th className="px-4 py-3 text-right font-semibold text-(--color-text-secondary)">
Giá Giá
</th> </th>
@@ -166,14 +162,6 @@ export default function ProductsTab() {
)} )}
</div> </div>
</td> </td>
<td className="px-4 py-3">
<span className="inline-flex items-center gap-1.5 rounded-full bg-(--color-accent-light) px-2.5 py-0.5 text-xs font-medium text-(--color-primary-dark)">
<i
className={`${categories.find((c) => c.id === p.category)?.icon ?? "fa-solid fa-tag"} text-[10px]`}
></i>
{getCategoryName(p.category)}
</span>
</td>
<td className="px-4 py-3 text-right font-semibold text-(--color-primary)"> <td className="px-4 py-3 text-right font-semibold text-(--color-primary)">
{formatPrice(p.price)} {formatPrice(p.price)}
</td> </td>
@@ -215,9 +203,9 @@ export default function ProductsTab() {
<ProductModal <ProductModal
product={modalProduct === "new" ? null : modalProduct} product={modalProduct === "new" ? null : modalProduct}
categories={categories} categories={categories}
onSave={(data) => { onSave={(data: MenuItemEntity) => {
if ("id" in data) { if ("id" in data) {
updateProduct(data as Product); updateProduct(data);
} else { } else {
addProduct(data); addProduct(data);
} }
+28 -3
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react"; import { useMutation, useQuery } from "@apollo/client/react";
import { import {
ReactNode, ReactNode,
createContext, createContext,
@@ -16,6 +16,7 @@ import type {
Combo, Combo,
MenuCategory, MenuCategory,
MenuItemEntity, MenuItemEntity,
addMenuItemMutation,
allEateriesQuery, allEateriesQuery,
} from "./types"; } from "./types";
@@ -64,12 +65,26 @@ const GET_EATERY_MENU = gql`
menuItems { menuItems {
id id
name name
available
description
price price
} }
} }
} }
`; `;
const ADD_MENU_ITEM = gql`
mutation addMenuItem($menuItem: AddMenuItemInput!) {
addMenuItem(menuItem: $menuItem) {
id
name
available
description
price
}
}
`;
// ─── Provider ───────────────────────────────────────────────────────────────── // ─── Provider ─────────────────────────────────────────────────────────────────
export function ManagerProvider({ children }: { children: ReactNode }) { export function ManagerProvider({ children }: { children: ReactNode }) {
@@ -86,6 +101,10 @@ export function ManagerProvider({ children }: { children: ReactNode }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
}); });
const [mutateAddMenuItem] = useMutation<addMenuItemMutation>(ADD_MENU_ITEM, {
client: eateryClient,
});
useEffect(() => { useEffect(() => {
if (data?.allEateries?.[0]) { if (data?.allEateries?.[0]) {
setProducts(data.allEateries[0].menuItems); setProducts(data.allEateries[0].menuItems);
@@ -94,8 +113,14 @@ export function ManagerProvider({ children }: { children: ReactNode }) {
// ── MenuItemEntity actions ────────────────────────────────────────────────────── // ── MenuItemEntity actions ──────────────────────────────────────────────────────
const addProduct = (product: MenuItemEntity) => { const addProduct = async (product: MenuItemEntity) => {
setProducts((prev) => [...prev, product]); const { data } = await mutateAddMenuItem({
variables: {
menuItem: product,
},
});
if (data) setProducts((prev) => [...prev, data.addMenuItem]);
}; };
const updateProduct = (product: MenuItemEntity) => { const updateProduct = (product: MenuItemEntity) => {
+2 -1
View File
@@ -139,9 +139,10 @@ export interface MenuItemEntity {
id: string; id: string;
name: string; name: string;
price: number; price: number;
eatery: EateryEntity; eatery?: EateryEntity;
imageUrl: string; imageUrl: string;
available: boolean; available: boolean;
description: string;
} }
export interface ShiftEntity {} export interface ShiftEntity {}