@traky-app/ui 1.6.3 → 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.
Files changed (60) hide show
  1. package/package.json +15 -1
  2. package/registry/traky/json/city-select.json +19 -0
  3. package/registry/traky/json/country-select.json +24 -0
  4. package/registry/traky/json/geo-search-select.json +24 -0
  5. package/registry/traky/json/geo-selectors.json +21 -0
  6. package/registry/traky/json/google-maps-address-picker.json +22 -0
  7. package/registry/traky/json/lib-geo-city-codes.json +14 -0
  8. package/registry/traky/json/lib-geo-countries.json +14 -0
  9. package/registry/traky/json/lib-geo-csc.json +17 -0
  10. package/registry/traky/json/lib-geo-locations.json +14 -0
  11. package/registry/traky/json/lib-geo-overrides.json +14 -0
  12. package/registry/traky/json/lib-geo-types.json +14 -0
  13. package/registry/traky/json/lib-google-maps-address.json +14 -0
  14. package/registry/traky/json/lib-phone-countries.json +14 -0
  15. package/registry/traky/json/lib-phone-e164.json +14 -0
  16. package/registry/traky/json/lib-utils.json +1 -1
  17. package/registry/traky/json/office-address-form-dialog.json +17 -0
  18. package/registry/traky/json/office-address-form.json +31 -0
  19. package/registry/traky/json/phone-number-input.json +25 -0
  20. package/registry/traky/json/state-select.json +19 -0
  21. package/registry/traky/lib/geo-city-codes.ts +234 -0
  22. package/registry/traky/lib/geo-countries.ts +75 -0
  23. package/registry/traky/lib/geo-csc.ts +82 -0
  24. package/registry/traky/lib/geo-locations.ts +171 -0
  25. package/registry/traky/lib/geo-overrides.ts +375 -0
  26. package/registry/traky/lib/geo-types.ts +34 -0
  27. package/registry/traky/lib/google-maps-address.ts +100 -0
  28. package/registry/traky/lib/phone-countries.ts +380 -0
  29. package/registry/traky/lib/phone-e164.ts +66 -0
  30. package/registry/traky/lib/utils.ts +24 -0
  31. package/registry/traky/registry.json +180 -0
  32. package/registry/traky/ui/city-select.tsx +113 -0
  33. package/registry/traky/ui/country-select.tsx +233 -0
  34. package/registry/traky/ui/geo-search-select.tsx +136 -0
  35. package/registry/traky/ui/geo-selectors.tsx +162 -0
  36. package/registry/traky/ui/google-maps-address-picker.tsx +422 -0
  37. package/registry/traky/ui/office-address-form-dialog.tsx +16 -0
  38. package/registry/traky/ui/office-address-form.tsx +868 -0
  39. package/registry/traky/ui/phone-number-input.tsx +253 -0
  40. package/registry/traky/ui/state-select.tsx +107 -0
  41. package/src/lib/geo-city-codes.ts +234 -0
  42. package/src/lib/geo-countries.ts +75 -0
  43. package/src/lib/geo-csc.ts +82 -0
  44. package/src/lib/geo-locations.ts +171 -0
  45. package/src/lib/geo-overrides.ts +375 -0
  46. package/src/lib/geo-types.ts +34 -0
  47. package/src/lib/google-maps-address.ts +100 -0
  48. package/src/lib/phone-countries.ts +380 -0
  49. package/src/lib/phone-e164.ts +66 -0
  50. package/src/test/setup.ts +31 -0
  51. package/src/ui/__tests__/geo-selects.test.tsx +504 -0
  52. package/src/ui/city-select.tsx +113 -0
  53. package/src/ui/country-select.tsx +233 -0
  54. package/src/ui/geo-search-select.tsx +136 -0
  55. package/src/ui/geo-selectors.tsx +162 -0
  56. package/src/ui/google-maps-address-picker.tsx +422 -0
  57. package/src/ui/office-address-form-dialog.tsx +16 -0
  58. package/src/ui/office-address-form.tsx +877 -0
  59. package/src/ui/phone-number-input.tsx +253 -0
  60. package/src/ui/state-select.tsx +107 -0
