| @@ -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 { | |||
| serverFetchJson, | |||
| serverFetchString, | |||
| serverFetchWithNoContent, | |||
| } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| @@ -83,6 +84,7 @@ export interface CreateProjectResponse { | |||
| team: string; | |||
| client: string; | |||
| } | |||
| export const saveProject = async (data: CreateProjectInputs) => { | |||
| const newProject = await serverFetchJson<CreateProjectResponse>( | |||
| `${BASE_API_URL}/projects/new`, | |||
| @@ -110,3 +112,15 @@ export const deleteProject = async (id: number) => { | |||
| revalidatePath("/(main)/home"); | |||
| 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 session = await getServerSession(authOptions) as any; | |||
| const abilities: string[] = session.abilities | |||
| const username: string = session.user.name | |||
| // console.log(abilities) | |||
| return ( | |||
| <I18nProvider namespaces={["common"]}> | |||
| <MUIAppBar position="sticky" color="default" elevation={4}> | |||
| <Toolbar> | |||
| <NavigationToggle abilities={abilities}/> | |||
| <NavigationToggle abilities={abilities} username={username}/> | |||
| <Box | |||
| sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | |||
| > | |||
| @@ -8,14 +8,16 @@ import { Session } from "inspector"; | |||
| import { authOptions } from "@/config/authConfig"; | |||
| import { getServerSession } from "next-auth"; | |||
| export interface SessionWithAbilities extends Session { | |||
| abilities?: string[] | |||
| abilities?: string[], | |||
| username?: string | |||
| } | |||
| 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 openNavigation = () => { | |||
| @@ -28,7 +30,7 @@ const NavigationToggle: React.FC<Props> = ({ abilities }) => { | |||
| return ( | |||
| <> | |||
| <Drawer variant="permanent" sx={{ display: { xs: "none", xl: "block" } }}> | |||
| <NavigationContent abilities={abilities}/> | |||
| <NavigationContent abilities={abilities} username={username}/> | |||
| </Drawer> | |||
| <Drawer | |||
| 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 ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; | |||
| import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; | |||
| import FileUploadIcon from '@mui/icons-material/FileUpload'; | |||
| import { | |||
| GENERATE_REPORTS, | |||
| IMPORT_INVOICE, | |||
| @@ -63,9 +64,10 @@ interface NavigationItem { | |||
| interface Props { | |||
| abilities?: string[]; | |||
| username?: string; | |||
| } | |||
| const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||
| const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||
| const navigationItems: NavigationItem[] = [ | |||
| { | |||
| icon: <WorkHistory />, | |||
| @@ -242,7 +244,8 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||
| label: "User 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> | |||
| {item.children && isOpen && ( | |||
| <List sx={{ pl: 2 }}> | |||
| {item.children.map((child) => renderNavigationItem(child))} | |||
| {item.children.map((child) => (!child.isHidden && renderNavigationItem(child)))} | |||
| </List> | |||
| )} | |||
| </Box> | |||