@@ -1,4 +1,4 @@ | |||
API_URL=http://localhost:8090/api | |||
API_URL=http://10.100.0.81:8090/api | |||
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 ( | |||
<> | |||
<Stack | |||
direction={"row"} | |||
justifyContent={"space-between"} | |||
flexWrap={"wrap"} | |||
rowGap={2} | |||
> | |||
<Typography variant="h4" marginInlineEnd={2}> | |||
{t("Pick Order")} | |||
</Typography> | |||
</Stack> | |||
<I18nProvider namespaces={["pickOrder", "common"]}> | |||
<Suspense fallback={<PickOrderSearch.Loading />}> | |||
<PickOrderSearch /> | |||
@@ -15,6 +15,27 @@ import { | |||
} from "."; | |||
import { PurchaseQcResult } from "../po/actions"; | |||
// 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> { | |||
id: number | null; | |||
name: string; | |||
@@ -22,7 +43,7 @@ export interface PostStockOutLiineResponse<T> { | |||
type?: string; | |||
message: string | null; | |||
errorPosition: string | keyof T; | |||
entity: T | T[]; | |||
entity: T | T[] | null; | |||
} | |||
export interface ReleasePickOrderInputs { | |||
@@ -60,6 +81,19 @@ export interface PickOrderApprovalInput { | |||
rejectQty: number; | |||
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[]) => { | |||
const pickOrder = await serverFetchJson<any>( | |||
@@ -13,7 +13,7 @@ export interface PickOrderResult { | |||
id: number; | |||
code: string; | |||
consoCode?: string; | |||
targetDate: number[]; | |||
targetDate: string; | |||
completeDate?: number[]; | |||
type: string; | |||
status: string; | |||
@@ -7,8 +7,9 @@ import { | |||
import { revalidateTag } from "next/cache"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { CreateItemResponse } from "../../utils"; | |||
import { ItemQc } from "."; | |||
import { ItemQc, ItemsResult } from "."; | |||
import { QcChecksInputs } from "../qcCheck/actions"; | |||
import { cache } from "react"; | |||
// export type TypeInputs = { | |||
// id: number; | |||
@@ -48,3 +49,16 @@ export const saveItem = async (data: CreateItemInputs) => { | |||
revalidateTag("items"); | |||
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(); | |||
}; | |||
export const convertObjToURLSearchParams = <T extends Object>( | |||
export const convertObjToURLSearchParams = <T extends object>( | |||
data?: T | null, | |||
): string => { | |||
if (isEmpty(data)) { | |||
@@ -92,7 +92,7 @@ export const minutesToHoursMinutes = (minutes: number): string => { | |||
finalMinStr = `1 ${defaultMinStr}` | |||
} | |||
let colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : "" | |||
const colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : "" | |||
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"; | |||
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 SearchBox, { Criterion } from "../SearchBox"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import { | |||
flatten, | |||
groupBy, | |||
intersectionWith, | |||
isEmpty, | |||
map, | |||
sortBy, | |||
sortedUniq, | |||
uniqBy, | |||
upperCase, | |||
upperFirst, | |||
} from "lodash"; | |||
import { | |||
arrayToDateString, | |||
arrayToDayjs, | |||
dateStringToDayjs, | |||
} 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 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 { | |||
pickOrders: PickOrderResult[]; | |||
} | |||
@@ -41,15 +35,30 @@ type SearchParamNames = keyof SearchQuery; | |||
const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
const { t } = useTranslation("pickOrder"); | |||
const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | |||
const [items, setItems] = useState<ItemCombo[]>([]) | |||
const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | |||
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const [totalCount, setTotalCount] = useState<number>(); | |||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
(_e, 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( | |||
() => [ | |||
@@ -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(() => { | |||
setFilteredPickOrders(pickOrders); | |||
}, [pickOrders]); | |||
useEffect(() => { | |||
if (!isOpenCreateModal) { | |||
setTabIndex(1) | |||
setTimeout(async () => { | |||
setTabIndex(0) | |||
}, 200) | |||
} | |||
}, [isOpenCreateModal]) | |||
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 | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
@@ -4,13 +4,15 @@ import { PickOrderResult } from "@/app/api/pickOrder"; | |||
import { useTranslation } from "react-i18next"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { isEmpty, upperCase, upperFirst } from "lodash"; | |||
import { arrayToDateString } from "@/app/utils/formatUtil"; | |||
import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
import { | |||
consolidatePickOrder, | |||
fetchPickOrderClient, | |||
} from "@/app/api/pickOrder/actions"; | |||
import useUploadContext from "../UploadProvider/useUploadContext"; | |||
import dayjs from "dayjs"; | |||
import arraySupport from "dayjs/plugin/arraySupport"; | |||
dayjs.extend(arraySupport); | |||
interface Props { | |||
filteredPickOrders: PickOrderResult[]; | |||
filterArgs: Record<string, any>; | |||
@@ -30,21 +32,6 @@ const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||
}); | |||
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( | |||
async ( | |||
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(() => { | |||
fetchNewPagePickOrder(pagingController, filterArgs); | |||
}, [fetchNewPagePickOrder, pagingController, filterArgs]); | |||
@@ -109,7 +112,11 @@ const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => { | |||
name: "targetDate", | |||
label: t("Target Date"), | |||
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 { fetchPoListClient, testing } from "@/app/api/po/actions"; | |||
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"; | |||
dayjs.extend(arraySupport); | |||
@@ -72,7 +72,7 @@ const PoSearch: React.FC<Props> = ({ | |||
[router], | |||
); | |||
const onDeleteClick = useCallback((po: PoResult) => {}, [router]); | |||
const onDeleteClick = useCallback((po: PoResult) => {}, []); | |||
const columns = useMemo<Column<PoResult>[]>( | |||
() => [ | |||
@@ -91,10 +91,9 @@ const PoSearch: React.FC<Props> = ({ | |||
label: t("OrderDate"), | |||
renderCell: (params) => { | |||
return ( | |||
// dayjs(params.orderDate) | |||
arrayToDateString(params.orderDate) | |||
// .add(-1, "month") | |||
// .format(OUTPUT_DATE_FORMAT) | |||
dayjs(params.orderDate) | |||
.add(-1, "month") | |||
.format(OUTPUT_DATE_FORMAT) | |||
); | |||
}, | |||
}, | |||