Skip to content

feat: allow owners to delete pets#266

Merged
johanbriger merged 1 commit into
mainfrom
245-ta-bort-djur-knapp-saknas-i-frontend
Apr 24, 2026
Merged

feat: allow owners to delete pets#266
johanbriger merged 1 commit into
mainfrom
245-ta-bort-djur-knapp-saknas-i-frontend

Conversation

@johanbriger

@johanbriger johanbriger commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

closes #245

Summary by CodeRabbit

  • New Features

    • Pet deletion capability added to the application
    • Deletion requires user confirmation before executing
    • Pet deletion is blocked when medical records exist for the pet
  • Style

    • Updated styling and UI components throughout pet detail views
    • Enhanced empty state and loading state presentation

@johanbriger johanbriger linked an issue Apr 24, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request implements pet deletion functionality by introducing a delete button in the frontend's PetDetail component, with a business rule enforcement on the backend that prevents deletion if medical records exist for the pet. The flow integrates frontend confirmation dialogs, API calls via petService, and backend validation through medical record existence checks.

Changes

Cohort / File(s) Summary
Frontend API Integration
frontend/src/services/api.jsx
Added updatePet and deletePet methods to petService for HTTP PUT and DELETE requests to /pets/{id}.
Frontend Deletion Flow
frontend/src/App.jsx, frontend/src/pages/PetDetail.jsx
PetDetail now accepts an onDelete callback; added handleDeletePet with confirmation dialog, deletion disabled when medical records exist (shown via Info notice), and callback invocation before navigation. UI updates include styling, back button, loading states, and journal list presentation.
Backend Deletion Validation
src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java, src/main/java/org/example/vet1177/services/PetService.java
Added existsByPetId(UUID petId) repository method; deletePet now checks for associated medical records and throws BusinessRuleException if found, preventing deletion.

Sequence Diagram

sequenceDiagram
    actor User
    participant PetDetail as PetDetail Component
    participant Dialog as Confirmation Dialog
    participant petService as petService API
    participant Backend as PetService Backend
    participant MedRepo as MedicalRecordRepository
    participant DB as Database

    User->>PetDetail: Click delete button
    PetDetail->>Dialog: Show confirmation dialog
    Dialog->>Dialog: User confirms deletion
    Dialog->>petService: deletePet(pet.id)
    petService->>Backend: HTTP DELETE /pets/{id}
    Backend->>MedRepo: existsByPetId(petId)
    MedRepo->>DB: Query for medical records
    DB-->>MedRepo: Records found or not found
    alt Records Found
        MedRepo-->>Backend: true
        Backend-->>petService: BusinessRuleException
        petService-->>Dialog: Error response
    else No Records Found
        MedRepo-->>Backend: false
        Backend->>DB: Delete pet
        DB-->>Backend: Deletion success
        Backend-->>petService: HTTP 200 OK
        petService->>PetDetail: onDelete callback
        PetDetail->>User: Navigate back to pet list
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement, backend, frontend

Suggested reviewers

  • lindaeskilsson
  • annikaholmqvist94
  • TatjanaTrajkovic

Poem

🐰 A button to bid pets farewell,
With confirmation's protective spell,
But history chains them down with care—
No deletion if records are there! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: allow owners to delete pets' directly and accurately summarizes the main change in this pull request.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #245: adds delete button with confirmation dialog [#245], implements DELETE /api/pets/{id} endpoint [#245], and includes backend authorization checks [#245].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing pet deletion: frontend UI/callbacks, API service methods, and backend validation logic. No unrelated modifications detected.

✏️ 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 245-ta-bort-djur-knapp-saknas-i-frontend

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/PetDetail.jsx (1)

11-26: ⚠️ Potential issue | 🟡 Minor

Client-side delete gating relies on a fetch that may silently fail.

If medicalRecordService.getRecordsByPet(pet.id) rejects, the .catch at line 20 sets petRecords to [], which makes hasHistory false and enables the "Ta bort djurprofil" button even when history actually exists. The backend will still reject the delete via BusinessRuleException, so no data is lost, but the owner gets an error alert instead of the pre-emptive Info notice.

Consider tracking a separate load error and treating it as "history status unknown" — either keep the button disabled with a different message, or at least surface a lightweight error state so the user understands why the UX differs.

🤖 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 11 - 26, The fetch for medical
records should not treat failures as "no history" — add a separate load error
state (e.g., recordsLoadError via setRecordsLoadError) and update the useEffect
(that calls medicalRecordService.getRecordsByPet) to: reset
recordsLoadError=false before fetching, set petRecords only on success, set
recordsLoadError=true in the .catch (do not set petRecords to [] on error), and
keep the cancelled guard logic; then change the delete-gating logic that uses
hasHistory (and any button/notice rendering) to consider the load error (e.g.,
treat history as unknown/disable delete when recordsLoadError is true or surface
an explicit lightweight error/info message) so the "Ta bort djurprofil" button
is not enabled on fetch failure.
🧹 Nitpick comments (2)
frontend/src/pages/PetDetail.jsx (1)

