Skip to content

Feature/front end react#215

Merged
johanbriger merged 40 commits into
mainfrom
feature/FrontEndReact
Apr 22, 2026
Merged

Feature/front end react#215
johanbriger merged 40 commits into
mainfrom
feature/FrontEndReact

Conversation

@johanbriger

@johanbriger johanbriger commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Complete frontend: JWT login/registration, role-aware routing (owner/vet/admin), dashboards and pages for pets, cases, admin, vet workflows, comments, attachments and case status flows.
    • Backend: new endpoints for fetching user pets, assigned records, and activity logs; activity log retrieval.
  • Style/Docs/Chores

    • Swedish README, Tailwind/Vite/PostCSS/ESLint configs, package manifests, global styles, and .gitignore added.

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new React + Vite + Tailwind frontend (pages, components, services, auth flow) and multiple backend endpoints/service updates (authenticated pet listing, activity-log/all, vet-assigned records, JWT claim changes, transactional/entity tweaks).

Changes

Cohort / File(s) Summary
Frontend: Project config & docs
frontend/package.json, frontend/vite.config.js, frontend/tailwind.config.js, frontend/postcss.config.js, frontend/.gitignore, frontend/eslint.config.js, frontend/README.md
New frontend package, build/tooling configs, ESLint, .gitignore and README documenting setup and JWT auth expectations.
Frontend: Entrypoint & styles
frontend/index.html, frontend/src/index.css, frontend/src/App.css, frontend/src/main.jsx
App HTML shell, React mount, Tailwind directives and global/component CSS.
Frontend: App + Layout
frontend/src/App.jsx, frontend/src/components/Layout.jsx
Main App component with JWT decode, role-based routing and logout handling; Layout/sidebar component with role-driven navigation.
Frontend: Pages & dashboards
frontend/src/pages/* (e.g., AdminDashboard.jsx, OwnerDashboard.jsx, VetDashboard.jsx, Login.jsx, Register.jsx, PetDetail.jsx, CaseDetail.jsx, CreateCase.jsx)
New pages for Owner/Vet/Admin flows: dashboards, auth pages, pet/case create/edit/detail, attachments, comments and status workflows.
Frontend: Components & admin UI
frontend/src/components/*, frontend/src/components/admin/* (e.g., PetForm.jsx, PetList.jsx, admin/AuditLogView.jsx, admin/UserModal.jsx, admin/ClinicModal.jsx)
Reusable UI components, admin modals, audit log timeline, pet form/list.
Frontend: Services & utils
frontend/src/services/api.jsx, frontend/src/utils/statusHelper.js
Axios client with request/response interceptors (attach token, 401/403 global handling) and service wrappers for auth, pets, records, attachments, comments, clinics, users, vets; status mapping constant.
Frontend: Tooling configs
frontend/eslint.config.js, frontend/postcss.config.js, frontend/tailwind.config.js
ESLint flat config, PostCSS enabling Tailwind + autoprefixer, Tailwind theme/extensions.
Backend: Controllers & services
src/main/java/.../controller/PetController.java, src/main/java/.../controller/ActivityLogController.java, src/main/java/.../controller/MedicalRecordController.java, src/main/java/.../services/ActivityLogService.java, src/main/java/.../services/MedicalRecordService.java, src/main/java/.../services/PetService.java, src/main/java/.../repository/ActivityLogRepository.java
New endpoints and service methods: GET /api/pets (authenticated), GET /api/activity-logs/all, GET /api/medical-records/my-assigned; repository method for logs ordering; class-level @Transactional added to PetService.
Backend: Security, entities & DB seeds
src/main/java/.../security/JwtService.java, src/main/java/.../entities/ActivityLog.java, src/main/resources/data.sql
JWT now includes name and optional clinicId; ActivityLog gained entityId field; data.sql updated ON CONFLICT and password hashes.
Repo root
package.json
Added placeholder root package.json (empty devDependencies).

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Browser)
    participant Frontend as Frontend (React)
    participant Local as LocalStorage/Session
    participant API as Axios Client
    participant Backend as Backend (Spring Boot)

    User->>Frontend: Open app / submit login
    Frontend->>API: POST /auth/login
    API->>Backend: Forward credentials
    Backend-->>API: Return JWT
    API-->>Frontend: Response with token
    Frontend->>Local: Store token (local/session)
    Frontend->>Frontend: Decode token (name, userId, role, clinicId?)
    Frontend->>API: GET /api/pets (Authorization: Bearer ...)
    API->>Backend: Validate token, fetch pets
    Backend-->>API: Return pet list
    API-->>Frontend: Pet data
    Frontend-->>User: Render role-specific dashboard
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Frontend as Frontend (React)
    participant API as Axios Client
    participant Backend as Backend (Spring Boot)

    User->>Frontend: Open "Create case" form
    Frontend->>API: GET /api/clinics
    API->>Backend: Fetch clinics
    Backend-->>API: Clinics list
    User->>Frontend: Submit case + attachments
    Frontend->>API: POST /api/medical-records
    API->>Backend: Create record -> return id
    Frontend->>API: POST /api/attachments (multipart) [repeat per file]
    API->>Backend: Store attachment(s)
    Backend-->>API: Attachment metadata
    API-->>Frontend: Attachments saved
    Frontend-->>User: Redirect to case detail with timeline
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • lindaeskilsson
  • TatjanaTrajkovic

Poem

🐰 I hop with keys and tiny paws,
New pages bloom and backend laws.
Tokens snug in hidden store,
Pets, cases, logs — and dashboards more.
Hooray — a patch to help you soar!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/front end react' is vague and generic, using broad terms that don't convey the specific changes made in this substantial PR. Replace with a more descriptive title that summarizes the main change, such as 'Add React frontend application with authentication and pet management UI' or 'Initialize React frontend with Vite, Tailwind CSS, and core dashboard components'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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/FrontEndReact

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: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

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/data.sql (1)

22-48: ⚠️ Potential issue | 🟡 Minor

Inconsistent ON CONFLICT updates across seed users.

Anna's UPSERT (Line 22) now updates password_hash on conflict, but Erik's (Line 36) and Sara's (Line 48) do not. If the bcrypt hashes for those accounts are ever rotated in this file, existing rows will silently keep the old hash and authentication will diverge between a fresh DB and an existing one. Apply the same fix to Erik and Sara for consistency.

🔧 Suggested fix
-    ON CONFLICT (email) DO UPDATE SET id = EXCLUDED.id, name = EXCLUDED.name;
+    ON CONFLICT (email) DO UPDATE SET id = EXCLUDED.id, name = EXCLUDED.name, password_hash = EXCLUDED.password_hash;

(apply to both the Vet Erik and Admin Sara inserts)

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

In `@src/main/resources/data.sql` around lines 22 - 48, The Vet Erik and Admin
Sara INSERT ... ON CONFLICT blocks currently only update id and name on email
conflict; add password_hash to their DO UPDATE SET so the clause matches Anna's
behavior (i.e., for the users inserts for 'Erik Veterinär' and 'Sara Admin'
update id, name, password_hash = EXCLUDED.password_hash on conflict) to ensure
rotated bcrypt hashes in the seed file replace existing rows' password hashes.
🟡 Minor comments (8)
frontend/src/components/Layout.jsx-29-135 (1)

29-135: ⚠️ Potential issue | 🟡 Minor

No navigation/logout available on mobile.

The sidebar is hidden md:flex and there is no alternative mobile header/drawer, so on screens < md users see only the main content with no way to navigate between views or log out. Consider adding a mobile top bar with a hamburger toggle that opens this sidebar.

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

In `@frontend/src/components/Layout.jsx` around lines 29 - 135, The mobile view
lacks navigation and logout because the sidebar is hidden via "hidden md:flex"
with no mobile alternative; add a mobile top bar (e.g., inside Layout.jsx) that
contains a hamburger button which toggles a drawer or mobile menu rendering the
same nav buttons (reuse getLinkStyle, onNavigate, userRole checks, and onLogout)
and ensure the drawer mounts the same button elements (Home, my-pets/my-cases,
vet/admin sections) so behavior and active styles match the desktop sidebar;
implement local state (e.g., isMobileMenuOpen) and a toggle handler to control
visibility and close on navigation.
frontend/src/components/PetForm.jsx-111-118 (1)

111-118: ⚠️ Potential issue | 🟡 Minor

No upper-bound validation on date of birth.

The date input has no max, so users can submit a future birth date. Add max={new Date().toISOString().split('T')[0]} (or validate in handleSubmit) to prevent invalid records from reaching the backend.

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

In `@frontend/src/components/PetForm.jsx` around lines 111 - 118, The date input
for pet DOB in PetForm.jsx allows future dates; constrain it by adding a max
attribute computed as today's date (e.g., max={new
Date().toISOString().split('T')[0]}) on the input bound to formData.dob, and
also add a defensive check in handleSubmit to reject or normalize submissions
where formData.dob > today (return validation error or set error state) to
ensure no future DOBs reach the backend; update references in the component (the
<input> using value={formData.dob} and onChange that calls setFormData, and the
handleSubmit function) accordingly.
frontend/src/pages/AdminDashboard.jsx-4-69 (1)

4-69: ⚠️ Potential issue | 🟡 Minor

Hardcoded/mock data — wire to backend before release.

Stats ('12', '1,402', '89') and the clinic row are fully hardcoded, and the Redigera/Radera/+ Ny Klinik buttons are non-functional. This is fine as a scaffold, but should not ship as the admin dashboard.

Want me to sketch the admin data-fetching hook and wire these cards/table to a backend endpoint?

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

In `@frontend/src/pages/AdminDashboard.jsx` around lines 4 - 69, The current
AdminDashboard.jsx uses hardcoded stats and a hardcoded clinic row (the stats
array and the table tbody) and non-functional buttons; replace these with a
data-fetching hook (e.g., create useAdminDashboard or useClinics) that calls
your backend endpoints (e.g., GET /api/admin/stats and GET /api/admin/clinics)
and returns {stats, clinics, loading, error} and actions (createClinic,
updateClinic, deleteClinic); wire the component to useAdminDashboard: replace
the hardcoded stats array with the hook-provided stats used in the stats.map
render, replace the hardcoded table body with clinics.map rendering each clinic
row, and replace the + Ny Klinik, Redigera and Radera buttons with handlers that
call createClinic/updateClinic/deleteClinic and show optimistic UI or
loading/error states using the hook's loading/error values. Ensure you reference
and update the symbols stats, clinics, useAdminDashboard (or useClinics),
createClinic, updateClinic, and deleteClinic in the component and add basic
error/loading UI.
src/main/java/org/example/vet1177/controller/PetController.java-34-45 (1)

34-45: ⚠️ Potential issue | 🟡 Minor

Avoid logging user email at INFO level.

Logging currentUser.getEmail() leaks PII into logs and is inconsistent with the other endpoints in this controller, which log user IDs (UUIDs) instead. Consider logging currentUser.getId() or dropping to DEBUG.

🔒 Proposed fix
-        log.info("GET /api/pets - Fetching all pets for user: {}", currentUser.getEmail());
+        log.info("GET /api/pets - fetching pets for userId={}", currentUser.getId());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/controller/PetController.java` around lines
34 - 45, The log statement in PetController.getAllMyPets currently logs PII via
currentUser.getEmail(); change the log to avoid email exposure by using
currentUser.getId() (UUID) or lowering to DEBUG level. Update the log.info call
(log.info("GET /api/pets - Fetching all pets for user: {}",
currentUser.getEmail())) to either log the ID (currentUser.getId()) at INFO or
keep email out and use log.debug if you need the email, ensuring consistency
with other endpoints that log user IDs.
frontend/README.md-30-42 (1)

30-42: ⚠️ Potential issue | 🟡 Minor

Fix stale file path references and minor typos.

  • Lines 30 and 42 reference src/services/api.js, but the actual file added in this PR is src/services/api.jsx.
  • Line 18: "Se till at du är" → "Se till att du är" (missing t).
  • Line 5 has a double space after ## (markdownlint MD019).
📝 Proposed fixes
-##  Kom igång
+## Kom igång
...
-Se till at du är i frontend-mappen när du kör detta:
+Se till att du är i frontend-mappen när du kör detta:
...
-API-anrop: Vi använder en Axios-interceptor (se src/services/api.js) som automatiskt bifogar din token i Authorization-headern på varje anrop:
+API-anrop: Vi använder en Axios-interceptor (se src/services/api.jsx) som automatiskt bifogar din token i Authorization-headern på varje anrop:
...
-**src/services/api.js:** All kommunikation med backend (Axios-instans).
+**src/services/api.jsx:** All kommunikation med backend (Axios-instans).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/README.md` around lines 30 - 42, Update the README to point to the
actual service file name (replace references to src/services/api.js with
src/services/api.jsx), fix the Swedish typo "Se till at du är" to "Se till att
du är", and remove the extra space after "##" (fix Markdown lint MD019); locate
these strings in the provided README content and apply the three textual changes
so the docs match the added file and correct typos.
frontend/src/pages/Register.jsx-5-10 (1)

5-10: ⚠️ Potential issue | 🟡 Minor

Remove the hardcoded role field from formData.

The backend's RegisterRequest DTO accepts only name, email, and password. The role field is ignored by the backend, which hardcodes Role.OWNER server-side. Remove role: 'OWNER' from the form state and stop sending it in the POST request to avoid unnecessary data transmission.

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

In `@frontend/src/pages/Register.jsx` around lines 5 - 10, Remove the hardcoded
role from the form state and stop sending it to the backend: remove "role:
'OWNER'" from the useState initial value for formData and update any state
updates that reference formData.role, and update the submit logic (e.g., the
function handling form submission such as handleSubmit or the fetch/POST body
that uses formData) to only serialize name, email, and password when building
the request payload; ensure setFormData and any input bindings still only touch
name, email, and password fields.
frontend/src/pages/CaseDetail.jsx-55-67 (1)

55-67: ⚠️ Potential issue | 🟡 Minor

Prevent duplicate comment submissions.

newMessage is only cleared after the request resolves, so repeated Enter/clicks can post the same comment multiple times.

🐛 Proposed send lock
     const [attachments, setAttachments] = useState([]);
     const [isUploading, setIsUploading] = useState(false);
+    const [isSending, setIsSending] = useState(false);
     const [loading, setLoading] = useState(true);
...
     const handleSendMessage = async () => {
-        if (!newMessage.trim()) return;
+        if (!newMessage.trim() || isSending) return;
         try {
+            setIsSending(true);
             const res = await commentService.createComment({
                 recordId: caseData.id,
                 body: newMessage
             });
             setTimeline(prev => [...prev, { ...res.data, type: 'COMMENT' }]);
             setNewMessage('');
         } catch (error) {
             alert("Kunde inte skicka meddelande.");
+        } finally {
+            setIsSending(false);
         }
     };
...
-                            <button onClick={handleSendMessage} disabled={!newMessage.trim()} className="bg-vet-navy text-white px-6 py-2 rounded-lg hover:bg-vet-accent transition-all font-bold text-xs uppercase tracking-widest shadow-md disabled:opacity-30">Skicka</button>
+                            <button onClick={handleSendMessage} disabled={!newMessage.trim() || isSending} className="bg-vet-navy text-white px-6 py-2 rounded-lg hover:bg-vet-accent transition-all font-bold text-xs uppercase tracking-widest shadow-md disabled:opacity-30">Skicka</button>

Also applies to: 178-180

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

In `@frontend/src/pages/CaseDetail.jsx` around lines 55 - 67, handleSendMessage
can submit duplicate comments because newMessage is only cleared after the async
call; add a send lock (e.g., isSending state) to early-return if a send is in
progress, set isSending = true immediately before calling
commentService.createComment and reset it in finally, and also clear newMessage
only once at a successful send; update UI controls (send button/Enter handler)
to respect isSending and disable while true. Reference: handleSendMessage,
newMessage, setNewMessage, setTimeline, commentService.createComment.
frontend/src/pages/CreateCase.jsx-137-142 (1)

137-142: ⚠️ Potential issue | 🟡 Minor

Use a semantic file-upload control.

The clickable <div> is not keyboard-accessible. Wrap the input with a <label> or use a button that handles Enter/Space.

♿ Proposed accessible markup
-                        <div onClick={() => fileInputRef.current.click()} className="border-2 border-dashed border-slate-200 rounded-xl p-6 text-center hover:bg-slate-50 cursor-pointer relative">
+                        <label className="block border-2 border-dashed border-slate-200 rounded-xl p-6 text-center hover:bg-slate-50 cursor-pointer relative">
                             <input type="file" ref={fileInputRef} multiple onChange={handleFileChange} className="hidden" />
                             <p className="text-sm text-slate-500">{files.length > 0 ? `${files.length} filer valda` : 'Klicka för att ladda upp'}</p>
-                        </div>
+                        </label>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/CreateCase.jsx` around lines 137 - 142, The current
clickable <div> for attachments isn't keyboard-accessible; update the markup so
the file input is triggered by a semantic control: either wrap the hidden <input
type="file" ref={fileInputRef} ...> and its contents inside a <label
htmlFor="attachments"> (give the input an id like "attachments") or replace the
div with a <button> that calls fileInputRef.current.click() and handles
Enter/Space, while keeping onChange={handleFileChange} and the files state
display intact; ensure you remove the onClick from the non-semantic div and
preserve classes, aria-label/role if needed so file selection via keyboard and
screen readers works.
🧹 Nitpick comments (5)
src/main/java/org/example/vet1177/entities/ActivityLog.java (1)

28-51: Minor: add a getter for entityId and guard against a null medicalRecord.

Two small points on the new entityId:

  1. There is no getEntityId() accessor, so the column cannot be read back from the entity (and won't surface in any future response DTO). Add one for symmetry with the other fields.
  2. this.entityId = medicalRecord.getId() in the constructor will NPE if a caller ever passes null. ActivityLogService.log(...) does guard against this today, but the constructor is public and should be defensive — or at minimum documented. Also, line 51 reassigns entityType to the same literal used as a field initializer on line 26, which is redundant.
🔧 Suggested tweaks
     public ActivityLog(ActivityType action, String description, User performedBy, MedicalRecord medicalRecord) {
+        if (medicalRecord == null) {
+            throw new IllegalArgumentException("medicalRecord must not be null");
+        }
         this.action = action;
         this.description = description;
         this.performedBy = performedBy;
         this.medicalRecord = medicalRecord;
         this.entityId = medicalRecord.getId();
-        this.entityType = "MEDICAL_RECORD";
     }
@@
     public MedicalRecord getMedicalRecord() { return medicalRecord; }
+
+    public UUID getEntityId() { return entityId; }
+
+    public String getEntityType() { return entityType; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/entities/ActivityLog.java` around lines 28
- 51, Add a public getter for the entityId field (getEntityId) for symmetry and
persistence access, and make the ActivityLog constructor defensive: in the
ActivityLog(ActivityType action, String description, User performedBy,
MedicalRecord medicalRecord) constructor check for a null medicalRecord before
calling medicalRecord.getId() (either throw a clear IllegalArgumentException or
assign entityId to null based on desired behavior) to avoid an NPE; also remove
the redundant reassignment of entityType in that constructor since it’s already
initialized on the field.
frontend/eslint.config.js (1)

16-23: Nit: conflicting ecmaVersion values.

languageOptions.ecmaVersion is set to 2020 while parserOptions.ecmaVersion is 'latest'. parserOptions will win in practice, but the inconsistency is confusing — unify both to 'latest' (or the specific year you target).

🔧 Suggested fix
     languageOptions: {
-      ecmaVersion: 2020,
+      ecmaVersion: 'latest',
       globals: globals.browser,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/eslint.config.js` around lines 16 - 23, Unify the conflicting
ECMAScript version settings by setting both languageOptions.ecmaVersion and
parserOptions.ecmaVersion to the same value (e.g., 'latest' or a specific year
like 2020); update the languageOptions.ecmaVersion to match
parserOptions.ecmaVersion (or change parserOptions.ecmaVersion to match
languageOptions) so the configuration is consistent and unambiguous.
src/main/java/org/example/vet1177/services/PetService.java (1)

3-26: Prefer Spring's @Transactional over jakarta.transaction.Transactional, and mark read paths readOnly.

Applying jakarta.transaction.Transactional at the class level works, but it has two downsides vs. org.springframework.transaction.annotation.Transactional in a Spring Boot app:

  • It does not support readOnly = true, so read-only methods like getPetById / getPetsByOwner now run in a full read-write transaction, losing the JPA/Hibernate read-only optimizations and flush skipping.
  • Its rollback semantics differ from Spring's (rolls back on any unchecked exception, but with fewer configuration hooks such as rollbackFor/noRollbackFor).

Recommend switching the import and annotating read methods explicitly:

🔧 Suggested change
-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;
@@
 `@Service`
-@Transactional
+@Transactional
 public class PetService {
@@
-    public Pet getPetById(UUID petId, User currentUser) {
+    `@Transactional`(readOnly = true)
+    public Pet getPetById(UUID petId, User currentUser) {
@@
-    public List<Pet> getPetsByOwner(UUID currentUserId, UUID ownerId) {
+    `@Transactional`(readOnly = true)
+    public List<Pet> getPetsByOwner(UUID currentUserId, UUID ownerId) {

The existing @Transactional on deletePet (line 135) should also be switched to the Spring annotation for consistency.

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

In `@src/main/java/org/example/vet1177/services/PetService.java` around lines 3 -
26, Replace the class-level jakarta.transaction.Transactional with
org.springframework.transaction.annotation.Transactional and update transaction
semantics: change the import to
org.springframework.transaction.annotation.Transactional on PetService, annotate
read methods (getPetById, getPetsByOwner) with `@Transactional`(readOnly = true)
instead of relying on the class-level default, and ensure mutating methods
(including deletePet) use the Spring `@Transactional` (keep class-level annotation
optional) so they have Spring rollback/rollbackFor capabilities and consistent
behavior across the service.
frontend/src/components/Layout.jsx (1)

60-60: Role substring matching is brittle.

userRole?.includes('OWNER') || userRole?.includes('VET') works today but silently matches any future role containing those substrings (e.g., ROLE_VET_ADMIN, ROLE_PET_OWNER_LEGACY). Prefer exact comparisons consistent with lines 85 and 96.

♻️ Proposed refactor
-                        {(userRole?.includes('OWNER') || userRole?.includes('VET')) && (
+                        {(userRole === 'ROLE_OWNER' || userRole === 'ROLE_VET') && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Layout.jsx` at line 60, The conditional using
userRole?.includes('OWNER') || userRole?.includes('VET') is brittle; change it
to exact equality checks consistent with other conditionals (e.g., compare
userRole === 'OWNER' || userRole === 'VET' or if userRole is an array, use
explicit array membership checks) so roles like 'ROLE_VET_ADMIN' won't match;
update the JSX conditional that references userRole in Layout.jsx to use exact
comparisons (matching the approach used at the other checks around the
component).
frontend/src/components/PetForm.jsx (1)

27-38: Replace alert() with an inline error, and drop debug console.log of the payload.

Login.jsx and Register.jsx already render errors inline via an error state — this form should do the same for consistency and better UX. Also, console.log("Skickar till backend:", petRequest) is debug noise that should be removed (or gated) before shipping.

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

In `@frontend/src/components/PetForm.jsx` around lines 27 - 38, Remove the debug
console.log of the payload and replace the alert-based error handling with an
inline error state similar to Login.jsx/Register.jsx: add an error state
variable (e.g., error and setError) in the PetForm component, clear it on submit
(before calling petService.createPet), remove console.log("Skickar till
backend:", petRequest), set error via setError(error.message || a friendly
message) inside the catch block instead of calling alert, and render that error
state in the component JSX (near the form submit button or top of the form) so
users see the validation/server error inline; ensure setIsSubmitting(false)
remains in finally and call onSave() only on successful createPet.
🤖 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/package.json`:
- Around line 12-29: The package.json is missing the Tailwind/PostCSS toolchain
so Vite will fail processing the Tailwind directives; update the
"devDependencies" block in frontend's package.json to add "tailwindcss",
"postcss", and "autoprefixer" (compatible with Tailwind v3), then reinstall dev
deps so the existing frontend/tailwind.config.js and frontend/postcss.config.js
and the `@tailwind` directives in src/index.css can be processed correctly.

In `@frontend/src/App.jsx`:
- Around line 125-159: The declaration const filteredRecords inside the case
'pet-detail' shares lexical scope across switch clauses and violates the
noSwitchDeclarations rule; fix it by wrapping the entire case body in its own
block (add { ... } after case 'pet-detail':) so filteredRecords and any other
locals (e.g., the async onCaseClick handler that calls
medicalRecordService.getRecordById, setSelectedRecord, setCurrentView,
setSelectedPet) are scoped to that block and the return <PetDetail ... />
remains inside the block.
- Around line 97-101: The switch over currentView in App.jsx is missing a
'vet-dashboard' case and falls through to the default which renders
OwnerDashboard with viewMode="vet-dashboard" (resulting in a blank page). Add an
explicit case for 'vet-dashboard' in the switch (before the default) and either
render the proper VetDashboard component (or redirect/set currentView to a
supported view) so vets are routed to a valid view; also consider updating
Layout.jsx to hide the vet nav item until VetDashboard is implemented. Ensure
you reference the currentView switch, the OwnerDashboard render that receives
viewMode="vet-dashboard", and Layout.jsx when making the change.

In `@frontend/src/pages/CreateCase.jsx`:
- Around line 39-50: The form currently allows submitting when selectedPet is
null, sending petId: null; update the handleSubmit function to guard against a
missing pet by checking selectedPet (the same place you check selectedClinic)
and aborting with an alert/return if it's falsy; ensure you don't set
isSubmitting or call the API when selectedPet is missing so the submission state
isn't left true (adjust setIsSubmitting flow in handleSubmit accordingly).
- Around line 45-65: The current flow always calls
medicalRecordService.createRecord even when existingCase exists and creates
duplicate records if attachments fail; change the logic to call
medicalRecordService.updateRecord when existingCase is present (use
existingCase.id) and call createRecord only when creating a new case (no
existingCase). Additionally, ensure attachments are uploaded inside a try/catch
and only call onSave after all uploads succeed; if createRecord succeeded but
any attachment upload fails, roll back by calling a cleanup method (e.g.,
medicalRecordService.deleteRecord(recordId)) or otherwise handle cleanup so
retrying the form does not create duplicates; reference functions/variables:
createRecord, updateRecord, deleteRecord (or cleanup), attachmentService.upload,
existingCase, recordId, files, onSave.

In `@frontend/src/pages/Login.jsx`:
- Around line 16-21: The token extraction in the login flow is fragile and may
store "[object Object]"; update the login handling where you call
authService.login (the response variable in Login.jsx) to validate and normalize
the token before calling localStorage.setItem('token', ...). Specifically,
inspect response.data and prefer known string fields (e.g., response.data.token,
response.data.accessToken, response.data.jwt) and only use a value if typeof
value === 'string'; if response.data is a string use it, otherwise throw or
handle an error path (do not stringify objects). Ensure this logic is applied
right before localStorage.setItem and keep onLoginSuccess() only called after a
valid string token is stored.

In `@frontend/src/pages/OwnerDashboard.jsx`:
- Around line 51-54: The clickable card currently rendered as a <div> with
key={record.id} and onClick={() => onCaseClick(record)} should be a semantic
<button type="button"> to restore keyboard navigation and accessibility; replace
the <div> wrapper with a <button type="button"> keeping the onClick handler
(onCaseClick), key, and all className styles but add the utility classes "w-full
text-left" so the visual layout doesn't change, and retain any hover/transition
classes. Apply the same change to the other clickable card instance (the similar
block that uses onPetClick/onCaseClick around record.id at the 95-98 region).

In `@frontend/src/pages/PetDetail.jsx`:
- Around line 6-7: Remove the debug console.log in PetDetail.jsx that prints
sensitive objects (console.log("PetDetail laddas med:", { pet, petRecords,
STATUS_MAP })); either delete the statement outright or wrap it behind a
development-only guard (e.g., check process.env.NODE_ENV === 'development' or a
DEBUG flag) so that pet, petRecords and STATUS_MAP are never logged in
production; update the PetDetail component accordingly to eliminate the privacy
risk.
- Around line 82-85: The journal card is a clickable <div> (key={record.id})
using onCaseClick(record) which is not keyboard-accessible; replace or update
that element so it behaves as a semantic interactive control: either render a
<button> or an element with role="button", tabIndex={0}, an accessible label
(aria-label or aria-labelledby), and handle Enter/Space in an onKeyDown that
calls onCaseClick(record). Ensure existing className, onClick, and key usage
remain, and keep focus styles for keyboard users.

In `@frontend/src/pages/Register.jsx`:
- Around line 19-33: The current Register.jsx token handling uses
`response.data.token || response.data` which can store an object as a token and
silently do nothing when no token is returned; update the registration handler
(the async submit function that calls `api.post('/auth/register', formData)`) to
explicitly check for a string token (e.g. `response.data.token` exists and
typeof is "string"), only call `localStorage.setItem('token', token)` and
`window.location.reload()` when a valid string token is present, and otherwise
handle the "no token" path by either redirecting the user to the login page or
setting an explicit error via `setError(...)` so the user gets feedback; ensure
`setLoading(false)` still runs in finally and avoid storing non-string
`response.data` values as the token.

In `@frontend/src/services/api.jsx`:
- Around line 30-36: The response interceptor currently removes the token and
reloads the app on any 401 (api.interceptors.response.use), which also fires for
failed logins and prevents Login.jsx from showing errors; change the interceptor
to only treat 401 as session-expiry for authenticated requests (e.g. skip if
error.config?.url matches auth endpoints like '/login' or '/register' or when
there was no token in localStorage). When you detect a real session expiry,
remove the token (localStorage.removeItem('token')) and dispatch a logout event
(new CustomEvent('auth:logout')) instead of calling window.location.reload();
leave other 401s to reject normally so Login.jsx can handle them. Update App.jsx
to listen for 'auth:logout' and call handleLogout() to complete UI teardown and
navigation.
- Around line 4-8: The base URL is hardcoded in the axios instance (const api =
axios.create({...})) which will break non-local and HTTPS deployments; change
the baseURL to read from the Vite env var (import.meta.env.VITE_API_BASE_URL)
with a safe fallback (e.g., 'http://localhost:8080/api') and use that value when
creating the axios instance in frontend/src/services/api.jsx so deployments can
override the URL via VITE_API_BASE_URL.

In `@frontend/tailwind.config.js`:
- Around line 1-18: Add the missing tailwindcss package to devDependencies in
package.json so PostCSS and the `@tailwind` directives work: open package.json and
under "devDependencies" add "tailwindcss" with an appropriate version (or run
the installer), then run the install command to update lockfile; verify
postcss.config.js, tailwind.config.js, and src/index.css (the `@tailwind`
directives) remain unchanged and that the build succeeds.

In `@package.json`:
- Around line 1-7: Move the devDependencies "autoprefixer", "postcss", and
"tailwindcss" out of the root package.json devDependencies and add them to
frontend/package.json devDependencies (preserving the versions), since
frontend/tailwind.config.js and frontend/postcss.config.js expect those
packages; alternatively, if you prefer a monorepo workspace, configure the root
package.json workspaces to include the frontend and adjust root build scripts so
npm install/build from frontend will resolve those packages—ensure the three
package names ("autoprefixer", "postcss", "tailwindcss") are declared in
frontend/package.json or made available via workspace config.

In `@src/main/java/org/example/vet1177/controller/MedicalRecordController.java`:
- Line 23: Remove the redundant controller-level CORS annotation: delete the
`@CrossOrigin`(origins = "http://localhost:5173") annotation from the
MedicalRecordController class so it no longer overrides or narrows the
centralized policy; rely on the existing
SecurityConfig.corsConfigurationSource() which registers allowed origins for
/api/** (including both http://localhost:5173 and http://localhost:3000).

---

Outside diff comments:
In `@src/main/resources/data.sql`:
- Around line 22-48: The Vet Erik and Admin Sara INSERT ... ON CONFLICT blocks
currently only update id and name on email conflict; add password_hash to their
DO UPDATE SET so the clause matches Anna's behavior (i.e., for the users inserts
for 'Erik Veterinär' and 'Sara Admin' update id, name, password_hash =
EXCLUDED.password_hash on conflict) to ensure rotated bcrypt hashes in the seed
file replace existing rows' password hashes.

---

Minor comments:
In `@frontend/README.md`:
- Around line 30-42: Update the README to point to the actual service file name
(replace references to src/services/api.js with src/services/api.jsx), fix the
Swedish typo "Se till at du är" to "Se till att du är", and remove the extra
space after "##" (fix Markdown lint MD019); locate these strings in the provided
README content and apply the three textual changes so the docs match the added
file and correct typos.

In `@frontend/src/components/Layout.jsx`:
- Around line 29-135: The mobile view lacks navigation and logout because the
sidebar is hidden via "hidden md:flex" with no mobile alternative; add a mobile
top bar (e.g., inside Layout.jsx) that contains a hamburger button which toggles
a drawer or mobile menu rendering the same nav buttons (reuse getLinkStyle,
onNavigate, userRole checks, and onLogout) and ensure the drawer mounts the same
button elements (Home, my-pets/my-cases, vet/admin sections) so behavior and
active styles match the desktop sidebar; implement local state (e.g.,
isMobileMenuOpen) and a toggle handler to control visibility and close on
navigation.

In `@frontend/src/components/PetForm.jsx`:
- Around line 111-118: The date input for pet DOB in PetForm.jsx allows future
dates; constrain it by adding a max attribute computed as today's date (e.g.,
max={new Date().toISOString().split('T')[0]}) on the input bound to
formData.dob, and also add a defensive check in handleSubmit to reject or
normalize submissions where formData.dob > today (return validation error or set
error state) to ensure no future DOBs reach the backend; update references in
the component (the <input> using value={formData.dob} and onChange that calls
setFormData, and the handleSubmit function) accordingly.

In `@frontend/src/pages/AdminDashboard.jsx`:
- Around line 4-69: The current AdminDashboard.jsx uses hardcoded stats and a
hardcoded clinic row (the stats array and the table tbody) and non-functional
buttons; replace these with a data-fetching hook (e.g., create useAdminDashboard
or useClinics) that calls your backend endpoints (e.g., GET /api/admin/stats and
GET /api/admin/clinics) and returns {stats, clinics, loading, error} and actions
(createClinic, updateClinic, deleteClinic); wire the component to
useAdminDashboard: replace the hardcoded stats array with the hook-provided
stats used in the stats.map render, replace the hardcoded table body with
clinics.map rendering each clinic row, and replace the + Ny Klinik, Redigera and
Radera buttons with handlers that call createClinic/updateClinic/deleteClinic
and show optimistic UI or loading/error states using the hook's loading/error
values. Ensure you reference and update the symbols stats, clinics,
useAdminDashboard (or useClinics), createClinic, updateClinic, and deleteClinic
in the component and add basic error/loading UI.

In `@frontend/src/pages/CaseDetail.jsx`:
- Around line 55-67: handleSendMessage can submit duplicate comments because
newMessage is only cleared after the async call; add a send lock (e.g.,
isSending state) to early-return if a send is in progress, set isSending = true
immediately before calling commentService.createComment and reset it in finally,
and also clear newMessage only once at a successful send; update UI controls
(send button/Enter handler) to respect isSending and disable while true.
Reference: handleSendMessage, newMessage, setNewMessage, setTimeline,
commentService.createComment.

In `@frontend/src/pages/CreateCase.jsx`:
- Around line 137-142: The current clickable <div> for attachments isn't
keyboard-accessible; update the markup so the file input is triggered by a
semantic control: either wrap the hidden <input type="file" ref={fileInputRef}
...> and its contents inside a <label htmlFor="attachments"> (give the input an
id like "attachments") or replace the div with a <button> that calls
fileInputRef.current.click() and handles Enter/Space, while keeping
onChange={handleFileChange} and the files state display intact; ensure you
remove the onClick from the non-semantic div and preserve classes,
aria-label/role if needed so file selection via keyboard and screen readers
works.

In `@frontend/src/pages/Register.jsx`:
- Around line 5-10: Remove the hardcoded role from the form state and stop
sending it to the backend: remove "role: 'OWNER'" from the useState initial
value for formData and update any state updates that reference formData.role,
and update the submit logic (e.g., the function handling form submission such as
handleSubmit or the fetch/POST body that uses formData) to only serialize name,
email, and password when building the request payload; ensure setFormData and
any input bindings still only touch name, email, and password fields.

In `@src/main/java/org/example/vet1177/controller/PetController.java`:
- Around line 34-45: The log statement in PetController.getAllMyPets currently
logs PII via currentUser.getEmail(); change the log to avoid email exposure by
using currentUser.getId() (UUID) or lowering to DEBUG level. Update the log.info
call (log.info("GET /api/pets - Fetching all pets for user: {}",
currentUser.getEmail())) to either log the ID (currentUser.getId()) at INFO or
keep email out and use log.debug if you need the email, ensuring consistency
with other endpoints that log user IDs.

---

Nitpick comments:
In `@frontend/eslint.config.js`:
- Around line 16-23: Unify the conflicting ECMAScript version settings by
setting both languageOptions.ecmaVersion and parserOptions.ecmaVersion to the
same value (e.g., 'latest' or a specific year like 2020); update the
languageOptions.ecmaVersion to match parserOptions.ecmaVersion (or change
parserOptions.ecmaVersion to match languageOptions) so the configuration is
consistent and unambiguous.

In `@frontend/src/components/Layout.jsx`:
- Line 60: The conditional using userRole?.includes('OWNER') ||
userRole?.includes('VET') is brittle; change it to exact equality checks
consistent with other conditionals (e.g., compare userRole === 'OWNER' ||
userRole === 'VET' or if userRole is an array, use explicit array membership
checks) so roles like 'ROLE_VET_ADMIN' won't match; update the JSX conditional
that references userRole in Layout.jsx to use exact comparisons (matching the
approach used at the other checks around the component).

In `@frontend/src/components/PetForm.jsx`:
- Around line 27-38: Remove the debug console.log of the payload and replace the
alert-based error handling with an inline error state similar to
Login.jsx/Register.jsx: add an error state variable (e.g., error and setError)
in the PetForm component, clear it on submit (before calling
petService.createPet), remove console.log("Skickar till backend:", petRequest),
set error via setError(error.message || a friendly message) inside the catch
block instead of calling alert, and render that error state in the component JSX
(near the form submit button or top of the form) so users see the
validation/server error inline; ensure setIsSubmitting(false) remains in finally
and call onSave() only on successful createPet.

In `@src/main/java/org/example/vet1177/entities/ActivityLog.java`:
- Around line 28-51: Add a public getter for the entityId field (getEntityId)
for symmetry and persistence access, and make the ActivityLog constructor
defensive: in the ActivityLog(ActivityType action, String description, User
performedBy, MedicalRecord medicalRecord) constructor check for a null
medicalRecord before calling medicalRecord.getId() (either throw a clear
IllegalArgumentException or assign entityId to null based on desired behavior)
to avoid an NPE; also remove the redundant reassignment of entityType in that
constructor since it’s already initialized on the field.

In `@src/main/java/org/example/vet1177/services/PetService.java`:
- Around line 3-26: Replace the class-level jakarta.transaction.Transactional
with org.springframework.transaction.annotation.Transactional and update
transaction semantics: change the import to
org.springframework.transaction.annotation.Transactional on PetService, annotate
read methods (getPetById, getPetsByOwner) with `@Transactional`(readOnly = true)
instead of relying on the class-level default, and ensure mutating methods
(including deletePet) use the Spring `@Transactional` (keep class-level annotation
optional) so they have Spring rollback/rollbackFor capabilities and consistent
behavior across the service.
🪄 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: d22c25ec-5dce-431f-84db-5ea021bd45a0

📥 Commits

Reviewing files that changed from the base of the PR and between caec9c7 and 61bc837.

⛔ Files ignored due to path filters (7)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/public/favicon.svg is excluded by !**/*.svg
  • frontend/public/icons.svg is excluded by !**/*.svg
  • frontend/src/assets/hero.png is excluded by !**/*.png
  • frontend/src/assets/react.svg is excluded by !**/*.svg
  • frontend/src/assets/vite.svg is excluded by !**/*.svg
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • .gitignore
  • frontend/.gitignore
  • frontend/README.md
  • frontend/eslint.config.js
  • frontend/index.html
  • frontend/package.json
  • frontend/postcss.config.js
  • frontend/src/App.css
  • frontend/src/App.jsx
  • frontend/src/components/Layout.jsx
  • frontend/src/components/PetForm.jsx
  • frontend/src/components/PetList.jsx
  • frontend/src/index.css
  • frontend/src/main.jsx
  • frontend/src/pages/AdminDashboard.jsx
  • frontend/src/pages/CaseDetail.jsx
  • frontend/src/pages/CreateCase.jsx
  • frontend/src/pages/Login.jsx
  • frontend/src/pages/OwnerDashboard.jsx
  • frontend/src/pages/PetDetail.jsx
  • frontend/src/pages/Register.jsx
  • frontend/src/pages/VetDashboard.jsx
  • frontend/src/services/api.jsx
  • frontend/src/utils/statusHelper.js
  • frontend/tailwind.config.js
  • frontend/vite.config.js
  • package.json
  • src/main/java/org/example/vet1177/controller/MedicalRecordController.java
  • src/main/java/org/example/vet1177/controller/PetController.java
  • src/main/java/org/example/vet1177/entities/ActivityLog.java
  • src/main/java/org/example/vet1177/security/JwtService.java
  • src/main/java/org/example/vet1177/services/PetService.java
  • src/main/resources/data.sql

