Переглянути джерело

Bom Supporting Function

reset-do-picking-order
B.E.N.S.O.N 2 тижднів тому
джерело
коміт
9ead9d244e
7 змінених файлів з 358 додано та 0 видалено
  1. +25
    -0
      src/app/(main)/settings/bomWeighting/page.tsx
  2. +30
    -0
      src/app/api/settings/bomWeighting/actions.ts
  3. +23
    -0
      src/app/api/settings/bomWeighting/client.ts
  4. +23
    -0
      src/app/api/settings/bomWeighting/index.ts
  5. +25
    -0
      src/app/api/settings/bomWeighting/page.tsx
  6. +231
    -0
      src/components/BomWeightingScoreTable/BomWeightingScoreTable.tsx
  7. +1
    -0
      src/components/BomWeightingScoreTable/index.ts

+ 25
- 0
src/app/(main)/settings/bomWeighting/page.tsx Переглянути файл

@@ -0,0 +1,25 @@
import { Metadata } from "next";
import { getServerI18n, I18nProvider } from "@/i18n";
import PageTitleBar from "@/components/PageTitleBar";
import BomWeightingScoreTable from "@/components/BomWeightingScoreTable";
import { fetchBomWeightingScores } from "@/app/api/settings/bomWeighting";

export const metadata: Metadata = {
title: "BOM Weighting Score",
};

const BomWeightingScorePage: React.FC = async () => {
const { t } = await getServerI18n("common");
const bomWeightingScores = await fetchBomWeightingScores();

return (
<>
<PageTitleBar title={t("BOM Weighting Score List")} className="mb-4" />
<I18nProvider namespaces={["common"]}>
<BomWeightingScoreTable bomWeightingScores={bomWeightingScores} />
</I18nProvider>
</>
);
};

export default BomWeightingScorePage;

+ 30
- 0
src/app/api/settings/bomWeighting/actions.ts Переглянути файл

@@ -0,0 +1,30 @@
"use server";

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { revalidatePath, revalidateTag } from "next/cache";
import { BomWeightingScoreResult } from ".";

export interface UpdateBomWeightingScoreInputs {
id: number;
name: string;
range: number;
weighting: number;
remarks?: string;
}

export const updateBomWeightingScore = async (data: UpdateBomWeightingScoreInputs) => {
const response = await serverFetchJson<BomWeightingScoreResult>(
`${BASE_API_URL}/bomWeightingScores/${data.id}`,
{
method: "PUT",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);

revalidateTag("bomWeightingScores");
revalidatePath("/(main)/settings/bomWeighting");

return response;
};

+ 23
- 0
src/app/api/settings/bomWeighting/client.ts Переглянути файл

@@ -0,0 +1,23 @@
"use client";

import axiosInstance from "@/app/(main)/axios/axiosInstance";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import { BomWeightingScoreResult } from "./index";

export interface UpdateBomWeightingScoreInputs {
id: number;
name: string;
range: number;
weighting: number;
remarks?: string;
}

export const updateBomWeightingScoreClient = async (
data: UpdateBomWeightingScoreInputs
): Promise<BomWeightingScoreResult> => {
const response = await axiosInstance.put<BomWeightingScoreResult>(
`${NEXT_PUBLIC_API_URL}/bomWeightingScores/${data.id}`,
data
);
return response.data;
};

+ 23
- 0
src/app/api/settings/bomWeighting/index.ts Переглянути файл

@@ -0,0 +1,23 @@
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import "server-only";

export interface BomWeightingScoreResult {
id: number;
code: string;
name: string;
range: number;
weighting: number | string | { value?: number; [key: string]: any };
remarks?: string;
}

export const preloadBomWeightingScores = () => {
fetchBomWeightingScores();
};

export const fetchBomWeightingScores = cache(async () => {
return serverFetchJson<BomWeightingScoreResult[]>(`${BASE_API_URL}/bomWeightingScores`, {
next: { tags: ["bomWeightingScores"] },
});
});

+ 25
- 0
src/app/api/settings/bomWeighting/page.tsx Переглянути файл

@@ -0,0 +1,25 @@
import { Metadata } from "next";
import { getServerI18n, I18nProvider } from "@/i18n";
import PageTitleBar from "@/components/PageTitleBar";
import BomWeightingScoreTable from "@/components/BomWeightingScoreTable";
import { fetchBomWeightingScores } from "@/app/api/settings/bomWeighting";

export const metadata: Metadata = {
title: "BOM Weighting Score",
};

const BomWeightingScorePage: React.FC = async () => {
const { t } = await getServerI18n("common");
const bomWeightingScores = await fetchBomWeightingScores();

return (
<>
<PageTitleBar title={t("BOM Weighting Score List")} className="mb-4" />
<I18nProvider namespaces={["common"]}>
<BomWeightingScoreTable bomWeightingScores={bomWeightingScores} />
</I18nProvider>
</>
);
};

export default BomWeightingScorePage;

+ 231
- 0
src/components/BomWeightingScoreTable/BomWeightingScoreTable.tsx Переглянути файл

@@ -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;

+ 1
- 0
src/components/BomWeightingScoreTable/index.ts Переглянути файл

@@ -0,0 +1 @@
export { default } from "./BomWeightingScoreTable";

Завантаження…
Відмінити
Зберегти