@trii/components 2.0.103 → 3.0.0

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 (39) hide show
  1. package/async-state/package.json +9 -0
  2. package/dist/async-state.d.ts +184 -0
  3. package/dist/cjs/async-state.js +2 -0
  4. package/dist/cjs/async-state.js.map +1 -0
  5. package/dist/cjs/index.js +36 -36
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/esm/async-state.js +2 -0
  8. package/dist/esm/async-state.js.map +1 -0
  9. package/dist/esm/index.js +38 -38
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/index.d.ts +346 -5
  12. package/dist/types/asyncState/asyncState.d.ts +99 -0
  13. package/dist/types/asyncState/getErrorMessage.d.ts +16 -0
  14. package/dist/types/asyncState/index.d.ts +15 -0
  15. package/dist/types/asyncState/types.d.ts +17 -0
  16. package/dist/types/asyncState/useAsyncData.d.ts +51 -0
  17. package/dist/types/components/AsyncContent/AsyncContent.d.ts +48 -0
  18. package/dist/types/components/AsyncContent/index.d.ts +2 -0
  19. package/dist/types/components/ContactSidebar/ChatDetailsSidebar.d.ts +17 -3
  20. package/dist/types/components/ContactSidebar/SidebarContent.d.ts +9 -4
  21. package/dist/types/components/ContactSidebar/components/PanelView/PanelView.d.ts +1 -4
  22. package/dist/types/components/ContactSidebar/components/PanelView/components/ContactMedia/ContactMedia.d.ts +0 -1
  23. package/dist/types/components/ContactSidebar/components/PanelView/components/GroupData/GroupData.d.ts +0 -1
  24. package/dist/types/components/ContactSidebar/components/PanelView/components/index.d.ts +0 -3
  25. package/dist/types/components/ContactSidebar/components/PanelView/layout/SectionCard/SectionCard.d.ts +1 -1
  26. package/dist/types/components/ContactSidebar/components/PanelView/types/ContactProps.d.ts +0 -1
  27. package/dist/types/components/ContactSidebar/components/TabsMenu/TabsMenu.d.ts +0 -3
  28. package/dist/types/components/ContactSidebar/components/index.d.ts +0 -3
  29. package/dist/types/components/EmptyState/EmptyState.d.ts +22 -0
  30. package/dist/types/components/EmptyState/index.d.ts +2 -0
  31. package/dist/types/components/ErrorState/ErrorState.d.ts +23 -0
  32. package/dist/types/components/ErrorState/index.d.ts +2 -0
  33. package/dist/types/components/PanelErrorBoundary/PanelErrorBoundary.d.ts +55 -0
  34. package/dist/types/components/PanelErrorBoundary/index.d.ts +2 -0
  35. package/dist/types/components/index.d.ts +8 -0
  36. package/dist/types/i18n/config.d.ts +6 -0
  37. package/dist/types/i18n/useComponentsTranslation.d.ts +17 -0
  38. package/dist/types/index.d.ts +1 -0
  39. package/package.json +5 -3
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { IContact, IBusiness, IContactAddress } from '@trii/types/dist/Contacts'
4
4
  import { Spaces } from '@trii/types';
5
5
  import { SxProps, Theme } from '@mui/material';
6
6
  import { IContact as IContact$1, IBusiness as IBusiness$1 } from '@trii/types/dist/Contacts/contacts';
7
- import React from 'react';
7
+ import React, { ReactNode, ErrorInfo, Component } from 'react';
8
8
  import { IConversation } from '@trii/types/dist/Conversations';
9
9
  import { ChannelType } from '@trii/types/dist/Common/Channels';
10
10
  import * as i18next from 'i18next';
@@ -244,6 +244,333 @@ interface TaskData {
244
244
  relatedContactId?: string;
245
245
  }
246
246
 
247
+ /**
248
+ * Modelo canónico de estado asíncrono (loading / error / caché remota).
249
+ *
250
+ * Unifica cómo los paneles representan el ciclo de vida de un dato remoto:
251
+ *
252
+ * - `idle`/`loading` sin datos → carga inicial (bloqueante: skeleton/spinner).
253
+ * - `loading` con datos → revalidación NO bloqueante (`isRevalidating`).
254
+ * - `rejected` con datos → error manteniendo datos previos + retry.
255
+ * - `rejected` sin datos → error bloqueante con retry.
256
+ * - `succeeded` sin datos → estado realmente vacío (`isEmpty`).
257
+ *
258
+ * Los literales de `AsyncStatus` coinciden con el `RequestStatus` que usan los
259
+ * slices de los frontends Trii, para que sean compatibles sin conversión.
260
+ *
261
+ * Módulo puro: sin React, sin MUI, sin Redux, sin dominio. Se publica también
262
+ * por el subpath `@trii/components/async-state` para que un slice pueda
263
+ * consumirlo sin arrastrar la UI del paquete.
264
+ *
265
+ * Guía de uso: docs/ASYNC_STATE.md
266
+ */
267
+ type AsyncStatus = 'idle' | 'loading' | 'succeeded' | 'rejected';
268
+ /**
269
+ * Estado "crudo" persistible: es lo que guarda un slice de Redux o el estado
270
+ * interno de `useAsyncData`. Los flags derivados se calculan con
271
+ * `deriveAsyncState` (nunca se almacenan, para evitar inconsistencias).
272
+ */
273
+ interface AsyncData<T> {
274
+ status: AsyncStatus;
275
+ /** Últimos datos válidos. Se preservan durante refetches y errores. */
276
+ data: T | undefined;
277
+ /** Mensaje del último error (null si la última operación fue exitosa). */
278
+ error: string | null;
279
+ /** Timestamp (Date.now()) del último fetch exitoso. Base para staleness. */
280
+ updatedAt: number | null;
281
+ }
282
+ /** Estado derivado listo para consumir desde la UI. */
283
+ interface AsyncState<T> extends AsyncData<T> {
284
+ /** true si hay datos utilizables para mostrar (no undefined ni "vacíos"). */
285
+ hasData: boolean;
286
+ /** Carga inicial: está cargando y NO hay datos que mostrar → bloqueante. */
287
+ isInitialLoading: boolean;
288
+ /** Revalidación: está cargando y SÍ hay datos → no bloquear la UI. */
289
+ isRevalidating: boolean;
290
+ /** Vacío real: la última carga fue exitosa y no hay datos que mostrar. */
291
+ isEmpty: boolean;
292
+ }
293
+ /** Decide si un dato cuenta como "vacío" (p.ej. lista sin elementos). */
294
+ type IsEmptyFn<T> = (data: T) => boolean;
295
+ /**
296
+ * Vacío por defecto: `null`/`undefined` y arrays sin elementos.
297
+ * Cada feature puede aportar su propia regla (p.ej. "objeto sin campos").
298
+ */
299
+ declare const defaultIsEmpty: <T>(data: T) => boolean;
300
+ /** Estado inicial: nunca se hizo un fetch. */
301
+ declare const asyncIdle: <T>() => AsyncData<T>;
302
+ /**
303
+ * Transición a "request en curso".
304
+ * Por defecto preserva los datos previos (refetch no bloqueante).
305
+ * Pasar `clearData: true` solo cuando los datos viejos dejarían de ser
306
+ * válidos para la nueva request (p.ej. cambio de contacto seleccionado).
307
+ */
308
+ declare const asyncRequestStarted: <T>(prev: AsyncData<T>, options?: {
309
+ clearData?: boolean;
310
+ }) => AsyncData<T>;
311
+ /** Transición a éxito: guarda datos, limpia error y sella `updatedAt`. */
312
+ declare const asyncRequestSucceeded: <T>(data: T) => AsyncData<T>;
313
+ /**
314
+ * Transición a error: guarda el mensaje y PRESERVA los datos previos
315
+ * para que la UI pueda seguir mostrándolos (error con datos).
316
+ */
317
+ declare const asyncRequestFailed: <T>(error: string, prev: AsyncData<T>) => AsyncData<T>;
318
+ /**
319
+ * Deriva los flags de UI a partir del estado crudo. Es la ÚNICA fuente de
320
+ * verdad para interpretar un estado asíncrono: la usan `useAsyncData`,
321
+ * los selectores de Redux y `<AsyncContent>`.
322
+ *
323
+ * Uso con un slice existente (sin reescribirlo):
324
+ *
325
+ * ```ts
326
+ * const selectChannelsAsyncState = createSelector(
327
+ * selectChannelsState,
328
+ * (state) =>
329
+ * deriveAsyncState({
330
+ * status: state.status.fetch,
331
+ * data: state.channels,
332
+ * error: state.error,
333
+ * })
334
+ * );
335
+ * ```
336
+ */
337
+ declare const deriveAsyncState: <T>(input: {
338
+ status: AsyncStatus;
339
+ data: T | undefined;
340
+ error?: string | null;
341
+ updatedAt?: number | null;
342
+ isEmpty?: IsEmptyFn<T>;
343
+ }) => AsyncState<T>;
344
+ /** true si el dato tiene más de `staleTime` ms (o nunca se cargó). */
345
+ declare const isStale: (updatedAt: number | null, staleTime: number, now?: number) => boolean;
346
+
347
+ /**
348
+ * Extrae un mensaje legible de cualquier error (axios, Error, string...).
349
+ *
350
+ * Orden de preferencia:
351
+ * 1. `error.response.data.message` / `error.response.data.error` (API Trii).
352
+ * 2. `error.message` (Error estándar, p.ej. "Network Error" de axios).
353
+ * 3. El string recibido tal cual.
354
+ * 4. `fallback`.
355
+ *
356
+ * No traduce: la traducción de mensajes genéricos ocurre en la UI
357
+ * (`<ErrorState>`); esto preserva el detalle técnico o del backend.
358
+ *
359
+ * Tipa axios por duck-typing a propósito: el módulo no importa axios, así que
360
+ * sirve igual con fetch o con cualquier cliente HTTP.
361
+ */
362
+ declare const getErrorMessage: (error: unknown, fallback?: string) => string;
363
+
364
+ /** Resultado de `useAsyncData`: estado derivado + acciones del ciclo de vida. */
365
+ interface UseAsyncDataResult<T> extends AsyncState<T> {
366
+ /**
367
+ * Re-ejecuta el fetcher. Si ya hay datos, NO bloquea la UI
368
+ * (`isRevalidating` pasa a true y los datos siguen visibles).
369
+ */
370
+ refetch: () => Promise<void>;
371
+ /**
372
+ * Alias semántico de `refetch` para los estados de error.
373
+ * Es el callback que debe recibir `<AsyncContent onRetry={...}>`.
374
+ */
375
+ retry: () => Promise<void>;
376
+ /** Vuelve al estado `idle` y cancela cualquier request en curso. */
377
+ reset: () => void;
378
+ }
379
+
380
+ /**
381
+ * Ciclo de vida completo de un dato remoto LOCAL a un panel (sin Redux).
382
+ *
383
+ * Comportamiento:
384
+ * - Fetch al montar (configurable con `fetchOnMount`) y cuando cambian `deps`.
385
+ * - Carga inicial sin datos → `isInitialLoading` (bloqueante).
386
+ * - Refetch con datos → `isRevalidating`: los datos siguen visibles.
387
+ * - Error con datos previos → `status: 'rejected'` + datos preservados.
388
+ * - Error sin datos → `error` para mostrar un estado de error con `retry`.
389
+ * - `refetch()`/`retry()` re-ejecutan el fetcher; nunca bloquean si hay datos.
390
+ * - Cancelación automática (AbortSignal) y guarda de carrera: si se dispara
391
+ * una request nueva, las respuestas viejas se descartan.
392
+ * - `autoRetry` opcional con backoff lineal antes de mostrar el error.
393
+ *
394
+ * Usar Redux en su lugar cuando el dato se comparte entre varios paneles o
395
+ * se actualiza por socket (ver docs/ASYNC_STATE.md §¿Redux o hook?).
396
+ *
397
+ * @example
398
+ * const state = useAsyncData(
399
+ * (signal) => reportsService.getReports(url, { signal }),
400
+ * { deps: [url] }
401
+ * );
402
+ * return (
403
+ * <AsyncContent state={state} onRetry={state.retry}>
404
+ * {(reports) => <ReportList reports={reports} />}
405
+ * </AsyncContent>
406
+ * );
407
+ */
408
+ declare function useAsyncData<T>(fetcher: (signal: AbortSignal) => Promise<T>, options?: {
409
+ /** Regla de "vacío" del dato (default: null/undefined o array vacío). */
410
+ isEmpty?: IsEmptyFn<T>;
411
+ /** Disparar el fetch al montar y al cambiar `deps` (default: true). */
412
+ fetchOnMount?: boolean;
413
+ /**
414
+ * Preservar datos previos durante refetches (default: true).
415
+ * Poner en false solo si los datos viejos serían inválidos/engañosos.
416
+ */
417
+ keepPreviousData?: boolean;
418
+ /**
419
+ * Reintentos automáticos antes de marcar error (default: sin reintentos;
420
+ * el usuario decide con el botón de retry).
421
+ */
422
+ autoRetry?: {
423
+ attempts: number;
424
+ delayMs?: number;
425
+ };
426
+ /** Dependencias que re-disparan el fetch (p.ej. un id o filtro). */
427
+ deps?: ReadonlyArray<unknown>;
428
+ }): UseAsyncDataResult<T>;
429
+
430
+ interface AsyncContentProps<T> {
431
+ /** Estado derivado (de `useAsyncData` o de un selector con `deriveAsyncState`). */
432
+ state: AsyncState<T>;
433
+ /** Contenido real: se llama solo cuando hay datos utilizables. */
434
+ children: (data: T) => ReactNode;
435
+ /**
436
+ * Callback de reintento (p.ej. `state.retry` del hook o re-dispatch del
437
+ * thunk). Habilita el botón "Reintentar" en los estados de error.
438
+ */
439
+ onRetry?: () => void;
440
+ /** Fallback de carga inicial (default: spinner centrado). Usar skeletons cuando el layout del contenido es conocido. */
441
+ loadingFallback?: ReactNode;
442
+ /** Fallback de vacío (default: `<EmptyState>` con textos i18n). */
443
+ emptyFallback?: ReactNode;
444
+ /** Fallback de error sin datos (default: `<ErrorState>` con retry). */
445
+ errorFallback?: ReactNode;
446
+ /** Overrides de textos del estado vacío. */
447
+ emptyTitle?: string;
448
+ emptyDescription?: string;
449
+ /** Override del título del estado de error. */
450
+ errorTitle?: string;
451
+ /** Mostrar la barra fina de revalidación sobre el contenido (default: true). */
452
+ showRevalidatingIndicator?: boolean;
453
+ /** Idioma de los textos por defecto ('es' | 'en'). Opcional: ver useComponentsTranslation. */
454
+ language?: string;
455
+ }
456
+ /**
457
+ * Renderiza un dato remoto según su `AsyncState`, con feedback consistente:
458
+ *
459
+ * | Situación | Render |
460
+ * |----------------------------------|------------------------------------------------|
461
+ * | `idle`/`loading` sin datos | `loadingFallback` (carga inicial bloqueante) |
462
+ * | `rejected` sin datos | `ErrorState` + retry |
463
+ * | `succeeded` sin datos (`isEmpty`)| `EmptyState` |
464
+ * | datos + `loading` | contenido + barra de revalidación (no bloquea) |
465
+ * | datos + `rejected` | contenido + banner de error con retry |
466
+ * | datos | contenido |
467
+ *
468
+ * La rama "con datos" usa siempre el mismo wrapper para que un refetch o un
469
+ * error NUNCA desmonte el contenido (conserva scroll, foco y estado interno).
470
+ *
471
+ * Para errores de RENDER (excepciones) combinar con `<PanelErrorBoundary>`.
472
+ * Guía completa: docs/ASYNC_STATE.md
473
+ */
474
+ declare const AsyncContent: <T>({ state, children, onRetry, loadingFallback, emptyFallback, errorFallback, emptyTitle, emptyDescription, errorTitle, showRevalidatingIndicator, language, }: AsyncContentProps<T>) => react_jsx_runtime.JSX.Element;
475
+
476
+ interface EmptyStateProps {
477
+ /** Título principal (default: i18n `asyncState.emptyTitle`). */
478
+ title?: string;
479
+ /** Descripción secundaria (default: i18n `asyncState.emptyDescription`). */
480
+ description?: string;
481
+ /** Icono personalizado (default: `InboxIcon`). */
482
+ icon?: ReactNode;
483
+ /** Acción opcional bajo el texto (p.ej. botón "Crear el primero"). */
484
+ action?: ReactNode;
485
+ /** Versión compacta (menos padding e icono más chico). */
486
+ compact?: boolean;
487
+ /** Idioma de los textos por defecto ('es' | 'en'). Opcional: ver useComponentsTranslation. */
488
+ language?: string;
489
+ }
490
+ /**
491
+ * Estado "vacío real": la carga fue exitosa pero no hay datos que mostrar.
492
+ * No confundir con "aún no se cargó" (eso es loading) ni con un error.
493
+ * Presentacional y genérico (ver docs/ASYNC_STATE.md).
494
+ */
495
+ declare const EmptyState: ({ title, description, icon, action, compact, language, }: EmptyStateProps) => react_jsx_runtime.JSX.Element;
496
+
497
+ interface ErrorStateProps {
498
+ /** Título principal (default: i18n `asyncState.errorTitle`). */
499
+ title?: string;
500
+ /** Descripción secundaria (default: i18n `asyncState.errorDescription`). */
501
+ description?: string;
502
+ /** Detalle técnico o mensaje del backend (se muestra atenuado). */
503
+ error?: string | null;
504
+ /** Si se provee, muestra el botón de reintento. */
505
+ onRetry?: () => void;
506
+ /** Texto del botón de reintento (default: i18n `asyncState.retry`). */
507
+ retryLabel?: string;
508
+ /** Versión compacta (menos padding e icono más chico) para paneles angostos. */
509
+ compact?: boolean;
510
+ /** Idioma de los textos por defecto ('es' | 'en'). Opcional: ver useComponentsTranslation. */
511
+ language?: string;
512
+ }
513
+ /**
514
+ * Estado de error a nivel panel, con retry opcional.
515
+ * Presentacional y genérico: no conoce Redux ni fetching
516
+ * (ver docs/ASYNC_STATE.md).
517
+ */
518
+ declare const ErrorState: ({ title, description, error, onRetry, retryLabel, compact, language, }: ErrorStateProps) => react_jsx_runtime.JSX.Element;
519
+
520
+ interface PanelErrorBoundaryProps {
521
+ children: ReactNode;
522
+ /**
523
+ * Fallback custom ante un crash de render. Puede ser un nodo o una función
524
+ * que recibe el error y `resetErrorBoundary` (para reintentar).
525
+ */
526
+ fallback?: ReactNode | ((args: {
527
+ error: Error | null;
528
+ resetErrorBoundary: () => void;
529
+ }) => ReactNode);
530
+ /** Telemetría/logging del error (p.ej. enviar a un servicio externo). */
531
+ onError?: (error: Error, info: ErrorInfo) => void;
532
+ /**
533
+ * Claves que al cambiar resetean el boundary automáticamente
534
+ * (p.ej. el id de la ruta actual, para reintentar al navegar).
535
+ */
536
+ resetKeys?: ReadonlyArray<unknown>;
537
+ /** Overrides de textos del fallback por defecto. */
538
+ title?: string;
539
+ description?: string;
540
+ /** Idioma de los textos por defecto ('es' | 'en'). Opcional: ver useComponentsTranslation. */
541
+ language?: string;
542
+ }
543
+ interface PanelErrorBoundaryState {
544
+ hasError: boolean;
545
+ error: Error | null;
546
+ }
547
+ /**
548
+ * Aísla fallos de RENDER por panel: si el contenido explota, el resto de la
549
+ * UI (hermanos, otros paneles, la navegación) sigue funcionando y el panel
550
+ * muestra un fallback con reintento.
551
+ *
552
+ * ```tsx
553
+ * <PanelErrorBoundary resetKeys={[conversationId]}>
554
+ * <ContactInfoPanel />
555
+ * </PanelErrorBoundary>
556
+ * <PanelErrorBoundary>
557
+ * <MessagesPanel />
558
+ * </PanelErrorBoundary>
559
+ * ```
560
+ *
561
+ * NOTA: los boundaries NO capturan errores de fetching (para eso está
562
+ * `<AsyncContent>`), ni de event handlers, ni de código asíncrono.
563
+ * Guía completa: docs/ASYNC_STATE.md
564
+ */
565
+ declare class PanelErrorBoundary extends Component<PanelErrorBoundaryProps, PanelErrorBoundaryState> {
566
+ state: PanelErrorBoundaryState;
567
+ static getDerivedStateFromError(error: Error): PanelErrorBoundaryState;
568
+ componentDidCatch(error: Error, info: ErrorInfo): void;
569
+ componentDidUpdate(prevProps: PanelErrorBoundaryProps): void;
570
+ reset: () => void;
571
+ render(): ReactNode;
572
+ }
573
+
247
574
  /**
248
575
  * Integración con un inbox de chat. Es opcional: sin ella el sidebar funciona
249
576
  * en "modo ficha" (sin GroupData, sin abrir conversaciones, sin iniciar una
@@ -293,6 +620,12 @@ interface ChatDetailsSidebarBaseProps {
293
620
  * propia instancia de i18n, así que no sigue solo los cambios del host.
294
621
  */