Comment thread frontend/package.json
Comment thread frontend/src/App.jsx Outdated
Comment thread frontend/src/App.jsx Outdated
Comment thread frontend/src/pages/CreateCase.jsx Outdated
Comment thread frontend/src/pages/CreateCase.jsx
Comment thread frontend/src/services/api.jsx
Comment thread frontend/src/services/api.jsx
Comment thread frontend/tailwind.config.js
Comment thread package.json
Comment thread src/main/java/org/example/vet1177/controller/MedicalRecordController.java Outdated

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

🧹 Nitpick comments (7)
frontend/src/pages/OwnerDashboard.jsx (1)

116-122: Add type="button" to the "Registrera nytt djur" action.

For consistency with the other two clickable cards on lines 51 and 96 and to avoid accidental form submission if this block is ever nested inside a <form>, explicitly declare the type.

♻️ Proposed tweak
                                 <button
+                                    type="button"
                                     onClick={onAddPet}
                                     className={`flex flex-col items-center justify-center p-6 border-2 border-dashed border-slate-200 rounded-xl hover:border-blue-400 hover:bg-blue-50 transition group ${isPetsView ? 'h-full' : 'w-full mt-4'}`}
                                 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/OwnerDashboard.jsx` around lines 116 - 122, The Add Pet
button lacks an explicit type, which can cause accidental form submissions;
update the <button> rendered in OwnerDashboard.jsx that uses onClick={onAddPet}
(the "Registrera nytt djur" button) to include type="button" so it behaves like
the other clickable cards and won't submit a surrounding form.
frontend/src/services/api.jsx (1)

3-10: Base URL fallback still points at http://localhost:8080.

Past feedback has been applied by reading VITE_API_BASE_URL. The remaining nit: the fallback is still http://localhost:8080/api, so a production build without an env file will ship a URL that breaks on HTTPS origins (mixed-content blocking) and for any user not running the backend locally. A relative '/api' fallback (proxied/rewritten by the hosting layer) is safer for deployments.

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

In `@frontend/src/services/api.jsx` around lines 3 - 10, The API_BASE_URL fallback
currently uses an absolute URL causing mixed-content and wrong-host issues;
change the fallback in the API_BASE_URL assignment (where
import.meta.env.VITE_API_BASE_URL is read) from 'http://localhost:8080/api' to a
relative '/api' so the axios instance (api) uses a proxied/rewrite-safe base
path in production and on HTTPS origins.
frontend/src/pages/PetDetail.jsx (1)

97-99: Minor: empty date placeholder inconsistent with OwnerDashboard.

When record.createdAt is missing, this renders an empty string, whereas OwnerDashboard.jsx (line 70) shows 'Datum saknas'. Consider aligning for UX consistency.

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

In `@frontend/src/pages/PetDetail.jsx` around lines 97 - 99, The date display in
PetDetail.jsx currently renders an empty string when record.createdAt is falsy;
update the conditional in the JSX that renders new
Date(record.createdAt).toLocaleDateString('sv-SE') (the block referencing
record.createdAt) to instead render the same placeholder string used in
OwnerDashboard ("Datum saknas") when createdAt is missing so the UX is
consistent across views.
frontend/src/App.jsx (2)

62-64: fetchInitialData is a redundant wrapper.

It only awaits fetchData() with no additional logic. Consider inlining fetchData() at the call site on line 37 and removing this wrapper.

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

In `@frontend/src/App.jsx` around lines 62 - 64, Remove the redundant
fetchInitialData wrapper and replace its call sites with fetchData() directly:
delete the fetchInitialData function (which only awaits fetchData()) and update
wherever fetchInitialData is invoked to call fetchData() instead, ensuring any
imports/exports or references to fetchInitialData are also removed or updated to
fetchData.

204-212: Swallowed error in onCaseClick; also differs from the equivalent PetDetail path.

The pet-detail version (line 143) logs the error silently while this branch alerts but doesn't log. Pick one behavior (ideally: log + show a non-blocking banner) and apply it consistently at both call sites to keep failure modes debuggable and uniform.

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

In `@frontend/src/App.jsx` around lines 204 - 212, The onCaseClick handler
swallows errors by alerting without logging and differs from the pet-detail
path; replace the alert with the same error-handling used by the pet-detail
flow: call the shared non-blocking banner/displayError utility (or set an error
state like setBanner/setError) and log the error (e.g., console.error or
logger.error) when medicalRecordService.getRecordById fails, keeping
setSelectedRecord and setCurrentView behavior unchanged; ensure the same utility
is used by both the case-detail onCaseClick and the pet-detail path so failures
are logged and surfaced consistently.
frontend/src/pages/Register.jsx (2)

19-49: Token extraction hardened — LGTM, plus a small consistency nit.

The robust typeof checks and the fallback to onSwitchToLogin() address the earlier silent-failure concern. One minor nit: this file imports raw api and calls api.post('/auth/register', ...) directly, whereas api.jsx already exports authService.register(userData). Prefer the service wrapper so the endpoint path lives in one place.

♻️ Optional refactor
-import api from '../services/api';
+import { authService } from '../services/api';
...
-            const response = await api.post('/auth/register', formData);
+            const response = await authService.register(formData);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/Register.jsx` around lines 19 - 49, Replace the direct
call to api.post('/auth/register', formData) with the centralized service
wrapper authService.register(formData) to keep the endpoint path single-sourced;
update the import in Register.jsx to pull authService (or register) from
api.jsx/authService and call authService.register instead of api.post, leaving
the surrounding token-extraction, error handling (setError) and loading logic
(setLoading) and post-success behavior (localStorage.setItem, onSwitchToLogin)
unchanged so the component behavior stays identical while removing duplicated
endpoint strings.

