frontend: enhance rooms lists component with improved loading states, search functionality, and empty filter messaging
This commit is contained in:
parent
1ac05d6157
commit
cea5421012
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,22 @@
|
||||
.rooms-toolbar {
|
||||
@extend .ov-data-toolbar;
|
||||
|
||||
.toolbar-left {
|
||||
::ng-deep .search-btn {
|
||||
padding: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
|
||||
::ng-deep .refresh-btn {
|
||||
padding: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
|
||||
::ng-deep .clear-btn {
|
||||
padding: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,10 +261,15 @@
|
||||
}
|
||||
|
||||
.getting-started-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
align-items: center;
|
||||
@extend .action-buttons;
|
||||
|
||||
.create-room-btn {
|
||||
@extend .refresh-btn;
|
||||
}
|
||||
|
||||
.clear-filters-btn {
|
||||
@extend .refresh-btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -92,10 +92,12 @@ export interface RoomTableAction {
|
||||
export class RoomsListsComponent implements OnInit, OnChanges {
|
||||
// Input properties
|
||||
@Input() rooms: MeetRoom[] = [];
|
||||
@Input() showSearchBox = true;
|
||||
@Input() showFilters = false;
|
||||
@Input() showSelection = true;
|
||||
@Input() showLoadMore = false;
|
||||
@Input() loading = false;
|
||||
@Input() initialFilters: { nameFilter: string; statusFilter: string } = { nameFilter: '', statusFilter: '' };
|
||||
|
||||
// Host binding for styling when rooms are selected
|
||||
@HostBinding('class.has-selections')
|
||||
@ -106,13 +108,15 @@ export class RoomsListsComponent implements OnInit, OnChanges {
|
||||
// Output events
|
||||
@Output() roomAction = new EventEmitter<RoomTableAction>();
|
||||
@Output() filterChange = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
|
||||
@Output() loadMore = new EventEmitter<void>();
|
||||
@Output() refresh = new EventEmitter<void>();
|
||||
@Output() loadMore = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
|
||||
@Output() refresh = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
|
||||
|
||||
// Filter controls
|
||||
nameFilterControl = new FormControl('');
|
||||
statusFilterControl = new FormControl('');
|
||||
|
||||
showEmptyFilterMessage = false; // Show message when no rooms match filters
|
||||
|
||||
// Selection state
|
||||
selectedRooms = signal<Set<string>>(new Set());
|
||||
allSelected = signal(false);
|
||||
@ -137,30 +141,39 @@ export class RoomsListsComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['rooms']) {
|
||||
// Update selected rooms based on current rooms
|
||||
const validIds = new Set(this.rooms.map((r) => r.roomId));
|
||||
const filteredSelection = new Set([...this.selectedRooms()].filter((id) => validIds.has(id)));
|
||||
this.selectedRooms.set(filteredSelection);
|
||||
this.updateSelectionState();
|
||||
|
||||
// Show message when no rooms match filters
|
||||
if (this.rooms.length === 0 && this.hasActiveFilters()) {
|
||||
this.showEmptyFilterMessage = true;
|
||||
} else {
|
||||
this.showEmptyFilterMessage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== INITIALIZATION METHODS =====
|
||||
|
||||
private setupFilters() {
|
||||
// Set up name filter with debounce
|
||||
this.nameFilterControl.valueChanges.pipe(debounceTime(300), distinctUntilChanged()).subscribe((value) => {
|
||||
this.filterChange.emit({
|
||||
nameFilter: value || '',
|
||||
statusFilter: this.statusFilterControl.value || ''
|
||||
});
|
||||
// Set up initial filter values
|
||||
this.nameFilterControl.setValue(this.initialFilters.nameFilter);
|
||||
this.statusFilterControl.setValue(this.initialFilters.statusFilter);
|
||||
|
||||
// Set up name filter change detection
|
||||
this.nameFilterControl.valueChanges.subscribe((value) => {
|
||||
// Emit filter change if value is empty
|
||||
if (!value) {
|
||||
this.emitFilterChange();
|
||||
}
|
||||
});
|
||||
|
||||
// Set up status filter
|
||||
this.statusFilterControl.valueChanges.subscribe((value) => {
|
||||
this.filterChange.emit({
|
||||
nameFilter: this.nameFilterControl.value || '',
|
||||
statusFilter: value || ''
|
||||
});
|
||||
// Set up status filter change detection
|
||||
this.statusFilterControl.valueChanges.subscribe(() => {
|
||||
this.emitFilterChange();
|
||||
});
|
||||
}
|
||||
|
||||
@ -261,8 +274,31 @@ export class RoomsListsComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
loadMoreRooms() {
|
||||
const nameFilter = this.nameFilterControl.value || '';
|
||||
const statusFilter = this.statusFilterControl.value || '';
|
||||
this.loadMore.emit({ nameFilter, statusFilter });
|
||||
}
|
||||
|
||||
refreshRooms() {
|
||||
const nameFilter = this.nameFilterControl.value || '';
|
||||
const statusFilter = this.statusFilterControl.value || '';
|
||||
this.refresh.emit({ nameFilter, statusFilter });
|
||||
}
|
||||
|
||||
// ===== FILTER METHODS =====
|
||||
|
||||
triggerSearch() {
|
||||
this.emitFilterChange();
|
||||
}
|
||||
|
||||
private emitFilterChange() {
|
||||
this.filterChange.emit({
|
||||
nameFilter: this.nameFilterControl.value || '',
|
||||
statusFilter: this.statusFilterControl.value || ''
|
||||
});
|
||||
}
|
||||
|
||||
hasActiveFilters(): boolean {
|
||||
return !!(this.nameFilterControl.value || this.statusFilterControl.value);
|
||||
}
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
@if (isLoading) {
|
||||
@if (showLoadingSpinner) {
|
||||
<!-- Enhanced Loading State with delay -->
|
||||
<div class="ov-page-loading">
|
||||
<div class="loading-content">
|
||||
<div class="loading-header">
|
||||
<div class="loading-title">
|
||||
<mat-icon class="ov-room-icon loading-icon"> video_chat </mat-icon>
|
||||
<h1>Loading Rooms</h1>
|
||||
</div>
|
||||
<p class="loading-subtitle">Please wait while we fetch your rooms...</p>
|
||||
<!-- Loading State -->
|
||||
@if (isInitializing && showInitialLoader) {
|
||||
<div class="ov-page-loading">
|
||||
<div class="loading-content">
|
||||
<div class="loading-header">
|
||||
<div class="loading-title">
|
||||
<mat-icon class="ov-room-icon loading-icon">video_chat</mat-icon>
|
||||
<h1>Loading Rooms</h1>
|
||||
</div>
|
||||
<p class="loading-subtitle">Please wait while we fetch your rooms...</p>
|
||||
</div>
|
||||
|
||||
<div class="loading-spinner-container">
|
||||
<mat-spinner diameter="48"></mat-spinner>
|
||||
</div>
|
||||
<div class="loading-spinner-container">
|
||||
<mat-spinner diameter="48"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!isInitializing) {
|
||||
<div class="ov-page-container ov-mb-xxl" id="rooms-page-container">
|
||||
<!-- Rooms Header -->
|
||||
<div class="page-header" id="rooms-page-header">
|
||||
<div class="title" id="rooms-title">
|
||||
<mat-icon class="ov-room-icon" id="rooms-icon"> video_chat </mat-icon>
|
||||
<mat-icon class="ov-room-icon" id="rooms-icon">video_chat</mat-icon>
|
||||
<h1 id="rooms-heading">Rooms</h1>
|
||||
</div>
|
||||
<p class="subtitle" id="rooms-subtitle">
|
||||
@ -29,16 +30,21 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="page-content" id="rooms-content">
|
||||
<ov-rooms-lists
|
||||
id="rooms-list"
|
||||
[rooms]="rooms()"
|
||||
[loading]="isLoading"
|
||||
[showSearchBox]="true"
|
||||
[showFilters]="false"
|
||||
[showSelection]="true"
|
||||
[showLoadMore]="hasMoreRooms"
|
||||
[initialFilters]="initialFilters"
|
||||
(roomAction)="onRoomAction($event)"
|
||||
(loadMore)="loadMoreRooms()"
|
||||
(refresh)="refreshRooms()"
|
||||
(loadMore)="loadMoreRooms($event)"
|
||||
(refresh)="refreshRooms($event)"
|
||||
(filterChange)="refreshRooms($event)"
|
||||
></ov-rooms-lists>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -49,8 +49,16 @@ export class RoomsComponent implements OnInit {
|
||||
// searchTerm = '';
|
||||
|
||||
rooms = signal<MeetRoom[]>([]);
|
||||
|
||||
// Loading state
|
||||
isInitializing = true;
|
||||
showInitialLoader = false;
|
||||
isLoading = false;
|
||||
showLoadingSpinner = false;
|
||||
|
||||
initialFilters = {
|
||||
nameFilter: '',
|
||||
statusFilter: ''
|
||||
};
|
||||
|
||||
// Pagination
|
||||
hasMoreRooms = false;
|
||||
@ -69,7 +77,15 @@ export class RoomsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const delayLoader = setTimeout(() => {
|
||||
this.showInitialLoader = true;
|
||||
}, 200);
|
||||
|
||||
await this.loadRooms();
|
||||
|
||||
clearTimeout(delayLoader);
|
||||
this.showInitialLoader = false;
|
||||
this.isInitializing = false;
|
||||
}
|
||||
|
||||
async onRoomAction(action: RoomTableAction) {
|
||||
@ -101,24 +117,34 @@ export class RoomsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadRooms() {
|
||||
this.isLoading = true;
|
||||
const delaySpinner = setTimeout(() => {
|
||||
this.showLoadingSpinner = true;
|
||||
private async loadRooms(filters?: { nameFilter: string; statusFilter: string }, refresh = false) {
|
||||
const delayLoader = setTimeout(() => {
|
||||
this.isLoading = true;
|
||||
}, 200);
|
||||
|
||||
try {
|
||||
const roomFilters: MeetRoomFilters = {
|
||||
maxItems: 50,
|
||||
nextPageToken: this.nextPageToken
|
||||
nextPageToken: !refresh ? this.nextPageToken : undefined
|
||||
};
|
||||
|
||||
// Apply room ID filter if provided
|
||||
// if (filters?.nameFilter) {
|
||||
// roomFilters.roomName = filters.nameFilter;
|
||||
// }
|
||||
|
||||
const response = await this.roomService.listRooms(roomFilters);
|
||||
|
||||
// TODO: Filter rooms
|
||||
// TODO: Filter rooms by status
|
||||
|
||||
// Update rooms list
|
||||
const currentRooms = this.rooms();
|
||||
this.rooms.set([...currentRooms, ...response.rooms]);
|
||||
if (!refresh) {
|
||||
// Update rooms list
|
||||
const currentRooms = this.rooms();
|
||||
this.rooms.set([...currentRooms, ...response.rooms]);
|
||||
} else {
|
||||
// Replace rooms list
|
||||
this.rooms.set(response.rooms);
|
||||
}
|
||||
|
||||
// TODO: Sort rooms
|
||||
// this.dataSource.data = this.rooms();
|
||||
@ -131,9 +157,8 @@ export class RoomsComponent implements OnInit {
|
||||
this.notificationService.showAlert('Error loading rooms');
|
||||
this.log.e('Error loading rooms:', error);
|
||||
} finally {
|
||||
clearTimeout(delayLoader);
|
||||
this.isLoading = false;
|
||||
clearTimeout(delaySpinner);
|
||||
this.showLoadingSpinner = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,16 +213,13 @@ export class RoomsComponent implements OnInit {
|
||||
// }
|
||||
// }
|
||||
|
||||
async loadMoreRooms() {
|
||||
async loadMoreRooms(filters?: { nameFilter: string; statusFilter: string }) {
|
||||
if (!this.hasMoreRooms || this.isLoading) return;
|
||||
await this.loadRooms();
|
||||
await this.loadRooms(filters);
|
||||
}
|
||||
|
||||
async refreshRooms() {
|
||||
this.rooms.set([]);
|
||||
this.nextPageToken = undefined;
|
||||
this.hasMoreRooms = false;
|
||||
await this.loadRooms();
|
||||
async refreshRooms(filters?: { nameFilter: string; statusFilter: string }) {
|
||||
await this.loadRooms(filters, true);
|
||||
}
|
||||
|
||||
private async createRoom() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user