diff --git a/src/components/auftraege/AuftragSchritte.jsx b/src/components/auftraege/AuftragSchritte.jsx
new file mode 100644
index 0000000..41e1916
--- /dev/null
+++ b/src/components/auftraege/AuftragSchritte.jsx
@@ -0,0 +1,321 @@
+import { Badge } from '../ui'
+import FormField from '../finance/FormField'
+import { useWorkorders } from '../../hooks/useWorkorders'
+import { euro } from '../../lib/financeFormat'
+import { customerName, lineTotalCents } from '../../lib/financeView'
+import { KUNDENART_OPTIONEN, kundenartLabel } from '../../lib/kundenFelder'
+import {
+ AUFTRAG_LIMITS,
+ AUFTRAG_WEGE,
+ formularSummeCents,
+ kundenartWirksam,
+ neuePosition,
+ parseProzent,
+ parseTage,
+ schalteZeitquelle,
+ vorOrtHinweis,
+ waehleProjekt,
+ wechselArt,
+ zahlungsplanOptionen,
+ zahlungsplanVorschau,
+ zeitquelleOptionen,
+} from '../../lib/auftragView'
+import './auftrag.css'
+
+/** Meldung eines Feldes ohne FormField (Art, Positionen, Zahlungsplan, Zeitquelle). */
+function FeldFehler({ text }) {
+ return text ?
{text}
: null
+}
+
+/** Schritt 1: Projekt, Titel und (falls am Kunden noch leer) die Kundenart. */
+export function SchrittProjekt({ form, onChange, fehler, kunde, konten, neueFassung = false }) {
+ const name = customerName(kunde) || 'Kunde'
+ return (
+
+
+ Kunde: {name}
+ {kunde?.email ? ` · ${kunde.email}` : ''}
+
+ {neueFassung &&
Neue Fassung: Beim Senden ersetzt sie die bisherige Auftragsbestätigung.
}
+
+
+
+
+ onChange({ ...form, titel: e.target.value, titelGeaendert: true })}
+ />
+
+ {!kunde?.kundenart ? (
+
+
+
+ ) : (
+
Kundenart: {kundenartLabel(kunde.kundenart)}. Änderbar in der Übersicht des Kunden.
+ )}
+
+ )
+}
+
+/** Schritt 2: Absprache (Datum, Weg), Art des Auftrags und Positionen bzw. Stundensatz. */
+export function SchrittAuftrag({ form, onChange, fehler, kunde }) {
+ const vorOrt = vorOrtHinweis(form.auftragWeg, kundenartWirksam(kunde, form.kundenart))
+ const positionen = form.positionen
+ const setPosition = (uid, feld, wert) =>
+ onChange({ ...form, positionen: positionen.map((p) => (p.uid === uid ? { ...p, [feld]: wert } : p)) })
+ const entferne = (uid) => onChange({ ...form, positionen: positionen.filter((p) => p.uid !== uid) })
+ const summe = formularSummeCents(form)
+
+ return (
+
+
+
+ onChange({ ...form, auftragVom: e.target.value })} />
+
+
+
+
+
+ {vorOrt &&
{vorOrt}
}
+
+
+
+ {form.art === 'festpreis' && (
+
+ )}
+
+ {form.art === 'aufwand' && (
+
+
+ onChange({ ...form, stundensatz: e.target.value })} />
+
+
+ onChange({ ...form, schaetzung: e.target.value })}
+ />
+
+
+ )}
+
+ )
+}
+
+/** Zeitquelle: Tickets des Kunden, deren Arbeitsblätter zum Auftrag zählen (höchstens 10). */
+function ZeitquelleAuswahl({ kundeId, form, onChange, projektTicketIds, fehler }) {
+ const { workorders, loading, error } = useWorkorders({ customerId: kundeId, limit: 100 })
+ const optionen = zeitquelleOptionen(workorders, form.zeitquelle, projektTicketIds)
+ const voll = form.zeitquelle.length >= AUFTRAG_LIMITS.maxZeitquellen
+ return (
+
+ )
+}
+
+/** Schritt 3: Zahlungsplan, Zahlungsziel, Termine und Zeitquelle. */
+export function SchrittZahlung({ form, onChange, fehler, kunde, konten }) {
+ const konto = (Array.isArray(konten) ? konten : []).find((k) => k.projektId && k.projektId === form.projektId)
+ const projektTicketIds = konto && Array.isArray(konto.tickets) ? konto.tickets.map((t) => t.id) : []
+ const vorschau = zahlungsplanVorschau({
+ art: form.zahlungsart,
+ prozent: parseProzent(form.prozent),
+ text: form.zahlungstext,
+ summeCents: formularSummeCents(form),
+ zielTage: parseTage(form.zahlungsziel),
+ })
+ return (
+
+ )
+}
diff --git a/src/components/auftraege/auftrag.css b/src/components/auftraege/auftrag.css
index 62dbd56..caeca2c 100644
--- a/src/components/auftraege/auftrag.css
+++ b/src/components/auftraege/auftrag.css
@@ -33,3 +33,46 @@
.auftrag-aktionen { grid-area: aktionen; padding-top: 4px; }
.auftrag-table td[data-label]::before { content: attr(data-label) " "; color: var(--text-2); font-weight: 500; }
}
+
+/* Dialog: Schritte 1 bis 3 */
+.auftrag-schritt { display: grid; gap: 16px; min-width: 0; }
+.auftrag-schritt .field { min-width: 0; }
+.auftrag-art { border: 0; padding: 0; margin: 0; display: grid; gap: 8px; min-width: 0; }
+.auftrag-art legend { font-weight: 500; padding: 0; margin-bottom: 4px; }
+.auftrag-art .check { flex-wrap: wrap; }
+.auftrag-art .check .hint { flex-basis: 100%; padding-left: 23px; font-size: 13px; color: var(--text-2); }
+.auftrag-items { border: 0; padding: 0; margin: 0; display: grid; gap: 12px; min-width: 0; }
+.auftrag-items legend { font-weight: 500; padding: 0; margin-bottom: 4px; }
+.auftrag-item {
+ display: grid; grid-template-columns: minmax(0, 1fr) 80px 120px 110px auto; gap: 8px 12px; align-items: end;
+ grid-template-areas: "desc qty price sum remove";
+}
+.auftrag-item .input { width: 100%; }
+.auftrag-item > div { display: grid; gap: 4px; min-width: 0; }
+.auftrag-item-desc { grid-area: desc; }
+.auftrag-item-qty { grid-area: qty; }
+.auftrag-item-price { grid-area: price; }
+.auftrag-item-sum { grid-area: sum; }
+.auftrag-item-remove { grid-area: remove; }
+.auftrag-item-qty .input, .auftrag-item-price .input { text-align: right; font-variant-numeric: tabular-nums; }
+.auftrag-item-wert { min-height: 36px; display: flex; align-items: center; justify-content: flex-end; font-variant-numeric: tabular-nums; white-space: nowrap; }
+.auftrag-item-remove { min-height: 36px; align-content: center; }
+.auftrag-item-label { font-size: 12px; color: var(--text-2); }
+.auftrag-item + .auftrag-item .auftrag-item-label { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
+.auftrag-total { display: flex; justify-content: space-between; gap: 16px; font-weight: 600; }
+.auftrag-narrow { max-width: 120px; }
+.auftrag-plan-vorschau { margin: 0; overflow-wrap: anywhere; }
+.auftrag-hinweis { margin: 0; overflow-wrap: anywhere; }
+.auftrag-zeitquelle { border: 0; padding: 0; margin: 0; display: grid; gap: 6px; min-width: 0; }
+.auftrag-zeitquelle legend { font-weight: 500; padding: 0; margin-bottom: 4px; }
+.auftrag-zeitquelle-liste { display: grid; gap: 6px; max-height: 220px; overflow-y: auto; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 6px 0; }
+.auftrag-zeitquelle-liste .check { flex-wrap: wrap; overflow-wrap: anywhere; }
+
+@media (max-width: 860px) {
+ .auftrag-item {
+ grid-template-columns: minmax(0, .7fr) minmax(0, 1fr) minmax(0, 1fr) auto;
+ grid-template-areas: "desc desc desc desc" "qty price sum remove";
+ }
+ .auftrag-item + .auftrag-item .auftrag-item-label, .auftrag-item-label { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; white-space: normal; }
+ .auftrag-narrow { max-width: none; }
+}