Browse Source

Merge commit 'da4f29f41ba5ddf7d5f65436b14f45f6387b6d97'

# Conflicts:
#	src/components/PoDetail/PoDetail.tsx
master
CANCERYS\kw093 1 week ago
parent
commit
87c7e7fb00
20 changed files with 376 additions and 91 deletions
  1. +4
    -0
      src/app/(main)/settings/items/edit/page.tsx
  2. +7
    -0
      src/app/api/dashboard/index.ts
  3. +1
    -0
      src/app/api/settings/item/actions.ts
  4. +2
    -0
      src/app/api/settings/item/index.ts
  5. +12
    -0
      src/app/api/settings/qcCategory/index.ts
  6. +20
    -0
      src/app/api/shop/index.ts
  7. +4
    -1
      src/components/CreateItem/CreateItem.tsx
  8. +5
    -0
      src/components/CreateItem/CreateItemWrapper.tsx
  9. +36
    -4
      src/components/CreateItem/ProductDetails.tsx
  10. +14
    -2
      src/components/DashboardPage/DashboardPage.tsx
  11. +7
    -1
      src/components/DashboardPage/DashboardWrapper.tsx
  12. +93
    -0
      src/components/DashboardPage/QC/SupervisorQcApproval.tsx
  13. +14
    -14
      src/components/InventorySearch/InventoryLotLineTable.tsx
  14. +14
    -14
      src/components/InventorySearch/InventoryTable.tsx
  15. +105
    -28
      src/components/PoDetail/PoDetail.tsx
  16. +30
    -23
      src/components/PoDetail/PoInputGrid.tsx
  17. +1
    -1
      src/components/SearchResults/SearchResults.tsx
  18. +3
    -1
      src/i18n/zh/dashboard.json
  19. +3
    -2
      src/i18n/zh/inventory.json
  20. +1
    -0
      src/i18n/zh/items.json

+ 4
- 0
src/app/(main)/settings/items/edit/page.tsx View File

@@ -1,3 +1,4 @@
import { fetchQcCategoryCombo } from "@/app/api/settings/qcCategory";
import { SearchParams } from "@/app/utils/fetchUtil"; import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum"; import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem"; import CreateProductMaterial from "@/components/CreateItem";
@@ -17,6 +18,9 @@ const productSetting: React.FC<Props> = async ({ searchParams }) => {
if (!id) { if (!id) {
notFound(); notFound();
} }

fetchQcCategoryCombo()

return ( return (
<> <>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} {/* <Typography variant="h4">{t("Create Material")}</Typography> */}


+ 7
- 0
src/app/api/dashboard/index.ts View File

@@ -6,6 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api"; import { BASE_API_URL } from "../../../config/api";
import { Uom } from "../settings/uom"; import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils"; import { RecordsRes } from "../utils";
import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval";


export interface PoResult { export interface PoResult {
id: number; id: number;
@@ -81,3 +82,9 @@ export const fetchPoWithStockInLines = cache(async (id: number) => {
next: { tags: ["po"] }, next: { tags: ["po"] },
}); });
}); });

export const fetchIqcLogByUser = cache(async () => {
return serverFetchJson<IQCItems[]>(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, {
next: { tags: ["qcLog"] },
});
});

+ 1
- 0
src/app/api/settings/item/actions.ts View File

@@ -35,6 +35,7 @@ export type CreateItemInputs = {
type: string; type: string;
qcChecks: QcChecksInputs[]; qcChecks: QcChecksInputs[];
qcChecks_active: number[]; qcChecks_active: number[];
qcCategoryId: number | undefined;
}; };


