FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 

156 行
4.8 KiB

  1. "use client";
  2. import { useEffect, useState } from "react";
  3. import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material";
  4. import DownloadIcon from "@mui/icons-material/Download";
  5. import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
  6. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  7. import {
  8. WorkbenchReportOption,
  9. fetchWorkbenchTruckRoutingLaneOptions,
  10. fetchWorkbenchTruckRoutingStoreOptions,
  11. fetchWorkbenchTruckRoutingSummaryPrecheck,
  12. } from "@/app/api/doworkbench/truckRoutingSummaryWorkbenchApi";
  13. import {
  14. FEATURE_USAGE,
  15. FEATURE_USAGE_ACTION,
  16. logFeatureUsage,
  17. } from "@/lib/featureUsageLog";
  18. const TruckRoutingSummaryTabWorkbench: React.FC = () => {
  19. const [storeOptions, setStoreOptions] = useState<WorkbenchReportOption[]>([]);
  20. const [laneOptions, setLaneOptions] = useState<WorkbenchReportOption[]>([]);
  21. const [storeId, setStoreId] = useState("");
  22. const [truckLanceCode, setTruckLanceCode] = useState("");
  23. const [date, setDate] = useState("");
  24. const [loading, setLoading] = useState(false);
  25. useEffect(() => {
  26. fetchWorkbenchTruckRoutingStoreOptions()
  27. .then(setStoreOptions)
  28. .catch((err) => console.error("Failed to load workbench store options", err));
  29. }, []);
  30. const onStoreChange = async (value: string) => {
  31. setStoreId(value);
  32. setTruckLanceCode("");
  33. try {
  34. const lanes = await fetchWorkbenchTruckRoutingLaneOptions(value);
  35. setLaneOptions(lanes);
  36. } catch (error) {
  37. console.error("Failed to load workbench lane options", error);
  38. setLaneOptions([]);
  39. }
  40. };
  41. const canDownload = storeId && truckLanceCode && date && !loading;
  42. const onDownload = async () => {
  43. if (!canDownload) return;
  44. try {
  45. const precheck = await fetchWorkbenchTruckRoutingSummaryPrecheck({
  46. storeId,
  47. truckLanceCode,
  48. date,
  49. });
  50. if (precheck.hasUnpickedOrders) {
  51. const confirmed = window.confirm(
  52. `此車線仍有 ${precheck.unpickedOrderCount} 張訂單未執拾。\n是否仍要列印 / 下載送貨路線摘要?`
  53. );
  54. if (!confirmed) return;
  55. }
  56. setLoading(true);
  57. const qs = new URLSearchParams({
  58. storeId,
  59. truckLanceCode,
  60. date,
  61. }).toString();
  62. const url = `${NEXT_PUBLIC_API_URL}/doPickOrder/workbench/truck-routing-summary/print?${qs}`;
  63. const response = await clientAuthFetch(url, {
  64. method: "GET",
  65. headers: { Accept: "application/pdf" },
  66. });
  67. if (!response.ok) {
  68. const errorText = await response.text();
  69. throw new Error(`HTTP ${response.status}: ${errorText}`);
  70. }
  71. const blob = await response.blob();
  72. const downloadUrl = window.URL.createObjectURL(blob);
  73. const link = document.createElement("a");
  74. link.href = downloadUrl;
  75. link.setAttribute("download", "TruckRoutingSummary.pdf");
  76. document.body.appendChild(link);
  77. link.click();
  78. link.remove();
  79. window.URL.revokeObjectURL(downloadUrl);
  80. logFeatureUsage(
  81. FEATURE_USAGE.TRUCK_ROUTING_SUMMARY,
  82. FEATURE_USAGE_ACTION.DOWNLOAD,
  83. `workbench-pdf:${storeId}:${truckLanceCode}:${date}`,
  84. );
  85. } catch (error) {
  86. console.error("Failed to download Workbench Truck Routing Summary", error);
  87. alert("下載 Workbench 送貨路線摘要失敗,請稍後再試。");
  88. } finally {
  89. setLoading(false);
  90. }
  91. };
  92. return (
  93. <Box sx={{ maxWidth: 820 }}>
  94. <Typography variant="h6" sx={{ mb: 2 }}>
  95. 送貨路線摘要 (Workbench)
  96. </Typography>
  97. <Stack direction={{ xs: "column", md: "row" }} spacing={2} sx={{ mb: 2 }}>
  98. <TextField
  99. select
  100. fullWidth
  101. label="2/F 或 4/F"
  102. value={storeId}
  103. onChange={(e) => onStoreChange(e.target.value)}
  104. >
  105. {storeOptions.map((opt) => (
  106. <MenuItem key={opt.value} value={opt.value}>
  107. {opt.label}
  108. </MenuItem>
  109. ))}
  110. </TextField>
  111. <TextField
  112. select
  113. fullWidth
  114. label="車線"
  115. value={truckLanceCode}
  116. onChange={(e) => setTruckLanceCode(e.target.value)}
  117. disabled={!storeId}
  118. >
  119. {laneOptions.map((opt) => (
  120. <MenuItem key={opt.value} value={opt.value}>
  121. {opt.label}
  122. </MenuItem>
  123. ))}
  124. </TextField>
  125. <TextField
  126. fullWidth
  127. label="日期"
  128. type="date"
  129. value={date}
  130. InputLabelProps={{ shrink: true }}
  131. onChange={(e) => setDate(e.target.value)}
  132. />
  133. </Stack>
  134. <Button
  135. variant="contained"
  136. startIcon={<DownloadIcon />}
  137. disabled={!canDownload}
  138. onClick={onDownload}
  139. >
  140. {loading ? "生成中..." : "下載報告 (PDF)"}
  141. </Button>
  142. </Box>
  143. );
  144. };
  145. export default TruckRoutingSummaryTabWorkbench;