36-40: Replace alert() with in-form messaging.

Using a blocking alert() for the "no token returned, please log in" path is jarring compared to the rest of the form that uses the inline error banner. Consider setting error (or a dedicated info state) and letting onSwitchToLogin() occur after acknowledgment, or display the banner on the Login screen via a prop/flag.

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

In `@frontend/src/pages/Register.jsx` around lines 36 - 40, Replace the blocking
alert in the Register.jsx success path with in-form non-blocking messaging:
instead of calling alert(...), set a component state (e.g., setError or
introduce setInfo) to show the same message as an inline banner inside the
Register form, and then call onSwitchToLogin() only after the user acknowledges
the banner (or pass an info flag/message via onSwitchToLogin(infoMessage) so the
Login component can display the inline banner). Update the rendering logic to
display the new info state in the same banner component used for errors and
ensure onSwitchToLogin() still performs the navigation once the message is
acknowledged.
🤖 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/App.jsx`:
- Around line 218-228: The login/register UI is rendering before auth state is
resolved because the component checks only currentUser; change the conditional
to gate rendering on the auth-loading flag so the auth screen is shown only
after loading completes (e.g., use the existing loading or an authChecked flag):
update the component render guard to return a placeholder (null/spinner) while
loading is true and only render the Login/Register block when loading is false
and currentUser is null; adjust references to isRegistering/setIsRegistering and
Login/Register usage so behavior is unchanged once authenticated.
- Around line 87-98: handleLogout currently clears auth token and currentUser
but leaves pet/record state which causes stale data to show; update handleLogout
to also clear myPets and myRecords and reset selectedPet and selectedRecord by
calling setMyPets([]), setMyRecords([]), setSelectedPet(null) and
setSelectedRecord(null) (same state setters used elsewhere) so no previous-user
data remains before fetchData runs.
- Around line 25-45: The effect inside useEffect decodes the token and always
calls fetchInitialData(), causing admins to trigger endpoints they shouldn't;
update the block after decoding (in the same useEffect where jwtDecode,
setCurrentUser, handleLogout are used) to only call fetchInitialData() when
decoded.role is 'ROLE_OWNER' or 'ROLE_VET', and call setLoading(false) for other
roles instead; keep existing error handling (console.error and handleLogout) and
the existing else branch that calls setLoading(false) when no token is present.

In `@frontend/src/services/api.jsx`:
- Around line 86-94: The upload call currently forces a Content-Type header that
breaks FormData boundary handling and the download call doesn't request binary;
remove the custom headers from the upload definition (the upload function
exported as upload) so axios can set multipart/form-data with its boundary, and
update the download function (download) to call api.get with { responseType:
'blob' } so binary file responses are preserved for URL.createObjectURL usage.

---

Nitpick comments:
In `@frontend/src/App.jsx`:
- Around line 62-64: Remove the redundant fetchInitialData wrapper and replace
its call sites with fetchData() directly: delete the fetchInitialData function
(which only awaits fetchData()) and update wherever fetchInitialData is invoked
to call fetchData() instead, ensuring any imports/exports or references to
fetchInitialData are also removed or updated to fetchData.
- Around line 204-212: The onCaseClick handler swallows errors by alerting
without logging and differs from the pet-detail path; replace the alert with the
same error-handling used by the pet-detail flow: call the shared non-blocking
banner/displayError utility (or set an error state like setBanner/setError) and
log the error (e.g., console.error or logger.error) when
medicalRecordService.getRecordById fails, keeping setSelectedRecord and
setCurrentView behavior unchanged; ensure the same utility is used by both the
case-detail onCaseClick and the pet-detail path so failures are logged and
surfaced consistently.

In `@frontend/src/pages/OwnerDashboard.jsx`:
- Around line 116-122: The Add Pet button lacks an explicit type, which can
cause accidental form submissions; update the <button> rendered in
OwnerDashboard.jsx that uses onClick={onAddPet} (the "Registrera nytt djur"
button) to include type="button" so it behaves like the other clickable cards
and won't submit a surrounding form.

In `@frontend/src/pages/PetDetail.jsx`:
- Around line 97-99: The date display in PetDetail.jsx currently renders an
empty string when record.createdAt is falsy; update the conditional in the JSX
that renders new Date(record.createdAt).toLocaleDateString('sv-SE') (the block
referencing record.createdAt) to instead render the same placeholder string used
in OwnerDashboard ("Datum saknas") when createdAt is missing so the UX is
consistent across views.

In `@frontend/src/pages/Register.jsx`:
- Around line 19-49: Replace the direct call to api.post('/auth/register',
formData) with the centralized service wrapper authService.register(formData) to
keep the endpoint path single-sourced; update the import in Register.jsx to pull
authService (or register) from api.jsx/authService and call authService.register
instead of api.post, leaving the surrounding token-extraction, error handling
(setError) and loading logic (setLoading) and post-success behavior
(localStorage.setItem, onSwitchToLogin) unchanged so the component behavior
stays identical while removing duplicated endpoint strings.
- Around line 36-40: Replace the blocking alert in the Register.jsx success path
with in-form non-blocking messaging: instead of calling alert(...), set a
component state (e.g., setError or introduce setInfo) to show the same message
as an inline banner inside the Register form, and then call onSwitchToLogin()
only after the user acknowledges the banner (or pass an info flag/message via
onSwitchToLogin(infoMessage) so the Login component can display the inline
banner). Update the rendering logic to display the new info state in the same
banner component used for errors and ensure onSwitchToLogin() still performs the
navigation once the message is acknowledged.

In `@frontend/src/services/api.jsx`:
- Around line 3-10: The API_BASE_URL fallback currently uses an absolute URL
causing mixed-content and wrong-host issues; change the fallback in the
API_BASE_URL assignment (where import.meta.env.VITE_API_BASE_URL is read) from
'http://localhost:8080/api' to a relative '/api' so the axios instance (api)
uses a proxied/rewrite-safe base path in production and on HTTPS origins.
🪄 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: 6f6904b0-0d13-4b69-9d98-5499f7863881

📥 Commits

Reviewing files that changed from the base of the PR and between 61bc837 and 3abccbe.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • frontend/.gitignore
  • frontend/package.json
  • frontend/src/App.jsx
  • frontend/src/pages/CreateCase.jsx
  • frontend/src/pages/Login.jsx
  • frontend/src/pages/OwnerDashboard.jsx
  • frontend/src/pages/PetDetail.jsx
  • frontend/src/pages/Register.jsx
  • frontend/src/services/api.jsx
  • package.json
✅ Files skipped from review due to trivial changes (3)
  • frontend/.gitignore
  • package.json
  • frontend/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/pages/Login.jsx
  • frontend/src/pages/CreateCase.jsx

Comment thread frontend/src/App.jsx
Comment thread frontend/src/App.jsx
Comment thread frontend/src/App.jsx
Comment thread frontend/src/services/api.jsx Outdated

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

♻️ Duplicate comments (3)
frontend/src/services/api.jsx (1)

92-97: ⚠️ Potential issue | 🟠 Major

Let Axios set multipart boundaries and request downloads as blobs.

The upload overrides the FormData content type, which can drop the required multipart boundary. The download path should request binary data explicitly.

🐛 Proposed fix
 export const attachmentService = {
-    upload: (recordId, formData) => api.post(`/attachments/record/${recordId}`, formData, {
-        headers: { 'Content-Type': 'multipart/form-data' },
-    }),
+    upload: (recordId, formData) => api.post(`/attachments/record/${recordId}`, formData),
     getByRecord: (recordId) => api.get(`/attachments/record/${recordId}`),
-    download: (id) => api.get(`/attachments/${id}/download`),
+    download: (id) => api.get(`/attachments/${id}/download`, { responseType: 'blob' }),
     delete: (id) => api.delete(`/attachments/${id}`),
 };
Axios 1.x FormData upload should Content-Type multipart/form-data boundary be set manually, and how to download binary files with responseType blob?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/services/api.jsx` around lines 92 - 97, For attachmentService:
stop overriding the FormData Content-Type in attachmentService.upload so Axios
can set the multipart boundary automatically (remove the headers:
{'Content-Type': 'multipart/form-data'} override) and for
attachmentService.download request binary data by adding responseType: 'blob' to
the api.get call; update the upload and download methods
(attachmentService.upload and attachmentService.download) accordingly.
frontend/src/App.jsx (2)

