Add online staff sidebar widget#86
Conversation
…dule; update UI for status management and display
📝 WalkthroughWalkthroughAdds an “online staff” client widget (module, integration, styles), makes Staff.status non-null with default "OFFLINE" at entity, migration, and seed levels, updates seeding and tests, and wires the widget into multiple pages. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Server as App Server
participant DB as Database
Browser->>Server: GET /staff (apiFetch)
Server->>DB: SELECT staff rows
DB-->>Server: staff rows (status values)
Server-->>Browser: 200 OK + staff JSON
Browser->>Browser: filter ONLINE/BUSY/AWAY
Browser->>Browser: update `#onlineStaffCount` and render list
Browser->>Browser: toggle list (show/hide) / periodic refresh
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/resources/static/js/common.js (1)
154-172:⚠️ Potential issue | 🟠 MajorPrevent overlapping status updates from racing.
Rapid status changes can send multiple PATCH requests; if responses arrive out of order, the select,
previousStatus, and server state can diverge. Disable the select while the update is in flight.🧯 Proposed fix
statusSelect.addEventListener("change", async (e) => { + const select = e.target; const nextStatus = e.target.value; + select.disabled = true; try { const res = await apiFetch(`/staff/me/status?status=${encodeURIComponent(nextStatus)}`, { method: "PATCH" }); if (!res.ok) { - e.target.value = previousStatus; + select.value = previousStatus; alert("Kunde inte uppdatera status."); return; } previousStatus = nextStatus; if (typeof refreshOnlineStaffWidget === "function") { await refreshOnlineStaffWidget(); } } catch (err) { - e.target.value = previousStatus; + select.value = previousStatus; console.error(err); alert("Något gick fel när status skulle sparas."); + } finally { + select.disabled = false; } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/js/common.js` around lines 154 - 172, The change handler for statusSelect can send overlapping PATCH requests and cause out-of-order state; modify the statusSelect.addEventListener("change", ...) handler to disable the select at the start of the async operation and re-enable it in a finally block so only one update is in flight; ensure you still set e.target.value back to previousStatus on non-ok responses, update previousStatus only after a successful response, and still call await refreshOnlineStaffWidget() if defined before re-enabling the control so UI and server stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/resources/static/css/styles.css`:
- Around line 12-16: The CSS rule on the body selector uses a quoted font-family
('Inter') which violates Stylelint's font-family-name-quotes rule; update the
font-family declaration in the body rule (the font-family property) to use Inter
without quotes (e.g., font-family: Inter, sans-serif) so the linter passes and
keep the rest of the declaration (var(--bg), var(--text)) unchanged.
- Around line 377-385: The .online-staff-list can grow taller than the fixed
100vh sidebar and push/hide the profile/logout area; fix it by giving
.online-staff-list a maximum height and enabling scrolling (e.g., add
max-height: calc(100vh - <space-for-profile-and-padding>); overflow-y: auto;
-webkit-overflow-scrolling: touch;) so the list scrolls instead of expanding and
the sidebar controls remain visible.
In `@src/main/resources/static/js/common.js`:
- Around line 118-129: Add accessible disclosure attributes to the toggle button
and update the toggle logic: on the button element with id "onlineStaffToggle"
add aria-controls="onlineStaffList" and initialize aria-expanded="false"
(optionally aria-haspopup="true"), and ensure the panel element with id
"onlineStaffList" has aria-hidden="true" when hidden. In the function
toggleOnlineStaffList() update the button's aria-expanded to "true"/"false"
based on the new state and set the onlineStaffList's aria-hidden accordingly
(and update onlineStaffArrow if you change visual state) so assistive tech can
read the control/panel relationship.
In `@src/main/resources/static/js/online-staff.js`:
- Around line 121-127: The widget currently calls refreshOnlineStaffWidget only
on init and local toggles (initOnlineStaffWidget, toggleOnlineStaffList) so
remote changes aren't seen until reload; add a lightweight guarded refresh loop
and visibility handler that calls refreshOnlineStaffWidget periodically (e.g.,
every 20–30s) but only while document.visibilityState === "visible", ensure the
interval is idempotent by storing/clearing a single intervalId (e.g.,
onlineStaffRefreshInterval) and clear it on page hide/unload to avoid duplicate
timers or background polling.
In `@src/main/resources/static/pages/ticket-detail.html`:
- Around line 61-63: Remove the stray ">" character after the final script tag
that loads ticket-detail.js in the ticket-detail.html page: edit the closing
script element that references "/js/ticket-detail.js" (currently "</script> >")
and eliminate the extra ">" so it is a well-formed "</script>" tag; verify no
other stray characters follow the tag.
---
Outside diff comments:
In `@src/main/resources/static/js/common.js`:
- Around line 154-172: The change handler for statusSelect can send overlapping
PATCH requests and cause out-of-order state; modify the
statusSelect.addEventListener("change", ...) handler to disable the select at
the start of the async operation and re-enable it in a finally block so only one
update is in flight; ensure you still set e.target.value back to previousStatus
on non-ok responses, update previousStatus only after a successful response, and
still call await refreshOnlineStaffWidget() if defined before re-enabling the
control so UI and server stay consistent.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3c8e2960-1a4f-4d2b-9685-d0b2c595f6ca
📒 Files selected for processing (12)
src/main/java/org/example/cyberwatch/config/DataInitializer.javasrc/main/java/org/example/cyberwatch/features/staff/model/Staff.javasrc/main/resources/db/migration/V11__set_default_status_for_staff.sqlsrc/main/resources/static/css/styles.csssrc/main/resources/static/js/common.jssrc/main/resources/static/js/online-staff.jssrc/main/resources/static/pages/create-ticket.htmlsrc/main/resources/static/pages/dashboard.htmlsrc/main/resources/static/pages/edit-ticket.htmlsrc/main/resources/static/pages/employment.htmlsrc/main/resources/static/pages/ticket-detail.htmlsrc/test/java/org/example/cyberwatch/features/staff/service/StaffServiceTest.java
# Conflicts: # src/main/resources/static/js/common.js
…to feature/sidebar-online-count # Conflicts: # src/main/resources/static/js/common.js
…dic refresh; add visibility change listener.
…ivity log with real-time updates
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/resources/static/js/common.js (1)
112-123:⚠️ Potential issue | 🟡 MinorKeep the panel hidden state in ARIA too.
The toggle now exposes
aria-expanded, but the panel never sets an initialaria-hiddenstate. Addaria-hidden="true"here and flip it intoggleOnlineStaffList()alongsidearia-expanded.♿ Proposed follow-up
- <div class="online-staff-list" id="onlineStaffList" aria-live="polite"></div> + <div class="online-staff-list" id="onlineStaffList" aria-live="polite" aria-hidden="true"></div>And in
src/main/resources/static/js/online-staff.js:if (isOpen) { listElement.classList.remove("show"); + listElement.setAttribute("aria-hidden", "true"); if (toggleButton) { toggleButton.setAttribute("aria-expanded", "false"); } @@ listElement.classList.add("show"); + listElement.setAttribute("aria-hidden", "false"); if (toggleButton) { toggleButton.setAttribute("aria-expanded", "true"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/js/common.js` around lines 112 - 123, Add and maintain the ARIA hidden state for the online staff panel: set aria-hidden="true" on the panel element with id "onlineStaffList" in the markup, and update the toggleOnlineStaffList() function to flip aria-hidden on "onlineStaffList" whenever it toggles aria-expanded on the button with id "onlineStaffToggle" so both attributes stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/resources/static/js/online-staff.js`:
- Around line 29-37: fetchActiveStaff currently calls /staff and retrieves full
StaffDTO (including PII); change this to call a minimal projection endpoint
(e.g. /staff/active or /staff/summary) that returns only id, fullName and status
(and ideally already filters to ONLINE/BUSY/AWAY). Update the frontend
fetchActiveStaff to request that new endpoint and parse the trimmed response
(keep or remove client-side filtering using validStatuses as needed). On the
server, add a controller/service method that returns a DTO like ActiveStaffDTO
{id, fullName, status} (not StaffDTO) to avoid sending socialSecurityNumber,
email, phoneNumber.
---
Duplicate comments:
In `@src/main/resources/static/js/common.js`:
- Around line 112-123: Add and maintain the ARIA hidden state for the online
staff panel: set aria-hidden="true" on the panel element with id
"onlineStaffList" in the markup, and update the toggleOnlineStaffList() function
to flip aria-hidden on "onlineStaffList" whenever it toggles aria-expanded on
the button with id "onlineStaffToggle" so both attributes stay in sync.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8c2a60ff-bf8b-4508-9baa-cca5de3dffb1
📒 Files selected for processing (7)
.gitignoresrc/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.javasrc/main/resources/logback-spring.xmlsrc/main/resources/static/css/styles.csssrc/main/resources/static/js/common.jssrc/main/resources/static/js/online-staff.jssrc/main/resources/static/pages/ticket-detail.html
✅ Files skipped from review due to trivial changes (3)
- src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java
- src/main/resources/logback-spring.xml
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/resources/static/pages/ticket-detail.html
- src/main/resources/static/css/styles.css
| async function fetchActiveStaff() { | ||
| const res = await apiFetch("/staff"); | ||
| if (!res.ok) { | ||
| throw new Error("Kunde inte hämta staff"); | ||
| } | ||
|
|
||
| const staff = await res.json(); | ||
| const validStatuses = ["ONLINE", "BUSY", "AWAY"]; | ||
| return staff.filter(person => validStatuses.includes(person.status)); |
There was a problem hiding this comment.
Avoid polling the full staff directory for this widget.
/staff returns full StaffDTO objects, including sensitive fields like socialSecurityNumber, email, and phoneNumber per src/main/java/org/example/cyberwatch/features/staff/model/StaffDTO.java. This widget only needs display name and status, but it now fetches the full staff directory repeatedly. Prefer a minimal endpoint/DTO for active staff, or at least a count/list projection without PII.
🛡️ Safer API shape
async function fetchActiveStaff() {
- const res = await apiFetch("/staff");
+ const res = await apiFetch("/staff/active");
if (!res.ok) {
throw new Error("Kunde inte hämta staff");
}
- const staff = await res.json();
- const validStatuses = ["ONLINE", "BUSY", "AWAY"];
- return staff.filter(person => validStatuses.includes(person.status));
+ return await res.json();
}Expected response fields should be limited to what lines 68-77 render, e.g. id, fullName, and status.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/static/js/online-staff.js` around lines 29 - 37,
fetchActiveStaff currently calls /staff and retrieves full StaffDTO (including
PII); change this to call a minimal projection endpoint (e.g. /staff/active or
/staff/summary) that returns only id, fullName and status (and ideally already
filters to ONLINE/BUSY/AWAY). Update the frontend fetchActiveStaff to request
that new endpoint and parse the trimmed response (keep or remove client-side
filtering using validStatuses as needed). On the server, add a
controller/service method that returns a DTO like ActiveStaffDTO {id, fullName,
status} (not StaffDTO) to avoid sending socialSecurityNumber, email,
phoneNumber.
Summary
Added a new online staff widget in the sidebar.
Changes
online-staff.jsfor handling online userscommon.jsto include the widget in the navbarFeatures
UI
Summary by CodeRabbit
New Features
Bug Fixes
Style
Tests