FPSMS-frontend
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 

345 рядки
12 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. }
  51. }, t);
  52. }, [t, router]);
  53. const handleReset = useCallback(() => {
  54. setSearchInputs({
  55. store_id: "",
  56. warehouse: "",
  57. area: "",
  58. slot: "",
  59. stockTakeSection: "",
  60. });
  61. setFilteredWarehouse(warehouses);
  62. setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
  63. }, [warehouses, pagingController.pageSize]);
  64. const handleSearch = useCallback(() => {
  65. setIsSearching(true);
  66. try {
  67. let results: WarehouseResult[] = warehouses;
  68. const storeId = searchInputs.store_id?.trim() || "";
  69. const warehouse = searchInputs.warehouse?.trim() || "";
  70. const area = searchInputs.area?.trim() || "";
  71. const slot = searchInputs.slot?.trim() || "";
  72. const stockTakeSection = searchInputs.stockTakeSection?.trim() || "";
  73. if (storeId || warehouse || area || slot || stockTakeSection) {
  74. results = warehouses.filter((warehouseItem) => {
  75. if (stockTakeSection) {
  76. const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
  77. if (!itemStockTakeSection.includes(stockTakeSection.toLowerCase())) {
  78. return false;
  79. }
  80. }
  81. if (storeId || warehouse || area || slot) {
  82. if (!warehouseItem.code) {
  83. return false;
  84. }
  85. const codeValue = String(warehouseItem.code).toLowerCase();
  86. const codeParts = codeValue.split("-");
  87. if (codeParts.length >= 4) {
  88. const codeStoreId = codeParts[0] || "";
  89. const codeWarehouse = codeParts[1] || "";
  90. const codeArea = codeParts[2] || "";
  91. const codeSlot = codeParts[3] || "";
  92. const storeIdMatch = !storeId || codeStoreId.includes(storeId.toLowerCase());
  93. const warehouseMatch = !warehouse || codeWarehouse.includes(warehouse.toLowerCase());
  94. const areaMatch = !area || codeArea.includes(area.toLowerCase());
  95. const slotMatch = !slot || codeSlot.includes(slot.toLowerCase());
  96. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  97. }
  98. const storeIdMatch = !storeId || codeValue.includes(storeId.toLowerCase());
  99. const warehouseMatch = !warehouse || codeValue.includes(warehouse.toLowerCase());
  100. const areaMatch = !area || codeValue.includes(area.toLowerCase());
  101. const slotMatch = !slot || codeValue.includes(slot.toLowerCase());
  102. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  103. }
  104. return true;
  105. });
  106. } else {
  107. results = warehouses;
  108. }
  109. setFilteredWarehouse(results);
  110. setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
  111. } catch (error) {
  112. console.error("Error searching warehouses:", error);
  113. const storeId = searchInputs.store_id?.trim().toLowerCase() || "";
  114. const warehouse = searchInputs.warehouse?.trim().toLowerCase() || "";
  115. const area = searchInputs.area?.trim().toLowerCase() || "";
  116. const slot = searchInputs.slot?.trim().toLowerCase() || "";
  117. const stockTakeSection = searchInputs.stockTakeSection?.trim().toLowerCase() || "";
  118. setFilteredWarehouse(
  119. warehouses.filter((warehouseItem) => {
  120. if (stockTakeSection) {
  121. const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
  122. if (!itemStockTakeSection.includes(stockTakeSection)) {
  123. return false;
  124. }
  125. }
  126. if (storeId || warehouse || area || slot) {
  127. if (!warehouseItem.code) {
  128. return false;
  129. }
  130. const codeValue = String(warehouseItem.code).toLowerCase();
  131. const codeParts = codeValue.split("-");
  132. if (codeParts.length >= 4) {
  133. const storeIdMatch = !storeId || codeParts[0].includes(storeId);
  134. const warehouseMatch = !warehouse || codeParts[1].includes(warehouse);
  135. const areaMatch = !area || codeParts[2].includes(area);
  136. const slotMatch = !slot || codeParts[3].includes(slot);
  137. return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
  138. }
  139. return (!storeId || codeValue.includes(storeId)) &&
  140. (!warehouse || codeValue.includes(warehouse)) &&
  141. (!area || codeValue.includes(area)) &&
  142. (!slot || codeValue.includes(slot));
  143. }
  144. return true;
  145. })
  146. );
  147. } finally {
  148. setIsSearching(false);
  149. }
  150. }, [searchInputs, warehouses, pagingController.pageSize]);
  151. const columns = useMemo<Column<WarehouseResult>[]>(
  152. () => [
  153. {
  154. name: "code",
  155. label: t("code"),
  156. align: "left",
  157. headerAlign: "left",
  158. sx: { width: "15%", minWidth: "120px" },
  159. },
  160. {
  161. name: "store_id",
  162. label: t("store_id"),
  163. align: "left",
  164. headerAlign: "left",
  165. sx: { width: "15%", minWidth: "120px" },
  166. },
  167. {
  168. name: "warehouse",
  169. label: t("warehouse"),
  170. align: "left",
  171. headerAlign: "left",
  172. sx: { width: "15%", minWidth: "120px" },
  173. },
  174. {
  175. name: "area",
  176. label: t("area"),
  177. align: "left",
  178. headerAlign: "left",
  179. sx: { width: "15%", minWidth: "120px" },
  180. },
  181. {
  182. name: "slot",
  183. label: t("slot"),
  184. align: "left",
  185. headerAlign: "left",
  186. sx: { width: "15%", minWidth: "120px" },
  187. },
  188. {
  189. name: "order",
  190. label: t("order"),
  191. align: "left",
  192. headerAlign: "left",
  193. sx: { width: "15%", minWidth: "120px" },
  194. },
  195. {
  196. name: "stockTakeSection",
  197. label: t("stockTakeSection"),
  198. align: "left",
  199. headerAlign: "left",
  200. sx: { width: "15%", minWidth: "120px" },
  201. },
  202. {
  203. name: "action",
  204. label: t("Delete"),
  205. onClick: onDeleteClick,
  206. buttonIcon: <DeleteIcon />,
  207. color: "error",
  208. sx: { width: "10%", minWidth: "80px" },
  209. },
  210. ],
  211. [t, onDeleteClick],
  212. );
  213. return (
  214. <>
  215. <Card>
  216. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  217. <Typography variant="overline">{t("Search Criteria")}</Typography>
  218. <Box
  219. sx={{
  220. display: "flex",
  221. alignItems: "center",
  222. gap: 1,
  223. flexWrap: "nowrap",
  224. justifyContent: "flex-start",
  225. }}
  226. >
  227. <TextField
  228. label={t("store_id")}
  229. value={searchInputs.store_id}
  230. onChange={(e) =>
  231. setSearchInputs((prev) => ({ ...prev, store_id: e.target.value }))
  232. }
  233. size="small"
  234. sx={{ width: "150px", minWidth: "120px" }}
  235. InputProps={{
  236. endAdornment: (
  237. <InputAdornment position="end">F</InputAdornment>
  238. ),
  239. }}
  240. />
  241. <Typography variant="body1" sx={{ mx: 0.5 }}>
  242. -
  243. </Typography>
  244. <TextField
  245. label={t("warehouse")}
  246. value={searchInputs.warehouse}
  247. onChange={(e) =>
  248. setSearchInputs((prev) => ({ ...prev, warehouse: e.target.value }))
  249. }
  250. size="small"
  251. sx={{ width: "150px", minWidth: "120px" }}
  252. />
  253. <Typography variant="body1" sx={{ mx: 0.5 }}>
  254. -
  255. </Typography>
  256. <TextField
  257. label={t("area")}
  258. value={searchInputs.area}
  259. onChange={(e) =>
  260. setSearchInputs((prev) => ({ ...prev, area: e.target.value }))
  261. }
  262. size="small"
  263. sx={{ width: "150px", minWidth: "120px" }}
  264. />
  265. <Typography variant="body1" sx={{ mx: 0.5 }}>
  266. -
  267. </Typography>
  268. <TextField
  269. label={t("slot")}
  270. value={searchInputs.slot}
  271. onChange={(e) =>
  272. setSearchInputs((prev) => ({ ...prev, slot: e.target.value }))
  273. }
  274. size="small"
  275. sx={{ width: "150px", minWidth: "120px" }}
  276. />
  277. <Box sx={{ flex: 1, minWidth: "150px", ml: 2 }}>
  278. <TextField
  279. label={t("stockTakeSection")}
  280. value={searchInputs.stockTakeSection}
  281. onChange={(e) =>
  282. setSearchInputs((prev) => ({ ...prev, stockTakeSection: e.target.value }))
  283. }
  284. size="small"
  285. fullWidth
  286. />
  287. </Box>
  288. </Box>
  289. <CardActions sx={{ justifyContent: "flex-start", px: 0, pt: 1 }}>
  290. <Button
  291. variant="text"
  292. startIcon={<RestartAlt />}
  293. onClick={handleReset}
  294. >
  295. {t("Reset")}
  296. </Button>
  297. <Button
  298. variant="outlined"
  299. startIcon={<Search />}
  300. onClick={handleSearch}
  301. >
  302. {t("Search")}
  303. </Button>
  304. </CardActions>
  305. </CardContent>
  306. </Card>
  307. <SearchResults<WarehouseResult>
  308. items={filteredWarehouse}
  309. columns={columns}
  310. pagingController={pagingController}
  311. setPagingController={setPagingController}
  312. />
  313. </>
  314. );
  315. };
  316. export default WarehouseHandle;