Files
picpeak/frontend/src/hooks/useInstallmentDefaults.ts
T
Luca a7e16e7bf6 feat(crm): frontend code — pages + services + components
Brings in the full frontend CRM stack: admin authoring pages,
customer-portal surfaces, public response flows, typed services,
and the supporting component library. i18n locale JSON is the next
commit (kept separate so reviewers can read it as data).

Pages
  - Quotes: list / editor / detail / public accept-decline
  - Invoices: list / editor / detail / public payment-check
  - Contracts: list / editor / detail / block library / public sign
  - Calendar (FullCalendar — admin-only v1)
  - Tax report (period picker + CSV/PDF export)
  - Hours (logged time entries, per-customer)
  - Deals lineage (DocumentLineageCard surfaces)
  - CRM Development (admin dev tools, gated by crmDevelopment flag)
  - Customer-portal pages for quotes / invoices / contracts
  - Settings reorg: CRM-Settings group + dedicated tabs for Business
    Profile, CRM behaviour, Contracts block library, Reminder emails
  - BrandingPage typography (PDF font picker)
  - EventDetailsPage / CustomerDetailPage / CreateEventPage extensions
    (event-time fields, hours toggle, per-event reminder override)

Services (typed)
  - quotes.service, bills.service, contracts.service
  - customerAdmin.service, deals.service, calendar.service,
    taxReport.service, contracts-blocks.service
  - businessProfile.service (timezone, font picker, bank accounts)
  - useInstallmentDefaults hook, useLocalizedDate dateInputLang extension

Components (admin)
  - CustomerPicker (shared across quote/invoice/contract editors)
  - LineItemsTable (hierarchy + details_text, memoised pricing)
  - InstallmentsPanel (simple + advanced toggle, fixed-date vs trigger)
  - DocumentLineageCard (deal_uuid grouped view)
  - EditInstallmentPlanModal (atomic post-spawn plan reshape)
  - EventReminderOverrideCard, EmailTemplateEditor (tiptap),
    PdfFontPicker, IntegrityCheckCard
  - Feature-flag context + RequireFeature wrapper + AdminSidebar
    featureFlagsAny derivation + UI-hiding sweep

Build infra
  - vite.config: fullcalendar chunk carved off (~200 KB lazy-loaded)
  - frontend/package.json: tiptap, fullcalendar, signature_pad,
    react-international-phone, et al.
  - tailwind + prose styles updated for editor surfaces

3-way merge note: 1 conflict (CustomerDetailPage.tsx) hand-resolved
to keep upstream's SUPPORTED_LANGUAGES.map() data-driven pattern
over feat/crm's hardcoded option list; feat/crm's DecimalInput
import preserved alongside.
2026-05-26 18:19:32 +02:00

52 lines
2.0 KiB
TypeScript

/**
* useInstallmentDefaults — fetch the three CRM-level installment
* defaults seeded by migration 141:
*
* - crm_invoices_installment_trigger_first ('quote_accepted')
* - crm_invoices_installment_days_before_event (14)
* - crm_invoices_installment_days_after_event (14)
*
* Used by InstallmentsPanel on the Quote and Invoice editors so a
* freshly-added installment row pre-populates with the admin's
* preferred trigger shape instead of an empty value.
*
* Cached via react-query with a 5-min staleTime — these settings
* change rarely and only via Settings → CRM. A `defaults` value is
* always returned (hard-coded fallbacks before the fetch completes)
* so callers don't have to guard a loading state.
*/
import { useQuery } from '@tanstack/react-query';
import { settingsService } from '../services/settings.service';
import type { PaymentTermInstallment } from '../services/quotes.service';
export interface InstallmentDefaults {
triggerFirst: PaymentTermInstallment['trigger'];
daysBeforeEvent: number;
daysAfterEvent: number;
}
const HARDCODED_FALLBACK: InstallmentDefaults = {
triggerFirst: 'quote_accepted',
daysBeforeEvent: 14,
daysAfterEvent: 14,
};
export function useInstallmentDefaults(): InstallmentDefaults {
const { data } = useQuery({
queryKey: ['installment-defaults'],
queryFn: () => settingsService.getAllSettings(),
staleTime: 5 * 60_000,
});
if (!data) return HARDCODED_FALLBACK;
return {
triggerFirst: (data.crm_invoices_installment_trigger_first as PaymentTermInstallment['trigger'])
|| HARDCODED_FALLBACK.triggerFirst,
daysBeforeEvent: Number.isFinite(Number(data.crm_invoices_installment_days_before_event))
? Number(data.crm_invoices_installment_days_before_event)
: HARDCODED_FALLBACK.daysBeforeEvent,
daysAfterEvent: Number.isFinite(Number(data.crm_invoices_installment_days_after_event))
? Number(data.crm_invoices_installment_days_after_event)
: HARDCODED_FALLBACK.daysAfterEvent,
};
}