@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.
Files changed (66) 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/internal-chat-mention.json +1 -1
  8. package/registry/traky/json/internal-chat-panel.json +1 -1
  9. package/registry/traky/json/lib-geo-city-codes.json +14 -0
  10. package/registry/traky/json/lib-geo-countries.json +14 -0
  11. package/registry/traky/json/lib-geo-csc.json +17 -0
  12. package/registry/traky/json/lib-geo-locations.json +14 -0
  13. package/registry/traky/json/lib-geo-overrides.json +14 -0
  14. package/registry/traky/json/lib-geo-types.json +14 -0
  15. package/registry/traky/json/lib-google-maps-address.json +14 -0
  16. package/registry/traky/json/lib-phone-countries.json +14 -0
  17. package/registry/traky/json/lib-phone-e164.json +14 -0
  18. package/registry/traky/json/lib-utils.json +1 -1
  19. package/registry/traky/json/office-address-form-dialog.json +17 -0
  20. package/registry/traky/json/office-address-form.json +31 -0
  21. package/registry/traky/json/phone-number-input.json +25 -0
  22. package/registry/traky/json/state-select.json +19 -0
  23. package/registry/traky/lib/geo-city-codes.ts +234 -0
  24. package/registry/traky/lib/geo-countries.ts +75 -0
  25. package/registry/traky/lib/geo-csc.ts +82 -0
  26. package/registry/traky/lib/geo-locations.ts +171 -0
  27. package/registry/traky/lib/geo-overrides.ts +375 -0
  28. package/registry/traky/lib/geo-types.ts +34 -0
  29. package/registry/traky/lib/google-maps-address.ts +100 -0
  30. package/registry/traky/lib/phone-countries.ts +380 -0
  31. package/registry/traky/lib/phone-e164.ts +66 -0
  32. package/registry/traky/lib/utils.ts +24 -0
  33. package/registry/traky/registry.json +180 -0
  34. package/registry/traky/ui/city-select.tsx +113 -0
  35. package/registry/traky/ui/country-select.tsx +233 -0
  36. package/registry/traky/ui/geo-search-select.tsx +136 -0
  37. package/registry/traky/ui/geo-selectors.tsx +162 -0
  38. package/registry/traky/ui/google-maps-address-picker.tsx +422 -0
  39. package/registry/traky/ui/internal-chat-mention.ts +34 -3
  40. package/registry/traky/ui/internal-chat-panel.tsx +147 -12
  41. package/registry/traky/ui/office-address-form-dialog.tsx +16 -0
  42. package/registry/traky/ui/office-address-form.tsx +868 -0
  43. package/registry/traky/ui/phone-number-input.tsx +253 -0
  44. package/registry/traky/ui/state-select.tsx +107 -0
  45. package/src/lib/geo-city-codes.ts +234 -0
  46. package/src/lib/geo-countries.ts +75 -0
  47. package/src/lib/geo-csc.ts +82 -0
  48. package/src/lib/geo-locations.ts +171 -0
  49. package/src/lib/geo-overrides.ts +375 -0
  50. package/src/lib/geo-types.ts +34 -0
  51. package/src/lib/google-maps-address.ts +100 -0
  52. package/src/lib/phone-countries.ts +380 -0
  53. package/src/lib/phone-e164.ts +66 -0
  54. package/src/test/setup.ts +31 -0
  55. package/src/ui/__tests__/geo-selects.test.tsx +504 -0
  56. package/src/ui/city-select.tsx +113 -0
  57. package/src/ui/country-select.tsx +233 -0
  58. package/src/ui/geo-search-select.tsx +136 -0
  59. package/src/ui/geo-selectors.tsx +162 -0
  60. package/src/ui/google-maps-address-picker.tsx +422 -0
  61. package/src/ui/internal-chat-mention.ts +34 -3
  62. package/src/ui/internal-chat-panel.tsx +147 -12
  63. package/src/ui/office-address-form-dialog.tsx +16 -0
  64. package/src/ui/office-address-form.tsx +877 -0
  65. package/src/ui/phone-number-input.tsx +253 -0
  66. package/src/ui/state-select.tsx +107 -0
