FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

321 строка
10 KiB

  1. // 新建檔案
  2. "use client";
  3. import {
  4. Modal,
  5. Box,
  6. Typography,
  7. TextField,
  8. Table,
  9. TableBody,
  10. TableCell,
  11. TableContainer,
  12. TableHead,
  13. TableRow,
  14. Paper,
  15. CircularProgress,
  16. Button,
  17. } from "@mui/material";
  18. import { useCallback, useEffect, useState } from "react";
  19. import { useTranslation } from "react-i18next";
  20. import {
  21. fetchReleasedDoPickOrdersForSelection,
  22. assignByDoPickOrderId,
  23. type ReleasedDoPickOrderListItem,
  24. fetchReleasedDoPickOrdersForSelectionToday,
  25. type PostPickOrderResponse,
  26. } from "@/app/api/pickOrder/actions";
  27. import { useSession } from "next-auth/react";
  28. import { SessionWithTokens } from "@/config/authConfig";
  29. import Swal from "sweetalert2";
  30. import { requiredDeliveryDateToDayString } from "@/app/utils/formatUtil";
  31. /** DO workbench 使用 workbench released API;Finished Good 不傳即可 */
  32. export type ReleasedDoPickListBridge = {
  33. loadBeforeToday: (
  34. shopName?: string,
  35. storeId?: string,
  36. truck?: string,
  37. releaseType?: string,
  38. /** Optional `2F`/`4F` for Truck X supplier-floor split */
  39. floor?: string
  40. ) => Promise<ReleasedDoPickOrderListItem[]>;
  41. /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */
  42. loadToday: (
  43. shopName?: string,
  44. storeId?: string,
  45. truck?: string,
  46. requiredDeliveryDate?: string,
  47. releaseType?: string,
  48. /** Optional `2F`/`4F` for Truck X supplier-floor split */
  49. floor?: string
  50. ) => Promise<ReleasedDoPickOrderListItem[]>;
  51. assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>;
  52. };
  53. interface Props {
  54. open: boolean;
  55. onClose: () => void;
  56. onAssigned: () => void;
  57. storeId: string;
  58. truck: string;
  59. isDefaultTruck: boolean;
  60. /** Truck X only: today → released-today; before → released (歷史未完工) */
  61. defaultDateScope?: "today" | "before";
  62. listBridge?: ReleasedDoPickListBridge;
  63. /** Workbench: `delivery_order_pick_order.requiredDeliveryDate` for Truck X (select day); used when [defaultDateScope] is today. */
  64. defaultTruckRequiredDeliveryDate?: string;
  65. /** Workbench: filter list to `releaseType` (e.g. `isExtra`). */
  66. releaseTypeFilter?: string;
  67. /** Prefill shop search when opening (e.g. Etra lane picker). */
  68. initialShopSearch?: string;
  69. /**
  70. * When set with a workbench listBridge, non–Truck-X list uses released-today with this
  71. * requiredDate instead of historical released (delivery date before calendar today).
  72. */
  73. filterRequiredDeliveryDate?: string;
  74. /**
  75. * Truck X only: `2F`/`4F` (or `2/F`/`4/F`) — filter by DO supplier preferred floor; storeId stays null.
  76. */
  77. truckXFloor?: string;
  78. }
  79. /** FP-MTMS Version Checklist | Functions Ref. No. 36 | v1.0.0 | 2026-07-27 */
  80. const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
  81. open,
  82. onClose,
  83. onAssigned,
  84. storeId,
  85. truck,
  86. isDefaultTruck,
  87. defaultDateScope: defaultDateScopeProp = "today",
  88. listBridge,
  89. defaultTruckRequiredDeliveryDate,
  90. releaseTypeFilter,
  91. initialShopSearch,
  92. filterRequiredDeliveryDate,
  93. truckXFloor,
  94. }) => {
  95. const { t } = useTranslation("pickOrder");
  96. const { data: session } = useSession() as { data: SessionWithTokens | null };
  97. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  98. const [list, setList] = useState<ReleasedDoPickOrderListItem[]>([]);
  99. const [loading, setLoading] = useState(false);
  100. const [shopSearch, setShopSearch] = useState("");
  101. const [isAssigning, setIsAssigning] = useState(false);
  102. const loadList = useCallback(async () => {
  103. if (!open) return;
  104. setLoading(true);
  105. try {
  106. let data: ReleasedDoPickOrderListItem[];
  107. const loadReleased = listBridge?.loadBeforeToday ?? fetchReleasedDoPickOrdersForSelection;
  108. const loadTodayFn = listBridge?.loadToday ?? fetchReleasedDoPickOrdersForSelectionToday;
  109. const floorArg = truckXFloor?.trim() || undefined;
  110. const shopArg = shopSearch.trim() || undefined;
  111. if (isDefaultTruck) {
  112. if (defaultDateScopeProp === "today") {
  113. data = await loadTodayFn(
  114. shopArg,
  115. undefined,
  116. "車線-X",
  117. defaultTruckRequiredDeliveryDate?.trim() || undefined,
  118. releaseTypeFilter?.trim() || undefined,
  119. floorArg
  120. );
  121. } else {
  122. data = await loadReleased(
  123. shopArg,
  124. undefined,
  125. "車線-X",
  126. releaseTypeFilter?.trim() || undefined,
  127. floorArg
  128. );
  129. }
  130. } else if (filterRequiredDeliveryDate?.trim() && listBridge?.loadToday) {
  131. data = await listBridge.loadToday(
  132. shopSearch.trim() || undefined,
  133. storeId,
  134. truck?.trim() || undefined,
  135. filterRequiredDeliveryDate.trim(),
  136. releaseTypeFilter?.trim() || undefined
  137. );
  138. } else {
  139. data = await loadReleased(
  140. shopSearch.trim() || undefined,
  141. storeId,
  142. truck?.trim() || undefined,
  143. releaseTypeFilter?.trim() || undefined
  144. );
  145. }
  146. setList(data);
  147. } catch (e) {
  148. console.error(e);
  149. setList([]);
  150. } finally {
  151. setLoading(false);
  152. }
  153. }, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate, releaseTypeFilter, filterRequiredDeliveryDate, truckXFloor]);
  154. useEffect(() => {
  155. loadList();
  156. }, [loadList]);
  157. useEffect(() => {
  158. if (!open) return;
  159. setShopSearch(initialShopSearch?.trim() ?? "");
  160. }, [open, initialShopSearch]);
  161. const handleSelectRow = useCallback(
  162. async (item: ReleasedDoPickOrderListItem) => {
  163. if (!currentUserId) return;
  164. const confirmResult = await Swal.fire({
  165. title: t("Confirm Assignment"),
  166. html: `
  167. <div style="text-align: left;">
  168. <p><strong>${t("Date")}:</strong> ${requiredDeliveryDateToDayString(item.requiredDeliveryDate) || "-"}</p>
  169. <p><strong>${t("Shop")}:</strong> ${item.shopName ?? item.shopCode ?? "-"}</p>
  170. <p><strong>${t("Truck")}:</strong> ${item.truckLanceCode ?? "-"}</p>
  171. <p><strong>${t("Delivery Order")}:</strong> ${(item.deliveryOrderCodes ?? []).join(", ")}</p>
  172. </div>
  173. `,
  174. icon: "question",
  175. showCancelButton: true,
  176. confirmButtonText: t("Confirm"),
  177. cancelButtonText: t("Cancel"),
  178. confirmButtonColor: "#8dba00",
  179. cancelButtonColor: "#F04438",
  180. didOpen: () => {
  181. const container = document.querySelector('.swal2-container');
  182. const popup = Swal.getPopup();
  183. if (container) (container as HTMLElement).style.zIndex = '9999';
  184. if (popup) popup.style.zIndex = '9999';
  185. }
  186. });
  187. if (!confirmResult.isConfirmed) return;
  188. setIsAssigning(true);
  189. try {
  190. const assignFn = listBridge?.assignByListItemId ?? assignByDoPickOrderId;
  191. const res = await assignFn(currentUserId, item.id);
  192. if (res?.code === "SUCCESS") {
  193. Swal.fire({
  194. icon: "success",
  195. text: t("Assigned successfully"),
  196. timer: 1500,
  197. showConfirmButton: false,
  198. });
  199. onAssigned?.();
  200. onClose();
  201. } else {
  202. Swal.fire({
  203. icon: "error",
  204. text: res?.message ?? t("Assignment failed"),
  205. });
  206. }
  207. } catch (e) {
  208. console.error(e);
  209. Swal.fire({ icon: "error", text: t("Error occurred during assignment.") });
  210. } finally {
  211. setIsAssigning(false);
  212. }
  213. },
  214. [currentUserId, t, onAssigned, onClose, listBridge]
  215. );
  216. return (
  217. <Modal open={open} onClose={onClose}>
  218. <Box
  219. sx={{
  220. position: "absolute",
  221. top: "50%",
  222. left: "50%",
  223. transform: "translate(-50%, -50%)",
  224. width: { xs: "95%", sm: 900 },
  225. maxHeight: "80vh",
  226. bgcolor: "background.paper",
  227. borderRadius: 2,
  228. boxShadow: 24,
  229. p: 2,
  230. display: "flex",
  231. flexDirection: "column",
  232. overflow: "hidden",
  233. }}
  234. >
  235. <Typography variant="h6" sx={{ mb: 2 }}>
  236. {t("Not Yet Finished Released Do Pick Orders")}
  237. </Typography>
  238. <TextField
  239. label={t("Search by Shop")}
  240. size="small"
  241. value={shopSearch}
  242. onChange={(e) => setShopSearch(e.target.value)}
  243. sx={{ mb: 2 }}
  244. fullWidth
  245. />
  246. {loading ? (
  247. <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
  248. <CircularProgress />
  249. </Box>
  250. ) : (
  251. <TableContainer sx={{ flex: 1, overflow: "auto" }}>
  252. <Table size="small" stickyHeader>
  253. <TableHead>
  254. <TableRow>
  255. <TableCell>{t("Date")}</TableCell>
  256. <TableCell>{t("Shop")}</TableCell>
  257. <TableCell>{t("Truck")}</TableCell>
  258. <TableCell>{t("Delivery Order Code")}</TableCell>
  259. <TableCell align="right">{t("Action")}</TableCell>
  260. </TableRow>
  261. </TableHead>
  262. <TableBody>
  263. {list.map((row) => (
  264. <TableRow key={row.id} hover>
  265. <TableCell>
  266. {requiredDeliveryDateToDayString(row.requiredDeliveryDate) || "-"}
  267. </TableCell>
  268. <TableCell>{row.shopName ?? row.shopCode ?? "-"}</TableCell>
  269. <TableCell>{row.truckLanceCode ?? "-"}</TableCell>
  270. <TableCell sx={{ whiteSpace: "pre-line" }}>
  271. {(row.deliveryOrderCodes ?? []).join("\n") || "-"}
  272. </TableCell>
  273. <TableCell align="right">
  274. <Button
  275. variant="outlined"
  276. size="small"
  277. disabled={isAssigning}
  278. onClick={() => handleSelectRow(row)}
  279. >
  280. {t("Select")}
  281. </Button>
  282. </TableCell>
  283. </TableRow>
  284. ))}
  285. </TableBody>
  286. </Table>
  287. </TableContainer>
  288. )}
  289. {!loading && list.length === 0 && (
  290. <Typography color="text.secondary" sx={{ py: 3, textAlign: "center" }}>
  291. {t("No entries available")}
  292. </Typography>
  293. )}
  294. <Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end" }}>
  295. <Button onClick={onClose} variant="outlined">
  296. {t("Close")}
  297. </Button>
  298. </Box>
  299. </Box>
  300. </Modal>
  301. );
  302. };
  303. export default ReleasedDoPickOrderSelectModal;