feat(calendar-week-view): Wochen-Kalender für Time-Entries [tsc:fail]

This commit is contained in:
Dennis (via Claude+Gemma) 2026-05-23 05:33:40 +02:00
parent d146a68174
commit 276ed8c798
3 changed files with 126 additions and 2 deletions

View File

@ -2,6 +2,9 @@
"completed_features": [ "completed_features": [
"password-change" "password-change"
], ],
"current_feature": "audit-log", "current_feature": "calendar-week-view",
"started_at": "2026-05-23T05:30:16.203066" "started_at": "2026-05-23T05:30:16.203066",
"attempted_features": [
"audit-log"
]
} }

View File

@ -678,3 +678,17 @@ src/routes/audit-log.ts(3,10): error TS2724: '"../db/schema"' has no exported me
undefined undefined
/home/dark/Developer/EmberClone/apps/api: /home/dark/Developer/EmberClone/apps/api:
ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command failed with exit code 2: tsc --noEmit -p tsconfig.json ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command failed with exit code 2: tsc --noEmit -p tsconfig.json
- `05:33:03` **INFO** Committed feature audit-log
- `05:33:03` **INFO** Pushed: rc=0
## Phase-3 Feature: calendar-week-view (2026-05-23 05:33:03)
- `05:33:03` **INFO** Description: Wochen-Kalender für Time-Entries
- `05:33:03` **INFO** Generating apps/web/src/pages/Calendar.tsx (Calendar-Page mit Week-View. 7-Spalten-Grid (Mon-Sun mit aktueller Woc…)
- `05:33:38` **INFO** wrote 4226 chars in 34.7s (attempt 1)
- `05:33:38` **INFO** Running tsc --noEmit on api…
- `05:33:40` **WARN** tsc errors:
src/routes/audit-log.ts(3,10): error TS2724: '"../db/schema"' has no exported member named 'auditLogs'. Did you mean 'auditLog'?
undefined
/home/dark/Developer/EmberClone/apps/api:
ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command failed with exit code 2: tsc --noEmit -p tsconfig.json

View File

@ -0,0 +1,107 @@
import React, { useState, useMemo } from "react"
import { useQuery } from "@tanstack/react-query"
import { api } from "../lib/api"
import { format, startOfWeek, addDays, endOfWeek, eachDayOfInterval, isSameDay } from "date-fns"
import { ChevronLeft, ChevronRight } from "lucide-react"
export default function CalendarPage() {
const [currentWeekStart, setCurrentWeekStart] = useState(startOfWeek(new Date(), { weekStartsOn: 1 }))
const weekEnd = endOfWeek(currentWeekStart, { weekStartsOn: 1 })
const days = eachDayOfInterval({ start: currentWeekStart, end: weekEnd })
const { data: entries = [], isLoading } = useQuery({
queryKey: ["time-entries", currentWeekStart.toISOString()],
queryFn: () => api.listTimeEntries({
from: currentWeekStart.toISOString(),
to: weekEnd.toISOString()
}),
})
const navigateWeek = (direction: "prev" | "next") => {
const offset = direction === "prev" ? -7 : 7
setCurrentWeekStart(prev => addDays(prev, offset))
}
const dayData = useMemo(() => {
return days.map(day => {
const dayEntries = entries.filter(e => {
const start = new Date(e.startTime)
return isSameDay(start, day)
})
const totalHours = dayEntries.reduce((sum, e) => {
const start = new Date(e.startTime)
const end = e.endTime ? new Date(e.endTime) : new Date()
return sum + (end.getTime() - start.getTime()) / (1000 * 60 * 60)
}, 0)
return { day, entries: dayEntries, totalHours }
})
}, [days, entries])
if (isLoading) return <div className="p-8 text-center">Loading calendar...</div>
return (
<div className="flex flex-col h-full p-4 gap-4">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold">Calendar</h1>
<div className="flex items-center gap-4">
<div className="text-sm font-medium">
{format(currentWeekStart, "MMM d")} - {format(weekEnd, "MMM d, yyyy")}
</div>
<div className="flex gap-1">
<button
onClick={() => navigateWeek("prev")}
className="p-2 hover:bg-gray-100 rounded-full border transition-colors"
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
onClick={() => navigateWeek("next")}
className="p-2 hover:bg-gray-100 rounded-full border transition-colors"
>
<ChevronRight className="w-5 h-5" />
</button>
</div>
</div>
</div>
<div className="grid grid-cols-7 gap-2 flex-1 min-h-0">
{dayData.map(({ day, entries, totalHours }) => (
<div key={day.toISOString()} className="flex flex-col bg-gray-50 rounded-lg border border-gray-200 overflow-hidden">
<div className="p-2 text-center border-b bg-white">
<div className="text-xs uppercase text-gray-500 font-semibold">
{format(day, "EEE")}
</div>
<div className="text-lg font-bold">
{format(day, "d")}
</div>
<div className="text-xs font-medium text-blue-600">
{totalHours.toFixed(2)}h
</div>
</div>
<div className="flex-1 overflow-y-auto p-2 space-y-2">
{entries.length === 0 ? (
<div className="text-center text-gray-400 text-xs py-4 italic">No entries</div>
) : (
entries.map(entry => (
<div key={entry.id} className="p-2 bg-white border rounded text-xs shadow-sm hover:border-blue-300 transition-colors">
<div className="font-bold truncate" title={entry.description}>
{entry.description}
</div>
<div className="text-gray-500 mt-1">
{format(new Date(entry.startTime), "HH:mm")} -
{entry.endTime ? format(new Date(entry.endTime), " HH:mm") : " ..."}
</div>
</div>
))
)}
</div>
</div>
))}
</div>
</div>
)
}