FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

380 satır
13 KiB

  1. "use client";
  2. import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel } from "@mui/material";
  3. import { useCallback, useEffect, useState } from "react";
  4. import { useTranslation } from "react-i18next";
  5. import { useSession } from "next-auth/react";
  6. import { SessionWithTokens } from "@/config/authConfig";
  7. import { fetchStoreLaneSummary, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions";
  8. import Swal from "sweetalert2";
  9. import dayjs from "dayjs";
  10. interface Props {
  11. onPickOrderAssigned?: () => void;
  12. onSwitchToDetailTab?: () => void;
  13. }
  14. const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitchToDetailTab }) => {
  15. const { t } = useTranslation("pickOrder");
  16. const { data: session } = useSession() as { data: SessionWithTokens | null };
  17. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  18. const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null);
  19. const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null);
  20. const [isLoadingSummary, setIsLoadingSummary] = useState(false);
  21. const [isAssigning, setIsAssigning] = useState(false);
  22. //const [selectedDate, setSelectedDate] = useState<string>("today");
  23. const [selectedDate, setSelectedDate] = useState<string>("today");
  24. const loadSummaries = useCallback(async () => {
  25. setIsLoadingSummary(true);
  26. try {
  27. // Convert selectedDate to the format needed
  28. let dateParam: string | undefined;
  29. if (selectedDate === "today") {
  30. dateParam = dayjs().format('YYYY-MM-DD');
  31. } else if (selectedDate === "tomorrow") {
  32. dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD');
  33. } else if (selectedDate === "dayAfterTomorrow") {
  34. dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD');
  35. }
  36. const [s2, s4] = await Promise.all([
  37. fetchStoreLaneSummary("2/F", dateParam),
  38. fetchStoreLaneSummary("4/F", dateParam)
  39. ]);
  40. setSummary2F(s2);
  41. setSummary4F(s4);
  42. } catch (error) {
  43. console.error("Error loading summaries:", error);
  44. } finally {
  45. setIsLoadingSummary(false);
  46. }
  47. }, [selectedDate]);
  48. // 初始化
  49. useEffect(() => {
  50. loadSummaries();
  51. }, [loadSummaries]);
  52. const handleAssignByLane = useCallback(async (
  53. storeId: string,
  54. truckDepartureTime: string,
  55. truckLanceCode: string,
  56. requiredDate: string
  57. ) => {
  58. if (!currentUserId) {
  59. console.error("Missing user id in session");
  60. return;
  61. }
  62. let dateParam: string | undefined;
  63. if (requiredDate === "today") {
  64. dateParam = dayjs().format('YYYY-MM-DD');
  65. } else if (requiredDate === "tomorrow") {
  66. dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD');
  67. } else if (requiredDate === "dayAfterTomorrow") {
  68. dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD');
  69. }
  70. setIsAssigning(true);
  71. try {
  72. const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam);
  73. if (res.code === "SUCCESS") {
  74. console.log(" Successfully assigned pick order from lane", truckLanceCode);
  75. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  76. loadSummaries(); // 刷新按钮状态
  77. onPickOrderAssigned?.();
  78. onSwitchToDetailTab?.();
  79. } else if (res.code === "USER_BUSY") {
  80. Swal.fire({
  81. icon: "warning",
  82. title: t("Warning"),
  83. text: t("You already have a pick order in progess. Please complete it first before taking next pick order."),
  84. confirmButtonText: t("Confirm"),
  85. confirmButtonColor: "#8dba00"
  86. });
  87. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  88. } else if (res.code === "NO_ORDERS") {
  89. Swal.fire({
  90. icon: "info",
  91. title: t("Info"),
  92. text: t("No available pick order(s) for this lane."),
  93. confirmButtonText: t("Confirm"),
  94. confirmButtonColor: "#8dba00"
  95. });
  96. } else {
  97. console.log("ℹ️ Assignment result:", res.message);
  98. }
  99. } catch (error) {
  100. console.error("❌ Error assigning by lane:", error);
  101. Swal.fire({
  102. icon: "error",
  103. title: t("Error"),
  104. text: t("Error occurred during assignment."),
  105. confirmButtonText: t("Confirm"),
  106. confirmButtonColor: "#8dba00"
  107. });
  108. } finally {
  109. setIsAssigning(false);
  110. }
  111. }, [currentUserId, t, selectedDate, onPickOrderAssigned, onSwitchToDetailTab, loadSummaries]);
  112. const getDateLabel = (offset: number) => {
  113. return dayjs().add(offset, 'day').format('YYYY-MM-DD');
  114. };
  115. // Flatten rows to create one box per lane
  116. const flattenRows = (rows: any[]) => {
  117. const flattened: any[] = [];
  118. rows.forEach(row => {
  119. row.lanes.forEach((lane: any) => {
  120. flattened.push({
  121. truckDepartureTime: row.truckDepartureTime,
  122. lane: lane
  123. });
  124. });
  125. });
  126. return flattened;
  127. };
  128. return (
  129. <Box sx={{ mb: 2 }}>
  130. {/* Date Selector Dropdown */}
  131. <Box sx={{ maxWidth: 300, mb: 2 }}>
  132. <FormControl fullWidth size="small">
  133. <InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
  134. <Select
  135. labelId="date-select-label"
  136. id="date-select"
  137. value={selectedDate}
  138. label={t("Select Date")}
  139. onChange={(e) => { {
  140. setSelectedDate(e.target.value);
  141. loadSummaries();
  142. }}}
  143. >
  144. <MenuItem value="today">
  145. {t("Today")} ({getDateLabel(0)})
  146. </MenuItem>
  147. <MenuItem value="tomorrow">
  148. {t("Tomorrow")} ({getDateLabel(1)})
  149. </MenuItem>
  150. <MenuItem value="dayAfterTomorrow">
  151. {t("Day After Tomorrow")} ({getDateLabel(2)})
  152. </MenuItem>
  153. </Select>
  154. </FormControl>
  155. </Box>
  156. {/* Grid containing both floors */}
  157. <Grid container spacing={2}>
  158. {/* 2/F 楼层面板 */}
  159. <Grid item xs={12}>
  160. <Stack direction="row" spacing={2} alignItems="flex-start">
  161. {/* Floor Label */}
  162. <Typography
  163. variant="h6"
  164. sx={{
  165. fontWeight: 600,
  166. minWidth: 60,
  167. pt: 1
  168. }}
  169. >
  170. 2/F
  171. </Typography>
  172. {/* Content Box */}
  173. <Box
  174. sx={{
  175. border: '1px solid #e0e0e0',
  176. borderRadius: 1,
  177. p: 1,
  178. backgroundColor: '#fafafa',
  179. flex: 1
  180. }}
  181. >
  182. {isLoadingSummary ? (
  183. <Typography variant="caption">Loading...</Typography>
  184. ) : !summary2F?.rows || summary2F.rows.length === 0 ? (
  185. <Typography
  186. variant="body2"
  187. color="text.secondary"
  188. sx={{
  189. fontWeight: 600,
  190. fontSize: '1rem',
  191. textAlign: 'center',
  192. py: 1
  193. }}
  194. >
  195. {t("No entries available")}
  196. </Typography>
  197. ) : (
  198. <Grid container spacing={1}>
  199. {flattenRows(summary2F.rows).map((item, idx) => (
  200. <Grid item xs={12} sm={6} md={3} key={idx}>
  201. <Stack
  202. direction="row"
  203. spacing={1}
  204. alignItems="center"
  205. sx={{
  206. border: '1px solid #e0e0e0',
  207. borderRadius: 0.5,
  208. p: 1,
  209. backgroundColor: '#fff',
  210. height: '100%'
  211. }}
  212. >
  213. {/* Time on the left */}
  214. <Typography
  215. variant="body2"
  216. sx={{
  217. fontWeight: 600,
  218. fontSize: '1rem',
  219. minWidth: 50,
  220. whiteSpace: 'nowrap'
  221. }}
  222. >
  223. {item.truckDepartureTime}
  224. </Typography>
  225. {/* Single Button on the right */}
  226. <Button
  227. variant="outlined"
  228. size="medium"
  229. disabled={item.lane.unassigned === 0 || isAssigning}
  230. onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
  231. sx={{
  232. flex: 1,
  233. fontSize: '1.1rem',
  234. py: 1,
  235. px: 1.5,
  236. borderWidth: 1,
  237. borderColor: '#ccc',
  238. fontWeight: 500,
  239. '&:hover': {
  240. borderColor: '#999',
  241. backgroundColor: '#f5f5f5'
  242. }
  243. }}
  244. >
  245. {`${item.lane.truckLanceCode} (${item.lane.unassigned}/${item.lane.total})`}
  246. </Button>
  247. </Stack>
  248. </Grid>
  249. ))}
  250. </Grid>
  251. )}
  252. </Box>
  253. </Stack>
  254. </Grid>
  255. {/* 4/F 楼层面板 */}
  256. <Grid item xs={12}>
  257. <Stack direction="row" spacing={2} alignItems="flex-start">
  258. {/* Floor Label */}
  259. <Typography
  260. variant="h6"
  261. sx={{
  262. fontWeight: 600,
  263. minWidth: 60,
  264. pt: 1
  265. }}
  266. >
  267. 4/F
  268. </Typography>
  269. {/* Content Box */}
  270. <Box
  271. sx={{
  272. border: '1px solid #e0e0e0',
  273. borderRadius: 1,
  274. p: 1,
  275. backgroundColor: '#fafafa',
  276. flex: 1
  277. }}
  278. >
  279. {isLoadingSummary ? (
  280. <Typography variant="caption">Loading...</Typography>
  281. ) : !summary4F?.rows || summary4F.rows.length === 0 ? (
  282. <Typography
  283. variant="body2"
  284. color="text.secondary"
  285. sx={{
  286. fontWeight: 600,
  287. fontSize: '1rem',
  288. textAlign: 'center',
  289. py: 1
  290. }}
  291. >
  292. {t("No entries available")}
  293. </Typography>
  294. ) : (
  295. <Grid container spacing={1}>
  296. {flattenRows(summary4F.rows).map((item, idx) => (
  297. <Grid item xs={12} sm={6} md={3} key={idx}>
  298. <Stack
  299. direction="row"
  300. spacing={1}
  301. alignItems="center"
  302. sx={{
  303. border: '1px solid #e0e0e0',
  304. borderRadius: 0.5,
  305. p: 1,
  306. backgroundColor: '#fff',
  307. height: '100%'
  308. }}
  309. >
  310. {/* Time on the left */}
  311. <Typography
  312. variant="body2"
  313. sx={{
  314. fontWeight: 600,
  315. fontSize: '1rem',
  316. minWidth: 50,
  317. whiteSpace: 'nowrap'
  318. }}
  319. >
  320. {item.truckDepartureTime}
  321. </Typography>
  322. {/* Single Button on the right */}
  323. <Button
  324. variant="outlined"
  325. size="medium"
  326. disabled={item.lane.unassigned === 0 || isAssigning}
  327. onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
  328. sx={{
  329. flex: 1,
  330. fontSize: '1.1rem',
  331. py: 1,
  332. px: 1.5,
  333. borderWidth: 1,
  334. borderColor: '#ccc',
  335. fontWeight: 500,
  336. '&:hover': {
  337. borderColor: '#999',
  338. backgroundColor: '#f5f5f5'
  339. }
  340. }}
  341. >
  342. {`${item.lane.truckLanceCode} (${item.lane.unassigned}/${item.lane.total})`}
  343. </Button>
  344. </Stack>
  345. </Grid>
  346. ))}
  347. </Grid>
  348. )}
  349. </Box>
  350. </Stack>
  351. </Grid>
  352. </Grid>
  353. </Box>
  354. );
  355. };
  356. export default FinishedGoodFloorLanePanel;