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:
@@ -81,17 +81,22 @@ export async function getEmailsForCustomer(req: AuthRequest, res: Response): Pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// E-Mails für einen Vertrag abrufen
|
// E-Mails für einen Vertrag abrufen.
|
||||||
|
// `accountId` (optional) schränkt zusätzlich auf ein bestimmtes Postfach
|
||||||
|
// ein – ohne, sieht man im Vertrags-Tab Mails aus ALLEN Postfächern des
|
||||||
|
// Kunden, die dem Vertrag zugeordnet sind (User-Bug 2026-06-21).
|
||||||
export async function getEmailsForContract(req: AuthRequest, res: Response): Promise<void> {
|
export async function getEmailsForContract(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const contractId = parseInt(req.params.contractId);
|
const contractId = parseInt(req.params.contractId);
|
||||||
if (!(await canAccessContract(req, res, contractId))) return;
|
if (!(await canAccessContract(req, res, contractId))) return;
|
||||||
const folder = req.query.folder as string | undefined; // INBOX oder SENT
|
const folder = req.query.folder as string | undefined; // INBOX oder SENT
|
||||||
|
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
|
||||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
|
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
|
||||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
|
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
|
||||||
|
|
||||||
const emails = await cachedEmailService.getCachedEmails({
|
const emails = await cachedEmailService.getCachedEmails({
|
||||||
contractId,
|
contractId,
|
||||||
|
stressfreiEmailId,
|
||||||
folder,
|
folder,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
@@ -238,13 +243,14 @@ export async function getFolderCounts(req: AuthRequest, res: Response): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// E-Mail-Anzahl pro Ordner für einen Vertrag
|
// E-Mail-Anzahl pro Ordner für einen Vertrag (optional pro Postfach)
|
||||||
export async function getContractFolderCounts(req: AuthRequest, res: Response): Promise<void> {
|
export async function getContractFolderCounts(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const contractId = parseInt(req.params.contractId);
|
const contractId = parseInt(req.params.contractId);
|
||||||
if (!(await canAccessContract(req, res, contractId))) return;
|
if (!(await canAccessContract(req, res, contractId))) return;
|
||||||
|
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
|
||||||
|
|
||||||
const counts = await cachedEmailService.getFolderCountsForContract(contractId);
|
const counts = await cachedEmailService.getFolderCountsForContract(contractId, stressfreiEmailId);
|
||||||
|
|
||||||
res.json({ success: true, data: counts } as ApiResponse);
|
res.json({ success: true, data: counts } as ApiResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -882,13 +888,21 @@ export async function deleteEmail(req: AuthRequest, res: Response): Promise<void
|
|||||||
|
|
||||||
// ==================== TRASH OPERATIONS ====================
|
// ==================== TRASH OPERATIONS ====================
|
||||||
|
|
||||||
// Papierkorb-E-Mails für einen Kunden abrufen
|
// Papierkorb-E-Mails für einen Kunden abrufen.
|
||||||
|
// Optional `accountId` (Postfach-Filter) und `contractId` (Vertrags-Filter)
|
||||||
|
// – beide aus User-Bug 2026-06-21. Wenn beide leer sind, Verhalten wie
|
||||||
|
// vorher: alle gelöschten E-Mails des Kunden.
|
||||||
export async function getTrashEmails(req: AuthRequest, res: Response): Promise<void> {
|
export async function getTrashEmails(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const customerId = parseInt(req.params.customerId);
|
const customerId = parseInt(req.params.customerId);
|
||||||
if (!(await canAccessCustomer(req, res, customerId))) return;
|
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||||
|
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
|
||||||
|
const contractId = req.query.contractId ? parseInt(req.query.contractId as string) : undefined;
|
||||||
|
|
||||||
const emails = await cachedEmailService.getTrashEmails(customerId);
|
const emails = await cachedEmailService.getTrashEmails(customerId, {
|
||||||
|
stressfreiEmailId,
|
||||||
|
contractId,
|
||||||
|
});
|
||||||
|
|
||||||
res.json({ success: true, data: emails } as ApiResponse);
|
res.json({ success: true, data: emails } as ApiResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -900,13 +914,18 @@ export async function getTrashEmails(req: AuthRequest, res: Response): Promise<v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Papierkorb-Anzahl für einen Kunden
|
// Papierkorb-Anzahl für einen Kunden (gleiche Filter wie getTrashEmails)
|
||||||
export async function getTrashCount(req: AuthRequest, res: Response): Promise<void> {
|
export async function getTrashCount(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const customerId = parseInt(req.params.customerId);
|
const customerId = parseInt(req.params.customerId);
|
||||||
if (!(await canAccessCustomer(req, res, customerId))) return;
|
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||||
|
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
|
||||||
|
const contractId = req.query.contractId ? parseInt(req.query.contractId as string) : undefined;
|
||||||
|
|
||||||
const count = await cachedEmailService.getTrashCount(customerId);
|
const count = await cachedEmailService.getTrashCount(customerId, {
|
||||||
|
stressfreiEmailId,
|
||||||
|
contractId,
|
||||||
|
});
|
||||||
|
|
||||||
res.json({ success: true, data: { count } } as ApiResponse);
|
res.json({ success: true, data: { count } } as ApiResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -603,33 +603,35 @@ export async function getFolderCountsForAccount(stressfreiEmailId: number): Prom
|
|||||||
return { inbox, inboxUnread, sent, sentUnread, trash, trashUnread };
|
return { inbox, inboxUnread, sent, sentUnread, trash, trashUnread };
|
||||||
}
|
}
|
||||||
|
|
||||||
// E-Mail-Anzahl pro Ordner für einen Vertrag (zugeordnete E-Mails)
|
// E-Mail-Anzahl pro Ordner für einen Vertrag (zugeordnete E-Mails).
|
||||||
export async function getFolderCountsForContract(contractId: number): Promise<{
|
// Optional auf ein bestimmtes Postfach einschränken. Bug 2026-06-21:
|
||||||
|
// vorher zählten die Badges Mails aus ALLEN Postfächern, während die
|
||||||
|
// Liste (nach Fix) nur die des ausgewählten Postfachs zeigt – Badge
|
||||||
|
// und Liste liefen auseinander. Trash mit reingenommen, weil der
|
||||||
|
// Contract-Trash-Badge sonst wieder auf account-globalen Zähler
|
||||||
|
// zurückfallen müsste.
|
||||||
|
export async function getFolderCountsForContract(
|
||||||
|
contractId: number,
|
||||||
|
stressfreiEmailId?: number,
|
||||||
|
): Promise<{
|
||||||
inbox: number;
|
inbox: number;
|
||||||
inboxUnread: number;
|
inboxUnread: number;
|
||||||
sent: number;
|
sent: number;
|
||||||
sentUnread: number;
|
sentUnread: number;
|
||||||
|
trash: number;
|
||||||
|
trashUnread: number;
|
||||||
}> {
|
}> {
|
||||||
const [inbox, inboxUnread, sent, sentUnread] = await Promise.all([
|
const baseWhere: Prisma.CachedEmailWhereInput = { contractId };
|
||||||
// INBOX total
|
if (stressfreiEmailId) baseWhere.stressfreiEmailId = stressfreiEmailId;
|
||||||
prisma.cachedEmail.count({
|
const [inbox, inboxUnread, sent, sentUnread, trash, trashUnread] = await Promise.all([
|
||||||
where: { contractId, folder: EmailFolder.INBOX, isDeleted: false },
|
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.INBOX, isDeleted: false } }),
|
||||||
}),
|
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.INBOX, isDeleted: false, isRead: false } }),
|
||||||
// INBOX unread
|
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.SENT, isDeleted: false } }),
|
||||||
prisma.cachedEmail.count({
|
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.SENT, isDeleted: false, isRead: false } }),
|
||||||
where: { contractId, folder: EmailFolder.INBOX, isDeleted: false, isRead: false },
|
prisma.cachedEmail.count({ where: { ...baseWhere, isDeleted: true } }),
|
||||||
}),
|
prisma.cachedEmail.count({ where: { ...baseWhere, isDeleted: true, isRead: false } }),
|
||||||
// SENT total
|
|
||||||
prisma.cachedEmail.count({
|
|
||||||
where: { contractId, folder: EmailFolder.SENT, isDeleted: false },
|
|
||||||
}),
|
|
||||||
// SENT unread
|
|
||||||
prisma.cachedEmail.count({
|
|
||||||
where: { contractId, folder: EmailFolder.SENT, isDeleted: false, isRead: false },
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
return { inbox, inboxUnread, sent, sentUnread, trash, trashUnread };
|
||||||
return { inbox, inboxUnread, sent, sentUnread };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alle StressfreiEmails eines Kunden mit Mailbox
|
// Alle StressfreiEmails eines Kunden mit Mailbox
|
||||||
@@ -904,14 +906,26 @@ export async function permanentDeleteEmail(id: number): Promise<TrashOperationRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Papierkorb-E-Mails für einen Kunden abrufen
|
// Papierkorb-E-Mails für einen Kunden abrufen
|
||||||
export async function getTrashEmails(customerId: number): Promise<CachedEmailWithRelations[]> {
|
// Optional: nach Postfach (stressfreiEmailId) und/oder Vertrag (contractId)
|
||||||
return prisma.cachedEmail.findMany({
|
// einschränken. Vorher zeigte der Papierkorb immer ALLE gelöschten E-Mails
|
||||||
where: {
|
// des Kunden, unabhängig von welchem Postfach man gerade angemeldet ist –
|
||||||
|
// User-Bug 2026-06-21.
|
||||||
|
export async function getTrashEmails(
|
||||||
|
customerId: number,
|
||||||
|
options?: { stressfreiEmailId?: number; contractId?: number },
|
||||||
|
): Promise<CachedEmailWithRelations[]> {
|
||||||
|
const where: Prisma.CachedEmailWhereInput = {
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
stressfreiEmail: {
|
stressfreiEmail: { customerId },
|
||||||
customerId,
|
};
|
||||||
},
|
if (options?.stressfreiEmailId) {
|
||||||
},
|
where.stressfreiEmailId = options.stressfreiEmailId;
|
||||||
|
}
|
||||||
|
if (options?.contractId) {
|
||||||
|
where.contractId = options.contractId;
|
||||||
|
}
|
||||||
|
return prisma.cachedEmail.findMany({
|
||||||
|
where,
|
||||||
include: {
|
include: {
|
||||||
stressfreiEmail: {
|
stressfreiEmail: {
|
||||||
select: {
|
select: {
|
||||||
@@ -931,16 +945,22 @@ export async function getTrashEmails(customerId: number): Promise<CachedEmailWit
|
|||||||
}) as Promise<CachedEmailWithRelations[]>;
|
}) as Promise<CachedEmailWithRelations[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Papierkorb-E-Mails zählen
|
// Papierkorb-E-Mails zählen (gleiche Filter wie getTrashEmails)
|
||||||
export async function getTrashCount(customerId: number): Promise<number> {
|
export async function getTrashCount(
|
||||||
return prisma.cachedEmail.count({
|
customerId: number,
|
||||||
where: {
|
options?: { stressfreiEmailId?: number; contractId?: number },
|
||||||
|
): Promise<number> {
|
||||||
|
const where: Prisma.CachedEmailWhereInput = {
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
stressfreiEmail: {
|
stressfreiEmail: { customerId },
|
||||||
customerId,
|
};
|
||||||
},
|
if (options?.stressfreiEmailId) {
|
||||||
},
|
where.stressfreiEmailId = options.stressfreiEmailId;
|
||||||
});
|
}
|
||||||
|
if (options?.contractId) {
|
||||||
|
where.contractId = options.contractId;
|
||||||
|
}
|
||||||
|
return prisma.cachedEmail.count({ where });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy: E-Mail löschen (jetzt deprecated, nutze moveEmailToTrash)
|
// Legacy: E-Mail löschen (jetzt deprecated, nutze moveEmailToTrash)
|
||||||
|
|||||||
@@ -97,6 +97,34 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🐞 E-Mail-Ansicht: Postfach-Filter griff in Trash/Sent nicht**
|
||||||
|
- Bug-Bericht 2026-06-21: im Vertrags-Tab (Gesendet/Gelöscht) und im
|
||||||
|
Kunden-Haupt-Postfach (Gelöscht) wurden E-Mails aus ALLEN Postfächern
|
||||||
|
des Kunden angezeigt, egal welches Postfach im Selector aktiv war.
|
||||||
|
Im Vertrag fehlte zusätzlich der Vertrags-Filter für den Papierkorb.
|
||||||
|
- Backend:
|
||||||
|
- `getEmailsForContract` controller akzeptiert jetzt `accountId`-
|
||||||
|
Query-Param und reicht ihn als `stressfreiEmailId` an
|
||||||
|
`getCachedEmails` weiter (der hat den Filter eh schon implementiert,
|
||||||
|
nur niemand hat ihn aufgerufen).
|
||||||
|
- `getTrashEmails` (controller + service) akzeptiert `accountId` und
|
||||||
|
`contractId` als optionale Filter. Default-Verhalten unverändert,
|
||||||
|
wenn keiner gesetzt ist.
|
||||||
|
- `getFolderCountsForContract` akzeptiert optional `stressfreiEmailId`,
|
||||||
|
bekommt zusätzlich `trash` + `trashUnread` ins Result – sonst läge
|
||||||
|
der Trash-Badge im Vertrag wieder account-global, während die Liste
|
||||||
|
contract-scoped ist.
|
||||||
|
- Frontend:
|
||||||
|
- `cachedEmailApi.getForContract` / `getTrash` / `getContractFolderCounts`
|
||||||
|
nehmen den Filter entgegen.
|
||||||
|
- `ContractEmailsSection` reicht `selectedAccountId` in alle drei
|
||||||
|
Queries durch und nimmt es in den queryKey mit auf – sonst greift
|
||||||
|
der React-Query-Cache beim Postfach-Wechsel nicht. Der Trash-Badge
|
||||||
|
kommt jetzt aus den contract-scoped Counts, damit Badge und Liste
|
||||||
|
synchron laufen.
|
||||||
|
- `EmailClientTab` reicht `selectedAccountId` in die Trash-Query
|
||||||
|
durch (Inbox/Sent waren schon korrekt).
|
||||||
|
|
||||||
- [x] **🔒 Pentest R89 – Provider-Adressfelder härten**
|
- [x] **🔒 Pentest R89 – Provider-Adressfelder härten**
|
||||||
- R89.1 (MEDIUM): `sanitizeNotes(…, 500)` macht silent `slice(0, 500)`
|
- R89.1 (MEDIUM): `sanitizeNotes(…, 500)` macht silent `slice(0, 500)`
|
||||||
statt 400 – 501+ Zeichen wurden auf 500 abgeschnitten und mit
|
statt 400 – 501+ Zeichen wurden auf 500 abgeschnitten und mit
|
||||||
|
|||||||
@@ -49,28 +49,44 @@ export default function ContractEmailsSection({
|
|||||||
|
|
||||||
const selectedAccount = accounts.find((a) => a.id === selectedAccountId);
|
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({
|
const { data: emailsData, isLoading, refetch: refetchEmails } = useQuery({
|
||||||
queryKey: ['emails', 'contract', contractId, selectedFolder],
|
queryKey: ['emails', 'contract', contractId, selectedAccountId, selectedFolder],
|
||||||
queryFn: () => cachedEmailApi.getForContract(contractId, { folder: selectedFolder as 'INBOX' | 'SENT' }),
|
queryFn: () => cachedEmailApi.getForContract(contractId, {
|
||||||
enabled: selectedFolder !== 'TRASH',
|
folder: selectedFolder as 'INBOX' | 'SENT',
|
||||||
|
accountId: selectedAccountId ?? undefined,
|
||||||
|
}),
|
||||||
|
enabled: selectedFolder !== 'TRASH' && !!selectedAccountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emails = emailsData?.data || [];
|
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({
|
const { data: trashData, isLoading: trashLoading } = useQuery({
|
||||||
queryKey: ['emails', 'trash', customerId],
|
queryKey: ['emails', 'trash', customerId, selectedAccountId, contractId],
|
||||||
queryFn: () => cachedEmailApi.getTrash(customerId),
|
queryFn: () => cachedEmailApi.getTrash(customerId, {
|
||||||
enabled: selectedFolder === 'TRASH' && canAccessTrash,
|
accountId: selectedAccountId ?? undefined,
|
||||||
|
contractId,
|
||||||
|
}),
|
||||||
|
enabled: selectedFolder === 'TRASH' && canAccessTrash && !!selectedAccountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const trashEmails = trashData?.data || [];
|
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({
|
const { data: folderCountsData } = useQuery({
|
||||||
queryKey: ['contract-folder-counts', contractId],
|
queryKey: ['contract-folder-counts', contractId, selectedAccountId],
|
||||||
queryFn: () => cachedEmailApi.getContractFolderCounts(contractId),
|
queryFn: () => cachedEmailApi.getContractFolderCounts(contractId, selectedAccountId ?? undefined),
|
||||||
|
enabled: !!selectedAccountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const folderCounts = folderCountsData?.data || {
|
const folderCounts = folderCountsData?.data || {
|
||||||
@@ -78,16 +94,6 @@ export default function ContractEmailsSection({
|
|||||||
inboxUnread: 0,
|
inboxUnread: 0,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
sentUnread: 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,
|
trash: 0,
|
||||||
trashUnread: 0,
|
trashUnread: 0,
|
||||||
};
|
};
|
||||||
@@ -429,18 +435,18 @@ export default function ContractEmailsSection({
|
|||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
Papierkorb
|
Papierkorb
|
||||||
{accountFolderCounts.trash > 0 && (
|
{folderCounts.trash > 0 && (
|
||||||
<span
|
<span
|
||||||
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
|
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-red-100 text-red-600 font-medium'
|
||||||
: 'bg-gray-100 text-gray-500'
|
: 'bg-gray-100 text-gray-500'
|
||||||
}`}
|
}`}
|
||||||
title={`${accountFolderCounts.trashUnread} ungelesen / ${accountFolderCounts.trash} gesamt`}
|
title={`${folderCounts.trashUnread} ungelesen / ${folderCounts.trash} gesamt`}
|
||||||
>
|
>
|
||||||
{accountFolderCounts.trashUnread > 0
|
{folderCounts.trashUnread > 0
|
||||||
? `${accountFolderCounts.trashUnread}/${accountFolderCounts.trash}`
|
? `${folderCounts.trashUnread}/${folderCounts.trash}`
|
||||||
: accountFolderCounts.trash}
|
: folderCounts.trash}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -124,11 +124,17 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
|||||||
|
|
||||||
const emails = emailsData?.data || [];
|
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({
|
const { data: trashData, isLoading: trashLoading } = useQuery({
|
||||||
queryKey: ['emails', 'trash', customerId],
|
queryKey: ['emails', 'trash', customerId, selectedAccountId],
|
||||||
queryFn: () => cachedEmailApi.getTrash(customerId),
|
queryFn: () => cachedEmailApi.getTrash(customerId, {
|
||||||
enabled: selectedFolder === 'TRASH' && canAccessTrash,
|
accountId: selectedAccountId ?? undefined,
|
||||||
|
}),
|
||||||
|
enabled: selectedFolder === 'TRASH' && canAccessTrash && !!selectedAccountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const trashEmails = trashData?.data || [];
|
const trashEmails = trashData?.data || [];
|
||||||
|
|||||||
@@ -597,19 +597,24 @@ export const cachedEmailApi = {
|
|||||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails`, { params: options });
|
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails`, { params: options });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// E-Mails für Vertrag abrufen
|
// E-Mails für Vertrag abrufen (optional pro Postfach gefiltert)
|
||||||
getForContract: async (contractId: number, options?: { folder?: 'INBOX' | 'SENT'; limit?: number; offset?: number }) => {
|
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 });
|
const res = await api.get<ApiResponse<CachedEmail[]>>(`/contracts/${contractId}/emails`, { params: options });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// Ordner-Anzahlen für Vertrag abrufen (zugeordnete E-Mails)
|
// Ordner-Anzahlen für Vertrag abrufen (zugeordnete E-Mails, optional pro Postfach)
|
||||||
getContractFolderCounts: async (contractId: number) => {
|
getContractFolderCounts: async (contractId: number, accountId?: number) => {
|
||||||
const res = await api.get<ApiResponse<{
|
const res = await api.get<ApiResponse<{
|
||||||
inbox: number;
|
inbox: number;
|
||||||
inboxUnread: number;
|
inboxUnread: number;
|
||||||
sent: number;
|
sent: number;
|
||||||
sentUnread: number;
|
sentUnread: number;
|
||||||
}>>(`/contracts/${contractId}/emails/folder-counts`);
|
trash: number;
|
||||||
|
trashUnread: number;
|
||||||
|
}>>(`/contracts/${contractId}/emails/folder-counts`, { params: accountId ? { accountId } : undefined });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// Mailbox-Konten eines Kunden abrufen
|
// Mailbox-Konten eines Kunden abrufen
|
||||||
@@ -669,14 +674,14 @@ export const cachedEmailApi = {
|
|||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// ==================== PAPIERKORB ====================
|
// ==================== PAPIERKORB ====================
|
||||||
// Papierkorb-E-Mails für Kunden abrufen
|
// Papierkorb-E-Mails für Kunden abrufen (optional pro Postfach/Vertrag gefiltert)
|
||||||
getTrash: async (customerId: number) => {
|
getTrash: async (customerId: number, options?: { accountId?: number; contractId?: number }) => {
|
||||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails/trash`);
|
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails/trash`, { params: options });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// Papierkorb-Anzahl für Kunden
|
// Papierkorb-Anzahl für Kunden (gleiche Filter wie getTrash)
|
||||||
getTrashCount: async (customerId: number) => {
|
getTrashCount: async (customerId: number, options?: { accountId?: number; contractId?: number }) => {
|
||||||
const res = await api.get<ApiResponse<{ count: number }>>(`/customers/${customerId}/emails/trash/count`);
|
const res = await api.get<ApiResponse<{ count: number }>>(`/customers/${customerId}/emails/trash/count`, { params: options });
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
// E-Mail aus Papierkorb wiederherstellen
|
// E-Mail aus Papierkorb wiederherstellen
|
||||||
|
|||||||
Reference in New Issue
Block a user