Kaynağa Gözat

Merge branch 'MergeProblem1' of http://svn.2fi-solutions.com:8300/derek/FPSMS-frontend into MergeProblem1

reset-do-picking-order
CANCERYS\kw093 2 hafta önce
ebeveyn
işleme
33cf1752b4
10 değiştirilmiş dosya ile 387 ekleme ve 13 silme
  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
  8. +16
    -11
      src/components/NavigationContent/NavigationContent.tsx
  9. +12
    -0
      src/i18n/zh/common.json
  10. +1
    -2
      src/i18n/zh/jo.json

+ 25
- 0
src/app/(main)/settings/bomWeighting/page.tsx Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

+ 16
- 11
src/components/NavigationContent/NavigationContent.tsx Dosyayı Görüntüle

@@ -196,17 +196,22 @@ const NavigationContent: React.FC = () => {
path: "/settings/user", path: "/settings/user",
requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN], requiredAbility: [AUTH.VIEW_USER, AUTH.ADMIN],
}, },
{
icon: <Group />,
label: "User Group",
path: "/settings/user",
requiredAbility: [AUTH.VIEW_GROUP, AUTH.ADMIN],
},
//{
// icon: <Group />,
// label: "User Group",
// path: "/settings/user",
// requiredAbility: [AUTH.VIEW_GROUP, AUTH.ADMIN],
//},
{ {
icon: <Category />, icon: <Category />,
label: "Items", label: "Items",
path: "/settings/items", path: "/settings/items",
}, },
{
icon: <ViewModule />,
label: "BOM Weighting Score List",
path: "/settings/bomWeighting",
},
{ {
icon: <Storefront />, icon: <Storefront />,
label: "ShopAndTruck", label: "ShopAndTruck",
@@ -232,11 +237,11 @@ const NavigationContent: React.FC = () => {
label: "Printer", label: "Printer",
path: "/settings/printer", path: "/settings/printer",
}, },
{
icon: <Person />,
label: "Customer",
path: "/settings/user",
},
//{
// icon: <Person />,
// label: "Customer",
// path: "/settings/user",
//},
{ {
icon: <VerifiedUser />, icon: <VerifiedUser />,
label: "QC Check Item", label: "QC Check Item",


+ 12
- 0
src/i18n/zh/common.json Dosyayı Görüntüle

@@ -77,6 +77,18 @@
"user": "用戶", "user": "用戶",
"User Group": "用戶群組", "User Group": "用戶群組",
"Items": "物料", "Items": "物料",
"BOM Weighting Score List": "物料清單",
"Column Name": "欄位名稱",
"Range": "範圍",
"Weighting": "權重",
"Edit": "編輯",
"Edit BOM Weighting Score": "編輯物料清單",
"Save": "儲存",
"Saving": "儲存中",
"Cancel": "取消",
"Update Success": "更新成功",
"Update Failed": "更新失敗",
"Remarks": "備註",
"Release": "放單", "Release": "放單",
"Demand Forecast Setting": "需求預測設定", "Demand Forecast Setting": "需求預測設定",
"EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號", "EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號",


+ 1
- 2
src/i18n/zh/jo.json Dosyayı Görüntüle

@@ -572,6 +572,5 @@
"seq": "序號", "seq": "序號",
"Handled By": "處理者", "Handled By": "處理者",
"Job Order Pick Execution": "工單提料", "Job Order Pick Execution": "工單提料",
"Finish": "完成"

"Finish": "完成"
} }

Yükleniyor…
İptal
Kaydet