Files
accounted/extensions/general/calendar/components/CalendarWeekView.tsx
T
Jakob Wennberg 091d043c85 feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements
Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:05:49 +01:00

158 lines
5.3 KiB
TypeScript

'use client'
import { Invoice, Deadline } from '@/types'
import { cn } from '@/lib/utils'
import {
getWeekDays,
formatWeekDayHeader,
formatDateISO,
isSameDay,
isInvoiceOverdue,
isDeadlineOverdue,
getTimeSlots,
} from '@/lib/calendar/utils'
interface CalendarWeekViewProps {
currentDate: Date
invoices: Invoice[]
deadlines: Deadline[]
onDayClick: (date: Date) => void
}
export function CalendarWeekView({
currentDate,
invoices,
deadlines,
onDayClick,
}: CalendarWeekViewProps) {
const weekDays = getWeekDays(currentDate)
const timeSlots = getTimeSlots(8, 20)
const today = new Date()
// Group invoices and deadlines by date for quick lookup
const getItemsForDate = (date: Date) => {
const dateStr = formatDateISO(date)
const dayInvoices = invoices.filter(inv => inv.due_date === dateStr)
const dayDeadlines = deadlines.filter(d => d.due_date === dateStr)
return { invoices: dayInvoices, deadlines: dayDeadlines }
}
return (
<div className="border rounded-lg overflow-hidden">
{/* Header row with day names and dates */}
<div className="grid grid-cols-[60px_repeat(7,1fr)] bg-muted border-b">
<div className="p-2 border-r" /> {/* Empty corner cell */}
{weekDays.map((date, index) => {
const isToday = isSameDay(date, today)
return (
<button
key={index}
onClick={() => onDayClick(date)}
className={cn(
'p-2 text-center border-r last:border-r-0 hover:bg-muted/80 transition-colors',
isToday && 'bg-primary/10'
)}
>
<div className={cn(
'text-sm font-medium',
isToday && 'text-primary font-bold'
)}>
{formatWeekDayHeader(date)}
</div>
</button>
)
})}
</div>
{/* All-day events section */}
<div className="grid grid-cols-[60px_repeat(7,1fr)] border-b bg-muted/30">
<div className="p-2 border-r text-xs text-muted-foreground">
Heldag
</div>
{weekDays.map((date, index) => {
const items = getItemsForDate(date)
const isToday = isSameDay(date, today)
// Filter to only show non-timed items (all invoices and deadlines without times are "all-day")
const allDayInvoices = items.invoices.filter(inv =>
inv.status !== 'paid' && inv.status !== 'cancelled' && inv.status !== 'credited'
)
const allDayDeadlines = items.deadlines.filter(d => !d.is_completed)
return (
<div
key={index}
className={cn(
'min-h-[60px] p-1 border-r last:border-r-0',
isToday && 'bg-primary/5'
)}
>
{/* Invoice indicators */}
{allDayInvoices.map((invoice) => (
<div
key={invoice.id}
className={cn(
'text-xs p-1 mb-1 rounded truncate',
isInvoiceOverdue(invoice)
? 'bg-destructive/20 text-destructive'
: 'bg-primary/20 text-primary'
)}
title={`Faktura ${invoice.invoice_number} - ${invoice.customer?.name || ''}`}
>
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-full bg-current flex-shrink-0" />
<span className="truncate">F#{invoice.invoice_number}</span>
</div>
</div>
))}
{/* Deadline indicators */}
{allDayDeadlines.map((deadline) => (
<div
key={deadline.id}
className={cn(
'text-xs p-1 mb-1 rounded truncate',
isDeadlineOverdue(deadline)
? 'bg-destructive/20 text-destructive'
: 'bg-warning/20 text-warning-foreground'
)}
title={deadline.title}
>
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-sm bg-current flex-shrink-0" />
<span className="truncate">{deadline.title}</span>
</div>
</div>
))}
</div>
)
})}
</div>
{/* Time grid */}
<div className="max-h-[500px] overflow-y-auto">
{timeSlots.map((time, _timeIndex) => (
<div key={time} className="grid grid-cols-[60px_repeat(7,1fr)] border-b last:border-b-0">
<div className="p-2 border-r text-xs text-muted-foreground text-right pr-2">
{time}
</div>
{weekDays.map((date, dayIndex) => {
const isToday = isSameDay(date, today)
return (
<button
key={dayIndex}
onClick={() => onDayClick(date)}
className={cn(
'min-h-[40px] border-r last:border-r-0 hover:bg-muted/50 transition-colors',
isToday && 'bg-primary/5'
)}
/>
)
})}
</div>
))}
</div>
</div>
)
}