changes to time off requests
This commit is contained in:
parent
c1f17908e1
commit
40b50cdd6c
@ -1,4 +1,3 @@
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const Time_off_requestsService = require('../services/time_off_requests');
|
||||
@ -303,19 +302,33 @@ router.get('/', wrapAsync(async (req, res) => {
|
||||
req.query, { currentUser }
|
||||
);
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id','reason','manager_note','external_reference',
|
||||
|
||||
'hours','days',
|
||||
'starts_at','ends_at','submitted_at','decided_at',
|
||||
];
|
||||
const fields = [
|
||||
{ label: 'ID', value: 'id' },
|
||||
{ label: 'Requester', value: (row) => row.requester ? `${row.requester.firstName} ${row.requester.lastName} (${row.requester.email})` : '' },
|
||||
{ label: 'Approver', value: (row) => row.approver ? `${row.approver.firstName} ${row.approver.lastName} (${row.approver.email})` : '' },
|
||||
{ label: 'Type', value: 'leave_type' },
|
||||
{ label: 'Request Kind', value: 'request_kind' },
|
||||
{ label: 'Start', value: 'starts_at' },
|
||||
{ label: 'Finish', value: 'ends_at' },
|
||||
{ label: 'Hours', value: 'hours' },
|
||||
{ label: 'Days', value: 'days' },
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'Requires Approval', value: 'requires_approval' },
|
||||
{ label: 'Submitted At', value: 'submitted_at' },
|
||||
{ label: 'Decided At', value: 'decided_at' },
|
||||
{ label: 'Reason', value: 'reason' },
|
||||
{ label: 'Manager Note', value: 'manager_note' },
|
||||
{ label: 'External Reference', value: 'external_reference' }
|
||||
];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.status(200).attachment('time_off_requests.csv');
|
||||
res.send(csv)
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).send('Error generating CSV');
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
@ -441,4 +454,4 @@ router.get('/:id', wrapAsync(async (req, res) => {
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
@ -12,6 +12,9 @@ import {
|
||||
DataGrid,
|
||||
GridColDef,
|
||||
} from '@mui/x-data-grid';
|
||||
import { Drawer, Box, Typography, IconButton } from '@mui/material';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import {loadColumns} from "./configureTime_off_requestsCols";
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter'
|
||||
@ -20,12 +23,20 @@ import moment from 'moment';
|
||||
|
||||
|
||||
import KanbanBoard from '../KanbanBoard/KanbanBoard';
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
const perPage = 10
|
||||
|
||||
const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||
const getUserLabel = (user: any) => {
|
||||
if (!user) return '';
|
||||
if (user.label) return user.label;
|
||||
if (user.firstName || user.lastName) {
|
||||
return `${user.firstName || ''} ${user.lastName || ''}`.trim();
|
||||
}
|
||||
return user.id || '';
|
||||
}
|
||||
|
||||
const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, showGrid, isFilterDrawerOpen, setIsFilterDrawerOpen }) => {
|
||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
@ -131,11 +142,14 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
|
||||
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
|
||||
const [isModalCancelActive, setIsModalCancelActive] = useState(false)
|
||||
const [isModalViewActive, setIsModalViewActive] = useState(false)
|
||||
const [selectedItem, setSelectedItem] = useState<any>(null)
|
||||
|
||||
const handleModalAction = () => {
|
||||
setIsModalInfoActive(false)
|
||||
setIsModalTrashActive(false)
|
||||
setIsModalCancelActive(false)
|
||||
setIsModalViewActive(false)
|
||||
}
|
||||
|
||||
|
||||
@ -202,6 +216,11 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewAction = (row: any) => {
|
||||
setSelectedItem(row);
|
||||
setIsModalViewActive(true);
|
||||
}
|
||||
|
||||
const generateFilterRequests = useMemo(() => {
|
||||
let request = '&';
|
||||
filterItems.forEach((item) => {
|
||||
@ -245,10 +264,10 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
loadData(0, generateFilterRequests);
|
||||
loadData(currentPage, generateFilterRequests);
|
||||
|
||||
setKanbanFilters(generateFilterRequests);
|
||||
|
||||
setIsFilterDrawerOpen(false);
|
||||
};
|
||||
|
||||
const handleChange = (id) => (e) => {
|
||||
@ -270,7 +289,7 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
loadData(0, '');
|
||||
|
||||
setKanbanFilters('');
|
||||
|
||||
setIsFilterDrawerOpen(false);
|
||||
};
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
@ -286,7 +305,8 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
handleDeleteModalAction,
|
||||
`time_off_requests`,
|
||||
currentUser,
|
||||
handleCancelModalAction
|
||||
handleCancelModalAction,
|
||||
handleViewAction
|
||||
).then((newCols) => setColumns(newCols));
|
||||
}, [currentUser]);
|
||||
|
||||
@ -385,175 +405,195 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
</div>
|
||||
)
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: _.uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0]?.title || '';
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
|
||||
<CardBox>
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={isFilterDrawerOpen}
|
||||
onClose={() => setIsFilterDrawerOpen(false)}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: 350, p: 4 }}>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<Typography variant="h6">Filters</Typography>
|
||||
<IconButton onClick={() => setIsFilterDrawerOpen(false)}>
|
||||
<BaseIcon path={mdiClose} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
checkboxes: ['lorem'],
|
||||
switches: ['lorem'],
|
||||
radio: 'lorem',
|
||||
}}
|
||||
initialValues={{}}
|
||||
onSubmit={() => null}
|
||||
>
|
||||
<Form>
|
||||
<>
|
||||
{filterItems && filterItems.map((filterItem) => {
|
||||
const selectedFilter = filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className=" text-gray-500 font-bold">Filter</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
id='selectedField'
|
||||
component='select'
|
||||
value={filterItem?.fields?.selectedField || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
{filters.map((selectOption) => (
|
||||
<option
|
||||
key={selectOption.title}
|
||||
value={`${selectOption.title}`}
|
||||
>
|
||||
{selectOption.label}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
id='filterValue'
|
||||
component="select"
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
) : filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.number ? (
|
||||
<div className="flex flex-row w-full mr-3">
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className=" text-gray-500 font-bold">From</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className=" text-gray-500 font-bold">To</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : filters.find(
|
||||
(filter) =>
|
||||
filter.title ===
|
||||
filterItem?.fields?.selectedField
|
||||
)?.date ? (
|
||||
<div className='flex flex-row w-full mr-3'>
|
||||
<div className='flex flex-col w-full mr-3'>
|
||||
<div className=' text-gray-500 font-bold'>
|
||||
From
|
||||
</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col w-full'>
|
||||
<div className=' text-gray-500 font-bold'>To</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className=" text-gray-500 font-bold">Contains</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<div className=" text-gray-500 font-bold">Action</div>
|
||||
<CardBox key={filterItem.id} className="mb-4" noPadding>
|
||||
<div className="p-4 border-b border-gray-100 dark:border-gray-700 flex justify-between items-center">
|
||||
<span className="font-bold text-gray-500 text-sm">Condition</span>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label='Remove'
|
||||
small
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="mb-2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">Field</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
id='selectedField'
|
||||
component='select'
|
||||
value={filterItem?.fields?.selectedField || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
{filters.map((selectOption) => (
|
||||
<option
|
||||
key={selectOption.title}
|
||||
value={`${selectOption.title}`}
|
||||
>
|
||||
{selectOption.label}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{selectedFilter?.type === 'enum' ? (
|
||||
<div className="mb-2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">Value</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
id='filterValue'
|
||||
component="select"
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
{selectedFilter?.options?.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
) : selectedFilter?.number ? (
|
||||
<div className="flex space-x-2">
|
||||
<div className="w-1/2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">From</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">To</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='To'
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : selectedFilter?.date ? (
|
||||
<div className="flex space-x-2">
|
||||
<div className="w-1/2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">From</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">To</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='To'
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-2">
|
||||
<div className="text-gray-500 text-xs mb-1 font-bold">Contains</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardBox>
|
||||
)
|
||||
})}
|
||||
<div className="flex">
|
||||
<div className="mt-6 flex flex-col space-y-3">
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
color="info"
|
||||
label='Add Condition'
|
||||
outline
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
color="success"
|
||||
label='Apply Filters'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
color='info'
|
||||
label='Clear All'
|
||||
outline
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox> : null
|
||||
}
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
buttonColor="info"
|
||||
@ -576,6 +616,35 @@ const TableSampleTime_off_requests = ({ filterItems, setFilterItems, filters, sh
|
||||
<p>Are you sure you want to cancel this time off request?</p>
|
||||
</CardBoxModal>
|
||||
|
||||
<CardBoxModal
|
||||
title="Request Details"
|
||||
buttonColor="info"
|
||||
buttonLabel="Close"
|
||||
isActive={isModalViewActive}
|
||||
onConfirm={handleModalAction}
|
||||
onCancel={handleModalAction}
|
||||
>
|
||||
{selectedItem && (
|
||||
<div className="space-y-2">
|
||||
<p><strong>Requester:</strong> {getUserLabel(selectedItem.requester)}</p>
|
||||
<p><strong>Approver:</strong> {getUserLabel(selectedItem.approver)}</p>
|
||||
<p><strong>Type:</strong> {selectedItem.leave_type}</p>
|
||||
<p><strong>Kind:</strong> {selectedItem.request_kind}</p>
|
||||
<p><strong>Start:</strong> {moment(selectedItem.starts_at).format('YYYY-MM-DD HH:mm')}</p>
|
||||
<p><strong>Finish:</strong> {moment(selectedItem.ends_at).format('YYYY-MM-DD HH:mm')}</p>
|
||||
<p><strong>Hours:</strong> {selectedItem.hours}</p>
|
||||
<p><strong>Days:</strong> {selectedItem.days}</p>
|
||||
<p><strong>Status:</strong> {selectedItem.status}</p>
|
||||
<p><strong>Requires Approval:</strong> {selectedItem.requires_approval ? 'Yes' : 'No'}</p>
|
||||
<p><strong>Submitted At:</strong> {selectedItem.submitted_at ? moment(selectedItem.submitted_at).format('YYYY-MM-DD HH:mm') : 'N/A'}</p>
|
||||
<p><strong>Decided At:</strong> {selectedItem.decided_at ? moment(selectedItem.decided_at).format('YYYY-MM-DD HH:mm') : 'N/A'}</p>
|
||||
<p><strong>Reason:</strong> {selectedItem.reason}</p>
|
||||
<p><strong>Manager Note:</strong> {selectedItem.manager_note}</p>
|
||||
<p><strong>External Reference:</strong> {selectedItem.external_reference}</p>
|
||||
</div>
|
||||
)}
|
||||
</CardBoxModal>
|
||||
|
||||
|
||||
|
||||
{!showGrid && kanbanColumns && (
|
||||
|
||||
@ -1,29 +1,31 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import {saveFile} from "../../helpers/fileSaver";
|
||||
import dataFormatter from '../../helpers/dataFormatter'
|
||||
import DataGridMultiSelect from "../DataGridMultiSelect";
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
const getUserLabel = (user: any) => {
|
||||
if (!user) return '';
|
||||
if (user.label) return user.label;
|
||||
if (user.firstName || user.lastName) {
|
||||
return `${user.firstName || ''} ${user.lastName || ''}`.trim();
|
||||
}
|
||||
return user.id || '';
|
||||
}
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
|
||||
onCancel?: Params
|
||||
onCancel?: Params,
|
||||
onView?: (row: any) => void
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
|
||||
@ -39,18 +41,26 @@ export const loadColumns = async (
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_TIME_OFF_REQUESTS')
|
||||
const isAdmin = user?.app_role?.name === 'Administrator';
|
||||
|
||||
return [
|
||||
const columns: any[] = [
|
||||
|
||||
{
|
||||
field: 'requester',
|
||||
headerName: 'Requester',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
minWidth: 150,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
renderCell: (params: GridValueGetterParams) => (
|
||||
<span
|
||||
className="cursor-pointer text-blue-600 hover:underline font-medium"
|
||||
onClick={() => onView && onView(params.row)}
|
||||
>
|
||||
{getUserLabel(params?.row?.requester)}
|
||||
</span>
|
||||
),
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -68,11 +78,15 @@ export const loadColumns = async (
|
||||
field: 'approver',
|
||||
headerName: 'Approver',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
minWidth: 150,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
renderCell: (params: GridValueGetterParams) => (
|
||||
<span>
|
||||
{getUserLabel(params?.row?.approver)}
|
||||
</span>
|
||||
),
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -88,7 +102,7 @@ export const loadColumns = async (
|
||||
|
||||
{
|
||||
field: 'leave_type',
|
||||
headerName: 'LeaveType',
|
||||
headerName: 'Type',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
@ -109,7 +123,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: !isAdmin,
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -118,7 +132,7 @@ export const loadColumns = async (
|
||||
|
||||
{
|
||||
field: 'starts_at',
|
||||
headerName: 'StartsAt',
|
||||
headerName: 'Start',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
@ -136,7 +150,7 @@ export const loadColumns = async (
|
||||
|
||||
{
|
||||
field: 'ends_at',
|
||||
headerName: 'EndsAt',
|
||||
headerName: 'Finish',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
@ -160,7 +174,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true,
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -207,7 +221,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true, // Hidden by default
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -223,7 +237,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true, // Hidden by default
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -241,7 +255,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true, // Hidden by default
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -259,7 +273,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true, // Hidden by default
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -274,7 +288,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true, // Hidden by default
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -289,22 +303,9 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true,
|
||||
editable: false,
|
||||
sortable: false,
|
||||
renderCell: (params: GridValueGetterParams) => (
|
||||
<>
|
||||
{dataFormatter.filesFormatter(params.row.attachments).map(link => (
|
||||
<button
|
||||
key={link.publicUrl}
|
||||
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
|
||||
>
|
||||
{link.name}
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
@ -315,7 +316,7 @@ export const loadColumns = async (
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
hide: true,
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
@ -328,7 +329,8 @@ export const loadColumns = async (
|
||||
minWidth: 30,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
getActions: (params: GridRowParams) => {
|
||||
hide: true,
|
||||
getActions: (params: any) => {
|
||||
|
||||
return [
|
||||
<div key={params?.row?.id}>
|
||||
@ -348,4 +350,14 @@ export const loadColumns = async (
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Filter columns for non-admins if they are strictly admin-only
|
||||
return columns.filter(col => {
|
||||
if (!isAdmin) {
|
||||
if (['requires_approval', 'submitted_at', 'decided_at', 'reason', 'manager_note'].includes(col.field)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js'
|
||||
import Head from 'next/head'
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated'
|
||||
@ -27,6 +26,7 @@ const Time_off_requestsTablesPage = () => {
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(true);
|
||||
const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false);
|
||||
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
@ -35,10 +35,10 @@ const Time_off_requestsTablesPage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
const [filters] = useState([{label: 'Reason', title: 'reason'},{label: 'ManagerNote', title: 'manager_note'},{label: 'ExternalReference', title: 'external_reference'},
|
||||
const [filters] = useState([{label: 'Reason', title: 'reason'},{label: 'Manager Note', title: 'manager_note'},{label: 'External Reference', title: 'external_reference'},
|
||||
|
||||
{label: 'Hours', title: 'hours', number: 'true'},{label: 'Days', title: 'days', number: 'true'},
|
||||
{label: 'StartsAt', title: 'starts_at', date: 'true'},{label: 'EndsAt', title: 'ends_at', date: 'true'},{label: 'SubmittedAt', title: 'submitted_at', date: 'true'},{label: 'DecidedAt', title: 'decided_at', date: 'true'},
|
||||
{label: 'Start', title: 'starts_at', date: 'true'},{label: 'Finish', title: 'ends_at', date: 'true'},{label: 'Submitted At', title: 'submitted_at', date: 'true'},{label: 'Decided At', title: 'decided_at', date: 'true'},
|
||||
|
||||
|
||||
{label: 'Requester', title: 'requester'},
|
||||
@ -50,7 +50,7 @@ const Time_off_requestsTablesPage = () => {
|
||||
|
||||
|
||||
|
||||
{label: 'LeaveType', title: 'leave_type', type: 'enum', options: ['regular_pto','unplanned_pto','medical_leave','bereavement','time_in_lieu']},{label: 'RequestKind', title: 'request_kind', type: 'enum', options: ['take_time_off','add_time_credit','manual_adjustment']},{label: 'Status', title: 'status', type: 'enum', options: ['draft','pending_approval','approved','rejected','cancelled']},
|
||||
{label: 'Type', title: 'leave_type', type: 'enum', options: ['regular_pto','unplanned_pto','medical_leave','bereavement','time_in_lieu']},{label: 'Request Kind', title: 'request_kind', type: 'enum', options: ['take_time_off','add_time_credit','manual_adjustment']},{label: 'Status', title: 'status', type: 'enum', options: ['draft','pending_approval','approved','rejected','cancelled']},
|
||||
]);
|
||||
|
||||
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_TIME_OFF_REQUESTS');
|
||||
@ -62,27 +62,13 @@ const Time_off_requestsTablesPage = () => {
|
||||
}, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getTime_off_requestsCSV = async () => {
|
||||
const response = await axios({url: '/time_off_requests?filetype=csv', method: 'GET',responseType: 'blob'});
|
||||
const type = response.headers['content-type']
|
||||
const blob = new Blob([response.data], { type: type })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = 'time_off_requestsCSV.csv'
|
||||
link.download = 'time_off_requests_export.csv'
|
||||
link.click()
|
||||
};
|
||||
|
||||
@ -102,10 +88,10 @@ const Time_off_requestsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Time_off_requests')}</title>
|
||||
<title>{getPageTitle('Time Off Requests')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Time_off_requests" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Time Off Requests" main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
@ -116,7 +102,7 @@ const Time_off_requestsTablesPage = () => {
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
onClick={() => setIsFilterDrawerOpen(true)}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
@ -124,10 +110,11 @@ const Time_off_requestsTablesPage = () => {
|
||||
label='Pending'
|
||||
onClick={showPending}
|
||||
/>
|
||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getTime_off_requestsCSV} />
|
||||
<BaseButton className={'mr-3'} color='info' label='Export' onClick={getTime_off_requestsCSV} />
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
@ -153,6 +140,8 @@ const Time_off_requestsTablesPage = () => {
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={showTableView}
|
||||
isFilterDrawerOpen={isFilterDrawerOpen}
|
||||
setIsFilterDrawerOpen={setIsFilterDrawerOpen}
|
||||
/>
|
||||
|
||||
</SectionMain>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user