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:
commit
2e0592eb90
37 changed files with 3078 additions and 0 deletions
71
frontend/src/api.ts
Normal file
71
frontend/src/api.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import type { DeptActuals, DeptScheduled, NetSalesDay, WageBudget, AppSetting } from './types'
|
||||
|
||||
const BASE = '/wages/api'
|
||||
|
||||
async function request<T>(path: string, opts: RequestInit = {}): Promise<T> {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
||||
...opts,
|
||||
})
|
||||
if (res.status === 401) {
|
||||
;(window.top ?? window).location.href = '/login'
|
||||
throw new Error('Unauthenticated')
|
||||
}
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new Error((err as { error?: string }).error || `Request failed: ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function getActuals(from: string, to: string): Promise<{ departments: DeptActuals[]; show_oncosts: boolean }> {
|
||||
return request(`/actuals?from=${from}&to=${to}`)
|
||||
}
|
||||
|
||||
export function getScheduled(from: string, to: string): Promise<{ departments: DeptScheduled[] }> {
|
||||
return request(`/scheduled?from=${from}&to=${to}`)
|
||||
}
|
||||
|
||||
export function getNetSales(from: string, to: string): Promise<{ days: NetSalesDay[] }> {
|
||||
return request(`/net-sales?from=${from}&to=${to}`)
|
||||
}
|
||||
|
||||
export function getBudgets(): Promise<{ budgets: WageBudget[] }> {
|
||||
return request('/budgets')
|
||||
}
|
||||
|
||||
export function saveBudget(month: string, budget_amount: number): Promise<{ ok: boolean }> {
|
||||
return request(`/budgets/${month}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ budget_amount }),
|
||||
})
|
||||
}
|
||||
|
||||
export function triggerSync(): Promise<{ ok: boolean; actual_rows: number; scheduled_rows: number }> {
|
||||
return request('/sync', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function getSyncStatus(): Promise<{ sync_last_at: string | null; backfill_last_at: string | null; backfill_running: boolean }> {
|
||||
return request('/sync/status')
|
||||
}
|
||||
|
||||
export function cancelBackfill(): Promise<{ ok: boolean }> {
|
||||
return request('/sync/backfill/cancel', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function getDepartments(): Promise<{ departments: { id: string; name: string }[] }> {
|
||||
return request('/departments')
|
||||
}
|
||||
|
||||
export function getSettings(): Promise<{ settings: AppSetting[] }> {
|
||||
return request('/settings')
|
||||
}
|
||||
|
||||
export function saveSettings(settings: { key: string; value: string }[]): Promise<{ ok: boolean }> {
|
||||
return request('/settings', { method: 'PUT', body: JSON.stringify({ settings }) })
|
||||
}
|
||||
|
||||
export function downloadExport(view: string, from: string, to: string): void {
|
||||
window.open(`${BASE}/export?view=${view}&from=${from}&to=${to}`, '_blank')
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue