| @@ -0,0 +1,228 @@ | |||||
| // 新建檔案 | |||||
| "use client"; | |||||
| import { | |||||
| Modal, | |||||
| Box, | |||||
| Typography, | |||||
| TextField, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TableHead, | |||||
| TableRow, | |||||
| Paper, | |||||
| CircularProgress, | |||||
| Button, | |||||
| } from "@mui/material"; | |||||
| import { useCallback, useEffect, useState } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { | |||||
| fetchReleasedDoPickOrdersForSelection, | |||||
| assignByDoPickOrderId, | |||||
| type ReleasedDoPickOrderListItem, | |||||
| } from "@/app/api/pickOrder/actions"; | |||||
| import { useSession } from "next-auth/react"; | |||||
| import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import Swal from "sweetalert2"; | |||||
| import dayjs from "dayjs"; | |||||
| interface Props { | |||||
| open: boolean; | |||||
| onClose: () => void; | |||||
| onAssigned: () => void; | |||||
| storeId: string; | |||||
| truck: string; | |||||
| } | |||||
| const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| open, | |||||
| onClose, | |||||
| onAssigned, | |||||
| storeId, | |||||
| truck, | |||||
| }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||||
| const [list, setList] = useState<ReleasedDoPickOrderListItem[]>([]); | |||||
| const [loading, setLoading] = useState(false); | |||||
| const [shopSearch, setShopSearch] = useState(""); | |||||
| const [isAssigning, setIsAssigning] = useState(false); | |||||
| const loadList = useCallback(async () => { | |||||
| if (!open) return; | |||||
| setLoading(true); | |||||
| try { | |||||
| const data = await fetchReleasedDoPickOrdersForSelection( | |||||
| shopSearch.trim() || undefined, | |||||
| storeId, | |||||
| truck?.trim() || undefined // 傳入 truck | |||||
| ); | |||||
| setList(data); | |||||
| } catch (e) { | |||||
| console.error(e); | |||||
| setList([]); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | |||||
| }, [open, shopSearch, storeId, truck]); | |||||
| useEffect(() => { | |||||
| loadList(); | |||||
| }, [loadList]); | |||||
| const handleSelectRow = useCallback( | |||||
| async (item: ReleasedDoPickOrderListItem) => { | |||||
| if (!currentUserId) return; | |||||
| const confirmResult = await Swal.fire({ | |||||
| title: t("Confirm Assignment"), | |||||
| html: ` | |||||
| <div style="text-align: left;"> | |||||
| <p><strong>${t("Date")}:</strong> ${item.requiredDeliveryDate ?? "-"}</p> | |||||
| <p><strong>${t("Shop")}:</strong> ${item.shopName ?? item.shopCode ?? "-"}</p> | |||||
| <p><strong>${t("Truck")}:</strong> ${item.truckLanceCode ?? "-"}</p> | |||||
| <p><strong>${t("Delivery Order")}:</strong> ${(item.deliveryOrderCodes ?? []).join(", ")}</p> | |||||
| </div> | |||||
| `, | |||||
| icon: "question", | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| didOpen: () => { | |||||
| const container = document.querySelector('.swal2-container'); | |||||
| const popup = Swal.getPopup(); | |||||
| if (container) (container as HTMLElement).style.zIndex = '9999'; | |||||
| if (popup) popup.style.zIndex = '9999'; | |||||
| } | |||||
| }); | |||||
| if (!confirmResult.isConfirmed) return; | |||||
| setIsAssigning(true); | |||||
| try { | |||||
| const res = await assignByDoPickOrderId(currentUserId, item.id); | |||||
| if (res?.code === "SUCCESS") { | |||||
| Swal.fire({ | |||||
| icon: "success", | |||||
| text: t("Assigned successfully"), | |||||
| timer: 1500, | |||||
| showConfirmButton: false, | |||||
| }); | |||||
| onAssigned?.(); | |||||
| onClose(); | |||||
| } else { | |||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| text: res?.message ?? t("Assignment failed"), | |||||
| }); | |||||
| } | |||||
| } catch (e) { | |||||
| console.error(e); | |||||
| Swal.fire({ icon: "error", text: t("Error occurred during assignment.") }); | |||||
| } finally { | |||||
| setIsAssigning(false); | |||||
| } | |||||
| }, | |||||
| [currentUserId, t, onAssigned, onClose] | |||||
| ); | |||||
| return ( | |||||
| <Modal open={open} onClose={onClose}> | |||||
| <Box | |||||
| sx={{ | |||||
| position: "absolute", | |||||
| top: "50%", | |||||
| left: "50%", | |||||
| transform: "translate(-50%, -50%)", | |||||
| width: { xs: "95%", sm: 900 }, | |||||
| maxHeight: "80vh", | |||||
| bgcolor: "background.paper", | |||||
| borderRadius: 2, | |||||
| boxShadow: 24, | |||||
| p: 2, | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| overflow: "hidden", | |||||
| }} | |||||
| > | |||||
| <Typography variant="h6" sx={{ mb: 2 }}> | |||||
| {t("Not Yet Finished Released Do Pick Orders")} | |||||
| </Typography> | |||||
| <TextField | |||||
| label={t("Search by Shop")} | |||||
| size="small" | |||||
| value={shopSearch} | |||||
| onChange={(e) => setShopSearch(e.target.value)} | |||||
| sx={{ mb: 2 }} | |||||
| fullWidth | |||||
| /> | |||||
| {loading ? ( | |||||
| <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| ) : ( | |||||
| <TableContainer sx={{ flex: 1, overflow: "auto" }}> | |||||
| <Table size="small" stickyHeader> | |||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableCell>{t("Date")}</TableCell> | |||||
| <TableCell>{t("Shop")}</TableCell> | |||||
| <TableCell>{t("Truck")}</TableCell> | |||||
| <TableCell>{t("Delivery Order Code")}</TableCell> | |||||
| <TableCell align="right">{t("Action")}</TableCell> | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {list.map((row) => ( | |||||
| <TableRow key={row.id} hover> | |||||
| <TableCell> | |||||
| {row.requiredDeliveryDate | |||||
| ? dayjs(row.requiredDeliveryDate).format("YYYY-MM-DD") | |||||
| : "-"} | |||||
| </TableCell> | |||||
| <TableCell>{row.shopName ?? row.shopCode ?? "-"}</TableCell> | |||||
| <TableCell>{row.truckLanceCode ?? "-"}</TableCell> | |||||
| <TableCell sx={{ whiteSpace: "pre-line" }}> | |||||
| {(row.deliveryOrderCodes ?? []).join("\n") || "-"} | |||||
| </TableCell> | |||||
| <TableCell align="right"> | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="small" | |||||
| disabled={isAssigning} | |||||
| onClick={() => handleSelectRow(row)} | |||||
| > | |||||
| {t("Select")} | |||||
| </Button> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| )} | |||||
| {!loading && list.length === 0 && ( | |||||
| <Typography color="text.secondary" sx={{ py: 3, textAlign: "center" }}> | |||||
| {t("No entries available")} | |||||
| </Typography> | |||||
| )} | |||||
| <Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end" }}> | |||||
| <Button onClick={onClose} variant="outlined"> | |||||
| {t("Close")} | |||||
| </Button> | |||||
| </Box> | |||||
| </Box> | |||||
| </Modal> | |||||
| ); | |||||
| }; | |||||
| export default ReleasedDoPickOrderSelectModal; | |||||