frontend: add toggle functionality for room status with UI updates

This commit is contained in:
juancarmore 2025-08-30 13:33:55 +02:00
parent 1af8f77b7b
commit 089877959a
7 changed files with 117 additions and 7 deletions

View File

@ -349,6 +349,16 @@
} }
<mat-divider id="actions-divider-{{ room.roomId }}"></mat-divider> <mat-divider id="actions-divider-{{ room.roomId }}"></mat-divider>
<button
mat-menu-item
(click)="toggleRoomStatus(room)"
[ngClass]="getRoomToggleIconClass(room)"
id="toggle-room-status-btn-{{ room.roomId }}"
>
<mat-icon>{{ getRoomToggleIcon(room) }}</mat-icon>
<span>{{ getRoomToggleLabel(room) }}</span>
</button>
<button <button
mat-menu-item mat-menu-item
(click)="deleteRoom(room)" (click)="deleteRoom(room)"

View File

@ -225,6 +225,7 @@
&:hover { &:hover {
background-color: transparent; background-color: transparent;
} }
&.primary-action { &.primary-action {
color: var(--ov-meet-color-primary); color: var(--ov-meet-color-primary);
} }
@ -247,6 +248,22 @@
} }
} }
.close-room-icon {
color: var(--ov-meet-color-warning) !important;
.mat-icon {
color: var(--ov-meet-color-warning) !important;
}
}
.open-room-icon {
color: var(--ov-meet-color-success) !important;
.mat-icon {
color: var(--ov-meet-color-success) !important;
}
}
.delete-action { .delete-action {
@extend .ov-delete-action; @extend .ov-delete-action;
} }

View File

@ -35,6 +35,8 @@ export interface RoomTableAction {
| 'copyModeratorLink' | 'copyModeratorLink'
| 'copySpeakerLink' | 'copySpeakerLink'
| 'viewRecordings' | 'viewRecordings'
| 'reopen'
| 'close'
| 'delete' | 'delete'
| 'bulkDelete'; | 'bulkDelete';
} }
@ -263,6 +265,14 @@ export class RoomsListsComponent implements OnInit, OnChanges {
this.roomAction.emit({ rooms: [room], action: 'viewRecordings' }); this.roomAction.emit({ rooms: [room], action: 'viewRecordings' });
} }
toggleRoomStatus(room: MeetRoom) {
if (room.status !== MeetRoomStatus.CLOSED) {
this.roomAction.emit({ rooms: [room], action: 'close' });
} else {
this.roomAction.emit({ rooms: [room], action: 'reopen' });
}
}
deleteRoom(room: MeetRoom) { deleteRoom(room: MeetRoom) {
this.roomAction.emit({ rooms: [room], action: 'delete' }); this.roomAction.emit({ rooms: [room], action: 'delete' });
} }
@ -410,4 +420,18 @@ export class RoomsListsComponent implements OnInit, OnChanges {
getAutoDeletionClass(room: MeetRoom): string { getAutoDeletionClass(room: MeetRoom): string {
return room.autoDeletionDate ? 'auto-deletion-scheduled' : 'auto-deletion-disabled'; return room.autoDeletionDate ? 'auto-deletion-scheduled' : 'auto-deletion-disabled';
} }
// ===== ROOM TOGGLE =====
getRoomToggleIcon(room: MeetRoom): string {
return room.status !== MeetRoomStatus.CLOSED ? 'lock' : 'meeting_room';
}
getRoomToggleLabel(room: MeetRoom): string {
return room.status !== MeetRoomStatus.CLOSED ? 'Close Room' : 'Open Room';
}
getRoomToggleIconClass(room: MeetRoom): string {
return room.status !== MeetRoomStatus.CLOSED ? 'close-room-icon' : 'open-room-icon';
}
} }

View File

