| @@ -20,7 +20,7 @@ export interface QcData { | |||||
| qcItemId: number, | qcItemId: number, | ||||
| code: string, | code: string, | ||||
| name: string, | name: string, | ||||
| qcDescription: string, | |||||
| description: string, | |||||
| qcPassed: boolean | undefined | qcPassed: boolean | undefined | ||||
| failQty: number | undefined | failQty: number | undefined | ||||
| remarks: string | undefined | remarks: string | undefined | ||||
| @@ -6,10 +6,11 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "../../../config/api"; | import { BASE_API_URL } from "../../../config/api"; | ||||
| export interface QrCodeInfo { | export interface QrCodeInfo { | ||||
| value: string; | |||||
| // warehouse qrcode | // warehouse qrcode | ||||
| warehouseId?: number; | warehouseId?: number; | ||||
| // item qrcode | // item qrcode | ||||
| stockInLineId?: number; | stockInLineId?: number; | ||||
| itemId: number; | |||||
| itemId?: number; | |||||
| lotNo?: string; | lotNo?: string; | ||||
| } | } | ||||
| @@ -4,8 +4,4 @@ | |||||
| html, body { | html, body { | ||||
| overscroll-behavior: none; | overscroll-behavior: none; | ||||
| } | |||||
| .swal2-custom-zindex { | |||||
| z-index: 10000 !important; | |||||
| } | } | ||||
| @@ -229,7 +229,9 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| flex: 0.2, | flex: 0.2, | ||||
| editable: false, | editable: false, | ||||
| renderCell(params) { | renderCell(params) { | ||||
| return `${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}.` | |||||
| return <span style={{fontSize:18}}> | |||||
| {`${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}.`} | |||||
| </span> | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -238,7 +240,9 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| flex: 1, | flex: 1, | ||||
| editable: false, | editable: false, | ||||
| renderCell(params) { | renderCell(params) { | ||||
| return `${(arrayToDateTimeString(params.value))}`; | |||||
| return <span style={{fontSize:24}}> | |||||
| {`${(arrayToDateTimeString(params.value))}`} | |||||
| </span> | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -246,6 +250,11 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| headerName: t("putawayUser"), | headerName: t("putawayUser"), | ||||
| flex: 1, | flex: 1, | ||||
| editable: false, | editable: false, | ||||
| renderCell(params) { | |||||
| return <span style={{fontSize:24}}> | |||||
| {params.value} | |||||
| </span> | |||||
| } | |||||
| }, | }, | ||||
| { | { | ||||
| field: "qty", | field: "qty", | ||||
| @@ -254,38 +263,55 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| editable: false, | editable: false, | ||||
| headerAlign: "right", | headerAlign: "right", | ||||
| align: "right", | align: "right", | ||||
| renderCell(params) { | |||||
| return <span style={{fontSize:24}}> | |||||
| {params.value} | |||||
| </span> | |||||
| } | |||||
| }, | }, | ||||
| { | { | ||||
| field: "warehouse", | field: "warehouse", | ||||
| headerName: t("warehouse"), | headerName: t("warehouse"), | ||||
| flex: 2, | flex: 2, | ||||
| editable: false, | editable: false, | ||||
| renderEditCell: (params) => { | |||||
| const index = params.api.getRowIndexRelativeToVisibleRows(params.row.id) | |||||
| // console.log(index) | |||||
| // console.log(watch(`putAwayLines.${index}.warehouse`)) | |||||
| return <Autocomplete | |||||
| fullWidth | |||||
| disableClearable | |||||
| options={options} | |||||
| // defaultValue={options.find((o) => o.value === itemDetail.defaultWarehouseId)} | |||||
| // value={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))} | |||||
| defaultValue={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))} | |||||
| onChange={(event, value) => { | |||||
| params.api.setEditCellValue({ id: params.id, field: params.field, value: options.find((o) => o.value === value.value)?.label ?? ""}) | |||||
| params.api.setEditCellValue({ id: params.id, field: "warehouseId", value: value.value}) | |||||
| // setValue(`putAwayLines.${index}.warehouseId`, value.value) | |||||
| // setValue(`putAwayLines.${index}.warehouse`, options.find((o) => o.value === value.value)?.label ?? "") | |||||
| }} | |||||
| renderInput={(params) => ( | |||||
| <TextField | |||||
| {...params} | |||||
| variant="outlined" | |||||
| // label={t("Warehouse")} | |||||
| /> | |||||
| )} | |||||
| /> | |||||
| renderCell(params) { | |||||
| return <span style={{fontSize:24}}> | |||||
| {params.value} | |||||
| </span> | |||||
| } | } | ||||
| // renderEditCell: (params) => { | |||||
| // const index = params.api.getRowIndexRelativeToVisibleRows(params.row.id) | |||||
| // // console.log(index) | |||||
| // // console.log(watch(`putAwayLines.${index}.warehouse`)) | |||||
| // return <Autocomplete | |||||
| // fullWidth | |||||
| // disableClearable | |||||
| // options={options} | |||||
| // // defaultValue={options.find((o) => o.value === itemDetail.defaultWarehouseId)} | |||||
| // // value={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))} | |||||
| // defaultValue={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))} | |||||
| // onChange={(event, value) => { | |||||
| // params.api.setEditCellValue({ id: params.id, field: params.field, value: options.find((o) => o.value === value.value)?.label ?? ""}) | |||||
| // params.api.setEditCellValue({ id: params.id, field: "warehouseId", value: value.value}) | |||||
| // // setValue(`putAwayLines.${index}.warehouseId`, value.value) | |||||
| // // setValue(`putAwayLines.${index}.warehouse`, options.find((o) => o.value === value.value)?.label ?? "") | |||||
| // }} | |||||
| // sx={{ | |||||
| // // Style the dropdown options | |||||
| // "& .MuiAutocomplete-option": { | |||||
| // fontSize: 24, | |||||
| // }, | |||||
| // }} | |||||
| // renderInput={(params) => ( | |||||
| // <TextField | |||||
| // {...params} | |||||
| // variant="outlined" | |||||
| // InputProps={{style: {fontSize: 24}}} | |||||
| // // label={t("Warehouse")} | |||||
| // /> | |||||
| // )} | |||||
| // /> | |||||
| // } | |||||
| // renderCell(params) { | // renderCell(params) { | ||||
| // return <>{filteredWarehouse[0].name}</> | // return <>{filteredWarehouse[0].name}</> | ||||
| // }, | // }, | ||||
| @@ -237,10 +237,10 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| const qcColumns: GridColDef[] = useMemo(() => [ | const qcColumns: GridColDef[] = useMemo(() => [ | ||||
| { | { | ||||
| field: "code", | |||||
| field: "name", | |||||
| headerName: t("qcItem"), | headerName: t("qcItem"), | ||||
| wrapText: true, | wrapText: true, | ||||
| flex: 2, | |||||
| flex: 2.5, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| const index = params.api.getRowIndexRelativeToVisibleRows(params.id) + 1; | const index = params.api.getRowIndexRelativeToVisibleRows(params.id) + 1; | ||||
| return ( | return ( | ||||
| @@ -248,17 +248,18 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| sx={{ | sx={{ | ||||
| lineHeight: 1.5, | lineHeight: 1.5, | ||||
| padding: "4px", | padding: "4px", | ||||
| fontSize: 18, | |||||
| }} | }} | ||||
| > | > | ||||
| <b>{`${index}. ${params.value}`}</b><br/> | <b>{`${index}. ${params.value}`}</b><br/> | ||||
| {params.row.name} | |||||
| {params.row.description} | |||||
| </Box> | </Box> | ||||
| )}, | )}, | ||||
| }, | }, | ||||
| { | { | ||||
| field: 'qcResult', | field: 'qcResult', | ||||
| headerName: t("qcResult"), | headerName: t("qcResult"), | ||||
| flex: 1.5, | |||||
| flex: 1, | |||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| const rowValue = params.row; | const rowValue = params.row; | ||||
| const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id); | const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id); | ||||
| @@ -307,14 +308,13 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| { | { | ||||
| field: "failQty", | field: "failQty", | ||||
| headerName: t("failedQty"), | headerName: t("failedQty"), | ||||
| flex: 1, | |||||
| flex: 0.5, | |||||
| // editable: true, | // editable: true, | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id); | const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id); | ||||
| return ( | return ( | ||||
| <TextField | <TextField | ||||
| type="number" | type="number" | ||||
| size="small" | |||||
| value={!params.row.qcPassed? params.value : '0'} | value={!params.row.qcPassed? params.value : '0'} | ||||
| disabled={params.row.qcPassed || qcDisabled(params.row)} | disabled={params.row.qcPassed || qcDisabled(params.row)} | ||||
| /* TODO improve */ | /* TODO improve */ | ||||
| @@ -335,7 +335,12 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| onMouseDown={(e) => e.stopPropagation()} | onMouseDown={(e) => e.stopPropagation()} | ||||
| onKeyDown={(e) => e.stopPropagation()} | onKeyDown={(e) => e.stopPropagation()} | ||||
| inputProps={{ min: 0 }} | inputProps={{ min: 0 }} | ||||
| sx={{ width: '100%' }} | |||||
| sx={{ width: '100%', | |||||
| "& .MuiInputBase-input": { | |||||
| padding: "0.75rem", | |||||
| fontSize: 24, | |||||
| }, | |||||
| }} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -369,7 +374,12 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| onClick={(e) => e.stopPropagation()} | onClick={(e) => e.stopPropagation()} | ||||
| onMouseDown={(e) => e.stopPropagation()} | onMouseDown={(e) => e.stopPropagation()} | ||||
| onKeyDown={(e) => e.stopPropagation()} | onKeyDown={(e) => e.stopPropagation()} | ||||
| sx={{ width: '100%' }} | |||||
| sx={{ width: '100%', | |||||
| "& .MuiInputBase-input": { | |||||
| padding: "0.75rem", | |||||
| fontSize: 24, | |||||
| }, | |||||
| }} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -54,6 +54,35 @@ type EntryError = | |||||
| // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | ||||
| const textfieldSx = { | |||||
| width: "100%", | |||||
| "& .MuiInputBase-root": { | |||||
| // height: "120", // Scales with root font size | |||||
| height: "5rem", // Scales with root font size | |||||
| }, | |||||
| "& .MuiInputBase-input": { | |||||
| height: "100%", | |||||
| boxSizing: "border-box", | |||||
| padding: "0.75rem", | |||||
| fontSize: 24, | |||||
| }, | |||||
| "& .MuiInputLabel-root": { | |||||
| fontSize: 24, | |||||
| transform: "translate(14px, 1.5rem) scale(1)", | |||||
| "&.MuiInputLabel-shrink": { | |||||
| fontSize: 18, | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | |||||
| // [theme.breakpoints.down("sm")]: { | |||||
| // fontSize: "1rem", | |||||
| // transform: "translate(14px, 1.5rem) scale(1)", | |||||
| // "&.MuiInputLabel-shrink": { | |||||
| // fontSize: "0.875rem", | |||||
| // }, | |||||
| // }, | |||||
| }, | |||||
| }; | |||||
| const StockInForm: React.FC<Props> = ({ | const StockInForm: React.FC<Props> = ({ | ||||
| // qc, | // qc, | ||||
| itemDetail, | itemDetail, | ||||
| @@ -136,6 +165,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("dnNo", { | {...register("dnNo", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={disabled} | disabled={disabled} | ||||
| // error={Boolean(errors.productLotNo)} | // error={Boolean(errors.productLotNo)} | ||||
| // helperText={errors.productLotNo?.message} | // helperText={errors.productLotNo?.message} | ||||
| @@ -148,6 +178,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("itemName", { | {...register("itemName", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| // error={Boolean(errors.productLotNo)} | // error={Boolean(errors.productLotNo)} | ||||
| // helperText={errors.productLotNo?.message} | // helperText={errors.productLotNo?.message} | ||||
| @@ -160,6 +191,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("poCode", { | {...register("poCode", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| // error={Boolean(errors.productLotNo)} | // error={Boolean(errors.productLotNo)} | ||||
| // helperText={errors.productLotNo?.message} | // helperText={errors.productLotNo?.message} | ||||
| @@ -180,7 +212,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| > | > | ||||
| <DatePicker | <DatePicker | ||||
| {...field} | {...field} | ||||
| sx={{ width: "100%" }} | |||||
| sx={textfieldSx} | |||||
| label={t("receiptDate")} | label={t("receiptDate")} | ||||
| value={dayjs(watch("receiptDate"))} | value={dayjs(watch("receiptDate"))} | ||||
| disabled={true} | disabled={true} | ||||
| @@ -210,6 +242,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("supplier", { | {...register("supplier", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -222,51 +255,52 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("productLotNo", { | {...register("productLotNo", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={disabled} | disabled={disabled} | ||||
| error={Boolean(errors.productLotNo)} | error={Boolean(errors.productLotNo)} | ||||
| helperText={errors.productLotNo?.message} | helperText={errors.productLotNo?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="productionDate" | |||||
| // rules={{ required: !Boolean(expiryDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={{ width: "100%" }} | |||||
| label={t("productionDate")} | |||||
| value={productionDate ? dayjs(productionDate) : undefined} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | |||||
| if (!date) return; | |||||
| setValue( | |||||
| "productionDate", | |||||
| date.format(INPUT_DATE_FORMAT), | |||||
| ); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.productionDate?.message), | |||||
| helperText: errors.productionDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| {putawayMode || ( | |||||
| {putawayMode || (<> | |||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="productionDate" | |||||
| // rules={{ required: !Boolean(expiryDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={textfieldSx} | |||||
| label={t("productionDate")} | |||||
| value={productionDate ? dayjs(productionDate) : undefined} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | |||||
| if (!date) return; | |||||
| setValue( | |||||
| "productionDate", | |||||
| date.format(INPUT_DATE_FORMAT), | |||||
| ); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.productionDate?.message), | |||||
| helperText: errors.productionDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("qty")} | label={t("qty")} | ||||
| @@ -274,9 +308,10 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("qty", { | {...register("qty", { | ||||
| required: "qty required!", | required: "qty required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid></> | |||||
| )} | )} | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <Controller | <Controller | ||||
| @@ -291,7 +326,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| > | > | ||||
| <DatePicker | <DatePicker | ||||
| {...field} | {...field} | ||||
| sx={{ width: "100%" }} | |||||
| sx={textfieldSx} | |||||
| label={t("expiryDate")} | label={t("expiryDate")} | ||||
| value={expiryDate ? dayjs(expiryDate) : undefined} | value={expiryDate ? dayjs(expiryDate) : undefined} | ||||
| disabled={disabled} | disabled={disabled} | ||||
| @@ -323,6 +358,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("receivedQty", { | {...register("receivedQty", { | ||||
| required: "receivedQty required!", | required: "receivedQty required!", | ||||
| })} | })} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -331,33 +367,47 @@ const StockInForm: React.FC<Props> = ({ | |||||
| <TextField | <TextField | ||||
| label={t("uom")} | label={t("uom")} | ||||
| fullWidth | fullWidth | ||||
| {...register("uom.code", { | |||||
| {...register("uom.udfudesc", { | |||||
| required: "uom required!", | required: "uom required!", | ||||
| })} | })} | ||||
| // value={uom?.code} | // value={uom?.code} | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| {putawayMode ? ( | |||||
| {putawayMode ? (<> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={`${t("processedQty")} / ${t("acceptedQty")}`} | |||||
| label={t("acceptedQty")} | |||||
| fullWidth | fullWidth | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| value={ | |||||
| `${itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0} / ${itemDetail.acceptedQty}` | |||||
| } | |||||
| value={itemDetail.acceptedQty} | |||||
| // disabled={true} | // disabled={true} | ||||
| // disabled={disabled} | // disabled={disabled} | ||||
| // error={Boolean(errors.acceptedQty)} | // error={Boolean(errors.acceptedQty)} | ||||
| // helperText={errors.acceptedQty?.message} | // helperText={errors.acceptedQty?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("processedQty")} | |||||
| fullWidth | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| value={itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0} | |||||
| // disabled={true} | |||||
| // disabled={disabled} | |||||
| // error={Boolean(errors.acceptedQty)} | |||||
| // helperText={errors.acceptedQty?.message} | |||||
| /> | |||||
| </Grid></> | |||||
| ) : ( | ) : ( | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
| fullWidth | fullWidth | ||||
| sx={textfieldSx} | |||||
| disabled={true} | disabled={true} | ||||
| {...register("acceptedQty", { | {...register("acceptedQty", { | ||||
| required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
| @@ -15,8 +15,8 @@ export const dummyQCData: QcData[] = [ | |||||
| id: 1, | id: 1, | ||||
| qcItemId: 4, | qcItemId: 4, | ||||
| code: "包裝", | code: "包裝", | ||||
| qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||||
| name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||||
| description: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||||
| name: "包裝", | |||||
| qcPassed: undefined, | qcPassed: undefined, | ||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| @@ -25,8 +25,8 @@ export const dummyQCData: QcData[] = [ | |||||
| id: 2, | id: 2, | ||||
| qcItemId: 5, | qcItemId: 5, | ||||
| code: "肉質", | code: "肉質", | ||||
| qcDescription: "肉質鬆散,則不合格", | |||||
| name: "肉質鬆散,則不合格", | |||||
| description: "肉質鬆散,則不合格", | |||||
| name: "肉質", | |||||
| qcPassed: undefined, | qcPassed: undefined, | ||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| @@ -35,8 +35,8 @@ export const dummyQCData: QcData[] = [ | |||||
| id: 3, | id: 3, | ||||
| qcItemId: 6, | qcItemId: 6, | ||||
| code: "顔色", | code: "顔色", | ||||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||||
| name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||||
| description: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||||
| name: "顔色", | |||||
| qcPassed: undefined, | qcPassed: undefined, | ||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| @@ -45,8 +45,8 @@ export const dummyQCData: QcData[] = [ | |||||
| id: 4, | id: 4, | ||||
| qcItemId: 7, | qcItemId: 7, | ||||
| code: "狀態", | code: "狀態", | ||||
| qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||||
| name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||||
| description: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||||
| name: "狀態", | |||||
| qcPassed: undefined, | qcPassed: undefined, | ||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| @@ -55,8 +55,8 @@ export const dummyQCData: QcData[] = [ | |||||
| id: 5, | id: 5, | ||||
| qcItemId: 8, | qcItemId: 8, | ||||
| code: "異物", | code: "異物", | ||||
| qcDescription: "有不屬於本食材的雜質,則不合格", | |||||
| name: "有不屬於本食材的雜質,則不合格", | |||||
| description: "有不屬於本食材的雜質,則不合格", | |||||
| name: "異物", | |||||
| qcPassed: undefined, | qcPassed: undefined, | ||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| @@ -52,10 +52,8 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!scanner.isScanning) { | if (!scanner.isScanning) { | ||||
| scanner.startScan(); | scanner.startScan(); | ||||
| console.log("%c Scanning started ", "color:cyan"); | |||||
| } else if (scanner.isScanning) { | } else if (scanner.isScanning) { | ||||
| scanner.stopScan(); | scanner.stopScan(); | ||||
| console.log("%c Scanning stopped ", "color:cyan"); | |||||
| } | } | ||||
| }, []); | }, []); | ||||
| @@ -87,37 +85,6 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||||
| setOpenPutAwayModal(false); | setOpenPutAwayModal(false); | ||||
| } | } | ||||
| const findIdByRoughMatch = (inputString : string, keyword : string) => { | |||||
| const keywordIndex = inputString.indexOf(keyword); | |||||
| if (keywordIndex === -1) { | |||||
| return { | |||||
| keywordFound: false, | |||||
| number: null, | |||||
| message: `${keyword} not found in the input`, | |||||
| }; | |||||
| } | |||||
| const substringAfterKeyword = inputString.slice(keywordIndex + keyword.length); | |||||
| const numberMatch = substringAfterKeyword.match(/\d+/); | |||||
| if (!numberMatch) { | |||||
| return { | |||||
| keywordFound: true, | |||||
| number: null, | |||||
| message: `No valid number found after ${keyword}`, | |||||
| }; | |||||
| } | |||||
| return { | |||||
| keywordFound: true, | |||||
| number: parseInt(numberMatch[0], 10), | |||||
| message: `Found ${keyword} at index ${keywordIndex}, first number found after is: ${numberMatch[0]}`, | |||||
| }; | |||||
| } | |||||
| const addPutAwayHistory = (putAwayData: PutAwayRecord) => { | const addPutAwayHistory = (putAwayData: PutAwayRecord) => { | ||||
| console.log("%c Added new data to Putaway history: ", "color:orange", putAwayData); | console.log("%c Added new data to Putaway history: ", "color:orange", putAwayData); | ||||
| const newPutaway = { ...putAwayData, id: putAwayHistory.length + 1 }; | const newPutaway = { ...putAwayData, id: putAwayHistory.length + 1 }; | ||||
| @@ -133,51 +100,22 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||||
| // Get Scanned Values | // Get Scanned Values | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (scanner.values.length > 0) {//} && !Boolean(itemDetail)) { | |||||
| const scannedValues = scanner.values[0]; | |||||
| console.log("%c Scanned: ", "color:cyan", scannedValues); | |||||
| if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING | |||||
| const number = scannedValues.substring(8, scannedValues.length - 1); | |||||
| if (/^\d+$/.test(number)) { // Check if number contains only digits | |||||
| console.log("%c DEBUG: Testing SIL ID: ", "color:cyan", number); | |||||
| if (scannedSilId === 0) { | |||||
| setScannedSilId(Number(number)); | |||||
| } else setScannedWareHouseId(Number(number)); | |||||
| } else { | |||||
| console.error("%c DEBUG: Invalid number format: ", "color:red", number); | |||||
| resetScan(); | |||||
| } | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const data: QrCodeInfo = JSON.parse(scannedValues); | |||||
| console.log("%c Scanned with data", "color:green", data); | |||||
| if (scannedSilId == 0) { // Initial State | |||||
| if (data.stockInLineId !== undefined) { | |||||
| setScannedSilId(Number(data.stockInLineId)); | |||||
| } else resetScan("Cannot read Stock In Line Id"); | |||||
| } else { // Processing | |||||
| if (data.warehouseId !== undefined) { | |||||
| setScannedWareHouseId(Number(data.warehouseId)); | |||||
| } else resetScan("Cannot read Warehouse Id"); | |||||
| } | |||||
| } catch (error) { // Rought match for other scanner -- Pending Review | |||||
| if (scannedSilId == 0) { | |||||
| const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0; | |||||
| setScannedSilId(silId); | |||||
| } else { | |||||
| const whId = findIdByRoughMatch(scannedValues, "warehouseId").number ?? 0; | |||||
| setScannedWareHouseId(whId); | |||||
| } | |||||
| resetScan(String(error)); | |||||
| if (scanner.result) { | |||||
| const data = scanner.result; | |||||
| if (scannedSilId == 0) { // Initial State | |||||
| if (!isNaN(Number(data.value))) { setScannedSilId(Number(data.value)); } | |||||
| else if (data.stockInLineId) { | |||||
| setScannedSilId(Number(data.stockInLineId)); | |||||
| } else resetScan("Cannot read Stock In Line Id"); | |||||
| } else { // Processing | |||||
| if (!isNaN(Number(data.value))) { setScannedWareHouseId(Number(data.value)); } | |||||
| else if (data.warehouseId) { | |||||
| setScannedWareHouseId(Number(data.warehouseId)); | |||||
| } else resetScan("Cannot read Warehouse Id"); | |||||
| } | } | ||||
| scanner.resetScan(); | scanner.resetScan(); | ||||
| } | } | ||||
| }, [scanner.values]); | |||||
| }, [scanner.result]); | |||||
| return (<> | return (<> | ||||
| <Paper sx={{ | <Paper sx={{ | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||||
| import { | import { | ||||
| ReactNode, | ReactNode, | ||||
| createContext, | createContext, | ||||
| @@ -14,6 +15,7 @@ export interface QrCodeScanner { | |||||
| startScan: () => void; | startScan: () => void; | ||||
| stopScan: () => void; | stopScan: () => void; | ||||
| resetScan: () => void; | resetScan: () => void; | ||||
| result: QrCodeInfo | undefined; | |||||
| } | } | ||||
| interface QrCodeScannerProviderProps { | interface QrCodeScannerProviderProps { | ||||
| @@ -32,19 +34,73 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| const [keys, setKeys] = useState<string[]>([]); | const [keys, setKeys] = useState<string[]>([]); | ||||
| const [leftCurlyBraceCount, setLeftCurlyBraceCount] = useState<number>(0); | const [leftCurlyBraceCount, setLeftCurlyBraceCount] = useState<number>(0); | ||||
| const [rightCurlyBraceCount, setRightCurlyBraceCount] = useState<number>(0); | const [rightCurlyBraceCount, setRightCurlyBraceCount] = useState<number>(0); | ||||
| const resetQrCodeScanner = useCallback(() => { | |||||
| const [scanResult, setScanResult] = useState<QrCodeInfo | undefined>() | |||||
| const resetScannerInput = useCallback(() => { | |||||
| setKeys(() => []); | |||||
| setLeftCurlyBraceCount(() => 0); | |||||
| setRightCurlyBraceCount(() => 0); | |||||
| }, []); | |||||
| const resetQrCodeScanner = useCallback((error : string = "") => { | |||||
| setQrCodeScannerValues(() => []); | setQrCodeScannerValues(() => []); | ||||
| setScanResult(undefined); | |||||
| resetScannerInput(); | |||||
| console.log("%c Scanner Reset", "color:cyan"); | |||||
| if (error.length > 0) { | |||||
| console.log("%c Error:", "color:red", error); | |||||
| } | |||||
| }, []); | }, []); | ||||
| const startQrCodeScanner = useCallback(() => { | const startQrCodeScanner = useCallback(() => { | ||||
| resetQrCodeScanner(); | resetQrCodeScanner(); | ||||
| setIsScanning(() => true); | setIsScanning(() => true); | ||||
| console.log("%c Scanning started ", "color:cyan"); | |||||
| }, []); | }, []); | ||||
| const endQrCodeScanner = useCallback(() => { | const endQrCodeScanner = useCallback(() => { | ||||
| setIsScanning(() => false); | setIsScanning(() => false); | ||||
| console.log("%c Scanning stopped ", "color:cyan"); | |||||
| }, []); | }, []); | ||||
| // Find by rough match, return 0 if not found | |||||
| const findIdByRoughMatch = (inputString : string, keyword : string) => { | |||||
| console.log(`%c Performed rough match for ${keyword} within ${inputString}`, "color:brown"); | |||||
| const keywordIndex = inputString.indexOf(keyword); | |||||
| let result : {keywordFound: boolean; number: number | null; message: string} = { | |||||
| keywordFound: false, | |||||
| number: null, | |||||
| message: `${keyword} not found in the input`, | |||||
| }; | |||||
| if (keywordIndex !== -1) { | |||||
| const substringAfterKeyword = inputString.slice(keywordIndex + keyword.length); | |||||
| const numberMatch = substringAfterKeyword.match(/\d+/); | |||||
| if (!numberMatch) { | |||||
| result = { | |||||
| keywordFound: true, | |||||
| number: null, | |||||
| message: `No valid number found after ${keyword}`, | |||||
| }; | |||||
| } else { | |||||
| result = { | |||||
| keywordFound: true, | |||||
| number: parseInt(numberMatch[0], 10), | |||||
| message: `Found ${keyword} at index ${keywordIndex}, first number found after is: ${numberMatch[0]}`, | |||||
| }; | |||||
| } | |||||
| } | |||||
| console.log(`%c ${result.message}`, "color:brown"); | |||||
| return result; | |||||
| }; | |||||
| // Check the KeyDown | // Check the KeyDown | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (isScanning) { | if (isScanning) { | ||||
| @@ -70,27 +126,72 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| // Update Qr Code Scanner Values | // Update Qr Code Scanner Values | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if ( | |||||
| leftCurlyBraceCount !== 0 && | |||||
| rightCurlyBraceCount !== 0 && | |||||
| leftCurlyBraceCount === rightCurlyBraceCount | |||||
| ) { | |||||
| const startBrace = keys.indexOf("{"); | |||||
| const endBrace = keys.lastIndexOf("}"); | |||||
| setQrCodeScannerValues((value) => [ | |||||
| ...value, | |||||
| keys.join("").substring(startBrace, endBrace + 1), | |||||
| ]); | |||||
| console.log(keys); | |||||
| console.log("%c QR Scanner Values:", "color:cyan", qrCodeScannerValues); | |||||
| // reset | |||||
| setKeys(() => []); | |||||
| setLeftCurlyBraceCount(() => 0); | |||||
| setRightCurlyBraceCount(() => 0); | |||||
| if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan | |||||
| resetQrCodeScanner("Too many scans at once"); | |||||
| } else { | |||||
| if (leftCurlyBraceCount == 1 && keys.length == 1) | |||||
| { console.log("%c Scan detected, waiting for inputs...", "color:cyan"); } | |||||
| if ( | |||||
| leftCurlyBraceCount !== 0 && | |||||
| rightCurlyBraceCount !== 0 && | |||||
| leftCurlyBraceCount === rightCurlyBraceCount | |||||
| ) { | |||||
| const startBrace = keys.indexOf("{"); | |||||
| const endBrace = keys.lastIndexOf("}"); | |||||
| setQrCodeScannerValues((value) => [ | |||||
| ...value, | |||||
| keys.join("").substring(startBrace, endBrace + 1), | |||||
| ]); | |||||
| // console.log(keys); | |||||
| // console.log("%c QR Scanner Values:", "color:cyan", qrCodeScannerValues); | |||||
| resetScannerInput(); | |||||
| } | |||||
| } | } | ||||
| }, [keys, leftCurlyBraceCount, rightCurlyBraceCount]); | }, [keys, leftCurlyBraceCount, rightCurlyBraceCount]); | ||||
| useEffect(() => { | |||||
| if (qrCodeScannerValues.length > 0) { | |||||
| const scannedValues = qrCodeScannerValues[0]; | |||||
| console.log("%c Scanned Result: ", "color:cyan", scannedValues); | |||||
| if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING | |||||
| const number = scannedValues.substring(8, scannedValues.length - 1); | |||||
| if (/^\d+$/.test(number)) { // Check if number contains only digits | |||||
| console.log("%c DEBUG: detected ID: ", "color:pink", number); | |||||
| const debugValue = { | |||||
| value: number | |||||
| } | |||||
| setScanResult(debugValue); | |||||
| } else { | |||||
| resetQrCodeScanner("DEBUG -- Invalid number format: " + number); | |||||
| } | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const data: QrCodeInfo = JSON.parse(scannedValues); | |||||
| console.log("%c Parsed scan data", "color:green", data); | |||||
| const content = scannedValues.substring(1, scannedValues.length - 1); | |||||
| data.value = content; | |||||
| setScanResult(data); | |||||
| } catch (error) { // Rought match for other scanner input -- Pending Review | |||||
| const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0; | |||||
| if (silId == 0) { | |||||
| const whId = findIdByRoughMatch(scannedValues, "warehouseId").number ?? 0; | |||||
| setScanResult({...scanResult, stockInLineId: whId, value: whId.toString()}); | |||||
| } else { setScanResult({...scanResult, stockInLineId: silId, value: silId.toString()}); } | |||||
| resetQrCodeScanner(String(error)); | |||||
| } | |||||
| // resetQrCodeScanner(); | |||||
| } | |||||
| }, [qrCodeScannerValues]); | |||||
| return ( | return ( | ||||
| <QrCodeScannerContext.Provider | <QrCodeScannerContext.Provider | ||||
| value={{ | value={{ | ||||
| @@ -99,6 +200,7 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| startScan: startQrCodeScanner, | startScan: startQrCodeScanner, | ||||
| stopScan: endQrCodeScanner, | stopScan: endQrCodeScanner, | ||||
| resetScan: resetQrCodeScanner, | resetScan: resetQrCodeScanner, | ||||
| result: scanResult, | |||||
| }} | }} | ||||
| > | > | ||||
| {children} | {children} | ||||
| @@ -150,10 +150,37 @@ const components: ThemeOptions["components"] = { | |||||
| width: "100%", | width: "100%", | ||||
| zIndex: 2000, | zIndex: 2000, | ||||
| }, | }, | ||||
| ".swal2-custom-zindex": { | |||||
| zIndex: 10000, // Set z-index for SweetAlert2 modals | |||||
| }, | |||||
| }, | }, | ||||
| }, | }, | ||||
| MuiInputBase: { | MuiInputBase: { | ||||
| styleOverrides: { | styleOverrides: { | ||||
| root: { | |||||
| "&:not(.Mui-disabled)": { | |||||
| backgroundColor: "rgba(200, 230, 255, 0.2)", // Slightly cyan-ish background | |||||
| "&:hover": { | |||||
| backgroundColor: "rgba(200, 230, 255, 0.25)", // Slightly darker on hover | |||||
| }, | |||||
| "&.Mui-focused": { | |||||
| backgroundColor: "rgba(200, 230, 255, 0.3)", // More pronounced on focus | |||||
| }, | |||||
| }, | |||||
| "&.Mui-disabled": { | |||||
| backgroundColor: "#ffffff", // White background | |||||
| opacity: 1, // Remove MUI's default opacity reduction | |||||
| "& .MuiInputBase-input": { | |||||
| color: "#000", // Black text | |||||
| cursor: "default", // Default cursor | |||||
| WebkitTextFillColor: "#000", // Ensure text color isn't grayed out in WebKit browsers | |||||
| }, | |||||
| "& fieldset": { | |||||
| backgroundColor: "transparent", // Ensure no extra background effects | |||||
| boxShadow: "none", // Remove any box-shadow | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| input: { | input: { | ||||
| "&::placeholder": { | "&::placeholder": { | ||||
| opacity: 1, | opacity: 1, | ||||
| @@ -244,6 +271,17 @@ const components: ThemeOptions["components"] = { | |||||
| boxShadow: `${palette.error.main} 0 0 0 2px`, | boxShadow: `${palette.error.main} 0 0 0 2px`, | ||||
| }, | }, | ||||
| }, | }, | ||||
| "&:not(.Mui-disabled)": { | |||||
| "&:hover .MuiOutlinedInput-notchedOutline": { | |||||
| borderColor: "rgba(0, 0, 0, 0.6)", // Darker border on hover | |||||
| }, | |||||
| "&.Mui-focused .MuiOutlinedInput-notchedOutline": { | |||||
| borderColor: "rgba(0, 0, 0, 0.7)", // Darkest border when focused | |||||
| }, | |||||
| }, | |||||
| "&.Mui-disabled .MuiOutlinedInput-notchedOutline": { | |||||
| border: "1px solid #ccc", // Simple gray border for disabled | |||||
| }, | |||||
| }, | }, | ||||
| input: { | input: { | ||||
| fontSize: defaultFontSize, | fontSize: defaultFontSize, | ||||
| @@ -278,6 +316,13 @@ const components: ThemeOptions["components"] = { | |||||
| background: palette.primary.contrastText, | background: palette.primary.contrastText, | ||||
| }, | }, | ||||
| }, | }, | ||||
| "&.Mui-disabled": { | |||||
| color: "rgba(0, 0, 0, 0.6)", // Black label | |||||
| transform: "translate(14px, 16px) scale(1)", // Position like placeholder | |||||
| "&.Mui-focused": { | |||||
| transform: "translate(14px, 16px) scale(1)", // Keep label in place when "focused" | |||||
| }, | |||||
| }, | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||