Files
TKB_plan/web/src/ProposalModal.tsx
Docker Config Backup db56403f7c feat: dochazka Excel export + auto-reload + 162 filter
- Add dochazka Excel export using direct ZIP/XML manipulation of template
  (preserves styles.xml byte-for-byte to avoid Excel "repaired styles" warning)
- Calculate per-person stravné doplatek, transport (AUV), and indiv1
  (closure count + Janouš internet) per sichtovnice.py logic
- Filter exported people to TEMPLATE_NAMES (12 fixed template rows)
- Add server version polling + auto-reload on deploy
- Add FPD check modal for monthly hour validation
- Add "162" filter button to hide first 5 TKB people from view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:39:04 +02:00

112 lines
3.8 KiB
TypeScript

import { useState, useEffect, useRef } from 'react'
interface ProposalModalProps {
onClose: () => void
}
export function ProposalModal({ onClose }: ProposalModalProps) {
const [text, setText] = useState('')
const [author, setAuthor] = useState('')
const [proposals, setProposals] = useState('')
const [submitting, setSubmitting] = useState(false)
const [submitted, setSubmitted] = useState(false)
const overlayRef = useRef<HTMLDivElement>(null)
const textareaRef = useRef<HTMLTextAreaElement>(null)
useEffect(() => {
fetch('/api/proposals')
.then(r => r.json())
.then(d => setProposals(d.content || ''))
.catch(() => {})
}, [submitted])
useEffect(() => {
textareaRef.current?.focus()
}, [])
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handleKey)
return () => document.removeEventListener('keydown', handleKey)
}, [onClose])
const handleSubmit = async () => {
if (!text.trim()) return
setSubmitting(true)
try {
const res = await fetch('/api/proposals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: text.trim(), author: author.trim() || undefined }),
})
if (res.ok) {
setText('')
setSubmitted(s => !s)
}
} catch {
} finally {
setSubmitting(false)
}
}
return (
<div
ref={overlayRef}
className="fixed inset-0 z-[300] bg-black/50 flex items-center justify-center"
onClick={(e) => { if (e.target === overlayRef.current) onClose() }}
>
<div className="bg-slate-800 border border-slate-600 rounded-xl shadow-2xl w-full max-w-xl max-h-[80vh] flex flex-col">
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-700">
<h2 className="text-sm font-semibold text-slate-200">Návrhy na vylepšení</h2>
<button
onClick={onClose}
className="text-slate-500 hover:text-slate-300 cursor-pointer text-lg leading-none"
>
&times;
</button>
</div>
<div className="px-5 py-3 border-b border-slate-700">
<input
type="text"
value={author}
onChange={e => setAuthor(e.target.value)}
placeholder="Vase jmeno (nepovinne)"
className="w-full bg-slate-900 border border-slate-600 rounded px-3 py-1.5 text-xs text-slate-200
placeholder-slate-500 outline-none focus:border-blue-500 transition-colors mb-2"
/>
<textarea
ref={textareaRef}
value={text}
onChange={e => setText(e.target.value)}
placeholder="Popiste svuj navrh nebo problem..."
rows={3}
className="w-full bg-slate-900 border border-slate-600 rounded px-3 py-2 text-xs text-slate-200
placeholder-slate-500 outline-none focus:border-blue-500 transition-colors resize-none"
/>
<div className="flex justify-end mt-2">
<button
onClick={handleSubmit}
disabled={!text.trim() || submitting}
className="px-4 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-500
disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer transition-colors"
>
{submitting ? 'Odesilam...' : 'Odeslat'}
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto px-5 py-3">
{proposals ? (
<pre className="text-xs text-slate-300 whitespace-pre-wrap font-sans">{proposals}</pre>
) : (
<p className="text-xs text-slate-500">Zatim zadne navrhy.</p>
)}
</div>
</div>
</div>
)
}