249-259: ⚠️ Potential issue | 🟠 Major

Gate the login/register screen until auth initialization finishes.

On refresh with a stored token, the first render still satisfies !currentUser, so users can see the auth form before initializeAuth() decodes the token.

🐛 Proposed guard
+    if (loading) {
+        return (
+            <div className="min-h-screen bg-slate-50 flex items-center justify-center">
+                <div className="w-8 h-8 border-4 border-slate-200 border-t-blue-500 rounded-full animate-spin" />
+            </div>
+        );
+    }
+
     if (!currentUser) {
         return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.jsx` around lines 249 - 259, The auth form is shown before
initializeAuth() finishes because the render only checks currentUser; add an
auth-initialized guard (e.g., authInitialized or isAuthReady) from your auth
context/hook and change the render logic so you return a loader/blank until
initializeAuth completes; specifically update the conditional around the JSX
that currently checks currentUser (the block containing currentUser,
isRegistering, Register, and Login) to require authInitialized (e.g., if
(!authInitialized) return null or a spinner) and only show the Register/Login UI
when authInitialized && !currentUser, ensuring initializeAuth() is called
earlier (or expose its completion flag) so the component waits for token
decoding before showing the form.

97-104: ⚠️ Potential issue | 🟡 Minor

Clear selected entities on logout too.

selectedPet and selectedRecord can survive logout in memory after the token/user/list state is cleared.

🛡️ Proposed cleanup
         setIsRegistering(false);
         setMyPets([]);
         setMyRecords([]);
+        setSelectedPet(null);
+        setSelectedRecord(null);
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.jsx` around lines 97 - 104, The handleLogout function leaves
selectedPet and selectedRecord in memory; update handleLogout to also clear
selection state by calling the selectors' setters (e.g., setSelectedPet(null)
and setSelectedRecord(null) or equivalent) alongside setCurrentUser, setMyPets,
setMyRecords, setCurrentView, and setIsRegistering so no selected entity
persists after logout.
🧹 Nitpick comments (2)
frontend/src/pages/CaseDetail.jsx (1)