@@ -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
+ }
@@ -0,0 +1,375 @@
1
+ import type { GeoCityOption, GeoOption } from "./geo-types";
2
+
3
+ /**
4
+ * Provincias de España (ISO 3166-2) para direcciones postales.
5
+ * `cscStateCode` es el código de comunidad en el catálogo geo (API / dataset).
6
+ * Castilla y León no trae ciudades en `CL`; el catálogo las agrupa en `LE`.
7
+ */
8
+ export const ES_PROVINCES: Array<{
9
+ code: string;
10
+ name: string;
11
+ cscStateCode: string;
12
+ }> = [
13
+ { code: "C", name: "A Coruña", cscStateCode: "GA" },
14
+ { code: "VI", name: "Álava", cscStateCode: "PV" },
15
+ { code: "AB", name: "Albacete", cscStateCode: "CM" },
16
+ { code: "A", name: "Alicante", cscStateCode: "VC" },
17
+ { code: "AL", name: "Almería", cscStateCode: "AN" },
18
+ { code: "O", name: "Asturias", cscStateCode: "AS" },
19
+ { code: "AV", name: "Ávila", cscStateCode: "LE" },
20
+ { code: "BA", name: "Badajoz", cscStateCode: "EX" },
21
+ { code: "PM", name: "Baleares", cscStateCode: "PM" },
22
+ { code: "B", name: "Barcelona", cscStateCode: "CT" },
23
+ { code: "BU", name: "Burgos", cscStateCode: "LE" },
24
+ { code: "CC", name: "Cáceres", cscStateCode: "EX" },
25
+ { code: "CA", name: "Cádiz", cscStateCode: "AN" },
26
+ { code: "S", name: "Cantabria", cscStateCode: "CB" },
27
+ { code: "CS", name: "Castellón", cscStateCode: "VC" },
28
+ { code: "CE", name: "Ceuta", cscStateCode: "CE" },
29
+ { code: "CR", name: "Ciudad Real", cscStateCode: "CM" },
30
+ { code: "CO", name: "Córdoba", cscStateCode: "AN" },
31
+ { code: "CU", name: "Cuenca", cscStateCode: "CM" },
32
+ { code: "SS", name: "Gipuzkoa", cscStateCode: "PV" },
33
+ { code: "GI", name: "Girona", cscStateCode: "CT" },
34
+ { code: "GR", name: "Granada", cscStateCode: "AN" },
35
+ { code: "GU", name: "Guadalajara", cscStateCode: "CM" },
36
+ { code: "H", name: "Huelva", cscStateCode: "AN" },
37
+ { code: "HU", name: "Huesca", cscStateCode: "AR" },
38
+ { code: "J", name: "Jaén", cscStateCode: "AN" },
39
+ { code: "LO", name: "La Rioja", cscStateCode: "RI" },
40
+ { code: "GC", name: "Las Palmas", cscStateCode: "CN" },
41
+ { code: "LE", name: "León", cscStateCode: "LE" },
42
+ { code: "L", name: "Lleida", cscStateCode: "CT" },
43
+ { code: "LU", name: "Lugo", cscStateCode: "GA" },
44
+ { code: "M", name: "Madrid", cscStateCode: "MD" },
45
+ { code: "MA", name: "Málaga", cscStateCode: "AN" },
46
+ { code: "ML", name: "Melilla", cscStateCode: "ML" },
47
+ { code: "MU", name: "Murcia", cscStateCode: "MC" },
48
+ { code: "NA", name: "Navarra", cscStateCode: "NC" },
49
+ { code: "OR", name: "Ourense", cscStateCode: "GA" },
50
+ { code: "P", name: "Palencia", cscStateCode: "LE" },
51
+ { code: "PO", name: "Pontevedra", cscStateCode: "GA" },
52
+ { code: "SA", name: "Salamanca", cscStateCode: "LE" },
53
+ { code: "TF", name: "Santa Cruz de Tenerife", cscStateCode: "CN" },
54
+ { code: "SG", name: "Segovia", cscStateCode: "LE" },
55
+ { code: "SE", name: "Sevilla", cscStateCode: "AN" },
56
+ { code: "SO", name: "Soria", cscStateCode: "LE" },
57
+ { code: "T", name: "Tarragona", cscStateCode: "CT" },
58
+ { code: "TE", name: "Teruel", cscStateCode: "AR" },
59
+ { code: "TO", name: "Toledo", cscStateCode: "CM" },
60
+ { code: "V", name: "Valencia", cscStateCode: "VC" },
61
+ { code: "VA", name: "Valladolid", cscStateCode: "LE" },
62
+ { code: "BI", name: "Bizkaia", cscStateCode: "PV" },
63
+ { code: "ZA", name: "Zamora", cscStateCode: "LE" },
64
+ { code: "Z", name: "Zaragoza", cscStateCode: "AR" },
65
+ ];
66
+
67
+ /** Limita el selector de región a códigos concretos (p. ej. naciones del Reino Unido). */
68
+ export const STATE_LIST_FILTER_BY_COUNTRY: Partial<Record<string, string[]>> = {
69
+ GB: ["ENG", "SCT", "WLS", "NIR"],
70
+ VE: [
71
+ "Z",
72
+ "B",
73
+ "C",
74
+ "D",
75
+ "E",
76
+ "F",
77
+ "G",
78
+ "H",
79
+ "Y",
80
+ "A",
81
+ "I",
82
+ "J",
83
+ "X",
84
+ "K",
85
+ "M",
86
+ "N",
87
+ "L",
88
+ "O",
89
+ "P",
90
+ "R",
91
+ "T",
92
+ "S",
93
+ "U",
94
+ "V",
95
+ ],
96
+ };
97
+
98
+ /** Nombres de región en español para el selector. */
99
+ export const STATE_DISPLAY_NAME_OVERRIDES: Partial<
100
+ Record<string, Record<string, string>>
101
+ > = {
102
+ ES: {
103
+ AN: "Andalucía",
104
+ AR: "Aragón",
105
+ AS: "Asturias",
106
+ PM: "Islas Baleares",
107
+ PV: "País Vasco",
108
+ CN: "Canarias",
109
+ CB: "Cantabria",
110
+ CL: "Castilla y León",
111
+ CM: "Castilla-La Mancha",
112
+ CT: "Cataluña",
113
+ CE: "Ceuta",
114
+ EX: "Extremadura",
115
+ GA: "Galicia",
116
+ RI: "La Rioja",
117
+ MD: "Comunidad de Madrid",
118
+ ML: "Melilla",
119
+ MC: "Región de Murcia",
120
+ NC: "Navarra",
121
+ VC: "Comunidad Valenciana",
122
+ },
123
+ VE: {
124
+ X: "La Guaira",
125
+ },
126
+ };
127
+
128
+ /** Ciudades que faltan en el catálogo para una región concreta. */
129
+ export const EXTRA_CITIES_BY_STATE: Partial<
130
+ Record<string, Partial<Record<string, GeoCityOption[]>>>
131
+ > = {
132
+ ES: {
133
+ B: [
134
+ {
135
+ code: "LHO",
136
+ name: "L'Hospitalet de Llobregat",
137
+ stateCode: "B",
138
+ stateName: "Barcelona",
139
+ },
140
+ {
141
+ code: "BDL",
142
+ name: "Badalona",
143
+ stateCode: "B",
144
+ stateName: "Barcelona",
145
+ },
146
+ {
147
+ code: "BCN",
148
+ name: "Barcelona",
149
+ stateCode: "B",
150
+ stateName: "Barcelona",
151
+ },
152
+ ],
153
+ AS: [
154
+ {
155
+ code: "OVD",
156
+ name: "Oviedo",
157
+ stateCode: "AS",
158
+ stateName: "Asturias",
159
+ },
160
+ {
161
+ code: "GIJ",
162
+ name: "Gijón",
163
+ stateCode: "AS",
164
+ stateName: "Asturias",
165
+ },
166
+ {
167
+ code: "AVS",
168
+ name: "Avilés",
169
+ stateCode: "AS",
170
+ stateName: "Asturias",
171
+ },
172
+ ],
173
+ CL: [
174
+ {
175
+ code: "LEN",
176
+ name: "León",
177
+ stateCode: "CL",
178
+ stateName: "Castilla y León",
179
+ },
180
+ {
181
+ code: "BUR",
182
+ name: "Burgos",
183
+ stateCode: "CL",
184
+ stateName: "Castilla y León",
185
+ },
186
+ {
187
+ code: "SLM",
188
+ name: "Salamanca",
189
+ stateCode: "CL",
190
+ stateName: "Castilla y León",
191
+ },
192
+ {
193
+ code: "VLL",
194
+ name: "Valladolid",
195
+ stateCode: "CL",
196
+ stateName: "Castilla y León",
197
+ },
198
+ {
199
+ code: "SEG",
200
+ name: "Segovia",
201
+ stateCode: "CL",
202
+ stateName: "Castilla y León",
203
+ },
204
+ {
205
+ code: "SOR",
206
+ name: "Soria",
207
+ stateCode: "CL",
208
+ stateName: "Castilla y León",
209
+ },
210
+ {
211
+ code: "ZMR",
212
+ name: "Zamora",
213
+ stateCode: "CL",
214
+ stateName: "Castilla y León",
215
+ },
216
+ {
217
+ code: "AVL",
218
+ name: "Ávila",
219
+ stateCode: "CL",
220
+ stateName: "Castilla y León",
221
+ },
222
+ {
223
+ code: "PAL",
224
+ name: "Palencia",
225
+ stateCode: "CL",
226
+ stateName: "Castilla y León",
227
+ },
228
+ ],
229
+ },
230
+ VE: {
231
+ C: [
232
+ {
233
+ code: "SFA",
234
+ name: "San Fernando de Apure",
235
+ stateCode: "C",
236
+ stateName: "Apure",
237
+ },
238
+ {
239
+ code: "GUA",
240
+ name: "Guasdualito",
241
+ stateCode: "C",
242
+ stateName: "Apure",
243
+ },
244
+ ],
245
+ G: [
246
+ {
247
+ code: "NGN",
248
+ name: "Naguanagua",
249
+ stateCode: "G",
250
+ stateName: "Carabobo",
251
+ },
252
+ ],
253
+ B: [
254
+ {
255
+ code: "LEC",
256
+ name: "Lechería",
257
+ stateCode: "B",
258
+ stateName: "Anzoátegui",
259
+ },
260
+ ],
261
+ F: [
262
+ {
263
+ code: "PZO",
264
+ name: "Puerto Ordaz",
265
+ stateCode: "F",
266
+ stateName: "Bolívar",
267
+ },
268
+ ],
269
+ },
270
+ GB: {
271
+ NIR: [
272
+ {
273
+ code: "BEL",
274
+ name: "Belfast",
275
+ stateCode: "NIR",
276
+ stateName: "Northern Ireland",
277
+ },
278
+ {
279
+ code: "DRY",
280
+ name: "Derry",
281
+ stateCode: "NIR",
282
+ stateName: "Northern Ireland",
283
+ },
284
+ {
285
+ code: "LSB",
286
+ name: "Lisburn",
287
+ stateCode: "NIR",
288
+ stateName: "Northern Ireland",
289
+ },
290
+ {
291
+ code: "NEW",
292
+ name: "Newry",
293
+ stateCode: "NIR",
294
+ stateName: "Northern Ireland",
295
+ },
296
+ ],
297
+ },
298
+ };
299
+
300
+ export function resolveCscStateCode(
301
+ countryCode: string,
302
+ stateCode: string,
303
+ ): string {
304
+ const cc = countryCode.trim().toUpperCase();
305
+ const code = stateCode.trim();
306
+ if (!code) return code;
307
+ if (cc === "ES") {
308
+ return (
309
+ ES_PROVINCES.find((province) => province.code === code)?.cscStateCode ??
310
+ code
311
+ );
312
+ }
313
+ return code;
314
+ }
315
+
316
+ export function resolveStateDisplayName(
317
+ countryCode: string,
318
+ stateCode: string,
319
+ defaultName: string,
320
+ ): string {
321
+ const cc = countryCode.trim().toUpperCase();
322
+ const code = stateCode.trim();
323
+ const provinceName = ES_PROVINCES.find(
324
+ (province) => cc === "ES" && province.code === code,
325
+ )?.name;
326
+ return (
327
+ provinceName ||
328
+ STATE_DISPLAY_NAME_OVERRIDES[cc]?.[code]?.trim() ||
329
+ defaultName
330
+ );
331
+ }
332
+
333
+ export function filterStatesForCountry(
334
+ countryCode: string,
335
+ states: GeoOption[],
336
+ ): GeoOption[] {
337
+ const cc = countryCode.trim().toUpperCase();
338
+ if (cc === "ES") {
339
+ return ES_PROVINCES.map((province) => ({
340
+ code: province.code,
341
+ name: province.name,
342
+ }));
343
+ }
344
+
345
+ const allowed = STATE_LIST_FILTER_BY_COUNTRY[cc];
346
+ const filtered = allowed?.length
347
+ ? states.filter((state) => new Set(allowed).has(state.code))
348
+ : states;
349
+
350
+ return filtered.map((state) => ({
351
+ ...state,
352
+ name: resolveStateDisplayName(cc, state.code, state.name),
353
+ }));
354
+ }
355
+
356
+ export function mergeExtraCitiesForState(
357
+ countryCode: string,
358
+ stateCode: string,
359
+ cities: GeoCityOption[],
360
+ ): GeoCityOption[] {
361
+ const extras = EXTRA_CITIES_BY_STATE[countryCode]?.[stateCode];
362
+ if (!extras?.length) return cities;
363
+
364
+ const seen = new Set(cities.map((city) => city.name.trim().toLowerCase()));
365
+ const merged = [...cities];
366
+
367
+ for (const city of extras) {
368
+ const key = city.name.trim().toLowerCase();
369
+ if (seen.has(key)) continue;
370
+ seen.add(key);
371
+ merged.push(city);
372
+ }
373
+
374
+ return merged.sort((a, b) => a.name.localeCompare(b.name, "es"));
375
+ }
@@ -0,0 +1,34 @@
1
+ export type GeoOption = {
2
+ code: string;
3
+ name: string;
4
+ };
5
+
6
+ export type GeoCityOption = GeoOption & {
7
+ stateCode?: string;
8
+ stateName?: string;
9
+ };
10
+
11
+ /**
12
+ * Países donde el formulario usa catálogo CSC (CDN) con provincia/estado primero.
13
+ */
14
+ export function usesGeoSearch(countryCode?: string | null): boolean {
15
+ return Boolean(countryCode?.trim() && countryCode.trim().length === 2);
16
+ }
17
+
18
+ /** Provincia/estado antes que ciudad (evita listar ciudades de país entero). */
19
+ export function isStateFirstCountry(countryCode?: string | null): boolean {
20
+ return usesGeoSearch(countryCode);
21
+ }
22
+
23
+ /**
24
+ * Países donde el nombre del departamento se ofrece también como ciudad
25
+ * (p. ej. Chontales en Nicaragua no existe como municipio en el catálogo).
26
+ */
27
+ export const DEPARTMENT_NAME_AS_CITY_COUNTRY_CODES = new Set([
28
+ "CO",
29
+ "ES",
30
+ "GB",
31
+ "NI",
32
+ "PE",
33
+ "VE",
34
+ ]);
@@ -0,0 +1,100 @@
1
+ export const OFFICE_ADDRESS_GEOCODE_SOURCE_GOOGLE_MAPS = "google_maps" as const
2
+
3
+ export type GoogleMapsAddressSelection = {
4
+ street: string
5
+ streetNumber?: string
6
+ city: string
7
+ state?: string
8
+ postalCode?: string
9
+ countryCode?: string
10
+ formattedAddress?: string
11
+ latitude: number
12
+ longitude: number
13
+ }
14
+
15
+ type AddressComponentLike = {
16
+ types: string[]
17
+ long_name?: string
18
+ short_name?: string
19
+ longText?: string
20
+ shortText?: string
21
+ }
22
+
23
+ export function parseGoogleAddressComponents(
24
+ components: AddressComponentLike[] | undefined,
25
+ fallbackStreet: string,
26
+ latitude: number,
27
+ longitude: number,
28
+ ): GoogleMapsAddressSelection | null {
29
+ let streetNumber = ""
30
+ let route = ""
31
+ let city = ""
32
+ let state = ""
33
+ let postalCode = ""
34
+ let countryCode = ""
35
+
36
+ for (const component of components ?? []) {
37
+ const types = component.types
38
+ const longName = component.long_name ?? component.longText ?? ""
39
+ const shortName = component.short_name ?? component.shortText ?? ""
40
+
41
+ if (types.includes("street_number")) streetNumber = longName
42
+ if (types.includes("route")) route = longName
43
+ if (types.includes("locality")) city = longName
44
+ else if (
45
+ !city &&
46
+ (types.includes("sublocality") || types.includes("postal_town"))
47
+ ) {
48
+ city = longName
49
+ }
50
+ if (types.includes("administrative_area_level_1")) {
51
+ state = longName || shortName
52
+ }
53
+ if (types.includes("postal_code")) postalCode = longName
54
+ if (types.includes("country")) countryCode = shortName.toUpperCase()
55
+ }
56
+
57
+ const street = route || fallbackStreet
58
+ if (!street.trim() && !fallbackStreet.trim()) return null
59
+
60
+ return {
61
+ street: street.trim() || fallbackStreet.trim(),
62
+ streetNumber: streetNumber.trim() || undefined,
63
+ city: city.trim(),
64
+ state: state.trim() || undefined,
65
+ postalCode: postalCode.trim() || undefined,
66
+ countryCode: countryCode.trim() || undefined,
67
+ latitude,
68
+ longitude,
69
+ }
70
+ }
71
+
72
+ export function googleMapsSelectionToFormPatch(
73
+ selection: GoogleMapsAddressSelection,
74
+ ): Pick<
75
+ GoogleMapsAddressSelection,
76
+ "latitude" | "longitude"
77
+ > & {
78
+ address: string
79
+ city: string
80
+ stateOrProvince: string
81
+ postalCode: string
82
+ countryCode: string
83
+ addressGeocodeSource: string
84
+ } {
85
+ const addressLine =
86
+ [selection.streetNumber, selection.street].filter(Boolean).join(" ").trim() ||
87
+ selection.formattedAddress?.split(",")[0]?.trim() ||
88
+ selection.street
89
+
90
+ return {
91
+ address: addressLine,
92
+ city: selection.city,
93
+ stateOrProvince: selection.state ?? "",
94
+ postalCode: selection.postalCode ?? "",
95
+ countryCode: selection.countryCode?.trim().toUpperCase() ?? "",
96
+ latitude: selection.latitude,
97
+ longitude: selection.longitude,
98
+ addressGeocodeSource: OFFICE_ADDRESS_GEOCODE_SOURCE_GOOGLE_MAPS,
99
+ }
100
+ }