Files
breakpilot-core/admin-core/components/common/PagePurpose.tsx
Benjamin Boenisch 97373580a8 Add admin-core frontend (Port 3008)
Next.js admin frontend for Core with 3 categories
(Communication, Infrastructure, Development), 13 modules,
2 roles (developer, ops), and 11 API proxy routes.
Includes docker-compose service and nginx SSL config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:44:37 +01:00

170 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import Link from 'next/link'
export interface PagePurposeProps {
title: string
purpose: string
audience: string[]
gdprArticles?: string[]
architecture?: {
services: string[]
databases: string[]
diagram?: string
}
relatedPages?: Array<{
name: string
href: string
description: string
}>
collapsible?: boolean
defaultCollapsed?: boolean
}
export function PagePurpose({
title,
purpose,
audience,
gdprArticles,
architecture,
relatedPages,
collapsible = true,
defaultCollapsed = false,
}: PagePurposeProps) {
const [collapsed, setCollapsed] = useState(defaultCollapsed)
const [showArchitecture, setShowArchitecture] = useState(false)
return (
<div className="bg-gradient-to-r from-slate-50 to-slate-100 rounded-xl border border-slate-200 mb-6 overflow-hidden">
{/* Header */}
<div
className={`flex items-center justify-between px-4 py-3 ${
collapsible ? 'cursor-pointer hover:bg-slate-100' : ''
}`}
onClick={collapsible ? () => setCollapsed(!collapsed) : undefined}
>
<div className="flex items-center gap-2">
<span className="text-lg">🎯</span>
<span className="font-semibold text-slate-700">Warum gibt es diese Seite?</span>
</div>
{collapsible && (
<svg
className={`w-5 h-5 text-slate-400 transition-transform ${collapsed ? '' : 'rotate-180'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</div>
{/* Content */}
{!collapsed && (
<div className="px-4 pb-4 space-y-4">
{/* Purpose */}
<p className="text-slate-600">{purpose}</p>
{/* Metadata */}
<div className="flex flex-wrap gap-4 text-sm">
{/* Audience */}
<div className="flex items-center gap-2">
<span className="text-slate-400">👥</span>
<span className="text-slate-500">Zielgruppe:</span>
<span className="text-slate-700">{audience.join(', ')}</span>
</div>
{/* GDPR Articles */}
{gdprArticles && gdprArticles.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-slate-400">📋</span>
<span className="text-slate-500">DSGVO-Bezug:</span>
<span className="text-slate-700">{gdprArticles.join(', ')}</span>
</div>
)}
</div>
{/* Architecture (expandable) */}
{architecture && (
<div className="border-t border-slate-200 pt-3">
<button
onClick={(e) => {
e.stopPropagation()
setShowArchitecture(!showArchitecture)
}}
className="flex items-center gap-2 text-sm text-slate-500 hover:text-slate-700"
>
<span>🏗</span>
<span>Architektur</span>
<svg
className={`w-4 h-4 transition-transform ${showArchitecture ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{showArchitecture && (
<div className="mt-2 p-3 bg-white rounded-lg border border-slate-200 text-sm">
<div className="grid grid-cols-2 gap-4">
<div>
<span className="font-medium text-slate-600">Services:</span>
<ul className="mt-1 space-y-1 text-slate-500">
{architecture.services.map((service) => (
<li key={service} className="flex items-center gap-1">
<span className="w-1.5 h-1.5 bg-primary-400 rounded-full" />
{service}
</li>
))}
</ul>
</div>
<div>
<span className="font-medium text-slate-600">Datenbanken:</span>
<ul className="mt-1 space-y-1 text-slate-500">
{architecture.databases.map((db) => (
<li key={db} className="flex items-center gap-1">
<span className="w-1.5 h-1.5 bg-green-400 rounded-full" />
{db}
</li>
))}
</ul>
</div>
</div>
</div>
)}
</div>
)}
{/* Related Pages */}
{relatedPages && relatedPages.length > 0 && (
<div className="border-t border-slate-200 pt-3">
<span className="text-sm text-slate-500 flex items-center gap-2 mb-2">
<span>🔗</span>
<span>Verwandte Seiten</span>
</span>
<div className="flex flex-wrap gap-2">
{relatedPages.map((page) => (
<Link
key={page.href}
href={page.href}
className="inline-flex items-center gap-1 px-3 py-1.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-600 hover:border-primary-300 hover:text-primary-600 transition-colors"
title={page.description}
>
<span>{page.name}</span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
))}
</div>
</div>
)}
</div>
)}
</div>
)
}