-
Notifications
You must be signed in to change notification settings - Fork 3
feat: stats csv export #922
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds a CSV export for admin statistics and exposes it via the dashboard.
- New route and controller action to stream a CSV with global, per-event, per-group, per-slot, and per-course stats.
- Admin dashboard UI button to trigger the CSV download.
- CSV headers and rows assembled server-side and streamed to the client.
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
File | Description |
---|---|
routes/web.php | Adds a GET route for downloading statistics, behind the view statistics ability. |
resources/js/pages/Dashboard/Admin/Index.vue | Adds a button that opens the download URL in a new tab. |
app/Http/Controllers/DashboardAdminController.php | Implements streamed CSV export with computed statistics across events, groups, slots, and courses. |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
$array = array_map('utf8_decode', array_values($row)); | ||
fputcsv($file, $array, ';'); |
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using utf8_decode here converts strings from UTF-8 to ISO-8859-1, which contradicts the declared UTF-8 charset and will corrupt non-ASCII characters. Remove utf8_decode and write UTF-8 directly; if Excel support is needed, write a UTF-8 BOM once before the first fputcsv (e.g., fputs($file, "\xEF\xBB\xBF")). Apply the same change to the later writes in this method.
Copilot uses AI. Check for mistakes.
// add basic stats | ||
$row['event_or_group'] = $event->name . ' - ' . $group->name; |
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The logic to build $row is duplicated across event, group, and slot sections, making future changes error-prone. Extract a helper to assemble a stats row (given a registrations source and labels) to centralize the counting and formatting, then reuse it for events, groups, and slots.
Copilot uses AI. Check for mistakes.
$row = []; | ||
|
||
// add basic stats | ||
$row['event_or_group'] = $event->name . ' - ' . $slot->name; | ||
$row['registered_users'] = $slot->registrations()->count(); | ||
$row['present_users'] = $slot->registrations()->where('is_present', true)->count(); | ||
$row['not_present_users'] = $slot->registrations()->where('is_present', false)->count(); | ||
$row['present_percentage'] = ($row['registered_users'] > 0 ? round(($row['present_users'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | ||
|
||
// add requirements stats if event has requirements | ||
if ($event->has_requirements) { | ||
$row['fulfills_requirements'] = $slot->registrations()->where('fulfils_requirements', true)->count(); | ||
$row['not_fulfills_requirements'] = $slot->registrations()->where('fulfils_requirements', false)->count(); | ||
$row['fulfills_requirements_percentage'] = ($row['registered_users'] > 0 ? round(($row['fulfills_requirements'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | ||
} else { | ||
$row['fulfills_requirements'] = 'N/A'; | ||
$row['not_fulfills_requirements'] = 'N/A'; | ||
$row['fulfills_requirements_percentage'] = 'N/A'; | ||
} | ||
|
||
// add alcohol stats if event consider alcohol | ||
if ($event->consider_alcohol) { | ||
$row['drinks_alcohol'] = $slot->registrations()->where('drinks_alcohol', true)->count(); | ||
$row['drinks_no_alcohol'] = $slot->registrations()->where('drinks_alcohol', false)->count(); | ||
$row['drinks_alcohol_percentage'] = ($row['registered_users'] > 0 ? round(($row['drinks_alcohol'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | ||
} else { | ||
$row['drinks_alcohol'] = 'N/A'; | ||
$row['drinks_no_alcohol'] = 'N/A'; | ||
$row['drinks_alcohol_percentage'] = 'N/A'; | ||
} | ||
|
||
// add course stats | ||
foreach ($courses as $course) { | ||
$row['course_' . $course->abbreviation] = $slot->registrations()->whereHas('user', function ($query) use ($course) { | ||
$query->where('course_id', $course->id); | ||
})->count(); | ||
} | ||
|
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The logic to build $row is duplicated across event, group, and slot sections, making future changes error-prone. Extract a helper to assemble a stats row (given a registrations source and labels) to centralize the counting and formatting, then reuse it for events, groups, and slots.
$row = []; | |
// add basic stats | |
$row['event_or_group'] = $event->name . ' - ' . $slot->name; | |
$row['registered_users'] = $slot->registrations()->count(); | |
$row['present_users'] = $slot->registrations()->where('is_present', true)->count(); | |
$row['not_present_users'] = $slot->registrations()->where('is_present', false)->count(); | |
$row['present_percentage'] = ($row['registered_users'] > 0 ? round(($row['present_users'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | |
// add requirements stats if event has requirements | |
if ($event->has_requirements) { | |
$row['fulfills_requirements'] = $slot->registrations()->where('fulfils_requirements', true)->count(); | |
$row['not_fulfills_requirements'] = $slot->registrations()->where('fulfils_requirements', false)->count(); | |
$row['fulfills_requirements_percentage'] = ($row['registered_users'] > 0 ? round(($row['fulfills_requirements'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | |
} else { | |
$row['fulfills_requirements'] = 'N/A'; | |
$row['not_fulfills_requirements'] = 'N/A'; | |
$row['fulfills_requirements_percentage'] = 'N/A'; | |
} | |
// add alcohol stats if event consider alcohol | |
if ($event->consider_alcohol) { | |
$row['drinks_alcohol'] = $slot->registrations()->where('drinks_alcohol', true)->count(); | |
$row['drinks_no_alcohol'] = $slot->registrations()->where('drinks_alcohol', false)->count(); | |
$row['drinks_alcohol_percentage'] = ($row['registered_users'] > 0 ? round(($row['drinks_alcohol'] / $row['registered_users']) * 100, 2) . '%' : '0%'); | |
} else { | |
$row['drinks_alcohol'] = 'N/A'; | |
$row['drinks_no_alcohol'] = 'N/A'; | |
$row['drinks_alcohol_percentage'] = 'N/A'; | |
} | |
// add course stats | |
foreach ($courses as $course) { | |
$row['course_' . $course->abbreviation] = $slot->registrations()->whereHas('user', function ($query) use ($course) { | |
$query->where('course_id', $course->id); | |
})->count(); | |
} | |
$row = $this->buildStatsRow( | |
$slot->registrations(), | |
$event->name . ' - ' . $slot->name, | |
$event, | |
$courses | |
); |
Copilot uses AI. Check for mistakes.
0ecd31e
to
38db4d6
Compare
closes #921