frontend: implement delete room dialog with meeting and recordings policy options

This commit is contained in:
juancarmore 2025-09-02 11:47:28 +02:00
parent 9641667359
commit 3ae0bdf2a2
4 changed files with 459 additions and 0 deletions

View File

@ -0,0 +1,51 @@
<div class="dialog-container">
<h2 mat-dialog-title class="dialog-title ov-text-center">
<mat-icon class="dialog-icon">delete_outlined</mat-icon>
{{ data.title }}
</h2>
<mat-dialog-content>
<div class="dialog-message" [innerHTML]="data.message"></div>
<p>Please choose how to handle this situation:</p>
<div class="policy-options-container">
@if (data.showWithMeetingPolicy) {
<div class="policy-section">
<h4>With Active Meeting Policy:</h4>
<mat-radio-group [(ngModel)]="selectedMeetingPolicy" class="policy-group">
@for (option of meetingPolicyOptions; track option.value) {
<mat-radio-button [value]="option.value" class="policy-option">
<div class="policy-option-content">
<span class="policy-label">{{ option.label }}</span>
<span class="policy-description">{{ option.description }}</span>
</div>
</mat-radio-button>
}
</mat-radio-group>
</div>
}
@if (data.showWithRecordingsPolicy) {
<div class="policy-section">
<h4>With Recordings Policy:</h4>
<mat-radio-group [(ngModel)]="selectedRecordingPolicy" class="policy-group">
@for (option of recordingPolicyOptions; track option.value) {
<mat-radio-button [value]="option.value" class="policy-option">
<div class="policy-option-content">
<span class="policy-label">{{ option.label }}</span>
<span class="policy-description">{{ option.description }}</span>
</div>
</mat-radio-button>
}
</mat-radio-group>
</div>
}
</div>
</mat-dialog-content>
<mat-dialog-actions class="dialog-action">
<button mat-button mat-dialog-close (click)="close('cancel')" class="cancel-button">Cancel</button>
<button mat-flat-button mat-dialog-close cdkFocusInitial (click)="close('confirm')" class="confirm-button">
{{ data.confirmText ?? 'Confirm' }}
</button>
</mat-dialog-actions>
</div>

View File

