Skip to content

003 event admin UI - #2

Merged
antonrademaker merged 52 commits into
mainfrom
003-event-admin-ui
Jan 13, 2026
Merged

003 event admin UI#2
antonrademaker merged 52 commits into
mainfrom
003-event-admin-ui

Conversation

@antonrademaker

@antonrademaker antonrademaker commented Jan 5, 2026

Copy link
Copy Markdown
Owner

I have also reinforced the log sanitization in SearchEventsAsync to explicitly replace \r and \n instead of relying on Environment.NewLine, ensuring stricter security compliance across all logging points.

- Created migration to add TimeSlot and related entities (event_series, time_slots, tracks) to the database schema.
- Implemented TimeSlot entity with properties such as Id, EventId, Name, StartTime, EndTime, Type, IsEventLevel, CreatedAt, and UpdatedAt.
- Added IScheduleService interface with method to copy day schedule for events.
- Developed ScheduleService class to handle the logic for copying time slots from one date to another within the same event.
- Removed obsolete columns from events table and updated foreign key relationships.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 152 out of 166 changed files in this pull request and generated 5 comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Injectable({
providedIn: 'root',
})
export class Event {

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name 'Event' conflicts with the built-in DOM Event class and the Event interface defined in event.model.ts. Rename this service class to avoid confusion (e.g., 'EventService' or remove this file if it's not needed).

Suggested change
export class Event {
export class EventService {

Copilot uses AI. Check for mistakes.
Comment thread src/PrivateApp/src/app/event-management/components/event-list/event-list.spec.ts Outdated
Comment on lines +21 to +24
Published = 1,
Cancelled = 2,
Completed = 3,
Postponed = 4

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventStatus enum values don't match the backend C# enum (which includes Active and Archived). This mismatch will cause runtime errors. Ensure the TypeScript enum matches the backend definition exactly.

Suggested change
Published = 1,
Cancelled = 2,
Completed = 3,
Postponed = 4
Active = 1,
Archived = 2,
Cancelled = 3,
Completed = 4,
Postponed = 5

Copilot uses AI. Check for mistakes.
Comment thread src/Shared/EventManagement/Entities/Session.cs

@antonrademaker antonrademaker left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed the latest review comments:

  1. Duplicate CSS: Removed duplicate rules in schedule-grid.component.scss.
  2. Unused File: Deleted src/PrivateApp/src/app/event-management/services/event.ts.
  3. Spec Import: Fixed EventList -> EventListComponent import/usage in event-list.spec.ts.
  4. Enum Sync: Aligned EventStatus in event.model.ts with the C# backend (Active, Archived added; Postponed removed).
  5. Log Injection: Sanitized the Slug parameter in EventService.cs logging.

Regarding Session.cs and [Table(..., Schema = ...)]: The property Schema in C#'s TableAttribute is PascalCase. Changing it to lowercase would cause a compilation error. I have left it as is.

Copilot AI review requested due to automatic review settings January 13, 2026 15:23

@antonrademaker antonrademaker left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also reinforced the log sanitization in SearchEventsAsync to explicitly replace \r and \n instead of relying on Environment.NewLine, ensuring stricter security compliance across all logging points.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 151 out of 165 changed files in this pull request and generated 3 comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

case EventStatus.Published: return 'Published';
case EventStatus.Cancelled: return 'Cancelled';
case EventStatus.Completed: return 'Completed';
case EventStatus.Postponed: return 'Postponed';

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EventStatus.Postponed case is handled but this status was removed from the EventStatus enum (as stated in the PR description). This will cause a compilation error since Postponed is no longer a valid enum value.

Suggested change
case EventStatus.Postponed: return 'Postponed';

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +51
'bg-blue-200 text-blue-800': event.status === EventStatus.Completed,
'bg-yellow-200 text-yellow-800': event.status === EventStatus.Postponed

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference to EventStatus.Postponed should be removed as this status no longer exists in the EventStatus enum.

Suggested change
'bg-blue-200 text-blue-800': event.status === EventStatus.Completed,
'bg-yellow-200 text-yellow-800': event.status === EventStatus.Postponed
'bg-blue-200 text-blue-800': event.status === EventStatus.Completed

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +15
import { TestBed } from '@angular/core/testing';

import { Event } from './event';

describe('Event', () => {
let service: Event;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(Event);
});

it('should be created', () => {
expect(service).toBeTruthy();
});

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test imports from './event' which doesn't exist. The description mentions deleting 'src/PrivateApp/src/app/event-management/services/event.ts', but this test file still references it. Should import from '../models/event.model' instead or the test file should be deleted.

Suggested change
import { TestBed } from '@angular/core/testing';
import { Event } from './event';
describe('Event', () => {
let service: Event;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(Event);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
import { Event } from '../models/event.model';
describe('Event', () => {
it('should create an instance', () => {
expect(new Event()).toBeTruthy();
});

Copilot uses AI. Check for mistakes.

@antonrademaker antonrademaker left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed recent review comments:

  • Removed EventStatus.Postponed usage in event-list.ts and event-list.html.
  • Deleted event.spec.ts as the corresponding service was deleted.

The build checks should pass now.

Copilot AI review requested due to automatic review settings January 13, 2026 15:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 151 out of 165 changed files in this pull request and generated 4 comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +19
<div *cdkDragPreview class="session-preview">
{{ session.title }}
</div>

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The *cdkDragPreview directive is being used, but Angular CDK recommends using the structural directive syntax <ng-template cdkDragPreview> instead of *cdkDragPreview on a regular div. The current approach may not work as expected.

Suggested change
<div *cdkDragPreview class="session-preview">
{{ session.title }}
</div>
<ng-template cdkDragPreview>
<div class="session-preview">
{{ session.title }}
</div>
</ng-template>

Copilot uses AI. Check for mistakes.

forkJoin({
slots: this.timeSlotService.getTimeSlots(this.eventId),
tracks: this.trackService.getTracks(this.eventId),

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The eventId is checked for null before calling loadData, but TypeScript's strict null checking means this.eventId could still be null when passed to the services. Use the non-null assertion operator (this.eventId!) or proper null handling.

Copilot uses AI. Check for mistakes.
private snackBar: MatSnackBar
) { }

handleDrop(event: CdkDragDrop<Session[] | any>, eventId: string, trackId?: string, timeSlotId?: string): void {

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any type for the generic parameter defeats the purpose of TypeScript's type safety. Consider defining a proper type for the drop container data.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
var firstAssignment = await _context.Set<SessionAssignment>()
.FirstOrDefaultAsync(a => a.SessionId == firstSessionId && a.Session.EventId == eventId, cancellationToken);

var secondAssignment = await _context.Set<SessionAssignment>()
.FirstOrDefaultAsync(a => a.SessionId == secondSessionId && a.Session.EventId == eventId, cancellationToken);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This performs two separate database queries for the session assignments. Consider using a single query with a Where clause that filters both sessions to reduce database round trips.

Suggested change
var firstAssignment = await _context.Set<SessionAssignment>()
.FirstOrDefaultAsync(a => a.SessionId == firstSessionId && a.Session.EventId == eventId, cancellationToken);
var secondAssignment = await _context.Set<SessionAssignment>()
.FirstOrDefaultAsync(a => a.SessionId == secondSessionId && a.Session.EventId == eventId, cancellationToken);
var assignments = await _context.Set<SessionAssignment>()
.Where(a =>
a.Session.EventId == eventId &&
(a.SessionId == firstSessionId || a.SessionId == secondSessionId))
.ToListAsync(cancellationToken);
var firstAssignment = assignments.FirstOrDefault(a => a.SessionId == firstSessionId);
var secondAssignment = assignments.FirstOrDefault(a => a.SessionId == secondSessionId);

Copilot uses AI. Check for mistakes.

@antonrademaker antonrademaker left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed the remaining 4 comments:

  1. Updated session-sidebar.component.html to use <ng-template cdkDragPreview> structural directive.
  2. Refactored EventSessionService.cs to use a single database query for session assignments.
  3. Removed any from handleDrop in schedule-drag-drop.service.ts and used CdkDragDrop<Session[]>.
  4. Added safe null handling for eventId in schedule-grid.component.ts.

All changes verified with local build and lint.

Copilot AI review requested due to automatic review settings January 13, 2026 15:52

@antonrademaker antonrademaker left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further refinement: Removed implicit any usage in private methods of schedule-drag-drop.service.ts to ensure full type safety. Lint checks now clean for this service.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 151 out of 165 changed files in this pull request and generated 8 comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

loadUnassignedSessions(): void {
if (this.eventId) {
this.sessionService.getSessions(this.eventId).subscribe(sessions => {
this.unassignedSessions = sessions.filter(s => s.status === SessionStatus.Draft);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filtering logic seems inconsistent. Sessions with status === SessionStatus.Draft may not be the same as 'unassigned' sessions. Consider checking s.assignments.length === 0 instead or in addition to the status check.

Suggested change
this.unassignedSessions = sessions.filter(s => s.status === SessionStatus.Draft);
this.unassignedSessions = sessions.filter(
s => s.status === SessionStatus.Draft && (!s.assignments || s.assignments.length === 0)
);

Copilot uses AI. Check for mistakes.
// 2. Remove session2 from target (it should be at index 0 or we find it)
const session2Index = targetContainer.data.indexOf(session2);
if (session2Index > -1) {
targetContainer.data.splice(session2Index, 1);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The swap logic assumes session2 is in the target container, but doesn't handle the case where it's not found properly. If indexOf returns -1, the splice will not execute, potentially leaving the arrays in an inconsistent state. Add an else clause to handle this error case.

Suggested change
targetContainer.data.splice(session2Index, 1);
targetContainer.data.splice(session2Index, 1);
} else {
// session2 was not found in the target container; revert and abort to avoid inconsistent state
sourceContainer.data.splice(event.previousIndex, 0, session1);
console.error(
'Failed to swap sessions: target session not found in target container data.',
{ eventId, session1Id: session1.id, session2Id: session2.id },
);
this.snackBar.open('Failed to swap sessions due to inconsistent state', 'Close', {
duration: 3000,
});
return;

Copilot uses AI. Check for mistakes.
return;
}

var timeDifference = targetDate.Date - sourceDate.Date;

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CopyDayScheduleAsync method lacks test coverage. Add unit tests to verify correct copying of time slots, especially edge cases like when sourceSlots is empty or when timeDifference calculation crosses daylight saving time boundaries.

Copilot uses AI. Check for mistakes.

public static class EventStatusMachine
{
public static Result CanTransition(EventStatus current, EventStatus next)

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state machine transitions are not covered by tests. Add unit tests to verify all valid and invalid state transitions to ensure business rules are enforced correctly.

Copilot uses AI. Check for mistakes.
}

deleteTrack(trackId: string): void {
if (confirm('Are you sure you want to delete this track?')) {

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using native browser confirm() is not recommended in Angular applications. Consider using a Material Dialog for consistency with the rest of the UI and better accessibility.

Copilot uses AI. Check for mistakes.

var shouldQueue = eventDetails.MaxCapacity.HasValue &&
currentCount >= eventDetails.MaxCapacity.Value;
var shouldQueue = false; // eventDetails.MaxCapacity.HasValue && currentCount >= eventDetails.MaxCapacity.Value;

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code should be removed rather than left in place. If queue processing is intentionally disabled, consider using a feature flag or configuration setting instead of hardcoded values.

Suggested change
var shouldQueue = false; // eventDetails.MaxCapacity.HasValue && currentCount >= eventDetails.MaxCapacity.Value;
var shouldQueue = false;

Copilot uses AI. Check for mistakes.

var processCount = Math.Min(availableSpots, maxProcessCount ?? availableSpots);
*/
var processCount = 0; // Disable queue processing for now

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code should be removed rather than left in place. If queue processing is intentionally disabled, consider using a feature flag or configuration setting instead of hardcoded values.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +108
// TODO: Open dialog to add time slot
console.log('Add time slot');
}

editTimeSlot(slot: TimeSlot): void {
// TODO: Open dialog to edit time slot
console.log('Edit time slot', slot);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comments and console.log statements should be removed or implemented before merging. These indicate incomplete functionality.

Suggested change
// TODO: Open dialog to add time slot
console.log('Add time slot');
}
editTimeSlot(slot: TimeSlot): void {
// TODO: Open dialog to edit time slot
console.log('Edit time slot', slot);
// Placeholder implementation: notify user that adding a time slot is not yet available.
window.alert('Adding a time slot is not yet available in this version.');
}
editTimeSlot(slot: TimeSlot): void {
// Placeholder implementation: notify user that editing a time slot is not yet available.
window.alert(`Editing the time slot "${slot.name}" is not yet available in this version.`);

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 13, 2026 16:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 171 out of 186 changed files in this pull request and generated 3 comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +34
var searchTextSafe = (searchRequest.SearchText ?? string.Empty).Replace("\r", "").Replace("\n", "");
_logger.LogInformation("Searching events with criteria: {SearchText}", searchTextSafe);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log sanitization is correctly implemented by removing carriage return and line feed characters before logging. This prevents log injection attacks where malicious input could create fake log entries.

Copilot uses AI. Check for mistakes.
public async Task<Event?> GetEventBySlugAsync(string slug, bool includeDetails = true)
{
_logger.LogInformation("Getting event by slug: {Slug}", slug);
_logger.LogInformation("Getting event by slug: {Slug}", slug.Replace("\r", "").Replace("\n", ""));

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log sanitization is properly applied to the slug parameter to prevent log injection vulnerabilities.

Copilot uses AI. Check for mistakes.
public async Task<Event> CreateEventAsync(CreateEventRequest eventData, Guid createdByUserId)
{
_logger.LogInformation("Creating new event: {Title}", eventData.Title);
_logger.LogInformation("Creating new event: {Title}", eventData.Title.Replace("\r", "").Replace("\n", ""));

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log sanitization correctly applied to event title before logging.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 13, 2026 16:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 172 out of 187 changed files in this pull request and generated no new comments.

Files not reviewed (5)
  • src/PrivateApp/package-lock.json: Language not supported
  • src/PublicApp/package-lock.json: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260104185946_AddFoundationalEntities.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105113600_AddTimeSlot.Designer.cs: Language not supported
  • src/Shared/EventManagement/Data/Migrations/20260105115202_AddSessionAndAssignment.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@antonrademaker
antonrademaker merged commit 39aec21 into main Jan 13, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants