@@ -1,6 +1,6 @@ | |||
import { preloadInventory } from "@/app/api/inventory"; | |||
import InventorySearch from "@/components/InventorySearch"; | |||
import { getServerI18n } from "@/i18n"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Stack, Typography } from "@mui/material"; | |||
import { Metadata } from "next"; | |||
import { Suspense } from "react"; | |||
@@ -10,7 +10,7 @@ export const metadata: Metadata = { | |||
} | |||
const Inventory: React.FC = async () => { | |||
const { t } = await getServerI18n("inventory") | |||
const { t } = await getServerI18n("inventory", "common") | |||
preloadInventory() | |||
@@ -25,9 +25,11 @@ const Inventory: React.FC = async () => { | |||
{t("Inventory")} | |||
</Typography> | |||
</Stack> | |||
<Suspense fallback={<InventorySearch.Loading />}> | |||
<InventorySearch /> | |||
</Suspense> | |||
<I18nProvider namespaces={["common", "inventory"]}> | |||
<Suspense fallback={<InventorySearch.Loading />}> | |||
<InventorySearch /> | |||
</Suspense> | |||
</I18nProvider> | |||
</>; | |||
} | |||
@@ -16,29 +16,20 @@ export const metadata: Metadata = { | |||
const PurchaseOrder: React.FC = async () => { | |||
const { t } = await getServerI18n("purchaseOrder"); | |||
// preloadClaims(); | |||
// preloadClaims(); | |||
return ( | |||
<> | |||
<I18nProvider namespaces={["purchaseOrder", "common"]}> | |||
<Stack | |||
direction="row" | |||
justifyContent="space-between" | |||
flexWrap="wrap" | |||
rowGap={2} | |||
> | |||
{/* <Button | |||
variant="contained" | |||
startIcon={<Add />} | |||
LinkComponent={Link} | |||
href="/po/create" | |||
<I18nProvider namespaces={["purchaseOrder", "common"]}> | |||
<Stack | |||
direction="row" | |||
justifyContent="space-between" | |||
flexWrap="wrap" | |||
rowGap={2} | |||
> | |||
{t("Create Po")} | |||
</Button> */} | |||
</Stack> | |||
<Suspense fallback={<PoSearch.Loading />}> | |||
<PoSearch /> | |||
</Suspense> | |||
</Stack> | |||
<Suspense fallback={<PoSearch.Loading />}> | |||
<PoSearch /> | |||
</Suspense> | |||
</I18nProvider> | |||
</> | |||
); | |||
@@ -11,9 +11,9 @@ export interface InventoryResult { | |||
qty: number; | |||
uomCode: string; | |||
uomUdfudesc: string; | |||
germPerSmallestUnit: number; | |||
qtyPerSmallestUnit: number; | |||
smallestUnit: string; | |||
// germPerSmallestUnit: number; | |||
// qtyPerSmallestUnit: number; | |||
// smallestUnit: string; | |||
price: number; | |||
currencyName: string; | |||
status: string; | |||
@@ -8,6 +8,11 @@ export interface M18ImportPoForm { | |||
modifiedDateTo: string, | |||
} | |||
export interface M18ImportDoForm { | |||
modifiedDateFrom: string, | |||
modifiedDateTo: string, | |||
} | |||
export interface M18ImportPqForm { | |||
modifiedDateFrom: string, | |||
modifiedDateTo: string, | |||
@@ -20,6 +25,7 @@ export interface M18ImportMasterDataForm { | |||
export interface M18ImportTestingForm { | |||
po: M18ImportPoForm, | |||
do: M18ImportDoForm, | |||
pq: M18ImportPqForm, | |||
masterData: M18ImportMasterDataForm, | |||
} | |||
@@ -32,6 +38,14 @@ export const testM18ImportPo = async (data: M18ImportPoForm) => { | |||
}) | |||
} | |||
export const testM18ImportDo = async (data: M18ImportDoForm) => { | |||
return serverFetchWithNoContent(`${BASE_API_URL}/m18/do`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}) | |||
} | |||
export const testM18ImportPq = async (data: M18ImportPqForm) => { | |||
return serverFetchWithNoContent(`${BASE_API_URL}/m18/pq`, { | |||
method: "POST", | |||
@@ -27,7 +27,7 @@ type SearchParamNames = keyof SearchQuery; | |||
const InventorySearch: React.FC<Props> = ({ | |||
inventories, | |||
}) => { | |||
const { t } = useTranslation("inventories"); | |||
const { t } = useTranslation(["inventory", "common"]); | |||
const [filteredInventories, setFilteredInventories] = useState(inventories) | |||
@@ -56,6 +56,9 @@ const InventorySearch: React.FC<Props> = ({ | |||
{ | |||
name: "type", | |||
label: t("Type"), | |||
renderCell: (params) => { | |||
return t(params.type) | |||
} | |||
}, | |||
{ | |||
name: "qty", | |||
@@ -68,17 +71,17 @@ const InventorySearch: React.FC<Props> = ({ | |||
name: "uomUdfudesc", | |||
label: t("UoM"), | |||
}, | |||
{ | |||
name: "qtyPerSmallestUnit", | |||
label: t("Qty Per Smallest Unit"), | |||
align: "right", | |||
headerAlign: "right", | |||
type: "decimal" | |||
}, | |||
{ | |||
name: "smallestUnit", | |||
label: t("Smallest Unit"), | |||
}, | |||
// { | |||
// name: "qtyPerSmallestUnit", | |||
// label: t("Qty Per Smallest Unit"), | |||
// align: "right", | |||
// headerAlign: "right", | |||
// type: "decimal" | |||
// }, | |||
// { | |||
// name: "smallestUnit", | |||
// label: t("Smallest Unit"), | |||
// }, | |||
// { | |||
// name: "price", | |||
// label: t("Price"), | |||
@@ -0,0 +1,126 @@ | |||
"use client" | |||
import { M18ImportTestingForm, M18ImportDoForm } from "@/app/api/settings/m18ImportTesting/actions"; | |||
import { INPUT_DATE_FORMAT, OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateTimeStringToDayjs } from "@/app/utils/formatUtil"; | |||
import { Check } from "@mui/icons-material"; | |||
import { Box, Button, Card, CardContent, FormControl, Grid, Stack, Typography } from "@mui/material"; | |||
import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
import dayjs, { Dayjs } from "dayjs"; | |||
import React, { useCallback, useState } from "react"; | |||
import { Controller, useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
interface Props { | |||
} | |||
const M18ImportDo: React.FC<Props> = ({ | |||
}) => { | |||
const { t } = useTranslation() | |||
const [isLoading, setIsLoading] = useState(false) | |||
const { | |||
control, | |||
formState: { errors }, | |||
watch | |||
} = useFormContext<M18ImportTestingForm>() | |||
const handleDateTimePickerOnChange = useCallback((value: Dayjs | null, onChange: (value: any) => void) => { | |||
const formattedValue = value ? value.format(`${INPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`) : null | |||
onChange(formattedValue) | |||
}, []) | |||
return ( | |||
<Card sx={{ width: '100%' }}> | |||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||
<Grid container> | |||
<Grid container> | |||
<Typography variant="overline">{t("Import Delivery Order")}</Typography> | |||
</Grid> | |||
<Grid item xs={8}> | |||
<LocalizationProvider | |||
dateAdapter={AdapterDayjs} | |||
// TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD | |||
adapterLocale="zh-hk" | |||
> | |||
<Box display="flex"> | |||
<Controller | |||
control={control} | |||
name="do.modifiedDateFrom" | |||
// rules={{ | |||
// required: "Please input the date From!", | |||
// validate: { | |||
// isValid: (value) => | |||
// value && dateTimeStringToDayjs(value).isValid() ? true : "Invalid date-time" | |||
// }, | |||
// }} | |||
render={({ field, fieldState: { error } }) => ( | |||
<DateTimePicker | |||
label={t("Modified Date From *")} | |||
format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||
onChange={(newValue: Dayjs | null) => (handleDateTimePickerOnChange(newValue, field.onChange))} | |||
slotProps={{ | |||
textField: { | |||
error: !!error, | |||
helperText: error ? error.message : null | |||
} | |||
}} | |||
/> | |||
)} | |||
/> | |||
<Box | |||
display="flex" | |||
alignItems="center" | |||
justifyContent="center" | |||
marginInline={2} | |||
> | |||
{"-"} | |||
</Box> | |||
<Controller | |||
control={control} | |||
name="do.modifiedDateTo" | |||
// rules={{ | |||
// required: "Please input the date to!", | |||
// validate: { | |||
// isValid: (value) => | |||
// value && dateTimeStringToDayjs(value).isValid() ? true : "Invalid date-time", | |||
// isFuture: (value) => | |||
// dateTimeStringToDayjs(value).isAfter(watch("do.dateFrom")) || "Date must be in the future", | |||
// }, | |||
// }} | |||
render={({ field, fieldState: { error } }) => ( | |||
<DateTimePicker | |||
label={t("Modified Date To *")} | |||
format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||
onChange={(newValue: Dayjs | null) => (handleDateTimePickerOnChange(newValue, field.onChange))} | |||
slotProps={{ | |||
textField: { | |||
error: !!error, | |||
helperText: error ? error.message : null | |||
} | |||
}} | |||
/> | |||
)} | |||
/> | |||
</Box> | |||
</LocalizationProvider> | |||
</Grid> | |||
</Grid> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
name="m18ImportDo" | |||
id="m18ImportDo" | |||
variant="contained" | |||
startIcon={<Check />} | |||
type="submit" | |||
> | |||
{t("Import Do")} | |||
</Button> | |||
</Stack> | |||
</CardContent> | |||
</Card> | |||
) | |||
} | |||
export default M18ImportDo; |
@@ -1,6 +1,6 @@ | |||
"use client" | |||
import { M18ImportTestingForm, testM18ImportPo, M18ImportPoForm, testM18ImportPq, testM18ImportMasterData } from "@/app/api/settings/m18ImportTesting/actions"; | |||
import { M18ImportTestingForm, testM18ImportPo, M18ImportPoForm, testM18ImportPq, testM18ImportMasterData, testM18ImportDo } from "@/app/api/settings/m18ImportTesting/actions"; | |||
import { Card, CardContent, Grid, Stack, Typography } from "@mui/material"; | |||
import React, { BaseSyntheticEvent, FormEvent, useCallback, useState } from "react"; | |||
import { FormProvider, SubmitErrorHandler, useForm } from "react-hook-form"; | |||
@@ -9,6 +9,7 @@ import M18ImportPo from "./M18ImportPo"; | |||
import M18ImportPq from "./M18ImportPq"; | |||
import { dateTimeStringToDayjs } from "@/app/utils/formatUtil"; | |||
import M18ImportMasterData from "./M18ImportMasterData"; | |||
import M18ImportDo from "./M18ImportDo"; | |||
interface Props { | |||
@@ -20,6 +21,7 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
const { t } = useTranslation() | |||
const [isLoading, setIsLoading] = useState(false) | |||
const [loadingType, setLoadingType] = useState<String | null>(null) | |||
const formProps = useForm<M18ImportTestingForm>() | |||
const onSubmit = useCallback(async (data: M18ImportTestingForm, event?: BaseSyntheticEvent) => { | |||
@@ -37,11 +39,11 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
case "m18ImportMasterData": | |||
const mdDateFrom = data.masterData.modifiedDateFrom | |||
const mdDateTo = data.masterData.modifiedDateTo | |||
if (!(!!mdDateFrom && dateTimeStringToDayjs(mdDateFrom).isValid())) { | |||
if (!(!mdDateFrom || dateTimeStringToDayjs(mdDateFrom).isValid())) { | |||
formProps.setError("masterData.modifiedDateFrom", { message: "Invalid DateTime Format" }) | |||
} | |||
if (!(!!mdDateTo && dateTimeStringToDayjs(mdDateTo).isValid())) { | |||
if (!(!mdDateTo || dateTimeStringToDayjs(mdDateTo).isValid())) { | |||
formProps.setError("masterData.modifiedDateTo", { message: "Invalid DateTime Format" }) | |||
} | |||
@@ -50,6 +52,7 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
} | |||
setIsLoading(() => true) | |||
setLoadingType(() => "Master Data") | |||
const mdResponse = await testM18ImportMasterData(data.masterData) | |||
console.log(mdResponse) | |||
if (mdResponse) { | |||
@@ -72,20 +75,44 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
} | |||
setIsLoading(() => true) | |||
const poResponse = await testM18ImportMasterData(data.po) | |||
setLoadingType(() => "Purchase Order") | |||
const poResponse = await testM18ImportPo(data.po) | |||
console.log(poResponse) | |||
if (poResponse) { | |||
setIsLoading(() => false) | |||
} | |||
break; | |||
case "m18ImportDo": | |||
const doDateFrom = data.do.modifiedDateFrom | |||
const doDateTo = data.do.modifiedDateTo | |||
if (!(doDateFrom && dateTimeStringToDayjs(doDateFrom).isValid())) { | |||
formProps.setError("do.modifiedDateFrom", { message: "Invalid DateTime Format" }) | |||
} | |||
if (!(doDateTo && dateTimeStringToDayjs(doDateTo).isValid())) { | |||
formProps.setError("do.modifiedDateTo", { message: "Invalid DateTime Format" }) | |||
} | |||
if (formProps.formState.errors.do) { | |||
return; | |||
} | |||
setIsLoading(() => true) | |||
setLoadingType(() => "Delivery Order") | |||
const doResponse = await testM18ImportDo(data.po) | |||
console.log(doResponse) | |||
if (doResponse) { | |||
setIsLoading(() => false) | |||
} | |||
break; | |||
case "m18ImportPq": | |||
const pqDateFrom = data.pq.modifiedDateFrom | |||
const pqDateTo = data.pq.modifiedDateTo | |||
if (!(!!pqDateFrom && dateTimeStringToDayjs(pqDateFrom).isValid())) { | |||
if (!(pqDateFrom || dateTimeStringToDayjs(pqDateFrom).isValid())) { | |||
formProps.setError("pq.modifiedDateFrom", { message: "Invalid DateTime Format" }) | |||
} | |||
if (!(!!pqDateTo && dateTimeStringToDayjs(pqDateTo).isValid())) { | |||
if (!(pqDateTo || dateTimeStringToDayjs(pqDateTo).isValid())) { | |||
formProps.setError("pq.modifiedDateTo", { message: "Invalid DateTime Format" }) | |||
} | |||
@@ -94,6 +121,7 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
} | |||
setIsLoading(() => true) | |||
setLoadingType(() => "Purchase Quotation") | |||
const pqResponse = await testM18ImportPq(data.pq) | |||
console.log(pqResponse) | |||
if (pqResponse) { | |||
@@ -115,7 +143,7 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
return ( | |||
<Card> | |||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||
<Typography variant="overline">{t("Status: ")}{isLoading ? t("Importing...") : t("Ready to import")}</Typography> | |||
<Typography variant="overline">{t("Status: ")}{isLoading ? t(`Importing ${loadingType}...`) : t("Ready to import")}</Typography> | |||
<FormProvider {...formProps}> | |||
<Stack | |||
spacing={2} | |||
@@ -129,6 +157,9 @@ const M18ImportTesting: React.FC<Props> = ({ | |||
<Grid item xs={12}> | |||
<M18ImportPo /> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<M18ImportDo /> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<M18ImportPq /> | |||
</Grid> | |||
@@ -382,7 +382,7 @@ function PoInputGrid({ | |||
headerName: t("acceptedQty"), | |||
flex: 0.5, | |||
type: "number", | |||
editable: true, | |||
// editable: true, | |||
// replace with tooltip + content | |||
}, | |||
{ | |||
@@ -0,0 +1,3 @@ | |||
{ | |||
} |
@@ -3,5 +3,8 @@ | |||
"All": "全部", | |||
"No options": "沒有選項", | |||
"Reset": "重置", | |||
"Search": "搜尋" | |||
"Search": "搜尋", | |||
"Code": "編號", | |||
"Name": "名稱", | |||
"Type": "類型" | |||
} |
@@ -0,0 +1,11 @@ | |||
{ | |||
"Inventory": "存貨", | |||
"Code": "編號", | |||
"Name": "名稱", | |||
"Type": "類型", | |||
"Status": "狀態", | |||
"Qty": "數量", | |||
"UoM": "單位", | |||
"mat": "物料", | |||
"fg": "成品" | |||
} |