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 {
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user