frontend: implement delete room dialog with meeting and recordings policy options
This commit is contained in:
parent
9641667359
commit
3ae0bdf2a2
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce';
|
||||
|
||||
export interface DialogOptions {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
@ -12,3 +14,15 @@ export interface DialogOptions {
|
||||
forceCheckboxDescription?: string;
|
||||
forceConfirmCallback?: () => void;
|
||||
}
|
||||
|
||||
export interface DeleteRoomDialogOptions {
|
||||
title: string;
|
||||
message: string;
|
||||
showWithMeetingPolicy: boolean;
|
||||
showWithRecordingsPolicy: boolean;
|
||||
confirmText?: string;
|
||||
confirmCallback: (
|
||||
meetingPolicy: MeetRoomDeletionPolicyWithMeeting,
|
||||
recordingPolicy: MeetRoomDeletionPolicyWithRecordings
|
||||
) => void;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user