3fefc550fe
- Django backend with DRF (clubs, wrestlers, trainers, exercises, templates, trainings, homework, locations, leistungstest) - Next.js 16 frontend with React, Shadcn UI, Tailwind - JWT authentication - Full CRUD for all entities - Calendar view for trainings - Homework management system - Leistungstest tracking
311 lines
9.3 KiB
Markdown
311 lines
9.3 KiB
Markdown
# Calendar UI Improvements - Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Enhance calendar with better colors, today highlighting, group-based event colors, hover tooltips, and click-to-day-panel
|
|
|
|
**Architecture:** Frontend-only CSS and component changes to existing calendar-view.tsx and calendar.css
|
|
|
|
**Tech Stack:** React, TypeScript, Tailwind CSS, react-big-calendar, date-fns
|
|
|
|
---
|
|
|
|
## Files Overview
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `frontend/src/components/ui/calendar-view.tsx` | Main calendar component |
|
|
| `frontend/src/components/ui/calendar.css` | Calendar styling overrides |
|
|
|
|
---
|
|
|
|
## Task 1: Enhance "Today" Highlighting
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/ui/calendar.css:68-70`
|
|
|
|
**Changes:**
|
|
- Replace today's subtle background with light green accent
|
|
- Add subtle animation pulse effect
|
|
- Make date number bold
|
|
|
|
**Steps:**
|
|
|
|
- [ ] **Step 1: Update .rbc-today styling**
|
|
Replace current implementation with light green accent
|
|
|
|
```css
|
|
.rbc-today {
|
|
background: hsl(142, 76%, 96%);
|
|
border: 2px solid hsl(142, 76%, 45%);
|
|
border-radius: 0.5rem;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Add today date cell bold styling**
|
|
Make today's date number more prominent
|
|
|
|
```css
|
|
.rbc-today .rbc-date-cell {
|
|
font-weight: 700;
|
|
color: hsl(142, 76%, 30%);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Test in browser**
|
|
Navigate to /trainings and verify today is highlighted
|
|
|
|
---
|
|
|
|
## Task 2: Group-Based Event Colors
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/ui/calendar.css:87-94`
|
|
- Modify: `frontend/src/components/ui/calendar-view.tsx:114-123`
|
|
|
|
**Changes:**
|
|
- Kids: Blue (#3B82F6) with 15% opacity background
|
|
- Youth: Purple (#8B5CF6) with 15% opacity background
|
|
- Adults: Orange (#F97316) with 15% opacity background
|
|
- Text in dark version of each color for readability
|
|
|
|
**Steps:**
|
|
|
|
- [ ] **Step 1: Define group color constants in calendar-view.tsx**
|
|
Add color configuration
|
|
|
|
```typescript
|
|
const groupColors = {
|
|
kids: { bg: "rgba(59, 130, 246, 0.15)", text: "#1E40AF", border: "#3B82F6" },
|
|
youth: { bg: "rgba(139, 92, 246, 0.15)", text: "#5B21B6", border: "#8B5CF6" },
|
|
adults: { bg: "rgba(249, 115, 22, 0.15)", text: "#9A3412", border: "#F97316" },
|
|
all: { bg: "rgba(100, 100, 100, 0.15)", text: "#404040", border: "#666666" },
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Update eventStyleGetter to use group colors**
|
|
Modify function at line 114
|
|
|
|
```typescript
|
|
const eventStyleGetter = useCallback((event: CalendarEvent) => {
|
|
const group = event.resource.group || "all"
|
|
const colors = groupColors[group as keyof typeof groupColors]
|
|
return {
|
|
style: {
|
|
backgroundColor: colors.bg,
|
|
borderLeft: `3px solid ${colors.border}`,
|
|
borderRadius: "0.25rem",
|
|
border: "none",
|
|
color: colors.text,
|
|
}
|
|
}
|
|
}, [])
|
|
```
|
|
|
|
- [ ] **Step 3: Update CSS for events**
|
|
Replace existing .rbc-event styles
|
|
|
|
```css
|
|
.rbc-event {
|
|
background: transparent;
|
|
border: none;
|
|
border-left: 3px solid;
|
|
border-radius: 0.25rem;
|
|
padding: 0.125rem 0.375rem;
|
|
font-size: 0.75rem;
|
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Test in browser**
|
|
Verify each group shows correct color
|
|
|
|
---
|
|
|
|
## Task 3: Hover Tooltip
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/ui/calendar-view.tsx`
|
|
- Create: `frontend/src/components/ui/calendar-tooltip.tsx` (optional, can be inline)
|
|
|
|
**Changes:**
|
|
- Show tooltip on event hover with training details
|
|
- Tooltip shows: Time, Group badge, Location, Attendee count
|
|
- Position tooltip above event
|
|
|
|
**Steps:**
|
|
|
|
- [ ] **Step 1: Add tooltip state and handlers to calendar-view.tsx**
|
|
Add state for tooltip visibility, position, and content
|
|
|
|
```typescript
|
|
const [tooltipInfo, setTooltipInfo] = useState<{
|
|
visible: boolean
|
|
x: number
|
|
y: number
|
|
content: { time: string, group: string, location: string, attendees: number }
|
|
} | null>(null)
|
|
```
|
|
|
|
- [ ] **Step 2: Create custom Event component with hover**
|
|
Replace CustomEvent at line 125
|
|
|
|
```typescript
|
|
const CustomEvent = ({ event }: { event: CalendarEvent }) => {
|
|
const [showTooltip, setShowTooltip] = useState(false)
|
|
|
|
return (
|
|
<div
|
|
className="relative"
|
|
onMouseEnter={(e) => {
|
|
setShowTooltip(true)
|
|
setTooltipInfo({
|
|
visible: true,
|
|
x: e.clientX,
|
|
y: e.clientY - 10,
|
|
content: {
|
|
time: `${event.resource.start_time} - ${event.resource.end_time}`,
|
|
group: groupConfig[event.resource.group as keyof typeof groupConfig]?.label || "",
|
|
location: event.resource.location_name || "Kein Ort",
|
|
attendees: event.resource.attendance_count || 0,
|
|
}
|
|
})
|
|
}}
|
|
onMouseLeave={() => setShowTooltip(false)}
|
|
>
|
|
<div className="flex items-center gap-1 text-xs">
|
|
<span className="font-medium">{event.resource.start_time}</span>
|
|
</div>
|
|
{showTooltip && tooltipInfo?.visible && (
|
|
<div
|
|
className="fixed z-50 bg-background border rounded-lg shadow-lg p-3 text-sm"
|
|
style={{ left: tooltipInfo.x, top: tooltipInfo.y, transform: 'translateY(-100%)' }}
|
|
>
|
|
<div className="font-medium mb-1">{tooltipInfo.content.time}</div>
|
|
<div className="flex items-center gap-2 text-muted-foreground">
|
|
<Badge className={groupConfig[event.resource.group as keyof typeof groupConfig]?.class}>
|
|
{tooltipInfo.content.group}
|
|
</Badge>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
📍 {tooltipInfo.content.location}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
👥 {tooltipInfo.content.attendees} Teilnehmer
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Test hover in browser**
|
|
Hover over training events and verify tooltip appears
|
|
|
|
---
|
|
|
|
## Task 4: Click on Day → Trainings Panel
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/ui/calendar-view.tsx:180-230`
|
|
|
|
**Changes:**
|
|
- Replace bottom popover with improved side panel or modal
|
|
- Show all trainings for selected day
|
|
- Better styling with group colors
|
|
- Click on training opens detail view
|
|
|
|
**Steps:**
|
|
|
|
- [ ] **Step 1: Import Sheet component**
|
|
Add Sheet import at top of file
|
|
|
|
```typescript
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
|
```
|
|
|
|
- [ ] **Step 2: Replace popover state with sheet state**
|
|
Change state from popoverOpen to sheetOpen
|
|
|
|
```typescript
|
|
const [selectedDaySheetOpen, setSelectedDaySheetOpen] = useState(false)
|
|
```
|
|
|
|
- [ ] **Step 3: Replace popover JSX with Sheet component**
|
|
Replace lines 180-230 with:
|
|
|
|
```typescript
|
|
<Sheet open={selectedDaySheetOpen} onOpenChange={setSelectedDaySheetOpen}>
|
|
<SheetContent side="bottom" className="h-[50vh] sm:max-w-[600px]">
|
|
<SheetHeader>
|
|
<SheetTitle>
|
|
{format(selectedDay!, "EEEE, d. MMMM yyyy", { locale: de })}
|
|
</SheetTitle>
|
|
</SheetHeader>
|
|
<div className="mt-4 space-y-3 max-h-[calc(50vh-100px)] overflow-y-auto">
|
|
{selectedDayTrainings.length === 0 ? (
|
|
<p className="text-muted-foreground text-center py-8">
|
|
Keine Trainings an diesem Tag
|
|
</p>
|
|
) : (
|
|
selectedDayTrainings.map(training => (
|
|
<div
|
|
key={training.id}
|
|
className="flex items-center justify-between p-4 rounded-xl border hover:bg-muted/50 transition-all cursor-pointer"
|
|
style={{
|
|
borderLeftWidth: 4,
|
|
borderLeftColor: groupColors[training.group as keyof typeof groupColors]?.border || "#666"
|
|
}}
|
|
onClick={() => {
|
|
onView(training)
|
|
setSelectedDaySheetOpen(false)
|
|
}}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-lg font-semibold">
|
|
{training.start_time} - {training.end_time}
|
|
</div>
|
|
<Badge
|
|
className={groupConfig[training.group as keyof typeof groupConfig]?.class}
|
|
>
|
|
{groupConfig[training.group as keyof typeof groupConfig]?.label}
|
|
</Badge>
|
|
{training.location_name && (
|
|
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
<MapPin className="h-4 w-4" />
|
|
{training.location_name}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-1 text-sm">
|
|
<Users className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">{training.attendance_count || 0}</span>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
```
|
|
|
|
- [ ] **Step 4: Update handleSelectSlot to use new state**
|
|
Change line 107-111 to set `setSelectedDaySheetOpen(true)` instead of `setPopoverOpen(true)`
|
|
|
|
- [ ] **Step 5: Test in browser**
|
|
Click on a day with trainings and verify Sheet appears
|
|
|
|
---
|
|
|
|
## Verification Checklist
|
|
|
|
After all tasks:
|
|
- [ ] Today is highlighted with light green accent and bold date
|
|
- [ ] Training events show group colors (blue/purple/orange)
|
|
- [ ] Hover on event shows tooltip with details
|
|
- [ ] Click on day opens bottom Sheet with training list
|
|
- [ ] Click on training in Sheet opens detail view
|
|
- [ ] No console errors
|
|
- [ ] Responsive on mobile
|