valtech-components 2.0.992 → 2.0.993

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.
@@ -0,0 +1,168 @@
1
+ import { OnDestroy, OnInit } from '@angular/core';
2
+ import { EmptyStateMetadata } from '../../molecules/empty-state/types';
3
+ import { ButtonMetadata } from '../../types';
4
+ import { NotificationDocument } from '../../../services/firebase/notifications.service';
5
+ import { NotificationsViewConfig } from './types';
6
+ import * as i0 from "@angular/core";
7
+ type GroupKey = 'today' | 'thisMonth' | 'earlier';
8
+ interface NotificationGroup {
9
+ key: GroupKey;
10
+ label: string;
11
+ items: NotificationDocument[];
12
+ }
13
+ type ResolvedIcon = {
14
+ kind: 'ionicon';
15
+ name: string;
16
+ color?: string;
17
+ } | {
18
+ kind: 'image';
19
+ src: string;
20
+ } | {
21
+ kind: 'avatar';
22
+ src: string;
23
+ };
24
+ /**
25
+ * `val-notifications-view` — inbox de notificaciones full-feature autocontenido
26
+ * (organism). Promovido desde `showcase` bajo el proceso de ADR-021.
27
+ *
28
+ * Separado de `/app/settings/notifications` (que controla permiso FCM + master
29
+ * toggle). Aquí el user lee/abre/marca-leídas sus mensajes.
30
+ *
31
+ * - **Stream real-time** vía `NotificationsService.getAll(maxLoad)` envuelto en
32
+ * `createRefreshableStream` (gateado por `firebaseAuthReady`, retry corto,
33
+ * `firstEmitTimeoutMs: 15000`). Estados: loading (skeleton), error
34
+ * (`val-empty-state` + reintentar), empty (`val-empty-state` + CTA), data
35
+ * (lista agrupada Hoy / Este mes / Anteriores).
36
+ * - **Abrir** una notif → `NotificationActionService.open()` (markRead +
37
+ * switch-org + navegar/handoff cross-app). Hook `onNotificationOpen` opcional.
38
+ * - **Marcar todas como leídas** (gateado por `enableMarkAll`).
39
+ * - **CTA push-off** cuando el permiso del navegador no está concedido (gateado
40
+ * por `showPushOffCta`); navega a `settingsNotificationsPath`.
41
+ * - **Paginación soft** — slice local (`pageSize`, +`pageSize` por click) sobre el
42
+ * cache del stream; el tope de fetch es `maxLoad`.
43
+ * - **i18n auto-registrado** es/en bajo `i18nNamespace` (default `Notifications`).
44
+ *
45
+ * NO renderiza ion-content — vive dentro de val-page-wrapper. `Router` y
46
+ * `ActivatedRoute` se inyectan `{ optional: true }` → funciona sin router
47
+ * (embebida / Storybook). El forward-compat icon resolver lee `notif.payload.icon`
48
+ * si existe (`{ type, value, color? }`), cae a un mapa por `notif.type`, luego a
49
+ * `notifications-outline`.
50
+ */
51
+ export declare class NotificationsViewComponent implements OnInit, OnDestroy {
52
+ private nav;
53
+ private i18n;
54
+ private notifs;
55
+ private action;
56
+ private toast;
57
+ private errors;
58
+ private auth;
59
+ private pageRefresh;
60
+ private route;
61
+ /**
62
+ * Config vía @Input (object-first). Si no se pasa, se cae al route data
63
+ * `notificationsConfig` (poblado por `provideValtechNotificationsRoutes`).
64
+ * `resolvedConfig` mergea con los defaults — `@Input` gana sobre route data.
65
+ */
66
+ config?: NotificationsViewConfig;
67
+ readonly resolvedConfig: import("@angular/core").Signal<Required<Omit<NotificationsViewConfig, "onNotificationOpen">> & Pick<NotificationsViewConfig, "onNotificationOpen">>;
68
+ /** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
69
+ private get ns();
70
+ /** Paso de paginación soft (revela +N por "cargar más"). */
71
+ private get pageStep();
72
+ private readonly _expanded;
73
+ private readonly _pageSize;
74
+ private readonly _bulkBusy;
75
+ private readonly _loadingMore;
76
+ /**
77
+ * Push activado = permiso del navegador en `granted`. Si NO está activado,
78
+ * el user puede leer el inbox (Firestore) pero NO recibirá push en su
79
+ * dispositivo → mostramos una card que lo invita a ir a configuración.
80
+ * Se evalúa en cada montaje de la vista.
81
+ */
82
+ readonly pushEnabled: import("@angular/core").WritableSignal<boolean>;
83
+ /**
84
+ * Stream firestore real-time vía el helper estándar `createRefreshableStream`
85
+ * (modo real-time: factory devuelve un listener vivo `notifs.getAll(maxLoad)`).
86
+ *
87
+ * El helper gatea la primera suscripción por `firebaseAuthReady`, aplica un
88
+ * `retry` corto con backoff y, tras agotar reintentos, emite el fallback (`[]`)
89
+ * y marca `error` — estado de error en UI, no skeleton infinito. `reload()`
90
+ * re-suscribe el stream completo (lo usa el pull-to-refresh).
91
+ */
92
+ private readonly _stream;
93
+ /** True si el stream falló tras agotar reintentos → muestra estado de error. */
94
+ readonly streamError: import("@angular/core").Signal<boolean>;
95
+ readonly loading: import("@angular/core").Signal<boolean>;
96
+ /**
97
+ * Notifs reales mostradas. Filtramos docs sin título Y sin body — son ruido
98
+ * (típicamente sentinel/system writes pre-schema con title/body opcionales).
99
+ */
100
+ readonly allNotifs: import("@angular/core").Signal<NotificationDocument[]>;
101
+ readonly hasMore: import("@angular/core").Signal<boolean>;
102
+ readonly showMarkAll: import("@angular/core").Signal<boolean>;
103
+ /** Sub-conjunto paginado, agrupado por período. */
104
+ readonly groups: import("@angular/core").Signal<NotificationGroup[]>;
105
+ readonly pageTitle: import("@angular/core").Signal<string>;
106
+ readonly pageDescription: import("@angular/core").Signal<string>;
107
+ readonly ctaTitle: import("@angular/core").Signal<string>;
108
+ readonly ctaHint: import("@angular/core").Signal<string>;
109
+ readonly ctaButtonProps: import("@angular/core").Signal<Partial<ButtonMetadata>>;
110
+ /**
111
+ * Estado de error de carga: convierte el error del stream en
112
+ * `EmptyStateMetadata` via el helper canónico `createErrorStateProps`.
113
+ * El helper decide variante (`offline` vs `error`) por `isNetwork`.
114
+ * Null cuando el stream no ha fallado.
115
+ */
116
+ readonly errorState: import("@angular/core").Signal<EmptyStateMetadata>;
117
+ /**
118
+ * Estado vacío (lista real = 0 notificaciones, sin error de carga).
119
+ * Se construye manualmente con `variant: 'empty'` — sin helper de error.
120
+ */
121
+ readonly emptyStateProps: import("@angular/core").Signal<EmptyStateMetadata>;
122
+ readonly seeMore: import("@angular/core").Signal<string>;
123
+ readonly seeLess: import("@angular/core").Signal<string>;
124
+ readonly markAllButtonProps: import("@angular/core").Signal<Partial<ButtonMetadata>>;
125
+ readonly loadMoreButtonProps: import("@angular/core").Signal<Partial<ButtonMetadata>>;
126
+ constructor();
127
+ /**
128
+ * Registra el pull-to-refresh estándar. Usamos `ngOnInit`/`ngOnDestroy`
129
+ * (NO `ionViewWillEnter`) porque `val-page-wrapper` usa `<router-outlet>`
130
+ * plano — los hooks `ionView*` solo disparan dentro de `<ion-router-outlet>`.
131
+ */
132
+ ngOnInit(): void;
133
+ /** Quita el handler al destruir la vista — el refresher desaparece. */
134
+ ngOnDestroy(): void;
135
+ /**
136
+ * Refresh manual: re-suscribe el stream Firestore (listener nuevo) vía el
137
+ * helper. Resuelve tras un margen breve — el stream es real-time y re-emite
138
+ * en milisegundos; el delay sólo mantiene el spinner visible lo justo.
139
+ */
140
+ private reload;
141
+ openButtonProps(): Partial<ButtonMetadata>;
142
+ isExpanded(id: string): boolean;
143
+ toggleExpand(id: string): void;
144
+ isLong(body: string | undefined): boolean;
145
+ /**
146
+ * Icon resolver con orden de fallback:
147
+ * 1. notif.payload.icon (forward-compat, sin breaking change en schema)
148
+ * 2. mapa por notif.type
149
+ * 3. default notifications-outline
150
+ */
151
+ resolveIcon(n: NotificationDocument): ResolvedIcon;
152
+ ioniconName(n: NotificationDocument): string;
153
+ ioniconColor(n: NotificationDocument): string | undefined;
154
+ imageSrc(n: NotificationDocument): string;
155
+ ionColor(token: string | undefined): string;
156
+ relativeTime(n: NotificationDocument): string;
157
+ /** Devuelve label "Desde {appName}" si la notif viene de otra app, sino null. */
158
+ showCrossAppBadge(n: NotificationDocument): string | null;
159
+ onOpen(n: NotificationDocument): Promise<void>;
160
+ onMarkAll(): Promise<void>;
161
+ onLoadMore(): void;
162
+ onGoToSettings(): void;
163
+ private bucketOf;
164
+ private tt;
165
+ static ɵfac: i0.ɵɵFactoryDeclaration<NotificationsViewComponent, never>;
166
+ static ɵcmp: i0.ɵɵComponentDeclaration<NotificationsViewComponent, "val-notifications-view", never, { "config": { "alias": "config"; "required": false; }; }, {}, never, never, true, never>;
167
+ }
168
+ export {};
@@ -0,0 +1,9 @@
1
+ import { LanguagesContent } from '../../../services/i18n/types';
2
+ /**
3
+ * Defaults i18n (es/en) embebidos en `val-notifications-view`. Auto-registrados
4
+ * en el constructor del componente si el consumer no proveyó el namespace
5
+ * (`Notifications` por default). SOLO incluye las keys que la vista usa.
6
+ *
7
+ * Portado del override `Notifications` (showcase) bajo el proceso de ADR-021.
8
+ */
9
+ export declare const NOTIFICATIONS_VIEW_I18N: LanguagesContent;
@@ -0,0 +1,27 @@
1
+ import { Routes } from '@angular/router';
2
+ import { NotificationsViewConfig } from './types';
3
+ /**
4
+ * Helper para montar el inbox de notificaciones (`val-notifications-view`) como
5
+ * ruta en una app del factory. El `config` se pasa por route `data`
6
+ * (`notificationsConfig`) y el componente lo lee como fallback de su
7
+ * `@Input() config`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // app.routes.ts
12
+ * export const appRoutes: Routes = [
13
+ * ...provideValtechNotificationsRoutes(), // → /app/notifications
14
+ * { path: 'settings', loadChildren: () => ... },
15
+ * ];
16
+ *
17
+ * // con config acotada:
18
+ * ...provideValtechNotificationsRoutes({ config: { enableMarkAll: false } }),
19
+ * ...provideValtechNotificationsRoutes({
20
+ * config: { settingsNotificationsPath: '/app/preferences/push' },
21
+ * }),
22
+ * ```
23
+ */
24
+ export declare function provideValtechNotificationsRoutes(opts?: {
25
+ path?: string;
26
+ config?: NotificationsViewConfig;
27
+ }): Routes;
@@ -0,0 +1,52 @@
1
+ import { NotificationOpenResult } from '../../../services/auth/notification-action.service';
2
+ import { NotificationDocument } from '../../../services/firebase/notifications.service';
3
+ /**
4
+ * Configuración acotada de `val-notifications-view` (object-first). Tres ejes de
5
+ * variación permitidos — **NADA de slots arbitrarios** (ADR-021: una vista
6
+ * full-feature no abre slots; si falta algo, se promueve un nuevo punto de
7
+ * extensión):
8
+ *
9
+ * - **secciones / flags** — gatean el render de bloques opcionales (CTA de push
10
+ * desactivado, botón "Marcar todas como leídas") y la ruta de configuración de
11
+ * notificaciones a la que apuntan los CTA.
12
+ * - **branding** — `i18nNamespace` para resolver los textos (default
13
+ * `'Notifications'`).
14
+ * - **comportamiento / callbacks** — `onNotificationOpen` (telemetría tras abrir
15
+ * una notif), tamaño de página y tope de fetch.
16
+ */
17
+ export interface NotificationsViewConfig {
18
+ /**
19
+ * Muestra la card "Activa las notificaciones push" cuando el permiso del
20
+ * navegador no está concedido. Default `true`.
21
+ */
22
+ showPushOffCta?: boolean;
23
+ /**
24
+ * Habilita el botón "Marcar todas como leídas" (visible solo si hay notifs sin
25
+ * leer). Default `true`.
26
+ */
27
+ enableMarkAll?: boolean;
28
+ /**
29
+ * Ruta de configuración de notificaciones a la que navegan los CTA ("Activar
30
+ * notificaciones" / "Ir a configuración"). Default `'/app/settings/notifications'`.
31
+ */
32
+ settingsNotificationsPath?: string;
33
+ /**
34
+ * Namespace i18n con el que la vista resuelve sus textos.
35
+ * Default `'Notifications'`.
36
+ */
37
+ i18nNamespace?: string;
38
+ /**
39
+ * Hook de telemetría tras abrir una notificación (markRead + switch-org +
40
+ * navegar/handoff cross-app). `result` es lo que devuelve
41
+ * `NotificationActionService.open(notif)`.
42
+ */
43
+ onNotificationOpen?: (notif: NotificationDocument, result: NotificationOpenResult) => void;
44
+ /**
45
+ * Cuántas notifs se revelan al inicio y por cada "cargar más". Default `10`.
46
+ */
47
+ pageSize?: number;
48
+ /**
49
+ * Tope de docs que carga el stream real-time (`getAll(maxLoad)`). Default `50`.
50
+ */
51
+ maxLoad?: number;
52
+ }
package/lib/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current version of valtech-components.
3
3
  * This is automatically updated during the publish process.
4
4
  */
5
- export declare const VERSION = "2.0.992";
5
+ export declare const VERSION = "2.0.993";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valtech-components",
3
- "version": "2.0.992",
3
+ "version": "2.0.993",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "valtech-firebase-config": "./src/lib/services/firebase/scripts/generate-sw-config.js"
package/public-api.d.ts CHANGED
@@ -284,6 +284,9 @@ export * from './lib/components/organisms/account-view/account.routes';
284
284
  export * from './lib/components/organisms/organization-view/organization-view.component';
285
285
  export * from './lib/components/organisms/organization-view/types';
286
286
  export * from './lib/components/organisms/organization-view/organization.routes';
287
+ export * from './lib/components/organisms/notifications-view/notifications-view.component';
288
+ export * from './lib/components/organisms/notifications-view/types';
289
+ export * from './lib/components/organisms/notifications-view/notifications.routes';
287
290
  export * from './lib/components/organisms/delete-account-modal/delete-account-modal.component';
288
291
  export * from './lib/components/organisms/switch-org-modal/switch-org-modal.component';
289
292
  export * from './lib/components/organisms/create-org-modal/create-org-modal.component';