diff --git a/src/app/api/salarys/actions.ts b/src/app/api/salarys/actions.ts index 7cd01d3..0bb1482 100644 --- a/src/app/api/salarys/actions.ts +++ b/src/app/api/salarys/actions.ts @@ -1,8 +1,9 @@ "use server" -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { serverFetchBlob, serverFetchJson, serverFetchString } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; +import { FileResponse } from "../reports/actions"; export interface comboProp { id: any; @@ -17,4 +18,31 @@ export const fetchSalaryCombo = cache(async () => { return serverFetchJson(`${BASE_API_URL}/salarys/combo`, { next: { tags: ["salary"] }, }); - }); \ No newline at end of file + }); + +export const importSalarys = async (data: FormData) => { + console.log("----------------",data) + const importSalarys = await serverFetchString( + `${BASE_API_URL}/salarys/import`, + { + method: "POST", + body: data, + // headers: { "Content-Type": "multipart/form-data" }, + }, + ); + + return importSalarys; +}; + +export const exportSalary = async () => { + const reportBlob = await serverFetchBlob( + `${BASE_API_URL}/salarys/export`, + { + method: "POST", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + + return reportBlob +}; \ No newline at end of file diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index a519164..c1f310b 100644 --- a/src/app/utils/fetchUtil.ts +++ b/src/app/utils/fetchUtil.ts @@ -27,7 +27,7 @@ export const serverFetch: typeof fetch = async (input, init) => { ? { Authorization: `Bearer ${accessToken}`, Accept: - "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, multipart/form-data", } : {}), }, @@ -71,6 +71,25 @@ export async function serverFetchWithNoContent(...args: FetchParams) { } } +export async function serverFetchString(...args: FetchParams) { + const response = await serverFetch(...args); + + if (response.ok) { + return response.text() as T; + } else { + switch (response.status) { + case 401: + signOutUser(); + default: + console.error(await response.text()); + throw new ServerFetchError( + "Something went wrong fetching data in server.", + response, + ); + } + } +} + export async function serverFetchBlob(...args: FetchParams) { const response = await serverFetch(...args); diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index 72e8b35..65809dc 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -30,6 +30,7 @@ const pathToLabelMap: { [path: string]: string } = { "/settings/position/new": "Create Position", "/settings/salarys": "Salary", "/analytics/ProjectCashFlowReport": "Project Cash Flow Report", + "/settings/holiday": "Holiday", }; const Breadcrumb = () => { diff --git a/src/components/CompanyHoliday/CompanyHoliday.tsx b/src/components/CompanyHoliday/CompanyHoliday.tsx index 291a2df..521b172 100644 --- a/src/components/CompanyHoliday/CompanyHoliday.tsx +++ b/src/components/CompanyHoliday/CompanyHoliday.tsx @@ -200,8 +200,9 @@ const CompanyHoliday: React.FC = ({ holidays }) => { end: "dayGridMonth listMonth" }} buttonText={{ - month: t("Month view"), - list: t("List View") + month: t("Calender View"), + list: t("List View"), + today: t("Today") }} /> = ({ open, onClo diff --git a/src/components/DepartmentSearch/DepartmentSearch.tsx b/src/components/DepartmentSearch/DepartmentSearch.tsx index b881bb4..bbbd0a7 100644 --- a/src/components/DepartmentSearch/DepartmentSearch.tsx +++ b/src/components/DepartmentSearch/DepartmentSearch.tsx @@ -70,7 +70,7 @@ const DepartmentSearch: React.FC = ({ departments }) => { onClick: onDeleteClick, buttonIcon: , color: "error" - }, + }, ], [t, onProjectClick], ); diff --git a/src/components/SalarySearch/SalarySearch.tsx b/src/components/SalarySearch/SalarySearch.tsx index e7469fd..b21176f 100644 --- a/src/components/SalarySearch/SalarySearch.tsx +++ b/src/components/SalarySearch/SalarySearch.tsx @@ -7,6 +7,11 @@ import SearchResults, { Column } from "../SearchResults"; import EditNote from "@mui/icons-material/EditNote"; import { SalaryResult } from "@/app/api/salarys"; import { convertLocaleStringToNumber } from "@/app/utils/formatUtil" +import { Button, ButtonGroup, Stack } from "@mui/material"; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import { exportSalary, importSalarys } from "@/app/api/salarys/actions"; +import { downloadFile } from "@/app/utils/commonUtil"; interface Props { salarys: SalaryResult[]; @@ -32,8 +37,47 @@ const SalarySearch: React.FC = ({ salarys }) => { setFilteredSalarys(salarys); }, [salarys]); - const onSalaryClick = useCallback((project: SalaryResult) => { - console.log(project); + const onSalaryClick = useCallback((salary: SalaryResult) => { + console.log(salary); + }, []); + + const handleImportClick = useCallback(async (event:any) => { + // console.log(event) + try { + + const file = event.target.files[0]; + + if (!file) { + console.log('No file selected'); + return; + } + + if (file.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') { + console.log('Invalid file format. Only XLSX files are allowed.'); + return; + } + + const formData = new FormData(); + formData.append('multipartFileList', file); + + const response = await importSalarys(formData); + + if (response === "OK") { + window.location.reload() + } + + } catch (err) { + console.log(err) + return false + } + }, []); + + const handleExportClick = useCallback(async (event:any) => { + // console.log(event); + const response = await exportSalary() + if (response) { + downloadFile(new Uint8Array(response.blobValue), response.filename!!) + } }, []); const columns = useMemo[]>( @@ -54,6 +98,28 @@ const SalarySearch: React.FC = ({ salarys }) => { return ( <> + + + + + + { diff --git a/src/components/SalarySearch/SalarySearchWrapper.tsx b/src/components/SalarySearch/SalarySearchWrapper.tsx index 3c910f9..83fe593 100644 --- a/src/components/SalarySearch/SalarySearchWrapper.tsx +++ b/src/components/SalarySearch/SalarySearchWrapper.tsx @@ -8,21 +8,21 @@ interface SubComponents { Loading: typeof SalarySearchLoading; } -function calculateHourlyRate(loweLimit: number, upperLimit: number, numOfWorkingDay: number, workingHour: number){ - const hourlyRate = (loweLimit + upperLimit)/2/numOfWorkingDay/workingHour - return hourlyRate.toLocaleString() -} +// function calculateHourlyRate(loweLimit: number, upperLimit: number, numOfWorkingDay: number, workingHour: number){ +// const hourlyRate = (loweLimit + upperLimit)/2/numOfWorkingDay/workingHour +// return hourlyRate.toLocaleString() +// } const SalarySearchWrapper: React.FC & SubComponents = async () => { const Salarys = await fetchSalarys(); // const Salarys:any[] = [] const salarysWithHourlyRate = Salarys.map((salary) => { - const hourlyRate = calculateHourlyRate(Number(salary.lowerLimit), Number(salary.upperLimit),22, 8) + // const hourlyRate = calculateHourlyRate(Number(salary.lowerLimit), Number(salary.upperLimit),22, 8) return { ...salary, upperLimit: salary.upperLimit.toLocaleString(), lowerLimit: salary.lowerLimit.toLocaleString(), - hourlyRate: hourlyRate + hourlyRate: salary.hourlyRate.toLocaleString(), } }) // console.log(salarysWithHourlyRate)