All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
160 lines
5.9 KiB
TypeScript
160 lines
5.9 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
AlertTriangle,
|
|
Briefcase,
|
|
Calendar,
|
|
Contact,
|
|
Database,
|
|
ClipboardCheck,
|
|
Cpu,
|
|
Gavel,
|
|
Globe,
|
|
Layers,
|
|
Scale,
|
|
OctagonAlert,
|
|
RotateCcw,
|
|
ShieldCheck,
|
|
Target,
|
|
Timer,
|
|
UserCheck,
|
|
Users,
|
|
type LucideIcon,
|
|
} from "lucide-react"
|
|
|
|
import { Badge } from "@/components/ui/badge"
|
|
import {
|
|
Dialog,
|
|
DialogBody,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogDescription,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { MarkdownRenderer } from "@/components/markdown-renderer"
|
|
import { PrintPolicy } from "@/components/print-policy"
|
|
import { PrivacyPolicy } from "@/types/privacy-policy"
|
|
import { formatDate } from "@/lib/format"
|
|
|
|
interface PolicyDetailProps {
|
|
policy: PrivacyPolicy | null
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
}
|
|
|
|
interface Section {
|
|
icon: LucideIcon
|
|
label: string
|
|
content?: string | null
|
|
}
|
|
|
|
function SectionBlock({ icon: Icon, label, content }: Section) {
|
|
if (!content) return null
|
|
|
|
return (
|
|
<section className="space-y-2">
|
|
<h3 className="flex items-center gap-2 text-sm font-semibold">
|
|
<Icon className="size-4 text-brand" />
|
|
{label}
|
|
</h3>
|
|
<div className="rounded-lg border bg-muted/30 p-4">
|
|
<MarkdownRenderer content={content} />
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export function PolicyDetail({ policy, open, onOpenChange }: PolicyDetailProps) {
|
|
const responsibleSections: Section[] = policy
|
|
? [
|
|
{ icon: Contact, label: "Verantwortlicher im Sinne der DSGVO", content: policy.responsible },
|
|
{ icon: ShieldCheck, label: "Kontaktdaten der/des Datenschutzbeauftragten", content: policy.contactDPO },
|
|
]
|
|
: []
|
|
|
|
const processingSections: Section[] = policy
|
|
? [
|
|
{ icon: Briefcase, label: "Bezeichnung der Verarbeitungstätigkeit", content: policy.processingDescription },
|
|
{ icon: Target, label: "Zweck der Datenerhebung", content: policy.processingPurposes },
|
|
{ icon: Gavel, label: "Rechtsgrundlage der Verarbeitung", content: policy.legalBasis },
|
|
{ icon: Scale, label: "Berechtigte Interessen", content: policy.legitimateInterests },
|
|
{ icon: Layers, label: "Kategorien der verarbeiteten Daten", content: policy.dataCategories },
|
|
{ icon: Users, label: "Geplante Empfänger der Daten", content: policy.recipients },
|
|
{ icon: Globe, label: "Übermittlung in Drittländer", content: policy.thirdCountryTransfer },
|
|
{ icon: Timer, label: "Speicherdauer", content: policy.storageDuration },
|
|
{ icon: Database, label: "Herkunft der Daten", content: policy.dataSource },
|
|
{ icon: Cpu, label: "Automatisierte Entscheidungsfindung", content: policy.automatedDecision },
|
|
]
|
|
: []
|
|
|
|
const rightsSections: Section[] = policy
|
|
? [
|
|
{ icon: UserCheck, label: "Betroffenen-Rechte", content: policy.dataSubjectsRights },
|
|
{ icon: AlertTriangle, label: "Beschwerderecht bei der Aufsichtsbehörde", content: policy.complaintRight },
|
|
{ icon: RotateCcw, label: "Widerrufsrecht bei Einwilligung", content: policy.withdrawalRight },
|
|
{ icon: OctagonAlert, label: "Widerspruchsrecht (Art. 21 DSGVO)", content: policy.objectionRight },
|
|
{ icon: ClipboardCheck, label: "Ihre Pflicht zur Bereitstellung der Daten", content: policy.provisionObligation },
|
|
{ icon: OctagonAlert, label: "Folgen, wenn Sie die Daten nicht angeben", content: policy.provisionConsequences },
|
|
]
|
|
: []
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-h-[calc(100dvh-2rem)] sm:max-w-3xl">
|
|
{policy && (
|
|
<>
|
|
<DialogHeader>
|
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
<div className="min-w-0 space-y-2">
|
|
<DialogTitle>{policy.title}</DialogTitle>
|
|
<DialogDescription>Vorschau der Datenschutzerklärung</DialogDescription>
|
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
|
<span className="flex items-center gap-1.5">
|
|
<Calendar className="size-4" />
|
|
Erstellt am {formatDate(policy.createdAt)}
|
|
</span>
|
|
{policy.updatedAt !== policy.createdAt && (
|
|
<span>Aktualisiert am {formatDate(policy.updatedAt)}</span>
|
|
)}
|
|
<Badge variant="brand">DSGVO</Badge>
|
|
</div>
|
|
</div>
|
|
<PrintPolicy policy={policy} size="sm" className="mr-8" />
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
<Tabs defaultValue="responsible" className="min-h-0 flex-1">
|
|
<TabsList className="w-full">
|
|
<TabsTrigger value="responsible">Verantwortlich</TabsTrigger>
|
|
<TabsTrigger value="processing">Verarbeitung</TabsTrigger>
|
|
<TabsTrigger value="rights">Ihre Rechte</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<DialogBody>
|
|
<TabsContent value="responsible" className="space-y-5 py-1">
|
|
{responsibleSections.map((section) => (
|
|
<SectionBlock key={section.label} {...section} />
|
|
))}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="processing" className="space-y-5 py-1">
|
|
{processingSections.map((section) => (
|
|
<SectionBlock key={section.label} {...section} />
|
|
))}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="rights" className="space-y-5 py-1">
|
|
{rightsSections.map((section) => (
|
|
<SectionBlock key={section.label} {...section} />
|
|
))}
|
|
</TabsContent>
|
|
</DialogBody>
|
|
</Tabs>
|
|
</>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|