@ -0,0 +1,321 @@
// Import design system utilities
@import '../../../../../../../src/assets/styles/design-tokens';
// === DIALOG CONTAINER ===
::ng-deep {
.mat-mdc-dialog-container {
border-radius: var(--ov-meet-card-border-radius) !important;
overflow: hidden;
}
.mat-mdc-dialog-surface {
border-radius: var(--ov-meet-card-border-radius) !important;
}
}
:host {
.dialog-container {
@include ov-theme-transition;
display: block;
animation: dialogFadeIn 0.2s ease-out;
}
}
// === DIALOG ANIMATION ===
@keyframes dialogFadeIn {
from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
// === DIALOG TITLE ===
.dialog-title {
@extend .ov-text-center;
@include ov-theme-transition;
@include ov-flex-center;
gap: var(--ov-meet-spacing-sm);
color: var(--ov-meet-text-primary);
font-size: var(--ov-meet-font-size-xl) !important;
font-weight: var(--ov-meet-font-weight-medium);
margin-bottom: var(--ov-meet-spacing-md);
padding: var(--ov-meet-spacing-lg) var(--ov-meet-spacing-lg) 0;
line-height: var(--ov-meet-line-height-tight);
.dialog-icon {
@include ov-icon(md);
color: var(--ov-meet-color-error);
}
}
// === DIALOG CONTENT ===
::ng-deep mat-dialog-content {
@include ov-theme-transition;
color: var(--ov-meet-text-secondary);
font-size: var(--ov-meet-font-size-md) !important;
line-height: var(--ov-meet-line-height-relaxed);
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-lg);
text-align: center;
min-height: auto;
.dialog-message {
margin-bottom: var(--ov-meet-spacing-md);
text-align: left;
strong {
color: var(--ov-meet-color-error);
font-weight: var(--ov-meet-font-weight-medium);
}
}
p {
margin: var(--ov-meet-spacing-sm) 0;
text-align: left;
}
}
// === POLICY OPTIONS CONTAINER ===
.policy-options-container {
@include ov-theme-transition;
margin-top: var(--ov-meet-spacing-md);
padding: var(--ov-meet-spacing-md);
background: var(--ov-meet-surface-variant);
border: 1px solid var(--ov-meet-border-color);
border-radius: var(--ov-meet-radius-md);
text-align: left;
}
.policy-section {
margin-bottom: var(--ov-meet-spacing-lg);
&:last-child {
margin-bottom: 0;
}
h4 {
margin: 0 0 var(--ov-meet-spacing-sm) 0;
font-size: var(--ov-meet-font-size-md);
font-weight: var(--ov-meet-font-weight-medium);
color: var(--ov-meet-text-primary);
}
}
.policy-group {
display: flex;
flex-direction: column;
gap: var(--ov-meet-spacing-sm);
}
.policy-option {
@include ov-theme-transition;
display: flex !important;
align-items: flex-start;
width: 100%;
margin: 0;
padding: var(--ov-meet-spacing-sm);
border-radius: var(--ov-meet-radius-sm);
&:hover {
background: var(--ov-meet-surface-hover);
}
// Override Material radio button styles
::ng-deep {
.mdc-radio {
margin-top: 2px; // Align with text baseline
}
.mdc-form-field > label {
width: 100%;
}
}
.policy-option-content {
display: flex;
flex-direction: column;
margin-left: var(--ov-meet-spacing-sm);
flex: 1;
.policy-label {
font-weight: var(--ov-meet-font-weight-medium);
font-size: var(--ov-meet-font-size-md);
color: var(--ov-meet-text-primary);
line-height: var(--ov-meet-line-height-tight);
}
.policy-description {
font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-secondary);
margin-top: var(--ov-meet-spacing-xs);
line-height: var(--ov-meet-line-height-relaxed);
}
}
// Selected state styling
&.mat-mdc-radio-checked {
background: var(--ov-meet-primary-container);
.policy-label {
color: var(--ov-meet-primary);
}
}
}
// === DIALOG ACTIONS ===
.dialog-action {
@include ov-flex-center;
gap: var(--ov-meet-spacing-sm);
padding: var(--ov-meet-spacing-lg);
margin: 0;
justify-content: center;
// Button styling
button {
@include ov-button-base;
@include ov-theme-transition;
@include ov-flex-center;
gap: var(--ov-meet-spacing-xs);
min-width: 100px;
margin: 0;
mat-icon {
@include ov-icon(sm);
}
// Cancel/Secondary button
&.cancel-button {
color: var(--ov-meet-text-secondary);
border: 1px solid var(--ov-meet-border-color);
background: transparent;
&:hover {
@include ov-hover-lift(-1px);
background: var(--ov-meet-surface-hover);
color: var(--ov-meet-text-primary);
}
}
// Confirm/Primary button (Delete action)
&.confirm-button {
background: var(--ov-meet-color-error);
border-color: var(--ov-meet-color-error);
color: var(--ov-meet-text-on-error);
&:hover {
@include ov-hover-lift(-1px);
background: var(--ov-meet-color-error);
filter: brightness(0.9);
}
&:focus {
outline: 2px solid var(--ov-meet-color-error);
outline-offset: 2px;
}
&:active {
transform: translateY(0);
}
}
}
}
// === RESPONSIVE DESIGN ===
@include ov-mobile-down {
.dialog-title {
font-size: var(--ov-meet-font-size-lg);
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-md) 0;
flex-direction: column;
gap: var(--ov-meet-spacing-xs);
.dialog-icon {
@include ov-icon(lg);
}
}
::ng-deep mat-dialog-content {
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
font-size: var(--ov-meet-font-size-sm);
}
.policy-options-container {
margin-top: var(--ov-meet-spacing-sm);
padding: var(--ov-meet-spacing-sm);
}
.policy-section {
margin-bottom: var(--ov-meet-spacing-md);
h4 {
font-size: var(--ov-meet-font-size-sm);
}
}
.policy-option {
padding: var(--ov-meet-spacing-xs);
.policy-option-content {
.policy-label {
font-size: var(--ov-meet-font-size-sm);
}
.policy-description {
font-size: var(--ov-meet-font-size-xs);
}
}
}
.dialog-action {
padding: var(--ov-meet-spacing-md);
flex-direction: column;
gap: var(--ov-meet-spacing-xs);
button {
width: 100%;
min-width: auto;
justify-content: center;
}
}
}
// === ACCESSIBILITY ENHANCEMENTS ===
@media (prefers-reduced-motion: reduce) {
:host {
animation: none;
}
@keyframes dialogFadeIn {
from,
to {
opacity: 1;
transform: none;
}
}
}
// === HIGH CONTRAST MODE ===
@media (prefers-contrast: high) {
.dialog-title {
font-weight: var(--ov-meet-font-weight-bold);
}
.policy-option {
border: 1px solid var(--ov-meet-border-color);
&.mat-mdc-radio-checked {
border-color: var(--ov-meet-primary);
border-width: 2px;
}
}
.dialog-action button {
border-width: 2px;
&[mat-button] {
border-color: currentColor;
}
}
}

