Extract components and hooks into _components/ and _hooks/ subdirectories to reduce each page.tsx to under 500 LOC (was 1545/1383/1316). Final line counts: evidence=213, process-tasks=304, hazards=157. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
3.5 KiB
TypeScript
82 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { ProcessTask, daysUntil } from './types'
|
|
|
|
export function CalendarView({ tasks }: { tasks: ProcessTask[] }) {
|
|
const [currentMonth, setCurrentMonth] = useState(() => {
|
|
const now = new Date()
|
|
return new Date(now.getFullYear(), now.getMonth(), 1)
|
|
})
|
|
|
|
const year = currentMonth.getFullYear()
|
|
const month = currentMonth.getMonth()
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
|
const firstDayOfWeek = new Date(year, month, 1).getDay()
|
|
const startOffset = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1
|
|
|
|
const monthLabel = currentMonth.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })
|
|
|
|
const tasksByDate: Record<string, ProcessTask[]> = {}
|
|
tasks.forEach(t => {
|
|
if (!t.next_due_date) return
|
|
const key = t.next_due_date.substring(0, 10)
|
|
if (!tasksByDate[key]) tasksByDate[key] = []
|
|
tasksByDate[key].push(t)
|
|
})
|
|
|
|
const prev = () => setCurrentMonth(new Date(year, month - 1, 1))
|
|
const next = () => setCurrentMonth(new Date(year, month + 1, 1))
|
|
const today = new Date().toISOString().substring(0, 10)
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<button onClick={prev} className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">← Vorher</button>
|
|
<h3 className="text-lg font-semibold text-gray-900">{monthLabel}</h3>
|
|
<button onClick={next} className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">Weiter →</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-7 gap-px bg-gray-200 rounded-xl overflow-hidden border border-gray-200">
|
|
{['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'].map(d => (
|
|
<div key={d} className="bg-gray-50 p-2 text-xs font-medium text-gray-500 text-center">{d}</div>
|
|
))}
|
|
{Array.from({ length: startOffset }).map((_, i) => (
|
|
<div key={`empty-${i}`} className="bg-white p-2 min-h-[80px]"></div>
|
|
))}
|
|
{Array.from({ length: daysInMonth }).map((_, i) => {
|
|
const day = i + 1
|
|
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
|
const dayTasks = tasksByDate[dateStr] || []
|
|
const isToday = dateStr === today
|
|
|
|
return (
|
|
<div key={day} className={`bg-white p-2 min-h-[80px] ${isToday ? 'ring-2 ring-purple-400 ring-inset' : ''}`}>
|
|
<span className={`text-xs font-medium ${isToday ? 'text-purple-600' : 'text-gray-500'}`}>{day}</span>
|
|
<div className="mt-1 space-y-0.5">
|
|
{dayTasks.slice(0, 3).map(t => {
|
|
const days = daysUntil(t.next_due_date)
|
|
let dotColor = 'bg-gray-400'
|
|
if (t.status === 'completed') dotColor = 'bg-green-500'
|
|
else if (days !== null && days < 0) dotColor = 'bg-red-500'
|
|
else if (days !== null && days <= 7) dotColor = 'bg-orange-500'
|
|
|
|
return (
|
|
<div key={t.id} className="flex items-center gap-1" title={t.title}>
|
|
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${dotColor}`}></span>
|
|
<span className="text-[10px] text-gray-600 truncate">{t.title}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
{dayTasks.length > 3 && (
|
|
<span className="text-[10px] text-gray-400">+{dayTasks.length - 3} mehr</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|