frontend: enhance dialog component with force delete option and improved layout
This commit is contained in:
parent
0e4cfa5bb5
commit
a499fe4cd4
@ -37,12 +37,19 @@
|
||||
.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-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
// === DIALOG CONTENT ===
|
||||
@ -54,6 +61,53 @@
|
||||
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);
|
||||
}
|
||||
|
||||
.force-checkbox-container {
|
||||
@include ov-theme-transition;
|
||||
margin-top: var(--ov-meet-spacing-lg);
|
||||
padding: var(--ov-meet-spacing-md);
|
||||
border: 1px solid var(--ov-meet-border-color);
|
||||
border-radius: var(--ov-meet-radius-md);
|
||||
background: var(--ov-meet-surface-variant);
|
||||
text-align: left;
|
||||
|
||||
.force-checkbox {
|
||||
margin-bottom: var(--ov-meet-spacing-sm);
|
||||
|
||||
.checkbox-text {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
color: var(--ov-meet-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-warning {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
margin-top: var(--ov-meet-spacing-xs);
|
||||
|
||||
.warning-icon {
|
||||
@include ov-icon(sm);
|
||||
color: var(--ov-meet-color-warning);
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px; // Align with first line of text
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: var(--ov-meet-font-size-xs);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
font-style: italic;
|
||||
line-height: var(--ov-meet-line-height-relaxed);
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === DIALOG ACTIONS ===
|
||||
@ -62,52 +116,61 @@
|
||||
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: 80px;
|
||||
margin: 0; // Reset default margin
|
||||
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
}
|
||||
|
||||
// Cancel/Secondary button
|
||||
&[mat-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);
|
||||
background: var(--ov-meet-surface-hover);
|
||||
color: var(--ov-meet-text-primary);
|
||||
}
|
||||
|
||||
// &:focus {
|
||||
// outline: 2px solid var(--ov-meet-color-primary);
|
||||
// outline-offset: 2px;
|
||||
// }
|
||||
}
|
||||
|
||||
// Confirm/Primary button
|
||||
&[mat-flat-button] {
|
||||
// background: var(--ov-meet-color-primary);
|
||||
&.confirm-button {
|
||||
color: var(--ov-meet-text-on-primary);
|
||||
|
||||
&:hover {
|
||||
@include ov-hover-lift(-1px);
|
||||
// background: var(--ov-meet-color-primary-dark);
|
||||
// box-shadow: var(--ov-meet-shadow-hover);
|
||||
}
|
||||
|
||||
// &:focus {
|
||||
// outline: 2px solid var(--ov-meet-color-primary-light);
|
||||
// outline-offset: 2px;
|
||||
// }
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
// Force delete state - changes to warning color
|
||||
&.force-delete {
|
||||
background: var(--ov-meet-color-warning) !important;
|
||||
border-color: var(--ov-meet-color-warning) !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--ov-meet-color-warning) !important;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--ov-meet-color-warning);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,11 +180,35 @@
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
mat-dialog-content {
|
||||
::ng-deep mat-dialog-content {
|
||||
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
|
||||
.force-checkbox-container {
|
||||
margin-top: var(--ov-meet-spacing-md);
|
||||
padding: var(--ov-meet-spacing-sm);
|
||||
|
||||
.checkbox-warning {
|
||||
margin-left: 0; // Remove extra margin on mobile
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
|
||||
.warning-icon {
|
||||
margin-top: 1px; // Adjust for smaller text
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: var(--ov-meet-font-size-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-action {
|
||||
@ -132,6 +219,7 @@
|
||||
button {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,50 @@
|
||||
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 } from '@angular/material/dialog';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog';
|
||||
import type { DialogOptions } from '@lib/models';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-dialog',
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, MatDialogActions, MatDialogContent],
|
||||
imports: [FormsModule, MatButtonModule, MatIconModule, MatCheckboxModule, MatDialogActions, MatDialogContent, MatDialogTitle],
|
||||
template: ` <div class="dialog-container">
|
||||
<h2 mat-dialog-title class="dialog-title ov-text-center">{{ data.title }}</h2>
|
||||
<mat-dialog-content [innerHTML]="data.message"></mat-dialog-content>
|
||||
<h2 mat-dialog-title class="dialog-title ov-text-center">
|
||||
<mat-icon class="dialog-icon">{{ getDialogIcon() }}</mat-icon>
|
||||
{{ data.title }}
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="dialog-message" [innerHTML]="data.message"></div>
|
||||
@if (data.showForceCheckbox) {
|
||||
<div class="force-checkbox-container">
|
||||
<mat-checkbox
|
||||
[(ngModel)]="forceDelete"
|
||||
class="force-checkbox"
|
||||
color="warn"
|
||||
>
|
||||
<span class="checkbox-text">{{ data.forceCheckboxText }}</span>
|
||||
</mat-checkbox>
|
||||
<div class="checkbox-warning">
|
||||
<mat-icon class="warning-icon">warning</mat-icon>
|
||||
<span class="warning-text">{{ data.forceCheckboxDescription }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="dialog-action">
|
||||
<button mat-button mat-dialog-close (click)="close('cancel')">{{ data.cancelText }}</button>
|
||||
<button mat-flat-button mat-dialog-close cdkFocusInitial (click)="close('confirm')">
|
||||
<button mat-button mat-dialog-close (click)="close('cancel')" class="cancel-button">
|
||||
{{ data.cancelText }}
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
mat-dialog-close
|
||||
cdkFocusInitial
|
||||
(click)="close('confirm')"
|
||||
class="confirm-button"
|
||||
[class.force-delete]="forceDelete"
|
||||
>
|
||||
{{ data.confirmText }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
@ -22,15 +54,33 @@ import type { DialogOptions } from '@lib/models';
|
||||
})
|
||||
export class DialogComponent {
|
||||
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
||||
forceDelete = false;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: DialogOptions) {}
|
||||
|
||||
close(type: 'confirm' | 'cancel'): void {
|
||||
this.dialogRef.close();
|
||||
if (type === 'confirm' && this.data.confirmCallback) {
|
||||
this.data.confirmCallback();
|
||||
if (type === 'confirm') {
|
||||
if (this.forceDelete && this.data.forceConfirmCallback) {
|
||||
this.data.forceConfirmCallback();
|
||||
} else if (this.data.confirmCallback) {
|
||||
this.data.confirmCallback();
|
||||
}
|
||||
} else if (type === 'cancel' && this.data.cancelCallback) {
|
||||
this.data.cancelCallback();
|
||||
}
|
||||
}
|
||||
|
||||
getDialogIcon(): string {
|
||||
if (this.data.title?.toLowerCase().includes('delete')) {
|
||||
return 'delete_outline';
|
||||
}
|
||||
if (this.data.title?.toLowerCase().includes('warning')) {
|
||||
return 'warning';
|
||||
}
|
||||
if (this.data.title?.toLowerCase().includes('error')) {
|
||||
return 'error_outline';
|
||||
}
|
||||
return 'help_outline';
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,4 +5,9 @@ export interface DialogOptions {
|
||||
cancelText?: string;
|
||||
cancelCallback?: () => void;
|
||||
confirmCallback?: () => void;
|
||||
// Force delete options
|
||||
showForceCheckbox?: boolean;
|
||||
forceCheckboxText?: string;
|
||||
forceCheckboxDescription?: string;
|
||||
forceConfirmCallback?: () => void;
|
||||
}
|
||||
|
||||
@ -276,12 +276,30 @@ export class RoomsComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
const forceDeleteCallback = async () => {
|
||||
try {
|
||||
const response = await this.roomService.deleteRoom(roomId, true); // force = true
|
||||
|
||||
const currentRooms = this.rooms();
|
||||
this.rooms.set(currentRooms.filter((r) => r.roomId !== roomId));
|
||||
this.notificationService.showSnackbar('Room force deleted successfully');
|
||||
} catch (error) {
|
||||
this.notificationService.showAlert('Failed to force delete room');
|
||||
this.log.e('Error force deleting room:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.notificationService.showDialog({
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
title: 'Delete Room',
|
||||
message: `Are you sure you want to delete the room <b>${roomId}</b>?`,
|
||||
confirmCallback: deleteCallback
|
||||
confirmCallback: deleteCallback,
|
||||
showForceCheckbox: true,
|
||||
forceCheckboxText: 'Force delete',
|
||||
forceCheckboxDescription:
|
||||
'This will immediately disconnect all active participants and delete the room without waiting for participants to leave',
|
||||
forceConfirmCallback: forceDeleteCallback
|
||||
});
|
||||
}
|
||||
|
||||
@ -339,13 +357,32 @@ export class RoomsComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
const bulkForceDeleteCallback = async () => {
|
||||
try {
|
||||
const roomIds = rooms.map((r) => r.roomId);
|
||||
const response = await this.roomService.bulkDeleteRooms(roomIds, true); // force = true
|
||||
|
||||
const currentRooms = this.rooms();
|
||||
this.rooms.set(currentRooms.filter((r) => !roomIds.includes(r.roomId)));
|
||||
this.notificationService.showSnackbar('All rooms force deleted successfully');
|
||||
} catch (error) {
|
||||
this.notificationService.showAlert('Failed to force delete rooms');
|
||||
this.log.e('Error force deleting rooms:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const count = rooms.length;
|
||||
this.notificationService.showDialog({
|
||||
confirmText: 'Delete all',
|
||||
cancelText: 'Cancel',
|
||||
title: 'Delete Rooms',
|
||||
message: `Are you sure you want to delete <b>${count}</b> rooms?`,
|
||||
confirmCallback: bulkDeleteCallback
|
||||
confirmCallback: bulkDeleteCallback,
|
||||
showForceCheckbox: true,
|
||||
forceCheckboxText: 'Force delete',
|
||||
forceCheckboxDescription:
|
||||
'This will immediately disconnect all active participants and delete the room without waiting for participants to leave',
|
||||
forceConfirmCallback: bulkForceDeleteCallback
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,11 +44,10 @@ export class NotificationService {
|
||||
panelClass: 'custom-snackbar'
|
||||
});
|
||||
}
|
||||
|
||||
showDialog(options: DialogOptions): void {
|
||||
this.dialog.open(DialogComponent, {
|
||||
data: options,
|
||||
width: '400px',
|
||||
width: '450px',
|
||||
disableClose: true
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user