From 9bd35898a2ae83f621512aff81c21aaaf44cf8ca Mon Sep 17 00:00:00 2001 From: Ysterbaard Date: Tue, 18 Aug 2026 12:25:06 +0200 Subject: [PATCH] Add onboarding form fields to companies + directors (visual + editable) PB: 15 fields on ysterbaard_companies (financial_year_end_month, authorised_shares, structured physical/postal address, company_cell_phone, public_officer_name, company_secretary_name), 11 fields on ysterbaard_directors (date_of_birth, structured residential/postal address). App: types widened in lib/types.ts. EditEntityModal rebuilt with full address sections (line 1, line 2, town/city, SA province dropdown, postal code), DOB, company-level fields, financial year end month, authorised shares, public officer, company secretary. View pages: structured address blocks now show labeled fields (line 1, line 2, town/city, province, postal code) on both /companies/[id] and /directors/[id]. --- app/companies/[id]/page.tsx | 85 +++++++- app/directors/[id]/page.tsx | 86 ++++++++ components/EditEntityModal.tsx | 374 ++++++++++++++++++++++++++++++++- lib/types.ts | 38 ++++ 4 files changed, 577 insertions(+), 6 deletions(-) diff --git a/app/companies/[id]/page.tsx b/app/companies/[id]/page.tsx index f24b021..8c8a9ae 100644 --- a/app/companies/[id]/page.tsx +++ b/app/companies/[id]/page.tsx @@ -250,17 +250,98 @@ export default async function CompanyDetailPage({ params }: Props) {

Physical address

-

{text(company.physical_address)}

+ {company.physical_address ? ( +

{text(company.physical_address)}

+ ) : null} +
+
+

Address line 1

+

{text(company.physical_address_line_1)}

+
+
+

Address line 2

+

{text(company.physical_address_line_2)}

+
+
+

Town / city

+

{text(company.physical_town_city)}

+
+
+

Province

+

{text(company.physical_province)}

+
+
+

Postal code

+

{text(company.physical_postal_code)}

+
+

Postal address

-

{text(company.postal_address)}

+ {company.postal_address ? ( +

{text(company.postal_address)}

+ ) : null} +
+
+

Address line 1

+

{text(company.postal_address_line_1)}

+
+
+

Address line 2

+

{text(company.postal_address_line_2)}

+
+
+

Town / city

+

{text(company.postal_town_city)}

+
+
+

Province

+

{text(company.postal_province)}

+
+
+

Postal code

+

{text(company.postal_postal_code)}

+
+
{company.company_email ? (

Email

{text(company.company_email)}
) : null} + {company.company_cell_phone ? ( +
+

Cell phone

+

{text(company.company_cell_phone)}

+
+ ) : null} +
+
+ + {/* Company details (added 2026-08-18) */} +
+

Company details

+
+
+

Public officer

+

{text(company.public_officer_name)}

+
+
+

Company secretary

+

{text(company.company_secretary_name)}

+
+
+

Financial year end

+

+ {company.financial_year_end_month + ? new Date(2000, company.financial_year_end_month - 1, 1).toLocaleString("en", { month: "long" }) + : "—"} +

+
+
+

Authorised shares

+

{company.authorised_shares ? company.authorised_shares.toLocaleString() : "—"}

+
diff --git a/app/directors/[id]/page.tsx b/app/directors/[id]/page.tsx index 9a4a220..8ee9f1a 100644 --- a/app/directors/[id]/page.tsx +++ b/app/directors/[id]/page.tsx @@ -142,6 +142,15 @@ export default async function DirectorDetailPage({ params }: Props) { ) : null} +
+

+ Date of birth +

+

+ {formatDate(director.date_of_birth)} +

+
+

