diff --git a/assets/pasted-20260413-145044-bf0d1263.png b/assets/pasted-20260413-145044-bf0d1263.png
new file mode 100644
index 0000000..f6d0529
Binary files /dev/null and b/assets/pasted-20260413-145044-bf0d1263.png differ
diff --git a/backend/src/routes/reports.js b/backend/src/routes/reports.js
index 3224401..9452846 100644
--- a/backend/src/routes/reports.js
+++ b/backend/src/routes/reports.js
@@ -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;
\ No newline at end of file
+module.exports = router;
diff --git a/backend/src/routes/workers_comp_report.js b/backend/src/routes/workers_comp_report.js
index 9b28756..107ae9a 100644
--- a/backend/src/routes/workers_comp_report.js
+++ b/backend/src/routes/workers_comp_report.js
@@ -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;
diff --git a/frontend/src/components/Employee_pay_types/CardEmployee_pay_types.tsx b/frontend/src/components/Employee_pay_types/CardEmployee_pay_types.tsx
index 8f95541..f4cfdda 100644
--- a/frontend/src/components/Employee_pay_types/CardEmployee_pay_types.tsx
+++ b/frontend/src/components/Employee_pay_types/CardEmployee_pay_types.tsx
@@ -109,34 +109,7 @@ const CardEmployee_pay_types = ({
-
-
-
-
-
-
EffectiveStart
-
-
- { dataFormatter.dateTimeFormatter(item.effective_start) }
-
-
-
-
-
-
-
-
-
EffectiveEnd
-
-
- { dataFormatter.dateTimeFormatter(item.effective_end) }
-
-
-
-
-
-
-
+
))}
{!loading && employee_pay_types.length === 0 && (
diff --git a/frontend/src/components/Employee_pay_types/ListEmployee_pay_types.tsx b/frontend/src/components/Employee_pay_types/ListEmployee_pay_types.tsx
index c756bb8..079da96 100644
--- a/frontend/src/components/Employee_pay_types/ListEmployee_pay_types.tsx
+++ b/frontend/src/components/Employee_pay_types/ListEmployee_pay_types.tsx
@@ -67,26 +67,7 @@ const ListEmployee_pay_types = ({ employee_pay_types, loading, onDelete, current
Active
{ dataFormatter.booleanFormatter(item.active) }
-
-
-
-
-
-
EffectiveStart
-
{ dataFormatter.dateTimeFormatter(item.effective_start) }
-
-
-
-
-
-
-
EffectiveEnd
-
{ dataFormatter.dateTimeFormatter(item.effective_end) }
-
-
-
-
-
+
- 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,
diff --git a/frontend/src/pages/employee_pay_types/[employee_pay_typesId].tsx b/frontend/src/pages/employee_pay_types/[employee_pay_typesId].tsx
index a011bed..dc43773 100644
--- a/frontend/src/pages/employee_pay_types/[employee_pay_typesId].tsx
+++ b/frontend/src/pages/employee_pay_types/[employee_pay_typesId].tsx
@@ -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}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setInitialValues({...initialValues, 'effective_start': date})}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setInitialValues({...initialValues, 'effective_end': date})}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/frontend/src/pages/employee_pay_types/employee_pay_types-edit.tsx b/frontend/src/pages/employee_pay_types/employee_pay_types-edit.tsx
index 7788fd6..ab51674 100644
--- a/frontend/src/pages/employee_pay_types/employee_pay_types-edit.tsx
+++ b/frontend/src/pages/employee_pay_types/employee_pay_types-edit.tsx
@@ -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}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setInitialValues({...initialValues, 'effective_start': date})}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setInitialValues({...initialValues, 'effective_end': date})}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/frontend/src/pages/employee_pay_types/employee_pay_types-list.tsx b/frontend/src/pages/employee_pay_types/employee_pay_types-list.tsx
index c604695..38cf56f 100644
--- a/frontend/src/pages/employee_pay_types/employee_pay_types-list.tsx
+++ b/frontend/src/pages/employee_pay_types/employee_pay_types-list.tsx
@@ -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'},
diff --git a/frontend/src/pages/employee_pay_types/employee_pay_types-new.tsx b/frontend/src/pages/employee_pay_types/employee_pay_types-new.tsx
index 076bb7a..87ce03c 100644
--- a/frontend/src/pages/employee_pay_types/employee_pay_types-new.tsx
+++ b/frontend/src/pages/employee_pay_types/employee_pay_types-new.tsx
@@ -69,46 +69,6 @@ const initialValues = {
active: false,
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- effective_start: '',
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- effective_end: '',
-
-
-
-
-
-
-
-
-
}
@@ -228,88 +188,7 @@ const Employee_pay_typesNew = () => {
component={SwitchField}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/frontend/src/pages/employee_pay_types/employee_pay_types-table.tsx b/frontend/src/pages/employee_pay_types/employee_pay_types-table.tsx
index aa11e0e..c4b8d3a 100644
--- a/frontend/src/pages/employee_pay_types/employee_pay_types-table.tsx
+++ b/frontend/src/pages/employee_pay_types/employee_pay_types-table.tsx
@@ -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'},
diff --git a/frontend/src/pages/employee_pay_types/employee_pay_types-view.tsx b/frontend/src/pages/employee_pay_types/employee_pay_types-view.tsx
index 286397c..04eff1d 100644
--- a/frontend/src/pages/employee_pay_types/employee_pay_types-view.tsx
+++ b/frontend/src/pages/employee_pay_types/employee_pay_types-view.tsx
@@ -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
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {employee_pay_types.effective_start ? : No EffectiveStart
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {employee_pay_types.effective_end ? : No EffectiveEnd
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
{
- EffectiveStart |
- EffectiveEnd |
+
+
@@ -298,21 +298,7 @@ const Pay_typesView = () => {
{ dataFormatter.booleanFormatter(item.active) }
|
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_start) }
- |
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_end) }
- |
-
-
-
+
))}
diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx
index 9e13342..96dc691 100644
--- a/frontend/src/pages/users/users-view.tsx
+++ b/frontend/src/pages/users/users-view.tsx
@@ -437,11 +437,11 @@ const UsersView = () => {
- EffectiveStart |
- EffectiveEnd |
+
+
@@ -459,21 +459,7 @@ const UsersView = () => {
{ dataFormatter.booleanFormatter(item.active) }
|
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_start) }
- |
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_end) }
- |
-
-
-
+
))}
diff --git a/frontend/src/pages/workers_comp_classes/workers_comp_classes-list.tsx b/frontend/src/pages/workers_comp_classes/workers_comp_classes-list.tsx
index e44f45d..3e59ee4 100644
--- a/frontend/src/pages/workers_comp_classes/workers_comp_classes-list.tsx
+++ b/frontend/src/pages/workers_comp_classes/workers_comp_classes-list.tsx
@@ -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([]);
+ const [filterItems, setFilterItems] = useState([])
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(null)
+ const [reportLoading, setReportLoading] = useState(false)
+ const [reportError, setReportError] = useState('')
- const dispatch = useAppDispatch();
- const { currentUser } = useAppSelector((state) => state.auth);
- const [reportData, setReportData] = useState(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') && (
-
+
{
)}
+
+
+
+
+
+ {reportError && (
+
+ {reportError}
+
+ )}
+
+
{reportData && (
- Workman's Comp Totals (All Time)
+ {reportHeading}
Total Work Comp
-
${reportData.totalComp.toFixed(2)}
+
+ ${Number(reportData.totalComp || 0).toFixed(2)}
+
- {Object.entries(reportData.totalsByClass).map(([className, total]: any) => (
+ {Object.entries(reportData.totalsByClass || {}).map(([className, total]: any) => (
{className}
${Number(total).toFixed(2)}
))}
+ {!reportLoading && !Object.keys(reportData.totalsByClass || {}).length && (
+
+ No workers comp totals found for the selected date range.
+
+ )}
)}
-
@@ -117,4 +257,4 @@ Workers_comp_classesList.getLayout = function getLayout(page: ReactElement) {
return {page}
}
-export default Workers_comp_classesList
\ No newline at end of file
+export default Workers_comp_classesList
diff --git a/frontend/src/pages/workers_comp_classes/workers_comp_classes-view.tsx b/frontend/src/pages/workers_comp_classes/workers_comp_classes-view.tsx
index 560f936..bba8fbe 100644
--- a/frontend/src/pages/workers_comp_classes/workers_comp_classes-view.tsx
+++ b/frontend/src/pages/workers_comp_classes/workers_comp_classes-view.tsx
@@ -276,11 +276,11 @@ const Workers_comp_classesView = () => {
- EffectiveStart |
- EffectiveEnd |
+
+
@@ -298,21 +298,7 @@ const Workers_comp_classesView = () => {
{ dataFormatter.booleanFormatter(item.active) }
|
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_start) }
- |
-
-
-
-
- { dataFormatter.dateTimeFormatter(item.effective_end) }
- |
-
-
-
+
))}