feat(calendar): enable calendar sync for Viktiga datum (#1853)
Turns on the calendar extension (built Feb 2026, stripped in the 2026-03-02 production readiness deploy, never re-enabled): ICS feed settings, calendar workspace, subscribe button on the Viktiga datum page. Hardening before first real use: feed serve route now requires the creator to still be a company member (offboarding stops the feed); stable pagination (due_date + id, dedupe) on feed queries; fetches inside the logged try block; invoice events limited to sent/paid/partially_paid/overdue; event UIDs rebranded to accounted.se while zero feeds exist; APP_URL fallback fails closed in production; mobile stacking for the deadlines header; settings note that Google Calendar needs default notifications on subscribed calendars; calendar workspace aligned with the design system. Skeptic reviewed (3 refutations, all fixed) plus one compliance swarm finding (fixed). No migrations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4fb449a474
commit
f75ea2384d
@@ -57,7 +57,7 @@ export function CalendarDayCell({
|
||||
!isCurrentMonth && 'bg-muted/30 text-muted-foreground',
|
||||
isToday && 'bg-primary/5',
|
||||
hasOverdue && 'border-l-2 border-l-destructive',
|
||||
!hasOverdue && hasActionNeeded && 'border-l-2 border-l-orange-500'
|
||||
!hasOverdue && hasActionNeeded && 'border-l-2 border-l-warning'
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
@@ -113,7 +113,7 @@ export function CalendarDayCell({
|
||||
{deadlinesByStatus.action_needed > 0 && (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className={cn('w-2 h-2 rounded-sm', STATUS_COLORS.action_needed.dot)} />
|
||||
<span className="text-xs text-orange-700 truncate">
|
||||
<span className="text-xs text-warning-foreground truncate">
|
||||
{deadlinesByStatus.action_needed} åtgärd
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,7 @@ export function CalendarDayView({
|
||||
<div
|
||||
key={invoice.id}
|
||||
className={cn(
|
||||
'p-2 rounded-md',
|
||||
'p-2 rounded-lg',
|
||||
isInvoiceOverdue(invoice)
|
||||
? 'bg-destructive/10 border border-destructive/30'
|
||||
: 'bg-primary/10 border border-primary/30'
|
||||
@@ -110,7 +110,7 @@ export function CalendarDayView({
|
||||
)}>
|
||||
Faktura {invoice.invoice_number}
|
||||
{isInvoiceOverdue(invoice) && (
|
||||
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded">
|
||||
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded-sm">
|
||||
Förfallen
|
||||
</span>
|
||||
)}
|
||||
@@ -131,14 +131,14 @@ export function CalendarDayView({
|
||||
{paidInvoices.map((invoice) => (
|
||||
<div
|
||||
key={invoice.id}
|
||||
className="p-2 rounded-md bg-success/10 border border-success/30"
|
||||
className="p-2 rounded-lg 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">
|
||||
<span className="ml-2 text-xs bg-success/20 px-1.5 py-0.5 rounded-sm">
|
||||
Betald
|
||||
</span>
|
||||
</div>
|
||||
@@ -159,7 +159,7 @@ export function CalendarDayView({
|
||||
<div
|
||||
key={deadline.id}
|
||||
className={cn(
|
||||
'p-2 rounded-md',
|
||||
'p-2 rounded-lg',
|
||||
isDeadlineOverdue(deadline)
|
||||
? 'bg-destructive/10 border border-destructive/30'
|
||||
: 'bg-warning/10 border border-warning/30'
|
||||
@@ -177,7 +177,7 @@ export function CalendarDayView({
|
||||
)}>
|
||||
{deadline.title}
|
||||
{isDeadlineOverdue(deadline) && (
|
||||
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded">
|
||||
<span className="ml-2 text-xs bg-destructive/20 px-1.5 py-0.5 rounded-sm">
|
||||
Försenad
|
||||
</span>
|
||||
)}
|
||||
@@ -186,9 +186,9 @@ export function CalendarDayView({
|
||||
{DEADLINE_TYPE_LABELS[deadline.deadline_type] || deadline.deadline_type}
|
||||
{deadline.priority !== 'normal' && (
|
||||
<span className={cn(
|
||||
'ml-2 px-1.5 py-0.5 rounded',
|
||||
'ml-2 px-1.5 py-0.5 rounded-sm',
|
||||
deadline.priority === 'critical' && 'bg-destructive/10 text-destructive',
|
||||
deadline.priority === 'important' && 'bg-orange-100 text-orange-700'
|
||||
deadline.priority === 'important' && 'bg-warning/10 text-warning-foreground'
|
||||
)}>
|
||||
{PRIORITY_LABELS[deadline.priority]}
|
||||
</span>
|
||||
@@ -208,7 +208,7 @@ export function CalendarDayView({
|
||||
{completedDeadlines.map((deadline) => (
|
||||
<div
|
||||
key={deadline.id}
|
||||
className="p-2 rounded-md bg-success/10 border border-success/30 opacity-60"
|
||||
className="p-2 rounded-lg 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" />
|
||||
@@ -218,7 +218,7 @@ export function CalendarDayView({
|
||||
</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>
|
||||
<span className="ml-2 bg-success/20 px-1.5 py-0.5 rounded-sm">Klar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,7 @@ export function CalendarWeekView({
|
||||
<div
|
||||
key={invoice.id}
|
||||
className={cn(
|
||||
'text-xs p-1 mb-1 rounded truncate',
|
||||
'text-xs p-1 mb-1 rounded-sm truncate',
|
||||
isInvoiceOverdue(invoice)
|
||||
? 'bg-destructive/20 text-destructive'
|
||||
: 'bg-primary/20 text-primary'
|
||||
@@ -111,7 +111,7 @@ export function CalendarWeekView({
|
||||
<div
|
||||
key={deadline.id}
|
||||
className={cn(
|
||||
'text-xs p-1 mb-1 rounded truncate',
|
||||
'text-xs p-1 mb-1 rounded-sm truncate',
|
||||
isDeadlineOverdue(deadline)
|
||||
? 'bg-destructive/20 text-destructive'
|
||||
: 'bg-warning/20 text-warning-foreground'
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { CalendarViewMode } from '@/types'
|
||||
import { VIEW_MODE_LABELS } from '@/lib/calendar/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
|
||||
interface ViewModeSelectorProps {
|
||||
viewMode: CalendarViewMode
|
||||
@@ -14,21 +13,11 @@ export function ViewModeSelector({ viewMode, onViewModeChange }: ViewModeSelecto
|
||||
const modes: CalendarViewMode[] = ['month', 'week', 'day']
|
||||
|
||||
return (
|
||||
<div className="inline-flex rounded-md border">
|
||||
{modes.map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onViewModeChange(mode)}
|
||||
className={cn(
|
||||
'rounded-none border-r last:border-r-0 px-3',
|
||||
viewMode === mode && 'bg-muted'
|
||||
)}
|
||||
>
|
||||
{VIEW_MODE_LABELS[mode]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={viewMode}
|
||||
onChange={onViewModeChange}
|
||||
options={modes.map((mode) => ({ value: mode, label: VIEW_MODE_LABELS[mode] }))}
|
||||
aria-label="Kalendervy"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user