@@ -1,4 +1,4 @@ | |||||
API_URL=http://localhost:8090/api | |||||
API_URL=http://10.100.0.81:8090/api | |||||
NEXTAUTH_SECRET=secret | NEXTAUTH_SECRET=secret | ||||
NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com | |||||
NEXT_PUBLIC_API_URL=http://localhost:8090/api | |||||
NEXTAUTH_URL=http://10.100.0.81:3000 | |||||
NEXT_PUBLIC_API_URL=http://10.100.0.81:8090/api |
@@ -17,16 +17,6 @@ const PickOrder: React.FC = async () => { | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack | |||||
direction={"row"} | |||||
justifyContent={"space-between"} | |||||
flexWrap={"wrap"} | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Pick Order")} | |||||
</Typography> | |||||
</Stack> | |||||
<I18nProvider namespaces={["pickOrder", "common"]}> | <I18nProvider namespaces={["pickOrder", "common"]}> | ||||
<Suspense fallback={<PickOrderSearch.Loading />}> | <Suspense fallback={<PickOrderSearch.Loading />}> | ||||
<PickOrderSearch /> | <PickOrderSearch /> | ||||
@@ -15,6 +15,27 @@ import { | |||||
} from "."; | } from "."; | ||||
import { PurchaseQcResult } from "../po/actions"; | import { PurchaseQcResult } from "../po/actions"; | ||||
// import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
export interface SavePickOrderLineRequest { | |||||
itemId: number | |||||
qty: number | |||||
uomId: number | |||||
} | |||||
export interface SavePickOrderRequest { | |||||
type: string | |||||
targetDate: string | |||||
pickOrderLine: SavePickOrderLineRequest[] | |||||
} | |||||
export interface PostPickOrderResponse<T = null> { | |||||
id: number | null; | |||||
name: string; | |||||
code: string; | |||||
type?: string; | |||||
message: string | null; | |||||
errorPosition: string | |||||
entity?: T | T[]; | |||||
} | |||||
export interface PostStockOutLiineResponse<T> { | export interface PostStockOutLiineResponse<T> { | ||||
id: number | null; | id: number | null; | ||||
name: string; | name: string; | ||||
@@ -22,7 +43,7 @@ export interface PostStockOutLiineResponse<T> { | |||||
type?: string; | type?: string; | ||||
message: string | null; | message: string | null; | ||||
errorPosition: string | keyof T; | errorPosition: string | keyof T; | ||||
entity: T | T[]; | |||||
entity: T | T[] | null; | |||||
} | } | ||||
export interface ReleasePickOrderInputs { | export interface ReleasePickOrderInputs { | ||||
@@ -60,6 +81,19 @@ export interface PickOrderApprovalInput { | |||||
rejectQty: number; | rejectQty: number; | ||||
status: string; | status: string; | ||||
} | } | ||||
export const createPickOrder = async (data: SavePickOrderRequest) => { | |||||
console.log(data); | |||||
const po = await serverFetchJson<PostPickOrderResponse>( | |||||
`${BASE_API_URL}/pickOrder/create`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
revalidateTag("pickorder"); | |||||
return po; | |||||
} | |||||
export const consolidatePickOrder = async (ids: number[]) => { | export const consolidatePickOrder = async (ids: number[]) => { | ||||
const pickOrder = await serverFetchJson<any>( | const pickOrder = await serverFetchJson<any>( | ||||
@@ -13,7 +13,7 @@ export interface PickOrderResult { | |||||
id: number; | id: number; | ||||
code: string; | code: string; | ||||
consoCode?: string; | consoCode?: string; | ||||
targetDate: number[]; | |||||
targetDate: string; | |||||
completeDate?: number[]; | completeDate?: number[]; | ||||
type: string; | type: string; | ||||
status: string; | status: string; | ||||
@@ -7,8 +7,9 @@ import { | |||||
import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { CreateItemResponse } from "../../utils"; | import { CreateItemResponse } from "../../utils"; | ||||
import { ItemQc } from "."; | |||||
import { ItemQc, ItemsResult } from "."; | |||||
import { QcChecksInputs } from "../qcCheck/actions"; | import { QcChecksInputs } from "../qcCheck/actions"; | ||||
import { cache } from "react"; | |||||
// export type TypeInputs = { | // export type TypeInputs = { | ||||
// id: number; | // id: number; | ||||
@@ -48,3 +49,16 @@ export const saveItem = async (data: CreateItemInputs) => { | |||||
revalidateTag("items"); | revalidateTag("items"); | ||||
return item; | return item; | ||||
}; | }; | ||||
export interface ItemCombo { | |||||
id: number, | |||||
label: string, | |||||
uomId: number, | |||||
uom: string, | |||||
} | |||||
export const fetchAllItemsInClient = cache(async () => { | |||||
return serverFetchJson<ItemCombo[]>(`${BASE_API_URL}/items/consumables`, { | |||||
next: { tags: ["items"] }, | |||||
}); | |||||
}); |
@@ -10,7 +10,7 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => { | |||||
link.click(); | link.click(); | ||||
}; | }; | ||||
export const convertObjToURLSearchParams = <T extends Object>( | |||||
export const convertObjToURLSearchParams = <T extends object>( | |||||
data?: T | null, | data?: T | null, | ||||
): string => { | ): string => { | ||||
if (isEmpty(data)) { | if (isEmpty(data)) { | ||||
@@ -92,7 +92,7 @@ export const minutesToHoursMinutes = (minutes: number): string => { | |||||
finalMinStr = `1 ${defaultMinStr}` | finalMinStr = `1 ${defaultMinStr}` | ||||
} | } | ||||
let colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : "" | |||||
const colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : "" | |||||
return `${finalHrStr} ${colon} ${finalMinStr}`.trim() | return `${finalHrStr} ${colon} ${finalMinStr}`.trim() | ||||
} | } | ||||
@@ -0,0 +1,318 @@ | |||||
"use client"; | |||||
import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; | |||||
import { | |||||
Autocomplete, | |||||
Box, | |||||
Card, | |||||
CardContent, | |||||
FormControl, | |||||
Grid, | |||||
Stack, | |||||
TextField, | |||||
Tooltip, | |||||
Typography, | |||||
} from "@mui/material"; | |||||
import { Controller, useFormContext } from "react-hook-form"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import StyledDataGrid from "../StyledDataGrid"; | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { | |||||
GridColDef, | |||||
GridRowIdGetter, | |||||
GridRowModel, | |||||
useGridApiContext, | |||||
GridRenderCellParams, | |||||
GridRenderEditCellParams, | |||||
useGridApiRef, | |||||
} from "@mui/x-data-grid"; | |||||
import InputDataGrid from "../InputDataGrid"; | |||||
import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
import { StockInLine } from "@/app/api/po"; | |||||
import { INPUT_DATE_FORMAT, stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||||
import { QcItemWithChecks } from "@/app/api/qc"; | |||||
import axios from "@/app/(main)/axios/axiosInstance"; | |||||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||||
import { SavePickOrderLineRequest, SavePickOrderRequest } from "@/app/api/pickOrder/actions"; | |||||
import TwoLineCell from "../PoDetail/TwoLineCell"; | |||||
import ItemSelect from "./ItemSelect"; | |||||
import { ItemCombo } from "@/app/api/settings/item/actions"; | |||||
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
import dayjs from "dayjs"; | |||||
interface Props { | |||||
items: ItemCombo[]; | |||||
// disabled: boolean; | |||||
} | |||||
type EntryError = | |||||
| { | |||||
[field in keyof SavePickOrderLineRequest]?: string; | |||||
} | |||||
| undefined; | |||||
type PolRow = TableRow<Partial<SavePickOrderLineRequest>, EntryError>; | |||||
// fetchQcItemCheck | |||||
const CreateForm: React.FC<Props> = ({ items }) => { | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation("purchaseOrder"); | |||||
const apiRef = useGridApiRef(); | |||||
const { | |||||
formState: { errors, defaultValues, touchedFields }, | |||||
watch, | |||||
control, | |||||
setValue, | |||||
} = useFormContext<SavePickOrderRequest>(); | |||||
console.log(defaultValues); | |||||
const targetDate = watch("targetDate"); | |||||
//// validate form | |||||
// const accQty = watch("acceptedQty"); | |||||
// const validateForm = useCallback(() => { | |||||
// console.log(accQty); | |||||
// if (accQty > itemDetail.acceptedQty) { | |||||
// setError("acceptedQty", { | |||||
// message: `${t("acceptedQty must not greater than")} ${ | |||||
// itemDetail.acceptedQty | |||||
// }`, | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// if (accQty < 1) { | |||||
// setError("acceptedQty", { | |||||
// message: t("minimal value is 1"), | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// if (isNaN(accQty)) { | |||||
// setError("acceptedQty", { | |||||
// message: t("value must be a number"), | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// }, [accQty]); | |||||
// useEffect(() => { | |||||
// clearErrors(); | |||||
// validateForm(); | |||||
// }, [clearErrors, validateForm]); | |||||
const columns = useMemo<GridColDef[]>( | |||||
() => [ | |||||
{ | |||||
field: "itemId", | |||||
headerName: t("Item"), | |||||
flex: 1, | |||||
editable: true, | |||||
valueFormatter(params) { | |||||
const row = params.id ? params.api.getRow<PolRow>(params.id) : null; | |||||
if (!row) { | |||||
return null; | |||||
} | |||||
const Item = items.find((q) => q.id === row.itemId); | |||||
return Item ? Item.label : t("Please select item"); | |||||
}, | |||||
renderCell(params: GridRenderCellParams<PolRow, number>) { | |||||
console.log(params.value); | |||||
return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||||
}, | |||||
renderEditCell(params: GridRenderEditCellParams<PolRow, number>) { | |||||
const errorMessage = | |||||
params.row._error?.[params.field as keyof SavePickOrderLineRequest]; | |||||
console.log(errorMessage); | |||||
const content = ( | |||||
// <></> | |||||
<ItemSelect | |||||
allItems={items} | |||||
value={params.row.itemId} | |||||
onItemSelect={async (itemId, uom, uomId) => { | |||||
console.log(uom) | |||||
await params.api.setEditCellValue({ | |||||
id: params.id, | |||||
field: "itemId", | |||||
value: itemId, | |||||
}); | |||||
await params.api.setEditCellValue({ | |||||
id: params.id, | |||||
field: "uom", | |||||
value: uom | |||||
}) | |||||
await params.api.setEditCellValue({ | |||||
id: params.id, | |||||
field: "uomId", | |||||
value: uomId | |||||
}) | |||||
}} | |||||
/> | |||||
); | |||||
return errorMessage ? ( | |||||
<Tooltip title={errorMessage}> | |||||
<Box width="100%">{content}</Box> | |||||
</Tooltip> | |||||
) : ( | |||||
content | |||||
); | |||||
}, | |||||
}, | |||||
{ | |||||
field: "qty", | |||||
headerName: t("qty"), | |||||
flex: 1, | |||||
type: "number", | |||||
editable: true, | |||||
renderEditCell(params: GridRenderEditCellParams<PolRow>) { | |||||
const errorMessage = | |||||
params.row._error?.[params.field as keyof SavePickOrderLineRequest]; | |||||
const content = <GridEditInputCell {...params} />; | |||||
return errorMessage ? ( | |||||
<Tooltip title={t(errorMessage)}> | |||||
<Box width="100%">{content}</Box> | |||||
</Tooltip> | |||||
) : ( | |||||
content | |||||
); | |||||
}, | |||||
}, | |||||
{ | |||||
field: "uom", | |||||
headerName: t("uom"), | |||||
flex: 1, | |||||
editable: true, | |||||
// renderEditCell(params: GridRenderEditCellParams<PolRow>) { | |||||
// console.log(params.row) | |||||
// const errorMessage = | |||||
// params.row._error?.[params.field as keyof SavePickOrderLineRequest]; | |||||
// const content = <GridEditInputCell {...params} />; | |||||
// return errorMessage ? ( | |||||
// <Tooltip title={t(errorMessage)}> | |||||
// <Box width="100%">{content}</Box> | |||||
// </Tooltip> | |||||
// ) : ( | |||||
// content | |||||
// ); | |||||
// } | |||||
} | |||||
], | |||||
[items, t], | |||||
); | |||||
/// validate datagrid | |||||
const validation = useCallback( | |||||
(newRow: GridRowModel<PolRow>): EntryError => { | |||||
const error: EntryError = {}; | |||||
const { itemId, qty } = newRow; | |||||
if (!itemId || itemId <= 0) { | |||||
error["itemId"] = t("select qc"); | |||||
} | |||||
if (!qty || qty <= 0) { | |||||
error["qty"] = t("enter a qty"); | |||||
} | |||||
return Object.keys(error).length > 0 ? error : undefined; | |||||
}, | |||||
[], | |||||
); | |||||
const typeList = [ | |||||
{ | |||||
type: "Consumable" | |||||
} | |||||
] | |||||
const onChange = useCallback( | |||||
(event: React.SyntheticEvent, newValue: {type: string}) => { | |||||
console.log(newValue); | |||||
setValue("type", newValue.type); | |||||
}, | |||||
[setValue], | |||||
); | |||||
return ( | |||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||||
<Grid item xs={12}> | |||||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||||
{t("Pick Order Detail")} | |||||
</Typography> | |||||
</Grid> | |||||
<Grid | |||||
container | |||||
justifyContent="flex-start" | |||||
alignItems="flex-start" | |||||
spacing={2} | |||||
sx={{ mt: 0.5 }} | |||||
> | |||||
<Grid item xs={6} lg={6}> | |||||
<FormControl fullWidth> | |||||
<Autocomplete | |||||
disableClearable | |||||
fullWidth | |||||
getOptionLabel={(option) => option.type} | |||||
options={typeList} | |||||
onChange={onChange} | |||||
renderInput={(params) => <TextField {...params} label={t("type")}/>} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<Controller | |||||
control={control} | |||||
name="targetDate" | |||||
// rules={{ required: !Boolean(productionDate) }} | |||||
render={({ field }) => { | |||||
return ( | |||||
<LocalizationProvider | |||||
dateAdapter={AdapterDayjs} | |||||
adapterLocale={`${language}-hk`} | |||||
> | |||||
<DatePicker | |||||
{...field} | |||||
sx={{ width: "100%" }} | |||||
label={t("targetDate")} | |||||
value={targetDate ? dayjs(targetDate) : undefined} | |||||
onChange={(date) => { | |||||
console.log(date); | |||||
if (!date) return; | |||||
console.log(date.format(INPUT_DATE_FORMAT)); | |||||
setValue("targetDate", date.format(INPUT_DATE_FORMAT)); | |||||
// field.onChange(date); | |||||
}} | |||||
inputRef={field.ref} | |||||
slotProps={{ | |||||
textField: { | |||||
// required: true, | |||||
error: Boolean(errors.targetDate?.message), | |||||
helperText: errors.targetDate?.message, | |||||
}, | |||||
}} | |||||
/> | |||||
</LocalizationProvider> | |||||
); | |||||
}} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
<Grid | |||||
container | |||||
justifyContent="flex-start" | |||||
alignItems="flex-start" | |||||
spacing={2} | |||||
sx={{ mt: 0.5 }} | |||||
> | |||||
<Grid item xs={12}> | |||||
<InputDataGrid<SavePickOrderRequest, SavePickOrderLineRequest, EntryError> | |||||
apiRef={apiRef} | |||||
checkboxSelection={false} | |||||
_formKey={"pickOrderLine"} | |||||
columns={columns} | |||||
validateRow={validation} | |||||
needAdd={true} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Grid> | |||||
); | |||||
}; | |||||
export default CreateForm; |
@@ -0,0 +1,98 @@ | |||||
import { createPickOrder, SavePickOrderRequest } from "@/app/api/pickOrder/actions"; | |||||
import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | |||||
import dayjs from "dayjs"; | |||||
import arraySupport from "dayjs/plugin/arraySupport"; | |||||
import { useCallback } from "react"; | |||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import CreateForm from "./CreateForm"; | |||||
import { ItemCombo } from "@/app/api/settings/item/actions"; | |||||
import { Check } from "@mui/icons-material"; | |||||
dayjs.extend(arraySupport); | |||||
const style = { | |||||
position: "absolute", | |||||
top: "50%", | |||||
left: "50%", | |||||
transform: "translate(-50%, -50%)", | |||||
overflow: "scroll", | |||||
bgcolor: "background.paper", | |||||
pt: 5, | |||||
px: 5, | |||||
pb: 10, | |||||
display: "block", | |||||
width: { xs: "60%", sm: "60%", md: "60%" }, | |||||
}; | |||||
interface Props extends Omit<ModalProps, "children"> { | |||||
items: ItemCombo[] | |||||
} | |||||
const CreatePickOrderModal: React.FC<Props> = ({ | |||||
open, | |||||
onClose, | |||||
items | |||||
}) => { | |||||
const { t } = useTranslation("pickOrder"); | |||||
const formProps = useForm<SavePickOrderRequest>(); | |||||
const errors = formProps.formState.errors; | |||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
(...args) => { | |||||
onClose?.(...args); | |||||
// reset(); | |||||
}, | |||||
[onClose] | |||||
); | |||||
const onSubmit = useCallback<SubmitHandler<SavePickOrderRequest>>( | |||||
async (data, event) => { | |||||
console.log(data) | |||||
try { | |||||
const res = await createPickOrder(data) | |||||
if (res.id) { | |||||
closeHandler({}, "backdropClick"); | |||||
} | |||||
} catch (error) { | |||||
console.log(error) | |||||
throw error | |||||
} | |||||
// formProps.reset() | |||||
}, | |||||
[closeHandler] | |||||
); | |||||
return ( | |||||
<> | |||||
<FormProvider {...formProps}> | |||||
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}> | |||||
<Box | |||||
sx={style} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit)} | |||||
> | |||||
<CreateForm | |||||
items={items} | |||||
/> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
name="submit" | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
> | |||||
{t("submit")} | |||||
</Button> | |||||
<Button | |||||
name="reset" | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
onClick={() => formProps.reset()} | |||||
> | |||||
{t("reset")} | |||||
</Button> | |||||
</Stack> | |||||
</Box> | |||||
</Modal> | |||||
</FormProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default CreatePickOrderModal; |
@@ -0,0 +1,79 @@ | |||||
import { ItemCombo } from "@/app/api/settings/item/actions"; | |||||
import { Autocomplete, TextField } from "@mui/material"; | |||||
import { useCallback, useMemo } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
interface CommonProps { | |||||
allItems: ItemCombo[]; | |||||
error?: boolean; | |||||
} | |||||
interface SingleAutocompleteProps extends CommonProps { | |||||
value: number | string | undefined; | |||||
onItemSelect: (itemId: number, uom: string, uomId: number) => void | Promise<void>; | |||||
// multiple: false; | |||||
} | |||||
type Props = SingleAutocompleteProps; | |||||
const ItemSelect: React.FC<Props> = ({ | |||||
allItems, | |||||
value, | |||||
error, | |||||
onItemSelect | |||||
}) => { | |||||
const { t } = useTranslation("item"); | |||||
const filteredItems = useMemo(() => { | |||||
return allItems | |||||
}, [allItems]) | |||||
const options = useMemo(() => { | |||||
return [ | |||||
{ | |||||
value: -1, // think think sin | |||||
label: t("None"), | |||||
uom: "", | |||||
uomId: -1, | |||||
group: "default", | |||||
}, | |||||
...filteredItems.map((i) => ({ | |||||
value: i.id as number, | |||||
label: i.label, | |||||
uom: i.uom, | |||||
uomId: i.uomId, | |||||
group: "existing", | |||||
})), | |||||
]; | |||||
}, [t, filteredItems]); | |||||
const currentValue = options.find((o) => o.value === value) || options[0]; | |||||
const onChange = useCallback( | |||||
( | |||||
event: React.SyntheticEvent, | |||||
newValue: { value: number; uom: string; uomId: number; group: string } | { uom: string; uomId: number; value: number }[], | |||||
) => { | |||||
const singleNewVal = newValue as { | |||||
value: number; | |||||
uom: string; | |||||
uomId: number; | |||||
group: string; | |||||
}; | |||||
onItemSelect(singleNewVal.value, singleNewVal.uom, singleNewVal.uomId) | |||||
} | |||||
, [onItemSelect]) | |||||
return ( | |||||
<Autocomplete | |||||
noOptionsText={t("No Item")} | |||||
disableClearable | |||||
fullWidth | |||||
value={currentValue} | |||||
onChange={onChange} | |||||
getOptionLabel={(option) => option.label} | |||||
options={options} | |||||
renderInput={(params) => <TextField {...params} error={error} />} | |||||
/> | |||||
); | |||||
} | |||||
export default ItemSelect |
@@ -1,33 +1,27 @@ | |||||
"use client"; | "use client"; | ||||
import { PickOrderResult } from "@/app/api/pickOrder"; | import { PickOrderResult } from "@/app/api/pickOrder"; | ||||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
import { useCallback, useMemo, useState } from "react"; | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
import SearchResults, { Column } from "../SearchResults"; | |||||
import { | import { | ||||
flatten, | flatten, | ||||
groupBy, | |||||
intersectionWith, | intersectionWith, | ||||
isEmpty, | isEmpty, | ||||
map, | |||||
sortBy, | sortBy, | ||||
sortedUniq, | |||||
uniqBy, | uniqBy, | ||||
upperCase, | upperCase, | ||||
upperFirst, | upperFirst, | ||||
} from "lodash"; | } from "lodash"; | ||||
import { | import { | ||||
arrayToDateString, | |||||
arrayToDayjs, | arrayToDayjs, | ||||
dateStringToDayjs, | |||||
} from "@/app/utils/formatUtil"; | } from "@/app/utils/formatUtil"; | ||||
import dayjs from "dayjs"; | |||||
import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | |||||
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||||
import PickOrders from "./PickOrders"; | import PickOrders from "./PickOrders"; | ||||
import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | ||||
import CreatePickOrderModal from "./CreatePickOrderModal"; | |||||
import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions"; | |||||
import { fetchPickOrderClient } from "@/app/api/pickOrder/actions"; | |||||
import { getServerI18n } from "@/i18n"; | |||||
interface Props { | interface Props { | ||||
pickOrders: PickOrderResult[]; | pickOrders: PickOrderResult[]; | ||||
} | } | ||||
@@ -41,15 +35,30 @@ type SearchParamNames = keyof SearchQuery; | |||||
const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | ||||
const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | |||||
const [items, setItems] = useState<ItemCombo[]>([]) | |||||
const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | ||||
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const [totalCount, setTotalCount] = useState<number>(); | |||||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
(_e, newValue) => { | (_e, newValue) => { | ||||
setTabIndex(newValue); | setTabIndex(newValue); | ||||
}, | }, | ||||
[], | [], | ||||
); | ); | ||||
const openCreateModal = useCallback(async () => { | |||||
console.log("testing") | |||||
const res = await fetchAllItemsInClient() | |||||
console.log(res) | |||||
setItems(res) | |||||
setIsOpenCreateModal(true) | |||||
}, []) | |||||
const closeCreateModal = useCallback(() => { | |||||
setIsOpenCreateModal(false) | |||||
}, []) | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
() => [ | () => [ | ||||
@@ -113,15 +122,67 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
), | ), | ||||
}, | }, | ||||
], | ], | ||||
[t], | |||||
[pickOrders, t], | |||||
); | ); | ||||
const fetchNewPagePickOrder = useCallback( | |||||
async ( | |||||
pagingController: Record<string, number>, | |||||
filterArgs: Record<string, number>, | |||||
) => { | |||||
const params = { | |||||
...pagingController, | |||||
...filterArgs, | |||||
}; | |||||
const res = await fetchPickOrderClient(params); | |||||
if (res) { | |||||
console.log(res); | |||||
setFilteredPickOrders(res.records); | |||||
setTotalCount(res.total); | |||||
} | |||||
}, | |||||
[], | |||||
); | |||||
const onReset = useCallback(() => { | const onReset = useCallback(() => { | ||||
setFilteredPickOrders(pickOrders); | setFilteredPickOrders(pickOrders); | ||||
}, [pickOrders]); | }, [pickOrders]); | ||||
useEffect(() => { | |||||
if (!isOpenCreateModal) { | |||||
setTabIndex(1) | |||||
setTimeout(async () => { | |||||
setTabIndex(0) | |||||
}, 200) | |||||
} | |||||
}, [isOpenCreateModal]) | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack | |||||
rowGap={2} | |||||
> | |||||
<Grid container> | |||||
<Grid item xs={8}> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Pick Order")} | |||||
</Typography> | |||||
</Grid> | |||||
<Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||||
<Button | |||||
onClick={openCreateModal} | |||||
> | |||||
{t("create")} | |||||
</Button> | |||||
{isOpenCreateModal && | |||||
<CreatePickOrderModal | |||||
open={isOpenCreateModal} | |||||
onClose={closeCreateModal} | |||||
items={items} | |||||
/>} | |||||
</Grid> | |||||
</Grid> | |||||
</Stack> | |||||
<SearchBox | <SearchBox | ||||
criteria={searchCriteria} | criteria={searchCriteria} | ||||
onSearch={(query) => { | onSearch={(query) => { | ||||
@@ -4,13 +4,15 @@ import { PickOrderResult } from "@/app/api/pickOrder"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import { isEmpty, upperCase, upperFirst } from "lodash"; | import { isEmpty, upperCase, upperFirst } from "lodash"; | ||||
import { arrayToDateString } from "@/app/utils/formatUtil"; | |||||
import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import { | import { | ||||
consolidatePickOrder, | consolidatePickOrder, | ||||
fetchPickOrderClient, | fetchPickOrderClient, | ||||
} from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
import useUploadContext from "../UploadProvider/useUploadContext"; | import useUploadContext from "../UploadProvider/useUploadContext"; | ||||
import dayjs from "dayjs"; | |||||
import arraySupport from "dayjs/plugin/arraySupport"; | |||||
dayjs.extend(arraySupport); | |||||
interface Props { | interface Props { | ||||
filteredPickOrders: PickOrderResult[]; | filteredPickOrders: PickOrderResult[]; | ||||
filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
@@ -30,21 +32,6 @@ const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||||
}); | }); | ||||
const [totalCount, setTotalCount] = useState<number>(); | const [totalCount, setTotalCount] = useState<number>(); | ||||
const handleConsolidatedRows = useCallback(async () => { | |||||
console.log(selectedRows); | |||||
setIsUploading(true); | |||||
try { | |||||
const res = await consolidatePickOrder(selectedRows as number[]); | |||||
if (res) { | |||||
console.log(res); | |||||
} | |||||
} catch { | |||||
setIsUploading(false); | |||||
} | |||||
fetchNewPagePickOrder(pagingController, filterArgs); | |||||
setIsUploading(false); | |||||
}, [selectedRows, pagingController]); | |||||
const fetchNewPagePickOrder = useCallback( | const fetchNewPagePickOrder = useCallback( | ||||
async ( | async ( | ||||
pagingController: Record<string, number>, | pagingController: Record<string, number>, | ||||
@@ -66,6 +53,22 @@ const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||||
[], | [], | ||||
); | ); | ||||
const handleConsolidatedRows = useCallback(async () => { | |||||
console.log(selectedRows); | |||||
setIsUploading(true); | |||||
try { | |||||
const res = await consolidatePickOrder(selectedRows as number[]); | |||||
if (res) { | |||||
console.log(res); | |||||
} | |||||
} catch { | |||||
setIsUploading(false); | |||||
} | |||||
fetchNewPagePickOrder(pagingController, filterArgs); | |||||
setIsUploading(false); | |||||
}, [selectedRows, setIsUploading, fetchNewPagePickOrder, pagingController, filterArgs]); | |||||
useEffect(() => { | useEffect(() => { | ||||
fetchNewPagePickOrder(pagingController, filterArgs); | fetchNewPagePickOrder(pagingController, filterArgs); | ||||
}, [fetchNewPagePickOrder, pagingController, filterArgs]); | }, [fetchNewPagePickOrder, pagingController, filterArgs]); | ||||
@@ -109,7 +112,11 @@ const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||||
name: "targetDate", | name: "targetDate", | ||||
label: t("Target Date"), | label: t("Target Date"), | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return arrayToDateString(params.targetDate); | |||||
return ( | |||||
dayjs(params.targetDate) | |||||
.add(-1, "month") | |||||
.format(OUTPUT_DATE_FORMAT) | |||||
); | |||||
}, | }, | ||||
}, | }, | ||||
{ | { | ||||
@@ -0,0 +1,73 @@ | |||||
import { ItemCombo } from "@/app/api/settings/item/actions"; | |||||
import { Autocomplete, TextField } from "@mui/material"; | |||||
import { useCallback, useMemo } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
interface CommonProps { | |||||
allUom: ItemCombo[]; | |||||
error?: boolean; | |||||
} | |||||
interface SingleAutocompleteProps extends CommonProps { | |||||
value: number | string | undefined; | |||||
onUomSelect: (itemId: number) => void | Promise<void>; | |||||
// multiple: false; | |||||
} | |||||
type Props = SingleAutocompleteProps; | |||||
const UomSelect: React.FC<Props> = ({ | |||||
allUom, | |||||
value, | |||||
error, | |||||
onUomSelect | |||||
}) => { | |||||
const { t } = useTranslation("item"); | |||||
const filteredUom = useMemo(() => { | |||||
return allUom | |||||
}, [allUom]) | |||||
const options = useMemo(() => { | |||||
return [ | |||||
{ | |||||
value: -1, // think think sin | |||||
label: t("None"), | |||||
group: "default", | |||||
}, | |||||
...filteredUom.map((i) => ({ | |||||
value: i.id as number, | |||||
label: i.label, | |||||
group: "existing", | |||||
})), | |||||
]; | |||||
}, [t, filteredUom]); | |||||
const currentValue = options.find((o) => o.value === value) || options[0]; | |||||
const onChange = useCallback( | |||||
( | |||||
event: React.SyntheticEvent, | |||||
newValue: { value: number; group: string } | { value: number }[], | |||||
) => { | |||||
const singleNewVal = newValue as { | |||||
value: number; | |||||
group: string; | |||||
}; | |||||
onUomSelect(singleNewVal.value) | |||||
} | |||||
, [onUomSelect]) | |||||
return ( | |||||
<Autocomplete | |||||
noOptionsText={t("No Uom")} | |||||
disableClearable | |||||
fullWidth | |||||
value={currentValue} | |||||
onChange={onChange} | |||||
getOptionLabel={(option) => option.label} | |||||
options={options} | |||||
renderInput={(params) => <TextField {...params} error={error} />} | |||||
/> | |||||
); | |||||
} | |||||
export default UomSelect |
@@ -15,7 +15,7 @@ import { useSession } from "next-auth/react"; | |||||
import { defaultPagingController } from "../SearchResults/SearchResults"; | import { defaultPagingController } from "../SearchResults/SearchResults"; | ||||
import { fetchPoListClient, testing } from "@/app/api/po/actions"; | import { fetchPoListClient, testing } from "@/app/api/po/actions"; | ||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import { arrayToDateString } from "@/app/utils/formatUtil"; | |||||
import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import arraySupport from "dayjs/plugin/arraySupport"; | import arraySupport from "dayjs/plugin/arraySupport"; | ||||
dayjs.extend(arraySupport); | dayjs.extend(arraySupport); | ||||
@@ -72,7 +72,7 @@ const PoSearch: React.FC<Props> = ({ | |||||
[router], | [router], | ||||
); | ); | ||||
const onDeleteClick = useCallback((po: PoResult) => {}, [router]); | |||||
const onDeleteClick = useCallback((po: PoResult) => {}, []); | |||||
const columns = useMemo<Column<PoResult>[]>( | const columns = useMemo<Column<PoResult>[]>( | ||||
() => [ | () => [ | ||||
@@ -91,10 +91,9 @@ const PoSearch: React.FC<Props> = ({ | |||||
label: t("OrderDate"), | label: t("OrderDate"), | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return ( | return ( | ||||
// dayjs(params.orderDate) | |||||
arrayToDateString(params.orderDate) | |||||
// .add(-1, "month") | |||||
// .format(OUTPUT_DATE_FORMAT) | |||||
dayjs(params.orderDate) | |||||
.add(-1, "month") | |||||
.format(OUTPUT_DATE_FORMAT) | |||||
); | ); | ||||
}, | }, | ||||
}, | }, | ||||