139-141: Avoid mutating the caseData prop.

Directly assigning caseData.description/title can desync parent state and makes rendering depend on prop mutation side effects. Keep a local record state or notify the parent with the updated record instead.

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

In `@frontend/src/pages/CaseDetail.jsx` around lines 139 - 141, The code is
mutating the caseData prop (caseData.description/title); instead, create and
update local component state or call a parent updater: initialize a local state
(e.g., localCase via useState) from caseData in the CaseDetail component,
replace direct assignments with setLocalCase({...localCase, description:
res.data.description, title: res.data.title}), or call a provided prop callback
(e.g., onUpdateCase(updatedCase)) to notify the parent with the updated record;
update rendering to use localCase rather than mutating caseData and remove any
direct writes to caseData.
frontend/src/components/admin/UserModal.jsx (1)

61-76: Add dialog semantics and an accessible close label.

The modal renders as a generic div, so assistive tech does not get modal context, and the icon-only close button has no accessible name.

♿ Suggested markup
-        <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-200">
-            <div className="bg-white w-full max-w-md rounded-[2.5rem] shadow-2xl overflow-hidden border border-slate-200 max-h-[95vh] flex flex-col">
+        <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-200">
+            <div
+                role="dialog"
+                aria-modal="true"
+                aria-labelledby="user-modal-title"
+                className="bg-white w-full max-w-md rounded-[2.5rem] shadow-2xl overflow-hidden border border-slate-200 max-h-[95vh] flex flex-col"
+            >
@@
-                        <h2 className="text-2xl font-black text-slate-900 italic tracking-tight text-left">
+                        <h2 id="user-modal-title" className="text-2xl font-black text-slate-900 italic tracking-tight text-left">
@@
-                    <button onClick={onClose} className="p-2 hover:bg-slate-100 rounded-full transition-colors text-slate-400">
+                    <button type="button" aria-label="Stäng dialog" onClick={onClose} className="p-2 hover:bg-slate-100 rounded-full transition-colors text-slate-400">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/admin/UserModal.jsx` around lines 61 - 76, Wrap the
modal container div in proper dialog semantics by adding role="dialog" and
aria-modal="true" and set aria-labelledby to the id of the title element; give
the <h2> title a unique id (e.g., modal-title) so screen readers announce the
dialog title (refer to the UserModal.jsx top-level modal div and the h2 that
renders {initialData ? 'Redigera Profil' : 'Ny Användare'}). Also make the
icon-only close button accessible by adding an explicit accessible name (e.g.,
aria-label="Stäng" or aria-label="Close dialog") on the button that uses onClose
so it is announced and focusable. Ensure these attributes are added to the
elements in UserModal.jsx (modal container div, h2 title, and the close button)
without changing visual layout.
🤖 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/App.jsx`:
- Around line 47-60: The loading state is cleared in the finally block before
async owner data finishes, causing owners to see empty dashboards; change the
flow in the auth/startup block so that when user.role is not ROLE_VET or
ROLE_ADMIN you await fetchData(user) (or otherwise handle its Promise) before
calling setLoading(false) — locate the conditional using setCurrentView and
fetchData in App.jsx and ensure fetchData(user) is awaited (or chained with
.then/.catch) and errors still call handleLogout, then move or delay
setLoading(false) until after fetchData completes.

