Files
picpeak/frontend/src/services/calendar.service.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

100 lines
2.8 KiB
TypeScript

/**
* Admin calendar API client.
*
* Wraps the single aggregate endpoint
* GET /api/admin/calendar/items?from=YYYY-MM-DD&to=YYYY-MM-DD
* exposed by `backend/src/routes/adminCalendar.js` (E.3).
*
* The response is a discriminated union on `kind` so the calendar page
* can switch render style per item type without juggling four separate
* react-query subscriptions.
*
* Hour-entry mutations stay on the existing customerAdmin.service —
* the calendar's drag-create / inline-edit modals call those directly.
*/
import { api } from '../config/api';
export type CalendarItemKind = 'event' | 'hours' | 'quote' | 'contract';
interface CalendarItemBase {
kind: CalendarItemKind;
customerName: string | null;
}
export interface CalendarEventItem extends CalendarItemBase {
kind: 'event';
id: number;
slug: string;
eventName: string;
eventDate: string;
eventTimeStart: string | null;
eventTimeEnd: string | null;
isFullDay: boolean;
}
export interface CalendarHoursItem extends CalendarItemBase {
kind: 'hours';
id: number;
customerAccountId: number;
entryDate: string;
startTime: string;
endTime: string;
description: string | null;
status: 'unbilled' | 'billed' | 'cancelled';
invoiceId: number | null;
invoiceStatus: string | null;
/**
* True when this entry's invoice has been armed for send (monthly
* draft cleared + scheduled_send_at set, OR status sent/paid/cancelled).
* Backend computes this via customerHoursService._internal.isEntryLocked.
* Drives the calendar's locked badge + edit-disabled state.
*/
locked: boolean;
}
export interface CalendarQuoteItem extends CalendarItemBase {
kind: 'quote';
id: number;
quoteNumber: string;
eventName: string | null;
eventDate: string;
eventTimeStart: string | null;
eventTimeEnd: string | null;
status: 'sent' | 'accepted';
}
export interface CalendarContractItem extends CalendarItemBase {
kind: 'contract';
id: number;
contractNumber: string;
eventName: string | null;
eventDate: string;
eventTimeStart: string | null;
eventTimeEnd: string | null;
status: 'signed_by_customer' | 'fully_signed';
}
export type CalendarItem =
| CalendarEventItem
| CalendarHoursItem
| CalendarQuoteItem
| CalendarContractItem;
export interface CalendarItemsResponse {
items: CalendarItem[];
range: { from: string; to: string };
}
export const calendarService = {
/**
* Fetch all four layers (events, hours, pending quotes, pending
* contracts) for the supplied date range. Both ends are inclusive,
* ISO YYYY-MM-DD. Backend caps the range at 90 days.
*/
async list(params: { from: string; to: string }): Promise<CalendarItemsResponse> {
const { data } = await api.get('/admin/calendar/items', { params });
return data.data || data;
},
};