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:
parent
91936ab131
commit
5372111f52
9 changed files with 289 additions and 16 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue