- 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>
112 lines
3.8 KiB
TypeScript
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"
|
|
>
|
|
×
|
|
</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>
|
|
)
|
|
}
|