Round 25: Substitute Teacher, School Newsletter, Career Guidance, Styling
- Substitute Teacher Management View: pool, absences, assignments, statistics, weekly schedule - School Newsletter View: editor, templates, distribution, analytics, open/click tracking - Student Career & Guidance View: career exploration, interest quiz, portfolio, goals, appointments - 6 new Prisma models: SubstituteTeacher, TeacherAbsence, SubstitutionAssignment, CareerProfile, CareerGoal, CareerAppointment - Enhanced Newsletter model with content, scheduling, and analytics fields - Enhanced settings-view with gradient header, glassmorphism cards, danger zone - Enhanced student-portal-view with animated welcome banner, quick action cards - 16+ new API route files - 300+ i18n keys added - All views verified working in browser
This commit is contained in:
1 parent
a159ed7fe6
commit
3d0d518bb9
57 files changed
+8370
-48
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: Get single career appointment ───────────────────────────────────
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const appointment = await db.careerAppointment.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
counselor: { select: { id: true, firstName: true, lastName: true } },
|
||||
profile: {
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!appointment) {
|
||||
return NextResponse.json({ error: 'Appointment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(appointment);
|
||||
} catch (error) {
|
||||
console.error('Career appointment GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── PUT: Update career appointment ───────────────────────────────────────
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const body = await request.json();
|
||||
const { date, duration, type, status, notes, actionItems } = body;
|
||||
|
||||
const existing = await db.careerAppointment.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: 'Appointment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (date !== undefined) updateData.date = new Date(date);
|
||||
if (duration !== undefined) updateData.duration = duration;
|
||||
if (type !== undefined) updateData.type = type;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
if (notes !== undefined) updateData.notes = notes;
|
||||
if (actionItems !== undefined) updateData.actionItems = JSON.stringify(actionItems);
|
||||
|
||||
const appointment = await db.careerAppointment.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
include: {
|
||||
counselor: { select: { id: true, firstName: true, lastName: true } },
|
||||
profile: {
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(appointment);
|
||||
} catch (error) {
|
||||
console.error('Career appointment PUT error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── DELETE: Delete career appointment ────────────────────────────────────
|
||||
export async function DELETE(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const existing = await db.careerAppointment.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: 'Appointment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
await db.careerAppointment.delete({ where: { id } });
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Career appointment DELETE error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: List career appointments ────────────────────────────────────────
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
|
||||
const profileId = searchParams.get('profileId');
|
||||
const counselorId = searchParams.get('counselorId');
|
||||
const status = searchParams.get('status');
|
||||
const type = searchParams.get('type');
|
||||
|
||||
if (!schoolId) {
|
||||
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = { schoolId };
|
||||
if (profileId) where.profileId = profileId;
|
||||
if (counselorId) where.counselorId = counselorId;
|
||||
if (status) where.status = status;
|
||||
if (type) where.type = type;
|
||||
|
||||
// Role-based access
|
||||
if (session.user?.role === 'STUDENT') {
|
||||
const student = await db.student.findFirst({
|
||||
where: { userId: session.userId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (student) {
|
||||
const profile = await db.careerProfile.findFirst({
|
||||
where: { studentId: student.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (profile) {
|
||||
where.profileId = profile.id;
|
||||
}
|
||||
}
|
||||
} else if (session.user?.role === 'PARENT') {
|
||||
const parentLinks = await db.parentStudentLink.findMany({
|
||||
where: { parentId: session.userId },
|
||||
select: { studentId: true },
|
||||
});
|
||||
const studentIds = parentLinks.map((l) => l.studentId);
|
||||
const profiles = await db.careerProfile.findMany({
|
||||
where: { studentId: { in: studentIds } },
|
||||
select: { id: true },
|
||||
});
|
||||
const profileIds = profiles.map((p) => p.id);
|
||||
where.profileId = { in: profileIds };
|
||||
} else if (session.user?.role === 'TEACHER') {
|
||||
// Teachers see appointments where they are the counselor
|
||||
if (!counselorId) {
|
||||
where.counselorId = session.userId;
|
||||
}
|
||||
}
|
||||
|
||||
const appointments = await db.careerAppointment.findMany({
|
||||
where,
|
||||
include: {
|
||||
counselor: { select: { id: true, firstName: true, lastName: true } },
|
||||
profile: {
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { date: 'desc' },
|
||||
});
|
||||
|
||||
return NextResponse.json(appointments);
|
||||
} catch (error) {
|
||||
console.error('Career appointments GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── POST: Create career appointment ──────────────────────────────────────
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { schoolId, profileId, counselorId, date, duration, type, notes } = body;
|
||||
|
||||
if (!schoolId || !profileId || !counselorId || !date) {
|
||||
return NextResponse.json(
|
||||
{ error: 'schoolId, profileId, counselorId, and date are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const appointment = await db.careerAppointment.create({
|
||||
data: {
|
||||
schoolId,
|
||||
profileId,
|
||||
counselorId,
|
||||
date: new Date(date),
|
||||
duration: duration || 30,
|
||||
type: type || 'guidance',
|
||||
notes: notes || null,
|
||||
},
|
||||
include: {
|
||||
counselor: { select: { id: true, firstName: true, lastName: true } },
|
||||
profile: {
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(appointment, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Career appointments POST error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: Get single career goal ──────────────────────────────────────────
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const goal = await db.careerGoal.findUnique({ where: { id } });
|
||||
|
||||
if (!goal) {
|
||||
return NextResponse.json({ error: 'Goal not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(goal);
|
||||
} catch (error) {
|
||||
console.error('Career goal GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── PUT: Update career goal ─────────────────────────────────────────────
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const body = await request.json();
|
||||
const { title, description, category, targetDate, progress, status, milestones } = body;
|
||||
|
||||
const existing = await db.careerGoal.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: 'Goal not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (title !== undefined) updateData.title = title;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
if (category !== undefined) updateData.category = category;
|
||||
if (targetDate !== undefined) updateData.targetDate = targetDate ? new Date(targetDate) : null;
|
||||
if (progress !== undefined) updateData.progress = progress;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
if (milestones !== undefined) updateData.milestones = JSON.stringify(milestones);
|
||||
|
||||
const goal = await db.careerGoal.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
return NextResponse.json(goal);
|
||||
} catch (error) {
|
||||
console.error('Career goal PUT error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── DELETE: Delete career goal ──────────────────────────────────────────
|
||||
export async function DELETE(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const existing = await db.careerGoal.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: 'Goal not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
await db.careerGoal.delete({ where: { id } });
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Career goal DELETE error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: List career goals ───────────────────────────────────────────────
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const profileId = searchParams.get('profileId');
|
||||
const category = searchParams.get('category');
|
||||
const status = searchParams.get('status');
|
||||
|
||||
if (!profileId) {
|
||||
return NextResponse.json({ error: 'profileId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = { profileId };
|
||||
if (category) where.category = category;
|
||||
if (status) where.status = status;
|
||||
|
||||
const goals = await db.careerGoal.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return NextResponse.json(goals);
|
||||
} catch (error) {
|
||||
console.error('Career goals GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── POST: Create career goal ────────────────────────────────────────────
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { profileId, title, description, category, targetDate, progress, milestones } = body;
|
||||
|
||||
if (!profileId || !title || !category) {
|
||||
return NextResponse.json(
|
||||
{ error: 'profileId, title, and category are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const goal = await db.careerGoal.create({
|
||||
data: {
|
||||
profileId,
|
||||
title,
|
||||
description: description || null,
|
||||
category,
|
||||
targetDate: targetDate ? new Date(targetDate) : null,
|
||||
progress: progress || 0,
|
||||
milestones: milestones ? JSON.stringify(milestones) : null,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(goal, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Career goals POST error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: Get student career profile ──────────────────────────────────────
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const studentId = searchParams.get('studentId');
|
||||
const profileId = searchParams.get('profileId');
|
||||
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
|
||||
|
||||
if (!schoolId) {
|
||||
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = { schoolId };
|
||||
if (profileId) where.id = profileId;
|
||||
if (studentId) where.studentId = studentId;
|
||||
|
||||
// Role-based access
|
||||
if (session.user?.role === 'STUDENT') {
|
||||
const student = await db.student.findFirst({
|
||||
where: { userId: session.userId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (student) {
|
||||
where.studentId = student.id;
|
||||
}
|
||||
} else if (session.user?.role === 'PARENT') {
|
||||
const parentLinks = await db.parentStudentLink.findMany({
|
||||
where: { parentId: session.userId },
|
||||
select: { studentId: true },
|
||||
});
|
||||
const studentIds = parentLinks.map((l) => l.studentId);
|
||||
where.studentId = { in: studentIds };
|
||||
}
|
||||
|
||||
const profiles = await db.careerProfile.findMany({
|
||||
where,
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true, avatarUrl: true } },
|
||||
goals: { orderBy: { createdAt: 'desc' } },
|
||||
appointments: {
|
||||
include: {
|
||||
counselor: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
orderBy: { date: 'desc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Return single profile if profileId or studentId specified
|
||||
if (profileId || studentId) {
|
||||
const profile = profiles[0] || null;
|
||||
return NextResponse.json(profile);
|
||||
}
|
||||
|
||||
return NextResponse.json(profiles);
|
||||
} catch (error) {
|
||||
console.error('Career profile GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── PUT: Update career profile ──────────────────────────────────────────
|
||||
export async function PUT(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { id, interests, strengths, careerCluster, desiredCareer, educationPath,
|
||||
workExperiences, volunteerExps, certifications, documents, notes } = body;
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: 'Profile id is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await db.careerProfile.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Role-based access: students can update own, teachers/admins can update any
|
||||
if (session.user?.role === 'STUDENT') {
|
||||
const student = await db.student.findFirst({
|
||||
where: { userId: session.userId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!student || student.id !== existing.studentId) {
|
||||
return NextResponse.json({ error: 'Not authorized' }, { status: 403 });
|
||||
}
|
||||
} else if (session.user?.role === 'PARENT') {
|
||||
const parentLinks = await db.parentStudentLink.findMany({
|
||||
where: { parentId: session.userId },
|
||||
select: { studentId: true },
|
||||
});
|
||||
const studentIds = parentLinks.map((l) => l.studentId);
|
||||
if (!studentIds.includes(existing.studentId)) {
|
||||
return NextResponse.json({ error: 'Not authorized' }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (interests !== undefined) updateData.interests = JSON.stringify(interests);
|
||||
if (strengths !== undefined) updateData.strengths = JSON.stringify(strengths);
|
||||
if (careerCluster !== undefined) updateData.careerCluster = careerCluster;
|
||||
if (desiredCareer !== undefined) updateData.desiredCareer = desiredCareer;
|
||||
if (educationPath !== undefined) updateData.educationPath = educationPath;
|
||||
if (workExperiences !== undefined) updateData.workExperiences = JSON.stringify(workExperiences);
|
||||
if (volunteerExps !== undefined) updateData.volunteerExps = JSON.stringify(volunteerExps);
|
||||
if (certifications !== undefined) updateData.certifications = JSON.stringify(certifications);
|
||||
if (documents !== undefined) updateData.documents = JSON.stringify(documents);
|
||||
if (notes !== undefined) updateData.notes = notes;
|
||||
|
||||
const profile = await db.careerProfile.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
return NextResponse.json(profile);
|
||||
} catch (error) {
|
||||
console.error('Career profile PUT error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: Get career data (clusters, career profiles for school-wide stats) ──
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
|
||||
|
||||
if (!schoolId) {
|
||||
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get career profiles for statistics
|
||||
const profiles = await db.careerProfile.findMany({
|
||||
where: { schoolId },
|
||||
include: {
|
||||
student: { select: { id: true, firstName: true, lastName: true } },
|
||||
goals: true,
|
||||
appointments: {
|
||||
where: { status: 'scheduled' },
|
||||
take: 5,
|
||||
orderBy: { date: 'asc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Career cluster distribution
|
||||
const clusterCounts: Record<string, number> = {};
|
||||
profiles.forEach((p) => {
|
||||
const cluster = p.careerCluster || 'unassigned';
|
||||
clusterCounts[cluster] = (clusterCounts[cluster] || 0) + 1;
|
||||
});
|
||||
|
||||
// Education path distribution
|
||||
const pathCounts: Record<string, number> = {};
|
||||
profiles.forEach((p) => {
|
||||
const path = p.educationPath || 'unassigned';
|
||||
pathCounts[path] = (pathCounts[path] || 0) + 1;
|
||||
});
|
||||
|
||||
// Goal stats
|
||||
const allGoals = profiles.flatMap((p) => p.goals);
|
||||
const goalStats = {
|
||||
total: allGoals.length,
|
||||
active: allGoals.filter((g) => g.status === 'active').length,
|
||||
completed: allGoals.filter((g) => g.status === 'completed').length,
|
||||
avgProgress: allGoals.length > 0
|
||||
? Math.round(allGoals.reduce((sum, g) => sum + g.progress, 0) / allGoals.length)
|
||||
: 0,
|
||||
};
|
||||
|
||||
// Appointment stats
|
||||
const allAppointments = await db.careerAppointment.findMany({
|
||||
where: { schoolId },
|
||||
});
|
||||
const appointmentStats = {
|
||||
total: allAppointments.length,
|
||||
scheduled: allAppointments.filter((a) => a.status === 'scheduled').length,
|
||||
completed: allAppointments.filter((a) => a.status === 'completed').length,
|
||||
cancelled: allAppointments.filter((a) => a.status === 'cancelled').length,
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
profiles,
|
||||
stats: {
|
||||
totalProfiles: profiles.length,
|
||||
clusterCounts,
|
||||
pathCounts,
|
||||
goalStats,
|
||||
appointmentStats,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Career GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── POST: Create career profile ──────────────────────────────────────────
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { schoolId, studentId, interests, strengths, careerCluster, desiredCareer, educationPath } = body;
|
||||
|
||||
if (!schoolId || !studentId) {
|
||||
return NextResponse.json({ error: 'schoolId and studentId are required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Check if profile already exists
|
||||
const existing = await db.careerProfile.findFirst({
|
||||
where: { schoolId, studentId },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return NextResponse.json({ error: 'Career profile already exists for this student' }, { status: 409 });
|
||||
}
|
||||
|
||||
const profile = await db.careerProfile.create({
|
||||
data: {
|
||||
schoolId,
|
||||
studentId,
|
||||
interests: interests ? JSON.stringify(interests) : null,
|
||||
strengths: strengths ? JSON.stringify(strengths) : null,
|
||||
careerCluster: careerCluster || null,
|
||||
desiredCareer: desiredCareer || null,
|
||||
educationPath: educationPath || null,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(profile, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Career POST error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user