295
622
  language?: string;
623
+ /**
624
+ * Gate de visibilidad (CSS display) del panel. Para hosts que renderizan
625
+ * vistas hermanas alternadas por CSS: el panel queda montado (conserva su
626
+ * estado y sus datos) pero oculto mientras `active` es false. Default: true.
627
+ */
628
+ active?: boolean;
296
629
  }
297
630
  /**
298
631
  * Qué mostrar. Hay dos formas y son excluyentes; TypeScript falla si no se pasa
@@ -325,9 +658,17 @@ type ChatDetailsSidebarTarget = {
325
658
  };
326
659
  type ChatDetailsSidebarProps = ChatDetailsSidebarBaseProps & ChatDetailsSidebarTarget;
327
660
  /**
328
- * Shell del sidebar (en camino al paquete npm): recibe las props del
329
- * contrato (docs/CONTRATO_CHATDETAILSSIDEBAR.md) y monta los providers
330
- * internos. La orquestación de UI vive en SidebarContent.
661
+ * Panel de contacto reutilizable: la ficha completa de un contacto/empresa
662
+ * (datos, conversaciones, archivos, media, historial, eventos, tareas, shop,
663
+ * relaciones, grupos de WhatsApp) con su riel vertical de tabs, skeleton y
664
+ * overlay de carga. Recibe las props del contrato y monta los providers
665
+ * internos; la orquestación de tabs vive en SidebarContent.
666
+ *
667
+ * El shell de vistas (header Contacto/Interacción/Asistente) es del host: este
668
+ * componente renderiza SOLO la vista de contacto y expone `active` para que el
669
+ * host lo alterne por CSS con sus vistas hermanas sin desmontarlo. Debe
670
+ * colocarse dentro de un contenedor flex con `position: relative` (el overlay
671
+ * y el skeleton se posicionan contra ese contenedor). Ver README.md.
331
672
  */
