E-Mail-Ansicht: Postfach-Filter in Trash/Sent durchreichen

Bug: Im Vertrags-Tab (Gesendet/Gelöscht) und im Kunden-Haupt-
Postfach (Gelöscht) wurden Mails aus ALLEN Postfächern angezeigt,
unabhängig vom ausgewählten Postfach. Im Vertrag fehlte zusätzlich
der Vertrags-Filter im Papierkorb.

Backend:
- getEmailsForContract akzeptiert accountId → stressfreiEmailId
- getTrashEmails (controller + service) nimmt {accountId, contractId}
- getFolderCountsForContract bekommt optional stressfreiEmailId und
  zusätzlich trash/trashUnread im Result

Frontend:
- API-Client (getForContract/getTrash/getContractFolderCounts) nimmt
  Filter entgegen
- ContractEmailsSection reicht selectedAccountId in alle drei Queries
  + queryKey durch. Trash-Badge kommt jetzt aus contract-scoped
  Counts statt account-globalem stressfreiEmailApi
- EmailClientTab reicht selectedAccountId in die Trash-Query durch

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:06:24 +02:00
parent f02824fe7d
commit 993f2d10f0
6 changed files with 171 additions and 87 deletions
@@ -49,28 +49,44 @@ export default function ContractEmailsSection({
const selectedAccount = accounts.find((a) => a.id === selectedAccountId);
// E-Mails für den Vertrag laden (nach Ordner gefiltert, nicht für TRASH)
// E-Mails für den Vertrag laden (nach Ordner UND Postfach gefiltert).
// Bug 2026-06-21: vorher gingen Mails aus allen Postfächern in den
// gewählten Vertrags-Ordner obwohl der User ein bestimmtes Postfach
// ausgewählt hatte. selectedAccountId muss in queryKey + queryFn.
const { data: emailsData, isLoading, refetch: refetchEmails } = useQuery({
queryKey: ['emails', 'contract', contractId, selectedFolder],
queryFn: () => cachedEmailApi.getForContract(contractId, { folder: selectedFolder as 'INBOX' | 'SENT' }),
enabled: selectedFolder !== 'TRASH',
queryKey: ['emails', 'contract', contractId, selectedAccountId, selectedFolder],
queryFn: () => cachedEmailApi.getForContract(contractId, {
folder: selectedFolder as 'INBOX' | 'SENT',
accountId: selectedAccountId ?? undefined,
}),
enabled: selectedFolder !== 'TRASH' && !!selectedAccountId,
});
const emails = emailsData?.data || [];
// Papierkorb-E-Mails laden (für den ganzen Kunden, da Trash nicht vertragsgebunden)
// Papierkorb-E-Mails laden jetzt strikt: nur das aktuell ausgewählte
// Postfach UND nur dem Vertrag zugeordnete Mails. Wenn man also den
// Vertrags-Papierkorb öffnet, sieht man nicht mehr alle gelöschten
// E-Mails des Kunden, sondern wirklich nur die, die diesem Vertrag
// aus diesem Postfach zugeordnet waren.
const { data: trashData, isLoading: trashLoading } = useQuery({
queryKey: ['emails', 'trash', customerId],
queryFn: () => cachedEmailApi.getTrash(customerId),
enabled: selectedFolder === 'TRASH' && canAccessTrash,
queryKey: ['emails', 'trash', customerId, selectedAccountId, contractId],
queryFn: () => cachedEmailApi.getTrash(customerId, {
accountId: selectedAccountId ?? undefined,
contractId,
}),
enabled: selectedFolder === 'TRASH' && canAccessTrash && !!selectedAccountId,
});
const trashEmails = trashData?.data || [];
// Ordner-Anzahlen für Badges (Vertrag)
// Ordner-Anzahlen für Badges (Vertrag + Postfach). Badge und Liste
// müssen mit derselben Filter-Kombination laufen, sonst zeigt der
// Badge eine andere Zahl als die sichtbare Liste.
const { data: folderCountsData } = useQuery({
queryKey: ['contract-folder-counts', contractId],
queryFn: () => cachedEmailApi.getContractFolderCounts(contractId),
queryKey: ['contract-folder-counts', contractId, selectedAccountId],
queryFn: () => cachedEmailApi.getContractFolderCounts(contractId, selectedAccountId ?? undefined),
enabled: !!selectedAccountId,
});
const folderCounts = folderCountsData?.data || {
@@ -78,16 +94,6 @@ export default function ContractEmailsSection({
inboxUnread: 0,
sent: 0,
sentUnread: 0,
};
// Ordner-Anzahlen für das Konto (für Trash-Badge)
const { data: accountFolderCountsData } = useQuery({
queryKey: ['folder-counts', selectedAccountId],
queryFn: () => stressfreiEmailApi.getFolderCounts(selectedAccountId!),
enabled: !!selectedAccountId && canAccessTrash,
});
const accountFolderCounts = accountFolderCountsData?.data || {
trash: 0,
trashUnread: 0,
};
@@ -429,18 +435,18 @@ export default function ContractEmailsSection({
>
<Trash2 className="w-4 h-4" />
Papierkorb
{accountFolderCounts.trash > 0 && (
{folderCounts.trash > 0 && (
<span
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
accountFolderCounts.trashUnread > 0
folderCounts.trashUnread > 0
? 'bg-red-100 text-red-600 font-medium'
: 'bg-gray-100 text-gray-500'
}`}
title={`${accountFolderCounts.trashUnread} ungelesen / ${accountFolderCounts.trash} gesamt`}
title={`${folderCounts.trashUnread} ungelesen / ${folderCounts.trash} gesamt`}
>
{accountFolderCounts.trashUnread > 0
? `${accountFolderCounts.trashUnread}/${accountFolderCounts.trash}`
: accountFolderCounts.trash}
{folderCounts.trashUnread > 0
? `${folderCounts.trashUnread}/${folderCounts.trash}`
: folderCounts.trash}
</span>
)}
</button>
@@ -124,11 +124,17 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
const emails = emailsData?.data || [];
// Papierkorb-E-Mails laden
// Papierkorb-E-Mails laden jetzt strikt pro Postfach.
// Bug 2026-06-21: vorher kamen alle gelöschten E-Mails des Kunden
// raus, egal welches Postfach selektiert war. selectedAccountId muss
// in queryKey + queryFn, sonst greift React-Query-Cache bei Wechsel
// nicht und der Folder-Count aus folderCountsData liefe auseinander.
const { data: trashData, isLoading: trashLoading } = useQuery({
queryKey: ['emails', 'trash', customerId],
queryFn: () => cachedEmailApi.getTrash(customerId),
enabled: selectedFolder === 'TRASH' && canAccessTrash,
queryKey: ['emails', 'trash', customerId, selectedAccountId],
queryFn: () => cachedEmailApi.getTrash(customerId, {
accountId: selectedAccountId ?? undefined,
}),
enabled: selectedFolder === 'TRASH' && canAccessTrash && !!selectedAccountId,
});
const trashEmails = trashData?.data || [];
+16 -11
View File
@@ -597,19 +597,24 @@ export const cachedEmailApi = {
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails`, { params: options });
return res.data;
},
// E-Mails für Vertrag abrufen
getForContract: async (contractId: number, options?: { folder?: 'INBOX' | 'SENT'; limit?: number; offset?: number }) => {
// E-Mails für Vertrag abrufen (optional pro Postfach gefiltert)
getForContract: async (
contractId: number,
options?: { folder?: 'INBOX' | 'SENT'; accountId?: number; limit?: number; offset?: number },
) => {
const res = await api.get<ApiResponse<CachedEmail[]>>(`/contracts/${contractId}/emails`, { params: options });
return res.data;
},
// Ordner-Anzahlen für Vertrag abrufen (zugeordnete E-Mails)
getContractFolderCounts: async (contractId: number) => {
// Ordner-Anzahlen für Vertrag abrufen (zugeordnete E-Mails, optional pro Postfach)
getContractFolderCounts: async (contractId: number, accountId?: number) => {
const res = await api.get<ApiResponse<{
inbox: number;
inboxUnread: number;
sent: number;
sentUnread: number;
}>>(`/contracts/${contractId}/emails/folder-counts`);
trash: number;
trashUnread: number;
}>>(`/contracts/${contractId}/emails/folder-counts`, { params: accountId ? { accountId } : undefined });
return res.data;
},
// Mailbox-Konten eines Kunden abrufen
@@ -669,14 +674,14 @@ export const cachedEmailApi = {
return res.data;
},
// ==================== PAPIERKORB ====================
// Papierkorb-E-Mails für Kunden abrufen
getTrash: async (customerId: number) => {
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails/trash`);
// Papierkorb-E-Mails für Kunden abrufen (optional pro Postfach/Vertrag gefiltert)
getTrash: async (customerId: number, options?: { accountId?: number; contractId?: number }) => {
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails/trash`, { params: options });
return res.data;
},
// Papierkorb-Anzahl für Kunden
getTrashCount: async (customerId: number) => {
const res = await api.get<ApiResponse<{ count: number }>>(`/customers/${customerId}/emails/trash/count`);
// Papierkorb-Anzahl für Kunden (gleiche Filter wie getTrash)
getTrashCount: async (customerId: number, options?: { accountId?: number; contractId?: number }) => {
const res = await api.get<ApiResponse<{ count: number }>>(`/customers/${customerId}/emails/trash/count`, { params: options });
return res.data;
},
// E-Mail aus Papierkorb wiederherstellen