@traky-app/ui 1.6.1 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -1
- package/registry/traky/json/city-select.json +19 -0
- package/registry/traky/json/country-select.json +24 -0
- package/registry/traky/json/geo-search-select.json +24 -0
- package/registry/traky/json/geo-selectors.json +21 -0
- package/registry/traky/json/google-maps-address-picker.json +22 -0
- package/registry/traky/json/internal-chat-mention.json +1 -1
- package/registry/traky/json/internal-chat-panel.json +1 -1
- package/registry/traky/json/lib-geo-city-codes.json +14 -0
- package/registry/traky/json/lib-geo-countries.json +14 -0
- package/registry/traky/json/lib-geo-csc.json +17 -0
- package/registry/traky/json/lib-geo-locations.json +14 -0
- package/registry/traky/json/lib-geo-overrides.json +14 -0
- package/registry/traky/json/lib-geo-types.json +14 -0
- package/registry/traky/json/lib-google-maps-address.json +14 -0
- package/registry/traky/json/lib-phone-countries.json +14 -0
- package/registry/traky/json/lib-phone-e164.json +14 -0
- package/registry/traky/json/lib-utils.json +1 -1
- package/registry/traky/json/office-address-form-dialog.json +17 -0
- package/registry/traky/json/office-address-form.json +31 -0
- package/registry/traky/json/phone-number-input.json +25 -0
- package/registry/traky/json/state-select.json +19 -0
- package/registry/traky/lib/geo-city-codes.ts +234 -0
- package/registry/traky/lib/geo-countries.ts +75 -0
- package/registry/traky/lib/geo-csc.ts +82 -0
- package/registry/traky/lib/geo-locations.ts +171 -0
- package/registry/traky/lib/geo-overrides.ts +375 -0
- package/registry/traky/lib/geo-types.ts +34 -0
- package/registry/traky/lib/google-maps-address.ts +100 -0
- package/registry/traky/lib/phone-countries.ts +380 -0
- package/registry/traky/lib/phone-e164.ts +66 -0
- package/registry/traky/lib/utils.ts +24 -0
- package/registry/traky/registry.json +180 -0
- package/registry/traky/ui/city-select.tsx +113 -0
- package/registry/traky/ui/country-select.tsx +233 -0
- package/registry/traky/ui/geo-search-select.tsx +136 -0
- package/registry/traky/ui/geo-selectors.tsx +162 -0
- package/registry/traky/ui/google-maps-address-picker.tsx +422 -0
- package/registry/traky/ui/internal-chat-mention.ts +34 -3
- package/registry/traky/ui/internal-chat-panel.tsx +147 -12
- package/registry/traky/ui/office-address-form-dialog.tsx +16 -0
- package/registry/traky/ui/office-address-form.tsx +868 -0
- package/registry/traky/ui/phone-number-input.tsx +253 -0
- package/registry/traky/ui/state-select.tsx +107 -0
- package/src/lib/geo-city-codes.ts +234 -0
- package/src/lib/geo-countries.ts +75 -0
- package/src/lib/geo-csc.ts +82 -0
- package/src/lib/geo-locations.ts +171 -0
- package/src/lib/geo-overrides.ts +375 -0
- package/src/lib/geo-types.ts +34 -0
- package/src/lib/google-maps-address.ts +100 -0
- package/src/lib/phone-countries.ts +380 -0
- package/src/lib/phone-e164.ts +66 -0
- package/src/test/setup.ts +31 -0
- package/src/ui/__tests__/geo-selects.test.tsx +504 -0
- package/src/ui/city-select.tsx +113 -0
- package/src/ui/country-select.tsx +233 -0
- package/src/ui/geo-search-select.tsx +136 -0
- package/src/ui/geo-selectors.tsx +162 -0
- package/src/ui/google-maps-address-picker.tsx +422 -0
- package/src/ui/internal-chat-mention.ts +34 -3
- package/src/ui/internal-chat-panel.tsx +147 -12
- package/src/ui/office-address-form-dialog.tsx +16 -0
- package/src/ui/office-address-form.tsx +877 -0
- package/src/ui/phone-number-input.tsx +253 -0
- package/src/ui/state-select.tsx +107 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Códigos cortos de ciudad (logística / etiquetas).
|
|
3
|
+
* Clave: `{ISO2}:{NombreCiudad}` → código de 3 letras.
|
|
4
|
+
*/
|
|
5
|
+
const CITY_CODE_OVERRIDES: Record<string, string> = {
|
|
6
|
+
// Ecuador
|
|
7
|
+
"EC:Guayaquil": "GYE",
|
|
8
|
+
"EC:Quito": "UIO",
|
|
9
|
+
"EC:Cuenca": "CUE",
|
|
10
|
+
"EC:Machala": "MCH",
|
|
11
|
+
"EC:Manta": "MEC",
|
|
12
|
+
"EC:Portoviejo": "PVO",
|
|
13
|
+
"EC:Ambato": "ATF",
|
|
14
|
+
"EC:Loja": "LOH",
|
|
15
|
+
"EC:Esmeraldas": "ESM",
|
|
16
|
+
"EC:Ibarra": "IBB",
|
|
17
|
+
"EC:Riobamba": "RIB",
|
|
18
|
+
"EC:Santo Domingo": "STD",
|
|
19
|
+
"EC:Milagro": "MLG",
|
|
20
|
+
"EC:Durán": "DUR",
|
|
21
|
+
"EC:Samborondón": "SBO",
|
|
22
|
+
// Colombia
|
|
23
|
+
"CO:Bogotá": "BOG",
|
|
24
|
+
"CO:Bogota": "BOG",
|
|
25
|
+
"CO:Medellín": "MDE",
|
|
26
|
+
"CO:Medellin": "MDE",
|
|
27
|
+
"CO:Cali": "CAL",
|
|
28
|
+
"CO:Barranquilla": "BAQ",
|
|
29
|
+
"CO:Cartagena": "CTG",
|
|
30
|
+
"CO:Bucaramanga": "BGA",
|
|
31
|
+
"CO:Pereira": "PEI",
|
|
32
|
+
"CO:Manizales": "MZL",
|
|
33
|
+
"CO:Cúcuta": "CUC",
|
|
34
|
+
"CO:Cucuta": "CUC",
|
|
35
|
+
"CO:Ibagué": "IBE",
|
|
36
|
+
"CO:Ibague": "IBE",
|
|
37
|
+
"CO:Santa Marta": "SMR",
|
|
38
|
+
// México
|
|
39
|
+
"MX:Ciudad de México": "CDM",
|
|
40
|
+
"MX:Guadalajara": "GDL",
|
|
41
|
+
"MX:Monterrey": "MTY",
|
|
42
|
+
"MX:Puebla": "PBC",
|
|
43
|
+
"MX:Tijuana": "TIJ",
|
|
44
|
+
"MX:León": "BJX",
|
|
45
|
+
"MX:Leon": "BJX",
|
|
46
|
+
// Perú
|
|
47
|
+
"PE:Lima": "LIM",
|
|
48
|
+
"PE:Arequipa": "AQP",
|
|
49
|
+
"PE:Trujillo": "TRU",
|
|
50
|
+
"PE:Cusco": "CUZ",
|
|
51
|
+
// Chile
|
|
52
|
+
"CL:Santiago": "SCL",
|
|
53
|
+
"CL:Valparaíso": "VAP",
|
|
54
|
+
"CL:Valparaiso": "VAP",
|
|
55
|
+
"CL:Concepción": "CCP",
|
|
56
|
+
"CL:Concepcion": "CCP",
|
|
57
|
+
// Argentina
|
|
58
|
+
"AR:Buenos Aires": "BUE",
|
|
59
|
+
"AR:Córdoba": "COR",
|
|
60
|
+
"AR:Cordoba": "COR",
|
|
61
|
+
"AR:Rosario": "ROS",
|
|
62
|
+
"AR:Mendoza": "MDZ",
|
|
63
|
+
// USA (casilleros)
|
|
64
|
+
"US:Miami": "MIA",
|
|
65
|
+
"US:New York": "NYC",
|
|
66
|
+
"US:Los Angeles": "LAX",
|
|
67
|
+
"US:Houston": "HOU",
|
|
68
|
+
"US:Chicago": "ORD",
|
|
69
|
+
// Nicaragua
|
|
70
|
+
"NI:Chontales": "CHO",
|
|
71
|
+
"NI:Juigalpa": "JUI",
|
|
72
|
+
"NI:Managua": "MGA",
|
|
73
|
+
"NI:León": "LEN",
|
|
74
|
+
"NI:Leon": "LEN",
|
|
75
|
+
"NI:Granada": "GRA",
|
|
76
|
+
"NI:Masaya": "MSY",
|
|
77
|
+
"NI:Matagalpa": "MTG",
|
|
78
|
+
// Reino Unido
|
|
79
|
+
"GB:London": "LON",
|
|
80
|
+
"GB:Greater London": "LON",
|
|
81
|
+
"GB:Manchester": "MAN",
|
|
82
|
+
"GB:Birmingham": "BHX",
|
|
83
|
+
"GB:Liverpool": "LIV",
|
|
84
|
+
"GB:Leeds": "LDS",
|
|
85
|
+
"GB:Glasgow": "GLA",
|
|
86
|
+
"GB:Edinburgh": "EDI",
|
|
87
|
+
"GB:Cardiff": "CDF",
|
|
88
|
+
"GB:Belfast": "BEL",
|
|
89
|
+
"GB:England": "ENG",
|
|
90
|
+
"GB:Scotland": "SCT",
|
|
91
|
+
"GB:Wales": "WLS",
|
|
92
|
+
"GB:Northern Ireland": "NIR",
|
|
93
|
+
// España
|
|
94
|
+
"ES:Madrid": "MAD",
|
|
95
|
+
"ES:Barcelona": "BCN",
|
|
96
|
+
"ES:L'Hospitalet de Llobregat": "LHO",
|
|
97
|
+
"ES:LHospitalet de Llobregat": "LHO",
|
|
98
|
+
"ES:Hospitalet de Llobregat": "LHO",
|
|
99
|
+
"ES:Valencia": "VLC",
|
|
100
|
+
"ES:Sevilla": "SVQ",
|
|
101
|
+
"ES:Zaragoza": "ZAZ",
|
|
102
|
+
"ES:Bilbao": "BIO",
|
|
103
|
+
"ES:Malaga": "AGP",
|
|
104
|
+
"ES:Málaga": "AGP",
|
|
105
|
+
"ES:Murcia": "MJV",
|
|
106
|
+
"ES:Palma": "PMI",
|
|
107
|
+
"ES:Las Palmas de Gran Canaria": "LPA",
|
|
108
|
+
"ES:Las Palmas": "LPA",
|
|
109
|
+
"ES:Alicante": "ALC",
|
|
110
|
+
"ES:Cordoba": "COR",
|
|
111
|
+
"ES:Córdoba": "COR",
|
|
112
|
+
"ES:Valladolid": "VLL",
|
|
113
|
+
"ES:Vigo": "VGO",
|
|
114
|
+
"ES:Gijon": "GIJ",
|
|
115
|
+
"ES:Gijón": "GIJ",
|
|
116
|
+
"ES:Oviedo": "OVD",
|
|
117
|
+
"ES:Leon": "LEN",
|
|
118
|
+
"ES:León": "LEN",
|
|
119
|
+
"ES:Burgos": "BUR",
|
|
120
|
+
"ES:Salamanca": "SLM",
|
|
121
|
+
"ES:San Sebastian": "EAS",
|
|
122
|
+
"ES:San Sebastián": "EAS",
|
|
123
|
+
"ES:Santander": "SDR",
|
|
124
|
+
"ES:Pamplona": "PNA",
|
|
125
|
+
"ES:Granada": "GRX",
|
|
126
|
+
"ES:Toledo": "TOL",
|
|
127
|
+
"ES:Badajoz": "BJZ",
|
|
128
|
+
"ES:Castilla y Leon": "CLY",
|
|
129
|
+
"ES:Castilla y León": "CLY",
|
|
130
|
+
"ES:Andalucia": "AND",
|
|
131
|
+
"ES:Andalucía": "AND",
|
|
132
|
+
"ES:Cataluna": "CAT",
|
|
133
|
+
"ES:Cataluña": "CAT",
|
|
134
|
+
"ES:Comunidad Valenciana": "VAL",
|
|
135
|
+
"ES:Comunidad de Madrid": "MAD",
|
|
136
|
+
"ES:Region de Murcia": "MUR",
|
|
137
|
+
"ES:Región de Murcia": "MUR",
|
|
138
|
+
"ES:Pais Vasco": "PV",
|
|
139
|
+
"ES:País Vasco": "PV",
|
|
140
|
+
"ES:Galicia": "GAL",
|
|
141
|
+
"ES:Extremadura": "EXT",
|
|
142
|
+
"ES:Asturias": "AST",
|
|
143
|
+
"ES:Navarra": "NAV",
|
|
144
|
+
"ES:Cantabria": "CAN",
|
|
145
|
+
"ES:La Rioja": "RIO",
|
|
146
|
+
"ES:Islas Baleares": "BAL",
|
|
147
|
+
"ES:Canarias": "CNR",
|
|
148
|
+
"ES:Ceuta": "CEU",
|
|
149
|
+
"ES:Melilla": "MEL",
|
|
150
|
+
// Venezuela
|
|
151
|
+
"VE:Caracas": "CCS",
|
|
152
|
+
"VE:Maracaibo": "MAR",
|
|
153
|
+
"VE:Valencia": "VLN",
|
|
154
|
+
"VE:Barquisimeto": "BRM",
|
|
155
|
+
"VE:Maracay": "MYC",
|
|
156
|
+
"VE:San Cristobal": "SCI",
|
|
157
|
+
"VE:San Cristóbal": "SCI",
|
|
158
|
+
"VE:Puerto La Cruz": "PLC",
|
|
159
|
+
"VE:Maturin": "MUN",
|
|
160
|
+
"VE:Maturín": "MUN",
|
|
161
|
+
"VE:Barcelona": "BLA",
|
|
162
|
+
"VE:Ciudad Guayana": "CGY",
|
|
163
|
+
"VE:Puerto Ordaz": "PZO",
|
|
164
|
+
"VE:Merida": "MER",
|
|
165
|
+
"VE:Mérida": "MER",
|
|
166
|
+
"VE:Punto Fijo": "PFJ",
|
|
167
|
+
"VE:Porlamar": "PMV",
|
|
168
|
+
"VE:San Fernando de Apure": "SFA",
|
|
169
|
+
"VE:Naguanagua": "NGN",
|
|
170
|
+
"VE:Lecheria": "LEC",
|
|
171
|
+
"VE:Lechería": "LEC",
|
|
172
|
+
"VE:Cumana": "CUM",
|
|
173
|
+
"VE:Cumaná": "CUM",
|
|
174
|
+
"VE:Coro": "COR",
|
|
175
|
+
"VE:Barinas": "BAR",
|
|
176
|
+
"VE:Trujillo": "TRU",
|
|
177
|
+
"VE:Valera": "VLR",
|
|
178
|
+
"VE:San Felipe": "SFP",
|
|
179
|
+
"VE:La Guaira": "LGR",
|
|
180
|
+
"VE:Maiquetia": "MAI",
|
|
181
|
+
"VE:Maiquetía": "MAI",
|
|
182
|
+
"VE:Distrito Capital": "CCS",
|
|
183
|
+
"VE:Apure": "APU",
|
|
184
|
+
"VE:Anzoategui": "ANZ",
|
|
185
|
+
"VE:Anzoátegui": "ANZ",
|
|
186
|
+
"VE:Aragua": "ARA",
|
|
187
|
+
"VE:Bolivar": "BOL",
|
|
188
|
+
"VE:Bolívar": "BOL",
|
|
189
|
+
"VE:Carabobo": "CAR",
|
|
190
|
+
"VE:Miranda": "MIR",
|
|
191
|
+
"VE:Zulia": "ZUL",
|
|
192
|
+
"VE:Tachira": "TAC",
|
|
193
|
+
"VE:Táchira": "TAC",
|
|
194
|
+
"VE:Lara": "LAR",
|
|
195
|
+
"VE:Monagas": "MON",
|
|
196
|
+
"VE:Nueva Esparta": "NEP",
|
|
197
|
+
"VE:Falcon": "FAL",
|
|
198
|
+
"VE:Falcón": "FAL",
|
|
199
|
+
"VE:Guarico": "GUA",
|
|
200
|
+
"VE:Guárico": "GUA",
|
|
201
|
+
"VE:Portuguesa": "POR",
|
|
202
|
+
"VE:Sucre": "SUC",
|
|
203
|
+
"VE:Yaracuy": "YAR",
|
|
204
|
+
"VE:Amazonas": "AMZ",
|
|
205
|
+
"VE:Cojedes": "COJ",
|
|
206
|
+
"VE:Delta Amacuro": "DAM",
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
function normalizeCityKey(cityName: string): string {
|
|
210
|
+
return cityName
|
|
211
|
+
.normalize("NFD")
|
|
212
|
+
.replace(/\p{M}/gu, "")
|
|
213
|
+
.replace(/[''`´’]/g, "")
|
|
214
|
+
.trim();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function fallbackCityCode(cityName: string): string {
|
|
218
|
+
const letters = normalizeCityKey(cityName).replace(/[^a-zA-Z]/g, "");
|
|
219
|
+
if (letters.length >= 3) return letters.slice(0, 3).toUpperCase();
|
|
220
|
+
return letters.toUpperCase().padEnd(3, "X");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function resolveCityCode(
|
|
224
|
+
countryCode: string,
|
|
225
|
+
cityName: string,
|
|
226
|
+
): string {
|
|
227
|
+
const cc = countryCode.trim().toUpperCase();
|
|
228
|
+
const normalized = normalizeCityKey(cityName);
|
|
229
|
+
const direct = CITY_CODE_OVERRIDES[`${cc}:${normalized}`];
|
|
230
|
+
if (direct) return direct;
|
|
231
|
+
const withoutAccents = CITY_CODE_OVERRIDES[`${cc}:${cityName.trim()}`];
|
|
232
|
+
if (withoutAccents) return withoutAccents;
|
|
233
|
+
return fallbackCityCode(cityName);
|
|
234
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Países para UI: nombres vía Intl; catálogo vía `@countrystatecity/countries-browser`. */
|
|
2
|
+
|
|
3
|
+
export interface CountryOption {
|
|
4
|
+
code: string;
|
|
5
|
+
short: string;
|
|
6
|
+
long: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** @deprecated Usar `CountryOption`. */
|
|
10
|
+
export type LatinCountry = CountryOption;
|
|
11
|
+
|
|
12
|
+
export function spanishCountryName(isoCode: string, fallback?: string): string {
|
|
13
|
+
const code = isoCode.trim().toUpperCase();
|
|
14
|
+
if (!code) return fallback?.trim() || "";
|
|
15
|
+
try {
|
|
16
|
+
const name = new Intl.DisplayNames(["es"], { type: "region" }).of(code);
|
|
17
|
+
return name?.trim() || fallback?.trim() || code;
|
|
18
|
+
} catch {
|
|
19
|
+
return fallback?.trim() || code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Mapea la respuesta CSC a opciones de UI (nombre en español, orden A–Z). */
|
|
24
|
+
export function mapGeoCountries(
|
|
25
|
+
apiCountries: Array<{ code: string; name: string }>,
|
|
26
|
+
): CountryOption[] {
|
|
27
|
+
const byCode = new Map<string, CountryOption>();
|
|
28
|
+
|
|
29
|
+
for (const c of apiCountries) {
|
|
30
|
+
const code = c.code.trim().toUpperCase();
|
|
31
|
+
if (!code) continue;
|
|
32
|
+
byCode.set(code, {
|
|
33
|
+
code,
|
|
34
|
+
short: code,
|
|
35
|
+
long: spanishCountryName(code, c.name),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [...byCode.values()].sort((a, b) =>
|
|
40
|
+
a.long.localeCompare(b.long, "es"),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @deprecated Usar `mapGeoCountries`. */
|
|
45
|
+
export const mergeGeoCountries = mapGeoCountries;
|
|
46
|
+
|
|
47
|
+
export function getCountryByCode(
|
|
48
|
+
countryCode?: string | null,
|
|
49
|
+
): CountryOption | undefined {
|
|
50
|
+
if (!countryCode?.trim()) return undefined;
|
|
51
|
+
const normalized = countryCode.trim().toUpperCase();
|
|
52
|
+
return {
|
|
53
|
+
code: normalized,
|
|
54
|
+
short: normalized,
|
|
55
|
+
long: spanishCountryName(normalized, normalized),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Nombre en español a partir del código ISO; si no hay match, usa el fallback o el código. */
|
|
60
|
+
export function getCountryDisplayName(
|
|
61
|
+
countryCode?: string | null,
|
|
62
|
+
fallbackName?: string | null,
|
|
63
|
+
): string {
|
|
64
|
+
if (!countryCode?.trim()) {
|
|
65
|
+
if (fallbackName?.trim()) return fallbackName.trim();
|
|
66
|
+
return "—";
|
|
67
|
+
}
|
|
68
|
+
return spanishCountryName(countryCode, fallbackName ?? undefined);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Fax en oficinas/direcciones cuando hay país ISO-2. */
|
|
72
|
+
export function isCommonFaxCountry(countryCode?: string | null): boolean {
|
|
73
|
+
const code = countryCode?.trim().toUpperCase();
|
|
74
|
+
return Boolean(code && /^[A-Z]{2}$/.test(code));
|
|
75
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catálogo geo vía `@countrystatecity/countries-browser` (CDN / fetch).
|
|
3
|
+
* Países, provincias y ciudades; sin JSON embebido en el repo.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
getCitiesOfState,
|
|
7
|
+
getCountries,
|
|
8
|
+
getStatesOfCountry,
|
|
9
|
+
} from "@countrystatecity/countries-browser";
|
|
10
|
+
|
|
11
|
+
export type GeoApiOption = {
|
|
12
|
+
code: string;
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type GeoApiCityOption = GeoApiOption & {
|
|
17
|
+
stateCode?: string;
|
|
18
|
+
stateName?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function getGeoCountries(): Promise<GeoApiOption[]> {
|
|
22
|
+
try {
|
|
23
|
+
const countries = await getCountries();
|
|
24
|
+
return (countries ?? []).map((c) => ({
|
|
25
|
+
code: c.iso2,
|
|
26
|
+
name: c.name,
|
|
27
|
+
}));
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("getGeoCountries:", error);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function getGeoStates(
|
|
35
|
+
countryCode: string,
|
|
36
|
+
): Promise<GeoApiOption[]> {
|
|
37
|
+
const cc = countryCode.trim().toUpperCase();
|
|
38
|
+
if (!cc) return [];
|
|
39
|
+
try {
|
|
40
|
+
const states = await getStatesOfCountry(cc);
|
|
41
|
+
return (states ?? [])
|
|
42
|
+
.filter((s) => Boolean(s.iso2?.trim() && s.name?.trim()))
|
|
43
|
+
.map((s) => ({
|
|
44
|
+
code: s.iso2.trim(),
|
|
45
|
+
name: s.name.trim(),
|
|
46
|
+
}));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("getGeoStates:", cc, error);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function getGeoCities(params: {
|
|
54
|
+
countryCode: string;
|
|
55
|
+
stateCode?: string | null;
|
|
56
|
+
q?: string | null;
|
|
57
|
+
limit?: number;
|
|
58
|
+
}): Promise<GeoApiCityOption[]> {
|
|
59
|
+
const cc = params.countryCode.trim().toUpperCase();
|
|
60
|
+
const stateCode = params.stateCode?.trim();
|
|
61
|
+
if (!cc || !stateCode) return [];
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const cities = await getCitiesOfState(cc, stateCode);
|
|
65
|
+
const q = params.q?.trim().toLowerCase();
|
|
66
|
+
let list = cities ?? [];
|
|
67
|
+
if (q && q.length >= 2) {
|
|
68
|
+
list = list.filter((c) => c.name.toLowerCase().includes(q));
|
|
69
|
+
}
|
|
70
|
+
if (params.limit != null && params.limit > 0) {
|
|
71
|
+
list = list.slice(0, params.limit);
|
|
72
|
+
}
|
|
73
|
+
return list.map((c) => ({
|
|
74
|
+
code: c.name,
|
|
75
|
+
name: c.name,
|
|
76
|
+
stateCode,
|
|
77
|
+
}));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("getGeoCities:", error);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { getGeoCities, getGeoStates } from "./geo-csc";
|
|
2
|
+
import { resolveCityCode } from "./geo-city-codes";
|
|
3
|
+
import {
|
|
4
|
+
filterStatesForCountry,
|
|
5
|
+
mergeExtraCitiesForState,
|
|
6
|
+
resolveCscStateCode,
|
|
7
|
+
resolveStateDisplayName,
|
|
8
|
+
} from "./geo-overrides";
|
|
9
|
+
import {
|
|
10
|
+
DEPARTMENT_NAME_AS_CITY_COUNTRY_CODES,
|
|
11
|
+
type GeoCityOption,
|
|
12
|
+
type GeoOption,
|
|
13
|
+
} from "./geo-types";
|
|
14
|
+
import { normalizeSearchText } from "./utils";
|
|
15
|
+
|
|
16
|
+
function mapApiCity(
|
|
17
|
+
countryCode: string,
|
|
18
|
+
city: {
|
|
19
|
+
name: string;
|
|
20
|
+
stateCode?: string;
|
|
21
|
+
stateName?: string;
|
|
22
|
+
},
|
|
23
|
+
stateNameByCode: Map<string, string>,
|
|
24
|
+
): GeoCityOption {
|
|
25
|
+
const stateCode = city.stateCode ?? "";
|
|
26
|
+
return {
|
|
27
|
+
code: resolveCityCode(countryCode, city.name),
|
|
28
|
+
name: city.name,
|
|
29
|
+
stateCode: stateCode || undefined,
|
|
30
|
+
stateName:
|
|
31
|
+
city.stateName ??
|
|
32
|
+
(stateCode ? stateNameByCode.get(stateCode) : undefined),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function fetchStates(countryCode: string): Promise<GeoOption[]> {
|
|
37
|
+
const cc = countryCode.trim().toUpperCase();
|
|
38
|
+
if (!cc) return [];
|
|
39
|
+
const states = await getGeoStates(cc);
|
|
40
|
+
return filterStatesForCountry(
|
|
41
|
+
cc,
|
|
42
|
+
states.map((s) => ({ code: s.code, name: s.name })),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchCitiesForStateCode(
|
|
47
|
+
cc: string,
|
|
48
|
+
stateCode: string,
|
|
49
|
+
): Promise<
|
|
50
|
+
Array<{ name: string; stateCode?: string; stateName?: string }>
|
|
51
|
+
> {
|
|
52
|
+
const primary = await getGeoCities({
|
|
53
|
+
countryCode: cc,
|
|
54
|
+
stateCode,
|
|
55
|
+
});
|
|
56
|
+
if (primary.length > 0) return primary;
|
|
57
|
+
|
|
58
|
+
const mapped = resolveCscStateCode(cc, stateCode);
|
|
59
|
+
if (mapped !== stateCode) {
|
|
60
|
+
return getGeoCities({
|
|
61
|
+
countryCode: cc,
|
|
62
|
+
stateCode: mapped,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function fetchCities(
|
|
69
|
+
countryCode: string,
|
|
70
|
+
stateCode?: string | null,
|
|
71
|
+
): Promise<GeoCityOption[]> {
|
|
72
|
+
const cc = countryCode.trim().toUpperCase();
|
|
73
|
+
if (!cc) return [];
|
|
74
|
+
|
|
75
|
+
// Sin estado no listamos el país entero (catálogo grande).
|
|
76
|
+
if (!stateCode?.trim()) return [];
|
|
77
|
+
|
|
78
|
+
const code = stateCode.trim();
|
|
79
|
+
const states = await fetchStates(cc);
|
|
80
|
+
const stateNameByCode = new Map(
|
|
81
|
+
states.map((s) => [
|
|
82
|
+
s.code,
|
|
83
|
+
resolveStateDisplayName(cc, s.code, s.name),
|
|
84
|
+
]),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const apiStateCode = resolveCscStateCode(cc, code);
|
|
88
|
+
const state =
|
|
89
|
+
states.find((s) => s.code === code) ||
|
|
90
|
+
states.find((s) => s.code === apiStateCode);
|
|
91
|
+
|
|
92
|
+
const fromCatalogRaw = await fetchCitiesForStateCode(cc, code);
|
|
93
|
+
const fromCatalog = fromCatalogRaw.map((c) =>
|
|
94
|
+
mapApiCity(cc, c, stateNameByCode),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const cities = mergeExtraCitiesForState(
|
|
98
|
+
cc,
|
|
99
|
+
code,
|
|
100
|
+
mergeExtraCitiesForState(cc, apiStateCode, fromCatalog),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const withDepartment = (() => {
|
|
104
|
+
if (
|
|
105
|
+
!DEPARTMENT_NAME_AS_CITY_COUNTRY_CODES.has(cc) ||
|
|
106
|
+
!state?.name?.trim()
|
|
107
|
+
) {
|
|
108
|
+
return cities;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const departmentName = resolveStateDisplayName(
|
|
112
|
+
cc,
|
|
113
|
+
state.code,
|
|
114
|
+
state.name,
|
|
115
|
+
).trim();
|
|
116
|
+
const alreadyListed = cities.some(
|
|
117
|
+
(city) => city.name.trim().toLowerCase() === departmentName.toLowerCase(),
|
|
118
|
+
);
|
|
119
|
+
if (alreadyListed) return cities;
|
|
120
|
+
|
|
121
|
+
return [
|
|
122
|
+
...cities,
|
|
123
|
+
{
|
|
124
|
+
code: resolveCityCode(cc, departmentName),
|
|
125
|
+
name: departmentName,
|
|
126
|
+
stateCode: state.code,
|
|
127
|
+
stateName: state.name,
|
|
128
|
+
},
|
|
129
|
+
].sort((a, b) => a.name.localeCompare(b.name, "es"));
|
|
130
|
+
})();
|
|
131
|
+
|
|
132
|
+
if (cc === "ES" && code !== apiStateCode) {
|
|
133
|
+
const postalName = resolveStateDisplayName(
|
|
134
|
+
cc,
|
|
135
|
+
code,
|
|
136
|
+
state?.name ?? code,
|
|
137
|
+
);
|
|
138
|
+
return withDepartment.map((city) => ({
|
|
139
|
+
...city,
|
|
140
|
+
stateCode: code,
|
|
141
|
+
stateName: postalName,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return withDepartment;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function geoNameKey(value: string): string {
|
|
149
|
+
return normalizeSearchText(value).replace(/[^a-z0-9]/g, "");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function findStateByName(
|
|
153
|
+
states: GeoOption[],
|
|
154
|
+
name?: string | null,
|
|
155
|
+
): GeoOption | undefined {
|
|
156
|
+
if (!name?.trim()) return undefined;
|
|
157
|
+
const q = geoNameKey(name);
|
|
158
|
+
const code = name.trim().toUpperCase();
|
|
159
|
+
return states.find(
|
|
160
|
+
(s) => geoNameKey(s.name) === q || s.code.toUpperCase() === code,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function findCityByName(
|
|
165
|
+
cities: GeoCityOption[],
|
|
166
|
+
name?: string | null,
|
|
167
|
+
): GeoCityOption | undefined {
|
|
168
|
+
if (!name?.trim()) return undefined;
|
|
169
|
+
const q = geoNameKey(name);
|
|
170
|
+
return cities.find((c) => geoNameKey(c.name) === q);
|
|
171
|
+
}
|