| @@ -15,6 +15,8 @@ export interface EscalationResult { | |||
| poId?: number; | |||
| reason?: string; | |||
| handlerId?: number; | |||
| status: string; | |||
| recordDate: string; | |||
| itemName?: string; | |||
| demandQty?: number; | |||
| acceptedQty?: number; | |||
| @@ -42,6 +42,7 @@ export interface PurchaseQcResult{ | |||
| qcPassed?: boolean; | |||
| failQty?: number; | |||
| remarks?: string; | |||
| escalationLogId?: number; | |||
| } | |||
| export interface StockInInput { | |||
| status: string; | |||
| @@ -8,7 +8,7 @@ import { | |||
| } from "../../utils/fetchUtil"; | |||
| import { BASE_API_URL } from "../../../config/api"; | |||
| import { revalidateTag } from "next/cache"; | |||
| import { UserDetail, UserResult } from "."; | |||
| import { EscalationCombo, UserDetail, UserResult } from "."; | |||
| import { cache } from "react"; | |||
| export interface UserInputs { | |||
| @@ -102,3 +102,9 @@ export const adminChangePassword = async (data: any) => { | |||
| }, | |||
| ); | |||
| }; | |||
| export const fetchEscalationCombo = async () => { | |||
| return serverFetchJson<EscalationCombo>(`${BASE_API_URL}/user/escalation-combo`, { | |||
| next: { tags: ["escalationCombo"]} | |||
| }) | |||
| }; | |||
| @@ -67,7 +67,7 @@ export const fetchPwRules = cache(async () => { | |||
| }); | |||
| export const fetchEscalationCombo = cache(async () => { | |||
| return serverFetchJson<EscalationCombo>(`${BASE_API_URL}/user/escalation-combo`, { | |||
| return serverFetchJson<EscalationCombo[]>(`${BASE_API_URL}/user/escalation-combo`, { | |||
| next: { tags: ["escalationCombo"]} | |||
| }) | |||
| }) | |||
| @@ -36,7 +36,7 @@ export const INPUT_TIME_FORMAT = "HH:mm:ss"; | |||
| export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | |||
| export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => { | |||
| export const arrayToDayjs = (arr: ConfigType | (number | undefined)[], showTime: boolean = false) => { | |||
| const isValidNumber = ( | |||
| item: ListIterateeCustom<number | undefined, boolean>, | |||
| ): boolean => typeof item === "number" && !isNaN(item) && isFinite(item); | |||
| @@ -47,6 +47,10 @@ export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => { | |||
| // [year, month, day] | |||
| // tempArr = take(arr, 3); | |||
| tempArr = `${arr[0]?.toString().padStart(4, "0")}-${arr[1]?.toString().padStart(2, "0")}-${arr[2]?.toString().padStart(2, "0")}`; | |||
| if (showTime) { | |||
| // [year, month, day, hour, minute, second] | |||
| tempArr += ` ${arr[3]?.toString().padStart(2, "0")}-${arr[4]?.toString().padStart(2, "0")}-${arr[5]?.toString().padStart(2, "0")}`; | |||
| } | |||
| } | |||
| return dayjs(tempArr as ConfigType); | |||
| @@ -56,6 +60,10 @@ export const arrayToDateString = (arr: ConfigType | (number | undefined)[]) => { | |||
| return arrayToDayjs(arr).format(OUTPUT_DATE_FORMAT); | |||
| }; | |||
| export const arrayToDateTimeString = (arr: ConfigType | (number | undefined)[]) => { | |||
| return arrayToDayjs(arr, true).format(`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`); | |||
| }; | |||
| export const arrayToInputDateString = (arr: ConfigType | (number | undefined)[]) => { | |||
| return arrayToDayjs(arr).format(INPUT_DATE_FORMAT); | |||
| }; | |||
| @@ -33,7 +33,7 @@ const DashboardPage: React.FC<Props> = ({ | |||
| <ThemeProvider theme={theme}> | |||
| <Grid container spacing={2}> | |||
| <Grid item xs={12}> | |||
| <CollapsibleCard title={t("Escalation List")}> | |||
| <CollapsibleCard title={t("My Escalation List")}> | |||
| <CardContent> | |||
| <EscalationLogTable items={escalationLogs || []}/> | |||
| </CardContent> | |||
| @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { Column } from "@/components/SearchResults"; | |||
| import SearchResults from "@/components/SearchResults/SearchResults"; | |||
| import { arrayToDateString } from "@/app/utils/formatUtil"; | |||
| import { arrayToDateString, arrayToDateTimeString } from "@/app/utils/formatUtil"; | |||
| export type IQCItems = { | |||
| id: number; | |||
| @@ -22,11 +22,12 @@ export type IQCItems = { | |||
| }; | |||
| type Props = { | |||
| type?: "dashboard" | "qc"; | |||
| items: EscalationResult[]; | |||
| }; | |||
| const EscalationLogTable: React.FC<Props> = ({ | |||
| items | |||
| type = "dashboard", items | |||
| }) => { | |||
| const { t } = useTranslation("dashboard"); | |||
| const CARD_HEADER = t("stock in escalation list") | |||
| @@ -58,19 +59,66 @@ const EscalationLogTable: React.FC<Props> = ({ | |||
| // [navigateTo] | |||
| // ); | |||
| const columns = useMemo<Column<EscalationResult>[]>( | |||
| const base_col = useMemo<Column<EscalationResult>[]>( | |||
| () => [ | |||
| { | |||
| name: "handler", | |||
| label: t("Responsible for handling colleagues"), | |||
| sx: { width: "20%", minWidth: 200, maxWidth: 500 }, | |||
| name: "qcFailCount", | |||
| label: t("QC Fail Count"), | |||
| align: "right", | |||
| headerAlign: "right", | |||
| sx: { width: "10%", minWidth: 120 }, | |||
| renderCell: (params) => { | |||
| return `${params.qcFailCount} / ${params.qcTotalCount}` | |||
| } | |||
| }, | |||
| { | |||
| name: "reason", | |||
| label: t("Reason"), | |||
| sx: { width: "30%", minWidth: 150 }, | |||
| }, | |||
| { | |||
| name: "status", | |||
| label: t("escalationStatus"), | |||
| sx: { width: "10%", minWidth: 150 }, | |||
| renderCell: (params) => { | |||
| return t(params.status); | |||
| // return t(`${params.status}`); | |||
| } | |||
| }, | |||
| ], []) | |||
| const columns_dashboard = useMemo<Column<EscalationResult>[]>( | |||
| () => [ | |||
| { | |||
| name: "poCode", | |||
| label: t("Po Code"), | |||
| align: "left", | |||
| headerAlign: "left", | |||
| sx: { width: "15%", minWidth: 100 }, | |||
| }, | |||
| { | |||
| name: "recordDate", | |||
| label: t("escalated date"), | |||
| align: "right", | |||
| headerAlign: "right", | |||
| sx: { width: "10%", minWidth: 100 }, | |||
| renderCell: (params) => { | |||
| return arrayToDateString(params.recordDate); | |||
| } | |||
| }, | |||
| { | |||
| name: "itemName", | |||
| label: t("Item Name"), | |||
| align: "left", | |||
| headerAlign: "left", | |||
| sx: { width: "15%", minWidth: 100 }, | |||
| }, | |||
| { | |||
| name: "acceptedQty", | |||
| label: t("Received Qty"), | |||
| align: "right", | |||
| headerAlign: "right", | |||
| sx: { width: "10%", minWidth: 100 }, | |||
| sx: { width: "5%", minWidth: 100 }, | |||
| }, | |||
| { | |||
| name: "purchaseUomDesc", | |||
| @@ -85,37 +133,55 @@ const EscalationLogTable: React.FC<Props> = ({ | |||
| return params.dnDate ? arrayToDateString(params.dnDate) : "N/A" | |||
| } | |||
| }, | |||
| ...base_col | |||
| ], []) | |||
| const columns_qc = useMemo<Column<EscalationResult>[]>( | |||
| () => [ | |||
| { | |||
| name: "qcFailCount", | |||
| label: t("QC Fail Count"), | |||
| name: "handler", | |||
| label: t("Responsible for handling colleagues"), | |||
| sx: { width: "20%", minWidth: 200, maxWidth: 500 }, | |||
| renderCell: (params) => { | |||
| const department = params.handlerDepartment; | |||
| const name = params.handlerName; | |||
| const title = params.handlerTitle; | |||
| return ( | |||
| (department ? `${department} - ` : "") + name + (title ? ` (${title})` : "") | |||
| ); | |||
| } | |||
| }, | |||
| { | |||
| name: "recordDate", | |||
| label: t("escalated date"), | |||
| align: "right", | |||
| headerAlign: "right", | |||
| sx: { width: "15%", minWidth: 120 }, | |||
| sx: { width: "10%", minWidth: 100 }, | |||
| renderCell: (params) => { | |||
| return `${params.qcFailCount} / ${params.qcTotalCount}` | |||
| return arrayToDateString(params.recordDate); | |||
| } | |||
| }, | |||
| // { | |||
| // name: "qcTotalCount", | |||
| // label: t("QC Completed Count"), | |||
| // align: "right", | |||
| // headerAlign: "right" | |||
| // flex: 1, | |||
| // }, | |||
| // { | |||
| // name: "qcFailCount", | |||
| // label: t("QC Fail Count"), | |||
| // align: "right", | |||
| // headerAlign: "right" | |||
| // flex: 1, | |||
| // }, | |||
| { | |||
| name: "reason", | |||
| label: t("Reason"), | |||
| sx: { width: "30%", minWidth: 150 }, | |||
| name: "acceptedQty", | |||
| label: t("Received Qty"), | |||
| align: "right", | |||
| headerAlign: "right", | |||
| sx: { width: "5%", minWidth: 100 }, | |||
| }, | |||
| { | |||
| name: "purchaseUomDesc", | |||
| label: t("Purchase UoM"), | |||
| sx: { width: "15%", minWidth: 120 }, | |||
| }, | |||
| ...base_col | |||
| ], []) | |||
| const getColumnByType = (type : Props['type']) => { | |||
| switch (type) { | |||
| case "qc": return columns_qc; | |||
| default: return columns_dashboard; | |||
| } | |||
| } | |||
| {/* return ( | |||
| <TableContainer component={Paper}> | |||
| <Table aria-label="Two column navigable table" size="small"> | |||
| @@ -158,7 +224,7 @@ const EscalationLogTable: React.FC<Props> = ({ | |||
| <SearchResults | |||
| onRowClick={onRowClick} | |||
| items={items} | |||
| columns={columns} | |||
| columns={getColumnByType(type)} | |||
| isAutoPaging={false} | |||
| /> | |||
| ) | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState, ChangeEvent, FormEvent, Dispatch } from 'react'; | |||
| import React, { useState, ChangeEvent, FormEvent, Dispatch, useEffect } from 'react'; | |||
| import { | |||
| Box, | |||
| Button, | |||
| @@ -21,8 +21,10 @@ import { SelectChangeEvent } from '@mui/material/Select'; | |||
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |||
| import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { Controller, useFormContext } from 'react-hook-form'; | |||
| import { EscalationInput, ModalFormInput } from '@/app/api/po/actions'; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| import { FireExtinguisher } from '@mui/icons-material'; | |||
| interface NameOption { | |||
| value: string; | |||
| @@ -39,15 +41,17 @@ interface Props { | |||
| forSupervisor: boolean | |||
| isCollapsed: boolean | |||
| setIsCollapsed: Dispatch<React.SetStateAction<boolean>> | |||
| escalationCombo: EscalationCombo[] | |||
| } | |||
| const EscalationComponent: React.FC<Props> = ({ | |||
| const EscalationComponent: React.FC<Props> = ({ | |||
| forSupervisor, | |||
| isCollapsed, | |||
| setIsCollapsed | |||
| }) => { | |||
| setIsCollapsed, | |||
| escalationCombo | |||
| }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const [formData, setFormData] = useState<FormData>({ | |||
| name: '', | |||
| quantity: '', | |||
| @@ -74,7 +78,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<ModalFormInput>(); | |||
| } = useFormContext<ModalFormInput>(); | |||
| const handleInputChange = ( | |||
| event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string> | |||
| @@ -86,7 +90,8 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| })); | |||
| }; | |||
| const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {console.log("called this?"); | |||
| const handleSubmit = (e: FormEvent<HTMLFormElement>): void => { | |||
| console.log("Handled Submit?"); | |||
| e.preventDefault(); | |||
| console.log('表單已提交:', formData); | |||
| // 處理表單提交 | |||
| @@ -99,9 +104,9 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| return ( | |||
| // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> | |||
| <> | |||
| <Paper sx={{padding: 2}}> | |||
| {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | |||
| <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> | |||
| <Paper sx={{ padding: 2 }}> | |||
| {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | |||
| <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> | |||
| <Box sx={{ display: 'flex', alignItems: 'center' }}> | |||
| <Typography variant="body1">上報結果</Typography> | |||
| {/* {isCollapsed ? ( | |||
| @@ -110,7 +115,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| <ExpandMoreIcon sx={{ ml: 1 }} /> | |||
| )} */} | |||
| </Box> | |||
| {/* <FormControlLabel | |||
| {/* <FormControlLabel | |||
| control={ | |||
| <Checkbox | |||
| checked={isCollapsed} | |||
| @@ -129,11 +134,34 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| </Box> | |||
| } | |||
| /> */} | |||
| </Box> | |||
| <Collapse in={isCollapsed}> | |||
| <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||
| <FormControl fullWidth> | |||
| <select | |||
| </Box> | |||
| <Collapse in={isCollapsed}> | |||
| <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||
| <FormControl fullWidth> | |||
| <Controller | |||
| control={control} | |||
| name="escalationLog.handlerId" | |||
| render={({field, fieldState, formState}) => { | |||
| return (<Autocomplete | |||
| noOptionsText={t("No Option")} | |||
| disableClearable | |||
| fullWidth | |||
| // value={} | |||
| // {...register("escalationLog.handlerId", { | |||
| // required: "handler required!", | |||
| // })} | |||
| // onChange={() => handleInputChange} | |||
| onChange={(event, value) => { | |||
| field.onChange(value.id); | |||
| }} | |||
| // getOptionLabel={(option) => option.label} | |||
| options={escalationCombo} | |||
| renderInput={(params) => <TextField {...params} error={false} label={"負責處理同事"}/>} | |||
| />) | |||
| }} | |||
| /> | |||
| {/* <select | |||
| id="handlerId" | |||
| // name="name" | |||
| // value={formData.name} | |||
| @@ -148,33 +176,33 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| {option.label} | |||
| </option> | |||
| ))} | |||
| </select> | |||
| </FormControl> | |||
| {forSupervisor ? ( | |||
| <FormControl> | |||
| <RadioGroup | |||
| row | |||
| aria-labelledby="demo-radio-buttons-group-label" | |||
| defaultValue="pass" | |||
| name="radio-buttons-group" | |||
| > | |||
| <FormControlLabel value="pass" control={<Radio />} label="合格" /> | |||
| <FormControlLabel value="fail" control={<Radio />} label="不合格" /> | |||
| </RadioGroup> | |||
| </select> */} | |||
| </FormControl> | |||
| ): undefined} | |||
| {forSupervisor && (<TextField | |||
| fullWidth | |||
| id="decision" | |||
| name="decision" | |||
| label="上報結果" | |||
| type="radio" | |||
| // value={formData.decision} | |||
| onChange={handleInputChange} | |||
| InputProps={{ inputProps: { min: 1 } }} | |||
| placeholder="請決定上報結果" | |||
| />)} | |||
| {/* <TextField | |||
| {forSupervisor ? ( | |||
| <FormControl> | |||
| <RadioGroup | |||
| row | |||
| aria-labelledby="demo-radio-buttons-group-label" | |||
| defaultValue="pass" | |||
| name="radio-buttons-group" | |||
| > | |||
| <FormControlLabel value="pass" control={<Radio />} label="合格" /> | |||
| <FormControlLabel value="fail" control={<Radio />} label="不合格" /> | |||
| </RadioGroup> | |||
| </FormControl> | |||
| ) : undefined} | |||
| {forSupervisor && (<TextField | |||
| fullWidth | |||
| id="decision" | |||
| name="decision" | |||
| label="上報結果" | |||
| type="radio" | |||
| // value={formData.decision} | |||
| onChange={handleInputChange} | |||
| InputProps={{ inputProps: { min: 1 } }} | |||
| placeholder="請決定上報結果" | |||
| />)} | |||
| {/* <TextField | |||
| fullWidth | |||
| id="quantity" | |||
| name="quantity" | |||
| @@ -186,22 +214,22 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| placeholder="請輸入數量" | |||
| /> */} | |||
| <TextField | |||
| fullWidth | |||
| id="reason" | |||
| // name="reason" | |||
| {...register("escalationLog.reason", { | |||
| required: "reason required!", | |||
| })} | |||
| label="上報原因" | |||
| multiline | |||
| rows={4} | |||
| // value={formData.reason} | |||
| onChange={handleInputChange} | |||
| placeholder="請輸入上報原因" | |||
| /> | |||
| <TextField | |||
| fullWidth | |||
| id="reason" | |||
| // name="reason" | |||
| {...register("escalationLog.reason", { | |||
| required: "reason required!", | |||
| })} | |||
| label="上報原因" | |||
| multiline | |||
| rows={4} | |||
| // value={formData.reason} | |||
| onChange={handleInputChange} | |||
| placeholder="請輸入上報原因" | |||
| /> | |||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| type="submit" | |||
| variant="contained" | |||
| @@ -210,9 +238,9 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| {t("update qc info")} | |||
| </Button> | |||
| </Stack> */} | |||
| </Box> | |||
| </Collapse> | |||
| </Paper> | |||
| </Box> | |||
| </Collapse> | |||
| </Paper> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -80,6 +80,7 @@ import { debounce } from "lodash"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| //import { useRouter } from "next/navigation"; | |||
| @@ -88,6 +89,7 @@ type Props = { | |||
| qc: QcItemWithChecks[]; | |||
| warehouse: WarehouseResult[]; | |||
| printerCombo: PrinterCombo[]; | |||
| escalationCombo: EscalationCombo[]; | |||
| }; | |||
| type EntryError = | |||
| @@ -190,7 +192,7 @@ interface PolInputResult { | |||
| dnQty: number, | |||
| } | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo, escalationCombo }) => { | |||
| const cameras = useContext(CameraContext); | |||
| // console.log(cameras); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -217,6 +219,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| useEffect(() => { | |||
| if (defaultPolId) { | |||
| setSelectedRow(rows.find((r) => r.id.toString() === defaultPolId) ?? null) | |||
| console.log("%c StockIn:", "color:green", selectedRow); | |||
| } | |||
| }, []) | |||
| @@ -263,6 +266,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const result = await fetchPoInClient(parseInt(poId)); | |||
| console.log(result) | |||
| if (result) { | |||
| console.log("%c Fetched PO:", "color:orange", result); | |||
| setPurchaseOrder(result); | |||
| setRows(result.pol || []); | |||
| if (result.pol && result.pol.length > 0) { | |||
| @@ -506,7 +510,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| <TableCell align="left">{row.itemNo}</TableCell> | |||
| <TableCell align="left">{row.itemName}</TableCell> | |||
| <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell> | |||
| <TableCell align="right">{integerFormatter.format(processedQty)}</TableCell> | |||
| <TableCell align="right">{integerFormatter.format(row.processed)}</TableCell> | |||
| <TableCell align="left">{row.uom?.code}</TableCell> | |||
| {/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> */} | |||
| <TableCell align="right">{decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio)}</TableCell> | |||
| @@ -814,7 +818,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| <TableCell align="right">{t("qty")}</TableCell> | |||
| <TableCell align="right">{t("processed")}</TableCell> | |||
| <TableCell align="left">{t("uom")}</TableCell> | |||
| <TableCell align="right">{t("Stock In Qty")}</TableCell> | |||
| <TableCell align="right">{t("receivedTotal")}</TableCell> | |||
| <TableCell align="left">{t("Stock UoM")}</TableCell> | |||
| {/* <TableCell align="right">{t("total weight")}</TableCell> */} | |||
| {/* <TableCell align="right">{`${t("price")} (HKD)`}</TableCell> */} | |||
| @@ -861,6 +865,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| fetchPoDetail={fetchPoDetail} | |||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
| printerCombo={printerCombo} | |||
| escalationCombo={escalationCombo} | |||
| /> | |||
| </Box> | |||
| </TableCell> | |||
| @@ -878,7 +883,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| /> */} | |||
| </Grid> | |||
| </Stack> | |||
| {itemInfo !== undefined && ( | |||
| {/* {itemInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| @@ -889,7 +894,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| itemDetail={itemInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| )} */} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -26,18 +26,18 @@ const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||
| poWithStockInLine, | |||
| warehouse, | |||
| qc, | |||
| escalationCombo, | |||
| printerCombo, | |||
| escalationCombo, | |||
| ] = await Promise.all([ | |||
| fetchPoWithStockInLines(id), | |||
| fetchWarehouseList(), | |||
| fetchQcItemCheck(), | |||
| fetchEscalationCombo(), | |||
| fetchPrinterCombo() | |||
| fetchPrinterCombo(), | |||
| fetchEscalationCombo() | |||
| ]); | |||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
| return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} printerCombo={printerCombo} />; | |||
| console.log("%c pol:", "color:green", poWithStockInLine); | |||
| return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} printerCombo={printerCombo} escalationCombo={escalationCombo}/>; | |||
| }; | |||
| PoDetailWrapper.Loading = PoDetailLoading; | |||
| @@ -66,6 +66,7 @@ import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| interface ResultWithId { | |||
| id: number; | |||
| @@ -82,6 +83,7 @@ interface Props { | |||
| fetchPoDetail: (poId: string) => void; | |||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
| printerCombo: PrinterCombo[]; | |||
| escalationCombo: EscalationCombo[]; | |||
| } | |||
| export type StockInLineEntryError = { | |||
| @@ -122,7 +124,8 @@ function PoInputGrid({ | |||
| warehouse, | |||
| fetchPoDetail, | |||
| handleMailTemplateForStockInLine, | |||
| printerCombo | |||
| printerCombo, | |||
| escalationCombo | |||
| }: Props) { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -263,8 +266,8 @@ function PoInputGrid({ | |||
| })); | |||
| const qcResult = await fetchQcDefaultValue(id); | |||
| const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||
| console.log(params.row); | |||
| console.log(qcResult); | |||
| // console.log(params.row); | |||
| console.log("Fetched QC Result:", qcResult); | |||
| setModalInfo({ | |||
| ...params.row, | |||
| @@ -580,8 +583,11 @@ const closeNewModal = useCallback(() => { | |||
| const handlerId = params.row.handlerId | |||
| const status = params.row.status | |||
| return (<span style={{ | |||
| color: (status == "escalated")? "red":"inherit"}}> | |||
| {t(`${params.row.status}`)} | |||
| color: | |||
| (status == "escalated")? "red": | |||
| (status == "rejected" || status == "partially_completed") ? "orange" : "inherit"}} | |||
| > | |||
| {t(`${params.row.status}`)} | |||
| </span>); | |||
| }, | |||
| }, | |||
| @@ -622,6 +628,7 @@ const closeNewModal = useCallback(() => { | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ width: '150px' }} | |||
| disabled={params.row.status != "rejected" && params.row.status != "partially_completed"} | |||
| onClick={() => handleMailTemplateForStockInLine(params.row.id as number)} | |||
| > | |||
| {t("email supplier")} | |||
| @@ -944,6 +951,7 @@ const closeNewModal = useCallback(() => { | |||
| itemDetail={modalInfo} | |||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
| printerCombo={printerCombo} | |||
| escalationCombo={escalationCombo} | |||
| /> | |||
| </> | |||
| ) | |||
| @@ -6,6 +6,7 @@ import { | |||
| Card, | |||
| CardContent, | |||
| Checkbox, | |||
| Collapse, | |||
| FormControl, | |||
| FormControlLabel, | |||
| Grid, | |||
| @@ -54,11 +55,13 @@ import { escape } from "lodash"; | |||
| import { PanoramaSharp } from "@mui/icons-material"; | |||
| import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| interface Props { | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| qc: QcItemWithChecks[]; | |||
| disabled: boolean; | |||
| escalationCombo: EscalationCombo[]; | |||
| // qcItems: QcData[] | |||
| // setQcItems: Dispatch<SetStateAction<QcData[]>> | |||
| } | |||
| @@ -71,7 +74,7 @@ type EntryError = | |||
| type QcRow = TableRow<Partial<QcData>, EntryError>; | |||
| // fetchQcItemCheck | |||
| const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escalationCombo }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| @@ -95,6 +98,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| const qcDecision = watch("qcDecision"); //WIP | |||
| // const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]); | |||
| const qcResult = [...watch("qcResult")]; | |||
| const [qcHistory, setQcHistory] = useState<PurchaseQcResult[]>([]); | |||
| // const [qcAccept, setQcAccept] = useState(true); | |||
| // const [qcItems, setQcItems] = useState(dummyQCData) | |||
| @@ -407,6 +411,12 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| if (qcHistory.length < 1) { | |||
| setQcHistory(qcResult.filter((qc) => {qc.escalationLogId != null})); | |||
| console.log("QC History updated:", qcHistory); | |||
| } | |||
| }, [watch("qcResult")]); | |||
| // useEffect(() => { | |||
| // // onFailedOpenCollapse(qcItems) | |||
| // }, [qcItems]); | |||
| @@ -428,7 +438,8 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| variant="scrollable" | |||
| > | |||
| <Tab label={t("QC Info")} iconPosition="end" /> | |||
| <Tab label={t("Escalation History")} iconPosition="end" /> | |||
| {(itemDetail.escResult && itemDetail.escResult?.length > 0) && | |||
| (<Tab label={t("Escalation History")} iconPosition="end" />)} | |||
| </Tabs> | |||
| </Grid> | |||
| {tabIndex == 0 && ( | |||
| @@ -478,7 +489,17 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| </Typography> | |||
| </Grid> */} | |||
| <Grid item xs={12}> | |||
| <EscalationLogTable items={itemDetail.escResult || []}/> | |||
| <EscalationLogTable type="qc" items={itemDetail.escResult || []}/> | |||
| {/* <Collapse in={true}> */} | |||
| <StyledDataGrid | |||
| columns={qcColumns} | |||
| rows={qcResult} | |||
| // rows={qcResult && qcResult.length > 0 ? qcResult : qcItems} | |||
| // rows={disabled? qcResult:qcItems} | |||
| autoHeight | |||
| sortModel={[]} | |||
| /> | |||
| {/* </Collapse> */} | |||
| {/* <StyledDataGrid | |||
| rows={escalationHistory} | |||
| columns={columns} | |||
| @@ -569,6 +590,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => { | |||
| forSupervisor={false} | |||
| isCollapsed={isCollapsed} | |||
| setIsCollapsed={setIsCollapsed} | |||
| escalationCombo={escalationCombo} | |||
| /> | |||
| </Grid>)} | |||
| {/* {qcAccept && <Grid item xs={12}> | |||
| @@ -21,7 +21,6 @@ import StockInForm from "./StockInForm"; | |||
| import StockInFormVer2 from "./StockInFormVer2"; | |||
| import QcComponent from "./QcComponent"; | |||
| import { dummyPutAwayLine, dummyQCData } from "./dummyQcTemplate"; | |||
| // import QcFormVer2 from "./QcFormVer2"; | |||
| import PutAwayForm from "./PutAwayForm"; | |||
| import { GridRowModes, useGridApiRef } from "@mui/x-data-grid"; | |||
| import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | |||
| @@ -34,6 +33,7 @@ import { EscalationResult } from "@/app/api/escalation"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| import { GridRowModesModel } from "@mui/x-data-grid"; | |||
| import { isEmpty } from "lodash"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| const style = { | |||
| @@ -68,6 +68,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||
| // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
| printerCombo: PrinterCombo[]; | |||
| escalationCombo: EscalationCombo[]; | |||
| onClose: () => void; | |||
| } | |||
| interface Props extends CommonProps { | |||
| @@ -86,7 +87,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| qc, | |||
| warehouse, | |||
| handleMailTemplateForStockInLine, | |||
| printerCombo | |||
| printerCombo, | |||
| escalationCombo | |||
| }) => { | |||
| const { | |||
| t, | |||
| @@ -486,7 +488,6 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| onSubmit={formProps.handleSubmit(onSubmitPutaway)} | |||
| > | |||
| <PutAwayForm | |||
| printerCombo={printerCombo} | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={viewOnly} | |||
| @@ -569,6 +570,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| qc={qc!} | |||
| itemDetail={itemDetail} | |||
| disabled={viewOnly} | |||
| escalationCombo={escalationCombo} | |||
| // qcItems={qcItems} | |||
| // setQcItems={setQcItems} | |||
| /> | |||
| @@ -17,7 +17,8 @@ | |||
| "Purchase Order Code": "採購單號", | |||
| "Item Name": "貨品名稱", | |||
| "Escalation Level": "上報等級", | |||
| "Reason": "原因", | |||
| "Reason": "上報原因", | |||
| "escalated date": "上報日期", | |||
| "Order completion": "訂單完成度", | |||
| "Raw material": "原料", | |||
| "Consumable": "消耗品", | |||
| @@ -32,6 +33,9 @@ | |||
| "Processed application": "已處理提料申請", | |||
| "Pending application": "待處理提料申請", | |||
| "pending inspection material": "待品檢物料", | |||
| "pending": "待處理", | |||
| "rejected": "已拒絕", | |||
| "escalated": "已上報", | |||
| "inspected material": "已品檢物料", | |||
| "total material": "物料總數", | |||
| "stock in escalation list": "收貨已上報列表", | |||
| @@ -40,8 +44,11 @@ | |||
| "QC Fail Count": "品檢不合格數量", | |||
| "DN Date": "送貨日期", | |||
| "Received Qty": "收貨數量", | |||
| "Po Code": "採購訂單編號", | |||
| "My Escalation List": "我的上報列表", | |||
| "Escalation List": "上報列表", | |||
| "Purchase UoM": "計量單位", | |||
| "QC Completed Count": "品檢完成數量", | |||
| "QC Fail-Total Count": "品檢不合格/總數" | |||
| "QC Fail-Total Count": "品檢不合格/總數", | |||
| "escalationStatus": "上報狀態" | |||
| } | |||
| @@ -39,7 +39,7 @@ | |||
| "qty": "訂單數量", | |||
| "uom": "計量單位", | |||
| "Stock UoM": "庫存單位", | |||
| "Stock In Qty": "收貨數量", | |||
| "Stock In Qty": "換算庫存數量", | |||
| "total weight": "總重量", | |||
| "weight unit": "重量單位", | |||
| "price": "訂單貨值", | |||
| @@ -105,7 +105,7 @@ | |||
| "No Warehouse": "沒有倉庫", | |||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||
| "receivedQty": "已來貨數量", | |||
| "dnQty": "送貨單數量", | |||
| "dnQty": "本批來貨數量", | |||
| "Accept submit": "接受來貨", | |||
| "qc processing": "處理來貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| @@ -138,5 +138,7 @@ | |||
| "Printer": "列印機", | |||
| "Printing": "列印中", | |||
| "rejectQty": "拒絕數量", | |||
| "QC decision is required": "請決定品檢結果" | |||
| "QC decision is required": "請決定品檢結果", | |||
| "No Option": "沒有選項", | |||
| "receivedTotal": "已來貨總數" | |||
| } | |||