frontend: enhance recording lists component with improved empty state messaging and filter handling

This commit is contained in:
juancarmore 2025-08-15 21:57:30 +02:00
parent 5f289d12b8
commit 3513733071
5 changed files with 267 additions and 219 deletions

View File

@ -1,10 +1,18 @@
<!-- Loading Spinner --> @if (!loading && recordings.length === 0 && !showEmptyFilterMessage) {
@if (loading) { <!-- Empty State -->
<div class="loading-container"> <div class="no-recordings-state">
<mat-spinner diameter="40"></mat-spinner> <div class="empty-content">
<span>Loading recordings...</span> <h3>No recordings yet</h3>
<p>Recordings from your meetings will appear here. Start a recording in any room to see them listed.</p>
<div class="getting-started-actions">
<button mat-button (click)="refresh.emit()" class="refresh-recordings-btn primary-button">
<mat-icon>refresh</mat-icon>
Refresh Recordings
</button>
</div> </div>
} @else if (recordings.length > 0) { </div>
</div>
} @else {
<!-- Recordings Toolbar --> <!-- Recordings Toolbar -->
<mat-toolbar class="recordings-toolbar"> <mat-toolbar class="recordings-toolbar">
<!-- Left Section: Search --> <!-- Left Section: Search -->
@ -52,14 +60,26 @@
<!-- Right Section: Filters --> <!-- Right Section: Filters -->
<div class="toolbar-right"> <div class="toolbar-right">
@if (hasActiveFilters()) {
<button
mat-icon-button
class="clear-btn"
(click)="clearFilters()"
[disabled]="loading"
matTooltip="Clear all filters"
>
<mat-icon>filter_alt_off</mat-icon>
</button>
}
<button <button
mat-icon-button mat-icon-button
class="refresh-btn" class="refresh-btn"
(click)="refresh.emit()" (click)="refreshRecordings()"
[disabled]="loading" [disabled]="loading"
matTooltip="Refresh recordings" matTooltip="Refresh recordings"
> >
<mat-icon> refresh </mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
@if (showFilters) { @if (showFilters) {
@ -84,6 +104,28 @@
</div> </div>
</mat-toolbar> </mat-toolbar>
<!-- Loading Spinner -->
@if (loading) {
<div class="loading-container">
<mat-spinner diameter="40"></mat-spinner>
<span>Loading recordings...</span>
</div>
} @else if (recordings.length === 0 && showEmptyFilterMessage) {
<!-- No recordings match the current filters -->
<div class="no-recordings-state">
<div class="empty-content">
<h3>No recordings match your search criteria and/or filters</h3>
<p>Try adjusting or clearing your filters to see more recordings.</p>
<div class="getting-started-actions">
<button mat-button (click)="clearFilters()" class="clear-filters-btn primary-button">
<mat-icon>filter_alt_off</mat-icon>
Clear Filters
</button>
</div>
</div>
</div>
} @else {
<!-- Recordings Table -->
<div class="table-container"> <div class="table-container">
<table mat-table [dataSource]="recordings" class="recordings-table"> <table mat-table [dataSource]="recordings" class="recordings-table">
<!-- Selection Column --> <!-- Selection Column -->
@ -277,29 +319,5 @@
</button> </button>
</div> </div>
} }
} @else { }
<!-- Empty State -->
<div class="no-recordings-state">
<div class="empty-content">
<h3>No recordings yet</h3>
<p>Recordings from your meetings will appear here. Start a recording in any room to see them listed.</p>
<div class="getting-started-actions">
<button mat-button (click)="refresh.emit()" class="refresh-recordings-btn primary-button">
<mat-icon>refresh</mat-icon>
Refresh Recordings
</button>
</div>
<!-- TODO: Show this when no recordings match the filters
@if (hasActiveFilters()) {
<h3>No recordings match your search criteria</h3>
<p>Try adjusting or clearing your filters to see more recordings.</p>
<div class="getting-started-actions">
<button mat-raised-button color="primary" (click)="clearFilters()" class="clear-filters-btn">
<mat-icon>filter_alt_off</mat-icon>
Clear Filters
</button>
</div>
} -->
</div>
</div>
} }

View File

@ -10,6 +10,10 @@
::ng-deep .refresh-btn { ::ng-deep .refresh-btn {
padding: var(--ov-meet-spacing-sm); padding: var(--ov-meet-spacing-sm);
} }
::ng-deep .clear-btn {
padding: var(--ov-meet-spacing-sm);
}
} }
} }
@ -152,6 +156,10 @@
.refresh-recordings-btn { .refresh-recordings-btn {
@extend .refresh-btn; @extend .refresh-btn;
} }
.clear-filters-btn {
@extend .refresh-btn;
}
} }
} }

