FPSMS-frontend
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 

306 рядки
10 KiB

  1. "use client";
  2. import React, { useCallback, useEffect, useMemo, useState } from "react";
  3. import {
  4. Box,
  5. Button,
  6. Dialog,
  7. DialogActions,
  8. DialogContent,
  9. DialogTitle,
  10. IconButton,
  11. Stack,
  12. Table,
  13. TableBody,
  14. TableCell,
  15. TableContainer,
  16. TableHead,
  17. TableRow,
  18. TextField,
  19. Autocomplete,
  20. CircularProgress,
  21. } from "@mui/material";
  22. import { useTranslation } from "react-i18next";
  23. import { Add, Delete, Edit } from "@mui/icons-material";
  24. import SearchBox, { Criterion } from "../SearchBox/SearchBox";
  25. import SearchResults, { Column } from "../SearchResults/SearchResults";
  26. import {
  27. saveQcCategoryQcItemMapping,
  28. deleteQcCategoryQcItemMapping,
  29. getQcCategoryQcItemMappings,
  30. fetchQcCategoriesForAll,
  31. fetchQcItemsForAll,
  32. } from "@/app/api/settings/qcItemAll/actions";
  33. import {
  34. QcCategoryResult,
  35. QcItemResult,
  36. } from "@/app/api/settings/qcItemAll";
  37. import { QcItemInfo } from "@/app/api/settings/qcItemAll";
  38. import {
  39. deleteDialog,
  40. errorDialogWithContent,
  41. submitDialog,
  42. successDialog,
  43. } from "../Swal/CustomAlerts";
  44. type SearchQuery = Partial<Omit<QcCategoryResult, "id">>;
  45. type SearchParamNames = keyof SearchQuery;
  46. const Tab1QcCategoryQcItemMapping: React.FC = () => {
  47. const { t } = useTranslation("qcItemAll");
  48. const [qcCategories, setQcCategories] = useState<QcCategoryResult[]>([]);
  49. const [filteredQcCategories, setFilteredQcCategories] = useState<QcCategoryResult[]>([]);
  50. const [selectedCategory, setSelectedCategory] = useState<QcCategoryResult | null>(null);
  51. const [mappings, setMappings] = useState<QcItemInfo[]>([]);
  52. const [openDialog, setOpenDialog] = useState(false);
  53. const [openAddDialog, setOpenAddDialog] = useState(false);
  54. const [qcItems, setQcItems] = useState<QcItemResult[]>([]);
  55. const [selectedQcItem, setSelectedQcItem] = useState<QcItemResult | null>(null);
  56. const [order, setOrder] = useState<number>(0);
  57. const [loading, setLoading] = useState(true);
  58. useEffect(() => {
  59. const loadData = async () => {
  60. setLoading(true);
  61. try {
  62. // Only load categories list (same as Tab 2) - fast!
  63. const categories = await fetchQcCategoriesForAll();
  64. setQcCategories(categories || []);
  65. setFilteredQcCategories(categories || []);
  66. } catch (error) {
  67. console.error("Error loading data:", error);
  68. setQcCategories([]); // Ensure it's always an array
  69. setFilteredQcCategories([]);
  70. } finally {
  71. setLoading(false);
  72. }
  73. };
  74. loadData();
  75. }, []);
  76. const handleViewMappings = useCallback(async (category: QcCategoryResult) => {
  77. setSelectedCategory(category);
  78. // Load mappings when user clicks View (lazy loading)
  79. const mappingData = await getQcCategoryQcItemMappings(category.id);
  80. setMappings(mappingData);
  81. setOpenDialog(true);
  82. }, []);
  83. const handleAddMapping = useCallback(async () => {
  84. if (!selectedCategory) return;
  85. // Load qc items list when opening add dialog
  86. try {
  87. const itemsData = await fetchQcItemsForAll();
  88. setQcItems(itemsData);
  89. } catch (error) {
  90. console.error("Error loading qc items:", error);
  91. }
  92. setOpenAddDialog(true);
  93. setOrder(0);
  94. setSelectedQcItem(null);
  95. }, [selectedCategory]);
  96. const handleSaveMapping = useCallback(async () => {
  97. if (!selectedCategory || !selectedQcItem) return;
  98. await submitDialog(async () => {
  99. try {
  100. await saveQcCategoryQcItemMapping(
  101. selectedCategory.id,
  102. selectedQcItem.id,
  103. order,
  104. undefined // No description needed - qcItem already has description
  105. );
  106. // Close add dialog first
  107. setOpenAddDialog(false);
  108. setSelectedQcItem(null);
  109. setOrder(0);
  110. // Reload mappings to update the view
  111. const mappingData = await getQcCategoryQcItemMappings(selectedCategory.id);
  112. setMappings(mappingData);
  113. // Show success message after closing dialogs
  114. await successDialog(t("Submit Success"), t);
  115. // Keep the view dialog open to show updated data
  116. } catch (error) {
  117. errorDialogWithContent(t("Submit Error"), String(error), t);
  118. }
  119. }, t);
  120. }, [selectedCategory, selectedQcItem, order, t]);
  121. const handleDeleteMapping = useCallback(
  122. async (mappingId: number) => {
  123. if (!selectedCategory) return;
  124. deleteDialog(async () => {
  125. try {
  126. await deleteQcCategoryQcItemMapping(mappingId);
  127. await successDialog(t("Delete Success"), t);
  128. // Reload mappings
  129. const mappingData = await getQcCategoryQcItemMappings(selectedCategory.id);
  130. setMappings(mappingData);
  131. // No need to reload categories list - it doesn't change
  132. } catch (error) {
  133. errorDialogWithContent(t("Delete Error"), String(error), t);
  134. }
  135. }, t);
  136. },
  137. [selectedCategory, t]
  138. );
  139. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  140. () => [
  141. { label: t("Code"), paramName: "code", type: "text" },
  142. { label: t("Name"), paramName: "name", type: "text" },
  143. ],
  144. [t]
  145. );
  146. const onReset = useCallback(() => {
  147. setFilteredQcCategories(qcCategories);
  148. }, [qcCategories]);
  149. const columnWidthSx = (width = "10%") => {
  150. return { width: width, whiteSpace: "nowrap" };
  151. };
  152. const columns = useMemo<Column<QcCategoryResult>[]>(
  153. () => [
  154. { name: "code", label: t("Qc Category Code"), sx: columnWidthSx("20%") },
  155. { name: "name", label: t("Qc Category Name"), sx: columnWidthSx("40%") },
  156. { name: "type", label: t("Type"), sx: columnWidthSx("10%") },
  157. {
  158. name: "id",
  159. label: t("Actions"),
  160. onClick: (category) => handleViewMappings(category),
  161. buttonIcon: <Edit />,
  162. buttonIcons: {} as any,
  163. sx: columnWidthSx("10%"),
  164. },
  165. ],
  166. [t, handleViewMappings]
  167. );
  168. return (
  169. <Box>
  170. <SearchBox
  171. criteria={searchCriteria}
  172. onSearch={(query) => {
  173. setFilteredQcCategories(
  174. qcCategories.filter(
  175. (qc) =>
  176. (!query.code || qc.code.toLowerCase().includes(query.code.toLowerCase())) &&
  177. (!query.name || qc.name.toLowerCase().includes(query.name.toLowerCase()))
  178. )
  179. );
  180. }}
  181. onReset={onReset}
  182. />
  183. <SearchResults<QcCategoryResult>
  184. items={filteredQcCategories}
  185. columns={columns}
  186. />
  187. {/* View Mappings Dialog */}
  188. <Dialog open={openDialog} onClose={() => setOpenDialog(false)} maxWidth="md" fullWidth sx={{ zIndex: 1000 }}>
  189. <DialogTitle>
  190. {t("Association Details")} - {selectedCategory?.name}
  191. </DialogTitle>
  192. <DialogContent>
  193. <Stack spacing={2} sx={{ mt: 1 }}>
  194. <Box sx={{ display: "flex", justifyContent: "flex-end" }}>
  195. <Button
  196. variant="contained"
  197. startIcon={<Add />}
  198. onClick={handleAddMapping}
  199. >
  200. {t("Add Association")}
  201. </Button>
  202. </Box>
  203. <TableContainer>
  204. <Table>
  205. <TableHead>
  206. <TableRow>
  207. <TableCell>{t("Order")}</TableCell>
  208. <TableCell>{t("Qc Item Code")}</TableCell>
  209. <TableCell>{t("Qc Item Name")}</TableCell>
  210. <TableCell>{t("Description")}</TableCell>
  211. <TableCell>{t("Actions")}</TableCell>
  212. </TableRow>
  213. </TableHead>
  214. <TableBody>
  215. {mappings.length === 0 ? (
  216. <TableRow>
  217. <TableCell colSpan={5} align="center">
  218. {t("No associations found")}
  219. </TableCell>
  220. </TableRow>
  221. ) : (
  222. mappings.map((mapping) => (
  223. <TableRow key={mapping.id}>
  224. <TableCell>{mapping.order}</TableCell>
  225. <TableCell>{mapping.code}</TableCell>
  226. <TableCell>{mapping.name}</TableCell>
  227. <TableCell>{mapping.description || "-"}</TableCell>
  228. <TableCell>
  229. <IconButton
  230. color="error"
  231. size="small"
  232. onClick={() => handleDeleteMapping(mapping.id)}
  233. >
  234. <Delete />
  235. </IconButton>
  236. </TableCell>
  237. </TableRow>
  238. ))
  239. )}
  240. </TableBody>
  241. </Table>
  242. </TableContainer>
  243. </Stack>
  244. </DialogContent>
  245. <DialogActions>
  246. <Button onClick={() => setOpenDialog(false)}>{t("Cancel")}</Button>
  247. </DialogActions>
  248. </Dialog>
  249. {/* Add Mapping Dialog */}
  250. <Dialog open={openAddDialog} onClose={() => setOpenAddDialog(false)} maxWidth="sm" fullWidth sx={{ zIndex: 1000 }}>
  251. <DialogTitle>{t("Add Association")}</DialogTitle>
  252. <DialogContent>
  253. <Stack spacing={2} sx={{ mt: 2 }}>
  254. <Autocomplete
  255. options={qcItems}
  256. getOptionLabel={(option) => `${option.code} - ${option.name}`}
  257. value={selectedQcItem}
  258. onChange={(_, newValue) => setSelectedQcItem(newValue)}
  259. renderInput={(params) => (
  260. <TextField {...params} label={t("Select Qc Item")} />
  261. )}
  262. />
  263. <TextField
  264. type="number"
  265. label={t("Order")}
  266. value={order}
  267. onChange={(e) => setOrder(parseInt(e.target.value) || 0)}
  268. fullWidth
  269. />
  270. </Stack>
  271. </DialogContent>
  272. <DialogActions>
  273. <Button onClick={() => setOpenAddDialog(false)}>{t("Cancel")}</Button>
  274. <Button
  275. variant="contained"
  276. onClick={handleSaveMapping}
  277. disabled={!selectedQcItem}
  278. >
  279. {t("Save")}
  280. </Button>
  281. </DialogActions>
  282. </Dialog>
  283. </Box>
  284. );
  285. };
  286. export default Tab1QcCategoryQcItemMapping;