import { db } from '@/lib/db' import { visibleCaseWhere } from '@/lib/access/caseFilter' import { visibleMessageWhere } from '@/lib/access/chatFilter' import { listWatched } from '@/lib/cases/watch' import { listRecent } from '@/lib/recent/service' import type { EffectivePermissions } from '@/lib/auth/permissions' import type { Prisma } from '@afis/db' /** * Was auf der Übersicht steht. * * Fünf Fragen, die man morgens hat: woran war ich dran, woran arbeite ich, was * ist fällig, wo hat jemand geschrieben, was hat sich getan. Jede läuft durch * `visibleCaseWhere` — die Übersicht ist der Ort, an dem eine unachtsame * Abfrage am ehesten eine Akte verrät, die man nicht sehen darf. */ const AKTE = { id: true, caseNumber: true, title: true, status: true, classification: true, updatedAt: true, dueAt: true, } satisfies Prisma.CaseSelect /** Fristen innerhalb dieser Anzahl Tage gelten als „steht an". */ const FRIST_TAGE = 14 export async function loadDashboard(p: EffectivePermissions) { const sichtbar = visibleCaseWhere(p) const jetzt = new Date() const frist = new Date(jetzt.getTime() + FRIST_TAGE * 86_400_000) const [meine, fristen, zuletzt, beobachtet, ungelesen, geoeffnet] = await Promise.all([ db.case.findMany({ where: { AND: [ sichtbar, { participants: { some: { userId: p.userId } } }, { status: { in: ['DRAFT', 'OPEN', 'REVIEW'] } }, ], }, orderBy: { updatedAt: 'desc' }, take: 10, select: AKTE, }), db.case.findMany({ where: { AND: [ sichtbar, { dueAt: { not: null, lte: frist } }, { status: { notIn: ['CLOSED', 'ARCHIVED', 'SEALED'] } }, ], }, orderBy: { dueAt: 'asc' }, take: 10, select: AKTE, }), db.case.findMany({ where: sichtbar, orderBy: { updatedAt: 'desc' }, take: 8, select: AKTE, }), listWatched(p, 10), ungeleseneChats(p), listRecent(p, 8), ]) return { meine, fristen, zuletzt, beobachtet, ungelesen, geoeffnet } } export type UnreadChat = { scopeType: 'case' | 'channel' scopeId: string label: string href: string count: number } /** * Wo seit dem letzten Besuch geschrieben wurde. * * Gezählt wird über `ReadMarker`. Nur an Orten, an denen der Nutzer beteiligt * ist, die er beobachtet, oder in Kanälen, die er sehen darf — eine Zählung * über alle sichtbaren Akten wäre auf der Übersicht nur Lärm. Eigene * Nachrichten zählen nicht mit. */ async function ungeleseneChats(p: EffectivePermissions): Promise { const marken = await db.readMarker.findMany({ where: { userId: p.userId }, select: { scopeType: true, scopeId: true, lastReadAt: true }, }) const markeVon = new Map(marken.map((m) => [`${m.scopeType}:${m.scopeId}`, m.lastReadAt])) const [meineAkten, kanaele] = await Promise.all([ db.case.findMany({ where: { AND: [ visibleCaseWhere(p), { OR: [ { participants: { some: { userId: p.userId } } }, { watches: { some: { userId: p.userId } } }, ], }, ], }, select: { id: true, caseNumber: true }, take: 50, }), db.channel.findMany({ where: { isArchived: false, ...(p.isAdmin ? {} : { minClearance: { lte: p.clearanceLevel } }), }, select: { id: true, key: true }, take: 50, }), ]) const orte = [ ...meineAkten.map((akte) => ({ scopeType: 'case' as const, scopeId: akte.id, label: akte.caseNumber, href: `/faelle/${akte.caseNumber}`, wo: { caseId: akte.id } as Prisma.ChatMessageWhereInput, })), ...kanaele.map((kanal) => ({ scopeType: 'channel' as const, scopeId: kanal.id, label: `#${kanal.key}`, href: `/kanaele/${kanal.key}`, wo: { channelId: kanal.id } as Prisma.ChatMessageWhereInput, })), ] if (orte.length === 0) return [] const zaehler = await Promise.all( orte.map(async (ort) => { const seit = markeVon.get(`${ort.scopeType}:${ort.scopeId}`) const count = await db.chatMessage.count({ where: { AND: [ visibleMessageWhere(p), ort.wo, { withdrawnAt: null }, { authorId: { not: p.userId } }, ...(seit ? [{ createdAt: { gt: seit } }] : []), ], }, }) return { ort, count } }), ) return zaehler .filter((eintrag) => eintrag.count > 0) .sort((a, b) => b.count - a.count) .slice(0, 10) .map(({ ort, count }) => ({ scopeType: ort.scopeType, scopeId: ort.scopeId, label: ort.label, href: ort.href, count, })) }