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.
 
 
 

306 satır
9.7 KiB

  1. "use client";
  2. import { PoResult } from "@/app/api/po";
  3. import { useCallback, useEffect, useMemo, useState } from "react";
  4. import { useTranslation } from "react-i18next";
  5. import { useRouter, useSearchParams } from "next/navigation";
  6. import SearchBox, { Criterion } from "../SearchBox";
  7. import SearchResults, { Column } from "../SearchResults";
  8. import { EditNote } from "@mui/icons-material";
  9. import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material";
  10. import QrModal from "../PoDetail/QrModal";
  11. import { WarehouseResult } from "@/app/api/warehouse";
  12. import NotificationIcon from "@mui/icons-material/NotificationImportant";
  13. import { useSession } from "next-auth/react";
  14. import { defaultPagingController } from "../SearchResults/SearchResults";
  15. import { fetchPoListClient, testing } from "@/app/api/po/actions";
  16. import dayjs from "dayjs";
  17. import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  18. import arraySupport from "dayjs/plugin/arraySupport";
  19. import { Checkbox, Box } from "@mui/material";
  20. dayjs.extend(arraySupport);
  21. type Props = {
  22. po: PoResult[];
  23. warehouse: WarehouseResult[];
  24. totalCount: number;
  25. };
  26. type SearchQuery = Partial<Omit<PoResult, "id">>;
  27. type SearchParamNames = keyof SearchQuery;
  28. // cal offset (pageSize)
  29. // cal limit (pageSize)
  30. const PoSearch: React.FC<Props> = ({
  31. po,
  32. warehouse,
  33. totalCount: initTotalCount,
  34. }) => {
  35. const [selectedPoIds, setSelectedPoIds] = useState<number[]>([]);
  36. const [selectAll, setSelectAll] = useState(false);
  37. const [filteredPo, setFilteredPo] = useState<PoResult[]>(po);
  38. const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
  39. const { t } = useTranslation("purchaseOrder");
  40. const router = useRouter();
  41. const [pagingController, setPagingController] = useState(
  42. defaultPagingController,
  43. );
  44. const [totalCount, setTotalCount] = useState(initTotalCount);
  45. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => {
  46. const searchCriteria: Criterion<SearchParamNames>[] = [
  47. { label: t("Supplier"), paramName: "supplier", type: "text" },
  48. { label: t("Po No."), paramName: "code", type: "text" },
  49. {
  50. label: t("Escalated"),
  51. paramName: "escalated",
  52. type: "select",
  53. options: [t("Escalated"), t("NotEscalated")],
  54. },
  55. { label: t("Order Date"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange" },
  56. {
  57. label: t("Status"),
  58. paramName: "status",
  59. type: "select-labelled",
  60. options: [
  61. { label: t(`pending`), value: `pending` },
  62. { label: t(`receiving`), value: `receiving` },
  63. { label: t(`completed`), value: `completed` },
  64. ],
  65. },
  66. { label: t("ETA"), label2: t("ETA To"), paramName: "estimatedArrivalDate", type: "dateRange" },
  67. ];
  68. return searchCriteria;
  69. }, [t]);
  70. const onDetailClick = useCallback(
  71. (po: PoResult) => {
  72. setSelectedPoIds([]);
  73. setSelectAll(false);
  74. router.push(`/po/edit?id=${po.id}&start=true&selectedIds=${po.id}`);
  75. },
  76. [router],
  77. );
  78. const onDeleteClick = useCallback((po: PoResult) => {}, []);
  79. // handle single checkbox selection
  80. const handleSelectPo = useCallback((poId: number, checked: boolean) => {
  81. if (checked) {
  82. setSelectedPoIds(prev => [...prev, poId]);
  83. } else {
  84. setSelectedPoIds(prev => prev.filter(id => id !== poId));
  85. }
  86. }, []);
  87. // 处理全选
  88. const handleSelectAll = useCallback((checked: boolean) => {
  89. if (checked) {
  90. setSelectedPoIds(filteredPo.map(po => po.id));
  91. setSelectAll(true);
  92. } else {
  93. setSelectedPoIds([]);
  94. setSelectAll(false);
  95. }
  96. }, [filteredPo]);
  97. // navigate to PoDetail page
  98. const handleGoToPoDetail = useCallback(() => {
  99. if (selectedPoIds.length > 0) {
  100. const selectedIdsParam = selectedPoIds.join(',');
  101. const firstPoId = selectedPoIds[0];
  102. router.push(`/po/edit?id=${firstPoId}&start=true&selectedIds=${selectedIdsParam}`);
  103. }
  104. }, [selectedPoIds, router]);
  105. const columns = useMemo<Column<PoResult>[]>(
  106. () => [
  107. {
  108. name: "id" as keyof PoResult,
  109. label: "Select",
  110. renderCell: (params) => (
  111. <Checkbox
  112. checked={selectedPoIds.includes(params.id)}
  113. onChange={(e) => handleSelectPo(params.id, e.target.checked)}
  114. onClick={(e) => e.stopPropagation()}
  115. />
  116. ),
  117. width: 60,
  118. },
  119. {
  120. name: "id",
  121. label: t("Details"),
  122. onClick: onDetailClick,
  123. buttonIcon: <EditNote />,
  124. },
  125. {
  126. name: "code",
  127. label: `${t("Po No.")} &\n${t("Supplier")}`,
  128. renderCell: (params) => {
  129. return <>{params.code}<br/>{params.supplier}</>
  130. },
  131. },
  132. {
  133. name: "orderDate",
  134. label: `${t("Order Date")} &\n${t("ETA")}`,
  135. renderCell: (params) => {
  136. // return (
  137. // dayjs(params.estimatedArrivalDate)
  138. // .add(-1, "month")
  139. // .format(OUTPUT_DATE_FORMAT)
  140. // );
  141. return <>{arrayToDateString(params.orderDate)}<br/>{arrayToDateString(params.estimatedArrivalDate)}</>
  142. },
  143. },
  144. {
  145. name: "itemDetail",
  146. label: t("Item Detail"),
  147. renderCell: (params) => {
  148. if (!params.itemDetail) {
  149. return "N/A"
  150. }
  151. const items = params.itemDetail.split(",")
  152. return items.map((item) => <Grid key={item}>{item}</Grid>)
  153. },
  154. },
  155. {
  156. name: "status",
  157. label: t("Status"),
  158. renderCell: (params) => {
  159. return t(`${params.status.toLowerCase()}`);
  160. },
  161. },
  162. {
  163. name: "escalated",
  164. label: t("Escalated"),
  165. renderCell: (params) => {
  166. console.log(params.escalated);
  167. return params.escalated ? (
  168. <NotificationIcon color="warning" />
  169. ) : undefined;
  170. },
  171. },
  172. ],
  173. [selectedPoIds, handleSelectPo, onDetailClick, t], // only keep necessary dependencies
  174. );
  175. const onReset = useCallback(() => {
  176. setFilteredPo(po);
  177. }, [po]);
  178. const [isOpenScanner, setOpenScanner] = useState(false);
  179. const onOpenScanner = useCallback(() => {
  180. setOpenScanner(true);
  181. }, []);
  182. const onCloseScanner = useCallback(() => {
  183. setOpenScanner(false);
  184. }, []);
  185. const newPageFetch = useCallback(
  186. async (
  187. pagingController: Record<string, number>,
  188. filterArgs: Record<string, number>,
  189. ) => {
  190. console.log(pagingController);
  191. console.log(filterArgs);
  192. const params = {
  193. ...pagingController,
  194. ...filterArgs,
  195. };
  196. const res = await fetchPoListClient(params);
  197. // const res = await testing(params);
  198. if (res) {
  199. console.log(res);
  200. setFilteredPo(res.records);
  201. setTotalCount(res.total);
  202. }
  203. },
  204. [],
  205. );
  206. useEffect(() => {
  207. console.log(filteredPo)
  208. }, [filteredPo])
  209. useEffect(() => {
  210. newPageFetch(pagingController, filterArgs);
  211. }, [newPageFetch, pagingController, filterArgs]);
  212. // when filteredPo changes, update select all state
  213. useEffect(() => {
  214. if (filteredPo.length > 0 && selectedPoIds.length === filteredPo.length) {
  215. setSelectAll(true);
  216. } else {
  217. setSelectAll(false);
  218. }
  219. }, [filteredPo, selectedPoIds]);
  220. return (
  221. <>
  222. <Grid container>
  223. <Grid item xs={8}>
  224. <Typography variant="h4" marginInlineEnd={2}>
  225. {t("Purchase Receipt")}
  226. </Typography>
  227. </Grid>
  228. <Grid item xs={4} display="flex" justifyContent="end" alignItems="end">
  229. <QrModal
  230. open={isOpenScanner}
  231. onClose={onCloseScanner}
  232. warehouse={warehouse}
  233. />
  234. <Button onClick={onOpenScanner}>{t("bind")}</Button>
  235. </Grid>
  236. </Grid>
  237. <>
  238. <SearchBox
  239. criteria={searchCriteria}
  240. onSearch={(query) => {
  241. console.log(query);
  242. setFilterArgs({
  243. code: query.code,
  244. supplier: query.supplier,
  245. status: query.status === "All" ? "" : query.status,
  246. escalated:
  247. query.escalated === "All"
  248. ? undefined
  249. : query.escalated === t("Escalated"),
  250. estimatedArrivalDate: query.estimatedArrivalDate === "Invalid Date" ? "" : query.estimatedArrivalDate,
  251. estimatedArrivalDateTo: query.estimatedArrivalDateTo === "Invalid Date" ? "" : query.estimatedArrivalDateTo,
  252. orderDate: query.orderDate === "Invalid Date" ? "" : query.orderDate,
  253. orderDateTo: query.orderDateTo === "Invalid Date" ? "" : query.orderDateTo,
  254. });
  255. setSelectedPoIds([]); // reset selected po ids
  256. setSelectAll(false); // reset select all
  257. }}
  258. onReset={onReset}
  259. />
  260. <SearchResults<PoResult>
  261. items={filteredPo}
  262. columns={columns}
  263. pagingController={pagingController}
  264. setPagingController={setPagingController}
  265. totalCount={totalCount}
  266. isAutoPaging={false}
  267. />
  268. {/* add select all and view selected button */}
  269. <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
  270. <Button
  271. variant="outlined"
  272. onClick={() => handleSelectAll(!selectAll)}
  273. startIcon={<Checkbox checked={selectAll} />}
  274. >
  275. {t("Select All")} ({selectedPoIds.length} / {filteredPo.length})
  276. </Button>
  277. <Button
  278. variant="contained"
  279. onClick={handleGoToPoDetail}
  280. disabled={selectedPoIds.length === 0}
  281. color="primary"
  282. >
  283. {t("View Selected")} ({selectedPoIds.length})
  284. </Button>
  285. </Box>
  286. </>
  287. </>
  288. );
  289. };
  290. export default PoSearch;