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') && (
- + { )} + +
+ +
+
+ + + setReportFilters({ + ...reportFilters, + startDate: event.target.value, + }) + } + /> + + + + setReportFilters({ + ...reportFilters, + endDate: event.target.value, + }) + } + /> + +
+ + +
+
+ {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) } - - - - + ))}