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.
 
 

315 lines
18 KiB

  1. import { BomCombo } from "@/app/api/bom";
  2. import { JoDetail } from "@/app/api/jo";
  3. import { SaveJo, manualCreateJo } from "@/app/api/jo/actions";
  4. import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil";
  5. import { Check } from "@mui/icons-material";
  6. import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography ,FormControl, InputLabel, Select, MenuItem,InputAdornment} from "@mui/material";
  7. import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers";
  8. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  9. import dayjs, { Dayjs } from "dayjs";
  10. import { isFinite } from "lodash";
  11. import React, { SetStateAction, SyntheticEvent, useCallback, useEffect } from "react";
  12. import { Controller, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form";
  13. import { useTranslation } from "react-i18next";
  14. import { msg } from "../Swal/CustomAlerts";
  15. interface Props {
  16. open: boolean;
  17. bomCombo: BomCombo[];
  18. onClose: () => void;
  19. onSearch: () => void;
  20. }
  21. const JoCreateFormModal: React.FC<Props> = ({
  22. open,
  23. bomCombo,
  24. onClose,
  25. onSearch,
  26. }) => {
  27. const { t } = useTranslation("jo");
  28. const formProps = useForm<SaveJo>({
  29. mode: "onChange",
  30. });
  31. const { reset, trigger, watch, control, register, formState: { errors } } = formProps
  32. const onModalClose = useCallback(() => {
  33. reset()
  34. onClose()
  35. }, [])
  36. const handleAutoCompleteChange = useCallback((event: SyntheticEvent<Element, Event>, value: BomCombo, onChange: (...event: any[]) => void) => {
  37. onChange(value.id)
  38. if (value.outputQty != null) {
  39. formProps.setValue("reqQty", Number(value.outputQty), { shouldValidate: true, shouldDirty: true })
  40. }
  41. }, [])
  42. const handleDateTimePickerChange = useCallback((value: Dayjs | null, onChange: (...event: any[]) => void) => {
  43. if (value != null) {
  44. const updatedValue = dayjsToDateTimeString(value)
  45. onChange(updatedValue)
  46. } else {
  47. onChange(value)
  48. }
  49. }, [])
  50. const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => {
  51. data.type = "manual"
  52. if (data.planStart) {
  53. const dateDayjs = dateStringToDayjs(data.planStart)
  54. data.planStart = dayjsToDateTimeString(dateDayjs.startOf('day'))
  55. }
  56. data.jobTypeId = Number(data.jobTypeId);
  57. const response = await manualCreateJo(data)
  58. if (response) {
  59. onSearch();
  60. msg(t("update success"));
  61. onModalClose();
  62. }
  63. }, [onSearch, onModalClose, t])
  64. const onSubmitError = useCallback<SubmitErrorHandler<SaveJo>>((error) => {
  65. console.log(error)
  66. }, [])
  67. const planStart = watch("planStart")
  68. const planEnd = watch("planEnd")
  69. useEffect(() => {
  70. trigger(['planStart', 'planEnd']);
  71. }, [trigger, planStart, planEnd])
  72. return (
  73. <Modal
  74. open={open}
  75. onClose={onModalClose}
  76. >
  77. <Card
  78. style={{
  79. flex: 10,
  80. marginBottom: "20px",
  81. width: "90%",
  82. // height: "80%",
  83. position: "fixed",
  84. top: "50%",
  85. left: "50%",
  86. transform: "translate(-50%, -50%)",
  87. }}
  88. >
  89. <Box
  90. sx={{
  91. display: "flex",
  92. "flex-direction": "column",
  93. padding: "20px",
  94. height: "100%", //'30rem',
  95. width: "100%",
  96. "& .actions": {
  97. color: "text.secondary",
  98. },
  99. "& .header": {
  100. // border: 1,
  101. // 'border-width': '1px',
  102. // 'border-color': 'grey',
  103. },
  104. "& .textPrimary": {
  105. color: "text.primary",
  106. },
  107. }}
  108. >
  109. <FormProvider {...formProps}>
  110. <Stack
  111. // spacing={2}
  112. component="form"
  113. onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
  114. >
  115. <LocalizationProvider
  116. dateAdapter={AdapterDayjs}
  117. // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
  118. adapterLocale="zh-hk"
  119. >
  120. <Grid container spacing={2}>
  121. <Grid item xs={12} sm={12} md={12}>
  122. <Typography variant="h6">{t("Create Job Order")}</Typography>
  123. </Grid>
  124. <Grid item xs={12} sm={12} md={6}>
  125. <Controller
  126. control={control}
  127. name="bomId"
  128. rules={{
  129. required: "Bom required!",
  130. validate: (value) => isFinite(value)
  131. }}
  132. render={({ field, fieldState: { error } }) => (
  133. <Autocomplete
  134. disableClearable
  135. options={bomCombo}
  136. onChange={(event, value) => {
  137. handleAutoCompleteChange(event, value, field.onChange)
  138. }}
  139. onBlur={field.onBlur}
  140. renderInput={(params) => (
  141. <TextField
  142. {...params}
  143. error={Boolean(error)}
  144. variant="outlined"
  145. label={t("Bom")}
  146. />
  147. )}
  148. />
  149. )}
  150. />
  151. </Grid>
  152. <Grid item xs={12} sm={12} md={6}>
  153. <Controller
  154. control={control}
  155. name="reqQty"
  156. rules={{
  157. required: "Req. Qty. required!",
  158. validate: (value) => value > 0
  159. }}
  160. render={({ field, fieldState: { error } }) => {
  161. const selectedBom = bomCombo.find(bom => bom.id === formProps.watch("bomId"));
  162. const uom = selectedBom?.outputQtyUom || "";
  163. return (
  164. <TextField
  165. {...field}
  166. label={t("Req. Qty")}
  167. fullWidth
  168. error={Boolean(error)}
  169. variant="outlined"
  170. type="number"
  171. disabled={true}
  172. value={field.value ?? ""}
  173. onChange={(e) => {
  174. const val = e.target.value === "" ? undefined : Number(e.target.value);
  175. field.onChange(val);
  176. }}
  177. InputProps={{
  178. endAdornment: uom ? (
  179. <InputAdornment position="end">
  180. <Typography variant="body2" sx={{ color: "text.secondary" }}>
  181. {uom}
  182. </Typography>
  183. </InputAdornment>
  184. ) : null
  185. }}
  186. />
  187. );
  188. }}
  189. />
  190. </Grid>
  191. <Grid item xs={12} sm={12} md={6}>
  192. <Controller
  193. control={control}
  194. name="jobTypeId"
  195. rules={{ required: t("Job Type required!") as string }}
  196. render={({ field, fieldState: { error } }) => (
  197. <FormControl fullWidth error={Boolean(error)}>
  198. <InputLabel>{t("Job Type")}</InputLabel>
  199. <Select
  200. {...field}
  201. label={t("Job Type")}
  202. value={field.value?.toString() ?? ""}
  203. onChange={(event) => {
  204. const value = event.target.value;
  205. field.onChange(value === "" ? undefined : Number(value));
  206. }}
  207. >
  208. <MenuItem value="">
  209. <em>{t("Please select")}</em>
  210. </MenuItem>
  211. <MenuItem value="1">{t("FG")}</MenuItem>
  212. <MenuItem value="2">{t("WIP")}</MenuItem>
  213. <MenuItem value="3">{t("R&D")}</MenuItem>
  214. <MenuItem value="4">{t("STF")}</MenuItem>
  215. <MenuItem value="5">{t("Other")}</MenuItem>
  216. </Select>
  217. {/*{error && <FormHelperText>{error.message}</FormHelperText>}*/}
  218. </FormControl>
  219. )}
  220. />
  221. </Grid>
  222. <Grid item xs={12} sm={12} md={6}>
  223. <Controller
  224. control={control}
  225. name="planStart"
  226. rules={{
  227. required: "Plan start required!",
  228. validate: {
  229. isValid: (value) => dateStringToDayjs(value).isValid(),
  230. // isBeforePlanEnd: (value) => {
  231. // const planStartDayjs = dateStringToDayjs(value)
  232. // const planEndDayjs = dateStringToDayjs(planEnd)
  233. // return planStartDayjs.isBefore(planEndDayjs) || planStartDayjs.isSame(planEndDayjs)
  234. // }
  235. }
  236. }}
  237. render={({ field, fieldState: { error } }) => (
  238. // <DateTimePicker
  239. <DatePicker
  240. label={t("Plan Start")}
  241. // views={['year','month','day','hours', 'minutes', 'seconds']}
  242. //format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
  243. format={OUTPUT_DATE_FORMAT}
  244. value={field.value ? dateStringToDayjs(field.value) : null}
  245. onChange={(newValue: Dayjs | null) => {
  246. handleDateTimePickerChange(newValue, field.onChange)
  247. }}
  248. slotProps={{ textField: { fullWidth: true, error: Boolean(error) } }}
  249. />
  250. )}
  251. />
  252. </Grid>
  253. {/* <Grid item xs={12} sm={12} md={6}>
  254. <Controller
  255. control={control}
  256. name="planEnd"
  257. rules={{
  258. required: "Plan end required!",
  259. validate: {
  260. isValid: (value) => dateStringToDayjs(value).isValid(),
  261. isBeforePlanEnd: (value) => {
  262. const planStartDayjs = dateStringToDayjs(planStart)
  263. const planEndDayjs = dateStringToDayjs(value)
  264. return planEndDayjs.isAfter(planStartDayjs) || planEndDayjs.isSame(planStartDayjs)
  265. }
  266. }
  267. }}
  268. render={({ field, fieldState: { error } }) => (
  269. <DateTimePicker
  270. label={t("Plan End")}
  271. format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
  272. onChange={(newValue: Dayjs | null) => {
  273. handleDateTimePickerChange(newValue, field.onChange)
  274. }}
  275. slotProps={{ textField: { fullWidth: true } }}
  276. />
  277. )}
  278. />
  279. </Grid> */}
  280. </Grid>
  281. <Stack
  282. direction="row"
  283. justifyContent="flex-end"
  284. spacing={2}
  285. sx={{ mt: 2 }}
  286. >
  287. <Button
  288. name="submit"
  289. variant="contained"
  290. startIcon={<Check />}
  291. type="submit"
  292. >
  293. {t("Create")}
  294. </Button>
  295. </Stack>
  296. </LocalizationProvider>
  297. </Stack>
  298. </FormProvider>
  299. </Box>
  300. </Card>
  301. </Modal>
  302. )
  303. }
  304. export default JoCreateFormModal;