Datenschutz vollmacht fixed, two time counter added
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useNavigate, useSearchParams, useLocation } from 'react-router-dom';
|
||||
import { pushHistory, popHistory } from '../../utils/navigation';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { customerApi, addressApi, bankCardApi, documentApi, meterApi, uploadApi, contractApi, stressfreiEmailApi, emailProviderApi, gdprApi, StressfreiEmail, ContractTreeNode } from '../../services/api';
|
||||
import { EmailClientTab } from '../../components/email';
|
||||
@@ -22,7 +23,7 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
|
||||
const queryClient = useQueryClient();
|
||||
const { hasPermission, isCustomerPortal } = useAuth();
|
||||
const location = useLocation();
|
||||
const backTo = (location.state as any)?.from as string | undefined;
|
||||
const back = popHistory(location.state, isCustomerPortal ? '/' : '/customers');
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const customerId = portalCustomerId || parseInt(id!);
|
||||
const defaultTab = searchParams.get('tab') || 'addresses';
|
||||
@@ -204,7 +205,7 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate(backTo || (isCustomerPortal ? '/' : '/customers'))}>
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate(back.to, { state: back.state })}>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
<div>
|
||||
@@ -221,7 +222,7 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{hasPermission('customers:update') && (
|
||||
<Link to={`/customers/${id}/edit`} state={{ from: `/customers/${id}` }}>
|
||||
<Link to={`/customers/${id}/edit`} state={pushHistory(location.pathname + location.search, (location as any).state)}>
|
||||
<Button variant="secondary">
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
Bearbeiten
|
||||
@@ -649,7 +650,7 @@ function AddressesTab({
|
||||
const queryClient = useQueryClient();
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: addressApi.delete,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -746,18 +747,18 @@ function BankCardsTab({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<BankCard> }) =>
|
||||
bankCardApi.update(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: bankCardApi.delete,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const handleDocumentUpload = async (cardId: number, file: File) => {
|
||||
try {
|
||||
await uploadApi.uploadBankCardDocument(cardId, file);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Upload fehlgeschlagen:', error);
|
||||
alert('Upload fehlgeschlagen');
|
||||
@@ -768,7 +769,7 @@ function BankCardsTab({
|
||||
if (!confirm('Dokument wirklich löschen?')) return;
|
||||
try {
|
||||
await uploadApi.deleteBankCardDocument(cardId);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Löschen fehlgeschlagen:', error);
|
||||
alert('Löschen fehlgeschlagen');
|
||||
@@ -972,18 +973,18 @@ function DocumentsTab({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<IdentityDocument> }) =>
|
||||
documentApi.update(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: documentApi.delete,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const handleDocumentUpload = async (docId: number, file: File) => {
|
||||
try {
|
||||
await uploadApi.uploadIdentityDocument(docId, file);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Upload fehlgeschlagen:', error);
|
||||
alert('Upload fehlgeschlagen');
|
||||
@@ -994,7 +995,7 @@ function DocumentsTab({
|
||||
if (!confirm('Dokument wirklich löschen?')) return;
|
||||
try {
|
||||
await uploadApi.deleteIdentityDocument(docId);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Löschen fehlgeschlagen:', error);
|
||||
alert('Löschen fehlgeschlagen');
|
||||
@@ -1204,26 +1205,34 @@ function MetersTab({
|
||||
onAdd: () => void;
|
||||
onEdit: (meter: Meter) => void;
|
||||
}) {
|
||||
const [showReadingModal, setShowReadingModal] = useState<{ meterId: number; meterType: 'ELECTRICITY' | 'GAS' } | null>(null);
|
||||
const [showReadingModal, setShowReadingModal] = useState<{ meterId: number; meterType: 'ELECTRICITY' | 'GAS'; tariffModel?: string } | null>(null);
|
||||
const [expandedMeter, setExpandedMeter] = useState<number | null>(null);
|
||||
const [editingReading, setEditingReading] = useState<{ meterId: number; meterType: 'ELECTRICITY' | 'GAS'; reading: any } | null>(null);
|
||||
const [editingReading, setEditingReading] = useState<{ meterId: number; meterType: 'ELECTRICITY' | 'GAS'; tariffModel?: string; reading: any } | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<Meter> }) =>
|
||||
meterApi.update(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: meterApi.delete,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
setDeleteError(null);
|
||||
},
|
||||
onError: (err) => {
|
||||
setDeleteError(err instanceof Error ? err.message : 'Fehler beim Löschen');
|
||||
},
|
||||
});
|
||||
|
||||
const deleteReadingMutation = useMutation({
|
||||
mutationFn: ({ meterId, readingId }: { meterId: number; readingId: number }) =>
|
||||
meterApi.deleteReading(meterId, readingId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const filtered = showInactive ? meters : meters.filter((m) => m.isActive);
|
||||
@@ -1273,6 +1282,9 @@ function MetersTab({
|
||||
<Badge variant={meter.type === 'ELECTRICITY' ? 'warning' : 'info'}>
|
||||
{meter.type === 'ELECTRICITY' ? 'Strom' : 'Gas'}
|
||||
</Badge>
|
||||
{meter.tariffModel === 'DUAL' && (
|
||||
<Badge variant="default">HT/NT</Badge>
|
||||
)}
|
||||
{!meter.isActive && <Badge variant="danger">Inaktiv</Badge>}
|
||||
</div>
|
||||
{canEdit && (
|
||||
@@ -1281,7 +1293,7 @@ function MetersTab({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowReadingModal({ meterId: meter.id, meterType: meter.type })}
|
||||
onClick={() => setShowReadingModal({ meterId: meter.id, meterType: meter.type, tariffModel: meter.tariffModel })}
|
||||
title="Zählerstand hinzufügen"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
@@ -1371,14 +1383,17 @@ function MetersTab({
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono flex items-center gap-1">
|
||||
{reading.value.toLocaleString('de-DE')} {reading.unit}
|
||||
<CopyButton value={reading.value.toString()} title="Nur Wert kopieren" />
|
||||
<CopyButton value={`${reading.value.toLocaleString('de-DE')} ${reading.unit}`} title="Mit Einheit kopieren" />
|
||||
{reading.valueNt !== undefined && reading.valueNt !== null ? (
|
||||
<>HT: {reading.value.toLocaleString('de-DE')} / NT: {reading.valueNt.toLocaleString('de-DE')} {reading.unit}</>
|
||||
) : (
|
||||
<>{reading.value.toLocaleString('de-DE')} {reading.unit}</>
|
||||
)}
|
||||
<CopyButton value={reading.value.toString()} title="Wert kopieren" />
|
||||
</span>
|
||||
{canEdit && (
|
||||
<div className="opacity-0 group-hover:opacity-100 flex gap-1">
|
||||
<button
|
||||
onClick={() => setEditingReading({ meterId: meter.id, meterType: meter.type, reading })}
|
||||
onClick={() => setEditingReading({ meterId: meter.id, meterType: meter.type, tariffModel: meter.tariffModel, reading })}
|
||||
className="text-gray-400 hover:text-blue-600"
|
||||
title="Bearbeiten"
|
||||
>
|
||||
@@ -1417,6 +1432,7 @@ function MetersTab({
|
||||
onClose={() => setShowReadingModal(null)}
|
||||
meterId={showReadingModal.meterId}
|
||||
meterType={showReadingModal.meterType}
|
||||
tariffModel={showReadingModal.tariffModel as any}
|
||||
customerId={customerId}
|
||||
/>
|
||||
)}
|
||||
@@ -1427,10 +1443,43 @@ function MetersTab({
|
||||
onClose={() => setEditingReading(null)}
|
||||
meterId={editingReading.meterId}
|
||||
meterType={editingReading.meterType}
|
||||
tariffModel={editingReading.tariffModel as any}
|
||||
customerId={customerId}
|
||||
reading={editingReading.reading}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Fehler-Modal beim Löschen (z.B. Zähler noch an Vertrag) */}
|
||||
{deleteError && (
|
||||
<Modal isOpen={true} onClose={() => setDeleteError(null)} title="Zähler kann nicht gelöscht werden">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Der Zähler ist noch folgenden Verträgen zugeordnet und kann daher nicht gelöscht werden:
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{deleteError.match(/[A-Z]+-[A-Z0-9]+/g)?.map((contractNumber) => (
|
||||
<Link
|
||||
key={contractNumber}
|
||||
to={`/contracts?search=${contractNumber}`}
|
||||
onClick={() => setDeleteError(null)}
|
||||
className="flex items-center gap-2 p-3 bg-gray-50 border rounded-lg text-blue-600 hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||||
>
|
||||
<FileText className="w-4 h-4" />
|
||||
<span className="font-mono">{contractNumber}</span>
|
||||
</Link>
|
||||
)) ?? (
|
||||
<p className="text-sm text-red-600">{deleteError}</p>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-4">
|
||||
Bitte entfernen Sie den Zähler zuerst aus den oben genannten Verträgen.
|
||||
</p>
|
||||
<div className="flex justify-end mt-4">
|
||||
<Button variant="secondary" onClick={() => setDeleteError(null)}>
|
||||
Schließen
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1457,7 +1506,7 @@ function ContractsTab({
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: contractApi.delete,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customers'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['contracts'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['contract-tree', customerId] });
|
||||
@@ -1549,7 +1598,7 @@ function ContractsTab({
|
||||
<div className="w-6" /> // Platzhalter für Ausrichtung
|
||||
) : null}
|
||||
|
||||
<Link to={`/contracts/${contract.id}`} state={{ from: `/customers/${customerId}?tab=contracts` }} className="font-mono flex items-center gap-1 text-blue-600 hover:underline">
|
||||
<Link to={`/contracts/${contract.id}`} state={pushHistory(location.pathname + location.search, (location as any).state)} className="font-mono flex items-center gap-1 text-blue-600 hover:underline">
|
||||
{contract.contractNumber}
|
||||
<CopyButton value={contract.contractNumber} />
|
||||
</Link>
|
||||
@@ -2054,7 +2103,7 @@ function AddressModal({
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: typeof formData) => addressApi.create(customerId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
setFormData({
|
||||
type: 'DELIVERY_RESIDENCE',
|
||||
@@ -2071,7 +2120,7 @@ function AddressModal({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: typeof formData) => addressApi.update(address!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
@@ -2201,7 +2250,7 @@ function BankCardModal({
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => bankCardApi.create(customerId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
setFormData({ accountHolder: '', iban: '', bic: '', bankName: '', expiryDate: '', isActive: true });
|
||||
},
|
||||
@@ -2210,7 +2259,7 @@ function BankCardModal({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: any) => bankCardApi.update(bankCard!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
@@ -2333,7 +2382,7 @@ function DocumentModal({
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => documentApi.create(customerId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
setFormData({
|
||||
type: 'ID_CARD',
|
||||
@@ -2351,7 +2400,7 @@ function DocumentModal({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: any) => documentApi.update(document!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
@@ -2498,6 +2547,7 @@ function MeterModal({
|
||||
const getInitialFormData = () => ({
|
||||
meterNumber: meter?.meterNumber || '',
|
||||
type: meter?.type || 'ELECTRICITY' as const,
|
||||
tariffModel: meter?.tariffModel || 'SINGLE' as const,
|
||||
location: meter?.location || '',
|
||||
isActive: meter?.isActive ?? true,
|
||||
});
|
||||
@@ -2507,16 +2557,16 @@ function MeterModal({
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => meterApi.create(customerId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
setFormData({ meterNumber: '', type: 'ELECTRICITY', location: '', isActive: true });
|
||||
setFormData({ meterNumber: '', type: 'ELECTRICITY', tariffModel: 'SINGLE', location: '', isActive: true });
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: any) => meterApi.update(meter!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
@@ -2557,6 +2607,18 @@ function MeterModal({
|
||||
]}
|
||||
/>
|
||||
|
||||
{formData.type === 'ELECTRICITY' && (
|
||||
<Select
|
||||
label="Tarifmodell"
|
||||
value={formData.tariffModel}
|
||||
onChange={(e) => setFormData({ ...formData, tariffModel: e.target.value as any })}
|
||||
options={[
|
||||
{ value: 'SINGLE', label: 'Eintarifzähler (Standard)' },
|
||||
{ value: 'DUAL', label: 'Zweitarifzähler (HT/NT)' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Standort"
|
||||
value={formData.location}
|
||||
@@ -2594,6 +2656,7 @@ function MeterReadingModal({
|
||||
onClose,
|
||||
meterId,
|
||||
meterType,
|
||||
tariffModel,
|
||||
customerId,
|
||||
reading,
|
||||
}: {
|
||||
@@ -2601,47 +2664,63 @@ function MeterReadingModal({
|
||||
onClose: () => void;
|
||||
meterId: number;
|
||||
meterType: 'ELECTRICITY' | 'GAS';
|
||||
tariffModel?: 'SINGLE' | 'DUAL';
|
||||
customerId: number;
|
||||
reading?: { id: number; readingDate: string; value: number; unit: string; notes?: string } | null;
|
||||
reading?: { id: number; readingDate: string; value: number; valueNt?: number; unit: string; notes?: string } | null;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const isEditing = !!reading;
|
||||
const defaultUnit = meterType === 'ELECTRICITY' ? 'kWh' : 'm³';
|
||||
const isDualTariff = tariffModel === 'DUAL';
|
||||
|
||||
const getInitialFormData = () => ({
|
||||
readingDate: reading?.readingDate
|
||||
? new Date(reading.readingDate).toISOString().split('T')[0]
|
||||
: new Date().toISOString().split('T')[0],
|
||||
value: reading?.value?.toString() || '',
|
||||
valueNt: reading?.valueNt?.toString() || '',
|
||||
notes: reading?.notes || '',
|
||||
});
|
||||
|
||||
const [formData, setFormData] = useState(getInitialFormData);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => meterApi.addReading(meterId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
setError(null);
|
||||
onClose();
|
||||
},
|
||||
onError: (err) => {
|
||||
setError(err instanceof Error ? err.message : 'Fehler beim Speichern');
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: any) => meterApi.updateReading(meterId, reading!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
setError(null);
|
||||
onClose();
|
||||
},
|
||||
onError: (err) => {
|
||||
setError(err instanceof Error ? err.message : 'Fehler beim Speichern');
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
const data: Record<string, unknown> = {
|
||||
readingDate: new Date(formData.readingDate),
|
||||
value: parseFloat(formData.value),
|
||||
unit: defaultUnit,
|
||||
notes: formData.notes || undefined,
|
||||
};
|
||||
if (isDualTariff && formData.valueNt) {
|
||||
data.valueNt = parseFloat(formData.valueNt);
|
||||
}
|
||||
if (isEditing) {
|
||||
updateMutation.mutate(data);
|
||||
} else {
|
||||
@@ -2667,10 +2746,10 @@ function MeterReadingModal({
|
||||
required
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="col-span-2">
|
||||
<div className={`grid ${isDualTariff ? 'grid-cols-2' : 'grid-cols-3'} gap-4`}>
|
||||
<div className={isDualTariff ? '' : 'col-span-2'}>
|
||||
<Input
|
||||
label="Zählerstand"
|
||||
label={isDualTariff ? 'HT-Stand (Hochtarif)' : 'Zählerstand'}
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.value}
|
||||
@@ -2678,12 +2757,26 @@ function MeterReadingModal({
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Einheit</label>
|
||||
<div className="h-10 flex items-center px-3 bg-gray-100 border border-gray-300 rounded-md text-gray-700">
|
||||
{defaultUnit}
|
||||
{isDualTariff && (
|
||||
<div>
|
||||
<Input
|
||||
label="NT-Stand (Niedertarif)"
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.valueNt}
|
||||
onChange={(e) => setFormData({ ...formData, valueNt: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isDualTariff && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Einheit</label>
|
||||
<div className="h-10 flex items-center px-3 bg-gray-100 border border-gray-300 rounded-md text-gray-700">
|
||||
{defaultUnit}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Input
|
||||
@@ -2693,6 +2786,12 @@ function MeterReadingModal({
|
||||
placeholder="Optionale Notizen..."
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="secondary" onClick={onClose}>
|
||||
Abbrechen
|
||||
@@ -2732,12 +2831,12 @@ function StressfreiEmailsTab({
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<StressfreiEmail> }) =>
|
||||
stressfreiEmailApi.update(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: stressfreiEmailApi.delete,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] }),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
const filtered = showInactive ? emails : emails.filter((e) => e.isActive);
|
||||
@@ -3087,7 +3186,7 @@ function StressfreiEmailModal({
|
||||
const result = await stressfreiEmailApi.enableMailbox(email.id);
|
||||
if (result.success) {
|
||||
setMailboxEnabled(true);
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mailbox-accounts', customerId] });
|
||||
} else {
|
||||
setProvisionError(result.error || 'Mailbox-Aktivierung fehlgeschlagen');
|
||||
@@ -3108,7 +3207,7 @@ function StressfreiEmailModal({
|
||||
setMailboxEnabled(result.data.hasMailbox);
|
||||
if (result.data.wasUpdated) {
|
||||
// DB wurde aktualisiert, Query invalidieren
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -3199,7 +3298,7 @@ function StressfreiEmailModal({
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mailbox-accounts', customerId] });
|
||||
setLocalPart('');
|
||||
setNotes('');
|
||||
@@ -3216,7 +3315,7 @@ function StressfreiEmailModal({
|
||||
mutationFn: (data: Partial<StressfreiEmail>) =>
|
||||
stressfreiEmailApi.update(email!.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId.toString()] });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
@@ -3664,6 +3763,7 @@ function ConsentTab({
|
||||
try {
|
||||
await uploadApi.uploadPrivacyPolicy(customerId, file);
|
||||
onUpdate?.();
|
||||
queryClient.invalidateQueries({ queryKey: ['customer-consents', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Upload fehlgeschlagen:', error);
|
||||
alert('Upload fehlgeschlagen');
|
||||
@@ -3675,6 +3775,7 @@ function ConsentTab({
|
||||
try {
|
||||
await uploadApi.deletePrivacyPolicy(customerId);
|
||||
onUpdate?.();
|
||||
queryClient.invalidateQueries({ queryKey: ['customer-consents', customerId] });
|
||||
} catch (error) {
|
||||
console.error('Löschen fehlgeschlagen:', error);
|
||||
alert('Löschen fehlgeschlagen');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { popHistory } from '../../utils/navigation';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customerApi } from '../../services/api';
|
||||
@@ -17,7 +18,7 @@ export default function CustomerForm() {
|
||||
const location = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
const backTo = (location.state as any)?.from as string | undefined;
|
||||
const back = popHistory(location.state, isEdit ? `/customers/${id}` : '/customers');
|
||||
|
||||
const { register, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm<CustomerFormData>();
|
||||
const customerType = watch('type');
|
||||
@@ -234,7 +235,7 @@ export default function CustomerForm() {
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button type="button" variant="secondary" onClick={() => navigate(backTo || (isEdit ? `/customers/${id}` : '/customers'))}>
|
||||
<Button type="button" variant="secondary" onClick={() => navigate(back.to, { state: back.state })}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pushHistory } from '../../utils/navigation';
|
||||
import { customerApi } from '../../services/api';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import Card from '../../components/ui/Card';
|
||||
@@ -78,12 +79,12 @@ export default function CustomerList() {
|
||||
{data.data.map((customer) => (
|
||||
<tr key={customer.id} className="border-b hover:bg-gray-50">
|
||||
<td className="py-3 px-4 font-mono text-sm">
|
||||
<Link to={`/customers/${customer.id}`} state={{ from: '/customers' }} className="text-blue-600 hover:underline">
|
||||
<Link to={`/customers/${customer.id}`} state={pushHistory('/customers')} className="text-blue-600 hover:underline">
|
||||
{customer.customerNumber}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Link to={`/customers/${customer.id}`} state={{ from: '/customers' }} className="text-blue-600 hover:underline">
|
||||
<Link to={`/customers/${customer.id}`} state={pushHistory('/customers')} className="text-blue-600 hover:underline">
|
||||
{customer.type === 'BUSINESS' && customer.companyName
|
||||
? customer.companyName
|
||||
: `${customer.firstName} ${customer.lastName}`}
|
||||
|
||||
Reference in New Issue
Block a user