Ports two modules from the friend's Google Apps Script build (kept as reference in gscript/) onto the TypeScript stack, rewritten cleanly against this app's own data model rather than copied 1:1: - Shift planning: weekly template (Sun-Thu evening, Fri/Sat two slots), lazy idempotent generation per period (no cron needed), employee signup/cancel with collision + capacity checks, admin calendar view with slot editing and manual assignment. - Month closure + payroll: employee confirms the month (blocked while a shift is still open), admin locks and finalizes pay (base hours * rate + tips/bonus/other), reopen to undo a premature lock, mark paid. Pay rates are versioned by date, defaulting the first-ever rate to apply retroactively to the employee's whole history. - A shift left open more than 12h (forgotten clock-out) is auto-closed at clock_in + 12h, checked lazily on read instead of a background job. - Full dark, sharp-edged modern restyle (theme.css replaces tui.css) with an amber accent, keeping every existing class name so no component logic needed to change. Backend test coverage (jest) for all three workflows: shift planning, closure/payroll, and the forgotten-clock-out auto-close.
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { EmployeeManager } from '../components/EmployeeManager';
|
|
import { AdminStats } from '../components/AdminStats';
|
|
import { ShiftPlanManager } from '../components/ShiftPlanManager';
|
|
import { PayrollManager } from '../components/PayrollManager';
|
|
|
|
type Tab = 'employees' | 'stats' | 'shifts' | 'payroll';
|
|
|
|
export function AdminApp() {
|
|
const [tab, setTab] = useState<Tab>('employees');
|
|
|
|
useEffect(() => {
|
|
function onKey(e: KeyboardEvent) {
|
|
const target = e.target as HTMLElement;
|
|
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;
|
|
if (e.key === '1') setTab('employees');
|
|
if (e.key === '2') setTab('stats');
|
|
if (e.key === '3') setTab('shifts');
|
|
if (e.key === '4') setTab('payroll');
|
|
}
|
|
window.addEventListener('keydown', onKey);
|
|
return () => window.removeEventListener('keydown', onKey);
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<div className="tabs">
|
|
<button
|
|
className={`tab ${tab === 'employees' ? 'active' : ''}`}
|
|
onClick={() => setTab('employees')}
|
|
>
|
|
[1] Zaměstnanci
|
|
</button>
|
|
<button className={`tab ${tab === 'stats' ? 'active' : ''}`} onClick={() => setTab('stats')}>
|
|
[2] Statistiky
|
|
</button>
|
|
<button className={`tab ${tab === 'shifts' ? 'active' : ''}`} onClick={() => setTab('shifts')}>
|
|
[3] Směny
|
|
</button>
|
|
<button className={`tab ${tab === 'payroll' ? 'active' : ''}`} onClick={() => setTab('payroll')}>
|
|
[4] Mzdy
|
|
</button>
|
|
</div>
|
|
|
|
<div style={{ marginTop: '1rem' }}>
|
|
{tab === 'employees' && <EmployeeManager />}
|
|
{tab === 'stats' && <AdminStats />}
|
|
{tab === 'shifts' && <ShiftPlanManager />}
|
|
{tab === 'payroll' && <PayrollManager />}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|