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>
This commit is contained in:
@@ -47,6 +47,7 @@ interface ScheduleTableProps {
|
||||
showMetro: boolean
|
||||
showD8: boolean
|
||||
showSazlt: boolean
|
||||
hide162: boolean
|
||||
hiddenValues: Set<string>
|
||||
onContextMenu: (dayIdx: number, personId: string | null, x: number, y: number, selectedCells: SelectedCell[]) => void
|
||||
onTunnelContextMenu: (dayIdx: number, x: number, y: number) => void
|
||||
@@ -243,15 +244,24 @@ const PersonRow = memo(function PersonRow({
|
||||
const pmsDayIdx = -pmsMonth // negative month as special key
|
||||
const pmsData = person.data[String(pmsDayIdx)]
|
||||
const pmsValue = pmsData?.v ?? ''
|
||||
const pmsColor = pmsData?.color
|
||||
const isPmsEditing = editingCell?.personId === person.id && editingCell?.dayIdx === pmsDayIdx
|
||||
const pmsCommentKey = `${person.id}-${pmsDayIdx}`
|
||||
const hasPmsComment = cellComments.has(pmsCommentKey)
|
||||
const pmsStyle: React.CSSProperties = { width: CELL_W, height: CELL_H }
|
||||
if (pmsColor) {
|
||||
pmsStyle.backgroundColor = pmsColor
|
||||
pmsStyle.color = getContrastColor(pmsColor)
|
||||
}
|
||||
|
||||
return [
|
||||
dayCell,
|
||||
<div
|
||||
key={`pms-${pmsMonth}`}
|
||||
className="flex items-center justify-center text-xs font-mono font-bold
|
||||
border-r border-slate-600 bg-slate-700/30 cursor-text hover:bg-slate-600/40"
|
||||
style={{ width: CELL_W, height: CELL_H }}
|
||||
className={`flex items-center justify-center text-sm font-mono font-bold relative
|
||||
border-r border-slate-600 cursor-text hover:brightness-125
|
||||
${!pmsColor ? 'bg-slate-700/30' : ''}`}
|
||||
style={pmsStyle}
|
||||
onDoubleClick={() => onStartEdit(person.id, pmsDayIdx, pmsValue)}
|
||||
onClick={() => {
|
||||
if (!isPmsEditing) onStartEdit(person.id, pmsDayIdx, pmsValue)
|
||||
@@ -260,12 +270,12 @@ const PersonRow = memo(function PersonRow({
|
||||
e.preventDefault()
|
||||
onContextMenu(pmsDayIdx, person.id, e.clientX, e.clientY)
|
||||
}}
|
||||
title={`PMS ${MONTH_NAMES_SHORT[pmsMonth] ?? pmsMonth}: ${pmsValue || '—'}`}
|
||||
title={`PMS ${MONTH_NAMES_SHORT[pmsMonth] ?? pmsMonth}: ${pmsValue || '—'}${hasPmsComment ? `\n💬 ${cellComments.get(pmsCommentKey)}` : ''}`}
|
||||
>
|
||||
{isPmsEditing ? (
|
||||
<input
|
||||
autoFocus
|
||||
className="w-full h-full bg-white text-slate-900 text-center text-xs font-mono font-bold outline-none border-2 border-purple-500"
|
||||
className="w-full h-full bg-white text-slate-900 text-center text-sm font-mono font-bold outline-none border-2 border-purple-500"
|
||||
style={{ width: CELL_W, height: CELL_H }}
|
||||
value={editingCell!.value}
|
||||
onChange={(e) => onEditChange(e.target.value)}
|
||||
@@ -278,7 +288,12 @@ const PersonRow = memo(function PersonRow({
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
) : (
|
||||
<span className="truncate text-slate-300">{pmsValue}</span>
|
||||
<span className="truncate font-medium">{pmsValue}</span>
|
||||
)}
|
||||
{hasPmsComment && !isPmsEditing && (
|
||||
<span className="absolute top-0 right-0 w-0 h-0 pointer-events-none"
|
||||
style={{ borderLeft: '5px solid transparent', borderTop: '5px solid #3b82f6' }}
|
||||
/>
|
||||
)}
|
||||
</div>,
|
||||
]
|
||||
@@ -296,7 +311,7 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
||||
dayComments, cellComments,
|
||||
dragState, onCellPointerDown, onSetCell, onSetTunnelClosure,
|
||||
onSetMetroClosure, onSetD8Closure, onSetSazltClosure,
|
||||
showMetro, showD8, showSazlt, hiddenValues,
|
||||
showMetro, showD8, showSazlt, hide162, hiddenValues,
|
||||
scrollRef, onContextMenu, onTunnelContextMenu, onInfoRowContextMenu, compareData,
|
||||
} = props
|
||||
|
||||
@@ -355,7 +370,11 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
||||
return combined
|
||||
}, [dayIndex])
|
||||
|
||||
const tkbPeople = useMemo(() => people.filter(p => p.group === 'TKB'), [people])
|
||||
const HIDE_162_NAMES = ['Pauzer Libor', 'Vörös Pavel', 'Janouš Petr', 'Franek Lukáš', 'Svoboda Daniel']
|
||||
const tkbPeople = useMemo(() => {
|
||||
const all = people.filter(p => p.group === 'TKB')
|
||||
return hide162 ? all.filter(p => !HIDE_162_NAMES.includes(p.name)) : all
|
||||
}, [people, hide162])
|
||||
const itPeople = useMemo(() => people.filter(p => p.group === 'IT'), [people])
|
||||
|
||||
// ---------- PMS (Práce mimo směnu) month-end positions ----------
|
||||
@@ -796,10 +815,10 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
||||
<div className="flex-shrink-0 z-20 bg-slate-800 border-r border-slate-600" style={{ width: nameColW }}>
|
||||
{/* Header labels */}
|
||||
<div className="bg-slate-800 border-b border-slate-700 flex items-center px-3" style={{ height: 28 }}>
|
||||
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Mesic</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Měsíc</span>
|
||||
</div>
|
||||
<div className="bg-slate-800 border-b border-slate-700 flex items-center px-3" style={{ height: 24 }}>
|
||||
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Tyden</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Týden</span>
|
||||
</div>
|
||||
<div className="bg-slate-800 border-b border-slate-700 flex items-center px-3" style={{ height: 28 }}>
|
||||
<span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Den</span>
|
||||
|
||||
Reference in New Issue
Block a user