export const saveItem = async (data: CreateItemInputs) => { export const saveItem = async (data: CreateItemInputs) => {


+ 2
- 0
src/app/api/settings/item/index.ts View File

@@ -4,6 +4,7 @@ import "server-only";
// import { BASE_API_URL } from "@/config/api"; // import { BASE_API_URL } from "@/config/api";
import { serverFetchJson } from "../../../utils/fetchUtil"; import { serverFetchJson } from "../../../utils/fetchUtil";
import { BASE_API_URL } from "../../../../config/api"; import { BASE_API_URL } from "../../../../config/api";
import { QcCategoryResult } from "../qcCategory";


// import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; // import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions";


@@ -37,6 +38,7 @@ export type ItemsResult = {
action?: any; action?: any;
fgName?: string; fgName?: string;
excludeDate?: string; excludeDate?: string;
qcCategory?: QcCategoryResult;
}; };


export type Result = { export type Result = {


+ 12
- 0
src/app/api/settings/qcCategory/index.ts View File

@@ -9,6 +9,12 @@ export interface QcCategoryResult {
name: string; name: string;
} }


export interface QcCategoryCombo {
id: number;
value: number;
label: string;
}

export const preloadQcCategory = () => { export const preloadQcCategory = () => {
fetchQcCategories(); fetchQcCategories();
}; };
@@ -18,3 +24,9 @@ export const fetchQcCategories = cache(async () => {
next: { tags: ["qcCategories"] }, next: { tags: ["qcCategories"] },
}); });
}); });

export const fetchQcCategoryCombo = cache(async () => {
return serverFetchJson<QcCategoryCombo[]>(`${BASE_API_URL}/qcCategories/combo`, {
next: { tags: ["qcCategoryCombo"] },
});
});

+ 20
- 0
src/app/api/shop/index.ts View File

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

export interface ShopCombo {
id: number;
value: number; // id
label: string;
}

export const fetchSupplierCombo = cache(async() => {
return serverFetchJson<ShopCombo[]>(`${BASE_API_URL}/shop/combo/supplier`, {
method: "GET",
headers: { "Content-Type": "application/json"},
next: {
tags: ["supplierCombo"]
}
})
})

+ 4
- 1
src/components/CreateItem/CreateItem.tsx View File

@@ -29,12 +29,14 @@ import QcDetails from "./QcDetails";
import { ItemQc } from "@/app/api/settings/item"; import { ItemQc } from "@/app/api/settings/item";
import { saveItemQcChecks } from "@/app/api/settings/qcCheck/actions"; import { saveItemQcChecks } from "@/app/api/settings/qcCheck/actions";
import { useGridApiRef } from "@mui/x-data-grid"; import { useGridApiRef } from "@mui/x-data-grid";
import { QcCategoryCombo } from "@/app/api/settings/qcCategory";


type Props = { type Props = {
isEditMode: boolean; isEditMode: boolean;
// type: TypeEnum; // type: TypeEnum;
defaultValues: Partial<CreateItemInputs> | undefined; defaultValues: Partial<CreateItemInputs> | undefined;
qcChecks: ItemQc[]; qcChecks: ItemQc[];
qcCategoryCombo: QcCategoryCombo[]
}; };


const CreateItem: React.FC<Props> = ({ const CreateItem: React.FC<Props> = ({
@@ -42,6 +44,7 @@ const CreateItem: React.FC<Props> = ({
// type, // type,
defaultValues, defaultValues,
qcChecks, qcChecks,
qcCategoryCombo,
}) => { }) => {
// console.log(type) // console.log(type)
const apiRef = useGridApiRef(); const apiRef = useGridApiRef();
@@ -192,7 +195,7 @@ const CreateItem: React.FC<Props> = ({
{serverError} {serverError}
</Typography> </Typography>
)} )}
{tabIndex === 0 && <ProductDetails isEditMode={isEditMode} />}
{tabIndex === 0 && <ProductDetails isEditMode={isEditMode} qcCategoryCombo={qcCategoryCombo}/>}
{tabIndex === 1 && <QcDetails apiRef={apiRef} />} {tabIndex === 1 && <QcDetails apiRef={apiRef} />}
{/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} {/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */}
{/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} {/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */}


+ 5
- 0
src/components/CreateItem/CreateItemWrapper.tsx View File

@@ -5,6 +5,7 @@ import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { fetchItem } from "@/app/api/settings/item"; import { fetchItem } from "@/app/api/settings/item";
import { fetchQcItems } from "@/app/api/settings/qcItem"; import { fetchQcItems } from "@/app/api/settings/qcItem";
import { fetchQcCategoryCombo } from "@/app/api/settings/qcCategory";
interface SubComponents { interface SubComponents {
Loading: typeof CreateItemLoading; Loading: typeof CreateItemLoading;
} }
@@ -37,14 +38,18 @@ const CreateItemWrapper: React.FC<Props> & SubComponents = async ({ id }) => {
maxQty: item?.maxQty, maxQty: item?.maxQty,
qcChecks: qcChecks, qcChecks: qcChecks,
qcChecks_active: activeRows, qcChecks_active: activeRows,
qcCategoryId: item.qcCategory?.id
}; };
} }


