FPSMS-frontend
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

365 řádky
13 KiB

  1. "use client";
  2. import { useCallback, useMemo, useState } from "react";
  3. import { useTranslation } from "react-i18next";
  4. import SearchResults, { Column } from "../SearchResults/index";
  5. import DeleteIcon from "@mui/icons-material/Delete";
  6. import { useRouter } from "next/navigation";
  7. import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
  8. import { WarehouseResult } from "@/app/api/warehouse";
  9. import { deleteWarehouse } from "@/app/api/warehouse/actions";
  10. import Card from "@mui/material/Card";
  11. import CardContent from "@mui/material/CardContent";
  12. import CardActions from "@mui/material/CardActions";
  13. import Typography from "@mui/material/Typography";
  14. import TextField from "@mui/material/TextField";
  15. import Button from "@mui/material/Button";
  16. import Box from "@mui/material/Box";
  17. import RestartAlt from "@mui/icons-material/RestartAlt";
  18. import Search from "@mui/icons-material/Search";
  19. import InputAdornment from "@mui/material/InputAdornment";
  20. interface Props {
  21. warehouses: WarehouseResult[];
  22. }
  23. type SearchQuery = Partial<Omit<WarehouseResult, "id">>;
  24. type SearchParamNames = keyof SearchQuery;
  25. const WarehouseHandle: React.FC<Props> = ({ warehouses }) => {
  26. const { t } = useTranslation(["warehouse", "common"]);
  27. const [filteredWarehouse, setFilteredWarehouse] = useState(warehouses);
  28. const [pagingController, setPagingController] = useState({
  29. pageNum: 1,
  30. pageSize: 10,
  31. });
  32. const router = useRouter();
  33. const [isSearching, setIsSearching] = useState(false);
  34. const [searchInputs, setSearchInputs] = useState({
  35. store_id: "",
  36. warehouse: "",
  37. area: "",
  38. slot: "",
  39. stockTakeSection: "",
  40. });
  41. const onDeleteClick = useCallback((warehouse: WarehouseResult) => {
  42. deleteDialog(async () => {
  43. try {
  44. await deleteWarehouse(warehouse.id);
  45. setFilteredWarehouse(prev => prev.filter(w => w.id !== warehouse.id));
  46. router.refresh();
  47. successDialog(t("Delete Success"), t);
  48. } catch (error) {
  49. console.error("Failed to delete warehouse:", error);
  50. // Don't redirect on error, just show error message
  51. // The error will be logged but user stays on the page
  52. }
  53. }, t);
  54. }, [t, router]);
  55. const handleReset = useCallback(() => {
  56. setSearchInputs({
  57. store_id: "",
  58. warehouse: "",
  59. area: "",
  60. slot: "",
  61. stockTakeSection: "",
  62. });
  63. setFilteredWarehouse(warehouses);
  64. setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
  65. }, [warehouses, pagingController.pageSize]);
  66. const handleSearch = useCallback(() => {
  67. setIsSearching(true);
  68. try {
  69. let results: WarehouseResult[] = warehouses;
  70. // Build search pattern from the four fields: store_idF-warehouse-area-slot
  71. // Only search by code field - match the code that follows this pattern
  72. const storeId = searchInputs.store_id?.trim() || "";
  73. const warehouse = searchInputs.warehouse?.trim() || "";
  74. const area = searchInputs.area?.trim() || "";
  75. const slot = searchInputs.slot?.trim() || "";
  76. const stockTakeSection = searchInputs.stockTakeSection?.trim() || "";
  77. // If any field has a value, filter by code pattern and stockTakeSection
  78. if (storeId || warehouse || area || slot || stockTakeSection) {
  79. results = warehouses.filter((warehouseItem) => {
  80. // Filter by stockTakeSection if provided
  81. if (stockTakeSection) {
  82. const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
  83. if (!itemStockTakeSection.includes(stockTakeSection.toLowerCase())) {
  84. return false;
  85. }
  86. }
  87. // Filter by code pattern if any code-related field is provided
  88. if (storeId || warehouse || area || slot) {
  89. if (!warehouseItem.code) {
  90. return false;
  91. }
  92. const codeValue = String(warehouseItem.code).toLowerCase();
  93. // Check if code matches the pattern: store_id-warehouse-area-slot
  94. // Match each part if provided
  95. const codeParts = codeValue.split("-");
  96. if (codeParts.length >= 4) {
  97. const codeStoreId = codeParts[0] || "";
  98. const codeWarehouse = codeParts[1] || "";
  99. const codeArea = codeParts[2] || "";
  100. const codeSlot = codeParts[3] || "";
  101. const storeIdMatch = !storeId || codeStoreId.includes(storeId.toLowerCase());
  102. const warehouseMatch = !warehouse || codeWarehouse.includes(warehouse.toLowerCase());
  103. const areaMatch = !area || codeArea.includes(area.toLowerCase());
  104. const slotMatch = !slot || codeSlot.includes(slot.toLowerCase());
  105. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  106. }
  107. // Fallback: if code doesn't follow the pattern, check if it contains any of the search terms
  108. const storeIdMatch = !storeId || codeValue.includes(storeId.toLowerCase());
  109. const warehouseMatch = !warehouse || codeValue.includes(warehouse.toLowerCase());
  110. const areaMatch = !area || codeValue.includes(area.toLowerCase());
  111. const slotMatch = !slot || codeValue.includes(slot.toLowerCase());
  112. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  113. }
  114. // If only stockTakeSection is provided, return true (already filtered above)
  115. return true;
  116. });
  117. } else {
  118. // If no search terms, show all warehouses
  119. results = warehouses;
  120. }
  121. setFilteredWarehouse(results);
  122. setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
  123. } catch (error) {
  124. console.error("Error searching warehouses:", error);
  125. // Fallback: filter by code pattern and stockTakeSection
  126. const storeId = searchInputs.store_id?.trim().toLowerCase() || "";
  127. const warehouse = searchInputs.warehouse?.trim().toLowerCase() || "";
  128. const area = searchInputs.area?.trim().toLowerCase() || "";
  129. const slot = searchInputs.slot?.trim().toLowerCase() || "";
  130. const stockTakeSection = searchInputs.stockTakeSection?.trim().toLowerCase() || "";
  131. setFilteredWarehouse(
  132. warehouses.filter((warehouseItem) => {
  133. // Filter by stockTakeSection if provided
  134. if (stockTakeSection) {
  135. const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
  136. if (!itemStockTakeSection.includes(stockTakeSection)) {
  137. return false;
  138. }
  139. }
  140. // Filter by code if any code-related field is provided
  141. if (storeId || warehouse || area || slot) {
  142. if (!warehouseItem.code) {
  143. return false;
  144. }
  145. const codeValue = String(warehouseItem.code).toLowerCase();
  146. const codeParts = codeValue.split("-");
  147. if (codeParts.length >= 4) {
  148. const storeIdMatch = !storeId || codeParts[0].includes(storeId);
  149. const warehouseMatch = !warehouse || codeParts[1].includes(warehouse);
  150. const areaMatch = !area || codeParts[2].includes(area);
  151. const slotMatch = !slot || codeParts[3].includes(slot);
  152. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  153. }
  154. return (!storeId || codeValue.includes(storeId)) &&
  155. (!warehouse || codeValue.includes(warehouse)) &&
  156. (!area || codeValue.includes(area)) &&
  157. (!slot || codeValue.includes(slot));
  158. }
  159. return true;
  160. })
  161. );
  162. } finally {
  163. setIsSearching(false);
  164. }
  165. }, [searchInputs, warehouses, pagingController.pageSize]);
  166. const columns = useMemo<Column<WarehouseResult>[]>(
  167. () => [
  168. {
  169. name: "code",
  170. label: t("code"),
  171. align: "left",
  172. headerAlign: "left",
  173. sx: { width: "15%", minWidth: "120px" },
  174. },
  175. {
  176. name: "store_id",
  177. label: t("store_id"),
  178. align: "left",
  179. headerAlign: "left",
  180. sx: { width: "15%", minWidth: "120px" },
  181. },
  182. {
  183. name: "warehouse",
  184. label: t("warehouse"),
  185. align: "left",
  186. headerAlign: "left",
  187. sx: { width: "15%", minWidth: "120px" },
  188. },
  189. {
  190. name: "area",
  191. label: t("area"),
  192. align: "left",
  193. headerAlign: "left",
  194. sx: { width: "15%", minWidth: "120px" },
  195. },
  196. {
  197. name: "slot",
  198. label: t("slot"),
  199. align: "left",
  200. headerAlign: "left",
  201. sx: { width: "15%", minWidth: "120px" },
  202. },
  203. {
  204. name: "order",
  205. label: t("order"),
  206. align: "left",
  207. headerAlign: "left",
  208. sx: { width: "15%", minWidth: "120px" },
  209. },
  210. {
  211. name: "stockTakeSection",
  212. label: t("stockTakeSection"),
  213. align: "left",
  214. headerAlign: "left",
  215. sx: { width: "15%", minWidth: "120px" },
  216. },
  217. {
  218. name: "action",
  219. label: t("Delete"),
  220. onClick: onDeleteClick,
  221. buttonIcon: <DeleteIcon />,
  222. color: "error",
  223. sx: { width: "10%", minWidth: "80px" },
  224. },
  225. ],
  226. [t, onDeleteClick],
  227. );
  228. return (
  229. <>
  230. <Card>
  231. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  232. <Typography variant="overline">{t("Search Criteria")}</Typography>
  233. <Box
  234. sx={{
  235. display: "flex",
  236. alignItems: "center",
  237. gap: 1,
  238. flexWrap: "nowrap",
  239. justifyContent: "flex-start",
  240. }}
  241. >
  242. {/* 樓層 field with F inside on the right */}
  243. <TextField
  244. label={t("store_id")}
  245. value={searchInputs.store_id}
  246. onChange={(e) =>
  247. setSearchInputs((prev) => ({ ...prev, store_id: e.target.value }))
  248. }
  249. size="small"
  250. sx={{ width: "150px", minWidth: "120px" }}
  251. InputProps={{
  252. endAdornment: (
  253. <InputAdornment position="end">F</InputAdornment>
  254. ),
  255. }}
  256. />
  257. <Typography variant="body1" sx={{ mx: 0.5 }}>
  258. -
  259. </Typography>
  260. {/* 倉庫 field */}
  261. <TextField
  262. label={t("warehouse")}
  263. value={searchInputs.warehouse}
  264. onChange={(e) =>
  265. setSearchInputs((prev) => ({ ...prev, warehouse: e.target.value }))
  266. }
  267. size="small"
  268. sx={{ width: "150px", minWidth: "120px" }}
  269. />
  270. <Typography variant="body1" sx={{ mx: 0.5 }}>
  271. -
  272. </Typography>
  273. {/* 區域 field */}
  274. <TextField
  275. label={t("area")}
  276. value={searchInputs.area}
  277. onChange={(e) =>
  278. setSearchInputs((prev) => ({ ...prev, area: e.target.value }))
  279. }
  280. size="small"
  281. sx={{ width: "150px", minWidth: "120px" }}
  282. />
  283. <Typography variant="body1" sx={{ mx: 0.5 }}>
  284. -
  285. </Typography>
  286. {/* 儲位 field */}
  287. <TextField
  288. label={t("slot")}
  289. value={searchInputs.slot}
  290. onChange={(e) =>
  291. setSearchInputs((prev) => ({ ...prev, slot: e.target.value }))
  292. }
  293. size="small"
  294. sx={{ width: "150px", minWidth: "120px" }}
  295. />
  296. {/* 盤點區域 field */}
  297. <Box sx={{ flex: 1, minWidth: "150px", ml: 2 }}>
  298. <TextField
  299. label={t("stockTakeSection")}
  300. value={searchInputs.stockTakeSection}
  301. onChange={(e) =>
  302. setSearchInputs((prev) => ({ ...prev, stockTakeSection: e.target.value }))
  303. }
  304. size="small"
  305. fullWidth
  306. />
  307. </Box>
  308. </Box>
  309. <CardActions sx={{ justifyContent: "flex-start", px: 0, pt: 1 }}>
  310. <Button
  311. variant="text"
  312. startIcon={<RestartAlt />}
  313. onClick={handleReset}
  314. >
  315. {t("Reset")}
  316. </Button>
  317. <Button
  318. variant="outlined"
  319. startIcon={<Search />}
  320. onClick={handleSearch}
  321. >
  322. {t("Search")}
  323. </Button>
  324. </CardActions>
  325. </CardContent>
  326. </Card>
  327. <SearchResults<WarehouseResult>
  328. items={filteredWarehouse}
  329. columns={columns}
  330. pagingController={pagingController}
  331. setPagingController={setPagingController}
  332. />
  333. </>
  334. );
  335. };
  336. export default WarehouseHandle;