731a57dd6e
* fix(deadlines): submit only form-managed fields from the deadline form Fixes #1176. The form fabricated 11 system-field values on every submit (source: 'user', status: 'upcoming', reminder_offsets, tax_* nulls, ...) and the edit path PUT the entire merged Deadline row; only the route handlers' whitelists prevented editing a system-generated tax deadline from nuking those fields. The form now has an explicit DeadlineFormValues contract (the 7 fields it renders), create and edit send exactly that, and the edit handler takes (id, values) instead of a whole Deadline. No behavior change today; removes the latent data-loss dependency on the server whitelist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(deadlines): migrate calendar DeadlineForm consumers to DeadlineFormValues The calendar extension's PaymentCalendar (and its CalendarWorkspace host) still typed the submit chain as the old full-row Omit<Deadline> shape, failing the core-only typecheck. Behavior unchanged: the raw insert already omitted ids, and the DB defaults cover system fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
127 lines
3.5 KiB
TypeScript
127 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import { createClient } from '@/lib/supabase/client'
|
|
import { useToast } from '@/components/ui/use-toast'
|
|
import { PaymentCalendar } from '@/extensions/general/calendar/components/PaymentCalendar'
|
|
import type { DeadlineFormValues } from '@/components/deadlines/DeadlineForm'
|
|
import type { WorkspaceComponentProps } from '@/lib/extensions/workspace-registry'
|
|
import type { Invoice, Deadline } from '@/types'
|
|
|
|
export default function CalendarWorkspace({ userId }: WorkspaceComponentProps) {
|
|
const [invoices, setInvoices] = useState<Invoice[]>([])
|
|
const [deadlines, setDeadlines] = useState<Deadline[]>([])
|
|
const [customers, setCustomers] = useState<{ id: string; name: string }[]>([])
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const { toast } = useToast()
|
|
const supabase = createClient()
|
|
|
|
const fetchData = useCallback(async () => {
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
const { data: invoicesData, error: invoicesError } = await supabase
|
|
.from('invoices')
|
|
.select('*, customer:customers(name)')
|
|
.order('due_date', { ascending: true })
|
|
|
|
if (invoicesError) throw invoicesError
|
|
|
|
const { data: deadlinesData, error: deadlinesError } = await supabase
|
|
.from('deadlines')
|
|
.select('*, customer:customers(name)')
|
|
.is('dismissed_at', null)
|
|
.order('due_date', { ascending: true })
|
|
|
|
if (deadlinesError) throw deadlinesError
|
|
|
|
const { data: customersData, error: customersError } = await supabase
|
|
.from('customers')
|
|
.select('id, name')
|
|
.order('name', { ascending: true })
|
|
|
|
if (customersError) throw customersError
|
|
|
|
setInvoices(invoicesData || [])
|
|
setDeadlines(deadlinesData || [])
|
|
setCustomers(customersData || [])
|
|
} catch {
|
|
toast({
|
|
title: 'Kunde inte hämta data',
|
|
variant: 'destructive',
|
|
})
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [supabase, toast])
|
|
|
|
useEffect(() => {
|
|
fetchData()
|
|
}, [fetchData])
|
|
|
|
const handleDeadlineCreate = async (data: DeadlineFormValues) => {
|
|
try {
|
|
const { error } = await supabase.from('deadlines').insert([data])
|
|
|
|
if (error) throw error
|
|
|
|
toast({
|
|
title: 'Deadline skapad',
|
|
description: 'Din deadline har sparats',
|
|
})
|
|
|
|
fetchData()
|
|
} catch (error) {
|
|
toast({
|
|
title: 'Kunde inte skapa deadline',
|
|
variant: 'destructive',
|
|
})
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const handleDeadlineToggle = async (deadline: Deadline) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('deadlines')
|
|
.update({
|
|
is_completed: !deadline.is_completed,
|
|
completed_at: !deadline.is_completed ? new Date().toISOString() : null,
|
|
})
|
|
.eq('id', deadline.id)
|
|
|
|
if (error) throw error
|
|
|
|
toast({
|
|
title: deadline.is_completed ? 'Markerad som ej klar' : 'Markerad som klar',
|
|
})
|
|
|
|
fetchData()
|
|
} catch {
|
|
toast({
|
|
title: 'Kunde inte uppdatera deadline',
|
|
variant: 'destructive',
|
|
})
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="animate-pulse">
|
|
<div className="h-10 bg-muted rounded w-48 mb-4" />
|
|
<div className="h-96 bg-muted rounded" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<PaymentCalendar
|
|
invoices={invoices}
|
|
deadlines={deadlines}
|
|
customers={customers}
|
|
onDeadlineCreate={handleDeadlineCreate}
|
|
onDeadlineToggle={handleDeadlineToggle}
|
|
/>
|
|
)
|
|
}
|