feat(invoices): expose the automatic reminder kill switch in settings (#1476)

* feat(invoices): expose the automatic reminder kill switch in settings

The send_invoice_reminders column, API schema, and cron processor check
already existed, but no UI ever exposed the toggle. Add a switch in
Settings -> Fakturering (day thresholds fold away when off, values
preserved), and make the invoice detail Paminnelser card say reminders
are off instead of promising emails that will never be sent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invoices): do not assume reminders enabled before settings load

CodeRabbit finding on PR 1476: with no company_settings row loaded, the
Paminnelser card defaulted to promising the reminder schedule. Track the
toggle as boolean | null and render no schedule text until the settings
row has actually resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-10 11:24:15 +02:00
committed by GitHub
parent 741cdcc60e
commit d4b0bd3df1
5 changed files with 81 additions and 48 deletions
+14 -7
View File
@@ -169,6 +169,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
const [showBookConfirm, setShowBookConfirm] = useState(false)
const [bookVoucherPreview, setBookVoucherPreview] = useState<string | null>(null)
const [reminderDays, setReminderDays] = useState<[number, number, number]>([15, 30, 45])
// null = settings row not loaded; don't promise a reminder schedule then.
const [autoRemindersEnabled, setAutoRemindersEnabled] = useState<boolean | null>(null)
const statusLabel = (status: InvoiceStatus): string => t(`status_${status}`)
const reminderLevelLabel = (level: 1 | 2 | 3): string => t(`reminder_level_${level}`)
@@ -213,7 +215,7 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
const settingsPromise = company?.id
? supabase
.from('company_settings')
.select('ore_rounding, vat_registered, accounting_method, defer_invoice_booking, reminder_days_level_1, reminder_days_level_2, reminder_days_level_3')
.select('ore_rounding, vat_registered, accounting_method, defer_invoice_booking, reminder_days_level_1, reminder_days_level_2, reminder_days_level_3, send_invoice_reminders')
.eq('company_id', company.id)
.maybeSingle()
: Promise.resolve(null)
@@ -311,6 +313,9 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
settings?.reminder_days_level_2 ?? 30,
settings?.reminder_days_level_3 ?? 45,
])
if (settings) {
setAutoRemindersEnabled(settings.send_invoice_reminders ?? true)
}
}
// Related documents need the invoice row but do not gate the main detail
@@ -1480,13 +1485,15 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
<Bell className="h-5 w-5" />
{t('reminders_card_title')}
</CardTitle>
{reminders.length === 0 && (
{reminders.length === 0 && autoRemindersEnabled !== null && (
<CardDescription>
{t('reminders_description', {
day1: reminderDays[0],
day2: reminderDays[1],
day3: reminderDays[2],
})}
{autoRemindersEnabled
? t('reminders_description', {
day1: reminderDays[0],
day2: reminderDays[1],
day3: reminderDays[2],
})
: t('reminders_disabled')}
</CardDescription>
)}
</CardHeader>
+60 -41
View File
@@ -1,9 +1,12 @@
'use client'
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { Switch } from '@/components/ui/switch'
import {
SettingsGroup,
SettingsInput,
SettingsReveal,
SettingsRow,
SettingsTextarea,
} from '@/components/settings/SettingsRows'
@@ -15,6 +18,7 @@ interface InvoiceSettingsFormProps {
export function InvoiceSettingsForm({ settings }: InvoiceSettingsFormProps) {
const t = useTranslations('settings_invoice_form')
const [sendReminders, setSendReminders] = useState(settings.send_invoice_reminders ?? true)
return (
<>
<SettingsGroup label={t('heading')}>
@@ -101,50 +105,65 @@ export function InvoiceSettingsForm({ settings }: InvoiceSettingsFormProps) {
<SettingsGroup label={t('reminder_days_heading')} help={t('reminder_days_help')}>
<SettingsRow
label={t('reminder_days_level_1')}
htmlFor="reminder_days_level_1"
align="baseline"
label={t('send_reminders_label')}
htmlFor="send_invoice_reminders"
help={t('send_reminders_help')}
borderless={sendReminders}
>
<SettingsInput
id="reminder_days_level_1"
name="reminder_days_level_1"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_1 ?? 15}
className="max-w-24 flex-none tabular-nums"
/>
</SettingsRow>
<SettingsRow
label={t('reminder_days_level_2')}
htmlFor="reminder_days_level_2"
align="baseline"
>
<SettingsInput
id="reminder_days_level_2"
name="reminder_days_level_2"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_2 ?? 30}
className="max-w-24 flex-none tabular-nums"
/>
</SettingsRow>
<SettingsRow
label={t('reminder_days_level_3')}
htmlFor="reminder_days_level_3"
align="baseline"
>
<SettingsInput
id="reminder_days_level_3"
name="reminder_days_level_3"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_3 ?? 45}
className="max-w-24 flex-none tabular-nums"
<Switch
id="send_invoice_reminders"
checked={sendReminders}
onCheckedChange={(v) => setSendReminders(v === true)}
/>
<input type="hidden" name="send_invoice_reminders" value={sendReminders ? 'true' : 'false'} />
</SettingsRow>
<SettingsReveal open={sendReminders}>
<SettingsRow
label={t('reminder_days_level_1')}
htmlFor="reminder_days_level_1"
align="baseline"
>
<SettingsInput
id="reminder_days_level_1"
name="reminder_days_level_1"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_1 ?? 15}
className="max-w-24 flex-none tabular-nums"
/>
</SettingsRow>
<SettingsRow
label={t('reminder_days_level_2')}
htmlFor="reminder_days_level_2"
align="baseline"
>
<SettingsInput
id="reminder_days_level_2"
name="reminder_days_level_2"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_2 ?? 30}
className="max-w-24 flex-none tabular-nums"
/>
</SettingsRow>
<SettingsRow
label={t('reminder_days_level_3')}
htmlFor="reminder_days_level_3"
align="baseline"
>
<SettingsInput
id="reminder_days_level_3"
name="reminder_days_level_3"
type="number"
min="1"
max="365"
defaultValue={settings.reminder_days_level_3 ?? 45}
className="max-w-24 flex-none tabular-nums"
/>
</SettingsRow>
</SettingsReveal>
</SettingsGroup>
</>
)
@@ -37,6 +37,7 @@ export function InvoicingSettingsContent() {
Number.parseInt(formData.get('reminder_days_level_2') as string) || 30,
reminder_days_level_3:
Number.parseInt(formData.get('reminder_days_level_3') as string) || 45,
send_invoice_reminders: formData.get('send_invoice_reminders') === 'true',
}
return {
updates,
+3
View File
@@ -1935,6 +1935,8 @@
"default_notes_help": "Suggested automatically for new invoices.",
"reminder_days_heading": "Automatic reminders",
"reminder_days_help": "Choose how many days after the due date each reminder is sent. The days must be in ascending order.",
"send_reminders_label": "Send automatic reminders",
"send_reminders_help": "When off, no automatic reminder emails are sent to customers with overdue invoices.",
"reminder_days_level_1": "First reminder (days)",
"reminder_days_level_2": "Second reminder (days)",
"reminder_days_level_3": "Final reminder (days)"
@@ -3502,6 +3504,7 @@
"payment_status_empty": "No payments recorded yet.",
"reminders_card_title": "Reminders",
"reminders_description": "Automatic reminders are sent when the invoice is {day1}, {day2} and {day3} days overdue",
"reminders_disabled": "Automatic reminders are turned off in settings.",
"reminders_empty": "No reminders have been sent yet.",
"reminder_level_label": "Level {level}",
"reminder_level_1": "Friendly reminder",
+3
View File
@@ -1935,6 +1935,8 @@
"default_notes_help": "Föreslås automatiskt vid ny faktura.",
"reminder_days_heading": "Automatiska påminnelser",
"reminder_days_help": "Ange hur många dagar efter förfallodatum varje påminnelse skickas. Dagarna måste vara i stigande ordning.",
"send_reminders_label": "Skicka automatiska påminnelser",
"send_reminders_help": "När detta är avstängt skickas inga automatiska påminnelsemejl till kunder med förfallna fakturor.",
"reminder_days_level_1": "Första påminnelsen (dagar)",
"reminder_days_level_2": "Andra påminnelsen (dagar)",
"reminder_days_level_3": "Slutlig påminnelse (dagar)"
@@ -3502,6 +3504,7 @@
"payment_status_empty": "Inga registrerade betalningar ännu.",
"reminders_card_title": "Påminnelser",
"reminders_description": "Automatiska påminnelser skickas när fakturan varit förfallen i {day1}, {day2} och {day3} dagar",
"reminders_disabled": "Automatiska påminnelser är avstängda i inställningarna.",
"reminders_empty": "Inga påminnelser har skickats ännu.",
"reminder_level_label": "Nivå {level}",
"reminder_level_1": "Vänlig påminnelse",