@traky-app/ui 1.6.4 → 1.6.5

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.
@@ -5,6 +5,8 @@ import { IconEdit, IconMapPin, IconPlus } from "@tabler/icons-react"
5
5
  import type { Icon } from "@tabler/icons-react"
6
6
 
7
7
  import { cn } from "@/lib/utils"
8
+ import { fetchStates } from "@/lib/geo-locations"
9
+ import { googleMapsSelectionToFormPatch } from "@/lib/google-maps-address"
8
10
  import {
9
11
  AlertDialog,
10
12
  AlertDialogAction,
@@ -14,7 +16,7 @@ import {
14
16
  } from "@/components/ui/alert-dialog"
15
17
  import { Button } from "@/components/ui/button"
16
18
  import { Spinner } from "@/components/ui/spinner"
17
- import { googleMapsSelectionToFormPatch } from "@/lib/google-maps-address"
19
+ import { StateSelect } from "@/components/ui/state-select"
18
20
  import {
19
21
  Card,
20
22
  CardContent,
@@ -24,6 +26,8 @@ import {
24
26
  CardTitle,
25
27
  } from "@/components/ui/card"
26
28
  import { EntityIcon } from "@/components/ui/entity-icon"
29
+ import { CitySelect } from "@/components/ui/city-select"
30
+ import { CountrySelect } from "@/components/ui/country-select"
27
31
  import { GoogleMapsAddressPicker } from "@/components/ui/google-maps-address-picker"
28
32
  import { Input } from "@/components/ui/input"
29
33
  import { Label } from "@/components/ui/label"
@@ -81,17 +85,6 @@ function OfficeAddressEntryModeSwitch({
81
85
  /** `customer` = dirección de envío (contacto/portal). `enterprise` = sucursal o bodega. */
82
86
  export type OfficeAddressFormVariant = "customer" | "enterprise"
83
87
 
84
- const OFFICE_ADDRESS_COUNTRY_OPTIONS: { code: string; label: string }[] = [
85
- { code: "EC", label: "Ecuador" },
86
- { code: "US", label: "Estados Unidos" },
87
- { code: "MX", label: "México" },
88
- { code: "CO", label: "Colombia" },
89
- { code: "PE", label: "Perú" },
90
- { code: "CL", label: "Chile" },
91
- { code: "PA", label: "Panamá" },
92
- { code: "CR", label: "Costa Rica" },
93
- ]
94
-
95
88
  const FAX_COUNTRY_CODES = new Set(["US", "CA", "PR"])
96
89
 
97
90
  export function isOfficeAddressFaxCountry(countryCode: string | undefined) {
@@ -232,6 +225,7 @@ export type OfficeAddressFormCardProps = {
232
225
  countryField?: React.ReactNode
233
226
  phoneField?: React.ReactNode
234
227
  faxField?: React.ReactNode
228
+ manualFields?: React.ReactNode
235
229
  }
236
230
 
237
231
  export function OfficeAddressFormCard({
@@ -254,6 +248,7 @@ export function OfficeAddressFormCard({
254
248
  countryField,
255
249
  phoneField,
256
250
  faxField,
251
+ manualFields,
257
252
  }: OfficeAddressFormCardProps) {
258
253
  const busy = disabled
259
254
  const isCustomer = variant === "customer"
@@ -274,29 +269,51 @@ export function OfficeAddressFormCard({
274
269
  }
275
270
 
276
271
  const embedded = layout === "dialog"
272
+ const countryCode = form.countryCode?.trim().toUpperCase() ?? ""
273
+ const [stateCode, setStateCode] = React.useState("")
274
+
275
+ React.useEffect(() => {
276
+ const stateName = form.stateOrProvince?.trim() ?? ""
277
+ if (!countryCode || !stateName) {
278
+ setStateCode("")
279
+ return
280
+ }
281
+
282
+ let cancelled = false
283
+ void fetchStates(countryCode).then((list) => {
284
+ if (cancelled) return
285
+ const wanted = stateName.toLowerCase()
286
+ const match = list.find(
287
+ (item) =>
288
+ item.name.toLowerCase() === wanted ||
289
+ item.code.toLowerCase() === wanted,
290
+ )
291
+ setStateCode(match?.code ?? "")
292
+ })
293
+
294
+ return () => {
295
+ cancelled = true
296
+ }
297
+ }, [countryCode, form.stateOrProvince])
277
298
 
278
299
  const countryControl =
279
300
  countryField ?? (
280
- <Select
281
- value={form.countryCode || undefined}
301
+ <CountrySelect
302
+ className="w-full"
303
+ size="lg"
304
+ value={form.countryCode}
282
305
  disabled={busy}
283
- onValueChange={(code) =>
284
- onFormChange((prev) => ({ ...prev, countryCode: code }))
285
- }
286
- >
287
- <SelectTrigger className="w-full" aria-label="País">
288
- <SelectValue placeholder="Selecciona el país" />
289
- </SelectTrigger>
290
- <SelectContent>
291
- <SelectGroup>
292
- {OFFICE_ADDRESS_COUNTRY_OPTIONS.map((c) => (
293
- <SelectItem key={c.code} value={c.code}>
294
- {c.label}
295
- </SelectItem>
296
- ))}
297
- </SelectGroup>
298
- </SelectContent>
299
- </Select>
306
+ searchPlaceholder="Escribe el país…"
307
+ onChange={(country) => {
308
+ setStateCode("")
309
+ onFormChange((prev) => ({
310
+ ...prev,
311
+ countryCode: country.code,
312
+ stateOrProvince: "",
313
+ city: "",
314
+ }))
315
+ }}
316
+ />
300
317
  )
301
318
 
302
319
  return (
@@ -385,51 +402,75 @@ export function OfficeAddressFormCard({
385
402
  {countryControl}
386
403
  </div>
387
404
 
388
- {addressEntryMode === "manual" ? (
389
- <>
390
- <div className="col-span-1 space-y-1 sm:col-span-2">
391
- <Label htmlFor="office-address">Dirección</Label>
392
- <Input
393
- id="office-address"
394
- name="address"
395
- value={form.address}
396
- onChange={handleChange}
397
- placeholder="Calle y número"
398
- disabled={busy}
399
- />
400
- </div>
401
- <div className="space-y-1">
402
- <Label htmlFor="office-city">Ciudad</Label>
403
- <Input
404
- id="office-city"
405
- name="city"
406
- value={form.city}
407
- onChange={handleChange}
408
- disabled={busy}
409
- />
410
- </div>
411
- <div className="space-y-1">
412
- <Label htmlFor="office-state">Estado / Provincia</Label>
413
- <Input
414
- id="office-state"
415
- name="stateOrProvince"
416
- value={form.stateOrProvince}
417
- onChange={handleChange}
418
- disabled={busy}
419
- />
420
- </div>
421
- <div className="space-y-1">
422
- <Label htmlFor="office-postal">Código postal</Label>
423
- <Input
424
- id="office-postal"
425
- name="postalCode"
426
- value={form.postalCode}
427
- onChange={handleChange}
428
- disabled={busy}
429
- />
430
- </div>
431
- </>
432
- ) : null}
405
+ {addressEntryMode === "manual"
406
+ ? (manualFields ?? (
407
+ <>
408
+ <div className="col-span-1 space-y-1 sm:col-span-2">
409
+ <Label htmlFor="office-address">Dirección</Label>
410
+ <Input
411
+ id="office-address"
412
+ name="address"
413
+ value={form.address}
414
+ onChange={handleChange}
415
+ placeholder="Calle y número"
416
+ disabled={busy}
417
+ />
418
+ </div>
419
+ <div className="space-y-1">
420
+ <Label htmlFor="office-state">
421
+ Estado / Región / Municipio
422
+ </Label>
423
+ <StateSelect
424
+ id="office-state"
425
+ className="w-full"
426
+ countryCode={countryCode}
427
+ value={form.stateOrProvince}
428
+ disabled={busy}
429
+ onChange={(option) => {
430
+ setStateCode(option?.code ?? "")
431
+ onFormChange((prev) => ({
432
+ ...prev,
433
+ stateOrProvince: option?.name ?? "",
434
+ city: "",
435
+ }))
436
+ }}
437
+ />
438
+ </div>
439
+ <div className="space-y-1">
440
+ <Label htmlFor="office-city">
441
+ Ciudad / Pueblo / Distrito
442
+ </Label>
443
+ <CitySelect
444
+ id="office-city"
445
+ className="w-full"
446
+ countryCode={countryCode}
447
+ stateCode={stateCode}
448
+ value={form.city}
449
+ disabled={busy}
450
+ onChange={(option) => {
451
+ onFormChange((prev) => ({
452
+ ...prev,
453
+ city: option?.name ?? "",
454
+ ...(option?.stateName
455
+ ? { stateOrProvince: option.stateName }
456
+ : {}),
457
+ }))
458
+ }}
459
+ />
460
+ </div>
461
+ <div className="space-y-1">
462
+ <Label htmlFor="office-postal">Código postal</Label>
463
+ <Input
464
+ id="office-postal"
465
+ name="postalCode"
466
+ value={form.postalCode}
467
+ onChange={handleChange}
468
+ disabled={busy}
469
+ />
470
+ </div>
471
+ </>
472
+ ))
473
+ : null}
433
474
 
434
475
  {addressEntryMode === "gps" && form.address.trim() ? (
435
476
  <div className="col-span-1 rounded-lg border border-border bg-muted/30 px-3 py-2.5 text-sm sm:col-span-2">
@@ -607,6 +648,7 @@ type OfficeAddressFormSharedProps = {
607
648
  countryField?: React.ReactNode
608
649
  phoneField?: React.ReactNode
609
650
  faxField?: React.ReactNode
651
+ manualFields?: React.ReactNode
610
652
  }
611
653
 
612
654
  type OfficeAddressFormInlineProps = OfficeAddressFormSharedProps & {
@@ -673,6 +715,7 @@ function OfficeAddressFormInline({
673
715
  countryField,
674
716
  phoneField,
675
717
  faxField,
718
+ manualFields,
676
719
  }: OfficeAddressFormInlineProps) {
677
720
  const internal = useOfficeAddressForm({ initialValues, initialEntryMode })
678
721
  const form = controlledForm ?? internal.form
@@ -702,6 +745,7 @@ function OfficeAddressFormInline({
702
745
  countryField={countryField}
703
746
  phoneField={phoneField}
704
747
  faxField={faxField}
748
+ manualFields={manualFields}
705
749
  />
706
750
  )
707
751
  }
@@ -728,6 +772,7 @@ function OfficeAddressFormModal({
728
772
  countryField,
729
773
  phoneField,
730
774
  faxField,
775
+ manualFields,
731
776
  icon,
732
777
  form: controlledForm,
733
778
  onFormChange: controlledOnFormChange,
@@ -816,6 +861,7 @@ function OfficeAddressFormModal({
816
861
  countryField={countryField}
817
862
  phoneField={phoneField}
818
863
  faxField={faxField}
864
+ manualFields={manualFields}
819
865
  form={form}
820
866
  onFormChange={setForm}
821
867
  addressEntryMode={addressEntryMode}