const qcCategoryCombo = await fetchQcCategoryCombo();

return ( return (
<CreateItem <CreateItem
isEditMode={Boolean(id)} isEditMode={Boolean(id)}
defaultValues={defaultValues} defaultValues={defaultValues}
qcChecks={qcChecks || []} qcChecks={qcChecks || []}
qcCategoryCombo={qcCategoryCombo}
/> />
); );
}; };


+ 36
- 4
src/components/CreateItem/ProductDetails.tsx View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import { import {
Autocomplete,
Box, Box,
Button, Button,
Card, Card,
@@ -10,11 +11,11 @@ import {
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { Check, Close, EditNote } from "@mui/icons-material"; import { Check, Close, EditNote } from "@mui/icons-material";
import { useFormContext } from "react-hook-form";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import InputDataGrid from "../InputDataGrid"; import InputDataGrid from "../InputDataGrid";


import { useCallback, useMemo, useState } from "react";
import { SyntheticEvent, useCallback, useMemo, useState } from "react";
import { GridColDef, GridRowModel } from "@mui/x-data-grid"; import { GridColDef, GridRowModel } from "@mui/x-data-grid";
import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid";
import { TypeEnum } from "@/app/utils/typeEnum"; import { TypeEnum } from "@/app/utils/typeEnum";
@@ -22,6 +23,7 @@ import { NumberInputProps } from "./NumberInputProps";
import { CreateItemInputs } from "@/app/api/settings/item/actions"; import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { RestartAlt } from "@mui/icons-material"; import { RestartAlt } from "@mui/icons-material";
import { ItemQc } from "@/app/api/settings/item"; import { ItemQc } from "@/app/api/settings/item";
import { QcCategoryCombo } from "@/app/api/settings/qcCategory";
type Props = { type Props = {
// isEditMode: boolean; // isEditMode: boolean;
// type: TypeEnum; // type: TypeEnum;
@@ -29,9 +31,10 @@ type Props = {
// type: TypeEnum; // type: TypeEnum;
defaultValues?: Partial<CreateItemInputs> | undefined; defaultValues?: Partial<CreateItemInputs> | undefined;
qcChecks?: ItemQc[]; qcChecks?: ItemQc[];
qcCategoryCombo: QcCategoryCombo[];
}; };


const ProductDetails: React.FC<Props> = ({ isEditMode }) => {
const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo }) => {
const { const {
t, t,
i18n: { language }, i18n: { language },
@@ -104,6 +107,11 @@ const ProductDetails: React.FC<Props> = ({ isEditMode }) => {
// router.replace(`/settings/product`); // router.replace(`/settings/product`);
console.log("cancel"); console.log("cancel");
}; };

const handleAutoCompleteChange = useCallback((event: SyntheticEvent<Element, Event>, value: QcCategoryCombo, onChange: (...event: any[]) => void) => {
onChange(value.id)
}, [])

return ( return (
<Card sx={{ display: "block" }}> <Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}> <CardContent component={Stack} spacing={4}>
@@ -202,7 +210,31 @@ const ProductDetails: React.FC<Props> = ({ isEditMode }) => {
helperText={errors.maxQty?.message} helperText={errors.maxQty?.message}
/> />
</Grid> </Grid>
<Grid item xs={0}>
<Grid item xs={6}>
<Controller
control={control}
name="qcCategoryId"
render={({ field }) => (
<Autocomplete
disableClearable
options={qcCategoryCombo}
defaultValue={qcCategoryCombo.find(qc => qc.id === field.value)}
onChange={(event, value) => {
handleAutoCompleteChange(event, value, field.onChange)
}}
onBlur={field.onBlur}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label={t("Qc Category")}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Stack <Stack
direction="row" direction="row"
justifyContent="flex-end" justifyContent="flex-end"


+ 14
- 2
src/components/DashboardPage/DashboardPage.tsx View File

@@ -14,15 +14,27 @@ import ApplicationCompletionChart from "./chart/ApplicationCompletionChart";
import OrderCompletionChart from "./chart/OrderCompletionChart"; import OrderCompletionChart from "./chart/OrderCompletionChart";
import DashboardBox from "./Dashboardbox"; import DashboardBox from "./Dashboardbox";
import CollapsibleCard from "./CollapsibleCard"; import CollapsibleCard from "./CollapsibleCard";
type Props = {};
import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval";
type Props = {
iqc: IQCItems[]
};


const DashboardPage: React.FC<Props> = ({}) => {
const DashboardPage: React.FC<Props> = ({
iqc
}) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const router = useRouter(); const router = useRouter();


return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}>
<CollapsibleCard title={t("stock in escalation list")}>
<CardContent>
<SupervisorQcApproval items={iqc || []}/>
</CardContent>
</CollapsibleCard>
</Grid>
<Grid item xs={12}> <Grid item xs={12}>
<CollapsibleCard title={t("Progress chart")}> <CollapsibleCard title={t("Progress chart")}>
<CardContent> <CardContent>


+ 7
- 1
src/components/DashboardPage/DashboardWrapper.tsx View File

@@ -4,6 +4,7 @@ import DashboardPage from "./DashboardPage";
import { Typography } from "@mui/material"; import { Typography } from "@mui/material";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import DashboardLoading from "./DashboardLoading"; import DashboardLoading from "./DashboardLoading";
import { fetchIqcLogByUser } from "@/app/api/dashboard";


// export type SessionWithAbilities = { // export type SessionWithAbilities = {
// abilities: string[] // abilities: string[]
@@ -22,11 +23,16 @@ const DashboardWrapper: React.FC<Props> & SubComponents = async ({
}) => { }) => {
const { t } = await getServerI18n("dashboard"); const { t } = await getServerI18n("dashboard");
// const session: SessionWithAbilities = await getServerSession(authOptions) // const session: SessionWithAbilities = await getServerSession(authOptions)

const [iqcLog] = await Promise.all([
fetchIqcLogByUser()
])
console.log(iqcLog)
return ( return (
<> <>
<Typography variant="h4">{t("Dashboard")}</Typography> <Typography variant="h4">{t("Dashboard")}</Typography>
<DashboardPage <DashboardPage
iqc={iqcLog}
// abilities={session ? session?.abilities : []} // abilities={session ? session?.abilities : []}
/> />
</> </>


+ 93
- 0
src/components/DashboardPage/QC/SupervisorQcApproval.tsx View File

@@ -0,0 +1,93 @@
"use client"

import { Box, Card, CardActionArea, CardContent, CardHeader, Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";

export type IQCItems = {
id: number;
poId: number;
polId: number;
stockInLineId: number;
poCode: string
itemName: string
escalationLevel: string
reason: string
};

type Props = {
items: IQCItems[];
};

const SupervisorQcApproval: React.FC<Props> = ({
items
}) => {
const { t } = useTranslation("dashboard");
const CARD_HEADER = t("stock in escalation list")

const pathname = usePathname();
const router = useRouter();
const [selectedId, setSelectedId] = useState<number | null>(null);

const navigateTo = useCallback(
(item: IQCItems) => {
setSelectedId(item.id);
console.log(pathname)
router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`);
},
[router, pathname]
);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent, item: IQCItems) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
navigateTo(item);
}
},
[navigateTo]
);

return (
<TableContainer component={Paper}>
<Table aria-label="Two column navigable table" size="small">
<TableHead>
<TableRow>
<TableCell>{t("purchase order code")}</TableCell>
<TableCell>{t("item name")}</TableCell>
<TableCell>{t("escalation level")}</TableCell>
<TableCell>{t("reason")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => {
const selected = selectedId === item.id;
return (
<TableRow
key={item.id}
hover
selected={selected}
onClick={() => navigateTo(item)}
// onKeyDown={(e) => handleKeyDown(e, item)}
tabIndex={0}
sx={{ cursor: 'pointer' }}
// aria-label={`${item.name}, ${item.detail}`}
>
<TableCell component="th" scope="row">
{item.poCode}
</TableCell>
<TableCell>{item.itemName}</TableCell>
<TableCell>{item.escalationLevel}</TableCell>
<TableCell>{item.reason}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
};

export default SupervisorQcApproval;

+ 14
- 14
src/components/InventorySearch/InventoryLotLineTable.tsx View File

@@ -82,23 +82,23 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
}, },
{ {
name: "uom", name: "uom",
label: t("Sales UoM"),
align: "left",
headerAlign: "left",
},
{
name: "qtyPerSmallestUnit",
label: t("Available Qty Per Smallest Unit"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "baseUom",
label: t("Base UoM"),
label: t("Stock UoM"),
align: "left", align: "left",
headerAlign: "left", headerAlign: "left",
}, },
// {
// name: "qtyPerSmallestUnit",
// label: t("Available Qty Per Smallest Unit"),
// align: "right",
// headerAlign: "right",
// type: "integer",
// },
// {
// name: "baseUom",
// label: t("Base UoM"),
// align: "left",
// headerAlign: "left",
// },
{ {
name: "expiryDate", name: "expiryDate",
label: t("Expiry Date"), label: t("Expiry Date"),


+ 14
- 14
src/components/InventorySearch/InventoryTable.tsx View File

@@ -41,25 +41,25 @@ const InventoryTable: React.FC<Props> = ({ inventories, pagingController, setPag
}, },
{ {
name: "uomUdfudesc", name: "uomUdfudesc",
label: t("Sales UoM"),
align: "left",
headerAlign: "left",
},
{
name: "qtyPerSmallestUnit",
label: t("Available Qty Per Smallest Unit"),
align: "right",
headerAlign: "right",
type: "integer",
},
{
name: "baseUom",
label: t("Base UoM"),
label: t("Stock UoM"),
align: "left", align: "left",
headerAlign: "left", headerAlign: "left",
}, },
// { // {
// name: "qtyPerSmallestUnit", // name: "qtyPerSmallestUnit",
// label: t("Available Qty Per Smallest Unit"),
// align: "right",
// headerAlign: "right",
// type: "integer",
// },
// {
// name: "baseUom",
// label: t("Base UoM"),
// align: "left",
// headerAlign: "left",
// },
// {
// name: "qtyPerSmallestUnit",
// label: t("Qty Per Smallest Unit"), // label: t("Qty Per Smallest Unit"),
// align: "right", // align: "right",
// headerAlign: "right", // headerAlign: "right",


+ 105
- 28
src/components/PoDetail/PoDetail.tsx View File

@@ -66,11 +66,9 @@ import DoneIcon from "@mui/icons-material/Done";
import { getCustomWidth } from "@/app/utils/commonUtil"; import { getCustomWidth } from "@/app/utils/commonUtil";
import PoInfoCard from "./PoInfoCard"; import PoInfoCard from "./PoInfoCard";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";



import { fetchPoListClient } from "@/app/api/po/actions"; import { fetchPoListClient } from "@/app/api/po/actions";
import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material"; import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material";
import { createStockInLine } from "@/app/api/dashboard/actions";
//import { useRouter } from "next/navigation"; //import { useRouter } from "next/navigation";




@@ -181,20 +179,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
purchaseOrder.pol || [], purchaseOrder.pol || [],
); );
const pathname = usePathname() const pathname = usePathname()
const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [row, setRow] = useState(rows[0]); const [row, setRow] = useState(rows[0]);
const [stockInLine, setStockInLine] = useState(rows[0].stockInLine);
const [stockInLine, setStockInLine] = useState<StockInLine[]>(rows[0].stockInLine);
const [processedQty, setProcessedQty] = useState(rows[0].processed); const [processedQty, setProcessedQty] = useState(rows[0].processed);


// const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status);





//const router = useRouter();
const router = useRouter();
const [poList, setPoList] = useState<PoResult[]>([]); const [poList, setPoList] = useState<PoResult[]>([]);
const [selectedPoId, setSelectedPoId] = useState(po.id); const [selectedPoId, setSelectedPoId] = useState(po.id);
const currentPoId = searchParams.get('id'); const currentPoId = searchParams.get('id');
@@ -297,14 +288,18 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setRows(purchaseOrder.pol || []); setRows(purchaseOrder.pol || []);
}, [purchaseOrder]); }, [purchaseOrder]);


// useEffect(() => {
// setStockInLine([])
// }, []);

function Row(props: { row: PurchaseOrderLine }) { function Row(props: { row: PurchaseOrderLine }) {
const { row } = props; const { row } = props;
const [firstReceiveQty, setFirstReceiveQty] = useState<number>()
// const [firstReceiveQty, setFirstReceiveQty] = useState<number>()
const [secondReceiveQty, setSecondReceiveQty] = useState<number>() const [secondReceiveQty, setSecondReceiveQty] = useState<number>()
const [open, setOpen] = useState(false);
// const [open, setOpen] = useState(false);
const [processedQty, setProcessedQty] = useState(row.processed); const [processedQty, setProcessedQty] = useState(row.processed);
const [currStatus, setCurrStatus] = useState(row.status); const [currStatus, setCurrStatus] = useState(row.status);
const [stockInLine, setStockInLine] = useState(row.stockInLine);
// const [stockInLine, setStockInLine] = useState(row.stockInLine);
const totalWeight = useMemo( const totalWeight = useMemo(
() => calculateWeight(row.qty, row.uom), () => calculateWeight(row.qty, row.uom),
[row.qty, row.uom], [row.qty, row.uom],
@@ -314,6 +309,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
[row.uom], [row.uom],
); );


useEffect(() => {
const polId = searchParams.get("polId") != null ? parseInt(searchParams.get("polId")!) : null
if (polId) {
setStockInLine(rows.find((r) => r.id == polId)!.stockInLine)
}
}, []);

useEffect(() => { useEffect(() => {
if (processedQty === row.qty) { if (processedQty === row.qty) {
setCurrStatus("completed".toUpperCase()); setCurrStatus("completed".toUpperCase());
@@ -330,9 +332,72 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setStockInLine(row.stockInLine); setStockInLine(row.stockInLine);
setProcessedQty(row.processed); setProcessedQty(row.processed);
}; };
const changeStockInLines = useCallback(
(id: number) => {
console.log(id)
//rows = purchaseOrderLine
console.log(rows)
const target = rows.find((r) => r.id === id)
const stockInLine = target!.stockInLine
console.log(stockInLine)
setStockInLine(stockInLine)
setRow(target!)
// console.log(pathname)
// router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`);
},
[rows]
);

const handleStart = useCallback(
() => {
setTimeout(async () => {
// post stock in line
const oldId = row.id;
const postData = {
itemId: row.itemId,
itemNo: row.itemNo,
itemName: row.itemName,
purchaseOrderId: row.purchaseOrderId,
purchaseOrderLineId: row.id,
acceptedQty: secondReceiveQty || 0,
// acceptedQty: row.acceptedQty,
};
if (secondReceiveQty === 0) return
const res = await createStockInLine(postData);
console.log(res);
}, 200);
},
[],
);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const raw = e.target.value;

// Allow empty input
if (raw.trim() === '') {
setSecondReceiveQty(undefined);
return;
}

// Keep digits only
const cleaned = raw.replace(/[^\d]/g, '');
if (cleaned === '') {
// If the user typed only non-digits, keep previous value
return;
}

// Parse and clamp to non-negative integer
const next = Math.max(0, Math.floor(Number(cleaned)));
setSecondReceiveQty(next);
};
return ( return (
<> <>
<TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}>
<TableRow
sx={{ "& > *": { borderBottom: "unset" },
color: "black"
}}
onClick={() => changeStockInLines(row.id)}
>
{/* <TableCell> {/* <TableCell>
<IconButton <IconButton
disabled={purchaseOrder.status.toLowerCase() === "pending"} disabled={purchaseOrder.status.toLowerCase() === "pending"}
@@ -355,9 +420,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
<TableCell align="right">{integerFormatter.format(row.qty)}</TableCell> <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell>
<TableCell align="right">{integerFormatter.format(processedQty)}</TableCell> <TableCell align="right">{integerFormatter.format(processedQty)}</TableCell>
<TableCell align="left">{row.uom?.code}</TableCell> <TableCell align="left">{row.uom?.code}</TableCell>
<TableCell align="right">
{/* <TableCell align="right">
{decimalFormatter.format(totalWeight)} {weightUnit} {decimalFormatter.format(totalWeight)} {weightUnit}
</TableCell>
</TableCell> */}
{/* <TableCell align="left">{weightUnit}</TableCell> */} {/* <TableCell align="left">{weightUnit}</TableCell> */}
<TableCell align="right">{decimalFormatter.format(row.price)}</TableCell> <TableCell align="right">{decimalFormatter.format(row.price)}</TableCell>
{/* <TableCell align="left">{row.expiryDate}</TableCell> */} {/* <TableCell align="left">{row.expiryDate}</TableCell> */}
@@ -371,7 +436,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
type="text" // Use type="text" to allow validation in the change handler type="text" // Use type="text" to allow validation in the change handler
variant="outlined" variant="outlined"
value={secondReceiveQty} value={secondReceiveQty}
// onChange={handleChange}
onChange={handleChange}
InputProps={{ InputProps={{
inputProps: { inputProps: {
min: 0, // Optional: set a minimum value min: 0, // Optional: set a minimum value
@@ -381,7 +446,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
/> />
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
<Button variant="contained">
<Button
variant="contained"
onClick={() =>
handleStart()
}
>
提交 提交
</Button> </Button>
</TableCell> </TableCell>
@@ -503,7 +573,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => { const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => {
switch (field) { switch (field) {
case FIRST_IN_FIELD: case FIRST_IN_FIELD:

return true; return true;
case SECOND_IN_FIELD: case SECOND_IN_FIELD:
return true; return true;
@@ -512,6 +581,14 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
} }
}, []); }, []);


useEffect(() => {
const params = searchParams.get("polId")
if (params) {
const polId = parseInt(params)

}
}, [searchParams])

return ( return (
<> <>
<Stack spacing={2}> <Stack spacing={2}>
@@ -546,12 +623,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
type="text" type="text"
variant="outlined" variant="outlined"
fullWidth fullWidth
InputProps={{
inputProps: {
min: 0,
step: 1
}
}}
// InputProps={{
// inputProps: {
// min: 0,
// step: 1
// }
// }}
/> />
<TextField <TextField
label={t("dnDate")} label={t("dnDate")}
@@ -589,7 +666,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
<TableCell align="right">{t("qty")}</TableCell> <TableCell align="right">{t("qty")}</TableCell>
<TableCell align="right">{t("processed")}</TableCell> <TableCell align="right">{t("processed")}</TableCell>
<TableCell align="left">{t("uom")}</TableCell> <TableCell align="left">{t("uom")}</TableCell>
<TableCell align="right">{t("total weight")}</TableCell>
{/* <TableCell align="right">{t("total weight")}</TableCell> */}
<TableCell align="right">{`${t("price")} (HKD)`}</TableCell> <TableCell align="right">{`${t("price")} (HKD)`}</TableCell>
<TableCell align="left" sx={{ width: '75px' }}>{t("status")}</TableCell> <TableCell align="left" sx={{ width: '75px' }}>{t("status")}</TableCell>
{renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="right">{t("receivedQty")}</TableCell> : undefined} {renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="right">{t("receivedQty")}</TableCell> : undefined}


+ 30
- 23
src/components/PoDetail/PoInputGrid.tsx View File

@@ -121,6 +121,9 @@ function PoInputGrid({
); );
console.log(stockInLine); console.log(stockInLine);
const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
useEffect(() => {
setEntries(stockInLine)
}, [stockInLine])
const [modalInfo, setModalInfo] = useState< const [modalInfo, setModalInfo] = useState<
StockInLine & { qcResult?: PurchaseQcResult[] } StockInLine & { qcResult?: PurchaseQcResult[] }
>(); >();
@@ -278,16 +281,11 @@ const closeNewModal = useCallback(() => {
setNewOpen(true); setNewOpen(true);
}, []); }, []);


// Open modal if `stockInLineId` exists in the URL
useEffect(() => {
if (stockInLineId && !newOpen) {
openNewModal();
}
}, [stockInLineId, newOpen, openNewModal]);

// Button handler to update the URL and open the modal // Button handler to update the URL and open the modal
const handleNewQC = useCallback( const handleNewQC = useCallback(
(id: GridRowId, params: any) => async () => { (id: GridRowId, params: any) => async () => {
console.log(id)
console.log(params)
setBtnIsLoading(true); setBtnIsLoading(true);
setRowModesModel((prev) => ({ setRowModesModel((prev) => ({
...prev, ...prev,
@@ -304,12 +302,21 @@ const closeNewModal = useCallback(() => {
const newParams = new URLSearchParams(searchParams.toString()); const newParams = new URLSearchParams(searchParams.toString());
newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates
router.replace(`${pathname}?${newParams.toString()}`); router.replace(`${pathname}?${newParams.toString()}`);
console.log("hello")
openNewModal()
setBtnIsLoading(false); setBtnIsLoading(false);
}, 200); }, 200);
}, },
[fetchQcDefaultValue, searchParams, router, pathname]
[fetchQcDefaultValue, openNewModal, pathname, router, searchParams]
); );

// Open modal if `stockInLineId` exists in the URL
useEffect(() => {
if (stockInLineId) {
console.log("heeloo")
console.log(stockInLineId)
handleNewQC(stockInLineId, apiRef.current.getRow(stockInLineId));
}
}, [stockInLineId, newOpen, handleNewQC, apiRef]);
const handleEscalation = useCallback( const handleEscalation = useCallback(
(id: GridRowId, params: any) => () => { (id: GridRowId, params: any) => () => {
// setBtnIsLoading(true); // setBtnIsLoading(true);
@@ -476,20 +483,20 @@ const closeNewModal = useCallback(() => {
return params.row.uom.code; return params.row.uom.code;
}, },
}, },
{
field: "weight",
headerName: t("weight"),
width: 120,
// flex: 0.5,
renderCell: (params) => {
const weight = calculateWeight(
params.row.acceptedQty,
params.row.uom,
);
const weightUnit = returnWeightUnit(params.row.uom);
return `${decimalFormatter.format(weight)} ${weightUnit}`;
},
},
// {
// field: "weight",
// headerName: t("weight"),
// width: 120,
// // flex: 0.5,
// renderCell: (params) => {
// const weight = calculateWeight(
// params.row.acceptedQty,
// params.row.uom,
// );
// const weightUnit = returnWeightUnit(params.row.uom);
// return `${decimalFormatter.format(weight)} ${weightUnit}`;
// },
// },
{ {
field: "status", field: "status",
headerName: t("status"), headerName: t("status"),


+ 1
- 1
src/components/SearchResults/SearchResults.tsx View File

@@ -281,7 +281,7 @@ function SearchResults<T extends ResultWithId>({
setCheckboxIds(newSelected); setCheckboxIds(newSelected);
} }
}, },
[checkboxIds],
[checkboxIds, setCheckboxIds],
); );


const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {


+ 3
- 1
src/i18n/zh/dashboard.json View File

@@ -29,5 +29,7 @@
"Pending application": "待處理提料申請", "Pending application": "待處理提料申請",
"pending inspection material": "待品檢物料", "pending inspection material": "待品檢物料",
"inspected material": "已品檢物料", "inspected material": "已品檢物料",
"total material": "物料總數"
"total material": "物料總數",

"stock in escalation list": "收貨已上報列表"
} }

+ 3
- 2
src/i18n/zh/inventory.json View File

@@ -8,12 +8,13 @@
"UoM": "單位", "UoM": "單位",
"mat": "物料", "mat": "物料",
"fg": "成品", "fg": "成品",
"Available Qty": "可用數量 (銷售單位)",
"Available Qty": "可用數量 (倉存單位)",
"Sales UoM": "銷售單位", "Sales UoM": "銷售單位",
"Stock UoM": "倉存單位",
"Available Qty Per Smallest Unit": "可用數量 (基本單位)", "Available Qty Per Smallest Unit": "可用數量 (基本單位)",
"Base UoM": "基本單位", "Base UoM": "基本單位",
"Lot No": "批號", "Lot No": "批號",
"Expiry Date": "到期日", "Expiry Date": "到期日",
"No items are selected yet.": "未選擇項目", "No items are selected yet.": "未選擇項目",
"Item selected": "已選擇項目" "Item selected": "已選擇項目"
}
}

+ 1
- 0
src/i18n/zh/items.json View File

@@ -10,6 +10,7 @@
"Product / Material": "產品 / 材料", "Product / Material": "產品 / 材料",
"Product / Material Details": "產品 / 材料詳情", "Product / Material Details": "產品 / 材料詳情",
"Qc items": "QC 項目", "Qc items": "QC 項目",
"Qc Category": "質檢模板",
"Name": "名稱", "Name": "名稱",
"name": "名稱", "name": "名稱",
"description": "描述", "description": "描述",


Loading…
Cancel
Save