Initial scaffold: wages app

Full wage cost reporting app — weekly/monthly views, rolling 12-week/12-month
history, budget management, Workforce API sync with SSE backfill, net sales
via forecasting public API, department filter, CSV export.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-23 09:03:52 +00:00
commit 2e0592eb90
37 changed files with 3078 additions and 0 deletions

77
frontend/src/App.tsx Normal file
View file

@ -0,0 +1,77 @@
import { useState } from 'react'
import { DollarSign, CalendarDays, TrendingUp, BarChart3, Wallet, Settings } from 'lucide-react'
import AuthGate, { useAuth } from './components/AuthGate'
import { UpdateBanner } from './components/UpdateBanner'
import { useVersionCheck } from './hooks/useVersionCheck'
import { can } from './types'
import Weekly from './pages/Weekly'
import Monthly from './pages/Monthly'
import Rolling12Weeks from './pages/Rolling12Weeks'
import Rolling12Months from './pages/Rolling12Months'
import Budgets from './pages/Budgets'
import SettingsPage from './pages/Settings'
type Page = 'weekly' | 'monthly' | 'rolling-weeks' | 'rolling-months' | 'budgets' | 'settings'
const NAV: { id: Page; label: string; icon: React.ElementType; cap?: string }[] = [
{ id: 'weekly', label: 'Weekly', icon: CalendarDays },
{ id: 'monthly', label: 'Monthly', icon: TrendingUp },
{ id: 'rolling-weeks', label: '12 Weeks', icon: BarChart3 },
{ id: 'rolling-months', label: '12 Months', icon: BarChart3 },
{ id: 'budgets', label: 'Budgets', icon: Wallet, cap: 'budget' },
{ id: 'settings', label: 'Settings', icon: Settings, cap: 'settings' },
]
function Shell() {
const { user } = useAuth()
const [page, setPage] = useState<Page>('weekly')
const hotelName = import.meta.env.VITE_HOTEL_NAME || 'Hotel'
return (
<div className="app-shell">
<nav className="sidebar">
<div className="sidebar-logo">
<DollarSign size={16} strokeWidth={1.75} />
Wage Costs
</div>
<div className="sidebar-nav">
{NAV.filter(n => !n.cap || can(user, n.cap)).map(n => (
<div
key={n.id}
className={`nav-item${page === n.id ? ' active' : ''}`}
onClick={() => setPage(n.id)}
>
<n.icon size={16} strokeWidth={1.75} />
{n.label}
</div>
))}
</div>
<div style={{ padding: '12px 16px', fontSize: '11px', color: 'rgba(255,255,255,0.3)' }}>
{hotelName}
</div>
</nav>
<main className="content">
{page === 'weekly' && <Weekly />}
{page === 'monthly' && <Monthly />}
{page === 'rolling-weeks' && <Rolling12Weeks />}
{page === 'rolling-months' && <Rolling12Months />}
{page === 'budgets' && <Budgets />}
{page === 'settings' && <SettingsPage />}
</main>
</div>
)
}
export default function App() {
const updateAvailable = useVersionCheck('/wages/health')
return (
<>
<AuthGate>
<Shell />
</AuthGate>
<UpdateBanner visible={updateAvailable} />
</>
)
}