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