tommy 2 недель назад
Родитель
Сommit
0a758038be
19 измененных файлов: 159 добавлений и 135 удалений
  1. +2
    -0
      src/components/Breadcrumb/Breadcrumb.tsx
  2. +6
    -6
      src/components/PickOrderSearch/EscalationComponent.tsx
  3. +3
    -3
      src/components/PickOrderSearch/PickQcStockInModalVer3.tsx
  4. +7
    -7
      src/components/PickOrderSearch/QcFormVer2.tsx
  5. +88
    -107
      src/components/Shop/TruckLane.tsx
  6. +2
    -2
      src/components/qrCodeHandles/qrCodeHandleTabs.tsx
  7. +3
    -1
      src/i18n/en/navigation.json
  8. +4
    -1
      src/i18n/en/pickOrder.json
  9. +1
    -0
      src/i18n/en/productionProcess.json
  10. +4
    -1
      src/i18n/en/purchaseOrder.json
  11. +2
    -1
      src/i18n/en/qrCodeHandle.json
  12. +12
    -1
      src/i18n/en/stockRecord.json
  13. +1
    -1
      src/i18n/zh/masterDataIssue.json
  14. +1
    -0
      src/i18n/zh/navigation.json
  15. +4
    -1
      src/i18n/zh/pickOrder.json
  16. +1
    -0
      src/i18n/zh/productionProcess.json
  17. +4
    -1
      src/i18n/zh/purchaseOrder.json
  18. +2
    -1
      src/i18n/zh/qrCodeHandle.json
  19. +12
    -1
      src/i18n/zh/stockRecord.json

+ 2
- 0
src/components/Breadcrumb/Breadcrumb.tsx Просмотреть файл

