From 8f90fdc05aada37b86d35063f15db8ae17a93af8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 14:25:22 +0000 Subject: [PATCH] =?UTF-8?q?Auftr=C3=A4ge=20und=20Rechnungen:=20Container?= =?UTF-8?q?=20des=20Kundenreiters=20und=20vier=20Platzhalter=20f=C3=BCr=20?= =?UTF-8?q?W1=20und=20W2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../auftraege/AuftraegeUndRechnungen.jsx | 171 ++++++++++++++++++ src/components/auftraege/AuftragDialog.jsx | 12 ++ src/components/auftraege/AuftragListe.jsx | 11 ++ .../auftraege/ProjektkontoListe.jsx | 11 ++ src/components/auftraege/RechnungDialog.jsx | 13 ++ 5 files changed, 218 insertions(+) create mode 100644 src/components/auftraege/AuftraegeUndRechnungen.jsx create mode 100644 src/components/auftraege/AuftragDialog.jsx create mode 100644 src/components/auftraege/AuftragListe.jsx create mode 100644 src/components/auftraege/ProjektkontoListe.jsx create mode 100644 src/components/auftraege/RechnungDialog.jsx diff --git a/src/components/auftraege/AuftraegeUndRechnungen.jsx b/src/components/auftraege/AuftraegeUndRechnungen.jsx new file mode 100644 index 0000000..4d8afed --- /dev/null +++ b/src/components/auftraege/AuftraegeUndRechnungen.jsx @@ -0,0 +1,171 @@ +import { useRef, useState } from 'react' +import { Button, Card, EmptyState } from '../ui' +import AddressWarning from '../finance/AddressWarning' +import InvoiceTable from '../finance/InvoiceTable' +import ProjektkontoListe from './ProjektkontoListe' +import RechnungDialog from './RechnungDialog' +import AuftragListe from './AuftragListe' +import AuftragDialog from './AuftragDialog' +import { useFinanceResource } from '../../hooks/useFinanceResource' +import { useScrollIntoView } from '../../hooks/useScrollIntoView' +import { getProjektkonten, listAuftraege } from '../../lib/financeApi' +import { customerInvoicesGate } from '../../lib/financeView' +import { noticeProps } from '../../lib/financeUi' +import { + KONTEN_FEHLER, + ladeAuftraegeAnsicht, + ansichtsZustand, + kundeMitKundenart, + erstelltMeldung, + auftragMeldung, +} from '../../lib/auftraegeAnsicht' + +/** + * Reiter „Aufträge und Rechnungen“ am Kunden (ersetzt CustomerInvoices). Kann für den Kunden keine Rechnung geschrieben + * werden (noch Lead, abgesagt, keine E-Mail), steht der Grund da, mit „Daten erfassen“; es werden keine Abrufe gestartet. + * Sonst von oben nach unten: Adresswarnung, Rückmeldung, Hinweis zum Neuladen, Projekte (Projektkonten), Aufträge (nur + * mit eingeschaltetem Schalter AUFTRAEGE_ENABLED) und die Rechnungsliste. Die Rechnungsliste und die Adresswarnung hängen + * nie vom Laden der Projekte oder Aufträge ab: fällt eines davon aus, bleibt „Rechnung schreiben“ bedienbar. + * Die Dialoge stehen als Geschwister hinter den Abschnitten (nie in InvoiceTable oder .fin-body). + */ +export default function AuftraegeUndRechnungen({ customer, onShowOverview }) { + const gate = customerInvoicesGate(customer) + if (!gate.ok) { + return ( + Daten erfassen : null} + /> + ) + } + return +} + +function AuftraegeInhalt({ customer, onShowOverview }) { + const res = useFinanceResource(() => ladeAuftraegeAnsicht(customer.$id, { getProjektkonten, listAuftraege }), KONTEN_FEHLER) + const ansicht = ansichtsZustand(res) + const reload = res.reload + + const [meldung, setMeldung] = useState(null) // { tone, text } oder null + const [rechnungsDialog, setRechnungsDialog] = useState(null) // { projektId } oder null + const [auftragsDialog, setAuftragsDialog] = useState(null) // { projektId, auftrag } oder null + const [listeToken, setListeToken] = useState(0) + const meldungRef = useRef(null) + useScrollIntoView(meldungRef, meldung) + + // Die Dialoge bekommen die frische Kundenart des Portals (die Kundenseite hält ggf. noch den alten Datensatz) + const kunde = kundeMitKundenart(customer, ansicht.kunde) + + function openRechnung(projektId) { + setMeldung(null) + setRechnungsDialog({ projektId }) + } + + function openAuftrag(projektId, auftrag = null) { + setMeldung(null) + setAuftragsDialog({ projektId, auftrag }) + } + + function handleCreated(rechnung, info) { + setRechnungsDialog(null) + setMeldung(erstelltMeldung(rechnung, info)) + setListeToken((n) => n + 1) + reload() + } + + function handleSaved(auftrag) { + setAuftragsDialog(null) + setMeldung(auftragMeldung(auftrag)) + reload() + } + + return ( + <> + + + {meldung && ( +

+ {meldung.text} +

+ )} + + {ansicht.projekteStatus === 'bereit' && ansicht.nachladeFehler && ( +

+ Die Übersicht konnte nicht neu geladen werden: {ansicht.nachladeFehler}{' '} + +

+ )} + + {ansicht.projekteStatus === 'laden' && ( + +
Projekte werden geladen …
+
+ )} + {ansicht.projekteStatus === 'fehler' && ( + +
+

{ansicht.fehler}

+ +
+
+ )} + {ansicht.projekteStatus === 'bereit' && ( + openAuftrag(projektId)} + /> + )} + + {ansicht.auftraegeStatus === 'bereit' && ( + openAuftrag(auftrag.projektId || null, auftrag)} + onShowOverview={onShowOverview} + /> + )} + {ansicht.auftraegeStatus === 'fehler' && ( + +
+

{ansicht.auftraegeFehler}

+ +
+
+ )} + + + + {rechnungsDialog && ( + setRechnungsDialog(null)} + onCreated={handleCreated} + kunde={kunde} + projektId={rechnungsDialog.projektId} + auftraegeAktiv={ansicht.auftraegeAktiv} + onShowOverview={onShowOverview} + /> + )} + {auftragsDialog && ( + setAuftragsDialog(null)} + onSaved={handleSaved} + kunde={kunde} + projektId={auftragsDialog.projektId} + auftrag={auftragsDialog.auftrag} + konten={ansicht.konten} + onShowOverview={onShowOverview} + /> + )} + + ) +} diff --git a/src/components/auftraege/AuftragDialog.jsx b/src/components/auftraege/AuftragDialog.jsx new file mode 100644 index 0000000..564cbc6 --- /dev/null +++ b/src/components/auftraege/AuftragDialog.jsx @@ -0,0 +1,12 @@ +/** + * Platzhalter von W0: Strang W2 ersetzt diese Datei vollständig (Dateiname, Default-Export und Props bleiben). + * + * Props (V8): open, onClose, onSaved(auftrag) (AuftragTeam mit mailStatus; nach Speichern oder Senden, danach onClose()), + * kunde (kommt mit aktueller kundenart: der Container gleicht sie über kundeMitKundenart ab), projektId (vorausgewähltes + * Projekt oder null = freier Titel), auftrag (Entwurf zum Bearbeiten oder null), konten (Projektkonten für die Projektwahl), + * onShowOverview (Weg „Zur Übersicht“). + * Die PDF-Vorschau öffnet openInNewTab(async () => URL.createObjectURL(await getAuftragPdf(id))). + */ +export default function AuftragDialog({ open, onClose, onSaved, kunde, projektId, auftrag, konten, onShowOverview }) { + return null +} diff --git a/src/components/auftraege/AuftragListe.jsx b/src/components/auftraege/AuftragListe.jsx new file mode 100644 index 0000000..3525a91 --- /dev/null +++ b/src/components/auftraege/AuftragListe.jsx @@ -0,0 +1,11 @@ +/** + * Platzhalter von W0: Strang W2 ersetzt diese Datei vollständig (Dateiname, Default-Export und Props bleiben). + * Die fertige Komponente rendert selbst und ruft nach jeder Aktion onChanged(). + * + * Props (V8): auftraege (AuftragTeam[], V3), konten (Projektname der Zeile und Auswahl für „Projekt zuordnen“, einer Aktion für + * ABs ohne Projekt über ordneAuftragProjektZu(id, projektId) aus financeApi.js), onChanged(), onBearbeiten(auftrag) + * („Entwurf bearbeiten“; nach „Neue Fassung“ mit dem neuen Entwurf) und onShowOverview (Weg „Zur Übersicht“ aus Meldungen). + */ +export default function AuftragListe({ auftraege, konten, onChanged, onBearbeiten, onShowOverview }) { + return null +} diff --git a/src/components/auftraege/ProjektkontoListe.jsx b/src/components/auftraege/ProjektkontoListe.jsx new file mode 100644 index 0000000..4b4bd06 --- /dev/null +++ b/src/components/auftraege/ProjektkontoListe.jsx @@ -0,0 +1,11 @@ +/** + * Platzhalter von W0: Strang W1 ersetzt diese Datei vollständig (Dateiname, Default-Export und Props bleiben). + * Die fertige Komponente rendert ihren Abschnitt selbst: (ein
; den Abstand zu den + * Nachbarn liefert `.page section + section`). + * + * Props (V8): konten (Projektkonten), ohneProjekt ({ berechnetCents, bezahltCents, anzahl }), auftraegeAktiv, stripeOk, + * onRechnung(projektId) und onAuftrag(projektId); die Kennung ist die des Projekts, `null` die Zeile „Ohne Projekt“. + */ +export default function ProjektkontoListe({ konten, ohneProjekt, auftraegeAktiv, stripeOk, onRechnung, onAuftrag }) { + return null +} diff --git a/src/components/auftraege/RechnungDialog.jsx b/src/components/auftraege/RechnungDialog.jsx new file mode 100644 index 0000000..1daa360 --- /dev/null +++ b/src/components/auftraege/RechnungDialog.jsx @@ -0,0 +1,13 @@ +/** + * Platzhalter von W0: Strang W1 ersetzt diese Datei vollständig (Dateiname, Default-Export und Props bleiben). + * + * Props (V8): open, onClose, onCreated(rechnung, info), kunde (Kundendokument oder null = Kundenwahl im Dialog), + * projektId (undefined = keine Vorauswahl, null = „Ohne Projekt“, Text = dieses Projekt), auftraegeAktiv (nur der Startwert, + * danach gilt getProjektkonten, das der Dialog selbst lädt), onShowOverview (optional, „Zur Übersicht“ in der Adresswarnung). + * `info` von onCreated wird unverändert weitergereicht wie im bisherigen InvoiceCreateModal ({ senden }, bei festgeschriebener, + * nicht versendeter Rechnung { senden: true, notice, tone: 'warn', finalizedFilter }); danach ruft der Dialog onClose(). + * Der Dialog sitzt zusätzlich in InvoiceTable.jsx (dort ersetzt W1 das Modal), FinancePage.jsx bleibt unverändert. + */ +export default function RechnungDialog({ open, onClose, onCreated, kunde, projektId, auftraegeAktiv, onShowOverview }) { + return null +}