WORKFLOW-APP/components/EditEntityModal.tsx
Ysterbaard 9bd35898a2 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].
2026-08-18 12:25:06 +02:00

853 lines
52 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
// components/EditEntityModal.tsx
// Pre-filled edit form for existing director or company records.
// Opens from the Edit button on a table row or from the detail page.
// PUT/PATCH via /api/pb/records/:id, invalidates list + detail cache.
import { useState, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { pbPatch, pbList } from "@/lib/api";
interface Director {
id: string;
surname?: string | null;
first_names?: string | null;
id_no?: string | null;
telephone?: string | null;
email?: string | null;
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 {
id: string;
enterprise_no?: string | null;
enterprise_name?: string | null;
enterprise_type?: string | null;
registration_date?: string | null;
physical_address?: string | null;
postal_address?: string | null;
company_email?: string | null;
director?: string | null;
notes?: string | null;
sars_login_username?: string | null;
sars_login_password?: string | null;
sars_paye_no?: string | null;
sars_vat_no?: string | null;
sars_tax_number?: string | null;
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 {
id: string;
name: string;
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 */
record: Director | Company;
open: boolean;
onClose: () => void;
/** Called after a successful save */
onAfterSave?: () => void;
}
export default function EditEntityModal({ entity, record, open, onClose, onAfterSave }: Props) {
const qc = useQueryClient();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Director fields
const [surname, setSurname] = useState("");
const [firstNames, setFirstNames] = useState("");
const [idNo, setIdNo] = useState("");
const [telephone, setTelephone] = useState("");
const [email, setEmail] = useState("");
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("");
const [enterpriseName, setEnterpriseName] = useState("");
const [enterpriseType, setEnterpriseType] = useState("");
const [registrationDate, setRegistrationDate] = useState("");
const [physicalAddress, setPhysicalAddress] = useState("");
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("");
const [sarsPayeNo, setSarsPayeNo] = useState("");
const [sarsVatNo, setSarsVatNo] = useState("");
const [sarsTaxNo, setSarsTaxNo] = useState("");
const [sarsVatReturnsPeriod, setSarsVatReturnsPeriod] = useState("");
const [sarsPayeReturnPeriod, setSarsPayeReturnPeriod] = useState("");
// Group fields
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
const [groupOptions, setGroupOptions] = useState<GroupOption[]>([]);
// Populate form when modal opens or record changes
useEffect(() => {
if (!open) return;
setError(null);
if (entity === "director") {
const d = record as Director;
setSurname(d.surname ?? "");
setFirstNames(d.first_names ?? "");
setIdNo(d.id_no ?? "");
setTelephone(d.telephone ?? "");
setEmail(d.email ?? "");
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 ?? "");
setEnterpriseName(c.enterprise_name ?? "");
setEnterpriseType(c.enterprise_type ?? "");
setRegistrationDate(c.registration_date ?? "");
setPhysicalAddress(c.physical_address ?? "");
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 ?? "");
setSarsVatNo(c.sars_vat_no ?? "");
setSarsTaxNo(c.sars_tax_number ?? "");
setSarsVatReturnsPeriod(c.sars_vat_returns_period != null ? String(c.sars_vat_returns_period) : "");
setSarsPayeReturnPeriod(c.sars_paye_return_period != null ? String(c.sars_paye_return_period) : "");
setSelectedGroups(c.groups ?? []);
}
}, [open, entity, record]);
// Fetch group options when modal opens (company entity only)
useEffect(() => {
if (!open || entity !== "company") return;
pbList<GroupOption>({ collection: "groups", perPage: 200, sort: "name" })
.then((res) => setGroupOptions(res.items))
.catch(() => {});
}, [open, entity]);
if (!open) return null;
const isDirector = entity === "director";
const title = isDirector ? `Edit director — ${(record as Director).surname}, ${(record as Director).first_names}` : `Edit company — ${(record as Company).enterprise_name}`;
const collection = isDirector ? "ysterbaard_directors" : "ysterbaard_companies";
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
const fields: Record<string, string | number | string[]> = {};
if (isDirector) {
if (!surname.trim() || !firstNames.trim()) {
setError("Surname and first names are required.");
setLoading(false);
return;
}
fields.surname = surname.trim();
fields.first_names = firstNames.trim();
if (idNo.trim()) fields.id_no = idNo.trim();
if (telephone.trim()) fields.telephone = telephone.trim();
if (email.trim()) fields.email = email.trim();
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.");
setLoading(false);
return;
}
fields.enterprise_no = enterpriseNo.trim().toUpperCase();
fields.enterprise_name = enterpriseName.trim().toUpperCase();
if (enterpriseType.trim()) fields.enterprise_type = enterpriseType.trim();
if (registrationDate.trim()) fields.registration_date = registrationDate.trim();
if (physicalAddress.trim()) fields.physical_address = physicalAddress.trim();
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
const vatPeriod = sarsVatReturnsPeriod;
const payePeriod = sarsPayeReturnPeriod;
if (vatPeriod !== "" || (record as Company).sars_vat_returns_period != null) {
fields.sars_vat_returns_period = vatPeriod !== "" ? Number(vatPeriod) : 0;
}
if (payePeriod !== "" || (record as Company).sars_paye_return_period != null) {
fields.sars_paye_return_period = payePeriod !== "" ? Number(payePeriod) : 0;
}
// Groups — always send current selection (empty array is valid to clear)
fields.groups = selectedGroups;
}
await pbPatch({ collection, id: record.id, fields });
// Invalidate both list and detail caches
await qc.invalidateQueries({ queryKey: ["pb-list", collection] });
await qc.invalidateQueries({ queryKey: ["director", record.id] });
await qc.invalidateQueries({ queryKey: ["company", record.id] });
onAfterSave?.();
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
} finally {
setLoading(false);
}
};
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
role="dialog"
aria-modal="true"
aria-label={title}
>
<div className="bg-surface border border-border rounded-xl shadow-2xl w-full max-w-lg flex flex-col max-h-[90vh]">
{/* Header */}
<div className="px-6 pt-6 pb-4 border-b border-border flex-shrink-0">
<div className="flex items-start justify-between">
<div>
<h2 className="text-lg font-bold text-fg">Edit {isDirector ? "director" : "company"}</h2>
<p className="text-sm text-muted mt-0.5">{title}</p>
</div>
<button
onClick={onClose}
className="ml-4 text-muted hover:text-fg text-xl leading-none p-1 rounded transition-colors"
aria-label="Close"
>
×
</button>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 text-sm text-red-400">
{error}
</div>
)}
{isDirector ? (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-surname">Surname <span className="text-red-400">*</span></label>
<input id="edit-surname" type="text" value={surname}
onChange={(e) => setSurname(e.target.value.toUpperCase())}
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"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-firstnames">First names <span className="text-red-400">*</span></label>
<input id="edit-firstnames" type="text" value={firstNames}
onChange={(e) => setFirstNames(e.target.value.toUpperCase())}
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"
required />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-idno">SA ID number <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-idno" type="tel" value={idNo}
onChange={(e) => setIdNo(e.target.value.replace(/\D/g, "").slice(0, 13))}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
maxLength={13} />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-dob">Date of birth <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-dob" type="date" value={dateOfBirth}
onChange={(e) => 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" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-tel">Telephone <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-tel" type="tel" value={telephone}
onChange={(e) => setTelephone(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" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-email">Email <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-email" type="email" value={email}
onChange={(e) => setEmail(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" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-postal">Postal address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-postal" value={postalAddress} rows={2}
onChange={(e) => setPostalAddress(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Structured residential address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Residential address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-res1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-res1" type="text" value={resAddr1}
onChange={(e) => setResAddr1(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-res2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-res2" type="text" value={resAddr2}
onChange={(e) => setResAddr2(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" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-restown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-restown" type="text" value={resTown}
onChange={(e) => setResTown(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-resprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-resprov" value={resProvince}
onChange={(e) => setResProvince(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">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-reszip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-reszip" type="text" value={resPostalCode}
onChange={(e) => setResPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
{/* Structured postal address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Postal address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-post1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-post1" type="text" value={postAddr1}
onChange={(e) => setPostAddr1(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-post2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-post2" type="text" value={postAddr2}
onChange={(e) => setPostAddr2(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" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-posttown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-posttown" type="text" value={postTown}
onChange={(e) => setPostTown(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-postprov" value={postProvince}
onChange={(e) => setPostProvince(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">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postzip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postzip" type="text" value={postPostalCode}
onChange={(e) => setPostPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-type">Director type <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-type" type="text" value={directorType}
onChange={(e) => setDirectorType(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"
placeholder="e.g. Individual, Corporate" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-notes">Notes <span className="text-xs text-muted font-normal">(optional)</span></label>
<textarea id="edit-notes" value={notes} rows={2}
onChange={(e) => setNotes(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</>
) : (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-eno">Enterprise No. <span className="text-red-400">*</span></label>
<input id="edit-eno" type="text" value={enterpriseNo}
onChange={(e) => setEnterpriseNo(e.target.value.toUpperCase())}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-etype">Enterprise type <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-etype" type="text" value={enterpriseType}
onChange={(e) => setEnterpriseType(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"
placeholder="Private Company" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-ename">Enterprise name <span className="text-red-400">*</span></label>
<input id="edit-ename" type="text" value={enterpriseName}
onChange={(e) => setEnterpriseName(e.target.value.toUpperCase())}
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"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-rdate">Registration date <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-rdate" type="date" value={registrationDate}
onChange={(e) => setRegistrationDate(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" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-phys">Physical address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-phys" value={physicalAddress} rows={2}
onChange={(e) => setPhysicalAddress(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-postalco">Postal address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-postalco" value={postalAddressCo} rows={2}
onChange={(e) => setPostalAddressCo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Structured physical address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Physical address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phys1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phys1" type="text" value={physAddr1}
onChange={(e) => setPhysAddr1(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phys2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phys2" type="text" value={physAddr2}
onChange={(e) => setPhysAddr2(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" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phystown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phystown" type="text" value={physTown}
onChange={(e) => setPhysTown(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-physprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-physprov" value={physProvince}
onChange={(e) => setPhysProvince(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">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-physzip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-physzip" type="text" value={physPostalCode}
onChange={(e) => setPhysPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
{/* Structured postal address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Postal address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postco1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postco1" type="text" value={postCoAddr1}
onChange={(e) => setPostCoAddr1(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postco2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postco2" type="text" value={postCoAddr2}
onChange={(e) => setPostCoAddr2(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" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcotown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postcotown" type="text" value={postCoTown}
onChange={(e) => setPostCoTown(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcoprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-postcoprov" value={postCoProvince}
onChange={(e) => setPostCoProvince(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">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcozip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postcozip" type="text" value={postCoPostalCode}
onChange={(e) => setPostCoPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-emailco">Company email <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-emailco" type="email" value={companyEmail}
onChange={(e) => setCompanyEmail(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"
placeholder="company@example.com" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-cellco">Company cell phone <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-cellco" type="tel" value={companyCellPhone}
onChange={(e) => setCompanyCellPhone(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" />
</div>
{/* Company-level fields (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Company details</p>
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-fye">Financial year end <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-fye" value={financialYearEndMonth}
onChange={(e) => setFinancialYearEndMonth(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">
<option value="">— select —</option>
{monthNames.map((m, i) => (
<option key={i + 1} value={String(i + 1)}>{m}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-shares">Authorised shares <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-shares" value={authorisedShares}
onChange={(e) => setAuthorisedShares(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">
<option value="">— select —</option>
<option value="100">100</option>
<option value="1000">1,000</option>
<option value="10000">10,000</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-officer">Public officer <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-officer" type="text" value={publicOfficerName}
onChange={(e) => setPublicOfficerName(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" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-secretary">Company secretary <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-secretary" type="text" value={companySecretaryName}
onChange={(e) => setCompanySecretaryName(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" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-notesco">Notes <span className="text-xs text-muted font-normal">(optional)</span></label>
<textarea id="edit-notesco" value={notesCo} rows={2}
onChange={(e) => setNotesCo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Group membership */}
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-groups">Group Membership <span className="text-xs text-muted font-normal">(optional)</span></label>
<p className="text-xs text-muted mb-2">Hold Ctrl (or Cmd on Mac) to select multiple groups.</p>
<select
id="edit-groups"
multiple
value={selectedGroups}
onChange={(e) => {
const opts = Array.from(e.target.selectedOptions).map((o) => o.value);
setSelectedGroups(opts);
}}
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 min-h-[100px]">
{groupOptions.length === 0 ? (
<option value="" disabled>No groups created yet — save and add groups via PocketBase</option>
) : (
groupOptions.map((g) => (
<option key={g.id} value={g.id}>{g.name}</option>
))
)}
</select>
</div>
{/* SARS credentials */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">SARS eFiling Details</p>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-user">SARS Login Username <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-user" type="text" value={sarsLoginUsername}
onChange={(e) => setSarsLoginUsername(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"
placeholder="eFiling username" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-pass">SARS Login Password <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-pass" type="text" value={sarsLoginPassword}
onChange={(e) => setSarsLoginPassword(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"
placeholder="eFiling password" />
</div>
</div>
<div className="grid grid-cols-2 gap-4 mt-3">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-paye">SARS PAYE No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-paye" type="text" value={sarsPayeNo}
onChange={(e) => setSarsPayeNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 7020829828" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-vat">SARS VAT No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-vat" type="text" value={sarsVatNo}
onChange={(e) => setSarsVatNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 4123456789" />
</div>
</div>
<div className="mt-3">
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-tax">SARS Tax No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-tax" type="text" value={sarsTaxNo}
onChange={(e) => setSarsTaxNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 9004615283" />
</div>
<div className="grid grid-cols-2 gap-4 mt-3">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-vat-period">SARS VAT Returns Period</label>
<select id="edit-sars-vat-period" value={sarsVatReturnsPeriod}
onChange={(e) => setSarsVatReturnsPeriod(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">
<option value="">— select —</option>
<option value="0">None</option>
<option value="1">Every Month</option>
<option value="2">Even Month</option>
<option value="3">Uneven Months</option>
<option value="6">Bi Annually</option>
<option value="12">Annually</option>
</select>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-paye-period">SARS PAYE Returns Period</label>
<select id="edit-sars-paye-period" value={sarsPayeReturnPeriod}
onChange={(e) => setSarsPayeReturnPeriod(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">
<option value="">— select —</option>
<option value="0">None</option>
<option value="1">Every month</option>
</select>
</div>
</div>
</div>
</>
)}
<div className="pt-2 flex gap-3">
<button type="button" onClick={onClose}
className="flex-1 px-4 py-2.5 rounded-lg border border-border bg-surface-warm text-sm font-semibold text-fg hover:bg-surface transition-colors">
Cancel
</button>
<button type="submit" disabled={loading}
className="flex-1 px-4 py-2.5 rounded-lg bg-accent text-accent-on text-sm font-bold hover:bg-accent-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed">
{loading ? "Saving..." : "Save changes"}
</button>
</div>
</form>
</div>
</div>
);
}