chore: updat
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user