diff --git a/src/app/api/warehouse/actions.ts b/src/app/api/warehouse/actions.ts index b548825..2ec4108 100644 --- a/src/app/api/warehouse/actions.ts +++ b/src/app/api/warehouse/actions.ts @@ -36,9 +36,11 @@ export const createWarehouse = async (data: WarehouseInputs) => { }; export const editWarehouse = async (id: number, data: WarehouseInputs) => { - const updatedWarehouse = await serverFetchWithNoContent(`${BASE_API_URL}/warehouse/${id}`, { - method: "PUT", - body: JSON.stringify(data), + // Backend uses the same /warehouse/save POST endpoint for both create and update, + // distinguished by presence of id in the payload. + const updatedWarehouse = await serverFetchWithNoContent(`${BASE_API_URL}/warehouse/save`, { + method: "POST", + body: JSON.stringify({ id, ...data }), headers: { "Content-Type": "application/json" }, }); revalidateTag("warehouse"); diff --git a/src/components/WarehouseHandle/WarehouseHandle.tsx b/src/components/WarehouseHandle/WarehouseHandle.tsx index 97e471b..8e4dc1d 100644 --- a/src/components/WarehouseHandle/WarehouseHandle.tsx +++ b/src/components/WarehouseHandle/WarehouseHandle.tsx @@ -2,12 +2,13 @@ import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import SearchResults, { Column } from "../SearchResults/index"; +import SearchResults, { Column } from "../SearchResults/SearchResults"; import DeleteIcon from "@mui/icons-material/Delete"; +import EditIcon from "@mui/icons-material/Edit"; import { useRouter } from "next/navigation"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { WarehouseResult } from "@/app/api/warehouse"; -import { deleteWarehouse } from "@/app/api/warehouse/actions"; +import { deleteWarehouse, editWarehouse } from "@/app/api/warehouse/actions"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; import CardActions from "@mui/material/CardActions"; @@ -18,6 +19,10 @@ import Box from "@mui/material/Box"; import RestartAlt from "@mui/icons-material/RestartAlt"; import Search from "@mui/icons-material/Search"; import InputAdornment from "@mui/material/InputAdornment"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; interface Props { warehouses: WarehouseResult[]; @@ -36,6 +41,15 @@ const WarehouseHandle: React.FC = ({ warehouses }) => { const router = useRouter(); const [isSearching, setIsSearching] = useState(false); + // State for editing order & stockTakeSection + const [editingWarehouse, setEditingWarehouse] = useState(null); + const [editValues, setEditValues] = useState({ + order: "", + stockTakeSection: "", + }); + const [isSavingEdit, setIsSavingEdit] = useState(false); + const [editError, setEditError] = useState(""); + const [searchInputs, setSearchInputs] = useState({ store_id: "", warehouse: "", @@ -69,6 +83,71 @@ const WarehouseHandle: React.FC = ({ warehouses }) => { setPagingController({ pageNum: 1, pageSize: pagingController.pageSize }); }, [warehouses, pagingController.pageSize]); + const onEditClick = useCallback((warehouse: WarehouseResult) => { + setEditingWarehouse(warehouse); + setEditValues({ + order: warehouse.order ?? "", + stockTakeSection: warehouse.stockTakeSection ?? "", + }); + setEditError(""); + }, []); + + const handleEditClose = useCallback(() => { + if (isSavingEdit) return; + setEditingWarehouse(null); + setEditError(""); + }, [isSavingEdit]); + + const handleEditSave = useCallback(async () => { + if (!editingWarehouse) return; + + const trimmedOrder = editValues.order.trim(); + const trimmedStockTakeSection = editValues.stockTakeSection.trim(); + + const orderPattern = /^[A-Za-z0-9]{2}-[A-Za-z0-9]{3}$/; + const sectionPattern = /^[A-Za-z0-9]{2}-[A-Za-z0-9]{3}$/; + + if (trimmedOrder && !orderPattern.test(trimmedOrder)) { + setEditError(`${t("order")} 格式必須為 XF-YYY`); + return; + } + + if (trimmedStockTakeSection && !sectionPattern.test(trimmedStockTakeSection)) { + setEditError(`${t("stockTakeSection")} 格式必須為 ST-YYY`); + return; + } + + try { + setIsSavingEdit(true); + setEditError(""); + + await editWarehouse(editingWarehouse.id, { + order: trimmedOrder || undefined, + stockTakeSection: trimmedStockTakeSection || undefined, + }); + + setFilteredWarehouse((prev) => + prev.map((w) => + w.id === editingWarehouse.id + ? { + ...w, + order: trimmedOrder || undefined, + stockTakeSection: trimmedStockTakeSection || undefined, + } + : w, + ), + ); + + router.refresh(); + setEditingWarehouse(null); + } catch (error) { + console.error("Failed to edit warehouse:", error); + setEditError(t("An error has occurred. Please try again later.")); + } finally { + setIsSavingEdit(false); + } + }, [editValues, editingWarehouse, router, t, setFilteredWarehouse]); + const handleSearch = useCallback(() => { setIsSearching(true); try { @@ -177,6 +256,14 @@ const WarehouseHandle: React.FC = ({ warehouses }) => { const columns = useMemo[]>( () => [ + { + name: "action", + label: t("Edit"), + onClick: onEditClick, + buttonIcon: , + color: "primary", + sx: { width: "10%", minWidth: "80px" }, + }, { name: "code", label: t("code"), @@ -226,6 +313,7 @@ const WarehouseHandle: React.FC = ({ warehouses }) => { headerAlign: "left", sx: { width: "15%", minWidth: "120px" }, }, + { name: "action", label: t("Delete"), @@ -338,6 +426,51 @@ const WarehouseHandle: React.FC = ({ warehouses }) => { pagingController={pagingController} setPagingController={setPagingController} /> + + {t("Edit")} + + {editError && ( + + {editError} + + )} + + setEditValues((prev) => ({ ...prev, order: e.target.value })) + } + size="small" + fullWidth + /> + + setEditValues((prev) => ({ ...prev, stockTakeSection: e.target.value })) + } + size="small" + fullWidth + /> + + + + + + ); };