Files
accounted/extensions/general/calendar/components/CalendarDayView.tsx
T
2026-02-24 11:33:22 +01:00

246 lines
9.2 KiB
TypeScript

'use client'
import { Invoice, Deadline } from '@/types'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Plus } from 'lucide-react'
import {
formatDateISO,
isSameDay,
isInvoiceOverdue,
isDeadlineOverdue,
getTimeSlots,
formatDayViewHeader,
DEADLINE_TYPE_LABELS,
PRIORITY_LABELS,
} from '@/lib/calendar/utils'
interface CalendarDayViewProps {
date: Date
invoices: Invoice[]
deadlines: Deadline[]
onAddDeadline: (date: Date) => void
}
export function CalendarDayView({
date,
invoices,
deadlines,
onAddDeadline,
}: CalendarDayViewProps) {
const timeSlots = getTimeSlots(8, 20)
const today = new Date()
const isToday = isSameDay(date, today)
const dateStr = formatDateISO(date)
// Filter items for this day
const dayInvoices = invoices.filter(inv => inv.due_date === dateStr)
const dayDeadlines = deadlines.filter(d => d.due_date === dateStr)
// Categorize items
const activeInvoices = dayInvoices.filter(inv =>
inv.status !== 'paid' && inv.status !== 'cancelled' && inv.status !== 'credited'
)
const paidInvoices = dayInvoices.filter(inv => inv.status === 'paid')
const activeDeadlines = dayDeadlines.filter(d => !d.is_completed)
const completedDeadlines = dayDeadlines.filter(d => d.is_completed)
return (
<div className="border rounded-lg overflow-hidden">
{/* Header */}
<div className={cn(
'p-4 border-b flex items-center justify-between',
isToday && 'bg-primary/10'
)}>
<h3 className={cn(
'text-lg font-semibold capitalize',
isToday && 'text-primary'
)}>
{formatDayViewHeader(date)}
{isToday && <span className="ml-2 text-sm font-normal text-muted-foreground">(Idag)</span>}
</h3>
<Button
variant="outline"
size="sm"
onClick={() => onAddDeadline(date)}
>
<Plus className="h-4 w-4 mr-1" />
Lägg till deadline
</Button>
</div>
{/* All-day events section */}
<div className="border-b">
<div className="p-2 bg-muted/30">
<div className="text-xs font-medium text-muted-foreground mb-2">Hela dagen</div>
{/* Active invoices */}
{activeInvoices.length > 0 && (
<div className="space-y-1 mb-2">
{activeInvoices.map((invoice) => (
<div
key={invoice.id}
className={cn(
'p-2 rounded-md',
isInvoiceOverdue(invoice)
? 'bg-destructive/10 border border-destructive/30'
: 'bg-primary/10 border border-primary/30'
)}
>
<div className="flex items-center gap-2">
<div className={cn(
'w-3 h-3 rounded-full flex-shrink-0',
isInvoiceOverdue(invoice) ? 'bg-destructive' : 'bg-primary'
)} />
<div className="flex-1 min-w-0">
<div className={cn(
'text-sm font-medium',
isInvoiceOverdue(invoice) ? 'text-destructive' : 'text-primary'
)}>
Faktura {invoice.invoice_number}
{isInvoiceOverdue(invoice) && (
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded">
Förfallen
</span>
)}
</div>
<div className="text-xs text-muted-foreground">
{invoice.customer?.name} {(invoice.total_sek || invoice.total).toLocaleString('sv-SE')} kr
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Paid invoices */}
{paidInvoices.length > 0 && (
<div className="space-y-1 mb-2">
{paidInvoices.map((invoice) => (
<div
key={invoice.id}
className="p-2 rounded-md bg-success/10 border border-success/30"
>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-success flex-shrink-0" />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-success">
Faktura {invoice.invoice_number}
<span className="ml-2 text-xs bg-success/20 px-1.5 py-0.5 rounded">
Betald
</span>
</div>
<div className="text-xs text-muted-foreground">
{invoice.customer?.name} {(invoice.total_sek || invoice.total).toLocaleString('sv-SE')} kr
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Active deadlines */}
{activeDeadlines.length > 0 && (
<div className="space-y-1 mb-2">
{activeDeadlines.map((deadline) => (
<div
key={deadline.id}
className={cn(
'p-2 rounded-md',
isDeadlineOverdue(deadline)
? 'bg-destructive/10 border border-destructive/30'
: 'bg-warning/10 border border-warning/30'
)}
>
<div className="flex items-center gap-2">
<div className={cn(
'w-3 h-3 rounded-sm flex-shrink-0',
isDeadlineOverdue(deadline) ? 'bg-destructive' : 'bg-warning'
)} />
<div className="flex-1 min-w-0">
<div className={cn(
'text-sm font-medium',
isDeadlineOverdue(deadline) ? 'text-destructive' : 'text-warning-foreground'
)}>
{deadline.title}
{isDeadlineOverdue(deadline) && (
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded">
Försenad
</span>
)}
</div>
<div className="text-xs text-muted-foreground">
{DEADLINE_TYPE_LABELS[deadline.deadline_type] || deadline.deadline_type}
{deadline.priority !== 'normal' && (
<span className={cn(
'ml-2 px-1.5 py-0.5 rounded',
deadline.priority === 'critical' && 'bg-red-100 text-red-700',
deadline.priority === 'important' && 'bg-orange-100 text-orange-700'
)}>
{PRIORITY_LABELS[deadline.priority]}
</span>
)}
{deadline.customer?.name && ` • ${deadline.customer.name}`}
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Completed deadlines */}
{completedDeadlines.length > 0 && (
<div className="space-y-1">
{completedDeadlines.map((deadline) => (
<div
key={deadline.id}
className="p-2 rounded-md bg-success/10 border border-success/30 opacity-60"
>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-sm bg-success flex-shrink-0" />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-success line-through">
{deadline.title}
</div>
<div className="text-xs text-muted-foreground">
{DEADLINE_TYPE_LABELS[deadline.deadline_type] || deadline.deadline_type}
<span className="ml-2 bg-success/20 px-1.5 py-0.5 rounded">Klar</span>
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Empty state */}
{dayInvoices.length === 0 && dayDeadlines.length === 0 && (
<div className="text-sm text-muted-foreground py-4 text-center">
Inga händelser för denna dag
</div>
)}
</div>
</div>
{/* Time grid */}
<div className="max-h-[400px] overflow-y-auto">
{timeSlots.map((time) => (
<button
key={time}
onClick={() => onAddDeadline(date)}
className="w-full flex border-b last:border-b-0 hover:bg-muted/50 transition-colors"
>
<div className="w-16 p-2 border-r text-xs text-muted-foreground text-right pr-2 flex-shrink-0">
{time}
</div>
<div className="flex-1 min-h-[40px]" />
</button>
))}
</div>
</div>
)
}