Initial commit

This commit is contained in:
Richard
2026-07-31 10:08:13 +02:00
parent 652943fb7e
commit a69f31649c
7192 changed files with 924319 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5 */
/* Hallmark · component: calendar-page · genre: editorial · theme: Atelier (Dark & Light) · studied-DNA: https://inihaus.de */
import React, { useState } from 'react';
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, ArrowRight, MapPin, Clock, Layers } from 'lucide-react';
export default function CalendarPage({ events, onSelectEvent, branding }) {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedCalendarEvent, setSelectedCalendarEvent] = useState(null);
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
const monthNames = [
'JANUAR', 'FEBRUAR', 'MÄRZ', 'APRIL', 'MAI', 'JUNI',
'JULI', 'AUGUST', 'SEPTEMBER', 'OKTOBER', 'NOVEMBER', 'DEZEMBER'
];
const prevMonth = () => setCurrentDate(new Date(year, month - 1, 1));
const nextMonth = () => setCurrentDate(new Date(year, month + 1, 1));
const todayMonth = () => setCurrentDate(new Date());
const firstDayOfMonth = new Date(year, month, 1).getDay();
const startOffset = (firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1);
const daysInMonth = new Date(year, month + 1, 0).getDate();
const formatDateString = (day) => {
const mStr = String(month + 1).padStart(2, '0');
const dStr = String(day).padStart(2, '0');
return `${year}-${mStr}-${dStr}`;
};
const getEventsForDay = (day) => {
const dateStr = formatDateString(day);
return events.filter((evt) => dateStr >= evt.start_date && dateStr <= evt.end_date);
};
const primaryColor = branding?.primary_color || 'var(--brand-primary)';
return (
<div className="space-y-6 max-w-5xl mx-auto animate-in fade-in duration-200">
{/* Calendar Masthead Bar */}
<div className="hallmark-panel rounded-sm p-6 sm:p-8 border border-grid flex flex-col sm:flex-row sm:items-center justify-between gap-4 font-mono">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-sm bg-subtle text-main border border-grid flex items-center justify-center font-bold">
<CalendarIcon className="w-4 h-4 text-amber-500" />
</div>
<div>
<h2 className="font-serif text-3xl sm:text-4xl font-bold uppercase tracking-tight text-main leading-none">
{monthNames[month]} {year}
</h2>
<p className="text-[10px] text-muted uppercase tracking-widest mt-1">
INITIATIVE E.V. TERMIN- & SCHICHTÜBERSICHT
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={todayMonth}
className="px-3.5 py-1.5 rounded-sm text-xs font-mono font-bold bg-surface hover:bg-surface-hover text-main border border-grid transition"
>
[ HEUTE ]
</button>
<button
onClick={prevMonth}
className="p-1.5 rounded-sm text-muted hover:text-main bg-surface border border-grid transition"
title="Vorheriger Monat"
>
<ChevronLeft className="w-4 h-4" />
</button>
<button
onClick={nextMonth}
className="p-1.5 rounded-sm text-muted hover:text-main bg-surface border border-grid transition"
title="Nächster Monat"
>
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
{/* Calendar Grid Container */}
<div className="hallmark-panel rounded-sm border border-grid overflow-hidden">
{/* Days Header */}
<div className="grid grid-cols-7 bg-subtle border-b border-grid text-center text-xs font-mono text-muted font-bold py-3 uppercase">
<div>MO</div><div>DI</div><div>MI</div><div>DO</div><div>FR</div><div>SA</div><div>SO</div>
</div>
{/* Days Cells */}
<div className="grid grid-cols-7 auto-rows-fr divide-x divide-y divide-grid bg-surface text-xs font-mono">
{Array.from({ length: startOffset }).map((_, i) => (
<div key={`offset-${i}`} className="min-h-[100px] sm:min-h-[110px] p-2 bg-subtle/50 text-muted opacity-30"></div>
))}
{Array.from({ length: daysInMonth }).map((_, i) => {
const dayNum = i + 1;
const dayEvents = getEventsForDay(dayNum);
const isToday =
new Date().getFullYear() === year &&
new Date().getMonth() === month &&
new Date().getDate() === dayNum;
return (
<div
key={`day-${dayNum}`}
className={`min-h-[100px] sm:min-h-[110px] p-2 flex flex-col justify-between transition ${
isToday ? 'bg-surface-hover/80 font-bold' : 'hover:bg-surface-hover/50'
}`}
>
<div className="flex items-center justify-between mb-1">
<span
className={`w-5 h-5 rounded-sm flex items-center justify-center font-mono text-xs ${
isToday
? 'bg-main text-paper font-bold shadow-sm'
: 'text-muted'
}`}
>
{dayNum}
</span>
{dayEvents.length > 0 && (
<span className="text-[9px] font-mono text-muted">
{dayEvents.length} {dayEvents.length === 1 ? 'Event' : 'Events'}
</span>
)}
</div>
<div className="space-y-1.5 overflow-y-auto max-h-[70px] no-scrollbar">
{dayEvents.map((evt) => {
const isSelected = selectedCalendarEvent?.id === evt.id;
return (
<button
key={`evt-${evt.id}`}
onClick={() => setSelectedCalendarEvent(evt)}
style={{
backgroundColor: isSelected ? primaryColor : `${primaryColor}15`,
borderColor: `${primaryColor}40`,
color: isSelected ? '#ffffff' : primaryColor
}}
className={`w-full text-left px-2 py-1 rounded-sm text-[10px] font-mono font-bold border truncate block hover:brightness-110 transition shadow-sm ${
evt.is_active === false ? 'opacity-50 line-through' : ''
}`}
>
{evt.title}
</button>
);
})}
</div>
</div>
);
})}
</div>
</div>
{/* Selected Event Detail Drawer */}
{selectedCalendarEvent && (
<div className="hallmark-panel rounded-sm p-6 border border-grid flex flex-col sm:flex-row sm:items-center justify-between gap-4 font-mono animate-in fade-in duration-200">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2 text-xs text-muted">
<span className="flex items-center gap-1 bg-subtle px-2 py-0.5 rounded-sm border border-grid font-bold text-main">
<Clock className="w-3 h-3 text-muted" />
{new Date(selectedCalendarEvent.start_date).toLocaleDateString('de-DE')} {new Date(selectedCalendarEvent.end_date).toLocaleDateString('de-DE')}
</span>
{selectedCalendarEvent.location && (
<span className="flex items-center gap-1 text-muted">
<MapPin className="w-3 h-3" />
{selectedCalendarEvent.location}
</span>
)}
</div>
<h3 className="font-serif text-2xl font-bold uppercase text-main">
{selectedCalendarEvent.title}
</h3>
{selectedCalendarEvent.description && (
<p className="text-xs font-sans text-muted max-w-xl">
{selectedCalendarEvent.description}
</p>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
<button
onClick={() => onSelectEvent(selectedCalendarEvent.id)}
style={{ backgroundColor: primaryColor }}
className="px-5 py-2 rounded-sm text-xs font-mono font-bold text-white transition flex items-center gap-1.5 hover:brightness-110 shadow-sm"
>
SCHICHTPLAN ÖFFNEN <ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
)}
</div>
);
}