Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a456761
Overhaul admin CRUD UI (WIP)
Gawdl3y Oct 23, 2025
e2e4754
WIP: Add new event creation UI
Gawdl3y Oct 23, 2025
cf9975a
Apply formatting
Gawdl3y May 4, 2026
04de752
Update frontend dependencies
Gawdl3y May 8, 2026
a844ce7
Require unique names for events
Gawdl3y May 8, 2026
218d869
Implement event editing & improve creation
Gawdl3y May 8, 2026
56081c6
Use new event CRUD in navigation
Gawdl3y May 8, 2026
ccdf8ce
Remove legacy event CRUD code
Gawdl3y May 8, 2026
e2d74ab
Remove Tempus Dominus dependency
Gawdl3y May 8, 2026
8852f65
Add help buttons to reward/bonus CRUD panels & extract into own compo…
Gawdl3y May 9, 2026
3ed5f1f
Add loading property to EventDataPage default slot
Gawdl3y May 9, 2026
e756041
Add switch field type & min/max lengths to CrudTable
Gawdl3y May 10, 2026
d42325d
Make departments event-specific & add new CRUD
Gawdl3y May 10, 2026
caf9db4
Adapt & improve database seeding for event-specific departments
Gawdl3y May 10, 2026
d1e2f71
Remove legacy departments code
Gawdl3y May 10, 2026
e11e74d
Remove more unused legacy code
Gawdl3y May 10, 2026
8e50d4d
Add required to CRUD fields
Gawdl3y May 10, 2026
7c365a9
Fix seeder not giving departments to time bonuses
Gawdl3y May 10, 2026
2f702cf
Default CrudTable readonly to false
Gawdl3y May 10, 2026
5b329a5
Add default display for multi-select fields to CrudTable
Gawdl3y May 10, 2026
124557b
Add readonly to EventManagementPanel
Gawdl3y May 10, 2026
385d7a1
Small formatting tweak
Gawdl3y May 10, 2026
7ee51a4
Fix adding new time bonus
Gawdl3y May 11, 2026
ca635dc
Require time bonus departments to be for the bonus' event
Gawdl3y May 11, 2026
02ab1f6
Limit department selection to ones for the event
Gawdl3y May 11, 2026
f5aa602
Require reward & attendee log names to be unique
Gawdl3y May 11, 2026
faab60d
Refactor & simplify TrackerController & routes into VolunteerController
Gawdl3y May 12, 2026
0defc6a
Improve navbar active matching
Gawdl3y May 12, 2026
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
4 changes: 2 additions & 2 deletions app/Http/Controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ public function postQuickcode(QuickCodeRequest $request): JsonResponse|RedirectR

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->route('tracker.index');
: redirect()->route('volunteer.index');
}

/**
* Display the banned notice
*/
public function getBanned(): InertiaResponse|RedirectResponse {
if (!Auth::user()?->isBanned()) return redirect()->route('tracker.index');
if (!Auth::user()?->isBanned()) return redirect()->route('volunteer.index');
return Inertia::render('Suspended');
}

