FPSMS-frontend
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

522 linhas
17 KiB

  1. "use client";
  2. import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions";
  3. import {
  4. Box,
  5. Card,
  6. CardContent,
  7. Checkbox,
  8. FormControl,
  9. FormControlLabel,
  10. Grid,
  11. Radio,
  12. RadioGroup,
  13. Stack,
  14. Tab,
  15. Tabs,
  16. TabsProps,
  17. TextField,
  18. Tooltip,
  19. Typography,
  20. } from "@mui/material";
  21. import { useFormContext, Controller } from "react-hook-form";
  22. import { useTranslation } from "react-i18next";
  23. import StyledDataGrid from "../StyledDataGrid";
  24. import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
  25. import {
  26. GridColDef,
  27. GridRowIdGetter,
  28. GridRowModel,
  29. useGridApiContext,
  30. GridRenderCellParams,
  31. GridRenderEditCellParams,
  32. useGridApiRef,
  33. GridRowSelectionModel,
  34. } from "@mui/x-data-grid";
  35. import InputDataGrid from "../InputDataGrid";
  36. import { TableRow } from "../InputDataGrid/InputDataGrid";
  37. import TwoLineCell from "./TwoLineCell";
  38. import QcSelect from "./QcSelect";
  39. import { GridEditInputCell } from "@mui/x-data-grid";
  40. import { StockInLine } from "@/app/api/po";
  41. import { stockInLineStatusMap } from "@/app/utils/formatUtil";
  42. import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions";
  43. import { QcItemWithChecks, QcData } from "@/app/api/qc";
  44. import axios from "@/app/(main)/axios/axiosInstance";
  45. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  46. import axiosInstance from "@/app/(main)/axios/axiosInstance";
  47. import EscalationComponent from "./EscalationComponent";
  48. import QcDataGrid from "./QCDatagrid";
  49. import StockInFormVer2 from "./StockInFormVer2";
  50. import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate";
  51. import { ModalFormInput } from "@/app/api/po/actions";
  52. import { escape } from "lodash";
  53. import { PanoramaSharp } from "@mui/icons-material";
  54. import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable";
  55. import { EscalationResult } from "@/app/api/escalation";
  56. interface Props {
  57. itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
  58. qc: QcItemWithChecks[];
  59. disabled: boolean;
  60. qcItems: QcData[]
  61. setQcItems: Dispatch<SetStateAction<QcData[]>>
  62. }
  63. type EntryError =
  64. | {
  65. [field in keyof QcData]?: string;
  66. }
  67. | undefined;
  68. type QcRow = TableRow<Partial<QcData>, EntryError>;
  69. // fetchQcItemCheck
  70. const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcItems }) => {
  71. const { t } = useTranslation("purchaseOrder");
  72. const apiRef = useGridApiRef();
  73. const {
  74. register,
  75. formState: { errors, defaultValues, touchedFields },
  76. watch,
  77. control,
  78. setValue,
  79. getValues,
  80. reset,
  81. resetField,
  82. setError,
  83. clearErrors,
  84. } = useFormContext<PurchaseQCInput>();
  85. const [tabIndex, setTabIndex] = useState(0);
  86. const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>();
  87. const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory);
  88. // const [qcResult, setQcResult] = useState();
  89. const qcAccept = watch("qcAccept");
  90. const qcDecision = watch("qcDecision"); //WIP
  91. const qcResult = watch("qcResult");
  92. console.log(qcResult);
  93. // const [qcAccept, setQcAccept] = useState(true);
  94. // const [qcItems, setQcItems] = useState(dummyQCData)
  95. const column = useMemo<GridColDef[]>(
  96. () => [
  97. {
  98. field: "escalation",
  99. headerName: t("escalation"),
  100. flex: 1,
  101. },
  102. {
  103. field: "supervisor",
  104. headerName: t("supervisor"),
  105. flex: 1,
  106. },
  107. ], []
  108. )
  109. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  110. (_e, newValue) => {
  111. setTabIndex(newValue);
  112. },
  113. [],
  114. );
  115. //// validate form
  116. const accQty = watch("acceptQty");
  117. const validateForm = useCallback(() => {
  118. if (qcDecision == 1) {
  119. if (accQty > itemDetail.acceptedQty) {
  120. setError("acceptQty", {
  121. message: `${t("acceptQty must not greater than")} ${
  122. itemDetail.acceptedQty
  123. }`,
  124. type: "required",
  125. });
  126. }
  127. if (accQty < 1) {
  128. setError("acceptQty", {
  129. message: t("minimal value is 1"),
  130. type: "required",
  131. });
  132. }
  133. if (isNaN(accQty)) {
  134. setError("acceptQty", {
  135. message: t("value must be a number"),
  136. type: "required",
  137. });
  138. }
  139. }
  140. }, [accQty, qcDecision]);
  141. useEffect(() => {
  142. clearErrors();
  143. validateForm();
  144. }, [clearErrors, validateForm]);
  145. const columns = useMemo<GridColDef[]>(
  146. () => [
  147. {
  148. field: "escalation",
  149. headerName: t("escalation"),
  150. flex: 1,
  151. },
  152. {
  153. field: "supervisor",
  154. headerName: t("supervisor"),
  155. flex: 1,
  156. },
  157. ],
  158. [],
  159. );
  160. /// validate datagrid
  161. const validation = useCallback(
  162. (newRow: GridRowModel<QcRow>): EntryError => {
  163. const error: EntryError = {};
  164. // const { qcItemId, failQty } = newRow;
  165. return Object.keys(error).length > 0 ? error : undefined;
  166. },
  167. [],
  168. );
  169. function BooleanEditCell(params: GridRenderEditCellParams) {
  170. const apiRef = useGridApiContext();
  171. const { id, field, value } = params;
  172. const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  173. apiRef.current.setEditCellValue({ id, field, value: e.target.checked });
  174. apiRef.current.stopCellEditMode({ id, field }); // commit immediately
  175. };
  176. return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />;
  177. }
  178. const qcColumns: GridColDef[] = [
  179. {
  180. field: "code",
  181. headerName: t("qcItem"),
  182. flex: 2,
  183. renderCell: (params) => (
  184. <Box>
  185. <b>{`${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}. ${params.value}`}</b><br/>
  186. {params.row.name}<br/>
  187. </Box>
  188. ),
  189. },
  190. {
  191. field: 'qcPassed',
  192. headerName: t("qcResult"),
  193. flex: 1.5,
  194. renderCell: (params) => {
  195. const currentValue = params.row;
  196. const index = params.api.getRowIndexRelativeToVisibleRows(params.id);
  197. // console.log(currentValue.row);
  198. return (
  199. <FormControl>
  200. <RadioGroup
  201. row
  202. aria-labelledby="demo-radio-buttons-group-label"
  203. value={currentValue.qcPassed === undefined ? "" : (currentValue.qcPassed ? "true" : "false")}
  204. // value={currentValue.qcPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.qcPassed ? "true" : "false")}
  205. onChange={(e) => {
  206. const value = e.target.value;
  207. setQcItems((prev) =>
  208. prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
  209. );
  210. // setValue(`qcResult.${index}.qcPassed`, value == "true");
  211. }}
  212. name={`qcPassed-${params.id}`}
  213. >
  214. <FormControlLabel
  215. value="true"
  216. control={<Radio />}
  217. label="合格"
  218. disabled={disabled || itemDetail.status == "escalated"}
  219. sx={{
  220. color: currentValue.qcPassed === true ? "green" : "inherit",
  221. "& .Mui-checked": {color: "green"}
  222. }}
  223. />
  224. <FormControlLabel
  225. value="false"
  226. control={<Radio />}
  227. label="不合格"
  228. disabled={disabled || itemDetail.status == "escalated"}
  229. sx={{
  230. color: currentValue.qcPassed === false ? "red" : "inherit",
  231. "& .Mui-checked": {color: "red"}
  232. }}
  233. />
  234. </RadioGroup>
  235. </FormControl>
  236. );
  237. },
  238. },
  239. {
  240. field: "failQty",
  241. headerName: t("failedQty"),
  242. flex: 1,
  243. // editable: true,
  244. renderCell: (params) => (
  245. <TextField
  246. type="number"
  247. size="small"
  248. value={!params.row.qcPassed? (params.value ?? '') : '0'}
  249. disabled={params.row.qcPassed || disabled || itemDetail.status == "escalated"}
  250. onChange={(e) => {
  251. const v = e.target.value;
  252. const next = v === '' ? undefined : Number(v);
  253. if (Number.isNaN(next)) return;
  254. setQcItems((prev) =>
  255. prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r))
  256. );
  257. // setValue(`failQty`,failQty);
  258. }}
  259. onClick={(e) => e.stopPropagation()}
  260. onMouseDown={(e) => e.stopPropagation()}
  261. onKeyDown={(e) => e.stopPropagation()}
  262. inputProps={{ min: 0 }}
  263. sx={{ width: '100%' }}
  264. />
  265. ),
  266. },
  267. {
  268. field: "remarks",
  269. headerName: t("remarks"),
  270. flex: 2,
  271. renderCell: (params) => (
  272. <TextField
  273. size="small"
  274. value={params.value ?? ''}
  275. disabled={disabled || itemDetail.status == "escalated"}
  276. onChange={(e) => {
  277. const remarks = e.target.value;
  278. // const next = v === '' ? undefined : Number(v);
  279. // if (Number.isNaN(next)) return;
  280. setQcItems((prev) =>
  281. prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
  282. );
  283. }}
  284. // {...register(`qcResult.${params.row.rowIndex}.remarks`, {
  285. // required: "remarks required!",
  286. // })}
  287. onClick={(e) => e.stopPropagation()}
  288. onMouseDown={(e) => e.stopPropagation()}
  289. onKeyDown={(e) => e.stopPropagation()}
  290. inputProps={{ min: 0 }}
  291. sx={{ width: '100%' }}
  292. />
  293. ),
  294. },
  295. ]
  296. // Set initial value for acceptQty
  297. useEffect(() => {
  298. if (itemDetail?.demandQty > 0) { //!== undefined) {
  299. setValue("acceptQty", itemDetail.demandQty); // THIS NEED TO UPDATE TO NOT USE DEMAND QTY
  300. } else {
  301. setValue("acceptQty", itemDetail?.acceptedQty);
  302. }
  303. }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]);
  304. // const [openCollapse, setOpenCollapse] = useState(false)
  305. const [isCollapsed, setIsCollapsed] = useState<boolean>(true);
  306. const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => {
  307. const isFailed = qcItems.some((qc) => !qc.qcPassed)
  308. console.log(isFailed)
  309. if (isFailed) {
  310. setIsCollapsed(true)
  311. } else {
  312. setIsCollapsed(false)
  313. }
  314. }, [])
  315. // const handleRadioChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  316. // const value = event.target.value === 'true';
  317. // setValue("qcAccept", value);
  318. // }, [setValue]);
  319. useEffect(() => {
  320. console.log("ItemDetail in QC:", itemDetail);
  321. console.log("Qc Result in QC:", qcResult);
  322. }, [itemDetail]);
  323. const setQcDecision = (status : string | undefined) => {
  324. const param = status?.toLowerCase();
  325. if (param !== undefined && param !== null) {
  326. if (param == "completed") {
  327. return 1;
  328. } else if (param == "rejected") {
  329. return 2;
  330. } else if (param == "escalated") {
  331. return 3;
  332. } else { return undefined; }
  333. } else {
  334. return undefined;
  335. }
  336. }
  337. useEffect(() => {
  338. // onFailedOpenCollapse(qcItems) // This function is no longer needed
  339. }, [qcItems]);
  340. return (
  341. <>
  342. <Grid container justifyContent="flex-start" alignItems="flex-start">
  343. <Grid
  344. container
  345. justifyContent="flex-start"
  346. alignItems="flex-start"
  347. spacing={2}
  348. sx={{ mt: 0.5 }}
  349. >
  350. <Grid item xs={12}>
  351. <Tabs
  352. value={tabIndex}
  353. onChange={handleTabChange}
  354. variant="scrollable"
  355. >
  356. <Tab label={t("QC Info")} iconPosition="end" />
  357. <Tab label={t("Escalation History")} iconPosition="end" />
  358. </Tabs>
  359. </Grid>
  360. {tabIndex == 0 && (
  361. <>
  362. <Grid item xs={12}>
  363. <Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
  364. <Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
  365. Group A - 急凍貨類 (QCA1-MEAT01)
  366. </Typography>
  367. <Typography variant="subtitle1" sx={{ color: '#666' }}>
  368. <b>品檢類型</b>:IQC
  369. </Typography>
  370. <Typography variant="subtitle2" sx={{ color: '#666' }}>
  371. 記錄探測溫度的時間,請在1小時内完成卸貨盤點入庫,以保障食品安全<br/>
  372. 監察方法:目視檢查、嗅覺檢查和使用適當的食物溫度計,檢查食物溫度是否符合指標
  373. </Typography>
  374. </Box>
  375. {/* <QcDataGrid<ModalFormInput, QcData, EntryError>
  376. apiRef={apiRef}
  377. columns={qcColumns}
  378. _formKey="qcResult"
  379. validateRow={validation}
  380. /> */}
  381. <StyledDataGrid
  382. columns={qcColumns}
  383. rows={qcResult && qcResult.length > 0 ? qcResult : qcItems}
  384. // rows={disabled? qcResult:qcItems}
  385. autoHeight
  386. />
  387. </Grid>
  388. </>
  389. )}
  390. {tabIndex == 1 && (
  391. <>
  392. {/* <Grid item xs={12}>
  393. <StockInFormVer2
  394. itemDetail={itemDetail}
  395. disabled={false}
  396. />
  397. </Grid> */}
  398. {/* <Grid item xs={12}>
  399. <Typography variant="h6" display="block" marginBlockEnd={1}>
  400. {t("Escalation Info")}
  401. </Typography>
  402. </Grid> */}
  403. <Grid item xs={12}>
  404. <EscalationLogTable items={itemDetail.escResult || []}/>
  405. {/* <StyledDataGrid
  406. rows={escalationHistory}
  407. columns={columns}
  408. onRowSelectionModelChange={(newRowSelectionModel) => {
  409. setRowSelectionModel(newRowSelectionModel);
  410. }}
  411. /> */}
  412. </Grid>
  413. </>
  414. )}
  415. <Grid item xs={12}>
  416. <FormControl>
  417. <Controller
  418. name="qcDecision"
  419. // name="qcAccept"
  420. control={control}
  421. defaultValue={setQcDecision(itemDetail?.status)}
  422. // defaultValue={true}
  423. render={({ field }) => (
  424. <RadioGroup
  425. row
  426. aria-labelledby="demo-radio-buttons-group-label"
  427. {...field}
  428. value={field.value}
  429. // value={field.value?.toString() || "true"}
  430. onChange={(e) => {
  431. const value = e.target.value.toString();// === 'true';
  432. if (value != "1" && Boolean(errors.acceptQty)) {
  433. // if (!value && Boolean(errors.acceptQty)) {
  434. setValue("acceptQty", itemDetail.acceptedQty ?? 0);
  435. }
  436. field.onChange(value);
  437. }}
  438. >
  439. <FormControlLabel disabled={disabled}
  440. value="1" control={<Radio />} label="接受" />
  441. <Box sx={{mr:2}}>
  442. <TextField
  443. type="number"
  444. label={t("acceptQty")}
  445. sx={{ width: '150px' }}
  446. value={(qcDecision == 1)? accQty : 0 }
  447. // value={qcAccept? accQty : 0 }
  448. disabled={qcDecision != 1 || disabled}
  449. // disabled={!qcAccept || disabled}
  450. {...register("acceptQty", {
  451. required: "acceptQty required!",
  452. })}
  453. error={Boolean(errors.acceptQty)}
  454. helperText={errors.acceptQty?.message}
  455. />
  456. </Box>
  457. <FormControlLabel disabled={disabled}
  458. value="2" control={<Radio />}
  459. sx={{"& .Mui-checked": {color: "red"}}}
  460. label="不接受" />
  461. <FormControlLabel disabled={disabled}
  462. value="3" control={<Radio />}
  463. sx={{"& .Mui-checked": {color: "blue"}}}
  464. label="上報品檢結果" />
  465. </RadioGroup>
  466. )}
  467. />
  468. </FormControl>
  469. </Grid>
  470. {qcDecision == 3 && (
  471. // {!qcAccept && (
  472. <Grid item xs={12}>
  473. <EscalationComponent
  474. forSupervisor={false}
  475. isCollapsed={isCollapsed}
  476. setIsCollapsed={setIsCollapsed}
  477. />
  478. </Grid>)}
  479. {/* {qcAccept && <Grid item xs={12}>
  480. <Typography variant="h6" display="block" marginBlockEnd={1}>
  481. {t("Escalation Result")}
  482. </Typography>
  483. </Grid>
  484. <Grid item xs={12}>
  485. <EscalationComponent
  486. forSupervisor={true}
  487. isCollapsed={isCollapsed}
  488. setIsCollapsed={setIsCollapsed}
  489. />
  490. </Grid>} */}
  491. </Grid>
  492. </Grid>
  493. </>
  494. );
  495. };
  496. export default QcFormVer2;