feat: add new legal document and database schema for module L compliance

Co-authored-by: cetinmustafa83 <30570791+cetinmustafa83@users.noreply.github.com>
This commit is contained in:
v0andcetinmustafa83 committed 2026-07-31 13:34:58 +00:00
1 parent e6d82083d7
commit 927aae8580
24 files changed
+6565 -1

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
"use client";
import React, { useEffect, useState } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { CheckCircle, AlertCircle, FileText, Users, Clock } from "lucide-react";
interface ComplianceData {
status: any;
policiesConfigured: number;
policiesReviewed: number;
dpiaRecords: any[];
consultations: any[];
}
export default function ComplianceDashboard() {
const [data, setData] = useState<ComplianceData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("/api/v1/compliance/status");
if (!response.ok) throw new Error("Failed to fetch compliance status");
const result = await response.json();
setData(result);
} catch (error) {
console.error("[v0] Error fetching compliance data:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<div className="p-8 text-center">
<p className="text-slate-600">Loading compliance status...</p>
</div>
);
}
if (!data) {
return (
<div className="p-8">
<Card className="border-red-200 bg-red-50">
<CardContent className="pt-6">
<p className="text-red-600">Failed to load compliance dashboard</p>
</CardContent>
</Card>
</div>
);
}
return (
<div className="space-y-8 p-8">
<div>
<h1 className="text-3xl font-bold text-slate-900">Compliance Monitoring Dashboard</h1>
<p className="text-slate-600 mt-2">
Track your school&apos;s data protection compliance status and document management
</p>
</div>
{/* Status Overview */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="pt-6">
<div className="flex items-center gap-3">
{data.status?.onboardingCompleted ? (
<CheckCircle className="w-8 h-8 text-emerald-600" />
) : (
<AlertCircle className="w-8 h-8 text-yellow-600" />
)}
<div>
<p className="text-sm text-slate-600">Onboarding Status</p>
<p className="font-semibold text-slate-900">
{data.status?.onboardingCompleted ? "Complete" : "Pending"}
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center gap-3">
<FileText className="w-8 h-8 text-blue-600" />
<div>
<p className="text-sm text-slate-600">Data Retention Policies</p>
<p className="font-semibold text-slate-900">
{data.policiesConfigured} / {data.policiesReviewed} reviewed
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center gap-3">
<FileText className="w-8 h-8 text-purple-600" />
<div>
<p className="text-sm text-slate-600">DPIA Records</p>
<p className="font-semibold text-slate-900">{data.dpiaRecords.length}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center gap-3">
<Users className="w-8 h-8 text-orange-600" />
<div>
<p className="text-sm text-slate-600">Consultations</p>
<p className="font-semibold text-slate-900">{data.consultations.length}</p>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Data Retention Policies */}
<Card>
<CardHeader>
<CardTitle>Active Data Retention Policies</CardTitle>
<CardDescription>
Configured retention periods for each data category
</CardDescription>
</CardHeader>
<CardContent>
<div className="text-sm text-slate-600">
<p>{data.policiesConfigured} policies configured</p>
<p className="mt-2">Policies are centrally defined and reviewed by your DPO.</p>
</div>
</CardContent>
</Card>
{/* DPIA Records */}
{data.dpiaRecords.length > 0 && (
<Card>
<CardHeader>
<CardTitle>DPIA Records</CardTitle>
<CardDescription>
Data Protection Impact Assessments for high-risk modules
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{data.dpiaRecords.map((dpia: any) => (
<div
key={dpia.id}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"
>
<span className="text-sm font-medium text-slate-900">
{dpia.moduleScope}
</span>
<span className="text-xs text-slate-500">
{dpia.approvedAt ? "✓ Approved" : "Pending review"}
</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Consultation Log */}
{data.consultations.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Consultation Log</CardTitle>
<CardDescription>
Staff and parent body consultations per module
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{data.consultations.slice(0, 5).map((consult: any) => (
<div
key={consult.id}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"
>
<div>
<p className="text-sm font-medium text-slate-900">
{consult.consultationType}
</p>
{consult.moduleScope && (
<p className="text-xs text-slate-500">{consult.moduleScope}</p>
)}
</div>
<span className="text-xs text-slate-500">
{new Date(consult.acknowledgedAt).toLocaleDateString()}
</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
);
}
+26
View File
@@ -0,0 +1,26 @@
"use client";
import React from "react";
export default function ComplianceLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-slate-900 mb-2">
Compliance & Data Protection Setup
</h1>
<p className="text-lg text-slate-600">
Configure your school&apos;s legal and data protection compliance
framework
</p>
</div>
{children}
</div>
</div>
);
}
+83
View File
@@ -0,0 +1,83 @@
"use client";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { ComplianceWizard } from "@/components/compliance-wizard";
import { Card, CardContent } from "@/components/ui/card";
import { Loader2 } from "lucide-react";
export default function CompliancePage() {
const router = useRouter();
const [schoolId, setSchoolId] = useState<string | null>(null);
const [isCompleted, setIsCompleted] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
// In a real app, fetch the current user's schoolId from session/auth
// For now, we'll get it from the URL or localStorage
const fetchSchoolId = async () => {
try {
const response = await fetch("/api/v1/user/current");
if (!response.ok) throw new Error("Failed to fetch user");
const user = await response.json();
if (user.schoolId) {
setSchoolId(user.schoolId);
}
} catch (error) {
console.error("[v0] Error fetching school ID:", error);
} finally {
setLoading(false);
}
};
fetchSchoolId();
}, []);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
</div>
);
}
if (!schoolId) {
return (
<div className="max-w-md mx-auto">
<Card>
<CardContent className="pt-6">
<p className="text-red-600">Unable to load compliance setup. Please ensure you are logged in.</p>
</CardContent>
</Card>
</div>
);
}
return (
<div>
{isCompleted ? (
<Card className="border-emerald-200 bg-emerald-50">
<CardContent className="pt-6">
<p className="text-emerald-800 mb-4">
✓ Compliance onboarding completed successfully! Your school is now ready to use ExaMetra.
</p>
<button
onClick={() => router.push("/admin/compliance-overview")}
className="inline-flex items-center gap-2 px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700"
>
Go to Compliance Dashboard →
</button>
</CardContent>
</Card>
) : (
<ComplianceWizard
schoolId={schoolId}
onComplete={(data) => {
console.log("[v0] Wizard completed:", data);
setIsCompleted(true);
}}
/>
)}
</div>
);
}