| @@ -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; | |||