View File

@ -0,0 +1,73 @@
import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogContent,
MatDialogRef,
MatDialogTitle
} from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatRadioModule } from '@angular/material/radio';
import type { DeleteRoomDialogOptions } from '@lib/models';
import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce';
@Component({
selector: 'ov-dialog',
standalone: true,
imports: [
FormsModule,
MatButtonModule,
MatIconModule,
MatRadioModule,
MatDialogActions,
MatDialogContent,
MatDialogTitle
],
templateUrl: './delete-room-dialog.component.html',
styleUrl: './delete-room-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DeleteRoomDialogComponent {
readonly dialogRef = inject(MatDialogRef<DeleteRoomDialogComponent>);
meetingPolicyOptions = [
{
value: MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS,
label: 'When meeting ends',
description: 'The room will be deleted when the meeting ends.'
},
{
value: MeetRoomDeletionPolicyWithMeeting.FORCE,
label: 'Force',
description:
'The meeting will be ended immediately, and the room will be deleted without waiting for participants to leave.'
}
];
recordingPolicyOptions = [
{
value: MeetRoomDeletionPolicyWithRecordings.CLOSE,
label: 'Close',
description: 'The room will be closed instead of deleted, maintaining its recordings.'
},
{
value: MeetRoomDeletionPolicyWithRecordings.FORCE,
label: 'Force',
description: 'The room and its recordings will be deleted immediately.'
}
];
selectedMeetingPolicy = MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS;
selectedRecordingPolicy = MeetRoomDeletionPolicyWithRecordings.CLOSE;
constructor(@Inject(MAT_DIALOG_DATA) public data: DeleteRoomDialogOptions) {}
close(type: 'confirm' | 'cancel'): void {
this.dialogRef.close();
if (type === 'confirm') {
this.data.confirmCallback(this.selectedMeetingPolicy, this.selectedRecordingPolicy);
}
}
}

View File

@ -1,3 +1,5 @@
import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce';
export interface DialogOptions { export interface DialogOptions {
title?: string; title?: string;
icon?: string; icon?: string;
@ -12,3 +14,15 @@ export interface DialogOptions {
forceCheckboxDescription?: string; forceCheckboxDescription?: string;
forceConfirmCallback?: () => void; forceConfirmCallback?: () => void;
} }
export interface DeleteRoomDialogOptions {
title: string;
message: string;
showWithMeetingPolicy: boolean;
showWithRecordingsPolicy: boolean;
confirmText?: string;
confirmCallback: (
meetingPolicy: MeetRoomDeletionPolicyWithMeeting,
recordingPolicy: MeetRoomDeletionPolicyWithRecordings
) => void;
}