New worker: Kropáček Ondřej (TKB, part-time student) inserted between Dvořák Václav and Vondrák Pavel in data.json, the full-month Excel template (new row 13, everything below shifted) and the docházka template (13 person rows). Not added to the harmonogram/CIS roster — he has no CIS personal number. Also checkpoints the uncommitted work deployed to production between May and July 2026: - half-cell day/night preference marks (color + text per half) - cell copy/paste (Ctrl+C/V, context menu), incl. from info rows - light/dark theme toggle, TKB people filter, 162 filter - full-month Excel export (template) and harmonogram export for CIS - docházka export: half-time workers, retired flag, contractor handling - third login role "cis" (viewer + export access) - retired flag hides a person from UI/FPD while keeping past data Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { useState } from 'react'
|
|
|
|
const VALID_USER = 'tkb'
|
|
const VALID_PASS = 'sluzby'
|
|
const VIEWER_USER = 'prohlizec'
|
|
const VIEWER_PASS = 'pohled'
|
|
const CIS_USER = 'cis'
|
|
const CIS_PASS = 'import'
|
|
const AUTH_KEY = 'tkb_auth'
|
|
const ROLE_KEY = 'tkb_role'
|
|
|
|
export type UserRole = 'editor' | 'viewer' | 'cis'
|
|
|
|
export function isAuthenticated(): boolean {
|
|
return localStorage.getItem(AUTH_KEY) === 'true'
|
|
}
|
|
|
|
export function getAuthRole(): UserRole {
|
|
const r = localStorage.getItem(ROLE_KEY)
|
|
if (r === 'viewer') return 'viewer'
|
|
if (r === 'cis') return 'cis'
|
|
return 'editor'
|
|
}
|
|
|
|
function setAuthenticated(role: UserRole): void {
|
|
localStorage.setItem(AUTH_KEY, 'true')
|
|
localStorage.setItem(ROLE_KEY, role)
|
|
}
|
|
|
|
export function clearAuthentication(): void {
|
|
localStorage.removeItem(AUTH_KEY)
|
|
localStorage.removeItem(ROLE_KEY)
|
|
}
|
|
|
|
interface LoginProps {
|
|
onLogin: (role: UserRole) => void
|
|
}
|
|
|
|
export function Login({ onLogin }: LoginProps) {
|
|
const [username, setUsername] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [error, setError] = useState(false)
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (username === VALID_USER && password === VALID_PASS) {
|
|
setAuthenticated('editor')
|
|
onLogin('editor')
|
|
} else if (username === VIEWER_USER && password === VIEWER_PASS) {
|
|
setAuthenticated('viewer')
|
|
onLogin('viewer')
|
|
} else if (username === CIS_USER && password === CIS_PASS) {
|
|
setAuthenticated('cis')
|
|
onLogin('cis')
|
|
} else {
|
|
setError(true)
|
|
setTimeout(() => setError(false), 3000)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-900 flex items-center justify-center">
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="bg-slate-800 border border-slate-700 rounded-lg p-8 w-80 shadow-xl"
|
|
>
|
|
<h1 className="text-lg font-semibold text-slate-100 mb-1 text-center">
|
|
TKB Plán služeb
|
|
</h1>
|
|
<p className="text-sm text-slate-400 mb-6 text-center">
|
|
Plánování směn a pohotovostí
|
|
</p>
|
|
|
|
<label className="block text-xs text-slate-400 mb-1">Uživatel</label>
|
|
<input
|
|
type="text"
|
|
value={username}
|
|
onChange={e => setUsername(e.target.value)}
|
|
className="w-full mb-4 px-3 py-2 rounded bg-slate-900 border border-slate-600 text-slate-100
|
|
text-sm focus:outline-none focus:border-blue-500"
|
|
autoFocus
|
|
/>
|
|
|
|
<label className="block text-xs text-slate-400 mb-1">Heslo</label>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={e => setPassword(e.target.value)}
|
|
className="w-full mb-6 px-3 py-2 rounded bg-slate-900 border border-slate-600 text-slate-100
|
|
text-sm focus:outline-none focus:border-blue-500"
|
|
/>
|
|
|
|
{error && (
|
|
<div className="mb-4 text-xs text-red-400 text-center">
|
|
Nesprávné přihlašovací údaje
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
className="w-full py-2 rounded bg-blue-600 hover:bg-blue-500 text-white text-sm
|
|
font-medium transition-colors cursor-pointer"
|
|
>
|
|
Přihlásit
|
|
</button>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|