diff --git a/package.json b/package.json index 923e637..bb714c3 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,24 @@ "@mui/material-nextjs": "^5.15.0", "@mui/x-data-grid": "^6.18.7", "@mui/x-date-pickers": "^6.18.7", - "@tiptap/react": "^2.12.0", + "@tiptap/core": "^2.14.0", + "@tiptap/extension-color": "^2.14.0", + "@tiptap/extension-document": "^2.14.0", + "@tiptap/extension-gapcursor": "^2.14.0", + "@tiptap/extension-highlight": "^2.14.0", + "@tiptap/extension-list-item": "^2.14.0", + "@tiptap/extension-paragraph": "^2.14.0", + "@tiptap/extension-table": "^2.14.0", + "@tiptap/extension-table-cell": "^2.14.0", + "@tiptap/extension-table-header": "^2.14.0", + "@tiptap/extension-table-row": "^2.14.0", + "@tiptap/extension-text": "^2.14.0", + "@tiptap/extension-text-align": "^2.14.0", + "@tiptap/extension-text-style": "^2.14.0", + "@tiptap/extension-underline": "^2.14.0", + "@tiptap/pm": "^2.14.0", + "@tiptap/react": "^2.14.0", + "@tiptap/starter-kit": "^2.14.0", "@unly/universal-language-detector": "^2.0.3", "apexcharts": "^3.45.2", "axios": "^1.9.0", @@ -28,6 +45,7 @@ "i18next": "^23.7.11", "i18next-resources-to-backend": "^1.2.0", "lodash": "^4.17.21", + "mui-color-input": "^7.0.0", "next": "14.0.4", "next-auth": "^4.24.5", "next-pwa": "^5.6.0", diff --git a/src/app/(main)/settings/importTesting/page.tsx b/src/app/(main)/settings/importTesting/page.tsx new file mode 100644 index 0000000..ca07e8b --- /dev/null +++ b/src/app/(main)/settings/importTesting/page.tsx @@ -0,0 +1,30 @@ +import ImportTesting from "@/components/ImportTesting"; +import { getServerI18n } from "@/i18n"; +import { Stack } from "@mui/material"; +import { Metadata } from "next"; +import React, { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Import Testing" +} + +const ImportTestingPage: React.FC = async () => { + const { t } = await getServerI18n("importTesting"); + + return ( + <> + + + }> + + + + ) +} + +export default ImportTestingPage; \ No newline at end of file diff --git a/src/app/api/settings/importTesting/actions.ts b/src/app/api/settings/importTesting/actions.ts new file mode 100644 index 0000000..20db792 --- /dev/null +++ b/src/app/api/settings/importTesting/actions.ts @@ -0,0 +1,21 @@ +"use server"; + +import { serverFetchWithNoContent } from '@/app/utils/fetchUtil'; +import { BASE_API_URL } from "@/config/api"; + +export interface ImportPoForm { + dateFrom: string, + dateTo: string, +} + +export interface ImportTestingForm { + po: ImportPoForm +} + +export const testImportPo = async (data: ImportPoForm) => { + return serverFetchWithNoContent(`${BASE_API_URL}/m18/po`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }) +} \ No newline at end of file diff --git a/src/app/api/settings/importTesting/index.ts b/src/app/api/settings/importTesting/index.ts new file mode 100644 index 0000000..f77bda3 --- /dev/null +++ b/src/app/api/settings/importTesting/index.ts @@ -0,0 +1 @@ +// "server only" \ No newline at end of file diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index a882bbc..206f0e3 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -49,6 +49,11 @@ export const dateStringToDayjs = (date: string) => { return dayjs(date, OUTPUT_DATE_FORMAT) } +export const dateTimeStringToDayjs = (dateTime: string) => { + // Format: YYYY/MM/DD HH:mm:ss + return dayjs(dateTime, `${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`) +} + export const stockInLineStatusMap: { [status: string]: number } = { "draft": 0, "pending": 1, diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index 65f399c..863c5e2 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -19,6 +19,7 @@ const pathToLabelMap: { [path: string]: string } = { "/scheduling/detail": "Detail Scheduling", "/scheduling/detail/edit": "FG Production Schedule", "/inventory": "Inventory", + "/settings/importTesting": "Import Testing", }; const Breadcrumb = () => { diff --git a/src/components/ImportTesting/ImportPo.tsx b/src/components/ImportTesting/ImportPo.tsx new file mode 100644 index 0000000..052676f --- /dev/null +++ b/src/components/ImportTesting/ImportPo.tsx @@ -0,0 +1,127 @@ +"use client" +import { ImportTestingForm } from "@/app/api/settings/importTesting/actions"; +import { ImportPoForm } from "@/app/api/settings/importTesting/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 ImportPo: React.FC = ({ +}) => { + + const { t } = useTranslation() + const [isLoading, setIsLoading] = useState(false) + const { + control, + formState: { errors }, + watch + } = useFormContext() + + 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 ( + + + + + {t("Import PO")} + + + + + + value && dateTimeStringToDayjs(value).isValid() ? true : "Invalid date-time" + }, + }} + render={({ field, fieldState: { error } }) => ( + (handleDateTimePickerOnChange(newValue, field.onChange))} + slotProps={{ + textField: { + error: !!error, + helperText: error ? error.message : null + } + }} + /> + )} + /> + + {"-"} + + + value && dateTimeStringToDayjs(value).isValid() ? true : "Invalid date-time", + isFuture: (value) => + dateTimeStringToDayjs(value).isAfter(watch("po.dateFrom")) || "Date must be in the future", + }, + }} + render={({ field, fieldState: { error } }) => ( + (handleDateTimePickerOnChange(newValue, field.onChange))} + slotProps={{ + textField: { + error: !!error, + helperText: error ? error.message : null + } + }} + /> + )} + /> + + + + + + + + + + ) +} + +export default ImportPo; \ No newline at end of file diff --git a/src/components/ImportTesting/ImportTesting.tsx b/src/components/ImportTesting/ImportTesting.tsx new file mode 100644 index 0000000..f9b7ad1 --- /dev/null +++ b/src/components/ImportTesting/ImportTesting.tsx @@ -0,0 +1,67 @@ +"use client" + +import { ImportTestingForm, testImportPo } from "@/app/api/settings/importTesting/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"; +import { useTranslation } from "react-i18next"; +import ImportPo from "./ImportPo"; +import { ImportPoForm } from "@/app/api/settings/importTesting/actions"; + +interface Props { + +} + +const ImportTesting: React.FC = ({ + +}) => { + + const { t } = useTranslation() + const [isLoading, setIsLoading] = useState(false) + const formProps = useForm() + + const onSubmit = useCallback(async (data: ImportTestingForm, event?: BaseSyntheticEvent) => { + const buttonId = (event?.nativeEvent as SubmitEvent).submitter?.id + console.log(data.po) + switch (buttonId) { + case "importPo": + setIsLoading(() => true) + const response = await testImportPo(data.po) + console.log(response) + if (response) { + setIsLoading(() => false) + } + break; + default: + break; + } + }, []) + + const onSubmitError = useCallback>( + (errors) => { + console.log(errors) + }, + [], + ); + + return ( + + + {t("Status: ")}{isLoading ? t("Importing...") : t("Ready to import")} + + + + + + + + + + ) +} + +export default ImportTesting; \ No newline at end of file diff --git a/src/components/ImportTesting/ImportTestingWrapper.tsx b/src/components/ImportTesting/ImportTestingWrapper.tsx new file mode 100644 index 0000000..843f4f1 --- /dev/null +++ b/src/components/ImportTesting/ImportTestingWrapper.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import GeneralLoading from "../General/GeneralLoading" +import ImportTesting from "./ImportTesting"; + +interface SubComponents { + Loading: typeof GeneralLoading; +} + +const ImportTestingWrapper: React.FC & SubComponents = async () => { + + return +} + + +ImportTestingWrapper.Loading = GeneralLoading; + +export default ImportTestingWrapper \ No newline at end of file diff --git a/src/components/ImportTesting/index.ts b/src/components/ImportTesting/index.ts new file mode 100644 index 0000000..de60105 --- /dev/null +++ b/src/components/ImportTesting/index.ts @@ -0,0 +1 @@ +export { default } from './ImportTestingWrapper' \ No newline at end of file diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 5de05b0..ca99d64 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -263,6 +263,11 @@ const NavigationContent: React.FC = () => { label: "Mail", path: "/settings/mail", }, + { + icon: , + label: "Import Testing", + path: "/settings/importTesting", + }, ], }, ];