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.
 
 

305 line
9.9 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. {
  157. name: "id",
  158. label: t("Actions"),
  159. onClick: (category) => handleViewMappings(category),
  160. buttonIcon: <Edit />,
  161. buttonIcons: {} as any,
  162. sx: columnWidthSx("10%"),
  163. },
  164. ],
  165. [t, handleViewMappings]
  166. );
  167. return (
  168. <Box>
  169. <SearchBox
  170. criteria={searchCriteria}
  171. onSearch={(query) => {
  172. setFilteredQcCategories(
  173. qcCategories.filter(
  174. (qc) =>
  175. (!query.code || qc.code.toLowerCase().includes(query.code.toLowerCase())) &&
  176. (!query.name || qc.name.toLowerCase().includes(query.name.toLowerCase()))
  177. )
  178. );
  179. }}
  180. onReset={onReset}
  181. />
  182. <SearchResults<QcCategoryResult>
  183. items={filteredQcCategories}
  184. columns={columns}
  185. />
  186. {/* View Mappings Dialog */}
  187. <Dialog open={openDialog} onClose={() => setOpenDialog(false)} maxWidth="md" fullWidth>
  188. <DialogTitle>
  189. {t("Association Details")} - {selectedCategory?.name}
  190. </DialogTitle>
  191. <DialogContent>
  192. <Stack spacing={2} sx={{ mt: 1 }}>
  193. <Box sx={{ display: "flex", justifyContent: "flex-end" }}>
  194. <Button
  195. variant="contained"
  196. startIcon={<Add />}
  197. onClick={handleAddMapping}
  198. >
  199. {t("Add Association")}
  200. </Button>
  201. </Box>
  202. <TableContainer>
  203. <Table>
  204. <TableHead>
  205. <TableRow>
  206. <TableCell>{t("Order")}</TableCell>
  207. <TableCell>{t("Qc Item Code")}</TableCell>
  208. <TableCell>{t("Qc Item Name")}</TableCell>
  209. <TableCell>{t("Description")}</TableCell>
  210. <TableCell>{t("Actions")}</TableCell>
  211. </TableRow>
  212. </TableHead>
  213. <TableBody>
  214. {mappings.length === 0 ? (
  215. <TableRow>
  216. <TableCell colSpan={5} align="center">
  217. {t("No associations found")}
  218. </TableCell>
  219. </TableRow>
  220. ) : (
  221. mappings.map((mapping) => (
  222. <TableRow key={mapping.id}>
  223. <TableCell>{mapping.order}</TableCell>
  224. <TableCell>{mapping.code}</TableCell>
  225. <TableCell>{mapping.name}</TableCell>
  226. <TableCell>{mapping.description || "-"}</TableCell>
  227. <TableCell>
  228. <IconButton
  229. color="error"
  230. size="small"
  231. onClick={() => handleDeleteMapping(mapping.id)}
  232. >
  233. <Delete />
  234. </IconButton>
  235. </TableCell>
  236. </TableRow>
  237. ))
  238. )}
  239. </TableBody>
  240. </Table>
  241. </TableContainer>
  242. </Stack>
  243. </DialogContent>
  244. <DialogActions>
  245. <Button onClick={() => setOpenDialog(false)}>{t("Cancel")}</Button>
  246. </DialogActions>
  247. </Dialog>
  248. {/* Add Mapping Dialog */}
  249. <Dialog open={openAddDialog} onClose={() => setOpenAddDialog(false)} maxWidth="sm" fullWidth>
  250. <DialogTitle>{t("Add Association")}</DialogTitle>
  251. <DialogContent>
  252. <Stack spacing={2} sx={{ mt: 2 }}>
  253. <Autocomplete
  254. options={qcItems}
  255. getOptionLabel={(option) => `${option.code} - ${option.name}`}
  256. value={selectedQcItem}
  257. onChange={(_, newValue) => setSelectedQcItem(newValue)}
  258. renderInput={(params) => (
  259. <TextField {...params} label={t("Select Qc Item")} />
  260. )}
  261. />
  262. <TextField
  263. type="number"
  264. label={t("Order")}
  265. value={order}
  266. onChange={(e) => setOrder(parseInt(e.target.value) || 0)}
  267. fullWidth
  268. />
  269. </Stack>
  270. </DialogContent>
  271. <DialogActions>
  272. <Button onClick={() => setOpenAddDialog(false)}>{t("Cancel")}</Button>
  273. <Button
  274. variant="contained"
  275. onClick={handleSaveMapping}
  276. disabled={!selectedQcItem}
  277. >
  278. {t("Save")}
  279. </Button>
  280. </DialogActions>
  281. </Dialog>
  282. </Box>
  283. );
  284. };
  285. export default Tab1QcCategoryQcItemMapping;