@@ -23,6 +23,8 @@ const pathToLabelKey: { [path: string]: string } = {
"/settings/qcItem": "nav.breadcrumb.qcItem",
"/settings/qcItemAll": "nav.breadcrumb.qcItemAll",
"/settings/qrCodeHandle": "nav.breadcrumb.qrCodeHandle",
"/settings/deliveryOrderFloor": "nav.breadcrumb.deliveryOrderFloor",
"/settings/masterDataIssues": "nav.breadcrumb.masterDataIssues",
"/settings/rss": "nav.breadcrumb.demandForecast",
"/settings/equipment": "nav.breadcrumb.equipment",
"/settings/equipment/MaintenanceEdit": "nav.breadcrumb.equipmentMaintenanceEdit",


+ 6
- 6
src/components/PickOrderSearch/EscalationComponent.tsx Просмотреть файл

@@ -116,8 +116,8 @@ const EscalationComponent: React.FC<Props> = ({
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
<FormControlLabel value="pass" control={<Radio />} label={t("passed")} />
<FormControlLabel value="fail" control={<Radio />} label={t("failed")} />
</RadioGroup>
</FormControl>
): undefined}
@@ -140,24 +140,24 @@ const EscalationComponent: React.FC<Props> = ({
fullWidth
id="quantity"
name="quantity"
label="數量"
label={t("Quantity")}
type="number"
value={formData.quantity}
onChange={handleInputChange}
InputProps={{ inputProps: { min: 1 } }}
placeholder="請輸入數量"
placeholder={t("Enter quantity")}
/>

<TextField
fullWidth
id="message"
name="message"
label="備註"
label={t("remarks")}
multiline
rows={4}
value={formData.message}
onChange={handleInputChange}
placeholder="請輸入您的備註"
placeholder={t("Enter your remark")}
/>

<Stack direction="row" justifyContent="flex-end" gap={1}>


+ 3
- 3
src/components/PickOrderSearch/PickQcStockInModalVer3.tsx Просмотреть файл

@@ -404,7 +404,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) {
submitDialogWithWarning(() => {
closeHandler?.({}, 'escapeKeyDown');
}, t, {title:"有不合格檢查項目,確認接受出庫?", confirmButtonText: "Confirm", html: ""});
}, t, {title: t("confirm_accept_with_fail"), confirmButtonText: t("Confirm"), html: ""});
return;
}
@@ -463,7 +463,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
<FormControlLabel
value="true"
control={<Radio />}
label="合格"
label={t("passed")}
sx={{
color: current.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
@@ -472,7 +472,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
<FormControlLabel
value="false"
control={<Radio />}
label="不合格"
label={t("failed")}
sx={{
color: current.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}


+ 7
- 7
src/components/PickOrderSearch/QcFormVer2.tsx Просмотреть файл

@@ -216,16 +216,16 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<FormControlLabel
value="true"
control={<Radio />}
label="合格"
label={t("passed")}
sx={{
color: currentValue === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
<FormControlLabel
value="false"
control={<Radio />}
label="不合格"
<FormControlLabel
value="false"
control={<Radio />}
label={t("failed")}
sx={{
color: currentValue === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
@@ -419,7 +419,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
field.onChange(value);
}}
>
<FormControlLabel value="true" control={<Radio />} label="接受" />
<FormControlLabel value="true" control={<Radio />} label={t("passed")} />
<Box sx={{mr:2}}>
<TextField
type="number"
@@ -434,7 +434,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
helperText={errors.acceptQty?.message}
/>
</Box>
<FormControlLabel value="false" control={<Radio />} label="不接受及上報" />
<FormControlLabel value="false" control={<Radio />} label={t("failed")} />
</RadioGroup>
)}
/>


+ 88
- 107
src/components/Shop/TruckLane.tsx Просмотреть файл

@@ -5,14 +5,6 @@ import {
Card,
CardContent,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
Paper,
Button,
CircularProgress,
Alert,
@@ -30,14 +22,20 @@ import {
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import SaveIcon from "@mui/icons-material/Save";
import { useState, useMemo } from "react";
import { useState, useMemo, useCallback } from "react";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import { findAllUniqueTruckLaneCombinationsClient, createTruckWithoutShopClient } from "@/app/api/shop/client";
import type { Truck } from "@/app/api/shop/actions";
import SearchBox, { Criterion } from "../SearchBox";
import SearchResults, {
Column,
defaultPagingController,
} from "../SearchResults/SearchResults";
import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil";

type TruckRow = Omit<Truck, "id"> & { id: string | number };

type SearchQuery = {
truckLanceCode: string;
departureTime: string;
@@ -53,8 +51,7 @@ const TruckLane: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [filters, setFilters] = useState<Record<string, string>>({});
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [pagingController, setPagingController] = useState(defaultPagingController);
const [addDialogOpen, setAddDialogOpen] = useState<boolean>(false);
const [newTruck, setNewTruck] = useState({
truckLanceCode: "",
@@ -93,11 +90,80 @@ const TruckLane: React.FC = () => {
});
}, [truckData, filters]);

// Paginated rows
const paginatedRows = useMemo(() => {
const startIndex = page * rowsPerPage;
return filteredRows.slice(startIndex, startIndex + rowsPerPage);
}, [filteredRows, page, rowsPerPage]);
const tableRows = useMemo<TruckRow[]>(
() =>
filteredRows.map((truck) => ({
...truck,
id: truck.id ?? (String(truck.truckLanceCode ?? "").trim() || `truck-${truck.truckLanceCode}`),
})),
[filteredRows],
);

const handleViewDetail = useCallback(
(truck: TruckRow) => {
const truckLanceCode = String(truck.truckLanceCode || "").trim();
if (truckLanceCode) {
const url = new URL(`/settings/shop/truckdetail`, window.location.origin);
url.searchParams.set("truckLanceCode", truckLanceCode);
router.push(url.pathname + url.search);
}
},
[router],
);

const columns = useMemo<Column<TruckRow>[]>(
() => [
{
name: "truckLanceCode",
label: t("TruckLance Code"),
sx: { width: "250px", minWidth: "250px", maxWidth: "250px" },
renderCell: (item) => String(item.truckLanceCode ?? "-"),
},
{
name: "departureTime",
label: t("Departure Time"),
sx: { width: "200px", minWidth: "200px", maxWidth: "200px" },
renderCell: (item) =>
formatDepartureTime(
Array.isArray(item.departureTime)
? item.departureTime
: item.departureTime
? String(item.departureTime)
: null,
),
},
{
name: "storeId",
label: t("Store ID"),
sx: { width: "150px", minWidth: "150px", maxWidth: "150px" },
renderCell: (item) =>
normalizeStoreId(
item.storeId
? typeof item.storeId === "string" || item.storeId instanceof String
? String(item.storeId)
: String(item.storeId)
: null,
),
},
{
name: "id",
label: t("Actions"),
align: "right",
headerAlign: "right",
sx: { width: "150px", minWidth: "150px", maxWidth: "150px" },
renderCell: (item) => (
<Button
size="small"
variant="outlined"
onClick={() => handleViewDetail(item)}
>
{t("View Detail")}
</Button>
),
},
],
[handleViewDetail, t],
);

const handleSearch = async (inputs: Record<string, string>) => {
setLoading(true);
@@ -113,7 +179,7 @@ const TruckLane: React.FC = () => {
});
setTruckData(Array.from(uniqueCodes.values()));
setFilters(inputs);
setPage(0);
setPagingController(defaultPagingController);
} catch (err: any) {
console.error("Failed to load truck lanes:", err);
setError(err?.message ?? String(err) ?? t("Failed to load truck lanes"));
@@ -122,26 +188,6 @@ const TruckLane: React.FC = () => {
}
};

const handlePageChange = (event: unknown, newPage: number) => {
setPage(newPage);
};

const handleRowsPerPageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0); // Reset to first page when changing rows per page
};

const handleViewDetail = (truck: Truck) => {
// Navigate to truck lane detail page using truckLanceCode
const truckLanceCode = String(truck.truckLanceCode || "").trim();
if (truckLanceCode) {
// Use router.push with proper URL encoding
const url = new URL(`/settings/shop/truckdetail`, window.location.origin);
url.searchParams.set("truckLanceCode", truckLanceCode);
router.push(url.pathname + url.search);
}
};

const handleOpenAddDialog = () => {
setNewTruck({
truckLanceCode: "",
@@ -270,77 +316,12 @@ const TruckLane: React.FC = () => {
<CircularProgress />
</Box>
) : (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell sx={{ width: "250px", minWidth: "250px", maxWidth: "250px" }}>
{t("TruckLance Code")}
</TableCell>
<TableCell sx={{ width: "200px", minWidth: "200px", maxWidth: "200px" }}>
{t("Departure Time")}
</TableCell>
<TableCell sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{t("Store ID")}
</TableCell>
<TableCell align="right" sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{t("Actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paginatedRows.length === 0 ? (
<TableRow>
<TableCell colSpan={4} align="center">
<Typography variant="body2" color="text.secondary">
{t("No Truck Lane data available")}
</Typography>
</TableCell>
</TableRow>
) : (
paginatedRows.map((truck) => (
<TableRow key={truck.id ?? `truck-${truck.truckLanceCode}`}>
<TableCell sx={{ width: "250px", minWidth: "250px", maxWidth: "250px" }}>
{String(truck.truckLanceCode ?? "-")}
</TableCell>
<TableCell sx={{ width: "200px", minWidth: "200px", maxWidth: "200px" }}>
{formatDepartureTime(
Array.isArray(truck.departureTime)
? truck.departureTime
: (truck.departureTime ? String(truck.departureTime) : null)
)}
</TableCell>
<TableCell sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{normalizeStoreId(
truck.storeId ? (typeof truck.storeId === 'string' || truck.storeId instanceof String
? String(truck.storeId)
: String(truck.storeId)) : null
)}
</TableCell>
<TableCell align="right" sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
<Button
size="small"
variant="outlined"
onClick={() => handleViewDetail(truck)}
>
{t("View Detail")}
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
<TablePagination
component="div"
count={filteredRows.length}
page={page}
onPageChange={handlePageChange}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleRowsPerPageChange}
rowsPerPageOptions={[5, 10, 25, 50]}
<SearchResults
items={tableRows}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
/>
</TableContainer>
)}
</CardContent>
</Card>


+ 2
- 2
src/components/qrCodeHandles/qrCodeHandleTabs.tsx Просмотреть файл

@@ -38,7 +38,7 @@ const QrCodeHandleTabs: React.FC<QrCodeHandleTabsProps> = ({
equipmentTabContent,
warehouseTabContent,
}) => {
const { t } = useTranslation("common");
const { t: tQrCodeHandle } = useTranslation("qrCodeHandle");
const { t: tUser } = useTranslation("user");
const { t: tWarehouse } = useTranslation("warehouse");
const searchParams = useSearchParams();
@@ -80,7 +80,7 @@ const QrCodeHandleTabs: React.FC<QrCodeHandleTabsProps> = ({
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs value={currentTab} onChange={handleTabChange}>
<Tab label={tUser("User")} />
<Tab label={t("Equipment")} />
<Tab label={tQrCodeHandle("Equipment")} />
<Tab label={tWarehouse("Warehouse")} />
</Tabs>
</Box>


+ 3
- 1
src/i18n/en/navigation.json Просмотреть файл

@@ -44,7 +44,7 @@
"nav.settings.deliveryOrderFloor": "DO floor (supplier)",
"nav.settings.demandForecast": "Demand Forecast Setting",
"nav.settings.bomWeighting": "BOM Weighting Score List",
"nav.settings.masterDataIssues": "Master Data Issues",
"nav.settings.masterDataIssues": "BOM / Item UOM Issues",
"nav.settings.qrCodeHandle": "QR Code Handle",
"nav.settings.importTesting": "Import Testing",
"nav.settings.importExcel": "Import Excel",
@@ -65,6 +65,8 @@
"nav.breadcrumb.qcItemAll": "QC Item All",
"nav.breadcrumb.qrCodeHandle": "QR Code Handle",
"nav.breadcrumb.demandForecast": "Demand Forecast Setting",
"nav.breadcrumb.deliveryOrderFloor": "Delivery Order Floor",
"nav.breadcrumb.masterDataIssues": "BOM / Item UOM Issues",
"nav.breadcrumb.equipment": "Equipment",
"nav.breadcrumb.equipmentMaintenanceEdit": "Maintenance Edit",
"nav.breadcrumb.shop": "Shop And Truck",


+ 4
- 1
src/i18n/en/pickOrder.json Просмотреть файл

@@ -514,5 +514,8 @@
"No lot rows. Select a line in the table above.": "No lot rows. Select a line in the table above.",
"No stock out line for this lot": "No stock out line for this lot",
"No data available for this pick order.": "No data available for this pick order.",
"Report missing or bad items": "Report missing or bad items"
"Report missing or bad items": "Report missing or bad items",
"passed": "Passed",
"failed": "Failed",
"confirm_accept_with_fail": "There are failed QC items. Confirm to accept stock out?"
}

+ 1
- 0
src/i18n/en/productionProcess.json Просмотреть файл

@@ -71,6 +71,7 @@
"Job Order Info": "Job Order Info",
"Job Order No.": "Job Order No.",
"Job Order and Product": "Job Order and Product",
"Job Order Production Process": "Job Order Production Process",
"Job Process Status Dashboard": "Job Process Status Dashboard",
"Job Type": "Job Type",
"Job process detail mode label": "Job process detail mode label",


+ 4
- 1
src/i18n/en/purchaseOrder.json Просмотреть файл

@@ -178,5 +178,8 @@
"Add Record": "Add Record",
"Clean Record": "Reset",
"Create Material": "Create Material",
"Will start binding procedure after scanning item qr code.": "Will start binding procedure after scanning item qr code."
"Will start binding procedure after scanning item qr code.": "Will start binding procedure after scanning item qr code.",
"Quantity": "Quantity",
"Enter quantity": "Enter quantity",
"Enter your remark": "Enter your remark"
}

+ 2
- 1
src/i18n/en/qrCodeHandle.json Просмотреть файл

@@ -1,4 +1,5 @@
{
"PDF Preview": "PDF Preview",
"QR Code Handle": "QR Code Handle"
"QR Code Handle": "QR Code Handle",
"Equipment": "Equipment"
}

+ 12
- 1
src/i18n/en/stockRecord.json Просмотреть файл

@@ -17,5 +17,16 @@
"Balance Qty": "Balance Qty",
"Loading...": "Loading...",
"Type": "Type",
"Status": "Status"
"Status": "Status",
"pending": "Pending",
"completed": "Completed",
"partially_completed": "Partially completed",
"receiving": "Receiving",
"rejected": "Rejected",
"checked": "Checked",
"determine1": "Determine 1",
"lot-change": "Lot change approval",
"qc1": "QC 1",
"qc2": "QC 2",
"qc3": "QC 3"
}

+ 1
- 1
src/i18n/zh/masterDataIssue.json Просмотреть файл

@@ -22,7 +22,7 @@
"masterDataIssue_scope_BOM": "BOM 總表",
"masterDataIssue_scope_BOM_MATERIAL": "BOM 原材料",
"masterDataIssue_scope_ITEM": "貨品",
"masterDataIssue_nav": "數據問題",
"masterDataIssue_nav": "BOM/貨品單位問題",
"masterDataIssue_MISSING_BOM_CODE": "BOM 編號為空",
"masterDataIssue_MISSING_BOM_NAME": "BOM 名稱為空",
"masterDataIssue_MISSING_ITEM": "關聯貨品不存在或已刪除",


+ 1
- 0
src/i18n/zh/navigation.json Просмотреть файл

@@ -10,6 +10,7 @@
"nav.breadcrumb.chartPurchase": "採購",
"nav.breadcrumb.chartWarehouse": "庫存與倉儲",
"nav.breadcrumb.demandForecast": "需求預測設定",
"nav.breadcrumb.deliveryOrderFloor": "送貨單樓層",
"nav.breadcrumb.doWorkbenchEdit": "DO Workbench 詳情",
"nav.breadcrumb.doWorkbenchPick": "DO Workbench 揀貨",
"nav.breadcrumb.doWorkbenchSearch": "DO Workbench 搜索",


+ 4
- 1
src/i18n/zh/pickOrder.json Просмотреть файл

@@ -514,5 +514,8 @@
"No lot rows. Select a line in the table above.": "尚無批號資料。請在上方表格勾選一行提料單明細。",
"No stock out line for this lot": "此批號尚無出庫行,無法提交。",
"No data available for this pick order.": "此提料單沒有可用資料。",
"Report missing or bad items": "報告缺失或不良物品"
"Report missing or bad items": "報告缺失或不良物品",
"passed": "合格",
"failed": "不合格",
"confirm_accept_with_fail": "有不合格檢查項目,確認接受出庫?"
}

+ 1
- 0
src/i18n/zh/productionProcess.json Просмотреть файл

@@ -71,6 +71,7 @@
"Job Order Info": "工單信息",
"Job Order No.": "工單編號",
"Job Order and Product": "工單及貨品",
"Job Order Production Process": "工單生產流程",
"Job Process Status Dashboard": "儀表板 - 工單狀態",
"Job Type": "工單類型",
"Job process detail mode label": "工序格顯示",


+ 4
- 1
src/i18n/zh/purchaseOrder.json Просмотреть файл

@@ -177,5 +177,8 @@
"Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}",
"Add Record": "新增",
"Clean Record": "重置",
"Will start binding procedure after scanning item qr code.": "掃描物品二維碼後將開始綁定流程。"
"Will start binding procedure after scanning item qr code.": "掃描物品二維碼後將開始綁定流程。",
"Quantity": "數量",
"Enter quantity": "請輸入數量",
"Enter your remark": "請輸入您的備註"
}

+ 2
- 1
src/i18n/zh/qrCodeHandle.json Просмотреть файл

@@ -1,4 +1,5 @@
{
"PDF Preview": "PDF 預覽",
"QR Code Handle": "二維碼列印及下載"
"QR Code Handle": "二維碼列印及下載",
"Equipment": "設備"
}

+ 12
- 1
src/i18n/zh/stockRecord.json Просмотреть файл

@@ -17,5 +17,16 @@
"Balance Qty": "庫存數量",
"Loading...": "載入中...",
"Type": "類別",
"Status": "狀態"
"Status": "狀態",
"pending": "待處理",
"completed": "已完成",
"partially_completed": "已部分完成",
"receiving": "收貨中",
"rejected": "已拒絕",
"checked": "已檢查",
"determine1": "上報1",
"lot-change": "批次更換審批",
"qc1": "質檢1",
"qc2": "質檢2",
"qc3": "質檢3"
}

Загрузка…
Отмена
Сохранить