import AppTextInput from '@/components/form-fields/AppTextInput';
import Iconify from '@/components/iconify/Iconify';
import AppButton from '@/components/templates/AppButton';
import AppDataGrid from '@/components/templates/AppDataGrid';
import AppLink from '@/components/templates/AppLink';
import AppPersona from '@/components/templates/AppPersona';
import TableSkeleton from '@/components/templates/AppTable/TableSkeleton';
import AppTag from '@/components/templates/AppTag';
import AppTitle from '@/components/templates/AppTitle';
import {
CACHE_KEY_AVAILABLE_PAYROLL,
CACHE_KEY_GENERATED_PAYROLL,
CACHE_KEY_PAYROLL_CYCLE_PAYRUN,
CACHE_KEY_POSTED_PAYROLL,
CACHE_KEY_PROCESSED_PAYROLL,
PAYROLL_CYCLE_LOCK_ENDPOINT,
PAYROLL_CYCLE_PAYRUN_ENDPOINT,
PAYROLL_CYCLE_SIF_ENDPOINT,
PAYROLL_EMP_BY_STATUS_ENDPOINT,
} from '@/constants/payroll';
import useGetAll from '@/hooks/useGetAll';
import {
Box,
Button,
Divider,
Grid,
IconButton,
Menu,
MenuItem,
Paper,
Stack,
Step,
StepButton,
Stepper,
Typography,
} from '@mui/material';
import {
GridColDef,
GridRenderCellParams,
GridRowSelectionModel,
} from '@mui/x-data-grid';
import * as React from 'react';
import { useLocation, useParams } from 'react-router-dom';
import StatusCardList from './status-card-list';
import ProcessPopup, { PAYROLL_UPDATE_PAYLOAD } from './process-popup';
import usePut from '@/hooks/usePut';
import AppDrawer from '@/components/templates/AppDrawer';
import PayView from './payview';
import { useState } from 'react';
import nav from '@/AppLayout/nav';
import APIClient from '@/services/api-client';
import { SearchOutlined } from '@mui/icons-material';
import { useQueryClient } from '@tanstack/react-query';
import { PAYROLL } from '../payroll-list';
import { toast } from 'react-toastify';
import useUserStore from '@/store/user.store';
export type PAYROLL_PROCESS =
| 'AVAILABLE'
| 'GENERATED'
| 'PROCESSED'
| 'POSTED';
interface STEP {
id: PAYROLL_PROCESS;
label: string;
}
const steps: STEP[] = [
// { id: 'AVAILABLE', label: 'Available Payroll' }, //included this line by Priti
{ id: 'AVAILABLE', label: 'Available Employee' },
{ id: 'GENERATED', label: 'Generated Payroll' },
{ id: 'POSTED', label: 'Posted Payroll' },
{ id: 'PROCESSED', label: 'Processed Payroll' },
];
function Payrun() {
const params = useParams();
const [activeStep, setActiveStep] = React.useState<number>(0);
const [rowSelectionModel, setRowSelectionModel] =
React.useState<GridRowSelectionModel>([]);
const [selectedRows, setSelectedRows] =
React.useState<PAYROLL_UPDATE_PAYLOAD>();
const [open, setOpen] = React.useState(false);
const [deleteOpen, setDeleteOpen] = React.useState(false);
const { data, refetch, error, isLoading, isFetching } =
useGetAll<EMP_PAYROLL_STATUS>(
PAYROLL_EMP_BY_STATUS_ENDPOINT +
'?' +
'payrollCycleId=' +
params.id +
'&status=' +
steps[activeStep].id,
activeStep === 0
? CACHE_KEY_AVAILABLE_PAYROLL
: activeStep === 1
? CACHE_KEY_GENERATED_PAYROLL
: activeStep === 2
? CACHE_KEY_POSTED_PAYROLL
: CACHE_KEY_PROCESSED_PAYROLL
);
React.useEffect(() => {
if (isFetching) {
console.log('Refetching data...');
}
}, [isFetching]);
const handleRowSelection = (rowIds: GridRowSelectionModel) => {
setRowSelectionModel(rowIds);
const rows = data && data.filter((d) => rowIds.includes(d.id));
if (activeStep === 0)
setSelectedRows({
payrollCycleId: params.id!,
employees: rows?.map((r) => r.id),
});
else
setSelectedRows({
payrollCycleId: params.id!,
payrollId: rows?.map((r) => r.payrollId),
});
};
const [menuAnchor, setMenuAnchor] = React.useState<HTMLElement | null>(null);
const handleMenuClick = (event: React.MouseEvent<HTMLElement>) => {
setMenuAnchor(event.currentTarget);
};
const handleMenuClose = () => {
setMenuAnchor(null);
};
const getActionLabel = () => {
switch (steps[activeStep].id) {
case 'AVAILABLE':
return ['Generate Payroll'];
case 'GENERATED':
return ['Post Payroll', 'Delete'];
case 'POSTED':
return ['Process Payroll', 'Delete'];
case 'PROCESSED':
return ['No Actions needed'];
default:
return ['Action'];
}
};
const refetchTableData = () => {
const queryClient = useQueryClient(); // Get the query client instance
queryClient.invalidateQueries(CACHE_KEY_PAYROLL_CYCLE_PAYRUN);
console.log('Table data refetched');
refetch();
};
const user = useUserStore((state) => state.user);
const columns: GridColDef[] = [
{
field: 'firstName',
minWidth: 200,
flex: 1,
headerName: 'Employee Name',
renderCell: (params: GridRenderCellParams) => {
const { value, row }: { value?: string; row: any } = params;
return (
<AppPersona
profile={{
maintext: value + ' ' + row.lastName,
subtext: '#' + row.empCode,
}}
/>
);
},
},
{
field: 'departmentId',
minWidth: 200,
flex: 1,
headerName: 'Department & Division',
renderCell: (params: GridRenderCellParams) => {
const { value, row }: { value?: string; row: any } = params;
return (
<Box>
<Typography
variant='body2'
fontWeight={'fontWeightMedium'}
sx={{ color: 'text.primary' }}
>
{value!}
</Typography>
<Typography
variant='caption'
sx={{ color: 'text.secondary' }}
fontWeight='fontWeightMedium'
>
{row.divisionId !}
</Typography>
</Box>
);
// return <AppTitle title={value!} subtitle={row.division!} />;
},
},
// {
// field: 'salary',
// minWidth: 100,
// width: 150,
// headerName: 'Salary',
// renderCell: (params: GridRenderCellParams) => {
// return (
// <Typography variant='body2' fontWeight='fontWeightMedium'>
// INR. {params.value}
// </Typography>
// );
// },
// },
{
field: 'status',
minWidth: 80,
width: 150,
headerName: 'Status',
renderCell: (params: GridRenderCellParams) => {
const val = params?.value?.toUpperCase() || 'NA'!;
return (
<AppTag
variant={
val === 'GENERATED'
? 'ACTIVE'
: val === 'POSTED'
? 'INACTIVE'
: ''
}
text={val}
/>
);
},
},
{
field: 'action',
flex: 1,
headerAlign: 'center',
align: 'center',
headerName: 'Action',
renderCell: (params: GridRenderCellParams) => {
const { row }: { row: any } = params;
return (
<Stack
sx={{ width: '70px' }}
direction='row'
alignItems='center'
justifyContent='space-between'
>
<AppButton
sx={{
color: 'primary.main',
fontWeight: 600,
textDecoration: 'none',
padding: '4px 10px',
display: 'inline-flex',
alignItems: 'center',
}}
variant='text'
onClick={() => handleOpenDrawer(row?.payrollId)}
>
View
</AppButton>
{/* <Iconify icon='ic:sharp-more-vert' width={22} /> */}
{/* <IconButton onClick={handleMenuClick}>
<Iconify icon='ic:sharp-more-vert' width={18} />
</IconButton>
<Menu
anchorEl={menuAnchor}
open={Boolean(menuAnchor)}
onClose={handleMenuClose}
>
{getActionLabel().map((label, index) => (
<MenuItem
key={index}
onClick={() => handleMenuItemClick(label)}
>
{label}
</MenuItem>
))}
</Menu> */}
</Stack>
);
},
},
];
const [viewTitle, setViewTitle] = useState('');
const [isDrawerOpen, setIsDrawerOpen] = useState<string>(''); // New state to track drawer open/close
const { id } = useParams();
// Function to open the drawer
const handleOpenDrawer = (id: string) => {
setIsDrawerOpen(id);
};
// Function to close the drawer
const handleCloseDrawer = () => {
setIsDrawerOpen('');
};
React.useEffect(() => {
if (id) {
setIsDrawerOpen('');
}
}, [id]);
const downloadSif = () => {
const apiClient = new APIClient(PAYROLL_CYCLE_SIF_ENDPOINT(params.id!));
apiClient.get(true).then((data) => {
console.log(data);
const fileURL = URL.createObjectURL(data);
var link = document.createElement('a');
link.setAttribute('href', fileURL);
const dt = new Date();
const yr = dt.getFullYear().toString().slice(-2);
const mn = (dt.getMonth() + 1).toString().padStart(2, '0');
const dd = dt.getDate().toString().padStart(2, '0');
const hr = dt.getHours().toString().padStart(2, '0');
const min = dt.getMinutes().toString().padStart(2, '0');
const sec = dt.getSeconds().toString().padStart(2, '0');
link.setAttribute(
'download',
`${'0000001145097'}${yr + mn + dd + hr + min + sec}.txt`
);
document.body.appendChild(link); // Required for FF
link.click();
link.remove();
});
};
const lockPayroll = (payrollCycleId: string) => {
const service = new APIClient(PAYROLL_CYCLE_LOCK_ENDPOINT + params.id);
service
.put({ id: payrollCycleId })
.then((data) => {
// Invalidate relevant queries
queryClient.invalidateQueries(CACHE_KEY_PAYROLL_CYCLE_PAYRUN);
toast.success('Payroll locked !');
refetch();
})
.catch((error) => {
// Handle error
console.error('Error making employee inactive:', error);
toast.error('Failed to lock payroll');
});
};
const queryClient = useQueryClient();
// const onEdit = (editData: PAYROLL) => {
// queryClient.setQueryData<PAYROLL>(CACHE_KEY_PAYROLL_CYCLE_PAYRUN, editData);
// };
const location = useLocation();
const { state } = location;
console.log(state, 'state');
return (
<Paper
sx={{
border: '1px solid',
borderColor: 'background.neutral',
backgroundColor: 'background.paper',
borderRadius: 2.5,
m: 4,
overflow: 'auto',
}}
>
<Grid container direction='row' sx={{ height: '100%' }}>
<Grid item xs={12}>
<Stack sx={{ height: '100%' }} divider={<Divider />}>
<Stack
sx={{ px: 2.5, pt: 2.5, pb: 4 }}
direction='row'
justifyContent='space-between'
>
<AppTitle
title={'Payroll Cycle Details '}
subtitle={`${state?.dateFrom} - ${state?.dateTo}`}
/>
<Button
variant='outlined'
onClick={() => {
if (params.id) {
lockPayroll(params.id);
} else {
console.error('PayrollCycleId is undefined');
toast.error(
'Failed to lock payroll: PayrollCycleId is undefined'
);
}
}}
>
<Iconify icon='ic:round-lock' width={18} />
Lock Payroll
</Button>
</Stack>
<Box>
<StatusCardList />
<Stepper sx={{ pt: 2, px: 1 }} nonLinear activeStep={activeStep}>
{steps.map((step, index) => (
<Step key={step.label}>
<StepButton onClick={() => setActiveStep(index)}>
{step.label}
</StepButton>
</Step>
))}
</Stepper>
<Box sx={{ mt: 5 }}>
<Stack
px={2}
direction='row'
justifyContent='space-between'
alignItems='center'
>
<AppTitle title={'List of Payrolls'} />
<Stack direction='row' spacing={2} sx={{ width: '50%' }}>
<AppTextInput
startIcon={<SearchOutlined />}
placeholder='Search by Request Id, Emp no'
/>
<IconButton onClick={() => {}}>
<Iconify icon='mdi:filter-outline' width={18} />
</IconButton>
{activeStep !== 3 && (
<AppButton
onClick={() => {
setOpen(true); // Open the dialog or popup for the action
// Assuming the action is completed successfully and you want to update the table
// Call refetch to fetch the latest data from the API
refetch();
console.log('Refetching data...');
}}
variant='contained'
disabled={!rowSelectionModel.length}
disableRipple
>
{activeStep === 0
? 'Generate '
: activeStep === 1
? 'Post '
: 'Process '}
Payroll
</AppButton>
)}
{activeStep === 3 && (
<Button
variant='text'
onClick={downloadSif}
startIcon={
<Iconify
icon='ic:baseline-cloud-download'
width={16}
/>
}
>
SIF Download
</Button>
)}
{activeStep === 1 && (
<Button
variant='text'
disabled={!rowSelectionModel.length}
startIcon={<Iconify icon='ic:delete' width={16} />}
onClick={() => {
setDeleteOpen(true);
}}
>
Delete
</Button>
)}
</Stack>
</Stack>
<Box sx={{ mt: 2 }}>
{data && !isLoading ? (
<AppDataGrid
columns={columns}
rows={data}
rowHeight={75}
getRowId={(row: EMP_PAYROLL_STATUS) => row.id}
checkboxSelection={activeStep !== 3}
onRowSelectionModelChange={(
newRowSelectionModel: GridRowSelectionModel
) => {
handleRowSelection(newRowSelectionModel);
}}
rowSelectionModel={rowSelectionModel}
/>
) : (
<TableSkeleton columns={columns} />
)}
</Box>
</Box>
</Box>
</Stack>
</Grid>
</Grid>
{selectedRows && (
<>
<ProcessPopup
data={selectedRows}
process={steps[activeStep].id}
open={open}
setOpen={setOpen}
refetchTableData={refetchTableData}
/>
{activeStep == 1 && (
<ProcessPopup
data={selectedRows}
process={'DELETE'}
open={deleteOpen}
setOpen={setDeleteOpen}
refetchTableData={refetchTableData}
/>
)}
</>
)}
<AppDrawer
header={viewTitle || 'View Leave'}
open={!!isDrawerOpen} // Use the state to control drawer open/close
setOpen={handleCloseDrawer} // Pass the close function to the AppDrawer
>
{isDrawerOpen && <PayView setTitle={setViewTitle} id={isDrawerOpen!} />}
</AppDrawer>
</Paper>
);
}
export default React.memo(Payrun);
interface EMP_PAYROLL_STATUS {
id: string;
payrollId: string;
payrollCycleId: string;
title: string;
firstName: string;
lastName: string;
empCode: string;
departmentId: string;
divisionId: string;
salary: number;
status: PAYROLL_PROCESS;
payMode: string;
}
这里我已经使用“queryClient”进行参考技术,但它不起作用使用 queryclient 刷新所有 api 的解决方案是什么
const refetchTableData = () => {
const queryClient = useQueryClient(); // Get the query client instance
queryClient.invalidateQueries(CACHE_KEY_PAYROLL_CYCLE_PAYRUN);
console.log('Table data refetched');
refetch();
};
使用 queryclient 创建了函数,但它不起作用,请帮助我使用查询客户端刷新所有 api,请更正我的代码和逻辑,提前感谢朋友们, 我的错误是什么,请也提供我在这里犯的错误。
请问,告诉我这个const“CACHE_KEY_PAYROLL_CYCLE_PAYRUN”是什么值?如果它的值等于 {queryKey: ["something_key"]},那么就可以,否则就不好,你应该更正为 {queryKey: ["somethig_key"]} https://tanstack.com/query/v4/docs/framework/react/guides/query-invalidation