@@ -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> | ||||