import { OnDestroy, OnInit } from '@angular/core'; import { EmptyStateMetadata } from '../../molecules/empty-state/types'; import { ButtonMetadata } from '../../types'; import { NotificationDocument } from '../../../services/firebase/notifications.service'; import { NotificationsViewConfig } from './types'; import * as i0 from "@angular/core"; type GroupKey = 'today' | 'thisMonth' | 'earlier'; interface NotificationGroup { key: GroupKey; label: string; items: NotificationDocument[]; } type ResolvedIcon = { kind: 'ionicon'; name: string; color?: string; } | { kind: 'image'; src: string; } | { kind: 'avatar'; src: string; }; /** * `val-notifications-view` — inbox de notificaciones full-feature autocontenido * (organism). Promovido desde `showcase` bajo el proceso de ADR-021. * * Separado de `/app/settings/notifications` (que controla permiso FCM + master * toggle). Aquí el user lee/abre/marca-leídas sus mensajes. * * - **Stream real-time** vía `NotificationsService.getAll(maxLoad)` envuelto en * `createRefreshableStream` (gateado por `firebaseAuthReady`, retry corto, * `firstEmitTimeoutMs: 15000`). Estados: loading (skeleton), error * (`val-empty-state` + reintentar), empty (`val-empty-state` + CTA), data * (lista agrupada Hoy / Este mes / Anteriores). * - **Abrir** una notif → `NotificationActionService.open()` (markRead + * switch-org + navegar/handoff cross-app). Hook `onNotificationOpen` opcional. * - **Marcar todas como leídas** (gateado por `enableMarkAll`). * - **CTA push-off** cuando el permiso del navegador no está concedido (gateado * por `showPushOffCta`); navega a `settingsNotificationsPath`. * - **Paginación soft** — slice local (`pageSize`, +`pageSize` por click) sobre el * cache del stream; el tope de fetch es `maxLoad`. * - **i18n auto-registrado** es/en bajo `i18nNamespace` (default `Notifications`). * * NO renderiza ion-content — vive dentro de val-page-wrapper. `Router` y * `ActivatedRoute` se inyectan `{ optional: true }` → funciona sin router * (embebida / Storybook). El forward-compat icon resolver lee `notif.payload.icon` * si existe (`{ type, value, color? }`), cae a un mapa por `notif.type`, luego a * `notifications-outline`. */ export declare class NotificationsViewComponent implements OnInit, OnDestroy { private nav; private i18n; private notifs; private action; private toast; private errors; private auth; private pageRefresh; private route; /** * Config vía @Input (object-first). Si no se pasa, se cae al route data * `notificationsConfig` (poblado por `provideValtechNotificationsRoutes`). * `resolvedConfig` mergea con los defaults — `@Input` gana sobre route data. */ config?: NotificationsViewConfig; readonly resolvedConfig: import("@angular/core").Signal> & Pick>; /** Namespace i18n resuelto (capturado para llamadas no-reactivas). */ private get ns(); /** Paso de paginación soft (revela +N por "cargar más"). */ private get pageStep(); private readonly _expanded; private readonly _pageSize; private readonly _bulkBusy; private readonly _loadingMore; /** * Push activado = permiso del navegador en `granted`. Si NO está activado, * el user puede leer el inbox (Firestore) pero NO recibirá push en su * dispositivo → mostramos una card que lo invita a ir a configuración. * Se evalúa en cada montaje de la vista. */ readonly pushEnabled: import("@angular/core").WritableSignal; /** * Stream firestore real-time vía el helper estándar `createRefreshableStream` * (modo real-time: factory devuelve un listener vivo `notifs.getAll(maxLoad)`). * * El helper gatea la primera suscripción por `firebaseAuthReady`, aplica un * `retry` corto con backoff y, tras agotar reintentos, emite el fallback (`[]`) * y marca `error` — estado de error en UI, no skeleton infinito. `reload()` * re-suscribe el stream completo (lo usa el pull-to-refresh). */ private readonly _stream; /** True si el stream falló tras agotar reintentos → muestra estado de error. */ readonly streamError: import("@angular/core").Signal; readonly loading: import("@angular/core").Signal; /** * Notifs reales mostradas. Filtramos docs sin título Y sin body — son ruido * (típicamente sentinel/system writes pre-schema con title/body opcionales). */ readonly allNotifs: import("@angular/core").Signal; readonly hasMore: import("@angular/core").Signal; readonly showMarkAll: import("@angular/core").Signal; /** Sub-conjunto paginado, agrupado por período. */ readonly groups: import("@angular/core").Signal; readonly pageTitle: import("@angular/core").Signal; readonly pageDescription: import("@angular/core").Signal; readonly ctaTitle: import("@angular/core").Signal; readonly ctaHint: import("@angular/core").Signal; readonly ctaButtonProps: import("@angular/core").Signal>; /** * Estado de error de carga: convierte el error del stream en * `EmptyStateMetadata` via el helper canónico `createErrorStateProps`. * El helper decide variante (`offline` vs `error`) por `isNetwork`. * Null cuando el stream no ha fallado. */ readonly errorState: import("@angular/core").Signal; /** * Estado vacío (lista real = 0 notificaciones, sin error de carga). * Se construye manualmente con `variant: 'empty'` — sin helper de error. */ readonly emptyStateProps: import("@angular/core").Signal; readonly seeMore: import("@angular/core").Signal; readonly seeLess: import("@angular/core").Signal; readonly markAllButtonProps: import("@angular/core").Signal>; readonly loadMoreButtonProps: import("@angular/core").Signal>; constructor(); /** * Registra el pull-to-refresh estándar. Usamos `ngOnInit`/`ngOnDestroy` * (NO `ionViewWillEnter`) porque `val-page-wrapper` usa `` * plano — los hooks `ionView*` solo disparan dentro de ``. */ ngOnInit(): void; /** Quita el handler al destruir la vista — el refresher desaparece. */ ngOnDestroy(): void; /** * Refresh manual: re-suscribe el stream Firestore (listener nuevo) vía el * helper. Resuelve tras un margen breve — el stream es real-time y re-emite * en milisegundos; el delay sólo mantiene el spinner visible lo justo. */ private reload; openButtonProps(): Partial; isExpanded(id: string): boolean; toggleExpand(id: string): void; isLong(body: string | undefined): boolean; /** * Icon resolver con orden de fallback: * 1. notif.payload.icon (forward-compat, sin breaking change en schema) * 2. mapa por notif.type * 3. default notifications-outline */ resolveIcon(n: NotificationDocument): ResolvedIcon; ioniconName(n: NotificationDocument): string; ioniconColor(n: NotificationDocument): string | undefined; imageSrc(n: NotificationDocument): string; ionColor(token: string | undefined): string; relativeTime(n: NotificationDocument): string; /** Devuelve label "Desde {appName}" si la notif viene de otra app, sino null. */ showCrossAppBadge(n: NotificationDocument): string | null; onOpen(n: NotificationDocument): Promise; onMarkAll(): Promise; onLoadMore(): void; onGoToSettings(): void; private bucketOf; private tt; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } export {};