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 {
|
.dialog-title {
|
||||||
@extend .ov-text-center;
|
@extend .ov-text-center;
|
||||||
@include ov-theme-transition;
|
@include ov-theme-transition;
|
||||||
|
@include ov-flex-center;
|
||||||
|
gap: var(--ov-meet-spacing-sm);
|
||||||
color: var(--ov-meet-text-primary);
|
color: var(--ov-meet-text-primary);
|
||||||
font-size: var(--ov-meet-font-size-xl) !important;
|
font-size: var(--ov-meet-font-size-xl) !important;
|
||||||
font-weight: var(--ov-meet-font-weight-medium);
|
font-weight: var(--ov-meet-font-weight-medium);
|
||||||
margin-bottom: var(--ov-meet-spacing-md);
|
margin-bottom: var(--ov-meet-spacing-md);
|
||||||
padding: var(--ov-meet-spacing-lg) var(--ov-meet-spacing-lg) 0;
|
padding: var(--ov-meet-spacing-lg) var(--ov-meet-spacing-lg) 0;
|
||||||
line-height: var(--ov-meet-line-height-tight);
|
line-height: var(--ov-meet-line-height-tight);
|
||||||
|
|
||||||
|
.dialog-icon {
|
||||||
|
@include ov-icon(md);
|
||||||
|
color: var(--ov-meet-text-secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === DIALOG CONTENT ===
|
// === DIALOG CONTENT ===
|
||||||
@ -54,6 +61,53 @@
|
|||||||
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-lg);
|
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-lg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-height: auto;
|
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 ===
|
// === DIALOG ACTIONS ===
|
||||||
@ -62,52 +116,61 @@
|
|||||||
gap: var(--ov-meet-spacing-sm);
|
gap: var(--ov-meet-spacing-sm);
|
||||||
padding: var(--ov-meet-spacing-lg);
|
padding: var(--ov-meet-spacing-lg);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
// Button styling
|
// Button styling
|
||||||
button {
|
button {
|
||||||
@include ov-button-base;
|
@include ov-button-base;
|
||||||
@include ov-theme-transition;
|
@include ov-theme-transition;
|
||||||
|
@include ov-flex-center;
|
||||||
|
gap: var(--ov-meet-spacing-xs);
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
margin: 0; // Reset default margin
|
margin: 0; // Reset default margin
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
@include ov-icon(sm);
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel/Secondary button
|
// Cancel/Secondary button
|
||||||
&[mat-button] {
|
&.cancel-button {
|
||||||
color: var(--ov-meet-text-secondary);
|
color: var(--ov-meet-text-secondary);
|
||||||
border: 1px solid var(--ov-meet-border-color);
|
border: 1px solid var(--ov-meet-border-color);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include ov-hover-lift(-1px);
|
@include ov-hover-lift(-1px);
|
||||||
|
background: var(--ov-meet-surface-hover);
|
||||||
// background: var(--ov-meet-surface-hover);
|
color: var(--ov-meet-text-primary);
|
||||||
// color: var(--ov-meet-text-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// &:focus {
|
|
||||||
// outline: 2px solid var(--ov-meet-color-primary);
|
|
||||||
// outline-offset: 2px;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm/Primary button
|
// Confirm/Primary button
|
||||||
&[mat-flat-button] {
|
&.confirm-button {
|
||||||
// background: var(--ov-meet-color-primary);
|
|
||||||
color: var(--ov-meet-text-on-primary);
|
color: var(--ov-meet-text-on-primary);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include ov-hover-lift(-1px);
|
@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 {
|
&:active {
|
||||||
transform: translateY(0);
|
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 {
|
.dialog-title {
|
||||||
font-size: var(--ov-meet-font-size-lg);
|
font-size: var(--ov-meet-font-size-lg);
|
||||||
padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-md) 0;
|
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);
|
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
|
||||||
font-size: var(--ov-meet-font-size-sm);
|
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 {
|
.dialog-action {
|
||||||
@ -132,6 +219,7 @@
|
|||||||
button {
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,50 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
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';
|
import type { DialogOptions } from '@lib/models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ov-dialog',
|
selector: 'ov-dialog',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MatButtonModule, MatDialogActions, MatDialogContent],
|
imports: [FormsModule, MatButtonModule, MatIconModule, MatCheckboxModule, MatDialogActions, MatDialogContent, MatDialogTitle],
|
||||||
template: ` <div class="dialog-container">
|
template: ` <div class="dialog-container">
|
||||||
<h2 mat-dialog-title class="dialog-title ov-text-center">{{ data.title }}</h2>
|
<h2 mat-dialog-title class="dialog-title ov-text-center">
|
||||||
<mat-dialog-content [innerHTML]="data.message"></mat-dialog-content>
|
<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">
|
<mat-dialog-actions class="dialog-action">
|
||||||
<button mat-button mat-dialog-close (click)="close('cancel')">{{ data.cancelText }}</button>
|
<button mat-button mat-dialog-close (click)="close('cancel')" class="cancel-button">
|
||||||
<button mat-flat-button mat-dialog-close cdkFocusInitial (click)="close('confirm')">
|
{{ data.cancelText }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
mat-dialog-close
|
||||||
|
cdkFocusInitial
|
||||||
|
(click)="close('confirm')"
|
||||||
|
class="confirm-button"
|
||||||
|
[class.force-delete]="forceDelete"
|
||||||
|
>
|
||||||
{{ data.confirmText }}
|
{{ data.confirmText }}
|
||||||
</button>
|
</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
@ -22,15 +54,33 @@ import type { DialogOptions } from '@lib/models';
|
|||||||
})
|
})
|
||||||
export class DialogComponent {
|
export class DialogComponent {
|
||||||
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
||||||
|
forceDelete = false;
|
||||||
|
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public data: DialogOptions) {}
|
constructor(@Inject(MAT_DIALOG_DATA) public data: DialogOptions) {}
|
||||||
|
|
||||||
close(type: 'confirm' | 'cancel'): void {
|
close(type: 'confirm' | 'cancel'): void {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
if (type === 'confirm' && this.data.confirmCallback) {
|
if (type === 'confirm') {
|
||||||
this.data.confirmCallback();
|
if (this.forceDelete && this.data.forceConfirmCallback) {
|
||||||
|
this.data.forceConfirmCallback();
|
||||||
|
} else if (this.data.confirmCallback) {
|
||||||
|
this.data.confirmCallback();
|
||||||
|
}
|
||||||
} else if (type === 'cancel' && this.data.cancelCallback) {
|
} else if (type === 'cancel' && this.data.cancelCallback) {
|
||||||
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;
|
cancelText?: string;
|
||||||
cancelCallback?: () => void;
|
cancelCallback?: () => void;
|
||||||
confirmCallback?: () => 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({
|
this.notificationService.showDialog({
|
||||||
confirmText: 'Delete',
|
confirmText: 'Delete',
|
||||||
cancelText: 'Cancel',
|
cancelText: 'Cancel',
|
||||||
title: 'Delete Room',
|
title: 'Delete Room',
|
||||||
message: `Are you sure you want to delete the room <b>${roomId}</b>?`,
|
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;
|
const count = rooms.length;
|
||||||
this.notificationService.showDialog({
|
this.notificationService.showDialog({
|
||||||
confirmText: 'Delete all',
|
confirmText: 'Delete all',
|
||||||
cancelText: 'Cancel',
|
cancelText: 'Cancel',
|
||||||
title: 'Delete Rooms',
|
title: 'Delete Rooms',
|
||||||
message: `Are you sure you want to delete <b>${count}</b> 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'
|
panelClass: 'custom-snackbar'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showDialog(options: DialogOptions): void {
|
showDialog(options: DialogOptions): void {
|
||||||
this.dialog.open(DialogComponent, {
|
this.dialog.open(DialogComponent, {
|
||||||
data: options,
|
data: options,
|
||||||
width: '400px',
|
width: '450px',
|
||||||
disableClose: true
|
disableClose: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user