feat(crm): country dropdown + name guard for customer create/edit
Replace the free-text 2-char country code field on the inline customer create form and the customer detail page with a dropdown that shows localized country names (Intl.DisplayNames, no hardcoded map) while still storing the ISO 3166-1 alpha-2 code. The create form now seeds the default country from the business profile instead of leaving it blank or guessing CH/FL. The free-text countryName override is kept for the rare case where an operator wants a custom display string. Standardize Liechtenstein on the ISO code LI instead of the colloquial plate code FL so it matches the PDF renderer's locale-aware lookup and the new dropdown. Migration 110 normalizes existing FL rows to LI on customer_accounts and business_profile (idempotent, case-insensitive). Require at least one human-readable identifier (company name or a contact name) at create time so the form can't produce a nameless row that's impossible to recognise in lists later. Enforced on both the frontend (isValid + toast) and the backend POST /admin/customers validator so the API can't be bypassed. i18n: en + de updated; other locales fall back to inline English defaults and should get a native review before release.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Country codes offered in the customer country picker. Stored value is
|
||||
* always the ISO 3166-1 alpha-2 code; Liechtenstein is `LI` (NOT the
|
||||
* colloquial `FL` plate code) so it matches ISO + the PDF renderer's
|
||||
* lookup. Display names are derived at runtime from `Intl.DisplayNames`
|
||||
* in the active UI language, so the list stays locale-aware without a
|
||||
* hand-maintained translation map.
|
||||
*/
|
||||
export const COUNTRY_CODES = [
|
||||
'LI', 'CH', 'AT', 'DE', 'FR', 'IT', 'ES', 'PT', 'NL', 'BE', 'LU',
|
||||
'GB', 'US', 'DK', 'SE', 'NO', 'FI', 'PL', 'CZ', 'SK', 'HU', 'IE',
|
||||
] as const;
|
||||
|
||||
export type CountryCode = (typeof COUNTRY_CODES)[number];
|
||||
|
||||
/** Localized country name for an ISO code, falling back to the code. */
|
||||
export function countryLabel(code: string, lang: string): string {
|
||||
if (!code) return '';
|
||||
const upper = code.trim().toUpperCase();
|
||||
try {
|
||||
return new Intl.DisplayNames([lang || 'en'], { type: 'region' }).of(upper) || upper;
|
||||
} catch {
|
||||
return upper;
|
||||
}
|
||||
}
|
||||
|
||||
/** Country codes sorted by their localized label for the active language. */
|
||||
export function sortedCountryOptions(lang: string): { code: string; label: string }[] {
|
||||
return COUNTRY_CODES.map((code) => ({ code, label: countryLabel(code, lang) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label, lang || 'en'));
|
||||
}
|
||||
Reference in New Issue
Block a user