169 lines
8.4 KiB
TypeScript
169 lines
8.4 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { prisma } from '@/lib/db'
|
||
import { getSession } from '@/lib/auth'
|
||
import { getProjectWithTenantCheck } from '@/lib/tenant'
|
||
import { sendEmail } from '@/lib/email'
|
||
|
||
export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
|
||
try {
|
||
const user = await getSession()
|
||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||
|
||
const project = await getProjectWithTenantCheck(params.id, user)
|
||
if (!project) return NextResponse.json({ error: 'Projekt nicht gefunden' }, { status: 404 })
|
||
|
||
// Load tenant logo
|
||
let tenantLogoUrl = ''
|
||
let tenantName = ''
|
||
if ((project as any).tenantId) {
|
||
const tenant = await (prisma as any).tenant.findUnique({
|
||
where: { id: (project as any).tenantId },
|
||
select: { logoUrl: true, name: true },
|
||
})
|
||
if (tenant?.logoUrl) tenantLogoUrl = tenant.logoUrl
|
||
if (tenant?.name) tenantName = tenant.name
|
||
}
|
||
|
||
const body = await req.json()
|
||
const { recipientEmail } = body
|
||
if (!recipientEmail) {
|
||
return NextResponse.json({ error: 'Empfänger-E-Mail erforderlich' }, { status: 400 })
|
||
}
|
||
|
||
// Load journal data
|
||
const entries = await (prisma as any).journalEntry.findMany({
|
||
where: { projectId: params.id },
|
||
orderBy: [{ time: 'asc' }, { sortOrder: 'asc' }],
|
||
})
|
||
|
||
const checkItems = await (prisma as any).journalCheckItem.findMany({
|
||
where: { projectId: params.id },
|
||
orderBy: { sortOrder: 'asc' },
|
||
})
|
||
|
||
const pendenzen = await (prisma as any).journalPendenz.findMany({
|
||
where: { projectId: params.id },
|
||
orderBy: { sortOrder: 'asc' },
|
||
})
|
||
|
||
// Build HTML report
|
||
const formatTime = (d: Date) => new Date(d).toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' })
|
||
const formatDate = (d: Date) => new Date(d).toLocaleDateString('de-CH', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||
|
||
const p = project as any
|
||
|
||
let entriesHtml = ''
|
||
for (const e of entries) {
|
||
const correctedStyle = e.isCorrected ? 'text-decoration:line-through;opacity:0.5;' : ''
|
||
const correctionStyle = e.correctionOfId ? 'color:#b45309;font-style:italic;' : ''
|
||
const doneIcon = e.done ? '✅' : ''
|
||
const doneAtStr = e.done && e.doneAt ? ` <span style="color:#16a34a;font-size:10px;">(erledigt ${formatTime(e.doneAt)})</span>` : ''
|
||
entriesHtml += `
|
||
<tr style="${correctedStyle}${correctionStyle}">
|
||
<td style="padding:4px 8px;border-bottom:1px solid #e5e7eb;font-family:monospace;font-size:12px;white-space:nowrap;">${formatTime(e.time)}</td>
|
||
<td style="padding:4px 8px;border-bottom:1px solid #e5e7eb;font-size:13px;">${e.what}${e.isCorrected ? ' <span style="color:#ef4444;font-size:11px;">(korrigiert)</span>' : ''}${doneAtStr}</td>
|
||
<td style="padding:4px 8px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#666;">${e.who || '–'}</td>
|
||
<td style="padding:4px 8px;border-bottom:1px solid #e5e7eb;text-align:center;">${doneIcon}</td>
|
||
</tr>`
|
||
}
|
||
|
||
let checkHtml = ''
|
||
for (const c of checkItems) {
|
||
const confirmedTime = c.confirmed && c.confirmedAt ? ` <span style="font-size:10px;color:#666;">${formatTime(c.confirmedAt)}</span>` : ''
|
||
checkHtml += `
|
||
<tr>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;font-size:13px;">${c.label}${confirmedTime}</td>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;text-align:center;">${c.confirmed ? '✅' : '–'}</td>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;text-align:center;">${c.ok ? '✅' : '–'}</td>
|
||
</tr>`
|
||
}
|
||
|
||
let pendHtml = ''
|
||
for (const p of pendenzen) {
|
||
const pendDoneAt = p.done && p.doneAt ? ` <span style="color:#16a34a;font-size:10px;">(${formatTime(p.doneAt)})</span>` : ''
|
||
pendHtml += `
|
||
<tr>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;font-size:13px;">${p.what}${pendDoneAt}</td>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#666;">${p.who || '–'}</td>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#666;">${p.whenHow || '–'}</td>
|
||
<td style="padding:3px 8px;border-bottom:1px solid #e5e7eb;text-align:center;">${p.done ? '✅' : '–'}</td>
|
||
</tr>`
|
||
}
|
||
|
||
const logoHtml = tenantLogoUrl
|
||
? `<img src="${tenantLogoUrl}" alt="${tenantName}" style="height:40px;max-width:120px;object-fit:contain;margin-right:16px;border-radius:4px;" />`
|
||
: ''
|
||
|
||
const html = `
|
||
<div style="font-family:sans-serif;max-width:800px;margin:0 auto;">
|
||
<div style="background:#dc2626;color:white;padding:20px 24px;border-radius:12px 12px 0 0;display:flex;align-items:center;">
|
||
${logoHtml}
|
||
<div>
|
||
<h1 style="margin:0;font-size:22px;">Einsatzrapport</h1>
|
||
<p style="margin:4px 0 0;opacity:0.9;">${p.title || 'Ohne Titel'}${tenantName ? ` — ${tenantName}` : ''}</p>
|
||
</div>
|
||
</div>
|
||
<div style="border:1px solid #e5e7eb;border-top:none;padding:24px;border-radius:0 0 12px 12px;">
|
||
<div style="display:flex;gap:24px;margin-bottom:20px;flex-wrap:wrap;">
|
||
<div><strong>Standort:</strong> ${p.location || '–'}</div>
|
||
<div><strong>Datum:</strong> ${p.createdAt ? formatDate(p.createdAt) : '–'}</div>
|
||
</div>
|
||
|
||
<h2 style="font-size:16px;border-bottom:2px solid #dc2626;padding-bottom:4px;margin:20px 0 8px;">Journal-Einträge</h2>
|
||
<table style="width:100%;border-collapse:collapse;">
|
||
<thead>
|
||
<tr style="background:#f5f5f4;">
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;border-bottom:2px solid #dc2626;">Zeit</th>
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;border-bottom:2px solid #dc2626;">Was</th>
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;border-bottom:2px solid #dc2626;">Wer</th>
|
||
<th style="padding:6px 8px;text-align:center;font-size:11px;color:#666;border-bottom:2px solid #dc2626;">Ok</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${entriesHtml || '<tr><td colspan="4" style="padding:12px;text-align:center;color:#999;">Keine Einträge</td></tr>'}</tbody>
|
||
</table>
|
||
|
||
${checkItems.length > 0 ? `
|
||
<h2 style="font-size:16px;border-bottom:2px solid #dc2626;padding-bottom:4px;margin:20px 0 8px;">SOMA Checkliste</h2>
|
||
<table style="width:100%;border-collapse:collapse;">
|
||
<thead>
|
||
<tr style="background:#f5f5f4;">
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;">Punkt</th>
|
||
<th style="padding:6px 8px;text-align:center;font-size:11px;color:#666;">Bestätigt</th>
|
||
<th style="padding:6px 8px;text-align:center;font-size:11px;color:#666;">Ok</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${checkHtml}</tbody>
|
||
</table>` : ''}
|
||
|
||
${pendenzen.length > 0 ? `
|
||
<h2 style="font-size:16px;border-bottom:2px solid #dc2626;padding-bottom:4px;margin:20px 0 8px;">Pendenzen</h2>
|
||
<table style="width:100%;border-collapse:collapse;">
|
||
<thead>
|
||
<tr style="background:#f5f5f4;">
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;">Was</th>
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;">Wer</th>
|
||
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#666;">Wann/Wie</th>
|
||
<th style="padding:6px 8px;text-align:center;font-size:11px;color:#666;">Erledigt</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${pendHtml}</tbody>
|
||
</table>` : ''}
|
||
|
||
<hr style="margin:20px 0;border:none;border-top:1px solid #e5e7eb;" />
|
||
<p style="color:#999;font-size:11px;">Gesendet von Lageplan am ${new Date().toLocaleString('de-CH')} durch ${user.name || user.email}</p>
|
||
</div>
|
||
</div>`
|
||
|
||
await sendEmail(
|
||
recipientEmail,
|
||
`Einsatzrapport — ${p.title || 'Ohne Titel'}`,
|
||
html
|
||
)
|
||
|
||
return NextResponse.json({ success: true, message: `Rapport an ${recipientEmail} gesendet` })
|
||
} catch (error) {
|
||
console.error('Error sending report:', error)
|
||
return NextResponse.json({ error: 'Fehler beim Senden des Rapports' }, { status: 500 })
|
||
}
|
||
}
|