28-47: Delete flow looks correct; a couple of chill notes.

  • window.confirm/alert satisfies the issue's "confirmation dialog" requirement but is inconsistent with the rest of the app's custom UI. Consider a styled modal for parity when time permits.
  • Order of operations is right: await petService.deletePetawait onDelete() (refresh parent lists) → onBack() (navigate). That avoids a flash of a stale pet in the owner list.
  • setIsDeleting(false) in the finally will usually run after onBack() has unmounted this component. React 19 no longer warns about this so it's harmless, just dead work. If you'd rather avoid it, track mount via the same pattern used in the useEffect (a cancelled flag) or skip the reset on the success path.
🤖 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 28 - 47, The delete handler
handleDeletePet is fine but can avoid setting state after unmount: wrap the
async flow with a mounted/cancelled flag (same pattern used in your component's
useEffect) and only call setIsDeleting(false) if still mounted, or alternatively
skip calling setIsDeleting(false) on the success path right before invoking
onBack(); keep the current window.confirm/alert for now (replace with the app
modal later for UI parity) and leave references to petService.deletePet,
onDelete, onBack, and setIsDeleting intact so the ordering (delete → onDelete →
onBack) remains unchanged.
src/main/java/org/example/vet1177/services/PetService.java (1)

144-156: Align the error message across the pre-check and the fallback catch.

The pre-check at line 147 and the DataIntegrityViolationException handler at line 155 raise the same semantic error with different wording. Since the frontend surfaces err.response?.data?.message directly to the user (see frontend/src/pages/PetDetail.jsx line 42), the user could see two different messages for the same root cause. Consider reusing a single constant.

♻️ Proposed tweak
+        final String recordsLinkedMsg =
+            "Djuret kan inte raderas eftersom journaler/ärenden finns kopplade";
+
         boolean hasRecords = medicalRecordRepository.existsByPetId(petId);
         if (hasRecords) {
-            throw new BusinessRuleException("Djuret kan inte raderas eftersom det finns registrerade journaler/ärenden.");
+            throw new BusinessRuleException(recordsLinkedMsg);
         }

         try {
             petRepository.delete(pet);
             petRepository.flush();
             log.info("Deleted pet id={}", petId);
         } catch (DataIntegrityViolationException e) {
-            throw new BusinessRuleException("Djuret kan inte raderas eftersom journaler finns kopplade");
+            log.warn("Cannot delete pet id={} due to related records", petId, e);
+            throw new BusinessRuleException(recordsLinkedMsg);
         }

Also note the catch block currently swallows e without logging — a log.warn(..., e) would help diagnose any non-medical-record FK constraints that slip past the pre-check (this matches the pattern in UserService.deleteUser shown in the context snippet).

