Files
TKB_plan/web/src/Login.tsx
Docker Config Backup 3d306e641c feat: add Kropáček Ondřej + May–Jul feature work (half-cell marks, copy/paste, exports, roles)
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>
2026-09-17 13:02:52 +02:00

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>
)
}