2026-07-30 14:38:05 +00:00
"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.
2026-08-11 16:33:03 +00:00
import { useState , useEffect } from "react" ;
2026-07-30 14:38:05 +00:00
import { useQueryClient } from "@tanstack/react-query" ;
2026-08-11 16:33:03 +00:00
import { pbPatch , pbList } from "@/lib/api" ;
2026-07-30 14:38:05 +00:00
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 ;
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 10:25:06 +00:00
// ─── 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 ;
2026-07-30 14:38:05 +00:00
}
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 ;
2026-08-17 18:08:33 +00:00
company_email? : string | null ;
2026-07-30 14:38:05 +00:00
director? : string | null ;
notes? : string | null ;
2026-08-11 16:33:03 +00:00
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 ;
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 10:25:06 +00:00
// ─── 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 ;
2026-08-11 16:33:03 +00:00
}
interface GroupOption {
id : string ;
name : string ;
description? : string | null ;
2026-07-30 14:38:05 +00:00
}
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 10:25:06 +00:00
const SA_PROVINCES = [
"Eastern Cape" ,
"Free State" ,
"Gauteng" ,
"KwaZulu-Natal" ,
"Limpopo" ,
"Mpumalanga" ,
"Northern Cape" ,
"North West" ,
"Western Cape" ,
] ;
2026-07-30 14:38:05 +00:00
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 ( "" ) ;
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 10:25:06 +00:00
// 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 ( "" ) ;
2026-07-30 14:38:05 +00:00
// 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 ( "" ) ;
2026-08-17 18:08:33 +00:00
const [ companyEmail , setCompanyEmail ] = useState ( "" ) ;
2026-07-30 14:38:05 +00:00
const [ notesCo , setNotesCo ] = useState ( "" ) ;
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 10:25:06 +00:00
// 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 ( "" ) ;
2026-08-11 16:33:03 +00:00
// 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 [ ] > ( [ ] ) ;
2026-07-30 14:38:05 +00:00
// 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 ? ? "" ) ;
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 10:25:06 +00:00
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 ? ? "" ) ;
2026-07-30 14:38:05 +00:00
} 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 ? ? "" ) ;
2026-08-17 18:08:33 +00:00
setCompanyEmail ( c . company_email ? ? "" ) ;
2026-07-30 14:38:05 +00:00
setNotesCo ( c . notes ? ? "" ) ;
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 10:25:06 +00:00
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 ? ? "" ) ;
2026-08-11 16:33:03 +00:00
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 ? ? [ ] ) ;
2026-07-30 14:38:05 +00:00
}
} , [ open , entity , record ] ) ;
2026-08-11 16:33:03 +00:00
// 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 ] ) ;
2026-07-30 14:38:05 +00:00
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 {
2026-08-11 16:33:03 +00:00
const fields : Record < string , string | number | string [ ] > = { } ;
2026-07-30 14:38:05 +00:00
2026-08-19 10:04:58 +00:00
// Always send every field — empty string clears the value in PB.
// The previous "if (value.trim())" guard LOST the user's intent to
// clear a field: empty fields were silently skipped, so a delete in
// the UI never landed in PB. We now send the literal value the user
// left in the field, including "" to clear, and only validate
// required fields up-front.
2026-07-30 14:38:05 +00:00
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 ( ) ;
2026-08-19 10:04:58 +00:00
fields . id_no = idNo . trim ( ) ;
fields . telephone = telephone . trim ( ) ;
fields . email = email . trim ( ) ;
fields . postal_address = postalAddress . trim ( ) ;
fields . director_type = directorType . trim ( ) ;
fields . notes = notes . trim ( ) ;
// Structured address fields
fields . date_of_birth = dateOfBirth . trim ( ) ;
fields . residential_address_line_1 = resAddr1 . trim ( ) ;
fields . residential_address_line_2 = resAddr2 . trim ( ) ;
fields . residential_town_city = resTown . trim ( ) ;
fields . residential_province = resProvince . trim ( ) ;
fields . residential_postal_code = resPostalCode . trim ( ) ;
fields . postal_address_line_1 = postAddr1 . trim ( ) ;
fields . postal_address_line_2 = postAddr2 . trim ( ) ;
fields . postal_town_city = postTown . trim ( ) ;
fields . postal_province = postProvince . trim ( ) ;
fields . postal_postal_code = postPostalCode . trim ( ) ;
2026-07-30 14:38:05 +00:00
} 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 ( ) ;
2026-08-19 10:04:58 +00:00
fields . enterprise_type = enterpriseType . trim ( ) ;
fields . registration_date = registrationDate . trim ( ) ;
fields . physical_address = physicalAddress . trim ( ) ;
fields . postal_address = postalAddressCo . trim ( ) ;
fields . company_email = companyEmail . trim ( ) ;
fields . notes = notesCo . trim ( ) ;
// Structured address fields
fields . physical_address_line_1 = physAddr1 . trim ( ) ;
fields . physical_address_line_2 = physAddr2 . trim ( ) ;
fields . physical_town_city = physTown . trim ( ) ;
fields . physical_province = physProvince . trim ( ) ;
fields . physical_postal_code = physPostalCode . trim ( ) ;
fields . postal_address_line_1 = postCoAddr1 . trim ( ) ;
fields . postal_address_line_2 = postCoAddr2 . trim ( ) ;
fields . postal_town_city = postCoTown . trim ( ) ;
fields . postal_province = postCoProvince . trim ( ) ;
fields . postal_postal_code = postCoPostalCode . trim ( ) ;
// Company-level fields
fields . financial_year_end_month = financialYearEndMonth !== "" ? Number ( financialYearEndMonth ) : 0 ;
fields . authorised_shares = authorisedShares !== "" ? Number ( authorisedShares ) : 0 ;
fields . company_cell_phone = companyCellPhone . trim ( ) ;
fields . public_officer_name = publicOfficerName . trim ( ) ;
fields . company_secretary_name = companySecretaryName . trim ( ) ;
fields . sars_login_username = sarsLoginUsername . trim ( ) ;
fields . sars_login_password = sarsLoginPassword . trim ( ) ;
fields . sars_paye_no = sarsPayeNo . trim ( ) ;
fields . sars_vat_no = sarsVatNo . trim ( ) ;
fields . sars_tax_number = sarsTaxNo . trim ( ) ;
fields . sars_vat_returns_period = sarsVatReturnsPeriod !== "" ? Number ( sarsVatReturnsPeriod ) : 0 ;
fields . sars_paye_return_period = sarsPayeReturnPeriod !== "" ? Number ( sarsPayeReturnPeriod ) : 0 ;
2026-08-11 16:33:03 +00:00
// Groups — always send current selection (empty array is valid to clear)
fields . groups = selectedGroups ;
2026-07-30 14:38:05 +00:00
}
await pbPatch ( { collection , id : record.id , fields } ) ;
// Invalidate both list and detail caches
2026-08-19 10:04:58 +00:00
// Invalidate every key the detail page or list pages might be holding.
// The list pages use "companies" / "directors" keys (not "pb-list"),
// and the detail page uses "director" / "company" keys.
2026-07-30 14:38:05 +00:00
await qc . invalidateQueries ( { queryKey : [ "pb-list" , collection ] } ) ;
2026-08-19 10:04:58 +00:00
await qc . invalidateQueries ( { queryKey : [ "companies" ] } ) ;
await qc . invalidateQueries ( { queryKey : [ "directors" ] } ) ;
2026-07-30 14:38:05 +00:00
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 ) ;
}
} ;
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 10:25:06 +00:00
const monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" ,
"July" , "August" , "September" , "October" , "November" , "December" ] ;
2026-07-30 14:38:05 +00:00
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 >
2026-07-30 15:53:41 +00:00
< input id = "edit-idno" type = "tel" value = { idNo }
2026-07-30 14:38:05 +00:00
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 >
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 10:25:06 +00:00
< 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 >
2026-07-30 14:38:05 +00:00
< 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 >
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 10:25:06 +00:00
< 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 >
2026-07-30 14:38:05 +00:00
< 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 >
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 10:25:06 +00:00
{ /* 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 >
2026-07-30 14:38:05 +00:00
< 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 >
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 10:25:06 +00:00
< 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 >
2026-07-30 14:38:05 +00:00
< 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 >
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 10:25:06 +00:00
< 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 >
2026-07-30 14:38:05 +00:00
< 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 >
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 10:25:06 +00:00
{ /* 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 >
2026-08-17 18:08:33 +00:00
< 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 >
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 10:25:06 +00:00
< 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 >
2026-07-30 14:38:05 +00:00
< 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 >
2026-08-11 16:33:03 +00:00
{ /* 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 >
2026-07-30 14:38:05 +00:00
< / >
) }
< 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 >
) ;
}