🤖 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 144
- 156, The two error messages for the same deletion failure should be unified
and the exception logged: replace the duplicate string literal with a single
constant (e.g. PET_DELETE_HAS_RECORDS_MESSAGE) used in the pre-check
(medicalRecordRepository.existsByPetId(petId) -> BusinessRuleException) and in
the DataIntegrityViolationException handler thrown after
petRepository.delete/flush; also log the caught exception (use log.warn with the
exception) before throwing the BusinessRuleException so unexpected FK issues are
visible (refer to BusinessRuleException, medicalRecordRepository.existsByPetId,
petRepository.delete, petRepository.flush, DataIntegrityViolationException, and
log.warn).
🤖 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/pages/PetDetail.jsx`:
- Around line 103-122: The delete button is disabled when hasHistory is true but
lacks programmatic explanation; update the button rendered in the block using
handleDeletePet / hasHistory / isDeleting so that when hasHistory is true it
includes a descriptive title and an aria-describedby pointing to the explanatory
message element, and give that explanatory <div> a stable id (e.g.,
"delete-disabled-reason") so screen readers announce why the button is disabled;
ensure the aria-describedby is only present when hasHistory is true and removed
otherwise.

---

Outside diff comments:
In `@frontend/src/pages/PetDetail.jsx`:
- Around line 11-26: The fetch for medical records should not treat failures as
"no history" — add a separate load error state (e.g., recordsLoadError via
setRecordsLoadError) and update the useEffect (that calls
medicalRecordService.getRecordsByPet) to: reset recordsLoadError=false before
fetching, set petRecords only on success, set recordsLoadError=true in the
.catch (do not set petRecords to [] on error), and keep the cancelled guard
logic; then change the delete-gating logic that uses hasHistory (and any
button/notice rendering) to consider the load error (e.g., treat history as
unknown/disable delete when recordsLoadError is true or surface an explicit
lightweight error/info message) so the "Ta bort djurprofil" button is not
enabled on fetch failure.

---

Nitpick comments:
In `@frontend/src/pages/PetDetail.jsx`:
- Around line 28-47: The delete handler handleDeletePet is fine but can avoid
setting state after unmount: wrap the async flow with a mounted/cancelled flag
(same pattern used in your component's useEffect) and only call
setIsDeleting(false) if still mounted, or alternatively skip calling
setIsDeleting(false) on the success path right before invoking onBack(); keep
the current window.confirm/alert for now (replace with the app modal later for
UI parity) and leave references to petService.deletePet, onDelete, onBack, and
setIsDeleting intact so the ordering (delete → onDelete → onBack) remains
unchanged.

In `@src/main/java/org/example/vet1177/services/PetService.java`:
- Around line 144-156: The two error messages for the same deletion failure
should be unified and the exception logged: replace the duplicate string literal
with a single constant (e.g. PET_DELETE_HAS_RECORDS_MESSAGE) used in the
pre-check (medicalRecordRepository.existsByPetId(petId) ->
BusinessRuleException) and in the DataIntegrityViolationException handler thrown
after petRepository.delete/flush; also log the caught exception (use log.warn
with the exception) before throwing the BusinessRuleException so unexpected FK
issues are visible (refer to BusinessRuleException,
medicalRecordRepository.existsByPetId, petRepository.delete,
petRepository.flush, DataIntegrityViolationException, and log.warn).
🪄 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: 622859da-81e3-4278-b104-dd2196c8b04d

📥 Commits

Reviewing files that changed from the base of the PR and between 4815922 and 44d0b45.

📒 Files selected for processing (5)
  • frontend/src/App.jsx
  • frontend/src/pages/PetDetail.jsx
  • frontend/src/services/api.jsx
  • src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java
  • src/main/java/org/example/vet1177/services/PetService.java

Comment on lines +103 to +122
<button
onClick={handleDeletePet}
disabled={hasHistory || isDeleting}
className={`w-full flex items-center justify-center gap-2 py-3 rounded-xl font-black text-[10px] uppercase tracking-widest transition-all italic
${hasHistory
? 'bg-slate-50 text-slate-300 cursor-not-allowed'
: 'text-red-500 hover:bg-red-50'}`}
>
<Trash2 size={14} />
{isDeleting ? 'Tar bort...' : 'Ta bort djurprofil'}
</button>

{hasHistory && (
<div className="flex items-start gap-3 p-4 bg-slate-50 rounded-2xl border border-slate-100 mt-2">
<Info size={16} className="text-slate-400 shrink-0 mt-0.5" />
<p className="text-[9px] font-black text-slate-500 uppercase leading-relaxed italic tracking-wide">
Profilen kan ej raderas då den är kopplad till medicinsk historik i systemet.
</p>
</div>
)}

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

Minor a11y: the disabled delete button has no programmatic explanation.

When hasHistory is true the button is disabled but assistive tech only reads "Ta bort djurprofil, disabled"; the explanatory notice at lines 115–122 is visually adjacent but not associated. Consider adding a title and aria-describedby so screen-reader users learn why the action is unavailable.

♻️ Suggested tweak
                                 <button
                                     onClick={handleDeletePet}
                                     disabled={hasHistory || isDeleting}
+                                    title={hasHistory ? 'Profilen kan ej raderas då den är kopplad till medicinsk historik.' : undefined}
+                                    aria-describedby={hasHistory ? 'delete-disabled-reason' : undefined}
                                     className={`w-full flex items-center justify-center gap-2 py-3 rounded-xl font-black text-[10px] uppercase tracking-widest transition-all italic
                                         ${hasHistory
                                         ? 'bg-slate-50 text-slate-300 cursor-not-allowed'
                                         : 'text-red-500 hover:bg-red-50'}`}
                                 >
                                     <Trash2 size={14} />
                                     {isDeleting ? 'Tar bort...' : 'Ta bort djurprofil'}
                                 </button>

                                 {hasHistory && (
-                                    <div className="flex items-start gap-3 p-4 bg-slate-50 rounded-2xl border border-slate-100 mt-2">
+                                    <div id="delete-disabled-reason" className="flex items-start gap-3 p-4 bg-slate-50 rounded-2xl border border-slate-100 mt-2">
🤖 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 103 - 122, The delete button
is disabled when hasHistory is true but lacks programmatic explanation; update
the button rendered in the block using handleDeletePet / hasHistory / isDeleting
so that when hasHistory is true it includes a descriptive title and an
aria-describedby pointing to the explanatory message element, and give that
explanatory <div> a stable id (e.g., "delete-disabled-reason") so screen readers
announce why the button is disabled; ensure the aria-describedby is only present
when hasHistory is true and removed otherwise.

@johanbriger
johanbriger merged commit 20bb171 into main Apr 24, 2026
2 checks passed
@johanbriger
johanbriger deleted the 245-ta-bort-djur-knapp-saknas-i-frontend branch April 24, 2026 09:38
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.

Ta bort djur: knapp saknas i frontend

1 participant