| @@ -0,0 +1,34 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import { Suspense } from "react"; | |||||
| import ExcelFileImport from "@/components/ExcelFileImport"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Excel File Import", | |||||
| }; | |||||
| const Import: React.FC = async () => { | |||||
| const { t } = await getServerI18n("holiday"); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Excel File Import")} | |||||
| </Typography> | |||||
| </Stack> | |||||
| <Suspense> | |||||
| <ExcelFileImport /> | |||||
| </Suspense> | |||||
| </> | |||||
| ) | |||||
| }; | |||||
| export default Import; | |||||
| @@ -2,6 +2,7 @@ | |||||
| import { | import { | ||||
| serverFetchJson, | serverFetchJson, | ||||
| serverFetchString, | |||||
| serverFetchWithNoContent, | serverFetchWithNoContent, | ||||
| } from "@/app/utils/fetchUtil"; | } from "@/app/utils/fetchUtil"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| @@ -83,6 +84,7 @@ export interface CreateProjectResponse { | |||||
| team: string; | team: string; | ||||
| client: string; | client: string; | ||||
| } | } | ||||
| export const saveProject = async (data: CreateProjectInputs) => { | export const saveProject = async (data: CreateProjectInputs) => { | ||||
| const newProject = await serverFetchJson<CreateProjectResponse>( | const newProject = await serverFetchJson<CreateProjectResponse>( | ||||
| `${BASE_API_URL}/projects/new`, | `${BASE_API_URL}/projects/new`, | ||||
| @@ -110,3 +112,15 @@ export const deleteProject = async (id: number) => { | |||||
| revalidatePath("/(main)/home"); | revalidatePath("/(main)/home"); | ||||
| return project; | return project; | ||||
| }; | }; | ||||
| export const importProjects = async (data: FormData) => { | |||||
| const importProjects = await serverFetchString<String>( | |||||
| `${BASE_API_URL}/projects/import`, | |||||
| { | |||||
| method: "POST", | |||||
| body: data, | |||||
| }, | |||||
| ); | |||||
| return importProjects; | |||||
| }; | |||||
| @@ -16,12 +16,13 @@ export interface AppBarProps { | |||||
| const AppBar: React.FC<AppBarProps> = async ({ avatarImageSrc, profileName }) => { | const AppBar: React.FC<AppBarProps> = async ({ avatarImageSrc, profileName }) => { | ||||
| const session = await getServerSession(authOptions) as any; | const session = await getServerSession(authOptions) as any; | ||||
| const abilities: string[] = session.abilities | const abilities: string[] = session.abilities | ||||
| const username: string = session.user.name | |||||
| // console.log(abilities) | // console.log(abilities) | ||||
| return ( | return ( | ||||
| <I18nProvider namespaces={["common"]}> | <I18nProvider namespaces={["common"]}> | ||||
| <MUIAppBar position="sticky" color="default" elevation={4}> | <MUIAppBar position="sticky" color="default" elevation={4}> | ||||
| <Toolbar> | <Toolbar> | ||||
| <NavigationToggle abilities={abilities}/> | |||||
| <NavigationToggle abilities={abilities} username={username}/> | |||||
| <Box | <Box | ||||
| sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | ||||
| > | > | ||||
| @@ -8,14 +8,16 @@ import { Session } from "inspector"; | |||||
| import { authOptions } from "@/config/authConfig"; | import { authOptions } from "@/config/authConfig"; | ||||
| import { getServerSession } from "next-auth"; | import { getServerSession } from "next-auth"; | ||||
| export interface SessionWithAbilities extends Session { | export interface SessionWithAbilities extends Session { | ||||
| abilities?: string[] | |||||
| abilities?: string[], | |||||
| username?: string | |||||
| } | } | ||||
| interface Props { | interface Props { | ||||
| abilities?: string[] | |||||
| abilities?: string[], | |||||
| username?: string | |||||
| } | } | ||||
| const NavigationToggle: React.FC<Props> = ({ abilities }) => { | |||||
| const NavigationToggle: React.FC<Props> = ({ abilities, username }) => { | |||||
| const [isOpened, setIsOpened] = React.useState(false); | const [isOpened, setIsOpened] = React.useState(false); | ||||
| const openNavigation = () => { | const openNavigation = () => { | ||||
| @@ -28,7 +30,7 @@ const NavigationToggle: React.FC<Props> = ({ abilities }) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Drawer variant="permanent" sx={{ display: { xs: "none", xl: "block" } }}> | <Drawer variant="permanent" sx={{ display: { xs: "none", xl: "block" } }}> | ||||
| <NavigationContent abilities={abilities}/> | |||||
| <NavigationContent abilities={abilities} username={username}/> | |||||
| </Drawer> | </Drawer> | ||||
| <Drawer | <Drawer | ||||
| sx={{ display: { xl: "none" } }} | sx={{ display: { xl: "none" } }} | ||||
| @@ -0,0 +1,75 @@ | |||||
| "use client"; | |||||
| import { importProjects } from "@/app/api/projects/actions"; | |||||
| import { FileUpload } from "@mui/icons-material"; | |||||
| import { Button, Stack } from "@mui/material"; | |||||
| import React, { ChangeEvent, useCallback } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { errorDialogWithContent, successDialog } from "../Swal/CustomAlerts"; | |||||
| interface Props { | |||||
| } | |||||
| const ExcelFileImport: React.FC<Props> = async ({ }) => { | |||||
| const { t } = useTranslation("projects"); | |||||
| const handleProjectImportClick = useCallback(async (event: ChangeEvent<HTMLInputElement>) => { | |||||
| try { | |||||
| console.log(event.target.files) | |||||
| if (event.target.files !== null) { | |||||
| const file = event.target.files[0] | |||||
| const formData = new FormData(); | |||||
| formData.append('multipartFileList', file); | |||||
| if (file.name.endsWith(".xlsx") || file.name.endsWith(".csv")) { | |||||
| const response = await importProjects(formData) | |||||
| if (response === "Import Excel success") { | |||||
| successDialog(t("Import Success"), t) | |||||
| } else { | |||||
| errorDialogWithContent(t("Import Fail"), t(`${response}`), t) | |||||
| } | |||||
| } | |||||
| } | |||||
| } catch (err) { | |||||
| console.log(err) | |||||
| return false | |||||
| } | |||||
| return | |||||
| }, []) | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Button | |||||
| variant="contained" | |||||
| color="info" | |||||
| startIcon={<FileUpload />} | |||||
| component="label" | |||||
| > | |||||
| <input | |||||
| id='importExcel' | |||||
| type='file' | |||||
| accept='.xlsx, .csv' | |||||
| hidden | |||||
| onChange={(event) => { | |||||
| handleProjectImportClick(event) | |||||
| }} | |||||
| /> | |||||
| {t("Import Project")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ExcelFileImport; | |||||
| @@ -0,0 +1,12 @@ | |||||
| import React from "react"; | |||||
| import { fetchProjects } from "@/app/api/projects"; | |||||
| import ExcelFileImport from "./ExcelFileImport"; | |||||
| import { getUserStaff } from "@/app/utils/commonUtil"; | |||||
| const ExcelFileImportWrapper: React.FC = async () => { | |||||
| return <ExcelFileImport/>; | |||||
| }; | |||||
| export default ExcelFileImportWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from './ExcelFileImportWrapper' | |||||
| @@ -34,6 +34,7 @@ import BusinessIcon from "@mui/icons-material/Business"; | |||||
| import ViewWeekIcon from "@mui/icons-material/ViewWeek"; | import ViewWeekIcon from "@mui/icons-material/ViewWeek"; | ||||
| import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; | import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; | ||||
| import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; | import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; | ||||
| import FileUploadIcon from '@mui/icons-material/FileUpload'; | |||||
| import { | import { | ||||
| GENERATE_REPORTS, | GENERATE_REPORTS, | ||||
| IMPORT_INVOICE, | IMPORT_INVOICE, | ||||
| @@ -63,9 +64,10 @@ interface NavigationItem { | |||||
| interface Props { | interface Props { | ||||
| abilities?: string[]; | abilities?: string[]; | ||||
| username?: string; | |||||
| } | } | ||||
| const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
| const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||||
| const navigationItems: NavigationItem[] = [ | const navigationItems: NavigationItem[] = [ | ||||
| { | { | ||||
| icon: <WorkHistory />, | icon: <WorkHistory />, | ||||
| @@ -242,7 +244,8 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
| label: "User Group", | label: "User Group", | ||||
| path: "/settings/group", | path: "/settings/group", | ||||
| }, | }, | ||||
| { icon: <Holiday />, label: "Holiday", path: "/settings/holiday" }, | |||||
| { icon: <Holiday />, label: "Holiday", path: "/settings/holiday"}, | |||||
| { icon: <FileUploadIcon />, label: "Import Excel File", path: "/settings/import", isHidden: username !== "2fi"}, | |||||
| ], | ], | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -279,7 +282,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
| </ListItemButton> | </ListItemButton> | ||||
| {item.children && isOpen && ( | {item.children && isOpen && ( | ||||
| <List sx={{ pl: 2 }}> | <List sx={{ pl: 2 }}> | ||||
| {item.children.map((child) => renderNavigationItem(child))} | |||||
| {item.children.map((child) => (!child.isHidden && renderNavigationItem(child)))} | |||||
| </List> | </List> | ||||
| )} | )} | ||||
| </Box> | </Box> | ||||