Skip to content

Zugferd aktivieren bzw. deaktivieren#569

Merged
dspangenberg merged 1 commit into
mainfrom
develop
Jul 4, 2026
Merged

Zugferd aktivieren bzw. deaktivieren#569
dspangenberg merged 1 commit into
mainfrom
develop

Conversation

@dspangenberg

@dspangenberg dspangenberg commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Neue Funktionen

    • Zugferd kann jetzt direkt in den Einstellungen aktiviert oder deaktiviert werden.
    • Benachrichtigungen unterstützen jetzt Ladezustände und eine bessere Rückmeldung bei asynchronen Aktionen.
  • Bug Fixes

    • Der Aktivierungs-Schalter bleibt jetzt mit dem aktuellen Status synchron.
    • Die Fokusdarstellung von Schaltern wurde visuell angepasst.
    • Toasts lassen sich nur noch schließen, wenn sie dafür vorgesehen sind.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Die Zugferd-Einstellungsseite erhält Aktivieren/Deaktivieren-Funktionalität: neue Controller-Endpunkte und Routen im Backend, ein kontrollierter Switch mit Toast-Feedback im Frontend, eine erweiterte Toast-Komponente mit Loading-/Promise-Unterstützung sowie eine kleine Fokus-Style-Anpassung am Switch.

Changes

Zugferd Enable/Disable Feature

Layer / File(s) Summary
Controller-Endpunkte und Routen für Enable/Disable
app/Http/Controllers/App/Setting/ZugferdSettingController.php, routes/tenant/settings.php
edit() liefert nun is_enabled mit, neue enable()/disable()-Aktionen setzen und speichern den Status; zwei neue PUT-Routen registrieren diese Endpunkte.
Toast-Komponente: Loading- und Promise-Unterstützung
resources/js/Components/twc-ui/sonner.tsx
ToastProps erhält isDismissible/isLoading, Styling wird typabhängig, Close-Button nur bei dismissbarem/nicht-ladendem Toast, neue Typen ToastOptions/PromiseToastOptions und toast.promise-Implementierung mit Loading→Success/Error-Übergängen.
Frontend: Kontrollierter Switch mit Toast-Feedback
resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx
Neue is_enabled-Prop und lokaler State steuern den Switch kontrolliert; handleEnabledChange ruft per router.put Enable-/Disable-Route auf und zeigt Lade-/Erfolgs-Toasts; isDisabled-Logik berücksichtigt Pending-Status und Seller-Felder.
Switch-Fokus-Styling
resources/js/Components/twc-ui/switch.tsx
Ring-Farbe des Fokus-Zustands wechselt von ring-ring/50 zu ring-primary/50.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ZugferdSettingEdit
  participant Toast
  participant Router
  participant ZugferdSettingController
  participant ZugferdSettings

  User->>ZugferdSettingEdit: Switch umschalten
  ZugferdSettingEdit->>Toast: Loading-Toast anzeigen
  ZugferdSettingEdit->>Router: router.put(enable/disable Route)
  Router->>ZugferdSettingController: enable() oder disable()
  ZugferdSettingController->>ZugferdSettings: is_enabled setzen, save()
  ZugferdSettingController-->>ZugferdSettingEdit: redirect back
  ZugferdSettingEdit->>Toast: Erfolgs-Toast aktualisieren
  ZugferdSettingEdit->>ZugferdSettingEdit: isEnabled State aktualisieren
Loading

Possibly related PRs

  • dspangenberg/opsc#564: Ändert ebenfalls das Toast-Handling in resources/js/Components/twc-ui/sonner.tsx.
  • dspangenberg/opsc#565: Baut auf demselben ZugferdSettingController mit edit()/update() auf, den dieser PR um is_enabled und enable()/disable() erweitert.

Poem

Ein Schalter klickt, ein Hase lacht,
Zugferd wird an- und ausgemacht,
der Toast dreht sich im Kreis herum,
lädt, jubelt dann – huch, wie schnell, wie stumm!
🐇🔔✨ Mit Ring in Primär-Ton,
springt der Switch nun stolz davon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Der Titel beschreibt präzise die Kernänderung: Zugferd kann nun aktiviert und deaktiviert werden.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 develop

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.2.2)