In `@frontend/src/components/admin/ClinicModal.jsx`:
- Around line 45-59: The modal in ClinicModal.jsx is missing dialog semantics
and accessible labeling; update the root modal container to include
role="dialog", aria-modal="true" and aria-labelledby that points to the heading
element (give the h2 a unique id), add an accessible name to the close icon
button by adding aria-label="Close" (the button currently wired to onClose and
renders <X />), and ensure every form input inside this component has an
associated <label> element with htmlFor matching a unique input id (and link any
descriptive text via aria-describedby where appropriate); keep these changes
within the ClinicModal component and reuse symbols visible in the diff such as
the h2 heading, onClose button, initialData conditional text, and the form
inputs so screen readers can correctly announce the dialog and controls.

In `@frontend/src/components/admin/UserModal.jsx`:
- Around line 43-50: The vet creation call currently omits the selected clinic
and always calls vetService.create even when editing; update the payload to
include formData.clinicId (or the component's clinic selection) when calling
vetService.create, and add a guard that checks for an existing vet record (e.g.,
response?.data?.id or a local existingVet/editingUser flag) so that on edits you
call vetService.update or vetService.upsert instead of create to avoid
duplicate/409 errors; ensure you reference isVet, response.data.id,
formData.clinicId, and vetService.create/update/upsert in your change.

In `@frontend/src/pages/CaseDetail.jsx`:
- Around line 74-82: handleCloseCase currently creates a comment then calls
medicalRecordService.closeRecord, which can leave a duplicate final note if
close fails; change the flow to call a single atomic endpoint (e.g.,
medicalRecordService.closeRecordWithNote or closeRecord({recordId, finalNote}))
that accepts the final note body (or noteId) so the backend can store the note
and mark the record closed in one transaction; update handleCloseCase to send
{recordId: caseData.id, finalNote: `SLUTGILTIG NOTERING: ${clinicalNote}`} to
that endpoint, remove the separate commentService.createComment call, handle
success/failure and setShowCloseModal(false) only on success, and ensure error
handling shows a retryable message and prevents duplicate notes by verifying
record closed state on retry.
- Around line 286-318: The attachments UI currently allows uploads/deletes even
when the journal is CLOSED; update the render and handlers to block changes: in
CaseDetail.jsx, disable the file input when localStatus === 'CLOSED' (add
localStatus === 'CLOSED' to the existing disabled prop and update the label
styling logic) and hide/disable the delete button in the attachments map when
localStatus === 'CLOSED' so users can't remove files from the UI; additionally,
add early-return guards at the top of handleFileUpload and
handleDeleteAttachment that check if localStatus === 'CLOSED' and abort
(optionally surface a user message), ensuring the client-side logic prevents
changes as well as the UI.
- Around line 16-19: The local editable state (localStatus, editedTitle,
editedDescription) is only initialized once and isn't updated when caseData
changes, causing stale values when opening a different case; add a useEffect
that runs when caseData changes and calls setLocalStatus(caseData?.status),
setEditedTitle(caseData?.title || ''), and
setEditedDescription(caseData?.description || '') (optionally guard so it only
syncs when isEditing is false if you want to preserve in-progress edits),
referencing the existing state setters (setLocalStatus, setEditedTitle,
setEditedDescription, isEditing) to keep the component in sync with new
caseData.

In `@frontend/src/pages/Login.jsx`:
- Line 33: The "Kom ihåg mig" checkbox is not wired and token is always saved to
localStorage (localStorage.setItem('token', token)), and the "Glömt lösenord?"
button is a no-op; wire the controls: add component state (e.g., rememberMe)
used by the Login component to store the token conditionally—if rememberMe is
true save to localStorage, otherwise save to sessionStorage or avoid persistent
storage—and implement a handler for the "Glömt lösenord?" button (e.g.,
onForgotPassword) to navigate to your password-reset route or open the reset
modal; update the controls referenced in Login.jsx (the checkbox and the forgot
password button handlers, also present around the other occurrence at lines
96-103) so the UI reflects real behavior.
- Line 38: Replace the permissive logging of the full Axios error object in
Login.jsx (where console.error("Login error:", err) is called) with a sanitized
log that only prints safe fields such as err.response?.status and err.message
(or a default message) to avoid exposing request body/config (credentials) in
browser logs; locate the error handling in the login handler function and update
the console.error call to only include these specific fields and optionally a
short descriptive string.

In `@frontend/src/pages/VetDashboard.jsx`:
- Around line 20-24: In fetchRecords (the useCallback in VetDashboard.jsx) the
early return when !isPersonalView && !clinicId leaves the component stuck
loading; before returning, call the loading state setter (setLoading(false)) to
clear the spinner (and optionally clear any stale list by calling setRecords([])
or similar) so the clinic overview doesn't stay on "Laddar ärenden..." when
clinicId is missing.

In `@frontend/src/services/api.jsx`:
- Line 121: The current searchByEmail function interpolates the email into the
URL which can break addresses with special characters; change the call to use
Axios' params config instead of string interpolation so Axios will URL-encode
the email (i.e., call api.get with the path '/users/search' and a params object
containing the email) and remove the template literal usage in searchByEmail.

In `@src/main/java/org/example/vet1177/controller/MedicalRecordController.java`:
- Line 66: Replace the PII usage in the logging call inside
MedicalRecordController: avoid currentUser.getEmail() and log either
currentUser.getId() or omit the user value; specifically update the log.info
invocation that currently reads "GET /api/medical-records/my-assigned - User:
{}" with currentUser.getEmail() to use currentUser.getId() (or remove the
placeholder) so logs do not contain email addresses.

In `@src/main/java/org/example/vet1177/repository/ActivityLogRepository.java`:
- Around line 13-14: The current repository method
ActivityLogRepository.findAllByOrderByCreatedAtDesc() loads the entire audit
table; replace or overload it with a pageable variant such as
findAllByOrderByCreatedAtDesc(Pageable pageable) returning Page<ActivityLog> (or
Slice<ActivityLog>) so queries are executed with pagination, and enforce a
maximum page size in the controller/service that calls this repository method to
cap results per request.

---

Duplicate comments:
In `@frontend/src/App.jsx`:
- Around line 249-259: The auth form is shown before initializeAuth() finishes
because the render only checks currentUser; add an auth-initialized guard (e.g.,
authInitialized or isAuthReady) from your auth context/hook and change the
render logic so you return a loader/blank until initializeAuth completes;
specifically update the conditional around the JSX that currently checks
currentUser (the block containing currentUser, isRegistering, Register, and
Login) to require authInitialized (e.g., if (!authInitialized) return null or a
spinner) and only show the Register/Login UI when authInitialized &&
!currentUser, ensuring initializeAuth() is called earlier (or expose its
completion flag) so the component waits for token decoding before showing the
form.
- Around line 97-104: The handleLogout function leaves selectedPet and
selectedRecord in memory; update handleLogout to also clear selection state by
calling the selectors' setters (e.g., setSelectedPet(null) and
setSelectedRecord(null) or equivalent) alongside setCurrentUser, setMyPets,
setMyRecords, setCurrentView, and setIsRegistering so no selected entity
persists after logout.

In `@frontend/src/services/api.jsx`:
- Around line 92-97: For attachmentService: stop overriding the FormData
Content-Type in attachmentService.upload so Axios can set the multipart boundary
automatically (remove the headers: {'Content-Type': 'multipart/form-data'}
override) and for attachmentService.download request binary data by adding
responseType: 'blob' to the api.get call; update the upload and download methods
(attachmentService.upload and attachmentService.download) accordingly.

---

Nitpick comments:
In `@frontend/src/components/admin/UserModal.jsx`:
- Around line 61-76: Wrap the modal container div in proper dialog semantics by
adding role="dialog" and aria-modal="true" and set aria-labelledby to the id of
the title element; give the <h2> title a unique id (e.g., modal-title) so screen
readers announce the dialog title (refer to the UserModal.jsx top-level modal
div and the h2 that renders {initialData ? 'Redigera Profil' : 'Ny Användare'}).
Also make the icon-only close button accessible by adding an explicit accessible
name (e.g., aria-label="Stäng" or aria-label="Close dialog") on the button that
uses onClose so it is announced and focusable. Ensure these attributes are added
to the elements in UserModal.jsx (modal container div, h2 title, and the close
button) without changing visual layout.

In `@frontend/src/pages/CaseDetail.jsx`:
- Around line 139-141: The code is mutating the caseData prop
(caseData.description/title); instead, create and update local component state
or call a parent updater: initialize a local state (e.g., localCase via
useState) from caseData in the CaseDetail component, replace direct assignments
with setLocalCase({...localCase, description: res.data.description, title:
res.data.title}), or call a provided prop callback (e.g.,
onUpdateCase(updatedCase)) to notify the parent with the updated record; update
rendering to use localCase rather than mutating caseData and remove any direct
writes to caseData.
🪄 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: c9e6ec0a-16f6-40d4-be6a-9a7837103703

📥 Commits

Reviewing files that changed from the base of the PR and between 3abccbe and 5558cc7.

📒 Files selected for processing (17)
  • frontend/src/App.jsx
  • frontend/src/components/Layout.jsx
  • frontend/src/components/admin/AuditLogView.jsx
  • frontend/src/components/admin/ClinicModal.jsx
  • frontend/src/components/admin/UserModal.jsx
  • frontend/src/pages/AdminDashboard.jsx
  • frontend/src/pages/CaseDetail.jsx
  • frontend/src/pages/Login.jsx
  • frontend/src/pages/VetDashboard.jsx
  • frontend/src/services/api.jsx
  • src/main/java/org/example/vet1177/controller/ActivityLogController.java
  • src/main/java/org/example/vet1177/controller/MedicalRecordController.java
  • src/main/java/org/example/vet1177/repository/ActivityLogRepository.java
  • src/main/java/org/example/vet1177/security/JwtService.java
  • src/main/java/org/example/vet1177/services/ActivityLogService.java
  • src/main/java/org/example/vet1177/services/MedicalRecordService.java
  • src/main/resources/data.sql
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/components/Layout.jsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/resources/data.sql
  • src/main/java/org/example/vet1177/security/JwtService.java
  • frontend/src/pages/AdminDashboard.jsx

