FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

334 lines
10 KiB

  1. "use client";
  2. import React from "react";
  3. import { X, Save } from "@mui/icons-material";
  4. import {
  5. Dialog,
  6. DialogTitle,
  7. DialogContent,
  8. DialogActions,
  9. IconButton,
  10. Typography,
  11. Button,
  12. Box,
  13. Stack,
  14. } from "@mui/material";
  15. import { useForm, Controller } from "react-hook-form";
  16. import {
  17. QualityCheckModalProps,
  18. QualityCheckData,
  19. QualityCheckRecord,
  20. QualityCheckItem,
  21. } from "./types";
  22. import { getStatusIcon, getStatusColor } from "./utils/QualityCheckHelper";
  23. import DefectsSection from "./DefectsSection";
  24. import OperatorScanner from "./OperatorScanner";
  25. const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
  26. isOpen,
  27. onClose,
  28. item = null,
  29. }) => {
  30. const {
  31. control,
  32. handleSubmit,
  33. reset,
  34. watch,
  35. setValue,
  36. setError,
  37. clearErrors,
  38. formState: { errors, isValid },
  39. } = useForm<QualityCheckData>({
  40. defaultValues: {
  41. inspectors: [],
  42. checkDate: new Date().toISOString().split("T")[0],
  43. status: "pending",
  44. notes: "",
  45. defects: [],
  46. },
  47. mode: "onChange",
  48. });
  49. const watchedDefects = watch("defects");
  50. const watchedInspectors = watch("inspectors");
  51. const validateForm = (): boolean => {
  52. let isValid = true;
  53. // Clear previous errors
  54. clearErrors();
  55. // Validate inspectors
  56. if (!watchedInspectors || watchedInspectors.length === 0) {
  57. setError("inspectors", {
  58. type: "required",
  59. message: "At least one inspector is required",
  60. });
  61. isValid = false;
  62. }
  63. return isValid;
  64. };
  65. const onSubmit = (data: QualityCheckData): void => {
  66. if (!validateForm()) {
  67. return;
  68. }
  69. console.log(data);
  70. const qualityRecord: QualityCheckRecord = {
  71. ...data,
  72. itemId: item?.id?.toString(),
  73. itemName: item?.name,
  74. timestamp: new Date().toISOString(),
  75. };
  76. // Save to localStorage or your preferred storage method
  77. // const existingRecords = JSON.parse(localStorage.getItem('qualityCheckRecords') || '[]');
  78. // const updatedRecords = [...existingRecords, qualityRecord];
  79. // localStorage.setItem('qualityCheckRecords', JSON.stringify(updatedRecords));
  80. // Close modal and reset form
  81. handleClose();
  82. };
  83. const handleClose = (): void => {
  84. reset({
  85. inspectors: [],
  86. checkDate: new Date().toISOString().split("T")[0],
  87. status: "pending",
  88. notes: "",
  89. defects: [],
  90. });
  91. onClose();
  92. };
  93. const statusOptions: Array<"pending" | "passed" | "failed"> = [
  94. "pending",
  95. "passed",
  96. "failed",
  97. ];
  98. if (!isOpen) return null;
  99. return (
  100. <Dialog open={isOpen} onClose={handleClose} maxWidth="md" fullWidth>
  101. <DialogTitle
  102. sx={{
  103. display: "flex",
  104. alignItems: "center",
  105. justifyContent: "space-between",
  106. }}
  107. >
  108. <Box>
  109. <Typography variant="h5" fontWeight={700}>
  110. Quality Check
  111. </Typography>
  112. {item && (
  113. <Typography variant="body2" color="text.secondary" mt={0.5}>
  114. Item: {item.name} (ID: {item.id})
  115. </Typography>
  116. )}
  117. </Box>
  118. <IconButton onClick={handleClose} size="large">
  119. <X />
  120. </IconButton>
  121. </DialogTitle>
  122. <DialogContent dividers sx={{ p: 3 }}>
  123. <Stack spacing={4}>
  124. {/* Inspector and Date */}
  125. <Stack direction={{ xs: "column", md: "row" }} spacing={2}>
  126. <Box sx={{ flex: 1 }}>
  127. <Typography variant="body2" fontWeight={600} mb={1}>
  128. Inspector *
  129. </Typography>
  130. <OperatorScanner
  131. operators={watchedInspectors || []}
  132. onOperatorsChange={(operators) => {
  133. setValue("inspectors", operators);
  134. if (operators.length > 0) {
  135. clearErrors("inspectors");
  136. }
  137. }}
  138. error={errors.inspectors?.message}
  139. />
  140. </Box>
  141. </Stack>
  142. <Stack direction={{ xs: "column", md: "row" }} spacing={2}>
  143. <Box sx={{ flex: 1 }}>
  144. <Typography variant="body2" fontWeight={600} mb={1}>
  145. Check Date *
  146. </Typography>
  147. <Controller
  148. name="checkDate"
  149. control={control}
  150. rules={{ required: "Check date is required" }}
  151. render={({ field }) => (
  152. <input
  153. {...field}
  154. type="date"
  155. style={{
  156. width: "100%",
  157. padding: "8px 12px",
  158. border: errors.checkDate
  159. ? "1px solid #f44336"
  160. : "1px solid #ccc",
  161. borderRadius: "4px",
  162. fontSize: "14px",
  163. outline: "none",
  164. }}
  165. />
  166. )}
  167. />
  168. {errors.checkDate && (
  169. <Typography variant="caption" color="error" sx={{ mt: 0.5 }}>
  170. {errors.checkDate.message}
  171. </Typography>
  172. )}
  173. </Box>
  174. </Stack>
  175. {/* Quality Status */}
  176. <Box>
  177. <Typography variant="body2" fontWeight={600} mb={2}>
  178. Quality Status *
  179. </Typography>
  180. <Controller
  181. name="status"
  182. control={control}
  183. rules={{ required: "Please select a quality status" }}
  184. render={({ field }) => (
  185. <Stack direction="row" spacing={2}>
  186. {statusOptions.map((statusOption) => {
  187. const IconComponent = getStatusIcon(statusOption);
  188. const isSelected = field.value === statusOption;
  189. return (
  190. <Button
  191. key={statusOption}
  192. variant={isSelected ? "contained" : "outlined"}
  193. onClick={() => field.onChange(statusOption)}
  194. startIcon={
  195. <IconComponent
  196. sx={{
  197. fontSize: 20,
  198. color:
  199. statusOption === "passed"
  200. ? "success.main"
  201. : statusOption === "failed"
  202. ? "error.main"
  203. : "warning.main",
  204. }}
  205. />
  206. }
  207. sx={{
  208. flex: 1,
  209. textTransform: "capitalize",
  210. py: 1.5,
  211. backgroundColor: isSelected
  212. ? statusOption === "passed"
  213. ? "success.main"
  214. : statusOption === "failed"
  215. ? "error.main"
  216. : "warning.main"
  217. : "transparent",
  218. borderColor:
  219. statusOption === "passed"
  220. ? "success.main"
  221. : statusOption === "failed"
  222. ? "error.main"
  223. : "warning.main",
  224. color: isSelected
  225. ? "white"
  226. : statusOption === "passed"
  227. ? "success.main"
  228. : statusOption === "failed"
  229. ? "error.main"
  230. : "warning.main",
  231. "&:hover": {
  232. backgroundColor: isSelected
  233. ? statusOption === "passed"
  234. ? "success.dark"
  235. : statusOption === "failed"
  236. ? "error.dark"
  237. : "warning.dark"
  238. : statusOption === "passed"
  239. ? "success.light"
  240. : statusOption === "failed"
  241. ? "error.light"
  242. : "warning.light",
  243. },
  244. }}
  245. >
  246. {statusOption}
  247. </Button>
  248. );
  249. })}
  250. </Stack>
  251. )}
  252. />
  253. {errors.status && (
  254. <Typography variant="caption" color="error" sx={{ mt: 0.5 }}>
  255. {errors.status.message}
  256. </Typography>
  257. )}
  258. </Box>
  259. {/* Defects Section */}
  260. <DefectsSection
  261. defects={watchedDefects || []}
  262. onDefectsChange={(defects) => {
  263. setValue("defects", defects);
  264. clearErrors("defects");
  265. }}
  266. error={errors.defects?.message}
  267. />
  268. {/* Additional Notes */}
  269. <Controller
  270. name="notes"
  271. control={control}
  272. render={({ field }) => (
  273. <Box>
  274. <Typography variant="body2" fontWeight={600} mb={1}>
  275. Additional Notes
  276. </Typography>
  277. <textarea
  278. {...field}
  279. rows={4}
  280. style={{
  281. width: "100%",
  282. padding: "12px",
  283. border: "1px solid #ccc",
  284. borderRadius: "4px",
  285. fontSize: "14px",
  286. fontFamily: "inherit",
  287. outline: "none",
  288. resize: "vertical",
  289. }}
  290. placeholder="Enter any additional observations or notes..."
  291. />
  292. </Box>
  293. )}
  294. />
  295. </Stack>
  296. </DialogContent>
  297. <DialogActions sx={{ p: 3 }}>
  298. <Button variant="outlined" color="inherit" onClick={handleClose}>
  299. Cancel
  300. </Button>
  301. <Button
  302. variant="contained"
  303. color="primary"
  304. onClick={handleSubmit(onSubmit)}
  305. startIcon={<Save />}
  306. disabled={!isValid}
  307. >
  308. Save Quality Check
  309. </Button>
  310. </DialogActions>
  311. </Dialog>
  312. );
  313. };
  314. export default QualityCheckModal;