Compare commits

...
3 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.7 eeb89e09db Vertrag-Zugangsdaten: Externtab-Links neben Stressfrei-Select
Rechts neben dem Stressfrei-Adressen-Dropdown sitzen jetzt zwei
kleine Icon-Links (nur bei gewähltem Kunden sichtbar):
- ExternalLink → öffnet den Stressfrei-Tab des Kunden im neuen Tab
  (dort kann man die Adressen anlegen/pflegen)
- Mail → öffnet den E-Mail-Postfach-Tab des Kunden im neuen Tab

Damit muss man beim Vertragserstellen nicht mehr die Seite
wechseln, um schnell eine Stressfrei-Adresse anzulegen oder ins
Postfach zu springen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-09 13:34:55 +02:00
duffyduckandClaude Opus 4.7 e7f9aa65d4 Stressfrei-Email: Mailbox+Passwort per Default aktiv
Beim Anlegen einer neuen Stressfrei-E-Mail-Adresse: sobald die
Checkbox "Beim Provider anlegen" aktiv ist, wird jetzt automatisch
auch die zweite Checkbox "Echte Mailbox erstellen" mitgeschaltet.
Damit läuft `createEmail` mit `createMailbox: true` durch –
Backend generiert per generateSecurePassword ein Kennwort,
schickt es via -passwd an Plesk und speichert es verschlüsselt.

Vorher musste man beide Häkchen manuell setzen und übersah leicht
die zweite Checkbox → Adresse landete nur mit Weiterleitung ohne
IMAP/SMTP-Zugang bei Plesk.

Falls jemand nur eine reine Weiterleitung ohne Postfach will,
kann er die zweite Checkbox weiterhin abwählen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-09 12:34:24 +02:00
duffyduckandClaude Opus 4.7 616cb77c0d Bankkarte: optionales Beschreibung-Feld
Neues optionales Freitext-Feld `description` (TEXT NULL) auf
BankCard – z.B. "Geschäftskonto", "gemeinsames Konto mit Partner".
Migration mit IF NOT EXISTS.

Backend service create/update nimmt description entgegen.
Frontend:
- BankCard-Type um description ergänzt.
- BankCardModal: neue Textarea zwischen Ablaufdatum und Aktiv-
  Checkbox mit Placeholder-Hilfetext.
- Bank-Kartenübersicht zeigt die Beschreibung kursiv unter den
  bestehenden Feldern, falls gesetzt (whitespace-pre-line für
  mehrzeilige Notizen).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-09 12:22:17 +02:00