Comment thread frontend/src/App.jsx
Comment thread frontend/src/components/admin/ClinicModal.jsx
Comment thread frontend/src/components/admin/UserModal.jsx Outdated
Comment thread frontend/src/pages/CaseDetail.jsx
Comment thread frontend/src/pages/CaseDetail.jsx
Comment thread frontend/src/pages/Login.jsx Outdated
Comment thread frontend/src/pages/VetDashboard.jsx
Comment thread frontend/src/services/api.jsx Outdated
public ResponseEntity<List<MedicalRecordSummaryResponse>> getMyAssignedRecords(
@AuthenticationPrincipal User currentUser) {

log.info("GET /api/medical-records/my-assigned - User: {}", currentUser.getEmail());

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 logging the user’s email address.

Email is PII; routine access logs should use currentUser.getId() or omit the user value.

🛡️ Proposed fix
-        log.info("GET /api/medical-records/my-assigned - User: {}", currentUser.getEmail());
+        log.info("GET /api/medical-records/my-assigned - userId={}", currentUser.getId());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.info("GET /api/medical-records/my-assigned - User: {}", currentUser.getEmail());
log.info("GET /api/medical-records/my-assigned - userId={}", currentUser.getId());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/controller/MedicalRecordController.java` at
line 66, Replace the PII usage in the logging call inside
MedicalRecordController: avoid currentUser.getEmail() and log either
currentUser.getId() or omit the user value; specifically update the log.info
invocation that currently reads "GET /api/medical-records/my-assigned - User:
{}" with currentUser.getEmail() to use currentUser.getId() (or remove the
placeholder) so logs do not contain email addresses.

Comment on lines +13 to +14
// Ny metod - Hämta alla
List<ActivityLog> findAllByOrderByCreatedAtDesc();

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

Make the audit-log query pageable.

findAllByOrderByCreatedAtDesc() materializes the full audit table. This admin feed will degrade as logs grow; expose a Pageable/Slice query and cap page size at the controller/service boundary.

♻️ Proposed repository shape
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
@@
-    // Ny metod - Hämta alla
-    List<ActivityLog> findAllByOrderByCreatedAtDesc();
+    Page<ActivityLog> findAllByOrderByCreatedAtDesc(Pageable pageable);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Ny metod - Hämta alla
List<ActivityLog> findAllByOrderByCreatedAtDesc();
package org.example.vet1177.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.example.vet1177.model.ActivityLog;
public interface ActivityLogRepository extends JpaRepository<ActivityLog, Long> {
Page<ActivityLog> findAllByOrderByCreatedAtDesc(Pageable pageable);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/repository/ActivityLogRepository.java`
around lines 13 - 14, The current repository method
ActivityLogRepository.findAllByOrderByCreatedAtDesc() loads the entire audit
table; replace or overload it with a pageable variant such as
findAllByOrderByCreatedAtDesc(Pageable pageable) returning Page<ActivityLog> (or
Slice<ActivityLog>) so queries are executed with pagination, and enforce a
maximum page size in the controller/service that calls this repository method to
cap results per request.

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

♻️ Duplicate comments (2)
frontend/src/pages/CaseDetail.jsx (1)

82-88: ⚠️ Potential issue | 🟠 Major

Send the close-case note through the service contract.

This still calls closeRecord(caseData.id, payload), but frontend/src/services/api.jsx:87-89 defines closeRecord with only (id), so the final note is ignored. Update the service/backend close endpoint to accept the payload, otherwise the journal closes without the required “SLUTGILTIG NOTERING”.

🛠️ Service contract fix
-    closeRecord: (id) => api.put(`/medical-records/${id}/close`),
+    closeRecord: (id, data) => api.put(`/medical-records/${id}/close`, data),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/CaseDetail.jsx` around lines 82 - 88, The close flow is
calling medicalRecordService.closeRecord(caseData.id, { finalNote: ... }) from
handleCloseCase but the service API closeRecord function only accepts (id) so
the final note is dropped; update the service contract and backend endpoint to
accept and forward the payload: change medicalRecordService.closeRecord to
accept (id, payload) and ensure the backend handler (the close record endpoint)
signature accepts the payload and persists finalNote (e.g., use the finalNote
key used in handleCloseCase) so the “SLUTGILTIG NOTERING” is saved when
handleCloseCase calls medicalRecordService.closeRecord.
frontend/src/App.jsx (1)

254-264: ⚠️ Potential issue | 🟠 Major

Gate the auth screen behind the startup loading state.

On the first render currentUser is still null while the token is being decoded, so this branch can flash Login/Register for already-authenticated users. Return a loading placeholder first, then render auth screens only after startup auth finishes.

🐛 Suggested guard
+    if (loading) {
+        return (
+            <div className="min-h-screen bg-slate-50 flex items-center justify-center">
+                <div className="w-8 h-8 border-4 border-slate-200 border-t-blue-500 rounded-full animate-spin" />
+            </div>
+        );
+    }
+
     if (!currentUser) {
         return (
             <div className="min-h-screen bg-slate-50 flex items-center justify-center p-6">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.jsx` around lines 254 - 264, The auth branch currently
returns Login/Register as soon as currentUser is falsy, causing a flash while
the app decodes a token; update the conditional to first check the
startup/auth-loading flag (e.g., isAuthLoading or isStartupDone) and render a
loading placeholder until startup auth finishes, then only if startup is
complete and currentUser is still null render the Register or Login components
(referencing currentUser, isRegistering, Register, and Login in App.jsx). Ensure
the loading state is set where token decoding occurs (or reuse an existing flag)
so the UI waits for that flag before showing auth screens.
🧹 Nitpick comments (2)
frontend/src/components/admin/ClinicModal.jsx (2)

154-158: Loading state hides the button's accessible label.

While loading is true the button renders only <Loader2 /> (decorative) with no visible or accessible text, so screen readers announce an unlabeled disabled button. Consider pairing the spinner with text (e.g. "Sparar...") or adding an aria-label on the button during loading.

♿ Suggested fix
-                            {loading ? (
-                                <Loader2 className="animate-spin" size={16} />
-                            ) : (
-                                initialData ? 'Uppdatera Klinik' : 'Registrera Klinik'
-                            )}
+                            {loading ? (
+                                <>
+                                    <Loader2 className="animate-spin" size={16} aria-hidden="true" />
+                                    <span>Sparar...</span>
+                                </>
+                            ) : (
+                                initialData ? 'Uppdatera Klinik' : 'Registrera Klinik'
+                            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/admin/ClinicModal.jsx` around lines 154 - 158, In
ClinicModal.jsx, the submit button currently renders only <Loader2 /> when the
loading state is true, leaving the button unlabeled for assistive tech; update
the button render (in the component ClinicModal where loading and initialData
are used) to include accessible text during loading (e.g. "Sparar..." or
"Updating...") alongside the <Loader2 /> OR add an appropriate
aria-label/aria-live attribute to the button while loading so screen readers get
a clear label; ensure the fallback still uses initialData ? 'Uppdatera Klinik' :
'Registrera Klinik' when not loading.

40-44: Prefer inline error UI over alert().

alert() blocks the main thread, can't be styled, and is easy to miss on mobile. Since the component already manages loading state, adding an error state and rendering it inline in the modal would be a cleaner UX and more consistent with the rest of the design.

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

In `@frontend/src/components/admin/ClinicModal.jsx` around lines 40 - 44, Replace
the blocking alert(...) in the catch block with an inline error state: add const
[error, setError] = useState(null) (and clear it when opening the modal or
before saving), in the catch do setError(err.response?.data?.message || "Ett fel
uppstod vid sparandet av kliniken.") instead of alert, keep setLoading(false) in
finally, and render the error string inside the modal JSX (near the form submit
button) with the same styling pattern used elsewhere; also clear setError(null)
on successful save.
🤖 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/App.jsx`:
- Around line 80-82: The fetchData function only loads myPets for OWNERs so vets
hit a silent no-op when CaseDetail's "Patientkort" action expects a pet; update
fetchData (or CaseDetail) so non-OWNER routes are handled: either (A) inside
fetchData(user = currentUser) detect when user.role !== 'ROLE_OWNER' and if a
petId prop/route param exists call a new or existing fetchPetById(petId) to load
that single pet into state (so CaseDetail can find it), or (B) change CaseDetail
to disable/hide the Patientkort button when currentUser.role !== 'ROLE_OWNER' or
when the pet lookup (myPets.find(...)) returns undefined; reference fetchData,
currentUser, myPets, petId, and CaseDetail to locate the logic to change. Ensure
you add minimal error handling for missing pet and update state/props
accordingly.

In `@frontend/src/components/admin/ClinicModal.jsx`:
- Around line 145-153: The submit button in ClinicModal.jsx uses a non-existent
Tailwind class `flex-2` (also present in UserModal.jsx), so replace that with a
valid utility like `grow-[2] basis-0` inside the className string for the submit
button (and the analogous submit button in UserModal.jsx), or add a custom
`flex-2` utility to the Tailwind config; update the className on the component's
submit button (the JSX element that currently includes `flex-2 px-10 ...`)
accordingly so the button takes the intended 2× width ratio.

In `@frontend/src/components/admin/UserModal.jsx`:
- Around line 92-98: The modal declared in UserModal.jsx needs focus management:
in the UserModal component add a ref (e.g., modalRef) and on open save
document.activeElement, move focus into the dialog (to the element with id
modalTitleId or first focusable), install a keydown handler to close on Escape
(call the existing close handler) and implement a Tab/Shift+Tab focus trap
within modalRef, and on close restore focus to the previously focused element;
alternatively wrap the markup with a shared accessible modal primitive that
provides these behaviors.
- Around line 58-80: The frontend calls vetService.update(existingVetId,
vetPayload) but vetService lacks an update method and the backend VetController
has no PUT/PATCH endpoint; add a new update method to frontend
src/services/api.jsx (vetService.update(id, payload)) that calls the API PUT
/vets/:id, implement a corresponding PUT handler in the backend VetController
(e.g., update(id) or put id route) that validates input and updates the vet
record in the vet service/repository, and also add backend logic (and a frontend
vetService.delete(id) if missing) to remove or orphan-clean existing vet records
when a user’s role changes away from VET so editing/role changes don’t leave
orphaned vet entries.

In `@frontend/src/pages/CaseDetail.jsx`:
- Around line 25-31: The effect that syncs local edit state (useEffect with
dependencies [caseData, isEditing]) resets editedTitle/editedDescription when
isEditing flips to false, causing saved changes to be overwritten by stale
caseData; update the logic in that effect (or create a separate effect) so it
only initializes local state from caseData when entering edit mode (i.e., when
isEditing becomes true or when caseData is initially loaded) and do not re-sync
on isEditing false; reference the useEffect block, setEditedTitle,
setEditedDescription, caseData, and setIsEditing/handleSaveRecord to ensure
saved edits persist until parent refetch updates caseData.

In `@frontend/src/pages/Login.jsx`:
- Around line 81-90: The label currently reads "E-post eller användarnamn" while
the input element uses type="email" (the input element with attributes
value={email} and onChange={(e) => setEmail(e.target.value)}), causing browser
email-only validation; either change the label text to an email-only string
(e.g., "E-post" or "E-postadress") to match the email-only contract, update
placeholder/aria-label to match, and keep the input as type="email", OR if
usernames should be accepted, change the input's type from "email" to "text" and
adjust any client-side/email validation accordingly (keep value={email} and
setEmail usage consistent).

---

Duplicate comments:
In `@frontend/src/App.jsx`:
- Around line 254-264: The auth branch currently returns Login/Register as soon
as currentUser is falsy, causing a flash while the app decodes a token; update
the conditional to first check the startup/auth-loading flag (e.g.,
isAuthLoading or isStartupDone) and render a loading placeholder until startup
auth finishes, then only if startup is complete and currentUser is still null
render the Register or Login components (referencing currentUser, isRegistering,
Register, and Login in App.jsx). Ensure the loading state is set where token
decoding occurs (or reuse an existing flag) so the UI waits for that flag before
showing auth screens.

In `@frontend/src/pages/CaseDetail.jsx`:
- Around line 82-88: The close flow is calling
medicalRecordService.closeRecord(caseData.id, { finalNote: ... }) from
handleCloseCase but the service API closeRecord function only accepts (id) so
the final note is dropped; update the service contract and backend endpoint to
accept and forward the payload: change medicalRecordService.closeRecord to
accept (id, payload) and ensure the backend handler (the close record endpoint)
signature accepts the payload and persists finalNote (e.g., use the finalNote
key used in handleCloseCase) so the “SLUTGILTIG NOTERING” is saved when
handleCloseCase calls medicalRecordService.closeRecord.

---

Nitpick comments:
In `@frontend/src/components/admin/ClinicModal.jsx`:
- Around line 154-158: In ClinicModal.jsx, the submit button currently renders
only <Loader2 /> when the loading state is true, leaving the button unlabeled
for assistive tech; update the button render (in the component ClinicModal where
loading and initialData are used) to include accessible text during loading
(e.g. "Sparar..." or "Updating...") alongside the <Loader2 /> OR add an
appropriate aria-label/aria-live attribute to the button while loading so screen
readers get a clear label; ensure the fallback still uses initialData ?
'Uppdatera Klinik' : 'Registrera Klinik' when not loading.
- Around line 40-44: Replace the blocking alert(...) in the catch block with an
inline error state: add const [error, setError] = useState(null) (and clear it
when opening the modal or before saving), in the catch do
setError(err.response?.data?.message || "Ett fel uppstod vid sparandet av
kliniken.") instead of alert, keep setLoading(false) in finally, and render the
error string inside the modal JSX (near the form submit button) with the same
styling pattern used elsewhere; also clear setError(null) on successful save.
🪄 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: 4258aaef-09c0-48a7-a587-8472d366efd7

📥 Commits

Reviewing files that changed from the base of the PR and between 5558cc7 and b1d1d75.

📒 Files selected for processing (7)
  • frontend/src/App.jsx
  • frontend/src/components/admin/ClinicModal.jsx
  • frontend/src/components/admin/UserModal.jsx
  • frontend/src/pages/CaseDetail.jsx
  • frontend/src/pages/Login.jsx
  • frontend/src/pages/VetDashboard.jsx
  • frontend/src/services/api.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/services/api.jsx

Comment thread frontend/src/App.jsx
Comment on lines +80 to +82
// 3. Datahämtning: Endast för OWNER-rollen
const fetchData = async (user = currentUser) => {
if (!user || user.role !== 'ROLE_OWNER') return;

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 | 🟡 Minor

Avoid a silent no-op patient-card action for vets.

Vet case detail routes are reachable from Lines 168-187, but fetchData only loads myPets for owners, so Line 214 usually finds nothing for vets and the Patientkort button in CaseDetail does nothing. Either hide/disable that action for non-owner routes or fetch the pet by petId when it is missing.

🛠️ Minimal no-op guard
                         onGoToPet={(petId) => {
                             const pet = myPets.find(p => p.id === petId);
                             if (pet) {
                                 setSelectedPet(pet);
                                 setCurrentView('pet-detail');
+                            } else {
+                                alert("Patientkort är inte tillgängligt från den här vyn ännu.");
                             }
                         }}

Also applies to: 168-187, 213-219

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

In `@frontend/src/App.jsx` around lines 80 - 82, The fetchData function only loads
myPets for OWNERs so vets hit a silent no-op when CaseDetail's "Patientkort"
action expects a pet; update fetchData (or CaseDetail) so non-OWNER routes are
handled: either (A) inside fetchData(user = currentUser) detect when user.role
!== 'ROLE_OWNER' and if a petId prop/route param exists call a new or existing
fetchPetById(petId) to load that single pet into state (so CaseDetail can find
it), or (B) change CaseDetail to disable/hide the Patientkort button when
currentUser.role !== 'ROLE_OWNER' or when the pet lookup (myPets.find(...))
returns undefined; reference fetchData, currentUser, myPets, petId, and
CaseDetail to locate the logic to change. Ensure you add minimal error handling
for missing pet and update state/props accordingly.

Comment on lines +145 to +153
<button
disabled={loading}
type="submit"
className={`flex-2 px-10 py-4 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all italic shadow-lg flex items-center justify-center gap-2 ${
initialData
? 'bg-blue-600 hover:bg-blue-700 shadow-blue-900/20'
: 'bg-emerald-600 hover:bg-emerald-700 shadow-emerald-900/20'
}`}
>

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the project extends Tailwind with a custom flex-2 utility.
fd -t f 'tailwind.config' frontend
fd -t f 'tailwind.config' frontend --exec cat {}
rg -nP '\bflex-2\b' frontend

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 1051


flex-2 is not a standard Tailwind utility and is not defined in the project's Tailwind config.

Tailwind ships flex-1, flex-auto, flex-initial, and flex-none by default — there is no flex-2. The class will be silently dropped, so the submit button won't take 2× the intended width ratio.

Replace with grow-[2] basis-0 or extend the Tailwind theme with a custom flex-2 utility. Note: The same issue appears in frontend/src/components/admin/UserModal.jsx:253.

♻️ Suggested fix
-                        <button
-                            disabled={loading}
-                            type="submit"
-                            className={`flex-2 px-10 py-4 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all italic shadow-lg flex items-center justify-center gap-2 ${
+                        <button
+                            disabled={loading}
+                            type="submit"
+                            className={`grow-[2] basis-0 px-10 py-4 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all italic shadow-lg flex items-center justify-center gap-2 ${
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
disabled={loading}
type="submit"
className={`flex-2 px-10 py-4 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all italic shadow-lg flex items-center justify-center gap-2 ${
initialData
? 'bg-blue-600 hover:bg-blue-700 shadow-blue-900/20'
: 'bg-emerald-600 hover:bg-emerald-700 shadow-emerald-900/20'
}`}
>
<button
disabled={loading}
type="submit"
className={`grow-[2] basis-0 px-10 py-4 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all italic shadow-lg flex items-center justify-center gap-2 ${
initialData
? 'bg-blue-600 hover:bg-blue-700 shadow-blue-900/20'
: 'bg-emerald-600 hover:bg-emerald-700 shadow-emerald-900/20'
}`}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/admin/ClinicModal.jsx` around lines 145 - 153, The
submit button in ClinicModal.jsx uses a non-existent Tailwind class `flex-2`
(also present in UserModal.jsx), so replace that with a valid utility like
`grow-[2] basis-0` inside the className string for the submit button (and the
analogous submit button in UserModal.jsx), or add a custom `flex-2` utility to
the Tailwind config; update the className on the component's submit button (the
JSX element that currently includes `flex-2 px-10 ...`) accordingly so the
button takes the intended 2× width ratio.

Comment on lines +58 to +80
// 1. Spara/Uppdatera användaren
const response = await onSave(formData);

// 2. Hantera Veterinär-detaljer (Om rollen är VET)
if (isVet) {
const userId = response?.data?.id || initialData?.id;

const vetPayload = {
userId: userId,
clinicId: formData.clinicId,
licenseId: formData.licenseId,
specialization: formData.specialization,
bookingInfo: formData.bookingInfo
};

// CodeRabbit Fix: Kolla om det redan finns en veterinärpost (Edit vs Create)
const existingVetId = initialData?.vetRecord?.id;

if (existingVetId) {
await vetService.update(existingVetId, vetPayload);
} else if (userId) {
await vetService.create(vetPayload);
}

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify vetService methods and backend support for vet update/delete lifecycle.
set -euo pipefail

rg -n -C3 'export const vetService|vetService\.(create|update|delete)|/vets' \
  frontend/src/services/api.jsx \
  frontend/src/components/admin/UserModal.jsx \
  frontend/src/pages/AdminDashboard.jsx

rg -n -C3 '(@(Put|Patch|Delete)Mapping|router\.(put|patch|delete)|app\.(put|patch|delete)).*vets|/vets' .

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 20173


Add missing update method to vetService and implement corresponding backend endpoint.

Line 77 calls vetService.update(existingVetId, vetPayload), but this method does not exist in frontend/src/services/api.jsx:127-131. The backend VetController also lacks a PUT/PATCH endpoint for updating veterinarian records—only POST (create), GET (getAll), and GET (getById) are implemented. Editing an existing veterinarian will throw "vetService.update is not a function" at runtime.

Implement the update method in vetService and add a corresponding PUT endpoint in the backend VetController to complete the vet edit workflow. Additionally, implement delete/cleanup handling for when a user role changes from VET to another role (currently the existing vet record is orphaned).

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

In `@frontend/src/components/admin/UserModal.jsx` around lines 58 - 80, The
frontend calls vetService.update(existingVetId, vetPayload) but vetService lacks
an update method and the backend VetController has no PUT/PATCH endpoint; add a
new update method to frontend src/services/api.jsx (vetService.update(id,
payload)) that calls the API PUT /vets/:id, implement a corresponding PUT
handler in the backend VetController (e.g., update(id) or put id route) that
validates input and updates the vet record in the vet service/repository, and
also add backend logic (and a frontend vetService.delete(id) if missing) to
remove or orphan-clean existing vet records when a user’s role changes away from
VET so editing/role changes don’t leave orphaned vet entries.

Comment on lines +92 to +98
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-200">
<div
className="bg-white w-full max-w-md rounded-[2.5rem] shadow-2xl overflow-hidden border border-slate-200 max-h-[95vh] flex flex-col"
role="dialog"
aria-modal="true"
aria-labelledby={modalTitleId}
>

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

Add focus management for the modal.

This declares an ARIA modal dialog, but the component does not move focus into the dialog, trap tab focus, restore focus on close, or handle Escape. Keyboard users can interact with background content while the modal is open.

Add modal focus handling here or wrap this with a shared accessible modal primitive.

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

In `@frontend/src/components/admin/UserModal.jsx` around lines 92 - 98, The modal
declared in UserModal.jsx needs focus management: in the UserModal component add
a ref (e.g., modalRef) and on open save document.activeElement, move focus into
the dialog (to the element with id modalTitleId or first focusable), install a
keydown handler to close on Escape (call the existing close handler) and
implement a Tab/Shift+Tab focus trap within modalRef, and on close restore focus
to the previously focused element; alternatively wrap the markup with a shared
accessible modal primitive that provides these behaviors.

Comment on lines +25 to +31
useEffect(() => {
if (caseData && !isEditing) {
setLocalStatus(caseData.status);
setEditedTitle(caseData.title || '');
setEditedDescription(caseData.description || '');
}
}, [caseData, isEditing]);

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

Don’t reset saved journal edits back to stale props.

After handleSaveRecord succeeds, setIsEditing(false) retriggers the sync effect because isEditing is in the dependency list; that reloads editedTitle/editedDescription from stale caseData. The read-only view also renders caseData.*, so saved edits can appear to vanish until the parent refetches.

🐛 Suggested local-state fix
-    useEffect(() => {
-        if (caseData && !isEditing) {
-            setLocalStatus(caseData.status);
-            setEditedTitle(caseData.title || '');
-            setEditedDescription(caseData.description || '');
-        }
-    }, [caseData, isEditing]);
+    useEffect(() => {
+        if (caseData) {
+            setLocalStatus(caseData.status);
+            setEditedTitle(caseData.title || '');
+            setEditedDescription(caseData.description || '');
+            setIsEditing(false);
+        }
+    }, [caseData?.id, caseData?.status, caseData?.title, caseData?.description]);
@@
-                        <h1 className="text-2xl font-bold text-slate-900 tracking-tight text-left">{caseData.title}</h1>
+                        <h1 className="text-2xl font-bold text-slate-900 tracking-tight text-left">{editedTitle}</h1>
@@
-                                <h3 className="text-lg font-bold text-slate-800 mb-2 italic text-left">{caseData.title}</h3>
-                                <p className="text-slate-600 leading-relaxed font-medium italic whitespace-pre-wrap text-left">{caseData.description}</p>
+                                <h3 className="text-lg font-bold text-slate-800 mb-2 italic text-left">{editedTitle}</h3>
+                                <p className="text-slate-600 leading-relaxed font-medium italic whitespace-pre-wrap text-left">{editedDescription}</p>

Also applies to: 148-160, 220-265

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

In `@frontend/src/pages/CaseDetail.jsx` around lines 25 - 31, The effect that
syncs local edit state (useEffect with dependencies [caseData, isEditing])
resets editedTitle/editedDescription when isEditing flips to false, causing
saved changes to be overwritten by stale caseData; update the logic in that
effect (or create a separate effect) so it only initializes local state from
caseData when entering edit mode (i.e., when isEditing becomes true or when
caseData is initially loaded) and do not re-sync on isEditing false; reference
the useEffect block, setEditedTitle, setEditedDescription, caseData, and
setIsEditing/handleSaveRecord to ensure saved edits persist until parent refetch
updates caseData.

Comment on lines +81 to +90
<label className="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2 ml-1 text-left">
E-post eller användarnamn
</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-5 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-[#003f5a] focus:bg-white outline-none transition-all font-medium text-sm"
placeholder="namn@exempel.se"

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 | 🟡 Minor

Align the label with the email-only input.

Line 82 says “E-post eller användarnamn”, but Line 85 uses type="email", so browser validation blocks username-style logins before submit. Since the backend login contract is email-based, rename the label to email-only; if usernames are intended, switch the input to type="text".

🛠️ Minimal fix
-                                E-post eller användarnamn
+                                E-post
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<label className="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2 ml-1 text-left">
E-post eller användarnamn
</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-5 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-[#003f5a] focus:bg-white outline-none transition-all font-medium text-sm"
placeholder="namn@exempel.se"
<label className="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2 ml-1 text-left">
E-post
</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-5 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-[`#003f5a`] focus:bg-white outline-none transition-all font-medium text-sm"
placeholder="namn@exempel.se"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/Login.jsx` around lines 81 - 90, The label currently reads
"E-post eller användarnamn" while the input element uses type="email" (the input
element with attributes value={email} and onChange={(e) =>
setEmail(e.target.value)}), causing browser email-only validation; either change
the label text to an email-only string (e.g., "E-post" or "E-postadress") to
match the email-only contract, update placeholder/aria-label to match, and keep
the input as type="email", OR if usernames should be accepted, change the
input's type from "email" to "text" and adjust any client-side/email validation
accordingly (keep value={email} and setEmail usage consistent).

@johanbriger
johanbriger merged commit 23ed0ca into main Apr 22, 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.

1 participant