|
|
|
@@ -0,0 +1,231 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import React, { useMemo, useState, useCallback, useEffect } from "react"; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
import { BomWeightingScoreResult } from "@/app/api/settings/bomWeighting"; |
|
|
|
import { updateBomWeightingScoreClient } from "@/app/api/settings/bomWeighting/client"; |
|
|
|
import { GridColDef, GridValueGetterParams, GridValueFormatterParams, GridRenderCellParams } from "@mui/x-data-grid"; |
|
|
|
import StyledDataGrid from "../StyledDataGrid"; |
|
|
|
import Paper from "@mui/material/Paper"; |
|
|
|
import IconButton from "@mui/material/IconButton"; |
|
|
|
import EditNote from "@mui/icons-material/EditNote"; |
|
|
|
import Dialog from "@mui/material/Dialog"; |
|
|
|
import DialogTitle from "@mui/material/DialogTitle"; |
|
|
|
import DialogContent from "@mui/material/DialogContent"; |
|
|
|
import DialogActions from "@mui/material/DialogActions"; |
|
|
|
import TextField from "@mui/material/TextField"; |
|
|
|
import Button from "@mui/material/Button"; |
|
|
|
import { successDialog } from "../Swal/CustomAlerts"; |
|
|
|
|
|
|
|
interface Props { |
|
|
|
bomWeightingScores: BomWeightingScoreResult[]; |
|
|
|
} |
|
|
|
|
|
|
|
const BomWeightingScoreTable: React.FC<Props> & { Loading?: React.FC } = ({ bomWeightingScores: initialBomWeightingScores }) => { |
|
|
|
const { t } = useTranslation("common"); |
|
|
|
const [bomWeightingScores, setBomWeightingScores] = useState(initialBomWeightingScores); |
|
|
|
const [editDialogOpen, setEditDialogOpen] = useState(false); |
|
|
|
const [editingItem, setEditingItem] = useState<BomWeightingScoreResult | null>(null); |
|
|
|
const [isSaving, setIsSaving] = useState(false); |
|
|
|
const [formData, setFormData] = useState({ |
|
|
|
name: "", |
|
|
|
range: "", |
|
|
|
weighting: "", |
|
|
|
remarks: "", |
|
|
|
}); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
setBomWeightingScores(initialBomWeightingScores); |
|
|
|
}, [initialBomWeightingScores]); |
|
|
|
|
|
|
|
const handleEditClick = useCallback((row: BomWeightingScoreResult) => { |
|
|
|
let weightingValue = 0; |
|
|
|
if (row.weighting != null) { |
|
|
|
if (typeof row.weighting === "object" && row.weighting !== null) { |
|
|
|
const obj = row.weighting as any; |
|
|
|
weightingValue = typeof obj.value === "number" ? obj.value : parseFloat(String(row.weighting)) || 0; |
|
|
|
} else { |
|
|
|
weightingValue = typeof row.weighting === "number" ? row.weighting : parseFloat(String(row.weighting)) || 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
setEditingItem(row); |
|
|
|
setFormData({ |
|
|
|
name: row.name || "", |
|
|
|
range: String(row.range || ""), |
|
|
|
weighting: String(weightingValue), |
|
|
|
remarks: row.remarks || "", |
|
|
|
}); |
|
|
|
setEditDialogOpen(true); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const handleCloseDialog = useCallback(() => { |
|
|
|
setEditDialogOpen(false); |
|
|
|
setEditingItem(null); |
|
|
|
setFormData({ name: "", range: "", weighting: "", remarks: "" }); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const handleSave = useCallback(async (e?: React.MouseEvent) => { |
|
|
|
e?.preventDefault(); |
|
|
|
e?.stopPropagation(); |
|
|
|
|
|
|
|
if (!editingItem || isSaving) return; |
|
|
|
|
|
|
|
setIsSaving(true); |
|
|
|
try { |
|
|
|
const updated = await updateBomWeightingScoreClient({ |
|
|
|
id: editingItem.id, |
|
|
|
name: editingItem.name, // Keep original name |
|
|
|
range: parseInt(formData.range, 10), |
|
|
|
weighting: parseFloat(formData.weighting), |
|
|
|
remarks: editingItem.remarks || undefined, // Keep original remarks |
|
|
|
}); |
|
|
|
|
|
|
|
// Update local state immediately |
|
|
|
setBomWeightingScores((prev) => |
|
|
|
prev.map((item) => (item.id === editingItem.id ? updated : item)) |
|
|
|
); |
|
|
|
|
|
|
|
// Close dialog first, then show success message |
|
|
|
handleCloseDialog(); |
|
|
|
await successDialog(t("Update Success"), t); |
|
|
|
} catch (error: any) { |
|
|
|
console.error("Error updating bom weighting score:", error); |
|
|
|
// Show error message to user |
|
|
|
const errorMessage = error?.response?.data?.message || error?.message || t("Update Failed") || "Update failed. Please try again."; |
|
|
|
alert(errorMessage); |
|
|
|
} finally { |
|
|
|
setIsSaving(false); |
|
|
|
} |
|
|
|
}, [editingItem, formData, t, handleCloseDialog, isSaving]); |
|
|
|
|
|
|
|
const columns = useMemo<GridColDef<BomWeightingScoreResult>[]>( |
|
|
|
() => [ |
|
|
|
{ |
|
|
|
field: "actions", |
|
|
|
headerName: t("Edit"), |
|
|
|
width: 100, |
|
|
|
sortable: false, |
|
|
|
renderCell: (params: GridRenderCellParams<BomWeightingScoreResult>) => ( |
|
|
|
<IconButton |
|
|
|
size="small" |
|
|
|
onClick={() => handleEditClick(params.row)} |
|
|
|
color="primary" |
|
|
|
> |
|
|
|
<EditNote fontSize="small" /> |
|
|
|
</IconButton> |
|
|
|
), |
|
|
|
}, |
|
|
|
{ |
|
|
|
field: "name", |
|
|
|
headerName: t("Name"), |
|
|
|
flex: 1, |
|
|
|
}, |
|
|
|
{ |
|
|
|
field: "range", |
|
|
|
headerName: t("Range"), |
|
|
|
flex: 1, |
|
|
|
align: "left", |
|
|
|
headerAlign: "left", |
|
|
|
}, |
|
|
|
{ |
|
|
|
field: "weighting", |
|
|
|
headerName: t("Weighting"), |
|
|
|
flex: 1, |
|
|
|
valueGetter: (params: GridValueGetterParams<BomWeightingScoreResult>) => { |
|
|
|
const weighting = params.row.weighting; |
|
|
|
if (weighting == null || weighting === undefined) return null; |
|
|
|
|
|
|
|
if (typeof weighting === "object" && weighting !== null) { |
|
|
|
const obj = weighting as any; |
|
|
|
if (typeof obj.value === "number") { |
|
|
|
return obj.value; |
|
|
|
} |
|
|
|
if (typeof obj.toString === "function") { |
|
|
|
return parseFloat(obj.toString()); |
|
|
|
} |
|
|
|
const numValue = parseFloat(String(weighting)); |
|
|
|
return isNaN(numValue) ? null : numValue; |
|
|
|
} |
|
|
|
|
|
|
|
const numValue = typeof weighting === "number" ? weighting : parseFloat(String(weighting)); |
|
|
|
return isNaN(numValue) ? null : numValue; |
|
|
|
}, |
|
|
|
valueFormatter: (params: GridValueFormatterParams) => { |
|
|
|
const value = params.value; |
|
|
|
if (value == null || value === undefined) return ""; |
|
|
|
return typeof value === "number" ? value.toFixed(2) : ""; |
|
|
|
}, |
|
|
|
}, |
|
|
|
], |
|
|
|
[t, handleEditClick], |
|
|
|
); |
|
|
|
|
|
|
|
return ( |
|
|
|
<> |
|
|
|
<Paper variant="outlined" sx={{ overflow: "hidden" }}> |
|
|
|
<StyledDataGrid |
|
|
|
rows={bomWeightingScores} |
|
|
|
columns={columns} |
|
|
|
getRowId={(row) => row.id} |
|
|
|
autoHeight |
|
|
|
disableRowSelectionOnClick |
|
|
|
hideFooterPagination={true} |
|
|
|
/> |
|
|
|
</Paper> |
|
|
|
|
|
|
|
<Dialog open={editDialogOpen} onClose={handleCloseDialog} maxWidth="sm" fullWidth> |
|
|
|
<DialogTitle>{t("Edit BOM Weighting Score")}</DialogTitle> |
|
|
|
<DialogContent> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
label={t("Name")} |
|
|
|
value={formData.name} |
|
|
|
margin="normal" |
|
|
|
disabled |
|
|
|
/> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
label={t("Range")} |
|
|
|
type="number" |
|
|
|
value={formData.range} |
|
|
|
onChange={(e) => setFormData({ ...formData, range: e.target.value })} |
|
|
|
margin="normal" |
|
|
|
required |
|
|
|
/> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
label={t("Weighting")} |
|
|
|
type="number" |
|
|
|
inputProps={{ step: "0.01" }} |
|
|
|
value={formData.weighting} |
|
|
|
onChange={(e) => setFormData({ ...formData, weighting: e.target.value })} |
|
|
|
margin="normal" |
|
|
|
required |
|
|
|
/> |
|
|
|
</DialogContent> |
|
|
|
<DialogActions> |
|
|
|
<Button onClick={handleCloseDialog} disabled={isSaving}>{t("Cancel")}</Button> |
|
|
|
<Button |
|
|
|
onClick={handleSave} |
|
|
|
variant="contained" |
|
|
|
color="primary" |
|
|
|
disabled={isSaving} |
|
|
|
> |
|
|
|
{isSaving ? t("Saving") || "Saving..." : t("Save")} |
|
|
|
</Button> |
|
|
|
</DialogActions> |
|
|
|
</Dialog> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
BomWeightingScoreTable.Loading = () => { |
|
|
|
return ( |
|
|
|
<Paper variant="outlined" sx={{ overflow: "hidden", minHeight: 400 }}> |
|
|
|
<div style={{ padding: "16px" }}>Loading...</div> |
|
|
|
</Paper> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
export default BomWeightingScoreTable; |