Autosave: 20260413-160302

This commit is contained in:
Flatlogic Bot 2026-04-13 16:03:03 +00:00
parent da86c68992
commit f4182557a8
16 changed files with 247 additions and 779 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -5,14 +5,17 @@ const db = require('../db/models');
const { wrapAsync } = require('../helpers');
const { Op } = require('sequelize');
const getDateBoundary = (date, boundary) =>
new Date(`${date}T${boundary === 'start' ? '00:00:00.000' : '23:59:59.999'}Z`);
router.post('/', passport.authenticate('jwt', { session: false }), wrapAsync(async (req, res) => {
const { startDate, endDate, employeeId } = req.body;
const where = {};
if (startDate || endDate) {
where.createdAt = {};
if (startDate) where.createdAt[Op.gte] = new Date(startDate);
if (endDate) where.createdAt[Op.lte] = new Date(endDate);
if (startDate) where.createdAt[Op.gte] = getDateBoundary(startDate, 'start');
if (endDate) where.createdAt[Op.lte] = getDateBoundary(endDate, 'end');
}
if (employeeId) {
where.employeeId = employeeId;
@ -33,4 +36,4 @@ router.post('/', passport.authenticate('jwt', { session: false }), wrapAsync(asy
res.json({ lineItems, summary });
}));
module.exports = router;
module.exports = router;

View File

@ -5,15 +5,40 @@ const db = require('../db/models');
const { wrapAsync } = require('../helpers');
const { Op } = require('sequelize');
const buildDateBoundary = (dateValue, boundary) => {
if (!dateValue) {
return null;
}
const timeSuffix =
boundary === 'start' ? 'T00:00:00.000Z' : 'T23:59:59.999Z';
return new Date(`${dateValue}${timeSuffix}`);
};
router.get('/report', passport.authenticate('jwt', { session: false }), wrapAsync(async (req, res) => {
const { startDate, endDate, classId } = req.query;
const where = {};
if (startDate || endDate) {
where.work_date = {};
if (startDate) where.work_date[Op.gte] = new Date(startDate);
if (endDate) where.work_date[Op.lte] = new Date(endDate);
const start = buildDateBoundary(startDate, 'start');
const end = buildDateBoundary(endDate, 'end');
if (start && end && start > end) {
return res.status(400).json({ message: 'startDate must be on or before endDate' });
}
if (start || end) {
where.work_date = {};
if (start) {
where.work_date[Op.gte] = start;
}
if (end) {
where.work_date[Op.lte] = end;
}
}
if (classId) {
where.workersCompClassId = classId;
}
@ -23,14 +48,14 @@ router.get('/report', passport.authenticate('jwt', { session: false }), wrapAsyn
include: [
{ model: db.workers_comp_classes, as: 'workersCompClass' },
{ model: db.pay_types, as: 'pay_type' },
{ model: db.users, as: 'employee' }
]
{ model: db.users, as: 'employee' },
],
});
const totalsByClass = {};
let totalComp = 0;
jobLogs.forEach(log => {
jobLogs.forEach((log) => {
if (!log.workersCompClass || !log.pay_type) return;
let employeePay = 0;

View File

@ -109,34 +109,7 @@ const CardEmployee_pay_types = ({
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>EffectiveStart</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.effective_start) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>EffectiveEnd</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.effective_end) }
</div>
</dd>
</div>
</dl>
</dl>
</li>
))}
{!loading && employee_pay_types.length === 0 && (

View File

@ -67,26 +67,7 @@ const ListEmployee_pay_types = ({ employee_pay_types, loading, onDelete, current
<p className={'text-xs text-gray-500 '}>Active</p>
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.active) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>EffectiveStart</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.effective_start) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>EffectiveEnd</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.effective_end) }</p>
</div>
</Link>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}

View File

@ -100,44 +100,7 @@ export const loadColumns = async (
type: 'boolean',
},
{
field: 'effective_start',
headerName: 'EffectiveStart',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.effective_start),
},
{
field: 'effective_end',
headerName: 'EffectiveEnd',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.effective_end),
},
{
{
field: 'actions',
type: 'actions',
minWidth: 30,

View File

@ -1,9 +1,6 @@
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import dayjs from "dayjs";
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
@ -109,78 +106,7 @@ const EditEmployee_pay_types = () => {
active: false,
effective_start: new Date(),
effective_end: new Date(),
}
}
const [initialValues, setInitialValues] = useState(initVals)
const { employee_pay_types } = useAppSelector((state) => state.employee_pay_types)
@ -392,106 +318,7 @@ const EditEmployee_pay_types = () => {
component={SwitchField}
></Field>
</FormField>
<FormField
label="EffectiveStart"
>
<DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={initialValues.effective_start ?
new Date(
dayjs(initialValues.effective_start).format('YYYY-MM-DD hh:mm'),
) : null
}
onChange={(date) => setInitialValues({...initialValues, 'effective_start': date})}
/>
</FormField>
<FormField
label="EffectiveEnd"
>
<DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={initialValues.effective_end ?
new Date(
dayjs(initialValues.effective_end).format('YYYY-MM-DD hh:mm'),
) : null
}
onChange={(date) => setInitialValues({...initialValues, 'effective_end': date})}
/>
</FormField>
<BaseDivider />
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="reset" color="info" outline label="Reset" />

View File

@ -1,9 +1,6 @@
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import dayjs from "dayjs";
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
@ -109,78 +106,7 @@ const EditEmployee_pay_typesPage = () => {
active: false,
effective_start: new Date(),
effective_end: new Date(),
}
}
const [initialValues, setInitialValues] = useState(initVals)
const { employee_pay_types } = useAppSelector((state) => state.employee_pay_types)
@ -389,106 +315,7 @@ const EditEmployee_pay_typesPage = () => {
component={SwitchField}
></Field>
</FormField>
<FormField
label="EffectiveStart"
>
<DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={initialValues.effective_start ?
new Date(
dayjs(initialValues.effective_start).format('YYYY-MM-DD hh:mm'),
) : null
}
onChange={(date) => setInitialValues({...initialValues, 'effective_start': date})}
/>
</FormField>
<FormField
label="EffectiveEnd"
>
<DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={initialValues.effective_end ?
new Date(
dayjs(initialValues.effective_end).format('YYYY-MM-DD hh:mm'),
) : null
}
onChange={(date) => setInitialValues({...initialValues, 'effective_end': date})}
/>
</FormField>
<BaseDivider />
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="reset" color="info" outline label="Reset" />

View File

@ -37,7 +37,7 @@ const Employee_pay_typesTablesPage = () => {
const [filters] = useState([
{label: 'EffectiveStart', title: 'effective_start', date: 'true'},{label: 'EffectiveEnd', title: 'effective_end', date: 'true'},
{label: 'Employee', title: 'employee'},

View File

@ -69,46 +69,6 @@ const initialValues = {
active: false,
effective_start: '',
effective_end: '',
}
@ -228,88 +188,7 @@ const Employee_pay_typesNew = () => {
component={SwitchField}
></Field>
</FormField>
<FormField
label="EffectiveStart"
>
<Field
type="datetime-local"
name="effective_start"
placeholder="EffectiveStart"
/>
</FormField>
<FormField
label="EffectiveEnd"
>
<Field
type="datetime-local"
name="effective_end"
placeholder="EffectiveEnd"
/>
</FormField>
<BaseDivider />
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="reset" color="info" outline label="Reset" />

View File

@ -37,7 +37,7 @@ const Employee_pay_typesTablesPage = () => {
const [filters] = useState([
{label: 'EffectiveStart', title: 'effective_start', date: 'true'},{label: 'EffectiveEnd', title: 'effective_end', date: 'true'},
{label: 'Employee', title: 'employee'},

View File

@ -1,8 +1,5 @@
import React, { ReactElement, useEffect } from 'react';
import Head from 'next/head'
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import dayjs from "dayjs";
import {useAppDispatch, useAppSelector} from "../../stores/hooks";
import {useRouter} from "next/router";
import { fetch } from '../../stores/employee_pay_types/employee_pay_typesSlice'
@ -199,112 +196,7 @@ const Employee_pay_typesView = () => {
disabled
/>
</FormField>
<FormField label='EffectiveStart'>
{employee_pay_types.effective_start ? <DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={employee_pay_types.effective_start ?
new Date(
dayjs(employee_pay_types.effective_start).format('YYYY-MM-DD hh:mm'),
) : null
}
disabled
/> : <p>No EffectiveStart</p>}
</FormField>
<FormField label='EffectiveEnd'>
{employee_pay_types.effective_end ? <DatePicker
dateFormat="yyyy-MM-dd hh:mm"
showTimeSelect
selected={employee_pay_types.effective_end ?
new Date(
dayjs(employee_pay_types.effective_end).format('YYYY-MM-DD hh:mm'),
) : null
}
disabled
/> : <p>No EffectiveEnd</p>}
</FormField>
<BaseDivider />
<BaseDivider />
<BaseButton
color='info'

View File

@ -276,11 +276,11 @@ const Pay_typesView = () => {
<th>EffectiveStart</th>
<th>EffectiveEnd</th>
</tr>
@ -298,21 +298,7 @@ const Pay_typesView = () => {
<td data-label="active">
{ dataFormatter.booleanFormatter(item.active) }
</td>
<td data-label="effective_start">
{ dataFormatter.dateTimeFormatter(item.effective_start) }
</td>
<td data-label="effective_end">
{ dataFormatter.dateTimeFormatter(item.effective_end) }
</td>
</tr>
</tr>
))}
</tbody>
</table>

View File

@ -437,11 +437,11 @@ const UsersView = () => {
<th>EffectiveStart</th>
<th>EffectiveEnd</th>
</tr>
@ -459,21 +459,7 @@ const UsersView = () => {
<td data-label="active">
{ dataFormatter.booleanFormatter(item.active) }
</td>
<td data-label="effective_start">
{ dataFormatter.dateTimeFormatter(item.effective_start) }
</td>
<td data-label="effective_end">
{ dataFormatter.dateTimeFormatter(item.effective_end) }
</td>
</tr>
</tr>
))}
</tbody>
</table>

View File

@ -1,7 +1,7 @@
import { mdiChartTimelineVariant, mdiPlus } from '@mdi/js'
import Head from 'next/head'
import { uniqueId } from 'lodash'
import React, { ReactElement, useEffect, useState } from 'react'
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
@ -11,47 +11,123 @@ import TableWorkers_comp_classes from '../../components/Workers_comp_classes/Tab
import BaseButton from '../../components/BaseButton'
import axios from 'axios'
import Link from 'next/link'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import CardBoxModal from '../../components/CardBoxModal'
import DragDropFilePicker from '../../components/DragDropFilePicker'
import { setRefetch, uploadCsv } from '../../stores/workers_comp_classes/workers_comp_classesSlice'
import { useAppSelector } from '../../stores/hooks'
import { hasPermission } from '../../helpers/userPermissions'
import FormField from '../../components/FormField'
const formatDateLabel = (value: string) => {
if (!value) {
return ''
}
return new Date(`${value}T00:00:00`).toLocaleDateString()
}
const formatDateInputValue = (value: Date) => {
const year = value.getFullYear()
const month = `${value.getMonth() + 1}`.padStart(2, '0')
const day = `${value.getDate()}`.padStart(2, '0')
return `${year}-${month}-${day}`
}
const getThisWeekFilters = () => {
const today = new Date()
const startOfWeek = new Date(today)
startOfWeek.setDate(today.getDate() - today.getDay())
return {
startDate: formatDateInputValue(startOfWeek),
endDate: formatDateInputValue(today),
}
}
const Workers_comp_classesList = () => {
const [filterItems, setFilterItems] = useState<any[]>([]);
const [filterItems, setFilterItems] = useState<any[]>([])
const [filters] = useState([
{label: 'Name', title: 'name'},
{label: 'Percentage', title: 'percentage', number: 'true'},
]);
{ label: 'Name', title: 'name' },
{ label: 'Percentage', title: 'percentage', number: 'true' },
])
const [reportFilters, setReportFilters] = useState({
startDate: '',
endDate: '',
})
const [appliedReportFilters, setAppliedReportFilters] = useState({
startDate: '',
endDate: '',
})
const [reportData, setReportData] = useState<any>(null)
const [reportLoading, setReportLoading] = useState(false)
const [reportError, setReportError] = useState('')
const dispatch = useAppDispatch();
const { currentUser } = useAppSelector((state) => state.auth);
const [reportData, setReportData] = useState<any>(null);
const { currentUser } = useAppSelector((state) => state.auth)
const addFilter = () => {
const newItem = {
id: uniqueId(),
fields: {
filterValue: '',
filterValueFrom: '',
filterValueTo: '',
selectedField: filters[0].title,
},
};
setFilterItems([...filterItems, newItem]);
};
const newItem = {
id: uniqueId(),
fields: {
filterValue: '',
filterValueFrom: '',
filterValueTo: '',
selectedField: filters[0].title,
},
}
setFilterItems([...filterItems, newItem])
}
const fetchReport = useCallback(async (nextFilters: { startDate: string; endDate: string }) => {
setReportLoading(true)
setReportError('')
try {
const params: { startDate?: string; endDate?: string } = {}
if (nextFilters.startDate) {
params.startDate = nextFilters.startDate
}
if (nextFilters.endDate) {
params.endDate = nextFilters.endDate
}
const res = await axios.get('/workers_comp_report/report', { params })
setReportData(res.data)
setAppliedReportFilters(nextFilters)
} catch (error: any) {
console.error('Failed to fetch workers comp report', error)
setReportData(null)
setReportError(
error?.response?.data?.message || 'Failed to fetch workers comp report.',
)
} finally {
setReportLoading(false)
}
}, [])
useEffect(() => {
const fetchReport = async () => {
try {
const res = await axios.get('/workers_comp_report/report');
setReportData(res.data);
} catch (e) {
console.error("Failed to fetch report", e);
}
};
fetchReport();
}, []);
fetchReport({ startDate: '', endDate: '' })
}, [fetchReport])
const handleApplyDateFilter = () => {
fetchReport(reportFilters)
}
const handleThisWeekFilter = () => {
const thisWeekFilters = getThisWeekFilters()
setReportFilters(thisWeekFilters)
fetchReport(thisWeekFilters)
}
const handleClearDateFilter = () => {
const clearedFilters = { startDate: '', endDate: '' }
setReportFilters(clearedFilters)
fetchReport(clearedFilters)
}
const reportHeading = appliedReportFilters.startDate || appliedReportFilters.endDate
? `Workman's Comp Totals (${formatDateLabel(appliedReportFilters.startDate) || 'Beginning'} - ${formatDateLabel(appliedReportFilters.endDate) || 'Today'})`
: "Workman's Comp Totals (All Time)"
return (
<>
@ -66,11 +142,7 @@ const Workers_comp_classesList = () => {
>
{hasPermission(currentUser, 'CREATE_WORKERS_COMP_CLASSES') && (
<div className="flex items-center gap-2">
<BaseButton
color='info'
label='Filter'
onClick={addFilter}
/>
<BaseButton color='info' label='Filter' onClick={addFilter} />
<Link href={'/workers_comp_classes/workers_comp_classes-new'}>
<BaseButton
color="info"
@ -82,30 +154,98 @@ const Workers_comp_classesList = () => {
)}
</SectionTitleLineWithButton>
<CardBox className="mb-6">
<div className="mb-4 flex flex-wrap gap-3">
<BaseButton
color="info"
outline
label="This Week"
onClick={handleThisWeekFilter}
disabled={reportLoading}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<FormField label="Start Date" labelFor="workers-comp-start-date">
<input
id="workers-comp-start-date"
type="date"
value={reportFilters.startDate}
onChange={(event) =>
setReportFilters({
...reportFilters,
startDate: event.target.value,
})
}
/>
</FormField>
<FormField label="End Date" labelFor="workers-comp-end-date">
<input
id="workers-comp-end-date"
type="date"
value={reportFilters.endDate}
onChange={(event) =>
setReportFilters({
...reportFilters,
endDate: event.target.value,
})
}
/>
</FormField>
<div className="flex items-end gap-3 pb-6">
<BaseButton
color="info"
label={reportLoading ? 'Applying...' : 'Apply Date Filter'}
onClick={handleApplyDateFilter}
disabled={reportLoading}
className="w-full"
/>
<BaseButton
color="info"
outline
label="Clear"
onClick={handleClearDateFilter}
disabled={reportLoading}
/>
</div>
</div>
{reportError && (
<div className="mt-2 rounded bg-red-100 p-3 text-red-700">
{reportError}
</div>
)}
</CardBox>
{reportData && (
<CardBox className="mb-6">
<h3 className="text-xl font-semibold mb-4">Workman&apos;s Comp Totals (All Time)</h3>
<h3 className="text-xl font-semibold mb-4">{reportHeading}</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-4 bg-blue-50 rounded shadow">
<p className="text-sm text-blue-600 font-bold">Total Work Comp</p>
<p className="text-2xl font-semibold">${reportData.totalComp.toFixed(2)}</p>
<p className="text-2xl font-semibold">
${Number(reportData.totalComp || 0).toFixed(2)}
</p>
</div>
{Object.entries(reportData.totalsByClass).map(([className, total]: any) => (
{Object.entries(reportData.totalsByClass || {}).map(([className, total]: any) => (
<div key={className} className="p-4 bg-gray-50 rounded shadow">
<p className="text-sm text-gray-500 font-bold">{className}</p>
<p className="text-xl font-semibold">${Number(total).toFixed(2)}</p>
</div>
))}
</div>
{!reportLoading && !Object.keys(reportData.totalsByClass || {}).length && (
<p className="mt-4 text-sm text-gray-500">
No workers comp totals found for the selected date range.
</p>
)}
</CardBox>
)}
<CardBox className="mb-6" hasTable>
<TableWorkers_comp_classes
filterItems={filterItems}
setFilterItems={setFilterItems}
filters={filters}
showGrid={false}
<TableWorkers_comp_classes
filterItems={filterItems}
setFilterItems={setFilterItems}
filters={filters}
showGrid={false}
/>
</CardBox>
</SectionMain>
@ -117,4 +257,4 @@ Workers_comp_classesList.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>
}
export default Workers_comp_classesList
export default Workers_comp_classesList

View File

@ -276,11 +276,11 @@ const Workers_comp_classesView = () => {
<th>EffectiveStart</th>
<th>EffectiveEnd</th>
</tr>
@ -298,21 +298,7 @@ const Workers_comp_classesView = () => {
<td data-label="active">
{ dataFormatter.booleanFormatter(item.active) }
</td>
<td data-label="effective_start">
{ dataFormatter.dateTimeFormatter(item.effective_start) }
</td>
<td data-label="effective_end">
{ dataFormatter.dateTimeFormatter(item.effective_end) }
</td>
</tr>
</tr>
))}
</tbody>
</table>