Kundendaten-Modal: Bank + Ausweis getrennte Text/PDF-Wahl
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 <noreply@anthropic.com>
This commit is contained in:
@@ -39,9 +39,7 @@ type SectionKey =
|
|||||||
| 'customer'
|
| 'customer'
|
||||||
| 'deliveryAddress'
|
| 'deliveryAddress'
|
||||||
| 'billingAddress'
|
| 'billingAddress'
|
||||||
| 'contract'
|
| 'contract';
|
||||||
| 'iban'
|
|
||||||
| 'identity';
|
|
||||||
|
|
||||||
export default function InsertCustomerDataModal({
|
export default function InsertCustomerDataModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -72,18 +70,23 @@ export default function InsertCustomerDataModal({
|
|||||||
const bankCard = contract?.bankCard;
|
const bankCard = contract?.bankCard;
|
||||||
const identityDocument = contract?.identityDocument;
|
const identityDocument = contract?.identityDocument;
|
||||||
|
|
||||||
// Sections die default-an sind: Anrede + Vertragsdaten. Anhang-Checkboxen
|
// Sections die default-an sind: Anrede + Vertragsdaten. Anhang-/Text-
|
||||||
// bleiben default-aus (User-Intent).
|
// Schalter für Bank + Ausweis bleiben default-aus (User-Intent: bewusst
|
||||||
|
// entscheiden, was vertraulich verschickt wird).
|
||||||
const [checked, setChecked] = useState<Record<SectionKey, boolean>>({
|
const [checked, setChecked] = useState<Record<SectionKey, boolean>>({
|
||||||
customer: true,
|
customer: true,
|
||||||
deliveryAddress: true,
|
deliveryAddress: true,
|
||||||
billingAddress: false,
|
billingAddress: false,
|
||||||
contract: true,
|
contract: true,
|
||||||
iban: false,
|
|
||||||
identity: false,
|
|
||||||
});
|
});
|
||||||
const [attachBankCard, setAttachBankCard] = useState(false);
|
// Bank: zwei unabhängige Schalter. Text fügt nur die letzten 4 IBAN-
|
||||||
const [attachIdentity, setAttachIdentity] = useState(false);
|
// 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:
|
// Welche E-Mail-Adresse in der Customer-Section steht:
|
||||||
// - 'master' = Stammdaten-E-Mail (customer.email)
|
// - 'master' = Stammdaten-E-Mail (customer.email)
|
||||||
// - 'sender' = Postfach-Adresse, von der die Mail abgeht (Stressfrei)
|
// - 'sender' = Postfach-Adresse, von der die Mail abgeht (Stressfrei)
|
||||||
@@ -100,11 +103,11 @@ export default function InsertCustomerDataModal({
|
|||||||
deliveryAddress: !!deliveryAddress,
|
deliveryAddress: !!deliveryAddress,
|
||||||
billingAddress: false, // nur wenn vorhanden, aber default aus
|
billingAddress: false, // nur wenn vorhanden, aber default aus
|
||||||
contract: true,
|
contract: true,
|
||||||
iban: false,
|
|
||||||
identity: false,
|
|
||||||
});
|
});
|
||||||
setAttachBankCard(false);
|
setInsertBankText(false);
|
||||||
setAttachIdentity(false);
|
setAttachBankPdf(false);
|
||||||
|
setInsertIdentityText(false);
|
||||||
|
setAttachIdentityPdf(false);
|
||||||
// Default: Stammdaten-E-Mail wenn vorhanden, sonst Absender-Adresse.
|
// Default: Stammdaten-E-Mail wenn vorhanden, sonst Absender-Adresse.
|
||||||
setEmailChoice(customer?.email ? 'master' : 'sender');
|
setEmailChoice(customer?.email ? 'master' : 'sender');
|
||||||
}
|
}
|
||||||
@@ -143,10 +146,10 @@ export default function InsertCustomerDataModal({
|
|||||||
if (checked.contract) {
|
if (checked.contract) {
|
||||||
blocks.push(formatContractBlock(contract));
|
blocks.push(formatContractBlock(contract));
|
||||||
}
|
}
|
||||||
if (checked.iban && bankCard) {
|
if (insertBankText && bankCard) {
|
||||||
blocks.push(formatBankBlock(bankCard));
|
blocks.push(formatBankBlock(bankCard));
|
||||||
}
|
}
|
||||||
if (checked.identity && identityDocument) {
|
if (insertIdentityText && identityDocument) {
|
||||||
blocks.push(formatIdentityBlock(identityDocument));
|
blocks.push(formatIdentityBlock(identityDocument));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,13 +182,13 @@ export default function InsertCustomerDataModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (attachBankCard && bankCard?.documentPath) {
|
if (attachBankPdf && bankCard?.documentPath) {
|
||||||
await tryAttach(
|
await tryAttach(
|
||||||
bankCard.documentPath,
|
bankCard.documentPath,
|
||||||
bankCardAttachmentName(bankCard.iban),
|
bankCardAttachmentName(bankCard.iban),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (attachIdentity && identityDocument?.documentPath) {
|
if (attachIdentityPdf && identityDocument?.documentPath) {
|
||||||
await tryAttach(
|
await tryAttach(
|
||||||
identityDocument.documentPath,
|
identityDocument.documentPath,
|
||||||
identityDocAttachmentName(
|
identityDocAttachmentName(
|
||||||
@@ -212,10 +215,10 @@ export default function InsertCustomerDataModal({
|
|||||||
!checked.deliveryAddress &&
|
!checked.deliveryAddress &&
|
||||||
!checked.billingAddress &&
|
!checked.billingAddress &&
|
||||||
!checked.contract &&
|
!checked.contract &&
|
||||||
!checked.iban &&
|
!insertBankText &&
|
||||||
!checked.identity &&
|
!attachBankPdf &&
|
||||||
!attachBankCard &&
|
!insertIdentityText &&
|
||||||
!attachIdentity;
|
!attachIdentityPdf;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -313,47 +316,31 @@ export default function InsertCustomerDataModal({
|
|||||||
preview={previewContract(contract)}
|
preview={previewContract(contract)}
|
||||||
/>
|
/>
|
||||||
{bankCard && (
|
{bankCard && (
|
||||||
<SectionRow
|
<DualChoiceRow
|
||||||
title="Bankverbindung"
|
title="Bankverbindung"
|
||||||
checked={checked.iban}
|
|
||||||
onToggle={() => toggle('iban')}
|
|
||||||
preview={previewBank(bankCard)}
|
preview={previewBank(bankCard)}
|
||||||
extra={
|
textChecked={insertBankText}
|
||||||
bankCard.documentPath && (
|
onToggleText={() => setInsertBankText((v) => !v)}
|
||||||
<label className="flex items-center gap-2 text-xs text-gray-600 mt-1 ml-6 cursor-pointer">
|
textLabel="Letzte 4 IBAN-Stellen einfügen"
|
||||||
<input
|
textDisabled={!lastFourIban(bankCard.iban)}
|
||||||
type="checkbox"
|
pdfChecked={attachBankPdf}
|
||||||
checked={attachBankCard}
|
onTogglePdf={() => setAttachBankPdf((v) => !v)}
|
||||||
onChange={(e) => setAttachBankCard(e.target.checked)}
|
pdfLabel="Bankkarte als PDF anhängen"
|
||||||
className="rounded"
|
pdfDisabled={!bankCard.documentPath}
|
||||||
/>
|
|
||||||
<span>Bankkarte als PDF anhängen</span>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{identityDocument && (
|
{identityDocument && (
|
||||||
<SectionRow
|
<DualChoiceRow
|
||||||
title={identityTypeLabel(identityDocument.type)}
|
title={identityTypeLabel(identityDocument.type)}
|
||||||
checked={checked.identity}
|
|
||||||
onToggle={() => toggle('identity')}
|
|
||||||
preview={previewIdentity(identityDocument)}
|
preview={previewIdentity(identityDocument)}
|
||||||
extra={
|
textChecked={insertIdentityText}
|
||||||
identityDocument.documentPath && (
|
onToggleText={() => setInsertIdentityText((v) => !v)}
|
||||||
<label className="flex items-center gap-2 text-xs text-gray-600 mt-1 ml-6 cursor-pointer">
|
textLabel={`${identityTypeLabel(identityDocument.type)}-Nummer einfügen`}
|
||||||
<input
|
textDisabled={!identityDocument.documentNumber}
|
||||||
type="checkbox"
|
pdfChecked={attachIdentityPdf}
|
||||||
checked={attachIdentity}
|
onTogglePdf={() => setAttachIdentityPdf((v) => !v)}
|
||||||
onChange={(e) => setAttachIdentity(e.target.checked)}
|
pdfLabel={`${identityTypeLabel(identityDocument.type)} als PDF anhängen`}
|
||||||
className="rounded"
|
pdfDisabled={!identityDocument.documentPath}
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
{identityTypeLabel(identityDocument.type)} als PDF anhängen
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -404,6 +391,69 @@ interface SectionRowProps {
|
|||||||
extra?: React.ReactNode;
|
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 (
|
||||||
|
<div className="border border-gray-200 rounded-lg p-3">
|
||||||
|
<div className="text-sm font-medium text-gray-700">{title}</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">{preview}</div>
|
||||||
|
<div className="mt-2 space-y-1">
|
||||||
|
<label className={`flex items-center gap-2 text-xs cursor-pointer ${textDisabled ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700'}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={textChecked}
|
||||||
|
onChange={onToggleText}
|
||||||
|
disabled={textDisabled}
|
||||||
|
className="rounded"
|
||||||
|
/>
|
||||||
|
<span>{textLabel}</span>
|
||||||
|
</label>
|
||||||
|
<label className={`flex items-center gap-2 text-xs cursor-pointer ${pdfDisabled ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700'}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={pdfChecked}
|
||||||
|
onChange={onTogglePdf}
|
||||||
|
disabled={pdfDisabled}
|
||||||
|
className="rounded"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{pdfLabel}
|
||||||
|
{pdfDisabled && <span className="ml-1">(keine PDF hinterlegt)</span>}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SectionRow({ title, checked, onToggle, preview, extra }: SectionRowProps) {
|
function SectionRow({ title, checked, onToggle, preview, extra }: SectionRowProps) {
|
||||||
return (
|
return (
|
||||||
<div className="border border-gray-200 rounded-lg p-3">
|
<div className="border border-gray-200 rounded-lg p-3">
|
||||||
@@ -502,17 +552,25 @@ function previewContract(c: Contract): string {
|
|||||||
return parts.join(' · ');
|
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 {
|
function formatBankBlock(b: BankCard): string {
|
||||||
const lines: string[] = ['Bankverbindung:'];
|
const last4 = lastFourIban(b.iban);
|
||||||
if (b.accountHolder) lines.push(`Kontoinhaber: ${b.accountHolder}`);
|
if (!last4) return '';
|
||||||
lines.push(`IBAN: ${b.iban}`);
|
return `Bankverbindung:\nIBAN endet auf: ${last4}`;
|
||||||
if (b.bic) lines.push(`BIC: ${b.bic}`);
|
|
||||||
if (b.bankName) lines.push(`Bank: ${b.bankName}`);
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewBank(b: BankCard): string {
|
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 {
|
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 {
|
function formatIdentityBlock(d: IdentityDocument): string {
|
||||||
const lines: string[] = [`${identityTypeLabel(d.type)}:`];
|
if (!d.documentNumber) return '';
|
||||||
if (d.documentNumber) lines.push(`Nummer: ${d.documentNumber}`);
|
return `${identityTypeLabel(d.type)}-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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewIdentity(d: IdentityDocument): string {
|
function previewIdentity(d: IdentityDocument): string {
|
||||||
const parts: string[] = [];
|
return d.documentNumber ? `Nr. ${d.documentNumber}` : 'Keine Nummer hinterlegt';
|
||||||
if (d.documentNumber) parts.push(`Nr. ${d.documentNumber}`);
|
|
||||||
if (d.expiryDate) parts.push(`gültig bis ${formatDate(d.expiryDate)}`);
|
|
||||||
return parts.join(' · ');
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user