Aufträge und Rechnungen: Container des Kundenreiters und vier Platzhalter für W1 und W2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1c9b57249
commit
8f90fdc05a
5 files changed
+218
No files matched your search
@@ -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 (
|
||||||
|
<EmptyState
|
||||||
|
title={gate.title}
|
||||||
|
hint={gate.hint}
|
||||||
|
action={onShowOverview ? <Button variant="primary" onClick={onShowOverview}>Daten erfassen</Button> : null}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <AuftraegeInhalt customer={customer} onShowOverview={onShowOverview} />
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<AddressWarning customer={customer} onShowOverview={onShowOverview} variant="page" />
|
||||||
|
|
||||||
|
{meldung && (
|
||||||
|
<p ref={meldungRef} {...noticeProps(meldung.tone, 'fin-notice')}>
|
||||||
|
{meldung.text}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ansicht.projekteStatus === 'bereit' && ansicht.nachladeFehler && (
|
||||||
|
<p {...noticeProps('warn', 'fin-notice')}>
|
||||||
|
Die Übersicht konnte nicht neu geladen werden: {ansicht.nachladeFehler}{' '}
|
||||||
|
<button type="button" className="link" onClick={reload}>
|
||||||
|
Erneut versuchen
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ansicht.projekteStatus === 'laden' && (
|
||||||
|
<Card title="Projekte">
|
||||||
|
<div className="loading" role="status">Projekte werden geladen …</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{ansicht.projekteStatus === 'fehler' && (
|
||||||
|
<Card title="Projekte">
|
||||||
|
<div className="fin-load-error">
|
||||||
|
<p className="msg warn" role="alert">{ansicht.fehler}</p>
|
||||||
|
<Button onClick={reload}>Erneut versuchen</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{ansicht.projekteStatus === 'bereit' && (
|
||||||
|
<ProjektkontoListe
|
||||||
|
konten={ansicht.konten}
|
||||||
|
ohneProjekt={ansicht.ohneProjekt}
|
||||||
|
auftraegeAktiv={ansicht.auftraegeAktiv}
|
||||||
|
stripeOk={ansicht.stripeOk}
|
||||||
|
onRechnung={openRechnung}
|
||||||
|
onAuftrag={(projektId) => openAuftrag(projektId)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ansicht.auftraegeStatus === 'bereit' && (
|
||||||
|
<AuftragListe
|
||||||
|
auftraege={ansicht.auftraege}
|
||||||
|
konten={ansicht.konten}
|
||||||
|
onChanged={reload}
|
||||||
|
onBearbeiten={(auftrag) => openAuftrag(auftrag.projektId || null, auftrag)}
|
||||||
|
onShowOverview={onShowOverview}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ansicht.auftraegeStatus === 'fehler' && (
|
||||||
|
<Card title="Aufträge">
|
||||||
|
<div className="fin-load-error">
|
||||||
|
<p className="msg warn" role="alert">{ansicht.auftraegeFehler}</p>
|
||||||
|
<Button onClick={reload}>Erneut versuchen</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InvoiceTable customer={customer} onShowOverview={onShowOverview} refreshToken={listeToken} onChanged={reload} />
|
||||||
|
|
||||||
|
{rechnungsDialog && (
|
||||||
|
<RechnungDialog
|
||||||
|
open
|
||||||
|
onClose={() => setRechnungsDialog(null)}
|
||||||
|
onCreated={handleCreated}
|
||||||
|
kunde={kunde}
|
||||||
|
projektId={rechnungsDialog.projektId}
|
||||||
|
auftraegeAktiv={ansicht.auftraegeAktiv}
|
||||||
|
onShowOverview={onShowOverview}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{auftragsDialog && (
|
||||||
|
<AuftragDialog
|
||||||
|
open
|
||||||
|
onClose={() => setAuftragsDialog(null)}
|
||||||
|
onSaved={handleSaved}
|
||||||
|
kunde={kunde}
|
||||||
|
projektId={auftragsDialog.projektId}
|
||||||
|
auftrag={auftragsDialog.auftrag}
|
||||||
|
konten={ansicht.konten}
|
||||||
|
onShowOverview={onShowOverview}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Platzhalter von W0: Strang W2 ersetzt diese Datei vollständig (Dateiname, Default-Export und Props bleiben).
|
||||||
|
* Die fertige Komponente rendert <Card title="Aufträge"> 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
|
||||||
|
}
|
||||||
@@ -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: <Card title="Projekte"> (ein <section>; 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user