Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/app/data/scheduledevent.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,41 @@ export class ScheduledeventService extends ListableResourceClient<ScheduledEvent
const se = JSON.parse(atou(s.content));
return of(se);
}),
map((otac: OTAC[]) => {
otac.map((iOTAC: OTAC) => {
if (iOTAC.redeemed_timestamp) {
iOTAC.redeemed_timestamp = new Date(iOTAC.redeemed_timestamp);
} else {
iOTAC.redeemed_timestamp = undefined;
}
});
return otac;
}),
);
}

public deleteOtac(seId: string, otacId: string) {
return this.garg.get(`/${seId}/otacs/delete/${otacId}`);
}

public updateOtac(seId: string, otacId: string, maxDuration: string) {
const params = new HttpParams().set('max_duration', maxDuration);
return this.garg.post(`/${seId}/otacs/update/${otacId}`, params).pipe(
switchMap((s: ServerResponse) => {
const se = JSON.parse(atou(s.content));
return of(se);
}),
map((otac: OTAC) => {
if (otac.redeemed_timestamp) {
otac.redeemed_timestamp = new Date(otac.redeemed_timestamp);
} else {
otac.redeemed_timestamp = undefined;
}
return otac;
}),
);
}

public listOtacs(seId: string) {
return this.garg.get(`/${seId}/otacs/list`).pipe(
switchMap((s: ServerResponse) => {
Expand Down
86 changes: 84 additions & 2 deletions src/app/event/otacmanagement/otacmanagement.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ <h3 class="modal-title">
<clr-dg-column>Claimed at</clr-dg-column>
<clr-dg-column>Max. Duration</clr-dg-column>
<clr-dg-column *rbac="['scheduledevents.update']"
>Delete</clr-dg-column
>Actions</clr-dg-column
>

<clr-dg-row *clrDgItems="let otac of otacs">
Expand Down Expand Up @@ -99,7 +99,14 @@ <h3 class="modal-title">
<clr-dg-cell *rbac="['scheduledevents.update']">
<button
type="button"
class="btn btn-warning-outline"
class="btn btn-sm btn-info-outline"
(click)="openEditModal(otac)"
>
Update Duration
</button>
<button
type="button"
class="btn btn-sm btn-warning-outline"
(click)="deleteOtac(otac)"
>
Delete
Expand All @@ -112,6 +119,7 @@ <h3 class="modal-title">
</div>
}
</div>

<div class="modal-footer">
@if (currentScheduledEvent) {
<button
Expand All @@ -127,3 +135,77 @@ <h3 class="modal-title">
</button>
</div>
</clr-modal>

<!-- Update Duration Modal - OUTSIDE main modal to avoid nesting issues -->
<clr-modal [(clrModalOpen)]="editModalOpen" [clrModalClosable]="false">
<h3 class="modal-title">Update OTAC Maximum Duration</h3>
<div class="modal-body">
@if (editingOtacId) {
<p>
Updating maximum duration for OTAC: <strong>{{ editingOtacId }}</strong>
</p>
}
<form clrForm [formGroup]="editDurationForm">
<clr-input-container>
<label class="form-label">New Maximum Duration</label>
<input
formControlName="duration"
type="text"
clrInput
placeholder="e.g. 1d, 24h, 60m"
title="Duration"
/>
<clr-control-error
>Input must be a valid duration consisting of a number followed by
(d,h,m).</clr-control-error
>
</clr-input-container>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="cancelEdit()">
Cancel
</button>
<button
type="button"
class="btn btn-primary"
[disabled]="!editDurationForm.valid"
(click)="updateOtacDuration()"
>
Update
</button>
</div>
</clr-modal>

<!-- Delete Confirmation Modal - OUTSIDE main modal to avoid nesting issues -->
<clr-modal
[(clrModalOpen)]="deleteConfirmationModalOpen"
[clrModalClosable]="false"
>
<h3 class="modal-title">Delete OTAC</h3>
<div class="modal-body">
@if (deleteConfirmationOtac) {
<p>
Are you sure you want to delete the OTAC
<strong>{{ deleteConfirmationOtac.name }}</strong
>?
</p>
@if (deleteConfirmationOtac.user) {
<p class="alert alert-warning">
<cds-icon shape="warning-standard"></cds-icon>
This OTAC has been claimed by
<strong>{{ deleteConfirmationOtac.userEmail }}</strong
>. Deleting it will remove their access.
</p>
}
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="cancelDelete()">
Cancel
</button>
<button type="button" class="btn btn-danger" (click)="confirmDeleteOtac()">
Delete
</button>
</div>
</clr-modal>
93 changes: 89 additions & 4 deletions src/app/event/otacmanagement/otacmanagement.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ export class OTACManagementComponent implements OnInit, OnDestroy {

descSort = ClrDatagridSortOrder.DESC;

// Edit Modal State
editModalOpen = false;
editingOtacId: string | null = null;
editDurationForm: FormGroup<{
duration: FormControl<string>;
}> = new FormGroup({
duration: new FormControl<string>('', {
validators: [Validators.pattern(/^(\d+[dhm]){1}$/)],
nonNullable: true,
}),
});

// Delete Confirmation State
deleteConfirmationOtac: iOTAC | null = null;
deleteConfirmationModalOpen = false;

constructor(
private seService: ScheduledeventService,
private userService: UserService,
Expand Down Expand Up @@ -131,18 +147,82 @@ export class OTACManagementComponent implements OnInit, OnDestroy {
}

deleteOtac(otac: iOTAC) {
if (!this.currentScheduledEvent) {
this.deleteConfirmationOtac = otac;
this.deleteConfirmationModalOpen = true;
}

confirmDeleteOtac() {
if (!this.deleteConfirmationOtac || !this.currentScheduledEvent) {
return;
}
const otacToDelete = this.deleteConfirmationOtac;
this.seService
.deleteOtac(this.currentScheduledEvent.id, otac.name)
.deleteOtac(this.currentScheduledEvent.id, otacToDelete.name)
.subscribe((res: ServerResponse) => {
if (res.status == 200) {
this.otacs.splice(this.otacs.indexOf(otac), 1);
this.otacs.splice(this.otacs.indexOf(otacToDelete), 1);
this.deleteConfirmationModalOpen = false;
this.deleteConfirmationOtac = null;
}
});
}

cancelDelete() {
this.deleteConfirmationModalOpen = false;
this.deleteConfirmationOtac = null;
}

openEditModal(otac: iOTAC) {
this.editingOtacId = otac.name;
this.editDurationForm.controls.duration.setValue(otac.max_duration || '');
this.editModalOpen = true;
}

updateOtacDuration() {
if (
!this.editingOtacId ||
!this.currentScheduledEvent ||
!this.editDurationForm.valid
) {
return;
}
const newDuration = this.editDurationForm.controls.duration.value;
this.seService
.updateOtac(
this.currentScheduledEvent.id,
this.editingOtacId,
newDuration,
)
.subscribe({
next: (updatedOtac: OTAC) => {
const index = this.otacs.findIndex(
(o) => o.name === this.editingOtacId,
);
if (index >= 0) {
this.otacs[index] = this.addUserinformation(updatedOtac);
}
this.editModalOpen = false;
this.editingOtacId = null;
this.editDurationForm.reset();
this.alertText = 'OTAC duration updated successfully';
this.alertType = ClrAlertType.Success;
this.toggleAlertState();
},
error: (error) => {
console.error('Error updating OTAC duration:', error);
this.alertText = 'Failed to update OTAC duration';
this.alertType = ClrAlertType.Danger;
this.toggleAlertState();
},
});
}

cancelEdit() {
this.editModalOpen = false;
this.editingOtacId = null;
this.editDurationForm.reset();
}

getOverallInfo() {
const usedOtacs: number = this.otacs.filter(
(otac) => otac.user != '',
Expand All @@ -158,8 +238,13 @@ export class OTACManagementComponent implements OnInit, OnDestroy {
if (otacUser) {
userCSVpart = `${otacUser}, ${this.getUsername(otacUser)}`;
}
const redeemedStr = otac.redeemed_timestamp
? otac.redeemed_timestamp instanceof Date
? otac.redeemed_timestamp.toISOString()
: otac.redeemed_timestamp
: '';
otacCSV = otacCSV.concat(
`${otac.name}, ${userCSVpart || ''}, ${otac.redeemed_timestamp?.toISOString() || ''}, ${otac.max_duration || ''}, ${otac.status || ''}\n`,
`${otac.name}, ${userCSVpart || ''}, ${redeemedStr}, ${otac.max_duration || ''}, ${otac.status || ''}\n`,
);
});
const filename = currentScheduledEvent.event_name + '_OTACs.csv';
Expand Down
Loading