Expand Down
34 changes: 24 additions & 10 deletions app/Http/Controllers/DepartmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,33 @@
use App\Http\Requests\DepartmentStoreRequest;
use App\Http\Requests\DepartmentUpdateRequest;
use App\Models\Department;
use App\Models\Event;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class DepartmentController extends Controller {
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse {
$this->authorize('viewAny', Department::class);
return response()->json(['departments' => Department::all()]);
public function index(Request $request, Event $event): JsonResponse|RedirectResponse {
$this->authorize('viewForEvent', [Department::class, $event]);
return $request->expectsJson()
? response()->json(['departments' => $event->departments])
: redirect()->route('events.show', $event);
}

/**
* Store a newly created resource in storage.
*/
public function store(DepartmentStoreRequest $request): JsonResponse {
public function store(DepartmentStoreRequest $request, Event $event): JsonResponse|RedirectResponse {
$department = new Department($request->validated());
$department->event_id = $event->id;
$department->save();
session()->flash('success', 'Department created.');
return response()->json(['department' => $department]);

return $request->expectsJson()
? response()->json(['department' => $department])
: redirect()->back()->withSuccess("Created department {$department->name}.");
}

/**
Expand All @@ -37,21 +45,27 @@ public function show(Department $department): JsonResponse {
/**
* Update the specified resource in storage.
*/
public function update(DepartmentUpdateRequest $request, Department $department): JsonResponse {
public function update(DepartmentUpdateRequest $request, Department $department): JsonResponse|RedirectResponse {
$department->update($request->validated());
return response()->json(['department' => $department]);

return $request->expectsJson()
? response()->json(['department' => $department])
: redirect()->back()->withSuccess("Updated department {$department->name}.");
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Department $department): JsonResponse {
public function destroy(Request $request, Department $department): JsonResponse|RedirectResponse {
$this->authorize('delete', $department);

// TODO: Once determination is made on what to do with soft-deletables, we may want to ensure relations get
// deleted or soft-deleted along with the parent. At the moment, we just try to gracefully handle the parent,
// Department in this case, being soft-deleted.
$department->delete();
return response()->json(null, 205);

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->back()->withSuccess("Deleted department {$department->name}.");
}
}
71 changes: 60 additions & 11 deletions app/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,103 @@

use App\Http\Requests\EventStoreRequest;
use App\Http\Requests\EventUpdateRequest;
use App\Models\Department;
use App\Models\Event;
use App\Models\Reward;
use App\Models\Setting;
use App\Models\TimeBonus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;

class EventController extends Controller {
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse {
public function index(Request $request): JsonResponse|InertiaResponse|RedirectResponse {
$this->authorize('viewAny', Event::class);
return response()->json(['events' => Event::all()]);

if ($request->expectsJson()) return response()->json(['events' => Event::all()]);

$event = Setting::activeEvent();
if ($event) {
$request->session()->reflash();
return redirect()->route('events.show', [$event->id]);
}

return Inertia::render('EventCrud', [
'event' => null,
'events' => Event::orderBy('name')->get(),
'departments' => null,
'rewards' => null,
'bonuses' => null,
]);
}

/**
* Store a newly created resource in storage.
*/
public function store(EventStoreRequest $request): JsonResponse {
public function store(EventStoreRequest $request): JsonResponse|RedirectResponse {
$event = new Event($request->validated());
$event->save();
session()->flash('success', 'Event created.');
return response()->json(['event' => $event]);

return $request->expectsJson()
? response()->json(['event' => $event])
: redirect()->route('events.show', [$event->id])->withSuccess("Created event {$event->name}.");
}

/**
* Display the specified resource.
*/
public function show(Event $event): JsonResponse {
public function show(Request $request, Event $event): JsonResponse|InertiaResponse {
$this->authorize('view', $event);
return response()->json(['event' => $event]);
$user = $request->user();

return $request->expectsJson()
? response()->json(['event' => $event])
: Inertia::render('EventCrud', [
'event' => $event,
'events' => fn () => $user->can('viewAny', Event::class) ? Event::orderBy('name')->get() : null,
'departments' => fn () => $user->can('viewForEvent', [Department::class, $event]) ? $event->departments : null,
'rewards' => fn () => $user->can('viewForEvent', [Reward::class, $event]) ? $event->rewards : null,
'bonuses' => fn () => $user->can('viewForEvent', [TimeBonus::class, $event])
? $event->timeBonuses()
->with('departments', fn ($query) => $query->select('id'))
->get()
->map(fn ($bonus) => $bonus->toArrayWithDepartmentIds())
: null,
]);
}

/**
* Update the specified resource in storage.
*/
public function update(EventUpdateRequest $request, Event $event): JsonResponse {
public function update(EventUpdateRequest $request, Event $event): JsonResponse|RedirectResponse {
$event->update($request->validated());
return response()->json(['event' => $event]);
return $request->expectsJson()
? response()->json(['event' => $event])
: redirect()->back()->withSuccess('Renamed event.');
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Event $event): JsonResponse {
public function destroy(Request $request, Event $event): JsonResponse|RedirectResponse {
$this->authorize('delete', $event);

$isActive = $event->isActive();

// TODO: Once determination is made on what to do with soft-deletables, we may want to ensure relations get
// deleted or soft-deleted along with the parent. At the moment, we just try to gracefully handle the parent,
// Event in this case, being soft-deleted.
$event->delete();
return response()->json(null, 205);

if ($isActive) Setting::set('active-event', null);

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->route('events.index')->withSuccess("Deleted event {$event->name}.");
}
}
54 changes: 3 additions & 51 deletions app/Http/Controllers/ManagementController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace App\Http\Controllers;

use App\Models\Activity;
use App\Models\Department;
use App\Models\Event;
use App\Models\Reward;
use App\Models\Setting;
Expand Down Expand Up @@ -48,7 +47,9 @@ public function getManageIndex(?Event $event = null, ?User $user = null): Respon
'event' => $event,
'events' => fn () => Event::orderBy('name')->get(),
'rewards' => fn () => Reward::forEvent($event)->orderBy('hours')->get(),
'departments' => fn () => Department::orderBy('hidden')->orderBy('name')->get(),
'departments' => fn () => $event
? $event->departments()->orderBy('hidden')->orderBy('name')->get()
: null,
'ongoingEntries' => Inertia::defer(
fn () => TimeEntry::with(['user', 'department'])
->forEvent($event)
Expand All @@ -69,55 +70,6 @@ public function getManageIndex(?Event $event = null, ?User $user = null): Respon
]);
}

/**
* Render the departments admin page
*/
public function getAdminDepartments(): View {
return view('admin.departments', ['departments' => Department::all()]);
}

/**
* Render the events admin page
*/
public function getAdminEvents(): View {
return view('admin.events', ['events' => Event::all()]);
}

/**
* Render the rewards admin page
*/
public function getAdminRewards(?Event $event = null): View|RedirectResponse {
// Get the event and redirect to the page for the active event, if applicable
if (!$event) {
$event = Setting::activeEvent();
if ($event) return redirect()->route('admin.event.rewards', $event);
}

return view('admin.rewards', [
'event' => $event,
'events' => Event::all(),
'rewards' => $event?->rewards,
]);
}

/**
* Render the bonuses admin page
*/
public function getAdminBonuses(?Event $event = null): View|RedirectResponse {
// Get the event and redirect to the page for the active event, if applicable
if (!$event) {
$event = Setting::activeEvent();
if ($event) return redirect()->route('admin.event.bonuses', $event);
}

return view('admin.bonuses', [
'event' => $event,
'events' => Event::all(),
'departments' => Department::all()->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE),
'bonuses' => $event?->timeBonuses()?->with('departments')?->get(),
]);
}

/**
* Render the reports list admin page
*/
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/NotificationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ public function postAcknowledge(Request $request): JsonResponse|RedirectResponse
$user->unreadNotifications()->update(['read_at' => now()]);
return $request->expectsJson()
? response()->json(null, 205)
: redirect()->route('tracker.index');
: redirect()->route('volunteer.index');
}
}
33 changes: 23 additions & 10 deletions app/Http/Controllers/RewardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,31 @@
use App\Models\Event;
use App\Models\Reward;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class RewardController extends Controller {
/**
* Display a listing of the resource.
*/
public function index(Event $event): JsonResponse {
$this->authorize('viewAny', Reward::class);
return response()->json(['rewards' => $event->rewards]);
public function index(Request $request, Event $event): JsonResponse|RedirectResponse {
$this->authorize('viewForEvent', [Reward::class, $event]);
return $request->expectsJson()
? response()->json(['rewards' => $event->rewards])
: redirect()->route('events.show', $event);
}

/**
* Store a newly created resource in storage.
*/
public function store(RewardStoreRequest $request, Event $event): JsonResponse {
public function store(RewardStoreRequest $request, Event $event): JsonResponse|RedirectResponse {
$reward = new Reward($request->validated());
$reward->event_id = $event->id;
$reward->save();
session()->flash('success', 'Reward created.');
return response()->json(['reward' => $reward]);

return $request->expectsJson()
? response()->json(['reward' => $reward])
: redirect()->back()->withSuccess("Created reward {$reward->name}.");
}

/**
Expand All @@ -39,17 +45,24 @@ public function show(Reward $reward): JsonResponse {
/**
* Update the specified resource in storage.
*/
public function update(RewardUpdateRequest $request, Reward $reward): JsonResponse {
public function update(RewardUpdateRequest $request, Reward $reward): JsonResponse|RedirectResponse {
$reward->update($request->validated());
return response()->json(['reward' => $reward]);

return $request->expectsJson()
? response()->json(['reward' => $reward])
: redirect()->back()->withSuccess("Updated reward {$reward->name}.");
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Reward $reward): JsonResponse {
public function destroy(Request $request, Reward $reward): JsonResponse|RedirectResponse {
$this->authorize('delete', $reward);

$reward->delete();
return response()->json(null, 205);

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->back()->withSuccess("Deleted reward {$reward->name}.");
}
}
Loading
Loading