Explorar el Código

Import Salary

Export Salary Temaplet, Todo: template with data in DB
tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi hace 1 año
padre
commit
e1e82fbaf7
Se han modificado 8 ficheros con 131 adiciones y 16 borrados
  1. +30
    -2
      src/app/api/salarys/actions.ts
  2. +20
    -1
      src/app/utils/fetchUtil.ts
  3. +1
    -0
      src/components/Breadcrumb/Breadcrumb.tsx
  4. +3
    -2
      src/components/CompanyHoliday/CompanyHoliday.tsx
  5. +2
    -2
      src/components/CompanyHoliday/CompanyHolidayDialog.tsx
  6. +1
    -1
      src/components/DepartmentSearch/DepartmentSearch.tsx
  7. +68
    -2
      src/components/SalarySearch/SalarySearch.tsx
  8. +6
    -6
      src/components/SalarySearch/SalarySearchWrapper.tsx

+ 30
- 2
src/app/api/salarys/actions.ts Ver fichero

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

+ 20
- 1
src/app/utils/fetchUtil.ts Ver fichero

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



+ 1
- 0
src/components/Breadcrumb/Breadcrumb.tsx Ver fichero

@@ -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 = () => {


+ 3
- 2
src/components/CompanyHoliday/CompanyHoliday.tsx Ver fichero

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


+ 2
- 2
src/components/CompanyHoliday/CompanyHolidayDialog.tsx Ver fichero

@@ -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)}
/>


+ 1
- 1
src/components/DepartmentSearch/DepartmentSearch.tsx Ver fichero

@@ -70,7 +70,7 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => {
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
color: "error"
},
},
],
[t, onProjectClick],
);


+ 68
- 2
src/components/SalarySearch/SalarySearch.tsx Ver fichero

@@ -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) => {


+ 6
- 6
src/components/SalarySearch/SalarySearchWrapper.tsx Ver fichero

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


Cargando…
Cancelar
Guardar