# Conflicts: # src/components/PoDetail/PoDetail.tsxmaster
@@ -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> */} | ||||
@@ -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"] }, | |||||
}); | |||||
}); |
@@ -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) => { | ||||
@@ -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 = { | ||||
@@ -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"] }, | |||||
}); | |||||
}); |
@@ -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"] | |||||
} | |||||
}) | |||||
}) |
@@ -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,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} | |||||
/> | /> | ||||
); | ); | ||||
}; | }; | ||||
@@ -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,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> | ||||
@@ -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 : []} | ||||
/> | /> | ||||
</> | </> | ||||
@@ -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; |
@@ -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"), | ||||
@@ -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", | ||||
@@ -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} | ||||
@@ -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"), | ||||
@@ -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>) => { | ||||
@@ -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": "收貨已上報列表" | |||||
} | } |
@@ -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": "已選擇項目" | ||||
} | |||||
} |
@@ -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": "描述", | ||||