332
673
  declare const ChatDetailsSidebar: (props: ChatDetailsSidebarProps) => react_jsx_runtime.JSX.Element;
333
674
 
@@ -459,4 +800,4 @@ declare const selectThemeToggleStatus: (state: ThemeRootState) => "idle" | "togg
459
800
  declare const selectedTheme: (state: ThemeRootState) => Theme;
460
801
  declare const selectedDarkTheme: (state: ThemeRootState) => boolean;
461
802
 
462
- export { type Calendar, type Contact, ContactInfoPopup, ChatDetailsSidebar as ContactSidebar, type ChatDetailsSidebarProps as ContactSidebarProps, EditContactModal, type EditContactModalProps, type EditContactMode, type EventData, EventsDialog, type EventsDialogProps, type ItemType, type ModalMode, MuiComponentsConfig, type Notification, type SidebarChatIntegration, type StartConversationTarget, type TaskData, type ThemeName, type ThemeRootState, type ThemeState, darkBlack, darkBlue, i18nInstance as i18n, isValidThemeName, lightAmber, lightBlue, lightBlueIndigo, lightBrown, lightGray, lightGreen, lightLime, lightOrange, lightPink, lightPurple, lightRed, lightTeal, lightYellow, selectThemeToggleStatus, selectedDarkTheme, selectedTheme, toggleModeTheme, toggleTheme, updateTheme };
803
+ export { AsyncContent, type AsyncContentProps, type AsyncData, type AsyncState, type AsyncStatus, type Calendar, type Contact, ContactInfoPopup, ChatDetailsSidebar as ContactSidebar, type ChatDetailsSidebarProps as ContactSidebarProps, EditContactModal, type EditContactModalProps, type EditContactMode, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, type EventData, EventsDialog, type EventsDialogProps, type IsEmptyFn, type ItemType, type ModalMode, MuiComponentsConfig, type Notification, PanelErrorBoundary, type PanelErrorBoundaryProps, type SidebarChatIntegration, type StartConversationTarget, type TaskData, type ThemeName, type ThemeRootState, type ThemeState, type UseAsyncDataResult, asyncIdle, asyncRequestFailed, asyncRequestStarted, asyncRequestSucceeded, darkBlack, darkBlue, defaultIsEmpty, deriveAsyncState, getErrorMessage, i18nInstance as i18n, isStale, isValidThemeName, lightAmber, lightBlue, lightBlueIndigo, lightBrown, lightGray, lightGreen, lightLime, lightOrange, lightPink, lightPurple, lightRed, lightTeal, lightYellow, selectThemeToggleStatus, selectedDarkTheme, selectedTheme, toggleModeTheme, toggleTheme, updateTheme, useAsyncData };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Modelo canónico de estado asíncrono (loading / error / caché remota).
3
+ *
4
+ * Unifica cómo los paneles representan el ciclo de vida de un dato remoto:
5
+ *
6
+ * - `idle`/`loading` sin datos → carga inicial (bloqueante: skeleton/spinner).
7
+ * - `loading` con datos → revalidación NO bloqueante (`isRevalidating`).
8
+ * - `rejected` con datos → error manteniendo datos previos + retry.
9
+ * - `rejected` sin datos → error bloqueante con retry.
10
+ * - `succeeded` sin datos → estado realmente vacío (`isEmpty`).
11
+ *
12
+ * Los literales de `AsyncStatus` coinciden con el `RequestStatus` que usan los
13
+ * slices de los frontends Trii, para que sean compatibles sin conversión.
14
+ *
15
+ * Módulo puro: sin React, sin MUI, sin Redux, sin dominio. Se publica también
16
+ * por el subpath `@trii/components/async-state` para que un slice pueda
17
+ * consumirlo sin arrastrar la UI del paquete.
18
+ *
19
+ * Guía de uso: docs/ASYNC_STATE.md
20
+ */
21
+ export type AsyncStatus = 'idle' | 'loading' | 'succeeded' | 'rejected';
22
+ /**
23
+ * Estado "crudo" persistible: es lo que guarda un slice de Redux o el estado
24
+ * interno de `useAsyncData`. Los flags derivados se calculan con
25
+ * `deriveAsyncState` (nunca se almacenan, para evitar inconsistencias).
26
+ */
27
+ export interface AsyncData<T> {
28
+ status: AsyncStatus;
29
+ /** Últimos datos válidos. Se preservan durante refetches y errores. */
30
+ data: T | undefined;
31
+ /** Mensaje del último error (null si la última operación fue exitosa). */
32
+ error: string | null;
33
+ /** Timestamp (Date.now()) del último fetch exitoso. Base para staleness. */
34
+ updatedAt: number | null;
35
+ }
36
+ /** Estado derivado listo para consumir desde la UI. */
37
+ export interface AsyncState<T> extends AsyncData<T> {
38
+ /** true si hay datos utilizables para mostrar (no undefined ni "vacíos"). */
39
+ hasData: boolean;
40
+ /** Carga inicial: está cargando y NO hay datos que mostrar → bloqueante. */
41
+ isInitialLoading: boolean;
42
+ /** Revalidación: está cargando y SÍ hay datos → no bloquear la UI. */
43
+ isRevalidating: boolean;
44
+ /** Vacío real: la última carga fue exitosa y no hay datos que mostrar. */
45
+ isEmpty: boolean;
46
+ }
47
+ /** Decide si un dato cuenta como "vacío" (p.ej. lista sin elementos). */
48
+ export type IsEmptyFn<T> = (data: T) => boolean;
49
+ /**
50
+ * Vacío por defecto: `null`/`undefined` y arrays sin elementos.
51
+ * Cada feature puede aportar su propia regla (p.ej. "objeto sin campos").
52
+ */
53
+ export declare const defaultIsEmpty: <T>(data: T) => boolean;
54
+ /** Estado inicial: nunca se hizo un fetch. */
55
+ export declare const asyncIdle: <T>() => AsyncData<T>;
56
+ /**
57
+ * Transición a "request en curso".
58
+ * Por defecto preserva los datos previos (refetch no bloqueante).
59
+ * Pasar `clearData: true` solo cuando los datos viejos dejarían de ser
60
+ * válidos para la nueva request (p.ej. cambio de contacto seleccionado).
61
+ */
62
+ export declare const asyncRequestStarted: <T>(prev: AsyncData<T>, options?: {
63
+ clearData?: boolean;
64
+ }) => AsyncData<T>;
65
+ /** Transición a éxito: guarda datos, limpia error y sella `updatedAt`. */
66
+ export declare const asyncRequestSucceeded: <T>(data: T) => AsyncData<T>;
67
+ /**
68
+ * Transición a error: guarda el mensaje y PRESERVA los datos previos
69
+ * para que la UI pueda seguir mostrándolos (error con datos).
70
+ */
71
+ export declare const asyncRequestFailed: <T>(error: string, prev: AsyncData<T>) => AsyncData<T>;
72
+ /**
73
+ * Deriva los flags de UI a partir del estado crudo. Es la ÚNICA fuente de
74
+ * verdad para interpretar un estado asíncrono: la usan `useAsyncData`,
75
+ * los selectores de Redux y `<AsyncContent>`.
76
+ *
77
+ * Uso con un slice existente (sin reescribirlo):
78
+ *
79
+ * ```ts
80
+ * const selectChannelsAsyncState = createSelector(
81
+ * selectChannelsState,
82
+ * (state) =>
83
+ * deriveAsyncState({
84
+ * status: state.status.fetch,
85
+ * data: state.channels,
86
+ * error: state.error,
87
+ * })
88
+ * );
89
+ * ```
90
+ */
91
+ export declare const deriveAsyncState: <T>(input: {
92
+ status: AsyncStatus;
93
+ data: T | undefined;
94
+ error?: string | null;
95
+ updatedAt?: number | null;
96
+ isEmpty?: IsEmptyFn<T>;
97
+ }) => AsyncState<T>;
98
+ /** true si el dato tiene más de `staleTime` ms (o nunca se cargó). */
99
+ export declare const isStale: (updatedAt: number | null, staleTime: number, now?: number) => boolean;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extrae un mensaje legible de cualquier error (axios, Error, string...).
3
+ *
4
+ * Orden de preferencia:
5
+ * 1. `error.response.data.message` / `error.response.data.error` (API Trii).
6
+ * 2. `error.message` (Error estándar, p.ej. "Network Error" de axios).
7
+ * 3. El string recibido tal cual.
8
+ * 4. `fallback`.
9
+ *
10
+ * No traduce: la traducción de mensajes genéricos ocurre en la UI
11
+ * (`<ErrorState>`); esto preserva el detalle técnico o del backend.
12
+ *
13
+ * Tipa axios por duck-typing a propósito: el módulo no importa axios, así que
14
+ * sirve igual con fetch o con cualquier cliente HTTP.
15
+ */
16
+ export declare const getErrorMessage: (error: unknown, fallback?: string) => string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Punto de entrada del modelo de estado asíncrono.
3
+ *
4
+ * Este módulo NO importa MUI ni react-i18next: se publica además por el
5
+ * subpath `@trii/components/async-state` para que un slice de Redux o un
6
+ * servicio pueda usar `deriveAsyncState`/`getErrorMessage` sin arrastrar la
7
+ * UI del paquete. Los componentes que lo pintan (`AsyncContent`,
8
+ * `EmptyState`, `ErrorState`, `PanelErrorBoundary`) viven en la entrada
9
+ * principal.
10
+ */
11
+ export { defaultIsEmpty, asyncIdle, asyncRequestStarted, asyncRequestSucceeded, asyncRequestFailed, deriveAsyncState, isStale, } from './asyncState';
12
+ export type { AsyncStatus, AsyncData, AsyncState, IsEmptyFn, } from './asyncState';
13
+ export { getErrorMessage } from './getErrorMessage';
14
+ export { useAsyncData } from './useAsyncData';
15
+ export type { UseAsyncDataResult } from './types';
@@ -0,0 +1,17 @@
1
+ import { AsyncState } from './asyncState';
2
+ export type { AsyncData, IsEmptyFn } from './asyncState';
3
+ /** Resultado de `useAsyncData`: estado derivado + acciones del ciclo de vida. */
4
+ export interface UseAsyncDataResult<T> extends AsyncState<T> {
5
+ /**
6
+ * Re-ejecuta el fetcher. Si ya hay datos, NO bloquea la UI
7
+ * (`isRevalidating` pasa a true y los datos siguen visibles).
8
+ */
9
+ refetch: () => Promise<void>;
10
+ /**
11
+ * Alias semántico de `refetch` para los estados de error.
12
+ * Es el callback que debe recibir `<AsyncContent onRetry={...}>`.
13
+ */
14
+ retry: () => Promise<void>;
15
+ /** Vuelve al estado `idle` y cancela cualquier request en curso. */
16
+ reset: () => void;
17
+ }
@@ -0,0 +1,51 @@
1
+ import { UseAsyncDataResult } from './types';
2
+ import { IsEmptyFn } from './asyncState';
3
+ /**
4
+ * Ciclo de vida completo de un dato remoto LOCAL a un panel (sin Redux).
5
+ *
6
+ * Comportamiento:
7
+ * - Fetch al montar (configurable con `fetchOnMount`) y cuando cambian `deps`.
8
+ * - Carga inicial sin datos → `isInitialLoading` (bloqueante).
9
+ * - Refetch con datos → `isRevalidating`: los datos siguen visibles.
10
+ * - Error con datos previos → `status: 'rejected'` + datos preservados.
11
+ * - Error sin datos → `error` para mostrar un estado de error con `retry`.
12
+ * - `refetch()`/`retry()` re-ejecutan el fetcher; nunca bloquean si hay datos.
13
+ * - Cancelación automática (AbortSignal) y guarda de carrera: si se dispara
14
+ * una request nueva, las respuestas viejas se descartan.
15
+ * - `autoRetry` opcional con backoff lineal antes de mostrar el error.
16
+ *
17
+ * Usar Redux en su lugar cuando el dato se comparte entre varios paneles o
18
+ * se actualiza por socket (ver docs/ASYNC_STATE.md §¿Redux o hook?).
19
+ *
20
+ * @example
21
+ * const state = useAsyncData(
22
+ * (signal) => reportsService.getReports(url, { signal }),
23
+ * { deps: [url] }
24
+ * );
25
+ * return (
26
+ * <AsyncContent state={state} onRetry={state.retry}>
27
+ * {(reports) => <ReportList reports={reports} />}
28
+ * </AsyncContent>
29
+ * );
30
+ */
31
+ export declare function useAsyncData<T>(fetcher: (signal: AbortSignal) => Promise<T>, options?: {
32
+ /** Regla de "vacío" del dato (default: null/undefined o array vacío). */
33
+ isEmpty?: IsEmptyFn<T>;
34
+ /** Disparar el fetch al montar y al cambiar `deps` (default: true). */
35
+ fetchOnMount?: boolean;
36
+ /**
37
+ * Preservar datos previos durante refetches (default: true).
38
+ * Poner en false solo si los datos viejos serían inválidos/engañosos.
39
+ */
40
+ keepPreviousData?: boolean;
41
+ /**
42
+ * Reintentos automáticos antes de marcar error (default: sin reintentos;
43
+ * el usuario decide con el botón de retry).
44
+ */
45
+ autoRetry?: {
46
+ attempts: number;
47
+ delayMs?: number;
48
+ };
49
+ /** Dependencias que re-disparan el fetch (p.ej. un id o filtro). */
50
+ deps?: ReadonlyArray<unknown>;
51
+ }): UseAsyncDataResult<T>;
@@ -0,0 +1,48 @@
1
+ import { ReactNode } from 'react';
2
+ import type { AsyncState } from '../../asyncState';
3
+ export interface AsyncContentProps<T> {
4
+ /** Estado derivado (de `useAsyncData` o de un selector con `deriveAsyncState`). */
5
+ state: AsyncState<T>;
6
+ /** Contenido real: se llama solo cuando hay datos utilizables. */
7
+ children: (data: T) => ReactNode;
8
+ /**
9
+ * Callback de reintento (p.ej. `state.retry` del hook o re-dispatch del
10
+ * thunk). Habilita el botón "Reintentar" en los estados de error.
11
+ */
12
+ onRetry?: () => void;
13
+ /** Fallback de carga inicial (default: spinner centrado). Usar skeletons cuando el layout del contenido es conocido. */
14
+ loadingFallback?: ReactNode;
15
+ /** Fallback de vacío (default: `<EmptyState>` con textos i18n). */
16
+ emptyFallback?: ReactNode;
17
+ /** Fallback de error sin datos (default: `<ErrorState>` con retry). */
18
+ errorFallback?: ReactNode;
19
+ /** Overrides de textos del estado vacío. */
20
+ emptyTitle?: string;
21
+ emptyDescription?: string;
22
+ /** Override del título del estado de error. */
23
+ errorTitle?: string;
24
+ /** Mostrar la barra fina de revalidación sobre el contenido (default: true). */
25
+ showRevalidatingIndicator?: boolean;
26
+ /** Idioma de los textos por defecto ('es' | 'en'). Opcional: ver useComponentsTranslation. */
27
+ language?: string;
28
+ }
29
+ /**
30
+ * Renderiza un dato remoto según su `AsyncState`, con feedback consistente:
31
+ *
32
+ * | Situación | Render |
33
+ * |----------------------------------|------------------------------------------------|
34
+ * | `idle`/`loading` sin datos | `loadingFallback` (carga inicial bloqueante) |
35
+ * | `rejected` sin datos | `ErrorState` + retry |
36
+ * | `succeeded` sin datos (`isEmpty`)| `EmptyState` |
37
+ * | datos + `loading` | contenido + barra de revalidación (no bloquea) |
38
+ * | datos + `rejected` | contenido + banner de error con retry |
39
+ * | datos | contenido |
40
+ *
41
+ * La rama "con datos" usa siempre el mismo wrapper para que un refetch o un
42
+ * error NUNCA desmonte el contenido (conserva scroll, foco y estado interno).
43
+ *
44
+ * Para errores de RENDER (excepciones) combinar con `<PanelErrorBoundary>`.
45
+ * Guía completa: docs/ASYNC_STATE.md
46
+ */
47
+ declare const AsyncContent: <T>({ state, children, onRetry, loadingFallback, emptyFallback, errorFallback, emptyTitle, emptyDescription, errorTitle, showRevalidatingIndicator, language, }: AsyncContentProps<T>) => import("react/jsx-runtime").JSX.Element;
48
+ export default AsyncContent;
@@ -0,0 +1,2 @@
1
+ export { default } from './AsyncContent';
2
+ export type { AsyncContentProps } from './AsyncContent';