FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

549 wiersze
17 KiB

  1. "use client";
  2. import {
  3. StockInInput,
  4. } from "@/app/api/stockIn/actions";
  5. import {
  6. Box,
  7. Card,
  8. CardContent,
  9. Grid,
  10. Stack,
  11. TextField,
  12. Tooltip,
  13. Typography,
  14. Button,
  15. } from "@mui/material";
  16. import { Controller, useFormContext } from "react-hook-form";
  17. import { useTranslation } from "react-i18next";
  18. import StyledDataGrid from "../StyledDataGrid";
  19. import { useCallback, useEffect, useMemo, useState } from "react";
  20. import {
  21. GridColDef,
  22. GridRowIdGetter,
  23. GridRowModel,
  24. useGridApiContext,
  25. GridRenderCellParams,
  26. GridRenderEditCellParams,
  27. useGridApiRef,
  28. } from "@mui/x-data-grid";
  29. import InputDataGrid from "../InputDataGrid";
  30. import { TableRow } from "../InputDataGrid/InputDataGrid";
  31. import { QcItemWithChecks } from "@/app/api/qc";
  32. import { GridEditInputCell } from "@mui/x-data-grid";
  33. import { StockInLine } from "@/app/api/stockIn";
  34. import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
  35. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  36. import { INPUT_DATE_FORMAT, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  37. import dayjs from "dayjs";
  38. import CalculateExpiryDateModal from "./CalculateExpiryDateModal";
  39. import { InputAdornment } from "@mui/material";
  40. import { dayjsToDateString } from "@/app/utils/formatUtil";
  41. // change PurchaseQcResult to stock in entry props
  42. interface Props {
  43. itemDetail: StockInLine;
  44. // qc: QcItemWithChecks[];
  45. disabled: boolean;
  46. putawayMode?: boolean;
  47. /** When true, use compact field sizing to match Putaway Detail (上架詳情) section. */
  48. compactFields?: boolean;
  49. }
  50. type EntryError =
  51. | {
  52. [field in keyof StockInInput]?: string;
  53. }
  54. | undefined;
  55. // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>;
  56. const textfieldSx = {
  57. width: "100%",
  58. "& .MuiInputBase-root": {
  59. // height: "120", // Scales with root font size
  60. height: "5rem", // Scales with root font size
  61. },
  62. "& .MuiInputBase-input": {
  63. height: "100%",
  64. boxSizing: "border-box",
  65. padding: "0.75rem",
  66. fontSize: 30,
  67. },
  68. "& .MuiInputLabel-root": {
  69. fontSize: 30,
  70. transform: "translate(14px, 1.2rem) scale(1)",
  71. "&.MuiInputLabel-shrink": {
  72. fontSize: 24,
  73. transform: "translate(14px, -0.5rem) scale(1)",
  74. },
  75. // [theme.breakpoints.down("sm")]: {
  76. // fontSize: "1rem",
  77. // transform: "translate(14px, 1.5rem) scale(1)",
  78. // "&.MuiInputLabel-shrink": {
  79. // fontSize: "0.875rem",
  80. // },
  81. // },
  82. },
  83. };
  84. const FgStockInForm: React.FC<Props> = ({
  85. // qc,
  86. itemDetail,
  87. disabled,
  88. putawayMode = false,
  89. compactFields = false,
  90. }) => {
  91. const {
  92. t,
  93. i18n: { language },
  94. } = useTranslation("purchaseOrder");
  95. const apiRef = useGridApiRef();
  96. const {
  97. register,
  98. formState: { errors, defaultValues, touchedFields },
  99. watch,
  100. control,
  101. setValue,
  102. getValues,
  103. reset,
  104. resetField,
  105. setError,
  106. clearErrors,
  107. } = useFormContext<StockInInput>();
  108. // console.log(itemDetail);
  109. useEffect(() => {
  110. // console.log("triggered");
  111. // // receiptDate default tdy
  112. // setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT));
  113. // setValue("status", "received");
  114. }, [setValue]);
  115. useEffect(() => {
  116. console.log(errors);
  117. }, [errors]);
  118. const [openModal, setOpenModal] = useState<boolean>(false);
  119. const [openExpDatePicker, setOpenExpDatePicker] = useState<boolean>(false);
  120. const productionDate = watch("productionDate");
  121. const expiryDate = watch("expiryDate");
  122. const uom = watch("uom");
  123. //// TODO : Add Checking ////
  124. // Check if dates are input
  125. // if (data.productionDate === undefined || data.productionDate == null) {
  126. // validationErrors.push("請輸入生產日期!");
  127. // }
  128. // if (data.expiryDate === undefined || data.expiryDate == null) {
  129. // validationErrors.push("請輸入到期日!");
  130. // }
  131. useEffect(() => {
  132. // console.log(uom);
  133. // console.log(productionDate);
  134. // console.log(expiryDate);
  135. if (expiryDate) clearErrors();
  136. if (productionDate) clearErrors();
  137. }, [productionDate, expiryDate, clearErrors]);
  138. useEffect(() => {
  139. console.log("%c StockInForm itemDetail update: ", "color: brown", itemDetail);
  140. }, [itemDetail]);
  141. const handleOpenModal = useCallback(() => {
  142. setOpenModal(true);
  143. }, []);
  144. const handleOnModalClose = useCallback(() => {
  145. setOpenExpDatePicker(false);
  146. setOpenModal(false);
  147. }, []);
  148. const handleReturnExpiryDate = useCallback((result: dayjs.Dayjs) => {
  149. if (result) {
  150. setValue("expiryDate", dayjsToDateString(result));
  151. }
  152. }, [setValue]);
  153. return (
  154. <>
  155. <Grid container justifyContent="flex-start" alignItems="flex-start">
  156. {/* <Grid item xs={12}>
  157. <Typography variant="h6" display="block" marginBlockEnd={1}>
  158. {t("Stock In Detail")}
  159. </Typography>
  160. </Grid> */}
  161. <Grid
  162. container
  163. justifyContent="flex-start"
  164. alignItems="flex-start"
  165. spacing={compactFields ? 1 : 2}
  166. sx={{ mt: 0.5 }}
  167. >
  168. {putawayMode && (
  169. <Grid item xs={6}>
  170. <TextField
  171. label={t("joCode")}
  172. fullWidth
  173. {...register("productLotNo", {
  174. // required: "productLotNo required!",
  175. })}
  176. sx={compactFields ? undefined : textfieldSx}
  177. disabled={true}
  178. />
  179. </Grid>
  180. )
  181. }
  182. <Grid item xs={6}>
  183. <TextField
  184. label={t("itemName")}
  185. fullWidth
  186. {...register("itemName", {
  187. // required: "productLotNo required!",
  188. })}
  189. sx={compactFields ? undefined : textfieldSx}
  190. disabled={true}
  191. // error={Boolean(errors.productLotNo)}
  192. // helperText={errors.productLotNo?.message}
  193. />
  194. </Grid>
  195. {putawayMode || (
  196. <>
  197. <Grid item xs={6}>
  198. <Controller
  199. control={control}
  200. name="receiptDate"
  201. rules={{ required: true }}
  202. render={({ field }) => {
  203. return (
  204. <LocalizationProvider
  205. dateAdapter={AdapterDayjs}
  206. adapterLocale={`${language}-hk`}
  207. >
  208. <DatePicker
  209. {...field}
  210. sx={compactFields ? { width: '100%' } : textfieldSx}
  211. label={t("receiptDate")}
  212. value={dayjs(watch("receiptDate"))}
  213. format={OUTPUT_DATE_FORMAT}
  214. disabled={true}
  215. onChange={(date) => {
  216. if (!date) return;
  217. // setValue("receiptDate", date.format(INPUT_DATE_FORMAT));
  218. field.onChange(date);
  219. }}
  220. inputRef={field.ref}
  221. slotProps={{
  222. textField: {
  223. fullWidth: true,
  224. // required: true,
  225. error: Boolean(errors.receiptDate?.message),
  226. helperText: errors.receiptDate?.message,
  227. },
  228. }}
  229. />
  230. </LocalizationProvider>
  231. );
  232. }}
  233. />
  234. </Grid>
  235. </>
  236. )}
  237. <Grid item xs={6}>
  238. {putawayMode ? (
  239. <TextField
  240. label={t("stockLotNo")}
  241. fullWidth
  242. {...register("lotNo", {
  243. // required: "productLotNo required!",
  244. })}
  245. sx={compactFields ? undefined : textfieldSx}
  246. disabled={true}
  247. error={Boolean(errors.productLotNo)}
  248. helperText={errors.productLotNo?.message}
  249. />) : (
  250. <TextField
  251. label={t("stockLotNo")}
  252. fullWidth
  253. {...register("productLotNo", {
  254. // required: "productLotNo required!",
  255. })}
  256. sx={compactFields ? undefined : textfieldSx}
  257. disabled={true}
  258. error={Boolean(errors.productLotNo)}
  259. helperText={errors.productLotNo?.message}
  260. />)
  261. }
  262. </Grid>
  263. <Grid item xs={6}>
  264. <Controller
  265. control={control}
  266. name="productionDate"
  267. render={({ field }) => {
  268. return (
  269. <LocalizationProvider
  270. dateAdapter={AdapterDayjs}
  271. adapterLocale={`${language}-hk`}
  272. >
  273. <DatePicker
  274. {...field}
  275. sx={compactFields ? { width: '100%' } : textfieldSx}
  276. label={t("productionDate")}
  277. value={productionDate ? dayjs(productionDate) : undefined}
  278. format={OUTPUT_DATE_FORMAT}
  279. disabled={disabled}
  280. onChange={(date) => {
  281. if (!date) return;
  282. setValue(
  283. "productionDate",
  284. date.format(INPUT_DATE_FORMAT),
  285. );
  286. }}
  287. inputRef={field.ref}
  288. slotProps={{
  289. textField: {
  290. fullWidth: true,
  291. error: Boolean(errors.productionDate?.message),
  292. helperText: errors.productionDate?.message,
  293. },
  294. }}
  295. />
  296. </LocalizationProvider>
  297. );
  298. }}
  299. />
  300. </Grid>
  301. {/* {putawayMode || (<>
  302. {/* {putawayMode || (<>
  303. <Grid item xs={6}>
  304. <Controller
  305. control={control}
  306. name="productionDate"
  307. // rules={{ required: !Boolean(expiryDate) }}
  308. render={({ field }) => {
  309. return (
  310. <LocalizationProvider
  311. dateAdapter={AdapterDayjs}
  312. adapterLocale={`${language}-hk`}
  313. >
  314. <DatePicker
  315. {...field}
  316. sx={compactFields ? undefined : textfieldSx}
  317. label={t("productionDate")}
  318. value={productionDate ? dayjs(productionDate) : undefined}
  319. disabled={disabled}
  320. onChange={(date) => {
  321. if (!date) return;
  322. setValue(
  323. "productionDate",
  324. date.format(INPUT_DATE_FORMAT),
  325. );
  326. // field.onChange(date);
  327. }}
  328. inputRef={field.ref}
  329. slotProps={{
  330. textField: {
  331. // required: true,
  332. error: Boolean(errors.productionDate?.message),
  333. helperText: errors.productionDate?.message,
  334. },
  335. }}
  336. />
  337. </LocalizationProvider>
  338. );
  339. }}
  340. />
  341. </Grid>
  342. <Grid item xs={6}>
  343. <TextField
  344. label={t("qty")}
  345. fullWidth
  346. {...register("qty", {
  347. required: "qty required!",
  348. })}
  349. sx={compactFields ? undefined : textfieldSx}
  350. disabled={true}
  351. />
  352. </Grid></>
  353. )} */}
  354. <Grid item xs={6}>
  355. <Controller
  356. control={control}
  357. name="expiryDate"
  358. render={({ field }) => {
  359. const expiryDateValue = watch("expiryDate");
  360. return (
  361. <LocalizationProvider
  362. dateAdapter={AdapterDayjs}
  363. adapterLocale={`${language}-hk`}
  364. >
  365. <DatePicker
  366. {...field}
  367. sx={compactFields ? { width: '100%' } : textfieldSx}
  368. label={t("expiryDate")}
  369. value={expiryDateValue ? dayjs(expiryDateValue) : null} // Use null instead of undefined
  370. format={OUTPUT_DATE_FORMAT}
  371. disabled={disabled}
  372. onChange={(date) => {
  373. if (!date) {
  374. setValue("expiryDate", "");
  375. return;
  376. }
  377. setValue("expiryDate", date.format(INPUT_DATE_FORMAT));
  378. }}
  379. inputRef={field.ref}
  380. open={openExpDatePicker && !openModal}
  381. onOpen={() => setOpenExpDatePicker(true)}
  382. onClose={() => setOpenExpDatePicker(false)}
  383. slotProps={{
  384. textField: {
  385. fullWidth: true,
  386. InputProps: {
  387. ...(!disabled && {
  388. endAdornment: (
  389. <InputAdornment position="end">
  390. <Button
  391. type="button"
  392. variant="contained"
  393. color="primary"
  394. size="small"
  395. sx={{
  396. fontSize: compactFields ? '0.875rem' : '24px',
  397. whiteSpace: 'nowrap',
  398. flexShrink: 0,
  399. }}
  400. onClick={handleOpenModal}
  401. >
  402. {t("Calculate Expiry Date")}
  403. </Button>
  404. </InputAdornment>
  405. ),
  406. })
  407. },
  408. error: Boolean(errors.expiryDate?.message),
  409. helperText: errors.expiryDate?.message,
  410. onClick: () => setOpenExpDatePicker(true),
  411. },
  412. }}
  413. />
  414. </LocalizationProvider>
  415. );
  416. }}
  417. />
  418. </Grid>
  419. {/* <Grid item xs={6}>
  420. {putawayMode ? (
  421. <TextField
  422. label={t("acceptedQty")}
  423. fullWidth
  424. sx={compactFields ? undefined : textfieldSx}
  425. disabled={true}
  426. value={itemDetail.acceptedQty}
  427. // disabled={true}
  428. // disabled={disabled}
  429. // error={Boolean(errors.acceptedQty)}
  430. // helperText={errors.acceptedQty?.message}
  431. />
  432. ) : (
  433. <TextField
  434. label={t("receivedQty")}
  435. fullWidth
  436. {...register("receivedQty", {
  437. required: "receivedQty required!",
  438. })}
  439. sx={compactFields ? undefined : textfieldSx}
  440. disabled={true}
  441. />
  442. )}
  443. </Grid> */}
  444. <Grid item xs={6}>
  445. <TextField
  446. label={t("salesUnit")}
  447. fullWidth
  448. {...register("uom.udfudesc", {
  449. required: "uom required!",
  450. })}
  451. // value={uom?.code}
  452. sx={compactFields ? undefined : textfieldSx}
  453. disabled={true}
  454. />
  455. </Grid>
  456. {putawayMode ? (<>
  457. <Grid item xs={3}>
  458. <TextField
  459. label={t("processedQty")}
  460. fullWidth
  461. sx={compactFields ? undefined : textfieldSx}
  462. disabled={true}
  463. value={itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0}
  464. />
  465. </Grid>
  466. <Grid item xs={3}>
  467. <TextField
  468. label={t("acceptedQty")}
  469. fullWidth
  470. sx={compactFields ? undefined : textfieldSx}
  471. disabled={true}
  472. {...register("acceptedQty", {
  473. required: "acceptedQty required!",
  474. })}
  475. />
  476. </Grid></>
  477. ) : (
  478. <Grid item xs={6}>
  479. <TextField
  480. label={t("acceptedQty")}
  481. fullWidth
  482. sx={compactFields ? undefined : textfieldSx}
  483. disabled={true}
  484. {...register("acceptedQty", {
  485. required: "acceptedQty required!",
  486. })}
  487. />
  488. </Grid>
  489. )}
  490. {/* <Grid item xs={4}>
  491. <TextField
  492. label={t("acceptedWeight")}
  493. fullWidth
  494. // {...register("acceptedWeight", {
  495. // required: "acceptedWeight required!",
  496. // })}
  497. disabled={disabled}
  498. error={Boolean(errors.acceptedWeight)}
  499. helperText={errors.acceptedWeight?.message}
  500. />
  501. </Grid> */}
  502. </Grid>
  503. <Grid
  504. container
  505. justifyContent="flex-start"
  506. alignItems="flex-start"
  507. spacing={2}
  508. sx={{ mt: 0.5 }}
  509. >
  510. {/* <Grid item xs={12}>
  511. <InputDataGrid<PurchaseQCInput, PurchaseQcResult, EntryError>
  512. apiRef={apiRef}
  513. checkboxSelection={false}
  514. _formKey={"qcCheck"}
  515. columns={columns}
  516. validateRow={validationTest}
  517. />
  518. </Grid> */}
  519. </Grid>
  520. </Grid>
  521. <CalculateExpiryDateModal
  522. open={openModal}
  523. onClose={handleOnModalClose}
  524. onSubmit={handleReturnExpiryDate}
  525. textfieldSx={compactFields ? undefined : textfieldSx}
  526. />
  527. </>
  528. );
  529. };
  530. export default FgStockInForm;