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.
 
 

221 lines
6.2 KiB

  1. "use client";
  2. import { createPrinter, PrinterInputs, fetchPrinterDescriptions } from "@/app/api/settings/printer/actions";
  3. import { successDialog } from "@/components/Swal/CustomAlerts";
  4. import { ArrowBack, Check } from "@mui/icons-material";
  5. import {
  6. Autocomplete,
  7. Box,
  8. Button,
  9. FormControl,
  10. Grid,
  11. InputLabel,
  12. MenuItem,
  13. Select,
  14. SelectChangeEvent,
  15. Stack,
  16. TextField,
  17. } from "@mui/material";
  18. import { useRouter } from "next/navigation";
  19. import { useCallback, useEffect, useState } from "react";
  20. import { useTranslation } from "react-i18next";
  21. const CreatePrinter: React.FC = () => {
  22. const { t } = useTranslation("common");
  23. const router = useRouter();
  24. const [isSubmitting, setIsSubmitting] = useState(false);
  25. const [descriptions, setDescriptions] = useState<string[]>([]);
  26. const [formData, setFormData] = useState<PrinterInputs>({
  27. name: "",
  28. ip: "",
  29. port: undefined,
  30. type: "A4",
  31. dpi: undefined,
  32. description: "",
  33. });
  34. useEffect(() => {
  35. const loadDescriptions = async () => {
  36. try {
  37. const descs = await fetchPrinterDescriptions();
  38. setDescriptions(descs);
  39. } catch (error) {
  40. console.error("Failed to load descriptions:", error);
  41. }
  42. };
  43. loadDescriptions();
  44. }, []);
  45. useEffect(() => {
  46. if (formData.type !== "Label") {
  47. setFormData((prev) => ({ ...prev, dpi: undefined }));
  48. }
  49. }, [formData.type]);
  50. const handleChange = useCallback((field: keyof PrinterInputs) => {
  51. return (e: React.ChangeEvent<HTMLInputElement>) => {
  52. const value = e.target.value;
  53. setFormData((prev) => ({
  54. ...prev,
  55. [field]:
  56. field === "port" || field === "dpi"
  57. ? value === ""
  58. ? undefined
  59. : parseInt(value, 10)
  60. : value,
  61. }));
  62. };
  63. }, []);
  64. const handleTypeChange = useCallback((e: SelectChangeEvent) => {
  65. setFormData((prev) => ({
  66. ...prev,
  67. type: e.target.value,
  68. }));
  69. }, []);
  70. const handleDescriptionChange = useCallback((_e: any, newValue: string | null) => {
  71. setFormData((prev) => ({
  72. ...prev,
  73. description: newValue || "",
  74. }));
  75. }, []);
  76. const handleSubmit = useCallback(async () => {
  77. setIsSubmitting(true);
  78. try {
  79. const needDpi = formData.type === "Label";
  80. const missing: string[] = [];
  81. if (!formData.ip || formData.ip.trim() === "") missing.push("IP");
  82. if (formData.port === undefined || formData.port === null || Number.isNaN(formData.port)) missing.push("Port");
  83. if (!formData.type || formData.type.trim() === "") missing.push(t("Type") || "類型");
  84. if (needDpi && (formData.dpi === undefined || formData.dpi === null || Number.isNaN(formData.dpi))) missing.push("DPI");
  85. if (missing.length > 0) {
  86. alert(`請必須輸入 ${missing.join("、")}`);
  87. setIsSubmitting(false);
  88. return;
  89. }
  90. await createPrinter(formData);
  91. successDialog(t("Create Printer") || "新增列印機", t);
  92. router.push("/settings/printer");
  93. router.refresh();
  94. } catch (error) {
  95. const errorMessage =
  96. error instanceof Error
  97. ? error.message
  98. : t("Error saving data") || "儲存失敗";
  99. alert(errorMessage);
  100. } finally {
  101. setIsSubmitting(false);
  102. }
  103. }, [formData, router, t]);
  104. return (
  105. <Box sx={{ mt: 3 }}>
  106. <Grid container spacing={3}>
  107. <Grid item xs={12} md={6}>
  108. <TextField
  109. fullWidth
  110. label={t("Name")}
  111. value={formData.name}
  112. onChange={handleChange("name")}
  113. variant="outlined"
  114. />
  115. </Grid>
  116. <Grid item xs={12} md={6}>
  117. <TextField
  118. fullWidth
  119. label="IP"
  120. value={formData.ip}
  121. onChange={handleChange("ip")}
  122. variant="outlined"
  123. />
  124. </Grid>
  125. <Grid item xs={12} md={6}>
  126. <TextField
  127. fullWidth
  128. label="Port"
  129. type="number"
  130. value={formData.port ?? ""}
  131. onChange={handleChange("port")}
  132. variant="outlined"
  133. />
  134. </Grid>
  135. <Grid item xs={12} md={6}>
  136. <FormControl fullWidth>
  137. <InputLabel>{t("Type")}</InputLabel>
  138. <Select
  139. label={t("Type")}
  140. value={formData.type ?? "A4"}
  141. onChange={handleTypeChange}
  142. >
  143. <MenuItem value={"A4"}>A4</MenuItem>
  144. <MenuItem value={"Label"}>Label</MenuItem>
  145. </Select>
  146. </FormControl>
  147. </Grid>
  148. <Grid item xs={12} md={6}>
  149. <TextField
  150. fullWidth
  151. label="DPI"
  152. type="number"
  153. value={formData.dpi ?? ""}
  154. onChange={handleChange("dpi")}
  155. variant="outlined"
  156. disabled={formData.type !== "Label"}
  157. />
  158. </Grid>
  159. <Grid item xs={12} md={6}>
  160. <Autocomplete
  161. freeSolo
  162. options={descriptions}
  163. value={formData.description || null}
  164. onChange={handleDescriptionChange}
  165. onInputChange={(_e, newInputValue) => {
  166. setFormData((prev) => ({
  167. ...prev,
  168. description: newInputValue,
  169. }));
  170. }}
  171. renderInput={(params) => (
  172. <TextField
  173. {...params}
  174. label={t("Description")}
  175. variant="outlined"
  176. fullWidth
  177. />
  178. )}
  179. />
  180. </Grid>
  181. <Grid item xs={12}>
  182. <Stack direction="row" spacing={2}>
  183. <Button
  184. variant="outlined"
  185. startIcon={<ArrowBack />}
  186. onClick={() => router.push("/settings/printer")}
  187. >
  188. {t("Back")}
  189. </Button>
  190. <Button
  191. variant="contained"
  192. startIcon={<Check />}
  193. onClick={handleSubmit}
  194. disabled={isSubmitting}
  195. >
  196. {t("Save")}
  197. </Button>
  198. </Stack>
  199. </Grid>
  200. </Grid>
  201. </Box>
  202. );
  203. };
  204. const CreatePrinterLoading: React.FC = () => {
  205. return null;
  206. };
  207. export default Object.assign(CreatePrinter, { Loading: CreatePrinterLoading });