Drei gemeldete Fehler, alle nachgestellt: 1. Ein vorhandener Termin ließ sich nicht öffnen. Der Klick auf den Termin reichte an die Tageszelle darunter durch, die sofort 'Neuer Termin' öffnete und den Bearbeiten-Dialog überschrieb. Fehlendes stopPropagation. 2. Klick auf den 3. legte den Termin am 2. an. Ein ganztägiger Termin ist ein *Datum*, kein Zeitpunkt - die Oberfläche rechnete ihn über new Date(...).toISOString() trotzdem in UTC um, was östlich von UTC den Vortag ergibt. Nachgestellt und bestätigt: DTSTART landete als 20260902 statt 20260903. Ganztägige Termine laufen jetzt über dates.ts (allDayISO/eventDay), damit Dialog und Raster dieselbe Rechnung benutzen. Nebenbefund mitgefixt: DTEND war gleich DTSTART, was RFC 5545 verbietet (DTEND ist exklusiv) - ein eintägiger Termin am 3. schreibt jetzt DTEND 20260904, nach außen bleibt die Schnittstelle beim letzten belegten Tag. 3. 'Fühlt sich starr an': Wochenansicht ist jetzt ein echtes Stundenraster (Klick trifft die Stunde statt Mitternacht, ganztägige in eigener Zeile, überlappende Termine nebeneinander, Jetzt-Linie), mehrtägige Termine stehen in jeder Zelle statt nur am Starttag, neue Termine schlagen die nächste volle Stunde bzw. 9 Uhr vor, Kalenderauswahl im Termindialog, Escape schließt, Hover-Rückmeldung, Kalenderliste auf schmalen Schirmen einklappbar. Außerdem: ein neu angelegter Kalender blieb unsichtbar, weil er in keiner Sichtbar-Liste stand - die Liste merkt sich jetzt die ausgeblendeten statt der sichtbaren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
85 lines
3.5 KiB
TypeScript
85 lines
3.5 KiB
TypeScript
import { useEffect, useRef, useState, type FormEvent } from 'react'
|
|
import { useOverlayDismiss } from '../../hooks/useOverlayDismiss'
|
|
import type { CalendarItem } from '../../api/client'
|
|
|
|
const palette = ['#4285f4', '#ea4335', '#fbbc04', '#34a853', '#a142f4', '#ff6d00', '#00acc1', '#607d8b']
|
|
|
|
interface Props {
|
|
calendar?: CalendarItem
|
|
onSave: (name: string, color: string) => void
|
|
onCancel: () => void
|
|
}
|
|
|
|
export default function CalendarDialog({ calendar, onSave, onCancel }: Props) {
|
|
const overlay = useOverlayDismiss(onCancel)
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
const [name, setName] = useState(calendar?.name ?? '')
|
|
const [color, setColor] = useState(calendar?.color ?? palette[0])
|
|
|
|
useEffect(() => {
|
|
function onKey(e: KeyboardEvent) { if (e.key === 'Escape') onCancel() }
|
|
window.addEventListener('keydown', onKey)
|
|
return () => window.removeEventListener('keydown', onKey)
|
|
}, [onCancel])
|
|
|
|
function handleSubmit(e: FormEvent) {
|
|
e.preventDefault()
|
|
if (!name.trim()) return
|
|
onSave(name.trim(), color)
|
|
}
|
|
|
|
return (
|
|
<div style={{ position: 'fixed', inset: 0, zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.6)', padding: 16 }} {...overlay}>
|
|
<form onSubmit={handleSubmit} style={{
|
|
background: 'var(--color-surface)', border: '1px solid var(--color-border)',
|
|
borderRadius: 14, padding: 24, width: 340, maxWidth: '100%',
|
|
}}>
|
|
<h2 style={{ margin: '0 0 16px', fontSize: 16, fontWeight: 600, color: 'var(--color-text)' }}>
|
|
{calendar ? 'Kalender bearbeiten' : 'Neuer Kalender'}
|
|
</h2>
|
|
|
|
<label style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 4, display: 'block' }}>Name</label>
|
|
<input
|
|
ref={inputRef} type="text" value={name} onChange={e => setName(e.target.value)}
|
|
autoFocus
|
|
style={{
|
|
width: '100%', boxSizing: 'border-box', marginBottom: 16,
|
|
background: 'var(--color-surface-2)', border: '1px solid var(--color-border)',
|
|
borderRadius: 8, padding: '8px 10px', fontSize: 13.5, color: 'var(--color-text)',
|
|
}}
|
|
/>
|
|
|
|
<label style={{ fontSize: 12, color: 'var(--color-text-muted)', marginBottom: 8, display: 'block' }}>Farbe</label>
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 20, flexWrap: 'wrap' }}>
|
|
{palette.map(c => (
|
|
<button
|
|
key={c} type="button" onClick={() => setColor(c)}
|
|
aria-label={c}
|
|
style={{
|
|
width: 28, height: 28, borderRadius: '50%', background: c, cursor: 'pointer',
|
|
border: color === c ? '2px solid var(--color-text)' : '2px solid transparent',
|
|
boxShadow: color === c ? '0 0 0 2px var(--color-surface)' : 'none',
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
|
<button type="button" onClick={onCancel} style={{
|
|
padding: '8px 16px', borderRadius: 8, border: '1px solid var(--color-border)',
|
|
background: 'transparent', color: 'var(--color-text-muted)', fontSize: 13, cursor: 'pointer',
|
|
}}>
|
|
Abbrechen
|
|
</button>
|
|
<button type="submit" style={{
|
|
padding: '8px 16px', borderRadius: 8, border: 'none',
|
|
background: 'var(--color-primary)', color: '#fff', fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
}}>
|
|
Speichern
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|