import { EventEmitter, OnDestroy, OnInit, ChangeDetectorRef, NgZone } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { BaseEntity, CompositeKey } from '@memberjunction/core'; import { MJRecordChangeEntity } from '@memberjunction/core-entities'; import { MJNotificationService } from '@memberjunction/ng-notifications'; import { BaseAngularComponent } from '@memberjunction/ng-base-types'; import { RestoreCommitEvent } from './restore-preview-panel/restore-preview-panel.component'; import * as i0 from "@angular/core"; /** Lightweight shape for displaying a version label associated with this record */ interface RecordLabel { ID: string; Name: string; Description: string | null; Scope: string; Status: string; CreatedAt: Date; ItemCount: number; } /** * Event payload emitted when the user confirms a restore. * * The host component (typically `record-form-container`) is responsible for: * 1. Reloading the live record (concurrency safety), * 2. Calling `record.SetRestoreContext(SourceChangeID, Reason)`, * 3. Calling `record.Set(FieldName, Value)` for each entry in `FieldValues`, * 4. Calling `record.Save()`, * 5. Calling `record.ClearRestoreContext()` after the save returns. * * The provider will write a new RecordChange row with `Source='Restore'`, * `RestoredFromID = SourceChangeID`, and `RestoreReason = Reason` — * producing the auditable lineage chain. */ export interface RestoreVersionEvent { /** ID of the historical RecordChange row whose state is being restored. */ SourceChangeID: string; /** When the historical change was made. */ ChangedAt: Date; /** Display name / email of who made the historical change. */ ChangedByUser: string; /** Optional user-entered reason for the restore. */ Reason: string | null; /** Selected field values, ready to pass to BaseEntity.Set(). */ FieldValues: Array<{ FieldName: string; Value: unknown; }>; } /** A single field change with type-aware rendering info (timeline display only). */ export interface FieldChangeInfo { field: string; displayName: string; oldValue: string; newValue: string; fieldType: 'boolean' | 'date' | 'number' | 'text'; diffHtml?: SafeHtml; } /** A group of changes that share the same date. */ export interface DateGroup { label: string; changes: MJRecordChangeEntity[]; } /** A conditional filter pill (one per type/source actually present in loaded data). */ export interface FilterPill { /** Unique key for selection state ('Update', 'Create', 'Delete', 'Snapshot', 'Restore'). */ Key: string; /** User-facing label (pluralized: 'Updates', 'Restored', etc.). */ Label: string; /** Font Awesome icon class. */ Icon: string; /** Number of changes of this kind in the loaded data. */ Count: number; /** Selection style — neutral type pill or violet restore pill. */ Variant: 'type' | 'restore'; } /** * Slide-out timeline of all changes to a single record. Hosts the reusable * {@link RestorePreviewPanelComponent} for the actual restore confirmation * flow, and exposes a `RestoreRequested` event the host can act on to * persist the restore. * * @example * * */ export declare class RecordChangesComponent extends BaseAngularComponent implements OnInit, OnDestroy { private cdr; private ngZone; private mjNotificationService; private sanitizer; IsLoading: boolean; IsVisible: boolean; dialogClosed: EventEmitter; record: BaseEntity; /** Whether to show a "Restore" button on each historical version row. Default false. */ AllowRestore: boolean; /** * Emitted when the user confirms a restore in the preview panel. * The host is responsible for applying the snapshot to the record and * saving with `record.SetRestoreContext()` set first. */ RestoreRequested: EventEmitter; viewData: MJRecordChangeEntity[]; filteredData: MJRecordChangeEntity[]; /** * Change lookup keyed by NormalizeUUID(ID), rebuilt whenever {@link viewData} * loads. Lets {@link getRestoredFromSourceChange} resolve a restore row's * source change in O(1) instead of an O(rows) `UUIDsEqual` scan — which was * previously executed twice per restore row per CD cycle (once in the * template `@if` and once inside `getChangeSummary`), giving O(rows^2)/CD. */ private viewDataById; dateGroups: DateGroup[]; expandedItems: Set; /** The change record currently selected for restore preview, or null. */ RestorePreviewChange: MJRecordChangeEntity | null; /** Visibility of the embedded restore preview slide-in. */ RestorePreviewVisible: boolean; /** Whether the restore commit is in progress (between confirmation and host response). */ IsRestoring: boolean; RecordLabels: RecordLabel[]; IsLoadingLabels: boolean; ShowCreateWizard: boolean; searchTerm: string; /** Single selected type filter (legacy, kept for backwards compat). */ selectedType: string; selectedSource: string; /** Map of Key → selected, used by the conditional chip system. */ ChipSelections: Record; /** Whether the overflow popover is open. */ ShowFilterOverflow: boolean; /** Highlighted change ID for the lineage-jump indicator (transient). */ HighlightedChangeID: string | null; /** * Conditional filter pills derived from loaded data. Always includes 'All' * implicitly. Other pills only render when at least one matching change * exists. Overflows into a popover when the count exceeds 2. */ ConditionalPills: FilterPill[]; /** Threshold above which conditional chips collapse into the overflow popover. */ private readonly OVERFLOW_THRESHOLD; /** * Subscription to the record's BaseEntity event stream. We listen for * 'save' events and auto-refresh the timeline so the user immediately * sees the new RecordChange row produced by either a normal save or a * restore. Cleaned up in ngOnDestroy. */ private _entitySaveSub; constructor(cdr: ChangeDetectorRef, ngZone: NgZone, mjNotificationService: MJNotificationService, sanitizer: DomSanitizer); ngOnInit(): void; ngOnDestroy(): void; /** * Wires up an event handler on the live record so the timeline auto- * refreshes immediately after any save lands — including the one * produced by the restore flow. Without this the user sees their record * update in the form but the panel above still shows the pre-restore * change list, which is confusing. */ private subscribeToRecordSaves; /** * Reloads the record changes list from the database. * Called by the parent container after a save completes while the drawer is open. */ Refresh(): void; OnClose(): void; LoadRecordChanges(pkey: CompositeKey, appName: string, entityName: string): Promise; onSearchChange(): void; onFilterChange(): void; /** * Toggles the "All" pill — clears every conditional selection. */ SelectAllPill(): void; /** * Toggles a conditional pill on/off. */ TogglePill(key: string): void; /** * Returns true when no conditional pills are selected (i.e. "All" mode). */ get IsAllSelected(): boolean; /** * Whether any conditional pills are actually visible at all. */ get HasConditionalPills(): boolean; /** * True when the conditional chips should collapse into an overflow popover * because there are more than the threshold. */ get UseOverflowPopover(): boolean; /** Number of currently selected conditional pills (for the popover trigger label). */ get SelectedConditionalCount(): number; ToggleFilterOverflow(): void; CloseFilterOverflow(): void; ClearFilters(): void; /** * Builds the list of conditional pills from currently loaded data — * a pill only appears for change types/sources that actually exist. */ private rebuildConditionalPills; private isRestoreChange; private applyFilters; LoadRecordLabels(): Promise; OpenCreateWizard(): void; OnLabelCreated(event: { LabelCount: number; ItemCount: number; }): void; OnLabelCreateCancelled(): void; getLabelStatusClass(status: string): string; toggleExpansion(changeId: string): void; onTimelineItemKeydown(event: KeyboardEvent, changeId: string): void; /** * Opens the embedded restore preview panel for a given change record. * The panel computes a full-record diff (current vs the change's * FullRecordJSON snapshot) — this is the semantic-correctness fix: * we restore TO the state at that point in time, not just undo the * one delta the user clicked. */ OnRestoreVersion(change: MJRecordChangeEntity, event: MouseEvent): void; /** * Called when the user confirms the restore in the embedded panel. * Translates the panel's RestoreCommitEvent into the broader-shape * RestoreVersionEvent the host is wired to handle. */ OnRestorePanelConfirmed(commit: RestoreCommitEvent): void; /** * Called when the user dismisses the restore preview without confirming. */ OnRestorePanelCancelled(): void; /** * Returns the source change for a given restored row, if found in the * currently loaded changes. Returns null when the source isn't loaded * (e.g., it's been pruned from history) or when this row isn't a restore. */ getRestoredFromSourceChange(change: MJRecordChangeEntity): MJRecordChangeEntity | null; /** * Rebuilds {@link viewDataById} from the current {@link viewData}. Called once * per data load so source-change resolution is an O(1) Map read. */ private rebuildViewDataIndex; /** * True when the row was produced by a restore operation (either the * `Source` is `'Restore'` OR `RestoredFromID` is populated). */ isRestoreRow(change: MJRecordChangeEntity): boolean; /** * True when this change is the most recent in the loaded history. * `viewData` is sorted DESC by `ChangedAt`, so the most recent is index 0. * Restoring to the most recent version is a no-op, so the timeline hides * the Restore button on this row. */ isMostRecentChange(change: MJRecordChangeEntity): boolean; /** * Click handler for the lineage chip — scrolls/highlights the source * change row in the timeline. Auto-clears after a few seconds. */ JumpToSourceChange(sourceChange: MJRecordChangeEntity, event: MouseEvent): void; private buildDateGroups; private formatDateGroupLabel; getChangeTypeCardClass(type: string): string; getChangeTypeBadgeText(type: string): string; /** * Badge text for the row's primary type tag. For restore rows we override * the underlying `Type='Update'` and show "Restore" instead — matches the * mockup's intent of treating restore as a first-class operation in the * timeline rather than a flavor of update. */ getEffectiveBadgeText(change: MJRecordChangeEntity): string; /** * Optional restore reason — pulled from the dynamic `RestoreReason` * column added by the lineage migration. Returns null when not present * or not a restore row. */ getRestoreReason(change: MJRecordChangeEntity): string | null; getSourceClass(source: string): string; getStatusClass(status: string): string; getTimelineItemLabel(change: MJRecordChangeEntity): string; getUserInitials(user: string | null): string; getUserDisplayName(user: string | null): string; getUniqueContributorCount(): number; formatTime(date: Date): string; formatRelativeTime(date: Date): string; formatFullDateTime(date: Date): string; getChangeSummary(change: MJRecordChangeEntity): string; getCreatedFieldCount(change: MJRecordChangeEntity): number; private extractFieldDisplayNames; private buildFieldListSummary; getFieldChanges(change: MJRecordChangeEntity): FieldChangeInfo[]; private buildFieldChangeInfo; private classifyFieldType; getCreatedFields(change: MJRecordChangeEntity): Array<{ name: string; displayName: string; value: string; }>; private formatChangeValue; private generateDiffHtml; private shouldUseWordDiff; private escapeHtml; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } export {}; //# sourceMappingURL=ng-record-changes.component.d.ts.map