@@ -8,6 +8,7 @@ import {
8
8
  IconPackage,
9
9
  IconPaperclip,
10
10
  IconPlus,
11
+ IconSparkles,
11
12
  IconTrash,
12
13
  IconTruckLoading,
13
14
  IconX,
@@ -92,9 +93,12 @@ import {
92
93
  parseMentionSegment,
93
94
  parseSlashMention,
94
95
  roomInitialLetter,
96
+ type BuildMentionListOptions,
97
+ type InternalChatMentionCatalogState,
95
98
  type InternalChatMentionConfig,
96
99
  type InternalChatMentionItem,
97
100
  type InternalChatMentionKind,
101
+ type InternalChatMentionListEntry,
98
102
  } from "./internal-chat-mention";
99
103
 
100
104
  export type InternalChatRoom = {
@@ -198,6 +202,7 @@ const DEFAULT_MENTION_CONFIG: InternalChatMentionConfig = {
198
202
  kindIcons: {
199
203
  guide: IconTruckLoading,
200
204
  package: IconPackage,
205
+ ai: IconSparkles,
201
206
  },
202
207
  categoryMeta: {
203
208
  guide: {
@@ -210,9 +215,40 @@ const DEFAULT_MENTION_CONFIG: InternalChatMentionConfig = {
210
215
  hint: "Paquetes de la empresa (WR / tracking)",
211
216
  keywords: ["paquete", "paquetes", "package", "trk", "wr"],
212
217
  },
218
+ ai: {
219
+ label: "Asistencia de IA",
220
+ hint: "Consultas sobre operación y datos (próximamente)",
221
+ keywords: ["ia", "ai", "asistencia", "inteligencia", "asistente"],
222
+ disabled: true,
223
+ },
213
224
  },
214
225
  };
215
226
 
227
+ function isMentionEntryDisabled(
228
+ entry: InternalChatMentionListEntry,
229
+ config: InternalChatMentionConfig,
230
+ ): boolean {
231
+ if (entry.type !== "category") return false;
232
+ return Boolean(
233
+ entry.disabled ?? config.categoryMeta[entry.kind]?.disabled,
234
+ );
235
+ }
236
+
237
+ function stepMentionHighlight(
238
+ entries: InternalChatMentionListEntry[],
239
+ config: InternalChatMentionConfig,
240
+ current: number,
241
+ delta: 1 | -1,
242
+ ): number {
243
+ if (entries.length === 0) return 0;
244
+ let next = current;
245
+ for (let i = 0; i < entries.length; i++) {
246
+ next = (next + delta + entries.length) % entries.length;
247
+ if (!isMentionEntryDisabled(entries[next]!, config)) return next;
248
+ }
249
+ return current;
250
+ }
251
+
216
252
  function renderMessageTextWithMentions(text: string): ReactNode {
217
253
  const parts = text.split(/(@\[[^\]]+\])/g);
218
254
  return parts.map((part, index) => {
@@ -245,6 +281,10 @@ export type InternalChatPanelProps = {
245
281
  mentionGuideItems?: InternalChatMentionItem[];
246
282
  mentionPackageItems?: InternalChatMentionItem[];
247
283
  mentionConfig?: InternalChatMentionConfig;
284
+ /** Catálogo de ítems ya filtrado en servidor (guías/paquetes). */
285
+ mentionServerFilteredItems?: boolean;
286
+ mentionCatalogLoading?: boolean;
287
+ onMentionCatalogChange?: (state: InternalChatMentionCatalogState) => void;
248
288
  typingLine?: string | null;
249
289
  onSendMessage: (text: string) => void | Promise<void>;
250
290
  onCreateRoom: (name: string) => void | Promise<void>;
@@ -274,6 +314,9 @@ function InternalChatPanel({
274
314
  mentionGuideItems = [],
275
315
  mentionPackageItems = [],
276
316
  mentionConfig = DEFAULT_MENTION_CONFIG,
317
+ mentionServerFilteredItems = false,
318
+ mentionCatalogLoading = false,
319
+ onMentionCatalogChange,
277
320
  typingLine = null,
278
321
  onSendMessage,
279
322
  onCreateRoom,
@@ -310,6 +353,13 @@ function InternalChatPanel({
310
353
  [rooms, activeRoomId],
311
354
  );
312
355
 
356
+ const mentionListOptions = useMemo((): BuildMentionListOptions | undefined => {
357
+ if (!mentionServerFilteredItems) return undefined;
358
+ const seg = parseMentionSegment(mentionQuery, mentionConfig);
359
+ if (seg.phase !== "item") return undefined;
360
+ return { serverFilteredItems: true };
361
+ }, [mentionServerFilteredItems, mentionQuery, mentionConfig]);
362
+
313
363
  const mentionEntries = useMemo(
314
364
  () =>
315
365
  buildMentionList(
@@ -317,8 +367,15 @@ function InternalChatPanel({
317
367
  mentionGuideItems,
318
368
  mentionPackageItems,
319
369
  mentionConfig,
370
+ mentionListOptions,
320
371
  ),
321
- [mentionQuery, mentionGuideItems, mentionPackageItems, mentionConfig],
372
+ [
373
+ mentionQuery,
374
+ mentionGuideItems,
375
+ mentionPackageItems,
376
+ mentionConfig,
377
+ mentionListOptions,
378
+ ],
322
379
  );
323
380
 
324
381
  const mentionSeg = useMemo(
@@ -329,7 +386,9 @@ function InternalChatPanel({
329
386
  const mentionOpen =
330
387
  Boolean(activeRoom) &&
331
388
  mentionStart !== null &&
332
- mentionEntries.length > 0;
389
+ (mentionEntries.length > 0 ||
390
+ mentionCatalogLoading ||
391
+ (mentionServerFilteredItems && mentionSeg.phase === "item"));
333
392
 
334
393
  const canSend =
335
394
  draft.trim().length > 0 &&
@@ -404,6 +463,32 @@ function InternalChatPanel({
404
463
  lastMentionQueryRef.current = "";
405
464
  }, [activeRoomId]);
406
465
 
466
+ useEffect(() => {
467
+ if (!onMentionCatalogChange) return;
468
+ if (mentionStart === null) {
469
+ onMentionCatalogChange({
470
+ open: false,
471
+ phase: "category",
472
+ filter: "",
473
+ });
474
+ return;
475
+ }
476
+ if (mentionSeg.phase === "category") {
477
+ onMentionCatalogChange({
478
+ open: true,
479
+ phase: "category",
480
+ filter: mentionSeg.filter,
481
+ });
482
+ return;
483
+ }
484
+ onMentionCatalogChange({
485
+ open: true,
486
+ phase: "item",
487
+ kind: mentionSeg.kind,
488
+ filter: mentionSeg.filter,
489
+ });
490
+ }, [mentionStart, mentionQuery, mentionConfig, onMentionCatalogChange]);
491
+
407
492
  function closeMentionMenu() {
408
493
  setMentionStart(null);
409
494
  setMentionQuery("");
@@ -416,13 +501,22 @@ function InternalChatPanel({
416
501
  closeMentionMenu();
417
502
  return;
418
503
  }
504
+ const seg = parseMentionSegment(m.query, mentionConfig);
505
+ const listOptions: BuildMentionListOptions | undefined =
506
+ mentionServerFilteredItems && seg.phase === "item"
507
+ ? { serverFilteredItems: true }
508
+ : undefined;
419
509
  const entries = buildMentionList(
420
510
  m.query,
421
511
  mentionGuideItems,
422
512
  mentionPackageItems,
423
513
  mentionConfig,
514
+ listOptions,
424
515
  );
425
- if (entries.length === 0) {
516
+ const keepOpenOnEmpty =
517
+ mentionCatalogLoading ||
518
+ (mentionServerFilteredItems && seg.phase === "item");
519
+ if (entries.length === 0 && !keepOpenOnEmpty) {
426
520
  closeMentionMenu();
427
521
  return;
428
522
  }
@@ -453,6 +547,7 @@ function InternalChatPanel({
453
547
  }
454
548
 
455
549
  function applyCategoryPick(kind: InternalChatMentionKind) {
550
+ if (mentionConfig.categoryMeta[kind]?.disabled) return;
456
551
  replaceSlashSegment(`${kind}:`);
457
552
  setMentionHighlight(0);
458
553
  }
@@ -883,24 +978,49 @@ function InternalChatPanel({
883
978
  )}
884
979
  </div>
885
980
  <div className="min-h-0 flex-1 overflow-y-auto p-1">
981
+ {mentionCatalogLoading && mentionEntries.length === 0 ? (
982
+ <div className="flex items-center justify-center gap-2 px-2 py-6 text-sm text-muted-foreground">
983
+ <IconLoader2 className="size-4 animate-spin" />
984
+ Buscando…
985
+ </div>
986
+ ) : null}
987
+ {!mentionCatalogLoading &&
988
+ mentionEntries.length === 0 &&
989
+ mentionSeg.phase === "item" ? (
990
+ <p className="px-2 py-4 text-center text-sm text-muted-foreground">
991
+ Sin resultados
992
+ </p>
993
+ ) : null}
886
994
  {mentionEntries.map((entry, index) => {
887
995
  if (entry.type === "category") {
888
996
  const IconComp = entry.icon;
997
+ const categoryDisabled = isMentionEntryDisabled(
998
+ entry,
999
+ mentionConfig,
1000
+ );
889
1001
  return (
890
1002
  <button
891
1003
  key={`cat-${entry.kind}`}
892
1004
  type="button"
893
1005
  role="option"
894
1006
  aria-selected={index === mentionHighlight}
1007
+ aria-disabled={categoryDisabled}
1008
+ disabled={categoryDisabled}
895
1009
  className={cn(
896
1010
  "flex w-full items-start gap-2 rounded-lg px-2 py-2 text-left text-sm",
897
- index === mentionHighlight
898
- ? "bg-accent text-accent-foreground"
899
- : "hover:bg-muted/80",
1011
+ categoryDisabled &&
1012
+ "cursor-not-allowed opacity-50",
1013
+ !categoryDisabled &&
1014
+ (index === mentionHighlight
1015
+ ? "bg-accent text-accent-foreground"
1016
+ : "hover:bg-muted/80"),
900
1017
  )}
901
- onMouseEnter={() => setMentionHighlight(index)}
1018
+ onMouseEnter={() => {
1019
+ if (!categoryDisabled) setMentionHighlight(index);
1020
+ }}
902
1021
  onMouseDown={(event) => {
903
1022
  event.preventDefault();
1023
+ if (categoryDisabled) return;
904
1024
  applyCategoryPick(entry.kind);
905
1025
  }}
906
1026
  >
@@ -985,15 +1105,24 @@ function InternalChatPanel({
985
1105
  if (event.key === "ArrowDown") {
986
1106
  event.preventDefault();
987
1107
  setMentionHighlight((h) =>
988
- (h + 1) % mentionEntries.length,
1108
+ stepMentionHighlight(
1109
+ mentionEntries,
1110
+ mentionConfig,
1111
+ h,
1112
+ 1,
1113
+ ),
989
1114
  );
990
1115
  return;
991
1116
  }
992
1117
  if (event.key === "ArrowUp") {
993
1118
  event.preventDefault();
994
1119
  setMentionHighlight((h) =>
995
- (h - 1 + mentionEntries.length) %
996
- mentionEntries.length,
1120
+ stepMentionHighlight(
1121
+ mentionEntries,
1122
+ mentionConfig,
1123
+ h,
1124
+ -1,
1125
+ ),
997
1126
  );
998
1127
  return;
999
1128
  }
@@ -1024,7 +1153,9 @@ function InternalChatPanel({
1024
1153
  if (event.key === "Enter" && !event.shiftKey) {
1025
1154
  event.preventDefault();
1026
1155
  const pick = mentionEntries[mentionHighlight];
1027
- if (!pick) return;
1156
+ if (!pick || isMentionEntryDisabled(pick, mentionConfig)) {
1157
+ return;
1158
+ }
1028
1159
  if (pick.type === "category") {
1029
1160
  applyCategoryPick(pick.kind);
1030
1161
  } else {
@@ -1035,7 +1166,9 @@ function InternalChatPanel({
1035
1166
  if (event.key === "Tab") {
1036
1167
  event.preventDefault();
1037
1168
  const pick = mentionEntries[mentionHighlight];
1038
- if (!pick) return;
1169
+ if (!pick || isMentionEntryDisabled(pick, mentionConfig)) {
1170
+ return;
1171
+ }
1039
1172
  if (pick.type === "category") {
1040
1173
  applyCategoryPick(pick.kind);
1041
1174
  } else {
@@ -1109,6 +1242,8 @@ export {
1109
1242
  export { initialsFromDisplayName } from "./internal-chat-mention";
1110
1243
 
1111
1244
  export type {
1245
+ BuildMentionListOptions,
1246
+ InternalChatMentionCatalogState,
1112
1247
  InternalChatMentionConfig,
1113
1248
  InternalChatMentionItem,
1114
1249
  InternalChatMentionKind,
@@ -0,0 +1,16 @@
1
+ "use client"
2
+
3
+ /** Compat: importa desde `@traky-app/ui/office-address-form` (`surface="modal"`). */
4
+ export {
5
+ OfficeAddressForm,
6
+ OfficeAddressForm as OfficeAddressFormDialog,
7
+ OfficeAddressFormCard,
8
+ useOfficeAddressForm,
9
+ EMPTY_OFFICE_ADDRESS_FORM_VALUES,
10
+ mergeOfficeAddressFormValues,
11
+ type OfficeAddressFormProps,
12
+ type OfficeAddressFormDialogProps,
13
+ type OfficeAddressFormValues,
14
+ type OfficeAddressEntryMode,
15
+ type OfficeAddressFormCardProps,
16
+ } from "./office-address-form"