PHPStan was skipped because the config uses disallowed bootstrapFiles, bootstrapFile, or includes directives.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
resources/js/Components/twc-ui/switch.tsx (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fokusring-Farbe weicht von bestehender Konvention ab.

Andere Komponenten wie badge.tsx und checkbox.tsx verwenden für den Fokuszustand konsistent ring-ring/50. Der Wechsel zu ring-primary/50 nur bei Switch bricht dieses Muster und könnte den Fokusring visuell schwer vom "selected"-Zustand (bg-primary) unterscheidbar machen.

Ist die Abweichung beabsichtigt (z.B. gezieltes Redesign) oder sollte hier ring-ring/50 beibehalten werden?

♻️ Vorschlag zur Wiederherstellung der Konsistenz
     isFocusVisible: {
-      true: 'border-ring ring-[3px] ring-primary/50'
+      true: 'border-ring ring-[3px] ring-ring/50'
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/Components/twc-ui/switch.tsx` at line 25, The Switch focus ring
in the twc-ui variant is inconsistent with the existing focus styling convention
used by components like badge.tsx and checkbox.tsx. Update the focus state in
switch.tsx (the switchVariants/variant definition that currently uses
ring-primary/50) to match the shared pattern, likely ring-ring/50, unless the
visual change is intentional and approved.

Source: Coding guidelines

resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx (1)

62-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ungenutzten Debug-Code entfernen.

myPromise wird erzeugt, aber nie verwendet (auch der Platzhaltertext 'My toast' deutet auf Test-/Debug-Code hin). Sollte vor dem Merge entfernt werden.

🧹 Vorschlag
-    const myPromise = new Promise<{ title: string }>(resolve => {
-      setTimeout(() => {
-        resolve({ title: 'My toast' })
-      }, 3000)
-    })
-
     const toastId = toast({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx` around
lines 62 - 66, The debug-only promise in ZugferdSettingEdit should be removed
because it is created but never used and appears to be leftover test code.
Delete the unused myPromise block from the component and ensure the surrounding
logic in ZugferdSettingEdit remains unchanged and clean of placeholder text like
the toast title.
app/Http/Controllers/App/Setting/ZugferdSettingController.php (2)

30-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

DRY: enable()/disable() sind fast identisch.

Beide Methoden unterscheiden sich nur im gesetzten Boolean-Wert. Eine private Hilfsmethode reduziert Duplikation und Wartungsaufwand.

♻️ Vorschlag zur Konsolidierung
-    public function enable(): RedirectResponse
-    {
-        $zugferdSettings = app(ZugferdSettings::class);
-        $zugferdSettings->is_enabled = true;
-        $zugferdSettings->save();
-
-        return redirect()->back();
-    }
-    public function disable(): RedirectResponse
-    {
-        $zugferdSettings = app(ZugferdSettings::class);
-        $zugferdSettings->is_enabled = false;
-        $zugferdSettings->save();
-
-        return redirect()->back();
-    }
+    public function enable(): RedirectResponse
+    {
+        return $this->setEnabled(true);
+    }
+
+    public function disable(): RedirectResponse
+    {
+        return $this->setEnabled(false);
+    }
+
+    private function setEnabled(bool $enabled): RedirectResponse
+    {
+        $zugferdSettings = app(ZugferdSettings::class);
+        $zugferdSettings->is_enabled = $enabled;
+        $zugferdSettings->save();
+
+        return redirect()->back();
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Http/Controllers/App/Setting/ZugferdSettingController.php` around lines
30 - 45, `enable()` and `disable()` in `ZugferdSettingController` are nearly
identical and only differ by the boolean value assigned to
`ZugferdSettings::$is_enabled`. Refactor the duplicated save-and-redirect flow
into a private helper method on `ZugferdSettingController` that accepts the
desired enabled state, then have both `enable()` and `disable()` delegate to it
using true/false respectively.

30-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Feature-Tests für die neuen Endpunkte fehlen.

Für die neuen enable/disable-Aktionen sind in diesem PR keine Tests enthalten. Da diese Endpunkte einen produktionsrelevanten Umschalter steuern, wäre eine kurze Feature-Test-Abdeckung sinnvoll (z. B. Assertion auf is_enabled nach dem Request).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Http/Controllers/App/Setting/ZugferdSettingController.php` around lines
30 - 45, The new ZugferdSettingController enable and disable endpoints are
missing feature-test coverage. Add a short feature test suite that exercises the
enable() and disable() actions on ZugferdSettingController, asserting that the
ZugferdSettings model’s is_enabled flag is updated correctly after each request
and that the redirect response still succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resources/js/Components/twc-ui/sonner.tsx`:
- Around line 148-168: The object-based branch in toast() is dropping
isDismissible from ToastOptions, so <Toast> always falls back to its default
dismissible behavior. Update the toast() implementation in sonner.tsx to pass
props.isDismissible through to the <Toast> component alongside isLoading, title,
message, button, and type, so callers like the new loading toast flow get the
intended non-dismissible behavior.

In `@resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx`:
- Around line 54-89: The `handleEnabledChange` flow in `ZugferdSettingEdit`
handles success and finish for `router.put` but never handles failures, so add
error handling for the request. Prefer wrapping the `router.put` call in a
Promise and using `toast.promise` to match the existing loading/success/error
pattern, or at minimum add an `onError` callback that shows a failure toast and
leaves `setIsEnabled` unchanged while still clearing `setPendingStatusChange` in
`onFinish`.

---

Nitpick comments:
In `@app/Http/Controllers/App/Setting/ZugferdSettingController.php`:
- Around line 30-45: `enable()` and `disable()` in `ZugferdSettingController`
are nearly identical and only differ by the boolean value assigned to
`ZugferdSettings::$is_enabled`. Refactor the duplicated save-and-redirect flow
into a private helper method on `ZugferdSettingController` that accepts the
desired enabled state, then have both `enable()` and `disable()` delegate to it
using true/false respectively.
- Around line 30-45: The new ZugferdSettingController enable and disable
endpoints are missing feature-test coverage. Add a short feature test suite that
exercises the enable() and disable() actions on ZugferdSettingController,
asserting that the ZugferdSettings model’s is_enabled flag is updated correctly
after each request and that the redirect response still succeeds.

In `@resources/js/Components/twc-ui/switch.tsx`:
- Line 25: The Switch focus ring in the twc-ui variant is inconsistent with the
existing focus styling convention used by components like badge.tsx and
checkbox.tsx. Update the focus state in switch.tsx (the switchVariants/variant
definition that currently uses ring-primary/50) to match the shared pattern,
likely ring-ring/50, unless the visual change is intentional and approved.

In `@resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx`:
- Around line 62-66: The debug-only promise in ZugferdSettingEdit should be
removed because it is created but never used and appears to be leftover test
code. Delete the unused myPromise block from the component and ensure the
surrounding logic in ZugferdSettingEdit remains unchanged and clean of
placeholder text like the toast title.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33b1319e-193f-4c98-843a-1983c0da2da8

📥 Commits

Reviewing files that changed from the base of the PR and between f955c9a and 848582d.

📒 Files selected for processing (5)
  • app/Http/Controllers/App/Setting/ZugferdSettingController.php
  • resources/js/Components/twc-ui/sonner.tsx
  • resources/js/Components/twc-ui/switch.tsx
  • resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx
  • routes/tenant/settings.php

Comment thread resources/js/Components/twc-ui/sonner.tsx
Comment on lines +54 to +89
const handleEnabledChange = async (value: boolean) => {
setPendingStatusChange(true)

const statusText = value ? 'aktiviert' : 'deaktiviert'
const routeName = value
? 'app.setting.invoice.zugferd.enable'
: 'app.setting.invoice.zugferd.disable'

const myPromise = new Promise<{ title: string }>(resolve => {
setTimeout(() => {
resolve({ title: 'My toast' })
}, 3000)
})

const toastId = toast({
isLoading: true,
type: 'default',
message: `ZUGFeRD wird ${statusText}.`
})

router.put(
route(routeName),
{},
{
onSuccess: () => {
toast({
id: toastId,
type: 'success',
message: `ZUGFeRD wurde ${statusText}.`
})
setIsEnabled(value)
},
onFinish: () => setPendingStatusChange(false)
}
)
}

@coderabbitai coderabbitai Bot Jul 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fehlende Fehlerbehandlung bei router.put.

onSuccess und onFinish werden behandelt, aber es gibt keinen onError-Handler. Schlägt der Request fehl (Server-/Netzwerkfehler), erhält der Nutzer keine Fehler-Rückmeldung, der Lade-Toast verschwindet lediglich nach der Default-Dauer von 10s (statt Infinity, wie es die neu implementierte toast.promise-Hilfsfunktion für genau diesen Fall vorsieht), und der Switch bleibt kommentarlos im alten Zustand.

Da toast.promise in diesem PR bereits für genau dieses Loading/Success/Error-Muster gebaut wurde, wäre es konsistenter, router.put in ein Promise zu wrappen und toast.promise zu nutzen, statt die Logik hier manuell (und unvollständig) zu reimplementieren.

🛡️ Minimaler Fix: `onError` ergänzen
         onSuccess: () => {
           toast({
             id: toastId,
             type: 'success',
             message: `ZUGFeRD wurde ${statusText}.`
           })
           setIsEnabled(value)
         },
+        onError: () => {
+          toast({
+            id: toastId,
+            type: 'error',
+            message: `ZUGFeRD konnte nicht ${statusText} werden.`
+          })
+        },
         onFinish: () => setPendingStatusChange(false)
📝 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
const handleEnabledChange = async (value: boolean) => {
setPendingStatusChange(true)
const statusText = value ? 'aktiviert' : 'deaktiviert'
const routeName = value
? 'app.setting.invoice.zugferd.enable'
: 'app.setting.invoice.zugferd.disable'
const myPromise = new Promise<{ title: string }>(resolve => {
setTimeout(() => {
resolve({ title: 'My toast' })
}, 3000)
})
const toastId = toast({
isLoading: true,
type: 'default',
message: `ZUGFeRD wird ${statusText}.`
})
router.put(
route(routeName),
{},
{
onSuccess: () => {
toast({
id: toastId,
type: 'success',
message: `ZUGFeRD wurde ${statusText}.`
})
setIsEnabled(value)
},
onFinish: () => setPendingStatusChange(false)
}
)
}
const handleEnabledChange = async (value: boolean) => {
setPendingStatusChange(true)
const statusText = value ? 'aktiviert' : 'deaktiviert'
const routeName = value
? 'app.setting.invoice.zugferd.enable'
: 'app.setting.invoice.zugferd.disable'
const myPromise = new Promise<{ title: string }>(resolve => {
setTimeout(() => {
resolve({ title: 'My toast' })
}, 3000)
})
const toastId = toast({
isLoading: true,
type: 'default',
message: `ZUGFeRD wird ${statusText}.`
})
router.put(
route(routeName),
{},
{
onSuccess: () => {
toast({
id: toastId,
type: 'success',
message: `ZUGFeRD wurde ${statusText}.`
})
setIsEnabled(value)
},
onError: () => {
toast({
id: toastId,
type: 'error',
message: `ZUGFeRD konnte nicht ${statusText} werden.`
})
},
onFinish: () => setPendingStatusChange(false)
}
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/Pages/App/Setting/ZugferdSetting/ZugferdSettingEdit.tsx` around
lines 54 - 89, The `handleEnabledChange` flow in `ZugferdSettingEdit` handles
success and finish for `router.put` but never handles failures, so add error
handling for the request. Prefer wrapping the `router.put` call in a Promise and
using `toast.promise` to match the existing loading/success/error pattern, or at
minimum add an `onError` callback that shows a failure toast and leaves
`setIsEnabled` unchanged while still clearing `setPendingStatusChange` in
`onFinish`.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Kommt noch nach einem Check, ob Zugferd aktiviert werden kann.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dspangenberg Verstanden, danke für die Info. Ich lasse es dabei bewenden – wenn die Prüfung, ob ZUGFeRD aktiviert werden kann, ergänzt wird, kann die Fehlerbehandlung für router.put gleich mit berücksichtigt werden.


🧠 Learnings used
Learnt from: dspangenberg
Repo: dspangenberg/opsc PR: 309
File: resources/js/Pages/App/Bookkeeping/Receipt/ReceiptConfirm.tsx:151-157
Timestamp: 2026-03-02T21:09:25.796Z
Learning: When using React Aria Button components, use the isDisabled prop to disable the button. Do not use the native disabled prop on React Aria Button components; instead pass isDisabled={true/false}. Ensure consistent usage across all React Aria components in the codebase.

@dspangenberg dspangenberg merged commit 544c329 into main Jul 4, 2026
3 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