feat: Práce mimo směnu column + right-click menu on PMS cells
- Added PMS summary column after each month's last day - Merged header cell with rotated "Práce mimo směnu" label spanning DEN through D8 rows (no internal borders, overflow hidden) - PMS cells in person rows are editable (click/double-click) - Right-click context menu works on PMS cells (value, color, comments) - PMS values stored as negative month keys in person data - Dynamic height adjustment when info rows toggled Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BIN
screenshots/Mimo_smenu_1.png
Normal file
BIN
screenshots/Mimo_smenu_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -417,7 +417,16 @@ function ScheduleApp({ fileId, fileName, data, compareFileId, onBack, onFileName
|
|||||||
await exportMonthPdf(month, year, data.dayIndex, people, tunnelClosures, tunnelColors)
|
await exportMonthPdf(month, year, data.dayIndex, people, tunnelClosures, tunnelColors)
|
||||||
}, [data.dayIndex, people, tunnelClosures, tunnelColors])
|
}, [data.dayIndex, people, tunnelClosures, tunnelColors])
|
||||||
|
|
||||||
const contextDayInfo = contextMenu ? data.dayIndex.find(d => d.idx === contextMenu.dayIdx) : null
|
const MONTH_NAMES_FULL: Record<number, string> = {
|
||||||
|
1:'Leden',2:'Únor',3:'Březen',4:'Duben',5:'Květen',6:'Červen',
|
||||||
|
7:'Červenec',8:'Srpen',9:'Září',10:'Říjen',11:'Listopad',12:'Prosinec',
|
||||||
|
}
|
||||||
|
const contextDayInfo = contextMenu
|
||||||
|
? contextMenu.dayIdx < 0
|
||||||
|
// PMS cell — synthetic day info
|
||||||
|
? { idx: contextMenu.dayIdx, day: 0, month: -contextMenu.dayIdx, year: 2026, week: 0, weekend: false } as import('./types').DayInfo
|
||||||
|
: data.dayIndex.find(d => d.idx === contextMenu.dayIdx) ?? null
|
||||||
|
: null
|
||||||
const contextPerson = contextMenu?.personId ? people.find(p => p.id === contextMenu.personId) : null
|
const contextPerson = contextMenu?.personId ? people.find(p => p.id === contextMenu.personId) : null
|
||||||
const contextCellData = contextMenu?.personId
|
const contextCellData = contextMenu?.personId
|
||||||
? people.find(p => p.id === contextMenu.personId)?.data[String(contextMenu.dayIdx)]
|
? people.find(p => p.id === contextMenu.personId)?.data[String(contextMenu.dayIdx)]
|
||||||
|
|||||||
@@ -131,7 +131,12 @@ export function ContextMenu({
|
|||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateStr = `${dayInfo.day}.${dayInfo.month}.${dayInfo.year}`
|
const isPms = dayInfo.idx < 0
|
||||||
|
const MONTH_NAMES_CTX: Record<number, string> = {
|
||||||
|
1:'Leden',2:'Únor',3:'Březen',4:'Duben',5:'Květen',6:'Červen',
|
||||||
|
7:'Červenec',8:'Srpen',9:'Září',10:'Říjen',11:'Listopad',12:'Prosinec',
|
||||||
|
}
|
||||||
|
const dateStr = isPms ? `Práce mimo směnu — ${MONTH_NAMES_CTX[dayInfo.month] ?? dayInfo.month}` : `${dayInfo.day}.${dayInfo.month}.${dayInfo.year}`
|
||||||
const isTunnelRow = !!state.isTunnelRow
|
const isTunnelRow = !!state.isTunnelRow
|
||||||
const isInfoRow = !!state.infoRowId
|
const isInfoRow = !!state.infoRowId
|
||||||
const infoRowId = state.infoRowId
|
const infoRowId = state.infoRowId
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, useCallback, useRef, memo, useEffect } from 'react'
|
import React, { useMemo, useState, useCallback, useRef, memo, useEffect } from 'react'
|
||||||
import type { DayInfo, Person, DragState, ScheduleData } from './types'
|
import type { DayInfo, Person, DragState, ScheduleData } from './types'
|
||||||
import { getCellStyle, getCellStyleForPerson, getContrastColor } from './cellColors'
|
import { getCellStyle, getCellStyleForPerson, getContrastColor } from './cellColors'
|
||||||
import { getHolidayMap } from './holidays'
|
import { getHolidayMap } from './holidays'
|
||||||
@@ -13,6 +13,11 @@ const MONTH_NAMES: Record<number, string> = {
|
|||||||
|
|
||||||
const DAY_NAMES = ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So']
|
const DAY_NAMES = ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So']
|
||||||
|
|
||||||
|
const MONTH_NAMES_SHORT: Record<number, string> = {
|
||||||
|
1: 'Led', 2: 'Úno', 3: 'Bře', 4: 'Dub', 5: 'Kvě', 6: 'Čvn',
|
||||||
|
7: 'Čvc', 8: 'Srp', 9: 'Zář', 10: 'Říj', 11: 'Lis', 12: 'Pro',
|
||||||
|
}
|
||||||
|
|
||||||
export interface SelectedCell {
|
export interface SelectedCell {
|
||||||
personId: string
|
personId: string
|
||||||
dayIdx: number
|
dayIdx: number
|
||||||
@@ -76,6 +81,8 @@ const PersonRow = memo(function PersonRow({
|
|||||||
onCellClick,
|
onCellClick,
|
||||||
onCellDragSelectStart,
|
onCellDragSelectStart,
|
||||||
onCellDragSelectMove,
|
onCellDragSelectMove,
|
||||||
|
pmsAfterSet,
|
||||||
|
pmsMonthAtPos,
|
||||||
}: {
|
}: {
|
||||||
person: Person
|
person: Person
|
||||||
dayIndex: DayInfo[]
|
dayIndex: DayInfo[]
|
||||||
@@ -95,12 +102,14 @@ const PersonRow = memo(function PersonRow({
|
|||||||
onCellClick: (personId: string, dayIdx: number, ctrlKey: boolean, shiftKey: boolean) => void
|
onCellClick: (personId: string, dayIdx: number, ctrlKey: boolean, shiftKey: boolean) => void
|
||||||
onCellDragSelectStart: (personId: string, dayIdx: number) => void
|
onCellDragSelectStart: (personId: string, dayIdx: number) => void
|
||||||
onCellDragSelectMove: (personId: string, dayIdx: number) => void
|
onCellDragSelectMove: (personId: string, dayIdx: number) => void
|
||||||
|
pmsAfterSet: Set<number>
|
||||||
|
pmsMonthAtPos: Map<number, number>
|
||||||
}) {
|
}) {
|
||||||
const isDragging = dragState?.personId === person.id
|
const isDragging = dragState?.personId === person.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex border-b border-slate-700/50" style={{ height: CELL_H }}>
|
<div className="flex border-b border-slate-700/50" style={{ height: CELL_H }}>
|
||||||
{dayIndex.map((d) => {
|
{dayIndex.map((d, i) => {
|
||||||
const cellData = person.data[String(d.idx)]
|
const cellData = person.data[String(d.idx)]
|
||||||
const value = cellData?.v ?? ''
|
const value = cellData?.v ?? ''
|
||||||
const isColorOnly = !value && !!cellData?.color
|
const isColorOnly = !value && !!cellData?.color
|
||||||
@@ -156,7 +165,7 @@ const PersonRow = memo(function PersonRow({
|
|||||||
className += ' cursor-grab active:cursor-grabbing'
|
className += ' cursor-grab active:cursor-grabbing'
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const dayCell = (
|
||||||
<div
|
<div
|
||||||
key={d.idx}
|
key={d.idx}
|
||||||
className={className}
|
className={className}
|
||||||
@@ -226,6 +235,53 @@ const PersonRow = memo(function PersonRow({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!pmsAfterSet.has(i)) return dayCell
|
||||||
|
|
||||||
|
// PMS cell after this day (month boundary)
|
||||||
|
const pmsMonth = pmsMonthAtPos.get(i)!
|
||||||
|
const pmsDayIdx = -pmsMonth // negative month as special key
|
||||||
|
const pmsData = person.data[String(pmsDayIdx)]
|
||||||
|
const pmsValue = pmsData?.v ?? ''
|
||||||
|
const isPmsEditing = editingCell?.personId === person.id && editingCell?.dayIdx === pmsDayIdx
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
onDoubleClick={() => onStartEdit(person.id, pmsDayIdx, pmsValue)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isPmsEditing) onStartEdit(person.id, pmsDayIdx, pmsValue)
|
||||||
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onContextMenu(pmsDayIdx, person.id, e.clientX, e.clientY)
|
||||||
|
}}
|
||||||
|
title={`PMS ${MONTH_NAMES_SHORT[pmsMonth] ?? pmsMonth}: ${pmsValue || '—'}`}
|
||||||
|
>
|
||||||
|
{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"
|
||||||
|
style={{ width: CELL_W, height: CELL_H }}
|
||||||
|
value={editingCell!.value}
|
||||||
|
onChange={(e) => onEditChange(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') onEditConfirm()
|
||||||
|
if (e.key === 'Escape') onEditCancel()
|
||||||
|
}}
|
||||||
|
onBlur={onEditConfirm}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="truncate text-slate-300">{pmsValue}</span>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -302,13 +358,51 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
const tkbPeople = useMemo(() => people.filter(p => p.group === 'TKB'), [people])
|
const tkbPeople = useMemo(() => people.filter(p => p.group === 'TKB'), [people])
|
||||||
const itPeople = useMemo(() => people.filter(p => p.group === 'IT'), [people])
|
const itPeople = useMemo(() => people.filter(p => p.group === 'IT'), [people])
|
||||||
|
|
||||||
|
// ---------- PMS (Práce mimo směnu) month-end positions ----------
|
||||||
|
|
||||||
|
const monthEndPositions = useMemo(() => {
|
||||||
|
const positions: { afterDayPos: number; month: number; year: number }[] = []
|
||||||
|
for (let i = 0; i < dayIndex.length - 1; i++) {
|
||||||
|
if (dayIndex[i].month !== dayIndex[i + 1].month) {
|
||||||
|
positions.push({ afterDayPos: i, month: dayIndex[i].month, year: dayIndex[i].year })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const last = dayIndex[dayIndex.length - 1]
|
||||||
|
if (last) positions.push({ afterDayPos: dayIndex.length - 1, month: last.month, year: last.year })
|
||||||
|
return positions
|
||||||
|
}, [dayIndex])
|
||||||
|
|
||||||
|
// Set of dayIndex positions after which a PMS column appears
|
||||||
|
const pmsAfterSet = useMemo(() => new Set(monthEndPositions.map(p => p.afterDayPos)), [monthEndPositions])
|
||||||
|
|
||||||
|
// Map from dayIndex position to the month number for PMS
|
||||||
|
const pmsMonthAtPos = useMemo(() => {
|
||||||
|
const m = new Map<number, number>()
|
||||||
|
for (const p of monthEndPositions) m.set(p.afterDayPos, p.month)
|
||||||
|
return m
|
||||||
|
}, [monthEndPositions])
|
||||||
|
|
||||||
// ---------- idxToPos for drag overlay ----------
|
// ---------- idxToPos for drag overlay ----------
|
||||||
|
|
||||||
|
// Maps dayIndex array position to column position (accounting for PMS columns)
|
||||||
|
const dayPosToColPos = useMemo(() => {
|
||||||
|
const arr: number[] = []
|
||||||
|
let pmsCount = 0
|
||||||
|
for (let i = 0; i < dayIndex.length; i++) {
|
||||||
|
arr.push(i + pmsCount)
|
||||||
|
if (pmsAfterSet.has(i)) pmsCount++
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}, [dayIndex, pmsAfterSet])
|
||||||
|
|
||||||
|
// Maps dayIdx to column position for drag overlay
|
||||||
const idxToPos = useMemo(() => {
|
const idxToPos = useMemo(() => {
|
||||||
const map = new Map<number, number>()
|
const map = new Map<number, number>()
|
||||||
dayIndex.forEach((d, i) => map.set(d.idx, i))
|
for (let i = 0; i < dayIndex.length; i++) {
|
||||||
|
map.set(dayIndex[i].idx, dayPosToColPos[i])
|
||||||
|
}
|
||||||
return map
|
return map
|
||||||
}, [dayIndex])
|
}, [dayIndex, dayPosToColPos])
|
||||||
|
|
||||||
// ---------- Editing handlers ----------
|
// ---------- Editing handlers ----------
|
||||||
|
|
||||||
@@ -599,7 +693,7 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
|
|
||||||
// ---------- Render ----------
|
// ---------- Render ----------
|
||||||
|
|
||||||
const totalGridW = dayIndex.length * CELL_W
|
const totalGridW = dayIndex.length * CELL_W + monthEndPositions.length * CELL_W
|
||||||
const nameColW = 200
|
const nameColW = 200
|
||||||
|
|
||||||
// Helper: render info row label (left column)
|
// Helper: render info row label (left column)
|
||||||
@@ -622,7 +716,7 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
onStartEditFn: (dayIdx: number, currentValue: string) => void,
|
onStartEditFn: (dayIdx: number, currentValue: string) => void,
|
||||||
) => (
|
) => (
|
||||||
<div className={`flex border-b ${borderClass}`} style={{ height: 28 }}>
|
<div className={`flex border-b ${borderClass}`} style={{ height: 28 }}>
|
||||||
{dayIndex.map((d) => {
|
{dayIndex.map((d, i) => {
|
||||||
const closureVal = closures.get(d.idx) ?? ''
|
const closureVal = closures.get(d.idx) ?? ''
|
||||||
const closureColor = colors.get(d.idx)
|
const closureColor = colors.get(d.idx)
|
||||||
const isEditingThis = editingCell?.personId === rowId && editingCell?.dayIdx === d.idx
|
const isEditingThis = editingCell?.personId === rowId && editingCell?.dayIdx === d.idx
|
||||||
@@ -634,7 +728,8 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
rowBg.color = getContrastColor(closureColor)
|
rowBg.color = getContrastColor(closureColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const cells: React.ReactNode[] = []
|
||||||
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key={d.idx}
|
key={d.idx}
|
||||||
className={`flex items-center justify-center text-xs font-mono font-bold relative
|
className={`flex items-center justify-center text-xs font-mono font-bold relative
|
||||||
@@ -671,6 +766,12 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
if (pmsAfterSet.has(i)) {
|
||||||
|
cells.push(
|
||||||
|
<div key={`pms-info-${rowId}-${i}`} className="bg-slate-700/30" style={{ width: CELL_W, height: 28 }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cells
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -756,7 +857,7 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
<div
|
<div
|
||||||
key={`${m.year}-${m.month}`}
|
key={`${m.year}-${m.month}`}
|
||||||
className="flex items-center justify-center text-xs font-semibold text-slate-300 border-r border-slate-600 bg-slate-800"
|
className="flex items-center justify-center text-xs font-semibold text-slate-300 border-r border-slate-600 bg-slate-800"
|
||||||
style={{ width: m.count * CELL_W }}
|
style={{ width: (m.count + 1) * CELL_W }}
|
||||||
>
|
>
|
||||||
{MONTH_NAMES[m.month]} {m.year}
|
{MONTH_NAMES[m.month]} {m.year}
|
||||||
</div>
|
</div>
|
||||||
@@ -765,25 +866,71 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
|
|
||||||
{/* Week header row */}
|
{/* Week header row */}
|
||||||
<div className="flex border-b border-slate-700" style={{ height: 24 }}>
|
<div className="flex border-b border-slate-700" style={{ height: 24 }}>
|
||||||
{weekSpans.map((w, i) => (
|
{weekSpans.map((w, i) => {
|
||||||
<div
|
// Check if any PMS column falls inside this week span
|
||||||
key={`w-${i}`}
|
const spanEnd = w.startIdx + w.count - 1
|
||||||
className="flex items-center justify-center text-[10px] text-slate-400 border-r border-slate-700/50 bg-slate-800/80"
|
const pmsCols: number[] = []
|
||||||
style={{ width: w.count * CELL_W }}
|
for (let di = w.startIdx; di <= spanEnd; di++) {
|
||||||
>
|
if (pmsAfterSet.has(di)) pmsCols.push(di)
|
||||||
{w.week}
|
}
|
||||||
</div>
|
if (pmsCols.length === 0) {
|
||||||
))}
|
return (
|
||||||
|
<div
|
||||||
|
key={`w-${i}`}
|
||||||
|
className="flex items-center justify-center text-[10px] text-slate-400 border-r border-slate-700/50 bg-slate-800/80"
|
||||||
|
style={{ width: w.count * CELL_W }}
|
||||||
|
>
|
||||||
|
{w.week}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Split span around PMS columns
|
||||||
|
const fragments: React.ReactNode[] = []
|
||||||
|
let fragStart = w.startIdx
|
||||||
|
for (const pmsPos of pmsCols) {
|
||||||
|
const fragCount = pmsPos - fragStart + 1
|
||||||
|
if (fragCount > 0) {
|
||||||
|
fragments.push(
|
||||||
|
<div
|
||||||
|
key={`w-${i}-f-${fragStart}`}
|
||||||
|
className="flex items-center justify-center text-[10px] text-slate-400 border-r border-slate-700/50 bg-slate-800/80"
|
||||||
|
style={{ width: fragCount * CELL_W }}
|
||||||
|
>
|
||||||
|
{w.week}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fragments.push(
|
||||||
|
<div key={`w-${i}-pms-${pmsPos}`} className="bg-slate-700/30" style={{ width: CELL_W, height: 24 }} />
|
||||||
|
)
|
||||||
|
fragStart = pmsPos + 1
|
||||||
|
}
|
||||||
|
// Remaining fragment after last PMS
|
||||||
|
const remaining = spanEnd - fragStart + 1
|
||||||
|
if (remaining > 0) {
|
||||||
|
fragments.push(
|
||||||
|
<div
|
||||||
|
key={`w-${i}-f-${fragStart}`}
|
||||||
|
className="flex items-center justify-center text-[10px] text-slate-400 border-r border-slate-700/50 bg-slate-800/80"
|
||||||
|
style={{ width: remaining * CELL_W }}
|
||||||
|
>
|
||||||
|
{w.week}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <React.Fragment key={`w-${i}`}>{fragments}</React.Fragment>
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Day number header row */}
|
{/* Day number header row (DEN) — PMS rotated text appears here */}
|
||||||
<div className="flex border-b border-slate-700" style={{ height: 28 }}>
|
<div className="flex border-b border-slate-700" style={{ height: 28 }}>
|
||||||
{dayIndex.map((d) => {
|
{dayIndex.map((d, i) => {
|
||||||
const comment = dayComments.get(d.idx)
|
const comment = dayComments.get(d.idx)
|
||||||
const holidayName = holidays.get(`${d.year}-${d.month}-${d.day}`)
|
const holidayName = holidays.get(`${d.year}-${d.month}-${d.day}`)
|
||||||
const isHoliday = !!holidayName
|
const isHoliday = !!holidayName
|
||||||
const isOff = d.weekend || isHoliday
|
const isOff = d.weekend || isHoliday
|
||||||
return (
|
const cells: React.ReactNode[] = []
|
||||||
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key={d.idx}
|
key={d.idx}
|
||||||
className={`flex items-center justify-center text-[10px] font-mono relative
|
className={`flex items-center justify-center text-[10px] font-mono relative
|
||||||
@@ -799,6 +946,39 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
if (pmsAfterSet.has(i)) {
|
||||||
|
cells.push(
|
||||||
|
<div
|
||||||
|
key={`pms-den-${i}`}
|
||||||
|
className="relative"
|
||||||
|
style={{ width: CELL_W, height: 28, zIndex: 15 }}
|
||||||
|
>
|
||||||
|
{/* Merged cell overlay covering DEN + DEN T. + all info rows */}
|
||||||
|
<div
|
||||||
|
className="absolute flex items-center justify-center border-l border-r border-slate-600 overflow-hidden"
|
||||||
|
style={{
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: CELL_W,
|
||||||
|
height: 28 + 24 + 28 + (showSazlt ? 28 : 0) + (showMetro ? 28 : 0) + (showD8 ? 28 : 0),
|
||||||
|
backgroundColor: '#283040',
|
||||||
|
zIndex: 15,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="text-[10px] font-bold text-slate-300 pointer-events-none select-none"
|
||||||
|
style={{
|
||||||
|
writingMode: 'vertical-rl',
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Práce mimo směnu
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cells
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -807,7 +987,8 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
{dayIndex.map((d, i) => {
|
{dayIndex.map((d, i) => {
|
||||||
const isHoliday = holidays.has(`${d.year}-${d.month}-${d.day}`)
|
const isHoliday = holidays.has(`${d.year}-${d.month}-${d.day}`)
|
||||||
const isOff = d.weekend || isHoliday
|
const isOff = d.weekend || isHoliday
|
||||||
return (
|
const cells: React.ReactNode[] = []
|
||||||
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key={d.idx}
|
key={d.idx}
|
||||||
className={`flex items-center justify-center text-[9px] font-mono
|
className={`flex items-center justify-center text-[9px] font-mono
|
||||||
@@ -819,6 +1000,12 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
{dayNames[i]}
|
{dayNames[i]}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
if (pmsAfterSet.has(i)) {
|
||||||
|
cells.push(
|
||||||
|
<div key={`pms-dayname-${i}`} className="bg-slate-700/30" style={{ width: CELL_W, height: 24 }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cells
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -870,6 +1057,8 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
onCellClick={onCellClick}
|
onCellClick={onCellClick}
|
||||||
onCellDragSelectStart={onCellDragSelectStart}
|
onCellDragSelectStart={onCellDragSelectStart}
|
||||||
onCellDragSelectMove={onCellDragSelectMove}
|
onCellDragSelectMove={onCellDragSelectMove}
|
||||||
|
pmsAfterSet={pmsAfterSet}
|
||||||
|
pmsMonthAtPos={pmsMonthAtPos}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -898,12 +1087,15 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
onCellClick={onCellClick}
|
onCellClick={onCellClick}
|
||||||
onCellDragSelectStart={onCellDragSelectStart}
|
onCellDragSelectStart={onCellDragSelectStart}
|
||||||
onCellDragSelectMove={onCellDragSelectMove}
|
onCellDragSelectMove={onCellDragSelectMove}
|
||||||
|
pmsAfterSet={pmsAfterSet}
|
||||||
|
pmsMonthAtPos={pmsMonthAtPos}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Compare ghost blocks */}
|
{/* Compare ghost blocks */}
|
||||||
{ghostBlocks && ghostBlocks.map((gb, i) => {
|
{ghostBlocks && ghostBlocks.map((gb, i) => {
|
||||||
const left = gb.startPos * CELL_W
|
const colPos = dayPosToColPos[gb.startPos] ?? gb.startPos
|
||||||
|
const left = colPos * CELL_W
|
||||||
const tkbCount = tkbPeople.length
|
const tkbCount = tkbPeople.length
|
||||||
let top: number
|
let top: number
|
||||||
if (gb.personIdx < tkbCount) {
|
if (gb.personIdx < tkbCount) {
|
||||||
@@ -911,7 +1103,10 @@ export function ScheduleTable(props: ScheduleTableProps) {
|
|||||||
} else {
|
} else {
|
||||||
top = (1 + tkbCount + 1 + (gb.personIdx - tkbCount)) * CELL_H
|
top = (1 + tkbCount + 1 + (gb.personIdx - tkbCount)) * CELL_H
|
||||||
}
|
}
|
||||||
const width = gb.length * CELL_W
|
// Ghost block width: count columns between start and end, including any PMS columns in between
|
||||||
|
const endDayPos = gb.startPos + gb.length - 1
|
||||||
|
const endColPos = dayPosToColPos[endDayPos] ?? endDayPos
|
||||||
|
const width = (endColPos - colPos + 1) * CELL_W
|
||||||
const cellStyle = getCellStyle(gb.value)
|
const cellStyle = getCellStyle(gb.value)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user