Export Salary Temaplet, Todo: template with data in DBtags/Baseline_30082024_FRONTEND_UAT
| @@ -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<combo>(`${BASE_API_URL}/salarys/combo`, { | |||
| 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}`, | |||
| 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) { | |||
| const response = await serverFetch(...args); | |||
| @@ -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 = () => { | |||
| @@ -200,8 +200,9 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||
| end: "dayGridMonth listMonth" | |||
| }} | |||
| buttonText={{ | |||
| month: t("Month view"), | |||
| list: t("List View") | |||
| month: t("Calender View"), | |||
| list: t("List View"), | |||
| today: t("Today") | |||
| }} | |||
| /> | |||
| <CompanyHolidayDialog | |||
| @@ -50,10 +50,10 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo | |||
| <Grid item xs={12}> | |||
| <TextField | |||
| disabled={!editable} | |||
| label={t("title")} | |||
| label={t("Description")} | |||
| fullWidth | |||
| {...register("name", { | |||
| required: "title required!", | |||
| required: "Description required!", | |||
| })} | |||
| error={Boolean(errors.name)} | |||
| /> | |||
| @@ -70,7 +70,7 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| }, | |||
| }, | |||
| ], | |||
| [t, onProjectClick], | |||
| ); | |||
| @@ -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<Props> = ({ 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<Column<SalaryResult>[]>( | |||
| @@ -54,6 +98,28 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||
| 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 | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| @@ -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) | |||