Export Salary Temaplet, Todo: template with data in DBtags/Baseline_30082024_FRONTEND_UAT
| @@ -1,8 +1,9 @@ | |||||
| "use server" | "use server" | ||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { serverFetchBlob, serverFetchJson, serverFetchString } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| import { FileResponse } from "../reports/actions"; | |||||
| export interface comboProp { | export interface comboProp { | ||||
| id: any; | id: any; | ||||
| @@ -17,4 +18,31 @@ export const fetchSalaryCombo = cache(async () => { | |||||
| return serverFetchJson<combo>(`${BASE_API_URL}/salarys/combo`, { | return serverFetchJson<combo>(`${BASE_API_URL}/salarys/combo`, { | ||||
| next: { tags: ["salary"] }, | next: { tags: ["salary"] }, | ||||
| }); | }); | ||||
| }); | |||||
| }); | |||||
| export const importSalarys = async (data: FormData) => { | |||||
| console.log("----------------",data) | |||||
| const importSalarys = await serverFetchString<String>( | |||||
| `${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<FileResponse>( | |||||
| `${BASE_API_URL}/salarys/export`, | |||||
| { | |||||
| method: "POST", | |||||
| // body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| return reportBlob | |||||
| }; | |||||
| @@ -27,7 +27,7 @@ export const serverFetch: typeof fetch = async (input, init) => { | |||||
| ? { | ? { | ||||
| Authorization: `Bearer ${accessToken}`, | Authorization: `Bearer ${accessToken}`, | ||||
| Accept: | 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<T>(...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<T>(...args: FetchParams) { | export async function serverFetchBlob<T>(...args: FetchParams) { | ||||
| const response = await serverFetch(...args); | const response = await serverFetch(...args); | ||||
| @@ -30,6 +30,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/settings/position/new": "Create Position", | "/settings/position/new": "Create Position", | ||||
| "/settings/salarys": "Salary", | "/settings/salarys": "Salary", | ||||
| "/analytics/ProjectCashFlowReport": "Project Cash Flow Report", | "/analytics/ProjectCashFlowReport": "Project Cash Flow Report", | ||||
| "/settings/holiday": "Holiday", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| @@ -200,8 +200,9 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||||
| end: "dayGridMonth listMonth" | end: "dayGridMonth listMonth" | ||||
| }} | }} | ||||
| buttonText={{ | buttonText={{ | ||||
| month: t("Month view"), | |||||
| list: t("List View") | |||||
| month: t("Calender View"), | |||||
| list: t("List View"), | |||||
| today: t("Today") | |||||
| }} | }} | ||||
| /> | /> | ||||
| <CompanyHolidayDialog | <CompanyHolidayDialog | ||||
| @@ -50,10 +50,10 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <TextField | <TextField | ||||
| disabled={!editable} | disabled={!editable} | ||||
| label={t("title")} | |||||
| label={t("Description")} | |||||
| fullWidth | fullWidth | ||||
| {...register("name", { | {...register("name", { | ||||
| required: "title required!", | |||||
| required: "Description required!", | |||||
| })} | })} | ||||
| error={Boolean(errors.name)} | error={Boolean(errors.name)} | ||||
| /> | /> | ||||
| @@ -70,7 +70,7 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||||
| onClick: onDeleteClick, | onClick: onDeleteClick, | ||||
| buttonIcon: <DeleteIcon />, | buttonIcon: <DeleteIcon />, | ||||
| color: "error" | color: "error" | ||||
| }, | |||||
| }, | |||||
| ], | ], | ||||
| [t, onProjectClick], | [t, onProjectClick], | ||||
| ); | ); | ||||
| @@ -7,6 +7,11 @@ import SearchResults, { Column } from "../SearchResults"; | |||||
| import EditNote from "@mui/icons-material/EditNote"; | import EditNote from "@mui/icons-material/EditNote"; | ||||
| import { SalaryResult } from "@/app/api/salarys"; | import { SalaryResult } from "@/app/api/salarys"; | ||||
| import { convertLocaleStringToNumber } from "@/app/utils/formatUtil" | 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 { | interface Props { | ||||
| salarys: SalaryResult[]; | salarys: SalaryResult[]; | ||||
| @@ -32,8 +37,47 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||||
| setFilteredSalarys(salarys); | setFilteredSalarys(salarys); | ||||
| }, [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<Column<SalaryResult>[]>( | const columns = useMemo<Column<SalaryResult>[]>( | ||||
| @@ -54,6 +98,28 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="right" | |||||
| flexWrap="wrap" | |||||
| spacing={2} | |||||
| > | |||||
| <ButtonGroup variant="contained"> | |||||
| <Button startIcon={<FileUploadIcon />} variant="contained" component="label"> | |||||
| <input | |||||
| id='importExcel' | |||||
| type='file' | |||||
| accept='.xlsx, .csv' | |||||
| hidden | |||||
| onChange={(event) => {handleImportClick(event)}} | |||||
| /> | |||||
| {t("Import Salary")} | |||||
| </Button> | |||||
| <Button startIcon={<FileDownloadIcon />} onClick={handleExportClick} component="label" variant="contained"> | |||||
| {t("Export Salary")} | |||||
| </Button> | |||||
| </ButtonGroup> | |||||
| </Stack> | |||||
| <SearchBox | <SearchBox | ||||
| criteria={searchCriteria} | criteria={searchCriteria} | ||||
| onSearch={(query) => { | onSearch={(query) => { | ||||
| @@ -8,21 +8,21 @@ interface SubComponents { | |||||
| Loading: typeof SalarySearchLoading; | 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 SalarySearchWrapper: React.FC & SubComponents = async () => { | ||||
| const Salarys = await fetchSalarys(); | const Salarys = await fetchSalarys(); | ||||
| // const Salarys:any[] = [] | // const Salarys:any[] = [] | ||||
| const salarysWithHourlyRate = Salarys.map((salary) => { | 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 { | return { | ||||
| ...salary, | ...salary, | ||||
| upperLimit: salary.upperLimit.toLocaleString(), | upperLimit: salary.upperLimit.toLocaleString(), | ||||
| lowerLimit: salary.lowerLimit.toLocaleString(), | lowerLimit: salary.lowerLimit.toLocaleString(), | ||||
| hourlyRate: hourlyRate | |||||
| hourlyRate: salary.hourlyRate.toLocaleString(), | |||||
| } | } | ||||
| }) | }) | ||||
| // console.log(salarysWithHourlyRate) | // console.log(salarysWithHourlyRate) | ||||