Skip to content

Implement Admin Gym Class management feature#42

Merged
mattknatt merged 5 commits into
mainfrom
feat/admin
Apr 29, 2026
Merged

Implement Admin Gym Class management feature#42
mattknatt merged 5 commits into
mainfrom
feat/admin

Conversation

@mattknatt

@mattknatt mattknatt commented Apr 21, 2026

Copy link
Copy Markdown
Owner
  • Add AdminDashboard.tsx for viewing, filtering, creating, editing, and canceling gym classes.
  • Develop AdminGymClassService, AdminGymClassController, and related domain logic for backend functionality.
  • Add AdminGymClassResponse for structured API responses.
  • Implement API routes: list, create, update, and cancel gym classes.
  • Add tests for AdminGymClassService.
  • Integrate styles and filter logic for class management UI.

Summary by CodeRabbit

  • New Features
    • Added Admin Dashboard page with role-based access for authorized administrators to manage classes
    • Implemented comprehensive class management interface supporting creation, editing, and cancellation operations
    • Added pagination and status-based filtering for efficient class list navigation
    • Added modal forms for streamlined class creation and editing workflows with automatic field management

- Add `AdminDashboard.tsx` for viewing, filtering, creating, editing, and canceling gym classes.
- Develop `AdminGymClassService`, `AdminGymClassController`, and related domain logic for backend functionality.
- Add `AdminGymClassResponse` for structured API responses.
- Implement API routes: list, create, update, and cancel gym classes.
- Add tests for `AdminGymClassService`.
- Integrate styles and filter logic for class management UI.
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@mattknatt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 58 minutes and 28 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6c635a35-3ae9-4de0-99bb-8fc071e8f815

📥 Commits

Reviewing files that changed from the base of the PR and between f14a13c and 73eccf0.

📒 Files selected for processing (8)
  • frontend/src/admin/ClassFormModal.tsx
  • src/main/java/com/example/boka/booking/BookingProviderAdapter.java
  • src/main/java/com/example/boka/booking/BookingProviderPort.java
  • src/main/java/com/example/boka/booking/domain/BookingRepository.java
  • src/main/java/com/example/boka/common/GlobalExceptionHandler.java
  • src/main/java/com/example/boka/config/DataSeeder.java
  • src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java
  • src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java
📝 Walkthrough

Walkthrough

