frontend: enhance room status display and meeting end actions in rooms list

This commit is contained in:
juancarmore 2025-08-29 17:24:55 +02:00
parent e20713288c
commit 2466acabce
6 changed files with 168 additions and 136 deletions

View File

@ -182,22 +182,36 @@
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef id="status-header">Status</th>
<td mat-cell *matCellDef="let room" id="status-cell-{{ room.roomId }}">
<div
class="status-badge"
[style.color]="getStatusColor(room)"
[matTooltip]="getStatusTooltip(room)"
id="status-badge-{{ room.roomId }}"
>
<mat-icon
class="status-icon"
<div class="status-container" id="status-container-{{ room.roomId }}">
<div
class="status-badge"
[style.color]="getStatusColor(room)"
id="status-icon-{{ room.roomId }}"
[matTooltip]="getStatusTooltip(room)"
id="status-badge-{{ room.roomId }}"
>
{{ getStatusIcon(room) }}
</mat-icon>
<span class="status-label" id="status-label-{{ room.roomId }}">{{
getRoomStatus(room)
}}</span>
<mat-icon
class="status-icon"
[style.color]="getStatusColor(room)"
id="status-icon-{{ room.roomId }}"
>
{{ getStatusIcon(room) }}
</mat-icon>
<span class="status-label" id="status-label-{{ room.roomId }}">{{
getRoomStatus(room)
}}</span>
</div>
<!-- Meeting End Action Info Icon -->
@if (hasMeetingEndAction(room)) {
<mat-icon
class="meeting-end-info-icon"
[ngClass]="getMeetingEndActionClass(room)"
[matTooltip]="getMeetingEndActionTooltip(room)"
id="meeting-end-info-icon-{{ room.roomId }}"
>
info_outline
</mat-icon>
}
</div>
</td>
</ng-container>
@ -227,52 +241,34 @@
<td mat-cell *matCellDef="let room" id="auto-deletion-cell-{{ room.roomId }}">
<div class="auto-deletion-content" id="auto-deletion-content-{{ room.roomId }}">
<div class="auto-deletion-info" id="auto-deletion-info-{{ room.roomId }}">
@if (hasAutoDeletion(room)) {
<div
class="deletion-badge"
[ngClass]="getAutoDeletionClass(room)"
[matTooltip]="getAutoDeletionTooltip(room)"
id="deletion-badge-{{ room.roomId }}"
<div
class="deletion-badge"
[ngClass]="getAutoDeletionClass(room)"
[matTooltip]="getAutoDeletionTooltip(room)"
id="deletion-badge-{{ room.roomId }}"
>
<mat-icon
class="deletion-icon material-symbols-outlined"
id="deletion-icon-{{ room.roomId }}"
>{{ getAutoDeletionIcon(room) }}</mat-icon
>
<mat-icon
class="deletion-icon material-symbols-outlined"
id="deletion-icon-{{ room.roomId }}"
>{{ getAutoDeletionIcon(room) }}</mat-icon
>
<span class="deletion-text" id="deletion-text-{{ room.roomId }}">{{
getAutoDeletionStatus(room)
}}</span>
</div>
<span class="deletion-text" id="deletion-text-{{ room.roomId }}">{{
getAutoDeletionStatus(room)
}}</span>
</div>
@if (!room.markedForDeletion) {
<div class="deletion-date-time" id="deletion-date-time-{{ room.roomId }}">
<div class="deletion-date" id="deletion-date-container-{{ room.roomId }}">
<span class="date" id="deletion-date-{{ room.roomId }}">{{
room.autoDeletionDate | date: 'mediumDate'
}}</span>
</div>
<div class="deletion-time" id="deletion-time-container-{{ room.roomId }}">
<span class="time" id="deletion-time-{{ room.roomId }}">{{
room.autoDeletionDate | date: 'shortTime'
}}</span>
</div>
@if (hasAutoDeletion(room)) {
<div class="deletion-date-time" id="deletion-date-time-{{ room.roomId }}">
<div class="deletion-date" id="deletion-date-container-{{ room.roomId }}">
<span class="date" id="deletion-date-{{ room.roomId }}">{{
room.autoDeletionDate | date: 'mediumDate'
}}</span>
</div>
<div class="deletion-time" id="deletion-time-container-{{ room.roomId }}">
<span class="time" id="deletion-time-{{ room.roomId }}">{{
room.autoDeletionDate | date: 'shortTime'
}}</span>
</div>
}
} @else {
<div
class="deletion-badge"
[ngClass]="getAutoDeletionClass(room)"
[matTooltip]="getAutoDeletionTooltip(room)"
id="deletion-badge-{{ room.roomId }}"
>
<mat-icon
class="deletion-icon material-symbols-outlined"
id="deletion-icon-{{ room.roomId }}"
>{{ getAutoDeletionIcon(room) }}</mat-icon
>
<span class="deletion-text" id="deletion-text-{{ room.roomId }}">{{
getAutoDeletionStatus(room)
}}</span>
</div>
}
</div>
@ -333,7 +329,7 @@
<span>View Recordings</span>
</button>
@if (!room.markedForDeletion) {
@if (canOpenRoom(room)) {
<button
mat-menu-item
(click)="copyModeratorLink(room)"
@ -350,18 +346,18 @@
<mat-icon>content_copy</mat-icon>
<span>Copy Speaker Link</span>
</button>
<mat-divider id="actions-divider-{{ room.roomId }}"></mat-divider>
<button
mat-menu-item
(click)="deleteRoom(room)"
class="delete-action"
id="delete-room-btn-{{ room.roomId }}"
>
<mat-icon>delete</mat-icon>
<span>Delete room</span>
</button>
}
<mat-divider id="actions-divider-{{ room.roomId }}"></mat-divider>
<button
mat-menu-item
(click)="deleteRoom(room)"
class="delete-action"
id="delete-room-btn-{{ room.roomId }}"
>
<mat-icon>delete</mat-icon>
<span>Delete room</span>
</button>
</mat-menu>
</div>
</td>
@ -372,7 +368,6 @@
mat-row
*matRowDef="let row; columns: displayedColumns"
[class.selected-row]="isRoomSelected(row)"
[class.marked-for-deletion]="row.markedForDeletion"
id="table-row-{{ row.roomId }}"
></tr>
</table>

View File

@ -109,16 +109,6 @@
@extend .actions-cell;
}
}
.mat-mdc-row {
&.marked-for-deletion {
background-color: rgba(244, 67, 54, 0.05);
&:hover {
background-color: rgba(244, 67, 54, 0.1);
}
}
}
}
.room-info {
@ -133,6 +123,13 @@
}
}
.status-container {
display: flex;
align-items: center;
gap: var(--ov-meet-spacing-sm);
width: 100%;
}
.status-badge {
@extend .ov-status-badge;
padding: var(--ov-meet-spacing-xs) var(--ov-meet-spacing-sm);
@ -145,6 +142,18 @@
}
}
.meeting-end-info-icon {
@include ov-icon(sm);
&.meeting-end-close {
color: var(--ov-meet-color-warning);
}
&.meeting-end-delete {
color: var(--ov-meet-color-error);
}
}
.date-info {
@extend .ov-date-info;
}
@ -183,15 +192,7 @@
}
}
.auto-deletion-pending {
color: var(--ov-meet-color-warn);
.deletion-icon {
color: var(--ov-meet-color-warn);
}
}
.auto-deletion-not-scheduled {
.auto-deletion-disabled {
color: var(--ov-meet-text-hint);
.deletion-icon {

View File

@ -24,8 +24,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetRoom } from '@lib/typings/ce';
import { debounceTime, distinctUntilChanged } from 'rxjs';
import { MeetingEndAction, MeetRoom, MeetRoomStatus } from '@lib/typings/ce';
export interface RoomTableAction {
rooms: MeetRoom[];
@ -128,8 +127,9 @@ export class RoomsListsComponent implements OnInit, OnChanges {
// Status options
statusOptions = [
{ value: '', label: 'All statuses' },
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' }
{ value: 'open', label: 'Open' },
{ value: 'active_meeting', label: 'Active Meeting' },
{ value: 'closed', label: 'Closed' }
];
constructor() {}
@ -228,8 +228,8 @@ export class RoomsListsComponent implements OnInit, OnChanges {
return this.selectedRooms().has(room.roomId);
}
canSelectRoom(room: MeetRoom): boolean {
return !room.markedForDeletion; // Only active rooms can be selected
canSelectRoom(_room: MeetRoom): boolean {
return true;
}
getSelectedRooms(): MeetRoom[] {
@ -308,72 +308,106 @@ export class RoomsListsComponent implements OnInit, OnChanges {
this.statusFilterControl.setValue('');
}
// ===== STATUS UTILITY METHODS =====
getRoomStatus(room: MeetRoom): string {
return room.markedForDeletion ? 'INACTIVE' : 'ACTIVE';
}
// ===== PERMISSION AND CAPABILITY METHODS =====
canOpenRoom(room: MeetRoom): boolean {
return !room.markedForDeletion;
return room.status !== MeetRoomStatus.CLOSED;
}
canEditRoom(room: MeetRoom): boolean {
return !room.markedForDeletion;
canEditRoom(_room: MeetRoom): boolean {
return true;
}
// ===== UI HELPER METHODS =====
getStatusIcon(room: MeetRoom): string {
return room.markedForDeletion ? 'delete_outline' : 'check_circle';
// ===== STATUS =====
getRoomStatus(room: MeetRoom): string {
return room.status.toUpperCase().replace(/_/g, ' ');
}
getStatusColor(room: MeetRoom): string {
if (room.markedForDeletion) {
return 'var(--ov-meet-color-error)';
getStatusIcon(room: MeetRoom): string {
switch (room.status) {
case MeetRoomStatus.OPEN:
return 'meeting_room';
case MeetRoomStatus.ACTIVE_MEETING:
return 'videocam';
case MeetRoomStatus.CLOSED:
return 'lock';
}
return 'var(--ov-meet-color-success)';
}
getStatusTooltip(room: MeetRoom): string {
return room.markedForDeletion
? 'Room is inactive and marked for deletion'
: 'Room is active and accepting participants';
switch (room.status) {
case MeetRoomStatus.OPEN:
return 'Room is open and ready to accept participants';
case MeetRoomStatus.ACTIVE_MEETING:
return 'A meeting is currently ongoing in this room';
case MeetRoomStatus.CLOSED:
return 'Room is closed and not accepting participants';
}
}
getStatusColor(room: MeetRoom): string {
switch (room.status) {
case MeetRoomStatus.OPEN:
return 'var(--ov-meet-color-success)';
case MeetRoomStatus.ACTIVE_MEETING:
return 'var(--ov-meet-color-primary)';
case MeetRoomStatus.CLOSED:
return 'var(--ov-meet-color-warning)';
}
}
// ===== MEETING END ACTION INFO =====
hasMeetingEndAction(room: MeetRoom): boolean {
return room.status === MeetRoomStatus.ACTIVE_MEETING && room.meetingEndAction !== MeetingEndAction.NONE;
}
getMeetingEndActionTooltip(room: MeetRoom): string {
switch (room.meetingEndAction) {
case MeetingEndAction.CLOSE:
return 'The room will be closed when the meeting ends';
case MeetingEndAction.DELETE:
return 'The room and its recordings will be deleted when the meeting ends';
default:
return '';
}
}
getMeetingEndActionClass(room: MeetRoom): string {
switch (room.meetingEndAction) {
case MeetingEndAction.CLOSE:
return 'meeting-end-close';
case MeetingEndAction.DELETE:
return 'meeting-end-delete';
default:
return '';
}
}
// ===== AUTO-DELETION =====
hasAutoDeletion(room: MeetRoom): boolean {
return !!room.autoDeletionDate;
}
getAutoDeletionStatus(room: MeetRoom): string {
if (room.markedForDeletion) {
return 'Immediate';
}
return room.autoDeletionDate ? 'Scheduled' : 'Disabled';
return room.autoDeletionDate ? 'SCHEDULED' : 'DISABLED';
}
getAutoDeletionIcon(room: MeetRoom): string {
if (room.markedForDeletion) {
return 'acute';
}
return room.autoDeletionDate ? 'auto_delete' : 'close';
}
getAutoDeletionClass(room: MeetRoom): string {
if (room.markedForDeletion) {
return 'auto-deletion-pending';
}
return room.autoDeletionDate ? 'auto-deletion-scheduled' : 'auto-deletion-not-scheduled';
}
getAutoDeletionTooltip(room: MeetRoom): string {
if (room.markedForDeletion) {
return 'Deletes when last participant leaves';
}
return room.autoDeletionDate
? 'Auto-deletion scheduled'
: 'No auto-deletion. Room remains until manually deleted';
}
getAutoDeletionClass(room: MeetRoom): string {
return room.autoDeletionDate ? 'auto-deletion-scheduled' : 'auto-deletion-disabled';
}
}

View File

@ -30,7 +30,7 @@
<div class="stat-content">
<div class="stat-number">{{ stats.totalRooms }}</div>
<div class="stat-label">Total Rooms</div>
<div class="stat-detail">{{ stats.activeRooms }} active</div>
<div class="stat-detail">{{ stats.activeRooms }} with active meetings</div>
</div>
</mat-card-content>
<mat-card-actions>

View File

@ -5,7 +5,7 @@ import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { NavigationService, RecordingService, RoomService } from '@lib/services';
import { MeetRecordingStatus, MeetRoom } from '@lib/typings/ce';
import { MeetRecordingStatus, MeetRoom, MeetRoomStatus } from '@lib/typings/ce';
interface OverviewStats {
totalRooms: number;
@ -56,7 +56,7 @@ export class OverviewComponent implements OnInit {
this.stats = {
totalRooms: rooms.length,
activeRooms: rooms.filter((room: MeetRoom) => !room.markedForDeletion).length,
activeRooms: rooms.filter((room: MeetRoom) => room.status === MeetRoomStatus.ACTIVE_MEETING).length,
totalRecordings: recordings.length,
playableRecordings: recordings.filter((recording) => recording.status === MeetRecordingStatus.COMPLETE)
.length,

View File

@ -16,7 +16,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { RoomsListsComponent, RoomTableAction } from '@lib/components';
import { NavigationService, NotificationService, RoomService } from '@lib/services';
import { MeetRoom, MeetRoomFilters } from '@lib/typings/ce';
import { MeetingEndAction, MeetRoom, MeetRoomFilters } from '@lib/typings/ce';
import { ILogger, LoggerService } from 'openvidu-components-angular';
@Component({
@ -273,7 +273,9 @@ export class RoomsComponent implements OnInit {
// If the room is marked for deletion, we don't remove it from the list immediately
const currentRooms = this.rooms();
this.rooms.set(
currentRooms.map((r) => (r.roomId === roomId ? { ...r, markedForDeletion: true } : r))
currentRooms.map((r) =>
r.roomId === roomId ? { ...r, meetingEndAction: MeetingEndAction.DELETE } : r
)
);
// this.dataSource.data = this.rooms();
this.notificationService.showSnackbar('Room marked for deletion');
@ -331,7 +333,7 @@ export class RoomsComponent implements OnInit {
// We don't remove them from the list immediately
this.rooms.set(
currentRooms.map((r) =>
roomIds.includes(r.roomId) ? { ...r, markedForDeletion: true } : r
roomIds.includes(r.roomId) ? { ...r, meetingEndAction: MeetingEndAction.DELETE } : r
)
);
this.notificationService.showSnackbar('All rooms marked for deletion');