810485ecc8
---ci--- phase: 7 milestone: v0.1 status: complete requirements: covered: [REQ-001, REQ-002, REQ-003, REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011, REQ-012, REQ-013, REQ-014, REQ-015, REQ-016, REQ-017, REQ-018, REQ-019, REQ-020, REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026, REQ-027, REQ-028] partial: [] ---/ci--- Milestone v0.1 (nextcraft-ui-prototype) complete. Summary: - 7 phases (P0 pre-execution + P1-P6 execution + P7 final review) - 28 requirements covered (all complete) - 4 surfaces: Learner (7 pages), Marketplace (5), Employer Dashboard (4), Admin (4) - 19 routes, 104 TypeScript files - Shared component library (5 primitives + design tokens) - Mock data: 5 competency stacks (70 competencies), 20 jobs, 15 candidates, 10 employers - Tech: Next.js 15, Tailwind CSS v4, lucide-react, recharts, @xyflow/react, Inter font - Storybook with 6 stories - Dark mode, breadcrumbs, role switcher, responsive design - Build passes, typecheck passes, Storybook build passes Phases: P0 pre-execution → v0.0.1 P1 project-scaffolding → v0.0.2 P2 learner-surface → v0.0.3 P3 marketplace-surface → v0.0.4 P4 employer-dashboard → v0.0.5 P5 admin-surface → v0.0.6 P6 polish-integration → v0.0.7 P7 final-review-ship → v0.1.0 (milestone release)
102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { ChevronDown, ChevronRight, ShieldCheck } from 'lucide-react';
|
|
import { Badge } from '@nextcraft/ui';
|
|
import { matchBadgeClasses } from '../../lib/format';
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Types + mock transcript data */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export interface DefenseSession {
|
|
id: string;
|
|
competencyName: string;
|
|
score: number;
|
|
date: string;
|
|
transcript: Array<{ question: string; answer: string }>;
|
|
}
|
|
|
|
interface DefenseAccordionProps {
|
|
sessions: DefenseSession[];
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Component */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export function DefenseAccordion({ sessions }: DefenseAccordionProps) {
|
|
const [openId, setOpenId] = useState<string | null>(sessions[0]?.id ?? null);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
{sessions.map((session) => {
|
|
const open = openId === session.id;
|
|
return (
|
|
<div
|
|
key={session.id}
|
|
className="overflow-hidden rounded-lg border border-slate-200 dark:border-slate-800"
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpenId(open ? null : session.id)}
|
|
className="flex w-full items-center gap-3 bg-white p-4 text-left transition-colors hover:bg-slate-50 dark:bg-slate-900 dark:hover:bg-slate-800"
|
|
aria-expanded={open}
|
|
>
|
|
<ShieldCheck className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" />
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
<span className="truncate text-sm font-semibold text-slate-900 dark:text-slate-100">
|
|
{session.competencyName}
|
|
</span>
|
|
<span className="text-xs text-slate-500 dark:text-slate-400">
|
|
{new Date(session.date).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
})}{' '}
|
|
· Defense session
|
|
</span>
|
|
</div>
|
|
<span
|
|
className={`inline-flex h-8 w-11 shrink-0 items-center justify-center rounded-full text-xs font-bold ring-2 ${matchBadgeClasses(
|
|
session.score,
|
|
)}`}
|
|
>
|
|
{session.score}
|
|
</span>
|
|
{open ? (
|
|
<ChevronDown className="h-4 w-4 shrink-0 text-slate-400" />
|
|
) : (
|
|
<ChevronRight className="h-4 w-4 shrink-0 text-slate-400" />
|
|
)}
|
|
</button>
|
|
{open && (
|
|
<div className="border-t border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900/60">
|
|
<div className="flex flex-col gap-4">
|
|
{session.transcript.map((qa, idx) => (
|
|
<div key={idx} className="flex flex-col gap-1.5">
|
|
<div className="flex items-start gap-2">
|
|
<Badge variant="info">Q{idx + 1}</Badge>
|
|
<p className="text-sm font-medium text-slate-800 dark:text-slate-200">
|
|
{qa.question}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-start gap-2 pl-1">
|
|
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded bg-slate-200 text-[10px] font-bold text-slate-600 dark:bg-slate-700 dark:text-slate-200">
|
|
A
|
|
</span>
|
|
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
|
{qa.answer}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
} |