Add per-employee dept breakdown via modal popup

Stores per-employee shift costs in wage_actuals_detail (APPROVED shifts
only). Sync fetches employee name map from /api/v2/users alongside dept
names. Clicking any dept row in Weekly or Monthly opens a modal showing
Employee · Shifts · Cost · % of Dept, fetched on demand.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-23 11:02:11 +00:00
parent 91936ab131
commit 5372111f52
9 changed files with 289 additions and 16 deletions

View file

@ -1,7 +1,8 @@
import { useState, useEffect, useCallback } from 'react'
import { ChevronLeft, ChevronRight, ChevronDown, Download } from 'lucide-react'
import { getActuals, getNetSales, getBudgets, downloadExport } from '../api'
import type { DeptActuals, WageBudget } from '../types'
import { getActuals, getNetSales, getBudgets, getDeptDetail, downloadExport } from '../api'
import type { DeptActuals, WageBudget, EmployeeDetail } from '../types'
import { DeptDetailModal } from '../components/DeptDetailModal'
function localStr(d: Date): string {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
@ -66,6 +67,9 @@ export default function Weekly() {
const [showOncosts, setShowOncosts] = useState(true)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [modal, setModal] = useState<{ deptId: string; deptName: string } | null>(null)
const [modalEmps, setModalEmps] = useState<EmployeeDetail[] | null>(null)
const [modalLoad, setModalLoad] = useState(false)
const load = useCallback(async () => {
setLoading(true); setError(null)
@ -123,6 +127,15 @@ export default function Weekly() {
useEffect(() => { load() }, [load])
useEffect(() => {
if (!modal) { setModalEmps(null); return }
setModalLoad(true)
getDeptDetail(modal.deptId, fromStr, isCurrentWeek ? todayStr : toStr)
.then(r => setModalEmps(r.employees))
.catch(() => setModalEmps([]))
.finally(() => setModalLoad(false))
}, [modal, fromStr, toStr, todayStr, isCurrentWeek])
const prev = () => setFromStr(s => addDaysStr(s, -7))
const next = () => setFromStr(s => addDaysStr(s, 7))
@ -224,7 +237,8 @@ export default function Weekly() {
const depOfTotal = totalWages > 0 ? (dep.cost / totalWages) * 100 : null
const depSPct = netSales > 0 ? (dep.cost / netSales) * 100 : null
return (
<tr key={dep.department_name}>
<tr key={dep.department_name} style={{ cursor: 'pointer' }}
onClick={() => setModal({ deptId: dep.department_id, deptName: dep.department_name })}>
<td>{dep.department_name}</td>
<td className="right">{fmtMoney(dep.cost)}</td>
<td className="right" style={{ color: 'var(--text-muted)' }}>
@ -303,6 +317,16 @@ export default function Weekly() {
)}
</>
)}
{modal && (
<DeptDetailModal
deptName={modal.deptName}
period={`${fmtDisplay(fromStr)} ${fmtDisplay(isCurrentWeek ? todayStr : toStr)}`}
employees={modalEmps}
loading={modalLoad}
onClose={() => setModal(null)}
/>
)}
</div>
)
}