FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 

290 righe
7.9 KiB

  1. "use client";
  2. import React from "react";
  3. import { X, Save } from "@mui/icons-material";
  4. import { useTranslation } from "react-i18next";
  5. import { useState } from "react";
  6. import {
  7. Dialog,
  8. DialogTitle,
  9. DialogContent,
  10. DialogActions,
  11. IconButton,
  12. Typography,
  13. TextField,
  14. Stack,
  15. Box,
  16. Button,
  17. } from "@mui/material";
  18. import { useForm, Controller } from "react-hook-form";
  19. import { Material, FormData, ProductionRecord, ProcessData } from "./types";
  20. import { Operator, Machine } from "@/app/api/jo";
  21. import OperatorScanner from "./OperatorScanner";
  22. import MachineScanner from "./MachineScanner";
  23. import MaterialLotScanner from "./MaterialLotScanner";
  24. import dayjs from 'dayjs';
  25. import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  26. interface ProductionRecordingModalProps {
  27. isOpen: boolean;
  28. onClose: () => void;
  29. jobProcess: ProcessData;
  30. onDataRecorded?: (processId: string, data: any) => void;
  31. }
  32. const ProductionRecordingModal: React.FC<ProductionRecordingModalProps> = ({
  33. isOpen,
  34. onClose,
  35. jobProcess,
  36. }) => {
  37. const { t } = useTranslation("common");
  38. const {
  39. control,
  40. handleSubmit,
  41. reset,
  42. watch,
  43. setValue,
  44. setError,
  45. clearErrors,
  46. formState: { errors },
  47. } = useForm<FormData>({
  48. defaultValues: {
  49. productionDate: dayjs().format(INPUT_DATE_FORMAT),
  50. notes: "",
  51. operators: [],
  52. machines: [],
  53. materials: jobProcess.materials || [],
  54. },
  55. });
  56. const watchedOperators = watch("operators");
  57. const watchedMachines = watch("machines");
  58. const watchedMaterials = watch("materials");
  59. const [activeScannerType, setActiveScannerType] = useState<'operator' | 'machine' | 'material' | null>(null);
  60. const handleScannerActivate = (scannerType: 'operator' | 'machine' | 'material') => {
  61. setActiveScannerType(scannerType);
  62. };
  63. const handleScannerDeactivate = () => {
  64. setActiveScannerType(null);
  65. };
  66. const validateForm = (): boolean => {
  67. let isValid = true;
  68. // Clear previous errors
  69. clearErrors();
  70. // Validate operators
  71. if (!watchedOperators || watchedOperators.length === 0) {
  72. setError("operators", {
  73. type: "required",
  74. message: "At least one operator is required",
  75. });
  76. isValid = false;
  77. }
  78. // Validate machines
  79. if (!watchedMachines || watchedMachines.length === 0) {
  80. setError("machines", {
  81. type: "required",
  82. message: "At least one machine is required",
  83. });
  84. isValid = false;
  85. }
  86. // Validate materials
  87. console.log(watchedMaterials);
  88. if (watchedMaterials) {
  89. const missingLotNumbers = watchedMaterials.filter(
  90. (m) =>
  91. m.required &&
  92. m.isUsed &&
  93. (!m.lotNumbers || m.lotNumbers.length === 0),
  94. ).length;
  95. if (missingLotNumbers > 0) {
  96. setError("materials", {
  97. type: "custom",
  98. message: `${missingLotNumbers} required material(s) missing lot numbers`,
  99. });
  100. isValid = false;
  101. }
  102. }
  103. return isValid;
  104. };
  105. const onSubmit = (data: FormData) => {
  106. if (!validateForm()) {
  107. return;
  108. }
  109. const usedMaterials = data.materials
  110. .filter((material) => material.isUsed)
  111. .map((material) => ({
  112. id: material.id,
  113. name: material.name,
  114. lotNumbers: material.lotNumbers,
  115. required: material.required,
  116. isUsed: false,
  117. }));
  118. const recordData: ProductionRecord = {
  119. ...data,
  120. materials: usedMaterials,
  121. timestamp: new Date().toISOString(),
  122. };
  123. console.log("Production record saved:", recordData);
  124. handleClose();
  125. };
  126. const handleClose = (): void => {
  127. reset({
  128. productionDate: dayjs().format(INPUT_DATE_FORMAT),
  129. notes: "",
  130. operators: [],
  131. machines: [],
  132. materials: jobProcess.materials.map((material) => ({
  133. ...material,
  134. isUsed: false,
  135. lotNumbers: [],
  136. })),
  137. });
  138. onClose();
  139. };
  140. const getUsedMaterialsCount = (): number =>
  141. watchedMaterials?.filter((m) => m.isUsed).length || 0;
  142. if (!isOpen) return null;
  143. return (
  144. <Dialog open={isOpen} onClose={handleClose} maxWidth="xl" fullWidth>
  145. <DialogTitle
  146. sx={{
  147. display: "flex",
  148. alignItems: "center",
  149. justifyContent: "space-between",
  150. }}
  151. >
  152. <Box>
  153. <Typography variant="h5" fontWeight={700}>
  154. Production Recording
  155. </Typography>
  156. <Typography variant="body2" color="text.secondary" mt={0.5}>
  157. {watchedOperators?.length || 0} operator(s),{" "}
  158. {watchedMachines?.length || 0} machine(s), {getUsedMaterialsCount()}{" "}
  159. materials
  160. </Typography>
  161. </Box>
  162. <IconButton onClick={handleClose} size="large">
  163. <X />
  164. </IconButton>
  165. </DialogTitle>
  166. <DialogContent dividers sx={{ p: 3 }}>
  167. <Stack spacing={4}>
  168. {/* Basic Information */}
  169. <Stack direction={{ xs: "column", md: "row" }} spacing={2}>
  170. <Controller
  171. name="productionDate"
  172. control={control}
  173. rules={{
  174. required: "Production date is required",
  175. }}
  176. render={({ field }) => (
  177. <TextField
  178. {...field}
  179. type="date"
  180. label="Production Date"
  181. InputLabelProps={{ shrink: true }}
  182. fullWidth
  183. size="small"
  184. error={!!errors.productionDate}
  185. helperText={errors.productionDate?.message}
  186. />
  187. )}
  188. />
  189. </Stack>
  190. {/* Operator Scanner */}
  191. <OperatorScanner
  192. operators={watchedOperators || []}
  193. onOperatorsChange={(operators) => {
  194. setValue("operators", operators);
  195. if (operators.length > 0) {
  196. clearErrors("operators");
  197. }
  198. }}
  199. error={errors.operators?.message}
  200. isActive={activeScannerType === 'operator'}
  201. onActivate={() => handleScannerActivate('operator')}
  202. onDeactivate={handleScannerDeactivate}
  203. />
  204. {/* Machine Scanner */}
  205. <MachineScanner
  206. machines={watchedMachines || []}
  207. onMachinesChange={(machines) => {
  208. setValue("machines", machines);
  209. if (machines.length > 0) {
  210. clearErrors("machines");
  211. }
  212. }}
  213. error={errors.machines?.message}
  214. isActive={activeScannerType === 'machine'}
  215. onActivate={() => handleScannerActivate('machine')}
  216. onDeactivate={handleScannerDeactivate}
  217. />
  218. {/* Material Lot Scanner */}
  219. <MaterialLotScanner
  220. materials={watchedMaterials || []}
  221. onMaterialsChange={(materials) => {
  222. setValue("materials", materials);
  223. clearErrors("materials");
  224. }}
  225. error={errors.materials?.message}
  226. />
  227. {/* Additional Notes */}
  228. <Controller
  229. name="notes"
  230. control={control}
  231. render={({ field }) => (
  232. <TextField
  233. {...field}
  234. label="Additional Notes"
  235. multiline
  236. minRows={3}
  237. fullWidth
  238. size="small"
  239. placeholder={t("Enter any additional production notes...")}
  240. error={!!errors.notes}
  241. helperText={errors.notes?.message}
  242. />
  243. )}
  244. />
  245. </Stack>
  246. </DialogContent>
  247. <DialogActions sx={{ p: 3 }}>
  248. <Button variant="outlined" color="inherit" onClick={handleClose}>
  249. Cancel
  250. </Button>
  251. <Button
  252. variant="contained"
  253. color="primary"
  254. onClick={handleSubmit(onSubmit)}
  255. startIcon={<Save />}
  256. >
  257. Save Record
  258. </Button>
  259. </DialogActions>
  260. </Dialog>
  261. );
  262. };
  263. export default ProductionRecordingModal;