6 changed files with 71 additions and 12 deletions
@@ -0,0 +1,6 @@
-- Optionale Freitext-Beschreibung pro Bankkarte (z.B. "Geschäftskonto",
-- "gemeinsames Konto mit Partner"). TEXT statt VARCHAR, weil Notizen
-- gelegentlich länger werden dürfen.
ALTER TABLE `BankCard`
ADD COLUMN IF NOT EXISTS `description` TEXT NULL;
+1
View File
@@ -327,6 +327,7 @@ model BankCard {
bankName String?
expiryDate DateTime?
documentPath String? // Pfad zur hochgeladenen PDF
description String? @db.Text // Freitext-Notiz (z.B. "Geschäftskonto", "gemeinsames Konto mit Partner")
isActive Boolean @default(true)
contracts Contract[]
createdAt DateTime @default(now())
+2
View File
@@ -317,6 +317,7 @@ export async function createBankCard(
bic?: string;
bankName?: string;
expiryDate?: Date;
description?: string;
}
) {
return prisma.bankCard.create({
@@ -336,6 +337,7 @@ export async function updateBankCard(
bic?: string;
bankName?: string;
expiryDate?: Date;
description?: string;
isActive?: boolean;
}
) {
+38 -10
View File
@@ -15,7 +15,7 @@ import { buildContractLabelParts } from '../../utils/contractLabel';
import type { ContractType } from '../../types';
import { formatDate } from '../../utils/dateFormat';
import { useProviderSettings } from '../../hooks/useProviderSettings';
import { Plus, Trash2, Eye, EyeOff, Info, X, ArrowLeft, ExternalLink } from 'lucide-react';
import { Plus, Trash2, Eye, EyeOff, Info, X, ArrowLeft, ExternalLink, Mail } from 'lucide-react';
// Kleine Helper-Komponente: Label + Mini-Link im neuen Tab. Wird neben
// Select-Feldern eingesetzt, deren Stammdaten an anderer Stelle gepflegt
@@ -1116,15 +1116,43 @@ export default function ContractForm() {
<span className="text-sm">{customerEmailLabel} Adresse</span>
</label>
{usernameType === 'stressfrei' && (
<Select
value={selectedStressfreiEmailId}
onChange={(e) => setSelectedStressfreiEmailId(e.target.value)}
options={stressfreiEmails.map((e: { id: number; email: string; notes?: string }) => ({
value: e.id,
label: e.email + (e.notes ? ` (${e.notes})` : ''),
}))}
placeholder={stressfreiEmails.length === 0 ? 'Keine Stressfrei-Adressen vorhanden' : 'Adresse auswählen...'}
/>
<div className="flex items-start gap-2">
<div className="flex-1 min-w-0">
<Select
value={selectedStressfreiEmailId}
onChange={(e) => setSelectedStressfreiEmailId(e.target.value)}
options={stressfreiEmails.map((e: { id: number; email: string; notes?: string }) => ({
value: e.id,
label: e.email + (e.notes ? ` (${e.notes})` : ''),
}))}
placeholder={stressfreiEmails.length === 0 ? 'Keine Stressfrei-Adressen vorhanden' : 'Adresse auswählen...'}
/>
</div>
{customerId && (
<div className="flex items-center gap-1 pt-1.5 flex-shrink-0">
<a
href={`/customers/${customerId}?tab=stressfrei`}
target="_blank"
rel="noopener noreferrer"
title={`${customerEmailLabel} Adressen des Kunden in neuem Tab öffnen`}
aria-label={`${customerEmailLabel} Adressen des Kunden in neuem Tab öffnen`}
className="text-gray-400 hover:text-blue-600 p-1 rounded"
>
<ExternalLink className="w-4 h-4" />
</a>
<a
href={`/customers/${customerId}?tab=emails`}
target="_blank"
rel="noopener noreferrer"
title="E-Mail-Postfach des Kunden in neuem Tab öffnen"
aria-label="E-Mail-Postfach des Kunden in neuem Tab öffnen"
className="text-gray-400 hover:text-blue-600 p-1 rounded"
>
<Mail className="w-4 h-4" />
</a>
</div>
)}
</div>
)}
{usernameType === 'stressfrei' && stressfreiEmails.length === 0 && (
<p className="text-xs text-amber-600">
@@ -961,6 +961,11 @@ function BankCardsTab({
Gültig bis: {formatDate(card.expiryDate)}
</p>
)}
{card.description && (
<p className="text-sm text-gray-600 mt-1 italic whitespace-pre-line">
{card.description}
</p>
)}
{/* Dokument-Upload Bereich */}
<div className="mt-3 pt-3 border-t">
@@ -2657,6 +2662,7 @@ function BankCardModal({
bic: bankCard?.bic || '',
bankName: bankCard?.bankName || '',
expiryDate: bankCard?.expiryDate ? new Date(bankCard.expiryDate).toISOString().split('T')[0] : '',
description: bankCard?.description || '',
isActive: bankCard?.isActive ?? true,
});
@@ -2672,7 +2678,7 @@ function BankCardModal({
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
onClose();
setFormData({ accountHolder: '', iban: '', bic: '', bankName: '', expiryDate: '', isActive: true });
setFormData({ accountHolder: '', iban: '', bic: '', bankName: '', expiryDate: '', description: '', isActive: true });
},
});
@@ -2743,6 +2749,17 @@ function BankCardModal({
onClear={() => setFormData({ ...formData, expiryDate: '' })}
/>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={2}
placeholder="z.B. Geschäftskonto, gemeinsames Konto mit Partner…"
className="block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{isEditing && (
<label className="flex items-center gap-2">
<input
@@ -4057,7 +4074,11 @@ function StressfreiEmailModal({
checked={provisionAtProvider}
onChange={(e) => {
setProvisionAtProvider(e.target.checked);
if (!e.target.checked) setCreateMailbox(false);
// Sobald "Beim Provider anlegen" aktiv ist, wollen wir
// per Default direkt eine echte Mailbox mit Passwort
// sonst müsste man zwei Häkchen setzen, um ein
// vollwertiges Postfach zu bekommen.
setCreateMailbox(e.target.checked);
}}
className="mt-1 rounded border-gray-300"
/>
+1
View File
@@ -150,6 +150,7 @@ export interface BankCard {
bankName?: string;
expiryDate?: string;
documentPath?: string;
description?: string;
isActive: boolean;
}