Adds an admin dashboard feature enabling admin users to view, create, edit, and cancel gym classes through a paginated UI with status filtering. Includes backend service layer, API endpoints, admin user seeding via configuration, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Frontend Admin Dashboard UI
frontend/src/App.tsx, frontend/src/admin/AdminDashboard.tsx, frontend/src/admin/ClassFormModal.tsx
Added admin navigation button (role-gated), /admin route with role check, and hero heading mapping. Created paginated class listing with status filtering, CRUD modals, and cancel confirmation flows. Added class form modal for create/edit with auto-fill logic for capacity and end time.
Backend Admin Service Layer
src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java, src/main/java/com/example/boka/gymclass/application/CreateGymClassRequest.java, src/main/java/com/example/boka/gymclass/application/UpdateGymClassRequest.java, src/main/java/com/example/boka/gymclass/application/AdminGymClassResponse.java, src/main/java/com/example/boka/gymclass/application/ClassTypeResponse.java
Implemented transactional service for admin class operations: paginated retrieval with optional status filtering, validated creation/update with time and referential integrity checks, and cancellation. Added request DTOs with validation annotations and response DTOs for class/type data serialization.
Backend Admin API Controller & Persistence
src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java, src/main/java/com/example/boka/gymclass/domain/GymClassRepository.java
Created REST controller exposing endpoints for listing, creating, updating, and cancelling classes with status-aware filtering. Added repository method to query classes by status with pagination support.
Instructor Provider Expansion
src/main/java/com/example/boka/gymclass/InstructorProviderPort.java, src/main/java/com/example/boka/user/infrastructure/InstructorProviderAdapter.java
Added port methods to retrieve all instructors and look up individual instructors by ID. Implemented adapter methods querying users with INSTRUCTOR role and mapping to details DTO.
Configuration & Security
src/main/java/com/example/boka/config/DataSeeder.java, src/main/java/com/example/boka/config/SpaController.java, src/main/java/com/example/boka/security/SecurityConfig.java
Modified seeding to load admin credentials from environment variables and upsert admin user on startup regardless of existing data. Added /admin route to SPA controller. Extended security config to permit /admin path and restrict /api/admin/** endpoints to ADMIN role.
Testing
src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java
Comprehensive test suite covering paginated retrieval with status filtering, successful and failed create/update/cancel flows, validation enforcement, and booking count enrichment.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin User
    participant UI as Frontend UI
    participant API as API Controller
    participant Service as Admin Service
    participant Repo as Repository
    participant Port as Provider Port

    Admin->>UI: View Admin Dashboard
    UI->>API: GET /api/admin/classes?status=SCHEDULED
    API->>Service: getAllClasses(status, pageable)
    Service->>Repo: findByStatus(SCHEDULED, pageable)
    Repo-->>Service: Page<GymClass>
    Service->>Port: getAllInstructors()
    Port-->>Service: List<InstructorDetails>
    Service-->>API: Page<AdminGymClassResponse>
    API-->>UI: Classes with enriched data
    UI-->>Admin: Display paginated class list

    Admin->>UI: Click Edit Class
    UI-->>Admin: Show ClassFormModal with class data
    Admin->>UI: Update capacity & end time, submit
    UI->>API: PUT /api/admin/classes/{id}
    API->>Service: updateClass(id, UpdateGymClassRequest)
    Service->>Repo: findById(id)
    Repo-->>Service: GymClass
    Service->>Service: Validate times & refs
    Service->>Repo: save(updated GymClass)
    Repo-->>Service: Updated GymClass
    Service-->>API: AdminGymClassResponse
    API-->>UI: 200 OK
    UI->>UI: Close modal, refresh list
    UI-->>Admin: Updated class visible
Loading
sequenceDiagram
    participant Admin as Admin User
    participant UI as Frontend UI
    participant API as API Controller
    participant Service as Admin Service
    participant Repo as Repository

    Admin->>UI: Click Cancel Class
    UI->>UI: Show confirmation dialog
    Admin->>UI: Confirm cancellation
    UI->>API: DELETE /api/admin/classes/{id}
    API->>Service: cancelClass(id)
    Service->>Repo: findById(id)
    Repo-->>Service: GymClass
    Service->>Service: Verify not already CANCELLED
    Service->>Repo: save(GymClass with CANCELLED)
    Repo-->>Service: Success
    Service-->>API: void
    API-->>UI: 204 No Content
    UI->>UI: Remove from list or refresh
    UI-->>Admin: Class marked cancelled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #30 — Extends InstructorProviderPort with new retrieval methods (getAllInstructors, findInstructorById) that are directly used in the admin dashboard for instructor selection and display.
  • PR #28 — Modifies DataSeeder.java to handle admin user creation/seeding via configuration, sharing the same seeding strategy pattern.
  • PR #23 — Updates SecurityConfig.java authorization rules to introduce admin role enforcement on protected API routes, complementing the admin feature's access control.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: implementing an admin gym class management feature across frontend and backend components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/admin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 58 minutes and 28 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (7)
frontend/src/App.tsx (1)

95-108: Optional: factor out the repeated nav-button styling.

The My Bookings / Settings / Admin buttons share the exact same className, style shape, and active-state logic; only the label and target path differ. Extracting a small NavButton (or using a CSS class toggled on pathname match) would remove the repetition and make future nav additions cheap.

♻️ Example extraction
const NavButton = ({ to, label }: { to: string; label: string }) => (
  <button
    type="button"
    className="nav-link"
    onClick={() => navigate(to)}
    style={{
      fontWeight: pathname === to ? 'bold' : 'normal',
      color: pathname === to ? '#ff1493' : '#333',
      marginRight: '15px',
    }}
  >
    {label}
  </button>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.tsx` around lines 95 - 108, Extract the repeated nav-button
markup inside App (the Admin / My Bookings / Settings buttons) into a small
reusable NavButton component (or create a CSS class toggled by active state);
NavButton should accept props like to and label, use the existing navigate and
pathname logic to set the onClick and active styling (fontWeight and color, plus
marginRight), and replace each inline button with <NavButton to="..."
label="..."> so the className, inline style shape, and active-state
determination are centralized.
src/main/java/com/example/boka/gymclass/application/AdminGymClassResponse.java (1)

11-12: Consider exposing startTime/endTime as LocalDateTime rather than String.

Mapping via LocalDateTime.toString() bypasses Jackson's JavaTimeModule/@JsonFormat so the wire format can drift from other endpoints, and clients lose the benefit of a typed, consistently serialized ISO-8601 value. Keeping the type as LocalDateTime (or OffsetDateTime) lets serialization be centrally configured and keeps the admin API consistent with the rest of the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/boka/gymclass/application/AdminGymClassResponse.java`
around lines 11 - 12, Change the AdminGymClassResponse fields startTime and
endTime from String to java.time.LocalDateTime (or OffsetDateTime if you need
timezone) and update their declarations, constructor parameters, getters/setters
(or records/ builder) in AdminGymClassResponse so types match; add the
appropriate import (java.time.LocalDateTime) and, if necessary, annotate with
`@JsonFormat` or ensure the module registration is used consistently with other
DTOs so Jackson's JavaTimeModule handles ISO‑8601 serialization/deserialization.
src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java (1)

153-175: Redundant stubbing.

instructorProviderPort.findInstructorById(INSTRUCTOR_ID) is stubbed on both line 157 and line 162 with identical return values — the second call just overrides the first to the same value. Same pattern is duplicated in updateClass_Successful_UpdatesAllFields (lines 231 and 235). Drop the duplicate line in each to keep the test intent crisp. Also, with MockitoExtension using strict stubbing by default, duplicate stubs can occasionally surface as "unnecessary stubbing" warnings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java`
around lines 153 - 175, Remove the redundant Mockito stubbing of
instructorProviderPort.findInstructorById(INSTRUCTOR_ID) in the test methods;
keep a single
when(instructorProviderPort.findInstructorById(INSTRUCTOR_ID)).thenReturn(Optional.of(instructor()))
per test (e.g., in createClass_Successful and
updateClass_Successful_UpdatesAllFields) so the duplicate call that re-stubs the
same return value is deleted and only one stub remains before invoking
adminGymClassService.createClass/updateClass.
src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java (1)

66-69: Unpaginated /api/admin/instructors.

The endpoint returns every instructor in one shot. Fine for small fleets, but if the instructor population can grow, consider pagination or at least a hard cap — the admin UI is the only known consumer today and it just populates a <select>, so this is informational rather than blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java`
around lines 66 - 69, The getInstructors method in AdminGymClassController
currently returns all instructors via instructorProviderPort.getAllInstructors()
which can scale poorly; modify getInstructors to accept pagination or limit
parameters (e.g., page/size or a maxResults cap) and update
InstructorProviderPort to expose a paginated method (or a method like
getInstructors(int limit, int offset)) that returns a subset of
InstructorProviderPort.InstructorDetails; enforce a sensible hard cap on size if
no paging is provided and document the new params so the admin UI can request
only what it needs for the <select>.
frontend/src/admin/AdminDashboard.tsx (1)

56-66: Check res.ok before parsing reference-data responses.

Each fetch calls r.json() unconditionally. A 401/403/500 response (e.g. admin session expired) may return HTML or a non-JSON error body, causing a parse error that's caught as a generic "Failed to load reference data" toast and hides the underlying issue. Prefer validating res.ok per request and surfacing a more specific message.

♻️ Proposed change
-    Promise.all([
-      fetch('/api/admin/class-types').then(r => r.json()),
-      fetch('/api/admin/instructors').then(r => r.json()),
-      fetch('/api/gyms?size=100').then(r => r.json()),
-    ]).then(([cts, insts, gymsData]) => {
+    const getJson = async (url: string) => {
+      const r = await fetch(url);
+      if (!r.ok) throw new Error(`${url} -> ${r.status}`);
+      return r.json();
+    };
+    Promise.all([
+      getJson('/api/admin/class-types'),
+      getJson('/api/admin/instructors'),
+      getJson('/api/gyms?size=100'),
+    ]).then(([cts, insts, gymsData]) => {

Also, /api/gyms?size=100 silently truncates beyond 100 gyms — fine for now, but consider paginating if the fleet can grow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/admin/AdminDashboard.tsx` around lines 56 - 66, In the useEffect
that loads reference data, don't call r.json() unconditionally in the
Promise.all fetches; instead check each Response.ok (for the three calls used in
useEffect) and if not ok throw or return a rejected Promise with the status/text
so the catch can surface a clearer error; update the three fetches (the ones
that feed setClassTypes, setInstructors, and setGyms) to validate res.ok before
parsing, and ensure you still handle gymsData.content ?? gymsData when assigning
setGyms so existing fallback logic remains.
src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java (1)

149-196: Reduce duplication between enrich(...) and toResponse(...).

The per-row mapping in enrich (lines 149-171) and toResponse (lines 181-195) are identical except for how the instructor is resolved (batch vs single lookup). Extracting the mapping into a private buildResponse(GymClass gc, String instructorName, int bookingCount) would remove the duplicated field-by-field construction and make future AdminGymClassResponse additions a one-line change.

Also minor: gc.getCapacity() is unboxed for the Math.max(0, capacity - bookingCount) arithmetic in both paths — if a legacy row has a null capacity this NPEs. Defending with gc.getCapacity() != null ? Math.max(0, gc.getCapacity() - bookingCount) : 0 (or asserting non-null at the entity level) avoids the crash.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java`
around lines 149 - 196, Extract the duplicated AdminGymClassResponse
construction into a private helper buildResponse(GymClass gc, String
instructorName, int bookingCount) and have both the page.map(...) lambda in
enrich(...) and toResponse(...) call buildResponse(...) instead of duplicating
field-by-field construction; in buildResponse compute remaining capacity
defensively (e.g. if gc.getCapacity() is null return 0 else Math.max(0,
gc.getCapacity() - bookingCount)) so null capacities don't NPE, and keep
toResponse only responsible for resolving the instructorName (call
instructorProviderPort.findInstructorById(...).orElse(null) and pass the
resolved name into buildResponse).
src/main/java/com/example/boka/gymclass/application/UpdateGymClassRequest.java (1)

9-16: Consider symmetry between startTime and endTime validation.

startTime uses @FutureOrPresent but endTime uses @Future. Since the service enforces startTime < endTime anyway, the asymmetry is functionally fine, but intent is clearer if both use @FutureOrPresent (the service boundary check catches the ordering). Minor consistency nit — feel free to ignore if the asymmetry was intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/boka/gymclass/application/UpdateGymClassRequest.java`
around lines 9 - 16, The annotations on UpdateGymClassRequest are asymmetric:
startTime is annotated with `@FutureOrPresent` while endTime uses `@Future`; update
the endTime annotation to `@FutureOrPresent` so both startTime and endTime use the
same validation annotation (change the `@Future` on the endTime parameter to
`@FutureOrPresent`) while leaving the service-level ordering check intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/admin/ClassFormModal.tsx`:
- Around line 100-103: The client currently expects a top-level data.message
after a non-OK response in ClassFormModal.tsx (the else branch handling
res.json()), but Spring's validation 400 uses a BindingResult shape so
data.message is often undefined; update the error handling where res is
processed (in the submit/response handler in ClassFormModal.tsx) to extract a
user-friendly message by checking data.errors?.[0]?.defaultMessage,
data.fieldErrors?.[0]?.defaultMessage, or data.message in that order and pass
the resulting string to toast.error (with the existing generic fallback).
Optionally note that the controller (Create/Update endpoints) could instead map
MethodArgumentNotValidException to {"message": "..."} via an `@ExceptionHandler`,
but implement the client-side parsing change to ensure validation messages
surface to users.
- Around line 48-57: The helper functions toDatetimeLocal and addMinutes are
wrong: remove the no-op .replace in toDatetimeLocal and make both functions
produce a timezone-free "YYYY-MM-DDTHH:MM" local datetime string. Fix by parsing
the input into local date components (year, month, day, hours, minutes), use
Date setters/getters (e.g., create Date(year, month-1, day, hour, minute)), for
addMinutes call setMinutes(getMinutes()+minutes) on that local Date, and then
format the result using local getters pad‑to‑2 digits into "YYYY-MM-DDTHH:MM".
Update the implementations of toDatetimeLocal and addMinutes accordingly so they
never call toISOString() and always return a local "datetime-local" string.

In `@src/main/java/com/example/boka/config/DataSeeder.java`:
- Around line 127-147: upsertAdminUser currently re-encodes and writes
adminPassword on every startup; change it so existing users are not overwritten
unless an explicit opt-in flag is set and avoid unnecessary writes: in
upsertAdminUser, when userRepository.findByEmail(adminEmail) returns an existing
user, first check passwordEncoder.matches(adminPassword,
existing.getPasswordHash()) and if it matches do nothing; if it does not match,
only overwrite/save when a new boolean property (e.g. adminPasswordForceSync or
admin.password.force-sync) is true (otherwise log/skips), and when creating a
new User still set the password as before; this ensures creation-once bootstrap
semantics by default and an explicit force-sync path when needed.
- Around line 48-52: Remove the hardcoded fallback for adminPassword and enforce
a safer behavior in DataSeeder: change the `@Value` usage so ADMIN_PASSWORD is not
defaulted to "password123" (make it required or nullable), inject Spring
Environment into DataSeeder, and in upsertAdminUser() check if adminPassword is
null/blank; if the active profile is not "dev" or "test" then throw an
IllegalStateException to fail startup (or alternatively annotate
DataSeeder/upsertAdminUser() with `@Profile`("dev") to restrict seeding to dev
only); if you choose to keep a fallback for local dev, replace the default with
a dev-only guard and emit a clear WARN via the class logger when the default is
being used.

In
`@src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java`:
- Around line 120-131: cancelClass currently sets GymClass.status to
ClassStatus.CANCELLED via gymClassRepository.save(gymClass) but does not notify
or update bookings; add a booking-side notification by extending the
BookingProviderPort with a cancelBookingsForClass(Long classId) (or similarly
named) and invoke bookingProviderPort.cancelBookingsForClass(id) from
cancelClass (inside the same `@Transactional` flow, after setting status and
before/after save as per desired semantics) so that confirmed bookings are
cancelled/marked notified; ensure BookingProviderPort is injected into
AdminGymClassService and handle/propagate any errors consistently with existing
exception behavior.
- Around line 107-113: When updating capacity in updateClass, reject any
requested capacity that is less than the current confirmed booking count to
avoid creating an overbooked state: if req.capacity() != null, compute the
current confirmed booking count from the gymClass (e.g.,
gymClass.getConfirmedBookingCount() or gymClass.getBookings().size()) and if
req.capacity() < confirmedCount throw an IllegalArgumentException (with a clear
message like "Capacity cannot be lower than current confirmed bookings"); keep
the existing start/end time validation intact.

In
`@src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java`:
- Around line 38-54: The controller currently only catches
IllegalArgumentException/IllegalStateException so validation failures from
`@Valid` on CreateGymClassRequest and UpdateGymClassRequest throw
MethodArgumentNotValidException and return Spring's default shape; add a handler
that converts MethodArgumentNotValidException into the same {"message": "..."}
payload: either add an `@ExceptionHandler`(MethodArgumentNotValidException.class)
method inside AdminGymClassController or create a `@RestControllerAdvice` that
handles MethodArgumentNotValidException, extract and join field error messages
(from ex.getBindingResult().getFieldErrors()) into a single string, and return
ResponseEntity.badRequest().body(Map.of("message", joinedMessage)) so clients
expecting the top-level "message" keep working.
- Around line 28-36: Clamp the incoming page size to a safe maximum in
AdminGymClassController#getClasses to prevent unbounded requests: validate the
request param size (and optionally page) before creating the PageRequest (e.g.,
enforce size = Math.min(Math.max(size, 1), MAX_PAGE_SIZE) where MAX_PAGE_SIZE is
a meaningful constant like 100), then call PageRequest.of(page, size,
Sort.by(...)) and pass that to adminGymClassService.getAllClasses(status,
pageable); alternatively replace the int params with Spring's Pageable using
`@PageableDefault`(size = 15) and configure spring.data.web.pageable.max-page-size
in configuration, but ensure the controller references and uses the bounded
value when constructing the PageRequest.

---

Nitpick comments:
In `@frontend/src/admin/AdminDashboard.tsx`:
- Around line 56-66: In the useEffect that loads reference data, don't call
r.json() unconditionally in the Promise.all fetches; instead check each
Response.ok (for the three calls used in useEffect) and if not ok throw or
return a rejected Promise with the status/text so the catch can surface a
clearer error; update the three fetches (the ones that feed setClassTypes,
setInstructors, and setGyms) to validate res.ok before parsing, and ensure you
still handle gymsData.content ?? gymsData when assigning setGyms so existing
fallback logic remains.

In `@frontend/src/App.tsx`:
- Around line 95-108: Extract the repeated nav-button markup inside App (the
Admin / My Bookings / Settings buttons) into a small reusable NavButton
component (or create a CSS class toggled by active state); NavButton should
accept props like to and label, use the existing navigate and pathname logic to
set the onClick and active styling (fontWeight and color, plus marginRight), and
replace each inline button with <NavButton to="..." label="..."> so the
className, inline style shape, and active-state determination are centralized.

In
`@src/main/java/com/example/boka/gymclass/application/AdminGymClassResponse.java`:
- Around line 11-12: Change the AdminGymClassResponse fields startTime and
endTime from String to java.time.LocalDateTime (or OffsetDateTime if you need
timezone) and update their declarations, constructor parameters, getters/setters
(or records/ builder) in AdminGymClassResponse so types match; add the
appropriate import (java.time.LocalDateTime) and, if necessary, annotate with
`@JsonFormat` or ensure the module registration is used consistently with other
DTOs so Jackson's JavaTimeModule handles ISO‑8601 serialization/deserialization.

In
`@src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java`:
- Around line 149-196: Extract the duplicated AdminGymClassResponse construction
into a private helper buildResponse(GymClass gc, String instructorName, int
bookingCount) and have both the page.map(...) lambda in enrich(...) and
toResponse(...) call buildResponse(...) instead of duplicating field-by-field
construction; in buildResponse compute remaining capacity defensively (e.g. if
gc.getCapacity() is null return 0 else Math.max(0, gc.getCapacity() -
bookingCount)) so null capacities don't NPE, and keep toResponse only
responsible for resolving the instructorName (call
instructorProviderPort.findInstructorById(...).orElse(null) and pass the
resolved name into buildResponse).

In
`@src/main/java/com/example/boka/gymclass/application/UpdateGymClassRequest.java`:
- Around line 9-16: The annotations on UpdateGymClassRequest are asymmetric:
startTime is annotated with `@FutureOrPresent` while endTime uses `@Future`; update
the endTime annotation to `@FutureOrPresent` so both startTime and endTime use the
same validation annotation (change the `@Future` on the endTime parameter to
`@FutureOrPresent`) while leaving the service-level ordering check intact.

In
`@src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java`:
- Around line 66-69: The getInstructors method in AdminGymClassController
currently returns all instructors via instructorProviderPort.getAllInstructors()
which can scale poorly; modify getInstructors to accept pagination or limit
parameters (e.g., page/size or a maxResults cap) and update
InstructorProviderPort to expose a paginated method (or a method like
getInstructors(int limit, int offset)) that returns a subset of
InstructorProviderPort.InstructorDetails; enforce a sensible hard cap on size if
no paging is provided and document the new params so the admin UI can request
only what it needs for the <select>.

In
`@src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java`:
- Around line 153-175: Remove the redundant Mockito stubbing of
instructorProviderPort.findInstructorById(INSTRUCTOR_ID) in the test methods;
keep a single
when(instructorProviderPort.findInstructorById(INSTRUCTOR_ID)).thenReturn(Optional.of(instructor()))
per test (e.g., in createClass_Successful and
updateClass_Successful_UpdatesAllFields) so the duplicate call that re-stubs the
same return value is deleted and only one stub remains before invoking
adminGymClassService.createClass/updateClass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 29922489-a915-4479-86b0-695d40b09aca

📥 Commits

Reviewing files that changed from the base of the PR and between 988ac05 and f14a13c.

📒 Files selected for processing (16)
  • frontend/src/App.tsx
  • frontend/src/admin/AdminDashboard.tsx
  • frontend/src/admin/ClassFormModal.tsx
  • src/main/java/com/example/boka/config/DataSeeder.java
  • src/main/java/com/example/boka/config/SpaController.java
  • src/main/java/com/example/boka/gymclass/InstructorProviderPort.java
  • src/main/java/com/example/boka/gymclass/application/AdminGymClassResponse.java
  • src/main/java/com/example/boka/gymclass/application/AdminGymClassService.java
  • src/main/java/com/example/boka/gymclass/application/ClassTypeResponse.java
  • src/main/java/com/example/boka/gymclass/application/CreateGymClassRequest.java
  • src/main/java/com/example/boka/gymclass/application/UpdateGymClassRequest.java
  • src/main/java/com/example/boka/gymclass/domain/GymClassRepository.java
  • src/main/java/com/example/boka/gymclass/infrastructure/AdminGymClassController.java
  • src/main/java/com/example/boka/security/SecurityConfig.java
  • src/main/java/com/example/boka/user/infrastructure/InstructorProviderAdapter.java
  • src/test/java/com/example/boka/gymclass/application/AdminGymClassServiceTest.java

Comment thread frontend/src/admin/ClassFormModal.tsx
Comment thread frontend/src/admin/ClassFormModal.tsx
Comment thread src/main/java/com/example/boka/config/DataSeeder.java
Comment thread src/main/java/com/example/boka/config/DataSeeder.java
…curacy

- Add `pad()` helper to ensure proper zero-padding for single-digit date-time components.
- Update `toDatetimeLocal()` to use `Date` object for reliable ISO-to-local conversion.
- Refactor `addMinutes()` to improve date-time parsing logic.
- Add capacity check in `AdminGymClassService` to prevent capacity below confirmed bookings, throwing `IllegalArgumentException` if violated.
- Introduce cancellation of class bookings upon gym class cancellation.
- Refactor admin password logic in `DataSeeder` to enforce `ADMIN_PASSWORD` for non-dev/test, add force-sync support, and use an insecure fallback for dev/test environments.
- Improve error messaging in `ClassFormModal.tsx` toast notifications.
- Add new `BookingRepository` query for booking cancellations and implement `BookingProviderPort.cancelBookingsForClass`.
- Introduce `GlobalExceptionHandler` to handle `MethodArgumentNotValidException`.
- Return structured validation error messages with bad request status.
@mattknatt
mattknatt merged commit 58a3d3b into main Apr 29, 2026
2 checks passed
@mattknatt
mattknatt deleted the feat/admin branch April 29, 2026 10:01
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.

1 participant