push
This commit is contained in:
@@ -1,125 +1,108 @@
|
||||
import { prisma } from '@innungsapp/shared'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { prisma } from '@innungsapp/shared'
|
||||
import { headers } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { StatsCards } from '@/components/stats/StatsCards'
|
||||
import Link from 'next/link'
|
||||
import { format } from 'date-fns'
|
||||
import { de } from 'date-fns/locale'
|
||||
import { NEWS_KATEGORIE_LABELS, TERMIN_TYP_LABELS } from '@innungsapp/shared'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await auth.api.getSession({ headers: await headers() })
|
||||
if (!session?.user) redirect('/login')
|
||||
export default async function GlobalDashboardRedirect() {
|
||||
const headerList = await headers()
|
||||
const host = headerList.get('host') || ''
|
||||
const session = await auth.api.getSession({ headers: headerList })
|
||||
|
||||
const userRole = await prisma.userRole.findFirst({
|
||||
where: { userId: session.user.id },
|
||||
include: { org: true },
|
||||
})
|
||||
if (!userRole) redirect('/login')
|
||||
if (!session?.user) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
const orgId = userRole.orgId
|
||||
const now = new Date()
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
// Superadmin logic
|
||||
const superAdminEmail = process.env.SUPERADMIN_EMAIL || 'superadmin@innungsapp.de'
|
||||
const isSuperAdmin = session.user.email === superAdminEmail || session.user.role === 'admin'
|
||||
|
||||
const [activeMembers, newsThisWeek, upcomingTermine, activeStellen, recentNews, nextTermine] =
|
||||
await Promise.all([
|
||||
prisma.member.count({ where: { orgId, status: 'aktiv' } }),
|
||||
prisma.news.count({ where: { orgId, publishedAt: { gte: weekAgo, not: null } } }),
|
||||
prisma.termin.count({ where: { orgId, datum: { gte: now } } }),
|
||||
prisma.stelle.count({ where: { orgId, aktiv: true } }),
|
||||
prisma.news.findMany({
|
||||
where: { orgId, publishedAt: { not: null } },
|
||||
orderBy: { publishedAt: 'desc' },
|
||||
take: 5,
|
||||
include: { author: { select: { name: true } } },
|
||||
}),
|
||||
prisma.termin.findMany({
|
||||
where: { orgId, datum: { gte: now } },
|
||||
orderBy: { datum: 'asc' },
|
||||
take: 3,
|
||||
}),
|
||||
])
|
||||
if (isSuperAdmin) {
|
||||
redirect('/superadmin')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Übersicht</h1>
|
||||
<p className="text-gray-500 mt-1">{userRole.org.name}</p>
|
||||
</div>
|
||||
const userRoles = await prisma.userRole.findMany({
|
||||
where: { userId: session.user.id, role: 'admin' },
|
||||
include: {
|
||||
org: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
})
|
||||
|
||||
<StatsCards
|
||||
stats={[
|
||||
{ label: 'Aktive Mitglieder', value: activeMembers, icon: '👥' },
|
||||
{ label: 'News diese Woche', value: newsThisWeek, icon: '📰' },
|
||||
{ label: 'Bevorstehende Termine', value: upcomingTermine, icon: '📅' },
|
||||
{ label: 'Aktive Stellen', value: activeStellen, icon: '🎓' },
|
||||
]}
|
||||
/>
|
||||
if (userRoles.length === 1) {
|
||||
const slug = userRoles[0].org.slug
|
||||
const protocol = host.includes('localhost') ? 'http' : 'https'
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Recent News */}
|
||||
<div className="bg-white rounded-lg border p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="font-semibold text-gray-900">Neueste Beiträge</h2>
|
||||
<Link href="/dashboard/news" className="text-sm text-brand-600 hover:underline">
|
||||
Alle anzeigen
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{recentNews.map((n) => (
|
||||
<div key={n.id} className="flex items-start gap-3 py-2 border-b last:border-0">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium text-sm text-gray-900 truncate">{n.title}</p>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{n.publishedAt
|
||||
? format(n.publishedAt, 'dd. MMM yyyy', { locale: de })
|
||||
: 'Entwurf'}{' '}
|
||||
· {n.author?.name ?? 'Unbekannt'}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full whitespace-nowrap">
|
||||
{NEWS_KATEGORIE_LABELS[n.kategorie]}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
// Construct the subdomain URL
|
||||
let newHost = host
|
||||
if (host.includes('localhost')) {
|
||||
const port = host.includes(':') ? `:${host.split(':')[1]}` : ''
|
||||
newHost = `${slug}.localhost${port}`
|
||||
} else {
|
||||
// Assumes domain.tld
|
||||
const parts = host.split('.')
|
||||
if (parts.length === 2) {
|
||||
newHost = `${slug}.${host}`
|
||||
} else if (parts.length > 2) {
|
||||
newHost = `${slug}.${parts.slice(-2).join('.')}`
|
||||
}
|
||||
}
|
||||
|
||||
redirect(`${protocol}://${newHost}/dashboard`)
|
||||
}
|
||||
|
||||
const getOrgUrl = (slug: string, currentHost: string) => {
|
||||
const protocol = currentHost.includes('localhost') ? 'http' : 'https'
|
||||
let newHost = currentHost
|
||||
if (currentHost.includes('localhost')) {
|
||||
const port = currentHost.includes(':') ? `:${currentHost.split(':')[1]}` : ''
|
||||
newHost = `${slug}.localhost${port}`
|
||||
} else {
|
||||
const parts = currentHost.split('.')
|
||||
newHost = parts.length >= 2 ? `${slug}.${parts.slice(-2).join('.')}` : `${slug}.${currentHost}`
|
||||
}
|
||||
return `${protocol}://${newHost}/dashboard`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-4">
|
||||
<div className="bg-white border rounded-xl p-8 max-w-md w-full text-center shadow-sm">
|
||||
<h1 className="text-xl font-bold text-gray-900 mb-2">
|
||||
{userRoles.length > 1 ? 'Bitte Innung auswählen' : 'Kein Mandant zugeordnet'}
|
||||
</h1>
|
||||
|
||||
{userRoles.length > 1 ? (
|
||||
<div className="space-y-2 mb-6">
|
||||
{userRoles.map((userRole) => (
|
||||
<Link
|
||||
key={userRole.org.id}
|
||||
href={getOrgUrl(userRole.org.slug, host)}
|
||||
className="block w-full rounded-lg border border-gray-200 px-4 py-3 text-sm text-gray-700 hover:border-brand-500 hover:text-brand-700 transition-colors"
|
||||
>
|
||||
{userRole.org.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 mb-6 text-sm">
|
||||
Ihr Konto hat aktuell keinen Admin-Zugriff auf eine Innung.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<form action={async () => {
|
||||
'use server'
|
||||
const { auth } = await import('@/lib/auth')
|
||||
const { headers } = await import('next/headers')
|
||||
await auth.api.signOut({ headers: await headers() })
|
||||
redirect('/login')
|
||||
}}>
|
||||
<button type="submit" className="text-sm font-medium text-brand-600 hover:text-brand-700">
|
||||
Abmelden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upcoming Termine */}
|
||||
<div className="bg-white rounded-lg border p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="font-semibold text-gray-900">Nächste Termine</h2>
|
||||
<Link href="/dashboard/termine" className="text-sm text-brand-600 hover:underline">
|
||||
Alle anzeigen
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{nextTermine.length === 0 && (
|
||||
<p className="text-sm text-gray-500">Keine bevorstehenden Termine</p>
|
||||
)}
|
||||
{nextTermine.map((t) => (
|
||||
<div key={t.id} className="flex items-start gap-3 py-2 border-b last:border-0">
|
||||
<div className="text-center min-w-[40px]">
|
||||
<p className="text-lg font-bold text-brand-500 leading-none">
|
||||
{format(t.datum, 'dd', { locale: de })}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 uppercase">
|
||||
{format(t.datum, 'MMM', { locale: de })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium text-sm text-gray-900 truncate">{t.titel}</p>
|
||||
<p className="text-xs text-gray-500">{t.ort ?? 'Kein Ort angegeben'}</p>
|
||||
</div>
|
||||
<span className="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full whitespace-nowrap">
|
||||
{TERMIN_TYP_LABELS[t.typ]}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user