WORKFLOW-APP/lib/types.ts
SJCoetz bb97fef453 feat: initialize full ysterbaard frontend application
Add complete production-ready Ysterbaard PocketBase registry browser frontend:
- Next.js 14 App Router with TypeScript, Tailwind CSS, and TanStack React Query
- Secure server-side PocketBase API proxy to avoid exposing superuser tokens
- Read-only directors and companies registry browse pages with search, filtering, and pagination
- Client onboarding workflow for creating and managing client submissions
- Docker container configuration for easy deployment
- All supporting UI components, utility functions, and configuration files
2026-07-23 19:10:47 +02:00

182 lines
5.9 KiB
TypeScript

// lib/types.ts
// Mirrors the two PocketBase collections used by the app. Field names
// match the live PB schema exactly (case-sensitive). Optional fields
// are nullable on the wire; we narrow them to `T | null` so callers
// have to handle empty / missing cases.
export interface YsterbaardDirector {
[key: string]: unknown;
id: string;
collectionId: string;
collectionName: string;
/** 13-digit SA ID number, e.g. "8001015009087". */
id_no: string;
surname: string;
first_names: string;
/** Free-text from CIPC: "Active", "Disqualified", etc. */
disqualification_status: string | null;
/** SA-format phone or empty. Pattern ^[0-9 +()\-]{0,30}$ enforced in PB. */
telephone: string | null;
total_companies: number;
active_companies: number;
inactive_companies: number;
first_seen: string | null; // ISO date
last_checked: string | null; // ISO date
notes: string | null;
created: string;
updated: string;
}
export interface YsterbaardCompany {
[key: string]: unknown;
id: string;
collectionId: string;
collectionName: string;
/** Unique CIPC enterprise number, e.g. "K2020123456". */
enterprise_no: string;
enterprise_name: string;
/** e.g. "In Business", "Deregistered", "In Process". */
enterprise_status: string | null;
/** Per-director status: "ACTIVE", "INACTIVE", "DISQUALIFIED", etc. */
director_status: string | null;
is_active: boolean;
/** Relation -> ysterbaard_directors. Expanded manually below. */
director: string;
/** Expanded director payload (when `?expand=director` is used). */
expand?: {
director?: Pick<
YsterbaardDirector,
"id" | "id_no" | "surname" | "first_names"
>;
};
source: string | null;
notes: string | null;
enterprise_type: string | null;
registration_date: string | null;
physical_address: string | null;
postal_address: string | null;
physical_address_normalized: string | null;
postal_address_normalized: string | null;
sars_tax_number: string | null;
uif_registration_number: string | null;
compensation_fund_registration_number: string | null;
compliance_notice: string | null;
bbbee_status: string | null;
bbbee_certificate_applied: boolean;
info_regulator_registered: boolean;
ar_outstanding: boolean;
ar_outstanding_details: string | null;
last_5_ars: unknown[] | Record<string, unknown> | null;
enterprise_history: unknown[] | Record<string, unknown> | null;
first_seen: string | null;
last_checked: string | null;
created: string;
updated: string;
}
/** PocketBase paginated response envelope. */
export interface PBList<T> {
page: number;
perPage: number;
totalItems: number;
totalPages: number;
items: T[];
}
/** Standard query params we forward to the proxy on every list call. */
export interface ListParams {
page?: number;
perPage?: number;
sort?: string;
filter?: string;
expand?: string;
fields?: string;
}
export type CompanyStatusFilter = "all" | "active" | "inactive" | "in_process" | "deregistered";
// ─────────────────────────────────────────────────────────────────────────
// Onboarding intake — paperless replacement for the Annexure 1/2/3 pack.
// A single submission holds company + directors + shareholders as JSON
// blobs, with uploaded documents attached. Once processed into the live
// `ysterbaard_directors` records, the row stays around for audit until
// archived (status='rejected') and deleted by hand.
// ─────────────────────────────────────────────────────────────────────────
export interface OnboardingDirector {
/** Generated client-side, never persisted as-is — just a UI key. */
ui_key: string;
first_names: string;
surname: string;
id_no: string;
date_of_birth: string; // YYYY-MM-DD
residential_address: string;
postal_address: string;
cell_phone: string;
email: string;
}
export interface OnboardingShareholder {
ui_key: string;
/** "person" or "entity" — toggles whether the next field validates as ID or company number. */
kind: "person" | "entity";
name: string;
id_or_company_no: string;
share_percent: number;
}
export interface OnboardingCompany {
proposed_names: [string, string, string]; // up to 3 in priority order
financial_year_end_month: number; // 1-12
authorised_shares: "100" | "1000";
physical_address: string;
postal_address: string;
company_cell_phone: string;
company_email: string;
public_officer_name: string;
company_secretary_name: string;
}
export type OnboardingDirectorPayload = Omit<OnboardingDirector, "ui_key">;
export type OnboardingShareholderPayload = Omit<OnboardingShareholder, "ui_key">;
export interface OnboardingSubmissionPayload {
/** Client may omit this — server mints a fresh reference_no on submit. */
reference_no?: string;
client_name: string;
client_email: string;
client_telephone: string;
company: OnboardingCompany;
directors: OnboardingDirectorPayload[];
shareholders: OnboardingShareholderPayload[];
}
export type OnboardingStatus =
| "draft"
| "submitted"
| "processing"
| "processed"
| "rejected";
export interface OnboardingSubmission {
[key: string]: unknown;
id: string;
collectionId: string;
collectionName: string;
created: string;
updated: string;
reference_no: string;
access_token_hash: string;
client_name: string;
client_email: string;
client_telephone: string | null;
company_data: OnboardingCompany;
directors_data: OnboardingDirector[];
shareholders_data: OnboardingShareholder[] | null;
processed_director_ids: string[] | null;
documents: string[];
submitted_at: string | null;
processed_at: string | null;
internal_notes: string | null;
status: OnboardingStatus;
}