Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
56 changes: 56 additions & 0 deletions web-app/admin/src/app/admin/admin-event/admin-events.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

import { CoreModule } from '../../core/core.module';
import { AdminBreadcrumbModule } from '../admin-breadcrumb/admin-breadcrumb.module';
import { EventDashboardComponent } from './dashboard/event-dashboard.component';
import { MatSelectModule } from '@angular/material/select';
import { MatOptionModule } from '@angular/material/core';
import { EventService } from 'src/app/event/event.service';
import { CreateEventDialogComponent } from './create-event/create-event.component';
import { MatTooltipModule } from '@angular/material/tooltip';

@NgModule({
declarations: [
EventDashboardComponent,
CreateEventDialogComponent
],
imports: [
CommonModule,
FormsModule,
CoreModule,
ReactiveFormsModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatCheckboxModule,
MatIconModule,
MatProgressSpinnerModule,
AdminBreadcrumbModule,
MatSelectModule,
MatOptionModule,
MatTooltipModule,
],
exports: [
EventDashboardComponent
],
providers: [
EventService
]
})
export class AdminEventsModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<div class="dialog-modal">
<div class="dialog-header">
<h2 class="dialog-title">Create a New Event</h2>
</div>

<div class="dialog-content">
<div *ngIf="errorMessage" class="error-alert">
<i class="fa fa-exclamation-triangle"></i>
<span>{{ errorMessage }}</span>
</div>

<form [formGroup]="eventForm" class="event-form">
<div class="form-field">
<label class="field-label">Event Name</label>
<input
type="text"
class="form-input"
formControlName="name"
placeholder="Enter event name"
/>
<div
*ngIf="
eventForm.get('name').invalid &&
(eventForm.get('name').touched || eventForm.get('name').dirty)
"
class="field-error"
>
<i class="fa fa-exclamation-circle"></i>
Name is required
</div>
</div>

<div class="form-field">
<label class="field-label">Description</label>
<textarea
class="form-input"
formControlName="description"
rows="3"
placeholder="Enter event description (optional)"
></textarea>
</div>
</form>
</div>

<div class="dialog-actions">
<button class="action-button" (click)="cancel()">Cancel</button>
<button
class="action-button btn-primary"
(click)="save()"
[disabled]="eventForm.invalid"
>
<i class="fa fa-save"></i>
Create Event
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
.error-alert {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
margin-bottom: 1.5rem;
color: #dc2626;
font-size: 0.875rem;

i {
flex-shrink: 0;
font-size: 1rem;
}
}

.event-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;

.field-label {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
text-transform: uppercase;
letter-spacing: 0.05em;
}

.form-input {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 0.875rem;
background-color: #f9fafb;
transition: all 0.2s ease;
resize: vertical;

&:focus {
outline: none;
border-color: #3b82f6;
background-color: white;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

&::placeholder {
color: #9ca3af;
}

&:disabled {
background-color: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
}

.field-error {
display: flex;
align-items: center;
gap: 0.25rem;
color: #dc2626;
font-size: 0.75rem;
margin-top: 0.25rem;

i {
font-size: 0.75rem;
}
}
}

::ng-deep .mat-dialog-container {
padding: 0;
border-radius: 12px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important;
}

::ng-deep .mat-dialog-actions {
padding: 0;
margin: 0;
}

::ng-deep .mat-dialog-content {
padding: 0;
margin: 0;
}

::ng-deep .mat-dialog-title {
padding: 0;
margin: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EventsService } from '../events.service';
import { Event } from 'src/app/filter/filter.types';

/**
* Dialog component for creating new events.
* Provides a form interface with validation for event name (required) and description (optional).
*/
@Component({
selector: 'mage-admin-event-create',
templateUrl: './create-event.component.html',
styleUrls: ['./create-event.component.scss']
})
export class CreateEventDialogComponent {
eventForm: FormGroup;
errorMessage: string = '';

constructor(
public dialogRef: MatDialogRef<CreateEventDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { event: Partial<Event> },
private fb: FormBuilder,
private eventsService: EventsService
) {
this.eventForm = this.fb.group({
name: [data.event?.name || '', [Validators.required]],
description: [data.event?.description || '']
});
}

/**
* Handles form submission for creating a new event.
* Validates the form, creates the event via the events service, and closes the dialog on success.
*/
save(): void {
if (this.eventForm.invalid) {
this.errorMessage = 'Please fill in all required fields.';
return;
}

this.errorMessage = '';
const eventData = this.eventForm.value;
this.eventsService.createEvent(eventData).subscribe({
next: (newEvent) => {
this.dialogRef.close(newEvent);
},
error: (err) => {
if (err.status === 409) {
this.errorMessage = err.error;
}
else {
this.errorMessage = 'Failed to create event. Please try again.';
}
}
});
}

/**
* Closes the dialog without saving any data or making any changes.
*/
cancel(): void {
this.dialogRef.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<admin-breadcrumb [breadcrumbs]="breadcrumbs"></admin-breadcrumb>

<div class="container bottom-gap-l">
<div class="page-header">
<h2 class="page-title">Events</h2>
</div>

<div class="table-wrapper mat-elevation-z2">
<!-- Top Bar -->
<div
class="integrated-navbar d-flex align-items-center justify-content-between"
>
<div class="filters d-flex align-items-center">
<mage-card-navbar
[isSearchable]="true"
searchPlaceholder="Search Events..."
(searchTermChanged)="onSearchTermChanged($event)"
(searchCleared)="onSearchCleared()"
></mage-card-navbar>

<mat-form-field appearance="outline" class="ml-3 status-filter">
<mat-label>Status</mat-label>
<mat-select
[(ngModel)]="eventStatusFilter"
(selectionChange)="onStatusFilterChange($event.value)"
>
<mat-option value="all">All</mat-option>
<mat-option value="active">Active</mat-option>
<mat-option value="complete">Complete</mat-option>
</mat-select>
</mat-form-field>
</div>

<button
*ngIf="hasEventCreatePermission"
mat-flat-button
color="primary"
(click)="createEvent()"
>
New Event
</button>
</div>

<!-- Event Table -->
<div class="table-scroll" *ngIf="filteredEvents?.length; else noEvents">
<table
mat-table
[dataSource]="filteredEvents"
class="mat-table-even-columns mat-table"
>
<ng-container matColumnDef="event">
<th mat-header-cell *matHeaderCellDef>Event</th>
<td mat-cell *matCellDef="let event">
<div class="d-flex align-items-center pointer">
<i class="icon fa fa-calendar mr-2" aria-hidden="true"></i>
<div class="w-100">
<div class="strong">{{ event.name }}</div>
<div
[matTooltip]="event.description"
[matTooltipDisabled]="
!event.description || event.description.length <= numChars
"
matTooltipClass="mat-tooltip-event-desc"
class="w-90"
>
<p class="muted ellipsis">{{ event.description }}</p>
</div>
</div>
</div>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
class="pointer"
(click)="gotoEvent(row)"
></tr>
</table>
</div>

<!-- Empty State -->
<ng-template #noEvents>
<div class="text-center p-4 muted">No events found.</div>
</ng-template>

<!-- Pagination -->
<div class="pagination">
<mat-paginator
[length]="totalEvents"
[pageSize]="searchOptions.page_size"
[pageIndex]="searchOptions.page"
[pageSizeOptions]="pageSizeOptions"
showFirstLastButtons
(page)="onPageChange($event)"
>
</mat-paginator>
</div>
</div>
</div>
Loading