@ -16,7 +16,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { RoomsListsComponent, RoomTableAction } from '@lib/components'; import { RoomsListsComponent, RoomTableAction } from '@lib/components';
import { NavigationService, NotificationService, RoomService } from '@lib/services'; import { NavigationService, NotificationService, RoomService } from '@lib/services';
import { MeetingEndAction, MeetRoom, MeetRoomFilters } from '@lib/typings/ce'; import { MeetingEndAction, MeetRoom, MeetRoomFilters, MeetRoomStatus } from '@lib/typings/ce';
import { ILogger, LoggerService } from 'openvidu-components-angular'; import { ILogger, LoggerService } from 'openvidu-components-angular';
@Component({ @Component({
@ -108,6 +108,12 @@ export class RoomsComponent implements OnInit {
case 'viewRecordings': case 'viewRecordings':
await this.viewRecordings(action.rooms[0]); await this.viewRecordings(action.rooms[0]);
break; break;
case 'reopen':
this.reopenRoom(action.rooms[0]);
break;
case 'close':
this.closeRoom(action.rooms[0]);
break;
case 'delete': case 'delete':
this.deleteRoom(action.rooms[0]); this.deleteRoom(action.rooms[0]);
break; break;
@ -265,6 +271,40 @@ export class RoomsComponent implements OnInit {
} }
} }
private async reopenRoom(room: MeetRoom) {
try {
const { room: updatedRoom } = await this.roomService.updateRoomStatus(room.roomId, MeetRoomStatus.OPEN);
// Update room in the list
this.rooms.set(this.rooms().map((r) => (r.roomId === updatedRoom.roomId ? updatedRoom : r)));
this.notificationService.showSnackbar('Room reopened successfully');
} catch (error) {
this.notificationService.showSnackbar('Failed to reopen room');
this.log.e('Error reopening room:', error);
}
}
private async closeRoom(room: MeetRoom) {
try {
const { statusCode, room: updatedRoom } = await this.roomService.updateRoomStatus(
room.roomId,
MeetRoomStatus.CLOSED
);
// Update room in the list
this.rooms.set(this.rooms().map((r) => (r.roomId === updatedRoom.roomId ? updatedRoom : r)));
if (statusCode === 202) {
this.notificationService.showSnackbar('Room scheduled to be closed when the meeting ends');
} else {
this.notificationService.showSnackbar('Room closed successfully');
}
} catch (error) {
this.notificationService.showSnackbar('Failed to close room');
this.log.e('Error closing room:', error);
}
}
private deleteRoom({ roomId }: MeetRoom) { private deleteRoom({ roomId }: MeetRoom) {
const deleteCallback = async () => { const deleteCallback = async () => {
try { try {

View File

@ -42,7 +42,7 @@ export class GlobalPreferencesService {
async saveSecurityPreferences(preferences: SecurityPreferences) { async saveSecurityPreferences(preferences: SecurityPreferences) {
const path = `${this.PREFERENCES_API}/security`; const path = `${this.PREFERENCES_API}/security`;
await this.httpService.putRequest<SecurityPreferences>(path, preferences); await this.httpService.putRequest(path, preferences);
this.securityPreferences = preferences; this.securityPreferences = preferences;
} }
@ -58,7 +58,7 @@ export class GlobalPreferencesService {
async saveWebhookPreferences(preferences: WebhookPreferences) { async saveWebhookPreferences(preferences: WebhookPreferences) {
const path = `${this.PREFERENCES_API}/webhooks`; const path = `${this.PREFERENCES_API}/webhooks`;
await this.httpService.putRequest<WebhookPreferences>(path, preferences); await this.httpService.putRequest(path, preferences);
} }
async testWebhookUrl(url: string): Promise<void> { async testWebhookUrl(url: string): Promise<void> {

View File

@ -24,8 +24,14 @@ export class HttpService {
} }
async putRequest<T>(path: string, body: any = {}, headers?: Record<string, string>): Promise<T> { async putRequest<T>(path: string, body: any = {}, headers?: Record<string, string>): Promise<T> {
const options = headers ? { headers: new HttpHeaders(headers) } : {}; const options = {
return lastValueFrom(this.http.put<T>(path, body, options)); observe: 'response' as const,
...(headers ? { headers: new HttpHeaders(headers) } : {})
};
return lastValueFrom(this.http.put<T>(path, body, options)).then((response) => ({
...(response.body as T),
statusCode: response.status
}));
} }
async patchRequest<T>(path: string, body: any = {}, headers?: Record<string, string>): Promise<T> { async patchRequest<T>(path: string, body: any = {}, headers?: Record<string, string>): Promise<T> {

View File

@ -5,7 +5,8 @@ import {
MeetRoomFilters, MeetRoomFilters,
MeetRoomOptions, MeetRoomOptions,
MeetRoomPreferences, MeetRoomPreferences,
MeetRoomRoleAndPermissions MeetRoomRoleAndPermissions,
MeetRoomStatus
} from '@lib/typings/ce'; } from '@lib/typings/ce';
import { LoggerService } from 'openvidu-components-angular'; import { LoggerService } from 'openvidu-components-angular';
@ -104,11 +105,23 @@ export class RoomService {
* @return A promise that resolves to the MeetRoom object * @return A promise that resolves to the MeetRoom object
*/ */
async getRoom(roomId: string): Promise<MeetRoom> { async getRoom(roomId: string): Promise<MeetRoom> {
let path = `${this.ROOMS_API}/${roomId}`; const path = `${this.ROOMS_API}/${roomId}`;
const headers = this.participantService.getParticipantRoleHeader(); const headers = this.participantService.getParticipantRoleHeader();
return this.httpService.getRequest(path, headers); return this.httpService.getRequest(path, headers);
} }
/**
* Updates the status of a room.
*
* @param roomId - The unique identifier of the room
* @param status - The new status to be set
* @return A promise that resolves to an object containing the updated room and a status code
*/
async updateRoomStatus(roomId: string, status: MeetRoomStatus): Promise<{ statusCode: number; room: MeetRoom }> {
const path = `${this.ROOMS_API}/${roomId}/status`;
return this.httpService.putRequest(path, { status });
}
/** /**
* Deletes a room by its ID. * Deletes a room by its ID.
* *