Telephone @@ -151,6 +160,19 @@ export default async function DirectorDetailPage({ params }: Props) {

+
+

+ Email +

+

+ {director.email ? ( + {text(director.email)} + ) : ( + — + )} +

+
+

First seen @@ -170,6 +192,70 @@ export default async function DirectorDetailPage({ params }: Props) {

+ {/* Addresses (added 2026-08-18) */} +
+
+

+ Residential address +

+ {director.residential_address ? ( +

{text(director.residential_address)}

+ ) : null} +
+
+

Address line 1

+

{text(director.residential_address_line_1)}

+
+
+

Address line 2

+

{text(director.residential_address_line_2)}

+
+
+

Town / city

+

{text(director.residential_town_city)}

+
+
+

Province

+

{text(director.residential_province)}

+
+
+

Postal code

+

{text(director.residential_postal_code)}

+
+
+
+
+

+ Postal address +

+ {director.postal_address ? ( +

{text(director.postal_address)}

+ ) : null} +
+
+

Address line 1

+

{text(director.postal_address_line_1)}

+
+
+

Address line 2

+

{text(director.postal_address_line_2)}

+
+
+

Town / city

+

{text(director.postal_town_city)}

+
+
+

Province

+

{text(director.postal_province)}

+
+
+

Postal code

+

{text(director.postal_postal_code)}

+
+
+
+
+

diff --git a/components/EditEntityModal.tsx b/components/EditEntityModal.tsx index 53b7a13..8f5d7ef 100644 --- a/components/EditEntityModal.tsx +++ b/components/EditEntityModal.tsx @@ -19,6 +19,19 @@ interface Director { postal_address?: string | null; director_type?: string | null; notes?: string | null; + // ─── Structured address fields (added 2026-08-18) ─── + residential_address?: string | null; + residential_address_line_1?: string | null; + residential_address_line_2?: string | null; + residential_town_city?: string | null; + residential_province?: string | null; + residential_postal_code?: string | null; + postal_address_line_1?: string | null; + postal_address_line_2?: string | null; + postal_town_city?: string | null; + postal_province?: string | null; + postal_postal_code?: string | null; + date_of_birth?: string | null; } interface Company { @@ -40,6 +53,23 @@ interface Company { sars_vat_returns_period?: number | null; sars_paye_return_period?: number | null; groups?: string[] | null; + // ─── Structured address fields (added 2026-08-18) ─── + physical_address_line_1?: string | null; + physical_address_line_2?: string | null; + physical_town_city?: string | null; + physical_province?: string | null; + physical_postal_code?: string | null; + postal_address_line_1?: string | null; + postal_address_line_2?: string | null; + postal_town_city?: string | null; + postal_province?: string | null; + postal_postal_code?: string | null; + // ─── Company-level fields (added 2026-08-18) ─── + financial_year_end_month?: number | null; + authorised_shares?: number | null; + company_cell_phone?: string | null; + public_officer_name?: string | null; + company_secretary_name?: string | null; } interface GroupOption { @@ -48,6 +78,18 @@ interface GroupOption { description?: string | null; } +const SA_PROVINCES = [ + "Eastern Cape", + "Free State", + "Gauteng", + "KwaZulu-Natal", + "Limpopo", + "Mpumalanga", + "Northern Cape", + "North West", + "Western Cape", +]; + interface Props { entity: "director" | "company"; /** Original record data for pre-filling the form */ @@ -72,6 +114,18 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter const [postalAddress, setPostalAddress] = useState(""); const [directorType, setDirectorType] = useState(""); const [notes, setNotes] = useState(""); + // Director structured address fields (added 2026-08-18) + const [dateOfBirth, setDateOfBirth] = useState(""); + const [resAddr1, setResAddr1] = useState(""); + const [resAddr2, setResAddr2] = useState(""); + const [resTown, setResTown] = useState(""); + const [resProvince, setResProvince] = useState(""); + const [resPostalCode, setResPostalCode] = useState(""); + const [postAddr1, setPostAddr1] = useState(""); + const [postAddr2, setPostAddr2] = useState(""); + const [postTown, setPostTown] = useState(""); + const [postProvince, setPostProvince] = useState(""); + const [postPostalCode, setPostPostalCode] = useState(""); // Company fields const [enterpriseNo, setEnterpriseNo] = useState(""); @@ -82,6 +136,23 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter const [postalAddressCo, setPostalAddressCo] = useState(""); const [companyEmail, setCompanyEmail] = useState(""); const [notesCo, setNotesCo] = useState(""); + // Company structured address fields (added 2026-08-18) + const [physAddr1, setPhysAddr1] = useState(""); + const [physAddr2, setPhysAddr2] = useState(""); + const [physTown, setPhysTown] = useState(""); + const [physProvince, setPhysProvince] = useState(""); + const [physPostalCode, setPhysPostalCode] = useState(""); + const [postCoAddr1, setPostCoAddr1] = useState(""); + const [postCoAddr2, setPostCoAddr2] = useState(""); + const [postCoTown, setPostCoTown] = useState(""); + const [postCoProvince, setPostCoProvince] = useState(""); + const [postCoPostalCode, setPostCoPostalCode] = useState(""); + // Company-level fields (added 2026-08-18) + const [financialYearEndMonth, setFinancialYearEndMonth] = useState(""); + const [authorisedShares, setAuthorisedShares] = useState(""); + const [companyCellPhone, setCompanyCellPhone] = useState(""); + const [publicOfficerName, setPublicOfficerName] = useState(""); + const [companySecretaryName, setCompanySecretaryName] = useState(""); // SARS fields const [sarsLoginUsername, setSarsLoginUsername] = useState(""); const [sarsLoginPassword, setSarsLoginPassword] = useState(""); @@ -110,6 +181,17 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter setPostalAddress(d.postal_address ?? ""); setDirectorType(d.director_type ?? ""); setNotes(d.notes ?? ""); + setDateOfBirth(d.date_of_birth ?? ""); + setResAddr1(d.residential_address_line_1 ?? ""); + setResAddr2(d.residential_address_line_2 ?? ""); + setResTown(d.residential_town_city ?? ""); + setResProvince(d.residential_province ?? ""); + setResPostalCode(d.residential_postal_code ?? ""); + setPostAddr1(d.postal_address_line_1 ?? ""); + setPostAddr2(d.postal_address_line_2 ?? ""); + setPostTown(d.postal_town_city ?? ""); + setPostProvince(d.postal_province ?? ""); + setPostPostalCode(d.postal_postal_code ?? ""); } else { const c = record as Company; setEnterpriseNo(c.enterprise_no ?? ""); @@ -120,6 +202,21 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter setPostalAddressCo(c.postal_address ?? ""); setCompanyEmail(c.company_email ?? ""); setNotesCo(c.notes ?? ""); + setPhysAddr1(c.physical_address_line_1 ?? ""); + setPhysAddr2(c.physical_address_line_2 ?? ""); + setPhysTown(c.physical_town_city ?? ""); + setPhysProvince(c.physical_province ?? ""); + setPhysPostalCode(c.physical_postal_code ?? ""); + setPostCoAddr1(c.postal_address_line_1 ?? ""); + setPostCoAddr2(c.postal_address_line_2 ?? ""); + setPostCoTown(c.postal_town_city ?? ""); + setPostCoProvince(c.postal_province ?? ""); + setPostCoPostalCode(c.postal_postal_code ?? ""); + setFinancialYearEndMonth(c.financial_year_end_month != null ? String(c.financial_year_end_month) : ""); + setAuthorisedShares(c.authorised_shares != null ? String(c.authorised_shares) : ""); + setCompanyCellPhone(c.company_cell_phone ?? ""); + setPublicOfficerName(c.public_officer_name ?? ""); + setCompanySecretaryName(c.company_secretary_name ?? ""); setSarsLoginUsername(c.sars_login_username ?? ""); setSarsLoginPassword(c.sars_login_password ?? ""); setSarsPayeNo(c.sars_paye_no ?? ""); @@ -167,6 +264,18 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter if (postalAddress.trim()) fields.postal_address = postalAddress.trim(); if (directorType.trim()) fields.director_type = directorType.trim(); if (notes.trim()) fields.notes = notes.trim(); + // Structured address fields (added 2026-08-18) + if (dateOfBirth.trim()) fields.date_of_birth = dateOfBirth.trim(); + if (resAddr1.trim()) fields.residential_address_line_1 = resAddr1.trim(); + if (resAddr2.trim()) fields.residential_address_line_2 = resAddr2.trim(); + if (resTown.trim()) fields.residential_town_city = resTown.trim(); + if (resProvince.trim()) fields.residential_province = resProvince.trim(); + if (resPostalCode.trim()) fields.residential_postal_code = resPostalCode.trim(); + if (postAddr1.trim()) fields.postal_address_line_1 = postAddr1.trim(); + if (postAddr2.trim()) fields.postal_address_line_2 = postAddr2.trim(); + if (postTown.trim()) fields.postal_town_city = postTown.trim(); + if (postProvince.trim()) fields.postal_province = postProvince.trim(); + if (postPostalCode.trim()) fields.postal_postal_code = postPostalCode.trim(); } else { if (!enterpriseNo.trim() || !enterpriseName.trim()) { setError("Enterprise number and name are required."); @@ -181,12 +290,33 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter if (postalAddressCo.trim()) fields.postal_address = postalAddressCo.trim(); if (companyEmail.trim()) fields.company_email = companyEmail.trim(); if (notesCo.trim()) fields.notes = notesCo.trim(); + // Structured address fields (added 2026-08-18) + if (physAddr1.trim()) fields.physical_address_line_1 = physAddr1.trim(); + if (physAddr2.trim()) fields.physical_address_line_2 = physAddr2.trim(); + if (physTown.trim()) fields.physical_town_city = physTown.trim(); + if (physProvince.trim()) fields.physical_province = physProvince.trim(); + if (physPostalCode.trim()) fields.physical_postal_code = physPostalCode.trim(); + if (postCoAddr1.trim()) fields.postal_address_line_1 = postCoAddr1.trim(); + if (postCoAddr2.trim()) fields.postal_address_line_2 = postCoAddr2.trim(); + if (postCoTown.trim()) fields.postal_town_city = postCoTown.trim(); + if (postCoProvince.trim()) fields.postal_province = postCoProvince.trim(); + if (postCoPostalCode.trim()) fields.postal_postal_code = postCoPostalCode.trim(); + // Company-level fields (added 2026-08-18) + if (financialYearEndMonth !== "" || (record as Company).financial_year_end_month != null) { + fields.financial_year_end_month = financialYearEndMonth !== "" ? Number(financialYearEndMonth) : 0; + } + if (authorisedShares !== "" || (record as Company).authorised_shares != null) { + fields.authorised_shares = authorisedShares !== "" ? Number(authorisedShares) : 0; + } + if (companyCellPhone.trim()) fields.company_cell_phone = companyCellPhone.trim(); + if (publicOfficerName.trim()) fields.public_officer_name = publicOfficerName.trim(); + if (companySecretaryName.trim()) fields.company_secretary_name = companySecretaryName.trim(); if (sarsLoginUsername.trim()) fields.sars_login_username = sarsLoginUsername.trim(); if (sarsLoginPassword.trim()) fields.sars_login_password = sarsLoginPassword.trim(); if (sarsPayeNo.trim()) fields.sars_paye_no = sarsPayeNo.trim(); if (sarsVatNo.trim()) fields.sars_vat_no = sarsVatNo.trim(); if (sarsTaxNo.trim()) fields.sars_tax_number = sarsTaxNo.trim(); - // Period fields: store the selected numeric value as string (PocketBase number field accepts string->number conversion) + // Period fields const vatPeriod = sarsVatReturnsPeriod; const payePeriod = sarsPayeReturnPeriod; if (vatPeriod !== "" || (record as Company).sars_vat_returns_period != null) { @@ -215,6 +345,9 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter } }; + const monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"]; + return (

+
+ + setDateOfBirth(e.target.value)} + className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" /> +
+
@@ -291,12 +431,100 @@ export default function EditEntityModal({ entity, record, open, onClose, onAfter
- +