View File

@ -90,6 +90,7 @@ export class RecordingListsComponent implements OnInit, OnChanges {
@Input() showRoomInfo = true; @Input() showRoomInfo = true;
@Input() showLoadMore = false; @Input() showLoadMore = false;
@Input() loading = false; @Input() loading = false;
@Input() initialFilters: { nameFilter: string; statusFilter: string } = { nameFilter: '', statusFilter: '' };
// Host binding for styling when recordings are selected // Host binding for styling when recordings are selected
@HostBinding('class.has-selections') @HostBinding('class.has-selections')
@ -107,6 +108,8 @@ export class RecordingListsComponent implements OnInit, OnChanges {
nameFilterControl = new FormControl(''); nameFilterControl = new FormControl('');
statusFilterControl = new FormControl(''); statusFilterControl = new FormControl('');
showEmptyFilterMessage = false; // Show message when no recordings match filters
// Selection state // Selection state
selectedRecordings = signal<Set<string>>(new Set()); selectedRecordings = signal<Set<string>>(new Set());
allSelected = signal(false); allSelected = signal(false);
@ -156,16 +159,28 @@ export class RecordingListsComponent implements OnInit, OnChanges {
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['recordings']) { if (changes['recordings']) {
// Update selected recordings based on current recordings
const validIds = new Set(this.recordings.map((r) => r.recordingId)); const validIds = new Set(this.recordings.map((r) => r.recordingId));
const filteredSelection = new Set([...this.selectedRecordings()].filter((id) => validIds.has(id))); const filteredSelection = new Set([...this.selectedRecordings()].filter((id) => validIds.has(id)));
this.selectedRecordings.set(filteredSelection); this.selectedRecordings.set(filteredSelection);
this.updateSelectionState(); this.updateSelectionState();
// Show message when no recordings match filters
if (this.recordings.length === 0 && this.hasActiveFilters()) {
this.showEmptyFilterMessage = true;
} else {
this.showEmptyFilterMessage = false;
}
} }
} }
// ===== INITIALIZATION METHODS ===== // ===== INITIALIZATION METHODS =====
private setupFilters() { private setupFilters() {
// Set up initial filter values
this.nameFilterControl.setValue(this.initialFilters.nameFilter);
this.statusFilterControl.setValue(this.initialFilters.statusFilter);
// Set up name filter with debounce // Set up name filter with debounce
this.nameFilterControl.valueChanges.pipe(debounceTime(300), distinctUntilChanged()).subscribe((value) => { this.nameFilterControl.valueChanges.pipe(debounceTime(300), distinctUntilChanged()).subscribe((value) => {
this.filterChange.emit({ this.filterChange.emit({

View File

@ -38,6 +38,7 @@
[showFilters]="false" [showFilters]="false"
[showSelection]="true" [showSelection]="true"
[showLoadMore]="hasMoreRecordings" [showLoadMore]="hasMoreRecordings"
[initialFilters]="initialFilters"
(recordingAction)="onRecordingAction($event)" (recordingAction)="onRecordingAction($event)"
(loadMore)="loadMoreRecordings($event)" (loadMore)="loadMoreRecordings($event)"
(refresh)="refreshRecordings($event)" (refresh)="refreshRecordings($event)"

View File

@ -22,6 +22,11 @@ export class RecordingsComponent implements OnInit {
showInitialLoader = false; showInitialLoader = false;
isLoading = false; isLoading = false;
initialFilters = {
nameFilter: '',
statusFilter: ''
};
// Pagination // Pagination
hasMoreRecordings = false; hasMoreRecordings = false;
private nextPageToken?: string; private nextPageToken?: string;
@ -45,7 +50,8 @@ export class RecordingsComponent implements OnInit {
if (roomId) { if (roomId) {
// If a specific room ID is provided, filter recordings by that room // If a specific room ID is provided, filter recordings by that room
await this.loadRecordings({ nameFilter: roomId, statusFilter: '' }); this.initialFilters.nameFilter = roomId;
await this.loadRecordings(this.initialFilters);
} else { } else {
// Load all recordings if no room ID is specified // Load all recordings if no room ID is specified
await this.loadRecordings(); await this.loadRecordings();