Skip to content

Add online staff sidebar widget#86

Merged
gitnes94 merged 7 commits into
mainfrom
feature/sidebar-online-count
Apr 23, 2026
Merged

Add online staff sidebar widget#86
gitnes94 merged 7 commits into
mainfrom
feature/sidebar-online-count

Conversation

@Ericthilen

@Ericthilen Ericthilen commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Added a new online staff widget in the sidebar.

Changes

  • Added online-staff.js for handling online users
  • Updated common.js to include the widget in the navbar
  • Updated all pages to load the new script
  • Added UI for:
    • Online count
    • Clickable dropdown list
    • Staff status display

Features

  • Shows number of active staff (ONLINE, BUSY, AWAY)
  • Click to view list of active staff
  • Updates when user changes status
  • Works on all pages

UI

  • Compact sidebar widget
  • Dropdown list with name + status
  • Color-coded status badges

Summary by CodeRabbit

  • New Features

    • Added an online staff sidebar widget showing active staff with localized availability labels; pages now load the widget’s client code.
  • Bug Fixes

    • Ensured staff status defaults to "OFFLINE" for existing and new records; database now enforces a non-null default.
  • Style

    • Refactored and extended CSS for the sidebar widget and related layout/spacing.
  • Tests

    • Added a unit test verifying the default staff status is "OFFLINE".

…dule; update UI for status management and display
@Ericthilen Ericthilen self-assigned this Apr 22, 2026
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Staff entity, migration & seeding
src/main/java/org/example/cyberwatch/features/staff/model/Staff.java, src/main/java/org/example/cyberwatch/config/DataInitializer.java, src/main/resources/db/migration/V11__set_default_status_for_staff.sql, src/test/java/org/example/cyberwatch/features/staff/service/StaffServiceTest.java
status now initialized to "OFFLINE" and @Column(nullable=false); migration sets NULLs to 'OFFLINE' and enforces NOT NULL/default; DataInitializer seeds staff with status "OFFLINE"; unit test asserts default.
Online staff client module
src/main/resources/static/js/online-staff.js
New module fetching /staff, filters ONLINE/BUSY/AWAY, updates count, renders collapsible list, exposes init/refresh/toggle behavior, and handles visibility/periodic refresh.
Frontend integration & shared JS
src/main/resources/static/js/common.js, src/main/resources/static/css/styles.css
common.js hooks into navbar/status flows to init/refresh widget when present; CSS adds online-widget styles and refactors other layout rules.
Page script includes
src/main/resources/static/pages/dashboard.html, src/main/resources/static/pages/create-ticket.html, src/main/resources/static/pages/edit-ticket.html, src/main/resources/static/pages/employment.html, src/main/resources/static/pages/ticket-detail.html
Inserted online-staff.js script include on multiple pages before page-specific scripts to enable the widget.
Minor infra & formatting
.gitignore, src/main/java/.../ActivityLogController.java, src/main/resources/logback-spring.xml
Adjusted .gitignore to ignore logs/cyberwatch.log specifically; replaced wildcard imports with explicit Spring MVC imports; minor whitespace formatting in logback XML.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Ericthilen
  • gitnes94

Poem

🐰 In code I nibble, soft and keen,
I mark each friend as OFFLINE — seen.
I count the hops, the busy crew,
I flip the list and show who’s new.
Widget hums — carrots for review! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title accurately summarizes the main feature being added: a sidebar widget for displaying online staff.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/sidebar-online-count

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

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: 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 | 🟠 Major

Prevent 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

📥 Commits

Reviewing files that changed from the base of the PR and between fdd8827 and e6fae71.

📒 Files selected for processing (12)
  • src/main/java/org/example/cyberwatch/config/DataInitializer.java
  • src/main/java/org/example/cyberwatch/features/staff/model/Staff.java
  • src/main/resources/db/migration/V11__set_default_status_for_staff.sql
  • src/main/resources/static/css/styles.css
  • src/main/resources/static/js/common.js
  • src/main/resources/static/js/online-staff.js
  • src/main/resources/static/pages/create-ticket.html
  • src/main/resources/static/pages/dashboard.html
  • src/main/resources/static/pages/edit-ticket.html
  • src/main/resources/static/pages/employment.html
  • src/main/resources/static/pages/ticket-detail.html
  • src/test/java/org/example/cyberwatch/features/staff/service/StaffServiceTest.java

Comment thread src/main/resources/static/css/styles.css
Comment thread src/main/resources/static/css/styles.css
Comment thread src/main/resources/static/js/common.js
Comment thread src/main/resources/static/js/online-staff.js
Comment thread src/main/resources/static/pages/ticket-detail.html Outdated
Ericthilen and others added 5 commits April 22, 2026 16:43
# 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.

@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: 1

♻️ Duplicate comments (1)
src/main/resources/static/js/common.js (1)

112-123: ⚠️ Potential issue | 🟡 Minor

Keep the panel hidden state in ARIA too.

The toggle now exposes aria-expanded, but the panel never sets an initial aria-hidden state. Add aria-hidden="true" here and flip it in toggleOnlineStaffList() alongside aria-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

📥 Commits

Reviewing files that changed from the base of the PR and between 87e4135 and dc8107a.

📒 Files selected for processing (7)
  • .gitignore
  • src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java
  • src/main/resources/logback-spring.xml
  • src/main/resources/static/css/styles.css
  • src/main/resources/static/js/common.js
  • src/main/resources/static/js/online-staff.js
  • src/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

Comment on lines +29 to +37
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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

@gitnes94
gitnes94 merged commit e8393a5 into main Apr 23, 2026
2 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