From ebaee024b63b88ab96e3678782c8136d9c528955 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 21 Jun 2026 16:08:35 +0200 Subject: [PATCH] Kundendaten-Modal: Bank + Ausweis getrennte Text/PDF-Wahl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bank- und Ausweis-Section haben jetzt jeweils zwei unabhängige Checkboxen statt der bisherigen Section-Checkbox + Sub-Attach: - Bank: "Letzte 4 IBAN-Stellen einfügen" + "Bankkarte als PDF anhängen". Text-Variante zeigt nur "IBAN endet auf: XXXX" – keine volle IBAN/BIC/Bank-Liste mehr (Mail-Hygiene). - Ausweis: "{Typ}-Nummer einfügen" + "{Typ} als PDF anhängen". Text-Variante zeigt nur die Nummer, keine Behörde/Daten. Alle drei Kombinationen "nur Text", "nur PDF" und "beides" sind damit möglich, "keins von beidem" entspricht der Section-aus. Schalter sind disabled wenn der jeweilige Wert (IBAN / documentNumber / documentPath) nicht vorhanden ist. Co-Authored-By: Claude Opus 4.7 --- .../email/InsertCustomerDataModal.tsx | 198 +++++++++++------- 1 file changed, 126 insertions(+), 72 deletions(-) diff --git a/frontend/src/components/email/InsertCustomerDataModal.tsx b/frontend/src/components/email/InsertCustomerDataModal.tsx index b0ced755..8dece209 100644 --- a/frontend/src/components/email/InsertCustomerDataModal.tsx +++ b/frontend/src/components/email/InsertCustomerDataModal.tsx @@ -39,9 +39,7 @@ type SectionKey = | 'customer' | 'deliveryAddress' | 'billingAddress' - | 'contract' - | 'iban' - | 'identity'; + | 'contract'; export default function InsertCustomerDataModal({ isOpen, @@ -72,18 +70,23 @@ export default function InsertCustomerDataModal({ const bankCard = contract?.bankCard; const identityDocument = contract?.identityDocument; - // Sections die default-an sind: Anrede + Vertragsdaten. Anhang-Checkboxen - // bleiben default-aus (User-Intent). + // Sections die default-an sind: Anrede + Vertragsdaten. Anhang-/Text- + // Schalter für Bank + Ausweis bleiben default-aus (User-Intent: bewusst + // entscheiden, was vertraulich verschickt wird). const [checked, setChecked] = useState>({ customer: true, deliveryAddress: true, billingAddress: false, contract: true, - iban: false, - identity: false, }); - const [attachBankCard, setAttachBankCard] = useState(false); - const [attachIdentity, setAttachIdentity] = useState(false); + // Bank: zwei unabhängige Schalter. Text fügt nur die letzten 4 IBAN- + // Stellen ein (kein vollständiger IBAN-Versand per Mail = Default-Hygiene). + const [insertBankText, setInsertBankText] = useState(false); + const [attachBankPdf, setAttachBankPdf] = useState(false); + // Ausweis: Text-Schalter fügt nur die Ausweisnummer ein, kein Geburtsdatum + // / keine Ausstellungsdaten – falls der Empfänger nur die Nummer braucht. + const [insertIdentityText, setInsertIdentityText] = useState(false); + const [attachIdentityPdf, setAttachIdentityPdf] = useState(false); // Welche E-Mail-Adresse in der Customer-Section steht: // - 'master' = Stammdaten-E-Mail (customer.email) // - 'sender' = Postfach-Adresse, von der die Mail abgeht (Stressfrei) @@ -100,11 +103,11 @@ export default function InsertCustomerDataModal({ deliveryAddress: !!deliveryAddress, billingAddress: false, // nur wenn vorhanden, aber default aus contract: true, - iban: false, - identity: false, }); - setAttachBankCard(false); - setAttachIdentity(false); + setInsertBankText(false); + setAttachBankPdf(false); + setInsertIdentityText(false); + setAttachIdentityPdf(false); // Default: Stammdaten-E-Mail wenn vorhanden, sonst Absender-Adresse. setEmailChoice(customer?.email ? 'master' : 'sender'); } @@ -143,10 +146,10 @@ export default function InsertCustomerDataModal({ if (checked.contract) { blocks.push(formatContractBlock(contract)); } - if (checked.iban && bankCard) { + if (insertBankText && bankCard) { blocks.push(formatBankBlock(bankCard)); } - if (checked.identity && identityDocument) { + if (insertIdentityText && identityDocument) { blocks.push(formatIdentityBlock(identityDocument)); } @@ -179,13 +182,13 @@ export default function InsertCustomerDataModal({ } }; - if (attachBankCard && bankCard?.documentPath) { + if (attachBankPdf && bankCard?.documentPath) { await tryAttach( bankCard.documentPath, bankCardAttachmentName(bankCard.iban), ); } - if (attachIdentity && identityDocument?.documentPath) { + if (attachIdentityPdf && identityDocument?.documentPath) { await tryAttach( identityDocument.documentPath, identityDocAttachmentName( @@ -212,10 +215,10 @@ export default function InsertCustomerDataModal({ !checked.deliveryAddress && !checked.billingAddress && !checked.contract && - !checked.iban && - !checked.identity && - !attachBankCard && - !attachIdentity; + !insertBankText && + !attachBankPdf && + !insertIdentityText && + !attachIdentityPdf; return ( {bankCard && ( - toggle('iban')} preview={previewBank(bankCard)} - extra={ - bankCard.documentPath && ( - - ) - } + textChecked={insertBankText} + onToggleText={() => setInsertBankText((v) => !v)} + textLabel="Letzte 4 IBAN-Stellen einfügen" + textDisabled={!lastFourIban(bankCard.iban)} + pdfChecked={attachBankPdf} + onTogglePdf={() => setAttachBankPdf((v) => !v)} + pdfLabel="Bankkarte als PDF anhängen" + pdfDisabled={!bankCard.documentPath} /> )} {identityDocument && ( - toggle('identity')} preview={previewIdentity(identityDocument)} - extra={ - identityDocument.documentPath && ( - - ) - } + textChecked={insertIdentityText} + onToggleText={() => setInsertIdentityText((v) => !v)} + textLabel={`${identityTypeLabel(identityDocument.type)}-Nummer einfügen`} + textDisabled={!identityDocument.documentNumber} + pdfChecked={attachIdentityPdf} + onTogglePdf={() => setAttachIdentityPdf((v) => !v)} + pdfLabel={`${identityTypeLabel(identityDocument.type)} als PDF anhängen`} + pdfDisabled={!identityDocument.documentPath} /> )} @@ -404,6 +391,69 @@ interface SectionRowProps { extra?: React.ReactNode; } +interface DualChoiceRowProps { + title: string; + preview: string; + textChecked: boolean; + onToggleText: () => void; + textLabel: string; + textDisabled?: boolean; + pdfChecked: boolean; + onTogglePdf: () => void; + pdfLabel: string; + pdfDisabled?: boolean; +} + +/** + * Sections, die unabhängig Text und PDF anbieten (Bank, Ausweis). + * Keine primäre Checkbox – beide Schalter wirken einzeln, deshalb + * kein "alle-ein/alle-aus" auf Section-Ebene nötig. + */ +function DualChoiceRow({ + title, + preview, + textChecked, + onToggleText, + textLabel, + textDisabled, + pdfChecked, + onTogglePdf, + pdfLabel, + pdfDisabled, +}: DualChoiceRowProps) { + return ( +
+
{title}
+
{preview}
+
+ + +
+
+ ); +} + function SectionRow({ title, checked, onToggle, preview, extra }: SectionRowProps) { return (
@@ -502,17 +552,25 @@ function previewContract(c: Contract): string { return parts.join(' · '); } +// User-Wunsch 2026-06-21: nur die letzten 4 IBAN-Stellen einfügen, nicht +// die komplette IBAN/BIC/Bank-Liste. Vollständige Kontonummern per Mail +// versenden ist sowieso heikel – der Empfänger kann sich mit den letzten +// 4 Stellen für Identifikationszwecke ausweisen, ohne dass die ganze +// IBAN im Mail-Verlauf hängenbleibt. +function lastFourIban(iban: string | undefined | null): string { + if (!iban) return ''; + return iban.replace(/\s+/g, '').slice(-4); +} + function formatBankBlock(b: BankCard): string { - const lines: string[] = ['Bankverbindung:']; - if (b.accountHolder) lines.push(`Kontoinhaber: ${b.accountHolder}`); - lines.push(`IBAN: ${b.iban}`); - if (b.bic) lines.push(`BIC: ${b.bic}`); - if (b.bankName) lines.push(`Bank: ${b.bankName}`); - return lines.join('\n'); + const last4 = lastFourIban(b.iban); + if (!last4) return ''; + return `Bankverbindung:\nIBAN endet auf: ${last4}`; } function previewBank(b: BankCard): string { - return `IBAN: ${b.iban}${b.bankName ? ` · ${b.bankName}` : ''}`; + const last4 = lastFourIban(b.iban); + return last4 ? `IBAN …${last4}` : 'IBAN nicht hinterlegt'; } function identityTypeLabel(type: IdentityDocument['type']): string { @@ -525,18 +583,14 @@ function identityTypeLabel(type: IdentityDocument['type']): string { } } +// User-Wunsch 2026-06-21: nur die Ausweisnummer einfügen, keine +// Behörde / Daten – wenn der Empfänger mehr Details braucht, soll er +// die beigefügte PDF benutzen. function formatIdentityBlock(d: IdentityDocument): string { - const lines: string[] = [`${identityTypeLabel(d.type)}:`]; - if (d.documentNumber) lines.push(`Nummer: ${d.documentNumber}`); - if (d.issuingAuthority) lines.push(`Ausstellende Behörde: ${d.issuingAuthority}`); - if (d.issueDate) lines.push(`Ausstellungsdatum: ${formatDate(d.issueDate)}`); - if (d.expiryDate) lines.push(`Gültig bis: ${formatDate(d.expiryDate)}`); - return lines.join('\n'); + if (!d.documentNumber) return ''; + return `${identityTypeLabel(d.type)}-Nummer: ${d.documentNumber}`; } function previewIdentity(d: IdentityDocument): string { - const parts: string[] = []; - if (d.documentNumber) parts.push(`Nr. ${d.documentNumber}`); - if (d.expiryDate) parts.push(`gültig bis ${formatDate(d.expiryDate)}`); - return parts.join(' · '); + return d.documentNumber ? `Nr. ${d.documentNumber}` : 'Keine Nummer hinterlegt'; }