@burdenoff/microfe-technospam 2026.801.1 → 2026.828.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
  2. package/dist/technospam/TechnoSpamRoutes.js +89 -88
  3. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  4. package/dist/technospam/api/technospamApi.d.ts +19 -0
  5. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  6. package/dist/technospam/api/technospamApi.js +171 -150
  7. package/dist/technospam/api/technospamApi.js.map +1 -1
  8. package/dist/technospam/components/SubmissionViews.d.ts +31 -0
  9. package/dist/technospam/components/SubmissionViews.d.ts.map +1 -0
  10. package/dist/technospam/components/SubmissionViews.js +149 -0
  11. package/dist/technospam/components/SubmissionViews.js.map +1 -0
  12. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts +25 -0
  13. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts.map +1 -0
  14. package/dist/technospam/components/TechnoSpamErrorBoundary.js +31 -0
  15. package/dist/technospam/components/TechnoSpamErrorBoundary.js.map +1 -0
  16. package/dist/technospam/components/index.d.ts +2 -0
  17. package/dist/technospam/components/index.d.ts.map +1 -1
  18. package/dist/technospam/components/index.js +2 -0
  19. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  20. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +725 -500
  21. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  22. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  23. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +343 -262
  24. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  25. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +2 -2
  26. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
  27. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  28. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +97 -235
  29. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  30. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +1 -1
  31. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  32. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  33. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +293 -202
  34. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  35. package/dist/technospam/pages/CoursesCoursesPagePage.js +1 -1
  36. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  37. package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
  38. package/dist/technospam/pages/CoursesMyLearningPagePage.js +2 -2
  39. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  40. package/dist/technospam/pages/DashboardDashboardPagePage.js +1 -1
  41. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  42. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +1 -1
  43. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  44. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +1 -1
  45. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  46. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +1 -2
  47. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  48. package/dist/technospam/pages/MentorshipMentorsPagePage.js +1 -1
  49. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  50. package/dist/technospam/pages/MentorshipSessionsPagePage.js +1 -1
  51. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  52. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.d.ts.map +1 -1
  53. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +180 -121
  54. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -1
  55. package/dist/test/setup.d.ts +2 -0
  56. package/dist/test/setup.d.ts.map +1 -0
  57. package/package.json +9 -3
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/technospam/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n CalendarRange,\n Code2,\n GraduationCap,\n Library,\n Percent,\n TrendingUp,\n Trophy,\n Users,\n X,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Legend,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip as ReTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport {\n EmptyView,\n ErrorView,\n LoadingGrid,\n LoadingRows,\n PageLayout,\n StatCard,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCoursesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useProblemsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecEnrollmentStatus,\n type TecSubmissionVerdict,\n} from '../api/technospamApi';\nimport { humanizeEnum } from '../utils/format';\n\n/* ----------------------------------------------------------- chart config */\n\nconst ENROLL_STATUS_ORDER: TecEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n];\n\nconst ENROLL_STATUS_COLOR: Record<TecEnrollmentStatus, string> = {\n ENROLLED: 'var(--color-accent-primary)',\n IN_PROGRESS: 'var(--color-accent-teal)',\n COMPLETED: 'var(--color-status-success)',\n DROPPED: 'var(--color-status-warning)',\n};\n\nconst VERDICT_ORDER: TecSubmissionVerdict[] = [\n 'ACCEPTED',\n 'WRONG_ANSWER',\n 'TIME_LIMIT_EXCEEDED',\n 'RUNTIME_ERROR',\n 'COMPILE_ERROR',\n 'PENDING',\n];\n\nconst VERDICT_LABEL: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'Accepted',\n WRONG_ANSWER: 'Wrong',\n TIME_LIMIT_EXCEEDED: 'TLE',\n RUNTIME_ERROR: 'Runtime',\n COMPILE_ERROR: 'Compile',\n PENDING: 'Pending',\n};\n\nconst VERDICT_COLOR: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'var(--color-status-success)',\n WRONG_ANSWER: 'var(--color-status-error)',\n TIME_LIMIT_EXCEEDED: 'var(--color-status-warning)',\n RUNTIME_ERROR: 'var(--color-status-error)',\n COMPILE_ERROR: 'var(--color-status-error)',\n PENDING: 'var(--color-text-muted)',\n};\n\nconst TOOLTIP_STYLE: CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n};\nconst TOOLTIP_LABEL_STYLE: CSSProperties = { color: 'var(--color-text-secondary)' };\nconst AXIS_TICK: SVGProps<SVGTextElement> = { fill: 'var(--color-text-muted)', fontSize: 12 };\n\ninterface StatusDatum {\n name: string;\n value: number;\n color: string;\n}\ninterface VerdictDatum {\n label: string;\n count: number;\n color: string;\n}\ninterface ActivityDatum {\n label: string;\n submissions: number;\n enrollments: number;\n}\n\n/** Normalize an ISO timestamp to the start of its (Sunday-based) week. */\nfunction weekBucket(iso: string): { key: number; label: string } {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return { key: 0, label: '—' };\n const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - d.getDay());\n return {\n key: start.getTime(),\n label: start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n };\n}\n\n/* -------------------------------------------------------- chart wrappers */\n\ntype ChipTone = 'blue' | 'teal' | 'purple' | 'indigo';\n\nconst CHIP: Record<ChipTone, string> = {\n blue: 'bg-accent-primary/15 text-accent-primary ring-accent-primary/20',\n teal: 'bg-accent-teal/15 text-accent-teal ring-accent-teal/20',\n purple: 'bg-accent-purple/15 text-accent-purple ring-accent-purple/20',\n indigo: 'bg-accent-indigo/15 text-accent-indigo ring-accent-indigo/20',\n};\n\nfunction ChartHeader({\n icon: Icon,\n title,\n subtitle,\n tone,\n}: {\n icon: LucideIcon;\n title: string;\n subtitle?: string;\n tone: ChipTone;\n}) {\n return (\n <div className=\"mb-4 flex items-center gap-3\">\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ring-1',\n CHIP[tone]\n )}\n >\n <Icon size={17} />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{title}</h3>\n {subtitle ? <p className=\"truncate text-xs text-text-muted\">{subtitle}</p> : null}\n </div>\n </div>\n );\n}\n\nfunction ChartCard({\n title,\n subtitle,\n icon,\n tone = 'teal',\n isEmpty,\n emptyLabel,\n children,\n}: {\n title: string;\n subtitle?: string;\n icon: LucideIcon;\n tone?: ChipTone;\n isEmpty: boolean;\n emptyLabel: string;\n children: React.ReactElement;\n}) {\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={icon} title={title} subtitle={subtitle} tone={tone} />\n {isEmpty ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {emptyLabel}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={260}>\n {children}\n </ResponsiveContainer>\n )}\n </GlassCard>\n );\n}\n\n/* ---------------------------------------------------------- date range */\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) into inclusive\n * ISO bounds so the server-side createdAt filter covers the whole day. */\nfunction dayStartIso(date: string): string | undefined {\n return date ? `${date}T00:00:00.000Z` : undefined;\n}\nfunction dayEndIso(date: string): string | undefined {\n return date ? `${date}T23:59:59.999Z` : undefined;\n}\n\nfunction DateRangeControls({\n start,\n end,\n onStart,\n onEnd,\n onPreset,\n onClear,\n}: {\n start: string;\n end: string;\n onStart: (v: string) => void;\n onEnd: (v: string) => void;\n onPreset: (days: number) => void;\n onClear: () => void;\n}) {\n const active = Boolean(start || end);\n return (\n <GlassCard className=\"mb-6 flex flex-col gap-3 p-4 lg:flex-row lg:items-end lg:justify-between\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex items-center gap-2 self-center text-sm font-medium text-text-secondary\">\n <CalendarRange size={16} className=\"text-accent-teal\" />\n Date range\n </div>\n <div>\n <label\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n htmlFor=\"tec-range-from\"\n >\n From\n </label>\n <Input\n id=\"tec-range-from\"\n type=\"date\"\n value={start}\n max={end || undefined}\n onChange={(e) => onStart(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-range-to\">\n To\n </label>\n <Input\n id=\"tec-range-to\"\n type=\"date\"\n value={end}\n min={start || undefined}\n onChange={(e) => onEnd(e.target.value)}\n className=\"w-40\"\n />\n </div>\n {active ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={onClear}>\n <X size={14} /> Clear\n </Button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(7)}>\n Last 7 days\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(30)}>\n Last 30 days\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(90)}>\n Last 90 days\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const [rangeStart, setRangeStart] = useState('');\n const [rangeEnd, setRangeEnd] = useState('');\n const startDate = dayStartIso(rangeStart);\n const endDate = dayEndIso(rangeEnd);\n\n const applyPreset = (days: number) => {\n const now = new Date();\n const from = new Date();\n from.setDate(from.getDate() - days);\n setRangeStart(from.toISOString().slice(0, 10));\n setRangeEnd(now.toISOString().slice(0, 10));\n };\n const clearRange = () => {\n setRangeStart('');\n setRangeEnd('');\n };\n\n const dateVars = startDate || endDate ? { startDate, endDate } : {};\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useCertificatesQuery(learnerId ? { learnerId } : {});\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const problemsQ = useProblemsQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({});\n\n const enrollments = useMemo(\n () => enrollmentsQ.data?.tecListEnrollments.items ?? [],\n [enrollmentsQ.data]\n );\n const submissions = useMemo(\n () => submissionsQ.data?.tecListSubmissions.items ?? [],\n [submissionsQ.data]\n );\n const sessions = sessionsQ.data?.tecListSessions.items ?? [];\n const certificates = certificatesQ.data?.tecListCertificates ?? [];\n const coursesTotal = coursesQ.data?.tecListCourses.items.length ?? 0;\n const problemsTotal = problemsQ.data?.tecListProblems.items.length ?? 0;\n const hackathonsTotal = hackathonsQ.data?.tecListHackathons.items.length ?? 0;\n\n const metrics = useMemo(() => {\n const coursesCompleted = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const avgProgress = enrollments.length\n ? Math.round(enrollments.reduce((s, e) => s + e.progressPct, 0) / enrollments.length)\n : 0;\n const acceptedSubs = submissions.filter((s) => s.verdict === 'ACCEPTED');\n const problemsSolved = new Set(acceptedSubs.map((s) => s.problemId)).size;\n const acceptanceRate = submissions.length\n ? Math.round((acceptedSubs.length / submissions.length) * 100)\n : 0;\n return { coursesCompleted, avgProgress, problemsSolved, acceptanceRate };\n }, [enrollments, submissions]);\n\n const enrollmentsByStatus = useMemo<StatusDatum[]>(\n () =>\n ENROLL_STATUS_ORDER.map((s) => ({\n name: humanizeEnum(s),\n value: enrollments.filter((e) => e.status === s).length,\n color: ENROLL_STATUS_COLOR[s],\n })).filter((d) => d.value > 0),\n [enrollments]\n );\n\n const submissionsByVerdict = useMemo<VerdictDatum[]>(\n () =>\n VERDICT_ORDER.map((v) => ({\n label: VERDICT_LABEL[v],\n count: submissions.filter((s) => s.verdict === v).length,\n color: VERDICT_COLOR[v],\n })).filter((d) => d.count > 0),\n [submissions]\n );\n\n const activityData = useMemo<ActivityDatum[]>(() => {\n const buckets = new Map<number, ActivityDatum>();\n const add = (iso: string, key: 'submissions' | 'enrollments') => {\n const { key: bucketKey, label } = weekBucket(iso);\n const existing = buckets.get(bucketKey) ?? { label, submissions: 0, enrollments: 0 };\n existing[key] += 1;\n buckets.set(bucketKey, existing);\n };\n submissions.forEach((s) => add(s.createdAt, 'submissions'));\n enrollments.forEach((e) => add(e.createdAt, 'enrollments'));\n return [...buckets.entries()]\n .sort((a, b) => a[0] - b[0])\n .slice(-8)\n .map(([, v]) => v);\n }, [submissions, enrollments]);\n\n const hasActivity =\n enrollments.length > 0 ||\n submissions.length > 0 ||\n sessions.length > 0 ||\n certificates.length > 0;\n\n const learnerLoading =\n enrollmentsQ.loading || submissionsQ.loading || sessionsQ.loading || certificatesQ.loading;\n const learnerError =\n enrollmentsQ.error || submissionsQ.error || sessionsQ.error || certificatesQ.error;\n const showLoading = learnerLoading && !hasActivity;\n\n const workspaceOverview = (\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Workspace overview</h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n Live content available across the TechnoSpam learning workspace right now.\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard label=\"Published courses\" value={coursesTotal} icon={Library} tone=\"blue\" />\n <StatCard label=\"Published problems\" value={problemsTotal} icon={Code2} tone=\"indigo\" />\n <StatCard label=\"Hackathons\" value={hackathonsTotal} icon={Trophy} tone=\"purple\" />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title=\"Analytics\"\n description=\"Your real learning performance across courses, problems, mentoring and certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title=\"Sign in to view your analytics\"\n description=\"Your personal learning metrics appear here once you're signed in.\"\n />\n {workspaceOverview}\n </div>\n ) : showLoading ? (\n <div className=\"space-y-6\">\n <LoadingGrid count={4} height=\"h-24\" />\n <LoadingRows count={2} height=\"h-72\" />\n </div>\n ) : learnerError && !hasActivity ? (\n <ErrorView title=\"Couldn't load your analytics\" error={learnerError} />\n ) : (\n <div className=\"space-y-8\">\n <DateRangeControls\n start={rangeStart}\n end={rangeEnd}\n onStart={setRangeStart}\n onEnd={setRangeEnd}\n onPreset={applyPreset}\n onClear={clearRange}\n />\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard\n label=\"Courses enrolled\"\n value={enrollments.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label=\"Courses completed\"\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label=\"Avg progress\"\n value={`${metrics.avgProgress}%`}\n icon={TrendingUp}\n tone=\"purple\"\n />\n <StatCard\n label=\"Problems solved\"\n value={metrics.problemsSolved}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Acceptance rate\"\n value={`${metrics.acceptanceRate}%`}\n icon={Percent}\n tone=\"teal\"\n />\n <StatCard label=\"Mentor sessions\" value={sessions.length} icon={Users} tone=\"blue\" />\n <StatCard label=\"Certificates\" value={certificates.length} icon={Award} tone=\"amber\" />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title=\"No learning activity yet\"\n description=\"Enroll in a course or solve a problem and your progress charts will build here. TechnoSpam is pre-launch — start learning to see your data.\"\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>Browse courses</Button>\n }\n />\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Enrollments by status\"\n subtitle=\"How your courses are progressing\"\n icon={BookOpen}\n tone=\"blue\"\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel=\"No enrollments yet\"\n >\n <PieChart>\n <Pie\n data={enrollmentsByStatus}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={60}\n outerRadius={92}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {enrollmentsByStatus.map((d) => (\n <Cell key={d.name} fill={d.color} />\n ))}\n </Pie>\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n </PieChart>\n </ChartCard>\n\n <ChartCard\n title=\"Submissions by verdict\"\n subtitle=\"Outcomes across your attempts\"\n icon={Code2}\n tone=\"purple\"\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel=\"No submissions yet\"\n >\n <BarChart\n data={submissionsByVerdict}\n margin={{ top: 8, right: 8, left: -18, bottom: 0 }}\n >\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip\n contentStyle={TOOLTIP_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-accent-primary)', fillOpacity: 0.08 }}\n />\n <Bar dataKey=\"count\" name=\"Submissions\" radius={[6, 6, 0, 0]} maxBarSize={56}>\n {submissionsByVerdict.map((d) => (\n <Cell key={d.label} fill={d.color} />\n ))}\n </Bar>\n </BarChart>\n </ChartCard>\n </div>\n\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Activity}\n title=\"Learning activity over time\"\n subtitle=\"Submissions and enrollments per week\"\n tone=\"teal\"\n />\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n No dated activity yet\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart\n data={activityData}\n margin={{ top: 8, right: 12, left: -18, bottom: 0 }}\n >\n <defs>\n <linearGradient id=\"tec-grad-submissions\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0}\n />\n </linearGradient>\n <linearGradient id=\"tec-grad-enrollments\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n <Area\n type=\"monotone\"\n dataKey=\"submissions\"\n name=\"Submissions\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-submissions)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"enrollments\"\n name=\"Enrollments\"\n stroke=\"var(--color-accent-teal)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-enrollments)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </div>\n )}\n\n {workspaceOverview}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/technospam/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n CalendarRange,\n Code2,\n GraduationCap,\n Library,\n Percent,\n Target,\n TrendingUp,\n Trophy,\n Users,\n X,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Legend,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip as ReTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport {\n EmptyView,\n ErrorView,\n LoadingGrid,\n LoadingRows,\n PageLayout,\n StatCard,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCoursesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useProblemsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecEnrollmentStatus,\n type TecSubmissionVerdict,\n} from '../api/technospamApi';\nimport { humanizeEnum } from '../utils/format';\n\n/* ----------------------------------------------------------- chart config */\n\nconst ENROLL_STATUS_ORDER: TecEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n];\n\nconst ENROLL_STATUS_COLOR: Record<TecEnrollmentStatus, string> = {\n ENROLLED: 'var(--color-accent-primary)',\n IN_PROGRESS: 'var(--color-accent-teal)',\n COMPLETED: 'var(--color-status-success)',\n DROPPED: 'var(--color-status-warning)',\n};\n\nconst VERDICT_ORDER: TecSubmissionVerdict[] = [\n 'ACCEPTED',\n 'WRONG_ANSWER',\n 'TIME_LIMIT_EXCEEDED',\n 'RUNTIME_ERROR',\n 'COMPILE_ERROR',\n 'PENDING',\n];\n\nconst VERDICT_LABEL: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'Accepted',\n WRONG_ANSWER: 'Wrong',\n TIME_LIMIT_EXCEEDED: 'TLE',\n RUNTIME_ERROR: 'Runtime',\n COMPILE_ERROR: 'Compile',\n PENDING: 'Pending',\n};\n\nconst VERDICT_COLOR: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'var(--color-status-success)',\n WRONG_ANSWER: 'var(--color-status-error)',\n TIME_LIMIT_EXCEEDED: 'var(--color-status-warning)',\n RUNTIME_ERROR: 'var(--color-status-error)',\n COMPILE_ERROR: 'var(--color-status-error)',\n PENDING: 'var(--color-text-muted)',\n};\n\nconst TOOLTIP_STYLE: CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n};\nconst TOOLTIP_LABEL_STYLE: CSSProperties = { color: 'var(--color-text-secondary)' };\nconst AXIS_TICK: SVGProps<SVGTextElement> = { fill: 'var(--color-text-muted)', fontSize: 12 };\n\ninterface StatusDatum {\n name: string;\n value: number;\n color: string;\n}\ninterface VerdictDatum {\n label: string;\n count: number;\n color: string;\n}\ninterface ActivityDatum {\n label: string;\n submissions: number;\n enrollments: number;\n}\ninterface TopicRateDatum {\n topic: string;\n accepted: number;\n attempted: number;\n rate: number;\n}\n\n/** Normalize an ISO timestamp to the start of its (Sunday-based) week. */\nfunction weekBucket(iso: string): { key: number; label: string } {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return { key: 0, label: '—' };\n const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - d.getDay());\n return {\n key: start.getTime(),\n label: start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n };\n}\n\n/* -------------------------------------------------------- chart wrappers */\n\ntype ChipTone = 'blue' | 'teal' | 'purple' | 'indigo';\n\nconst CHIP: Record<ChipTone, string> = {\n blue: 'bg-accent-primary/15 text-accent-primary ring-accent-primary/20',\n teal: 'bg-accent-teal/15 text-accent-teal ring-accent-teal/20',\n purple: 'bg-accent-purple/15 text-accent-purple ring-accent-purple/20',\n indigo: 'bg-accent-indigo/15 text-accent-indigo ring-accent-indigo/20',\n};\n\nfunction ChartHeader({\n icon: Icon,\n title,\n subtitle,\n tone,\n}: {\n icon: LucideIcon;\n title: string;\n subtitle?: string;\n tone: ChipTone;\n}) {\n return (\n <div className=\"mb-4 flex items-center gap-3\">\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ring-1',\n CHIP[tone]\n )}\n >\n <Icon size={17} />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{title}</h3>\n {subtitle ? <p className=\"truncate text-xs text-text-muted\">{subtitle}</p> : null}\n </div>\n </div>\n );\n}\n\nfunction ChartCard({\n title,\n subtitle,\n icon,\n tone = 'teal',\n isEmpty,\n emptyLabel,\n children,\n}: {\n title: string;\n subtitle?: string;\n icon: LucideIcon;\n tone?: ChipTone;\n isEmpty: boolean;\n emptyLabel: string;\n children: React.ReactElement;\n}) {\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={icon} title={title} subtitle={subtitle} tone={tone} />\n {isEmpty ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {emptyLabel}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={260}>\n {children}\n </ResponsiveContainer>\n )}\n </GlassCard>\n );\n}\n\n/**\n * Accepted rate per problem topic.\n *\n * Built entirely from the submissions this page already loads: each\n * TecSubmission carries its `problem { topic }` and its `verdict`, so the\n * rate is `ACCEPTED / attempts` per topic -- a count, not an inference. The\n * lowest row is called out as exactly that (the lowest measured rate), never\n * as a diagnosis or a recommendation.\n */\nfunction TopicAcceptanceCard({ data }: { data: TopicRateDatum[] }) {\n const weakest = data.length > 1 ? data[data.length - 1] : undefined;\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Target}\n title=\"Accepted rate by topic\"\n subtitle=\"Where your solutions land, and where they don't\"\n tone=\"indigo\"\n />\n {data.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n Solve a practice problem and your per-topic rate builds here\n </div>\n ) : (\n <div className=\"space-y-3\">\n {data.map((row) => {\n const lowest = weakest?.topic === row.topic;\n return (\n <div key={row.topic}>\n <div className=\"mb-1 flex flex-wrap items-baseline gap-x-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {row.topic}\n </span>\n <span\n className={cn(\n 'text-sm font-semibold tabular-nums',\n lowest ? 'text-status-warning-text' : 'text-text-secondary'\n )}\n >\n {row.rate}%\n </span>\n <span className=\"ml-auto shrink-0 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-muted\">\n {row.accepted} of {row.attempted} accepted\n </span>\n </div>\n <div className=\"h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn(\n 'h-full rounded-full transition-all duration-500',\n lowest ? 'bg-status-warning-text' : 'bg-accent-primary'\n )}\n style={{ width: `${row.rate}%` }}\n />\n </div>\n </div>\n );\n })}\n {weakest ? (\n <p className=\"pt-1 text-xs text-text-muted\">\n Lowest measured rate: <span className=\"text-text-secondary\">{weakest.topic}</span> —{' '}\n {weakest.accepted} of {weakest.attempted} attempts accepted in the loaded range.\n </p>\n ) : null}\n </div>\n )}\n </GlassCard>\n );\n}\n\n/* ---------------------------------------------------------- date range */\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) into inclusive\n * ISO bounds so the server-side createdAt filter covers the whole day. */\nfunction dayStartIso(date: string): string | undefined {\n return date ? `${date}T00:00:00.000Z` : undefined;\n}\nfunction dayEndIso(date: string): string | undefined {\n return date ? `${date}T23:59:59.999Z` : undefined;\n}\n\nfunction DateRangeControls({\n start,\n end,\n onStart,\n onEnd,\n onPreset,\n onClear,\n}: {\n start: string;\n end: string;\n onStart: (v: string) => void;\n onEnd: (v: string) => void;\n onPreset: (days: number) => void;\n onClear: () => void;\n}) {\n const active = Boolean(start || end);\n return (\n <GlassCard className=\"mb-6 flex flex-col gap-3 p-4 lg:flex-row lg:items-end lg:justify-between\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex items-center gap-2 self-center text-sm font-medium text-text-secondary\">\n <CalendarRange size={16} className=\"text-accent-teal\" />\n Date range\n </div>\n <div>\n <label\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n htmlFor=\"tec-range-from\"\n >\n From\n </label>\n <Input\n id=\"tec-range-from\"\n type=\"date\"\n value={start}\n max={end || undefined}\n onChange={(e) => onStart(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-range-to\">\n To\n </label>\n <Input\n id=\"tec-range-to\"\n type=\"date\"\n value={end}\n min={start || undefined}\n onChange={(e) => onEnd(e.target.value)}\n className=\"w-40\"\n />\n </div>\n {active ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={onClear}>\n <X size={14} /> Clear\n </Button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(7)}>\n Last 7 days\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(30)}>\n Last 30 days\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(90)}>\n Last 90 days\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const [rangeStart, setRangeStart] = useState('');\n const [rangeEnd, setRangeEnd] = useState('');\n const startDate = dayStartIso(rangeStart);\n const endDate = dayEndIso(rangeEnd);\n\n const applyPreset = (days: number) => {\n const now = new Date();\n const from = new Date();\n from.setDate(from.getDate() - days);\n setRangeStart(from.toISOString().slice(0, 10));\n setRangeEnd(now.toISOString().slice(0, 10));\n };\n const clearRange = () => {\n setRangeStart('');\n setRangeEnd('');\n };\n\n const dateVars = startDate || endDate ? { startDate, endDate } : {};\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useCertificatesQuery(learnerId ? { learnerId } : {});\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const problemsQ = useProblemsQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({});\n\n const enrollments = useMemo(\n () => enrollmentsQ.data?.tecListEnrollments?.items ?? [],\n [enrollmentsQ.data]\n );\n const submissions = useMemo(\n () => submissionsQ.data?.tecListSubmissions?.items ?? [],\n [submissionsQ.data]\n );\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.data?.tecListCertificates ?? [];\n const coursesTotal = coursesQ.data?.tecListCourses?.items.length ?? 0;\n const problemsTotal = problemsQ.data?.tecListProblems?.items.length ?? 0;\n const hackathonsTotal = hackathonsQ.data?.tecListHackathons?.items.length ?? 0;\n\n const metrics = useMemo(() => {\n const coursesCompleted = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const avgProgress = enrollments.length\n ? Math.round(enrollments.reduce((s, e) => s + e.progressPct, 0) / enrollments.length)\n : 0;\n const acceptedSubs = submissions.filter((s) => s.verdict === 'ACCEPTED');\n const problemsSolved = new Set(acceptedSubs.map((s) => s.problemId)).size;\n const acceptanceRate = submissions.length\n ? Math.round((acceptedSubs.length / submissions.length) * 100)\n : 0;\n return { coursesCompleted, avgProgress, problemsSolved, acceptanceRate };\n }, [enrollments, submissions]);\n\n const enrollmentsByStatus = useMemo<StatusDatum[]>(\n () =>\n ENROLL_STATUS_ORDER.map((s) => ({\n name: humanizeEnum(s),\n value: enrollments.filter((e) => e.status === s).length,\n color: ENROLL_STATUS_COLOR[s],\n })).filter((d) => d.value > 0),\n [enrollments]\n );\n\n const submissionsByVerdict = useMemo<VerdictDatum[]>(\n () =>\n VERDICT_ORDER.map((v) => ({\n label: VERDICT_LABEL[v],\n count: submissions.filter((s) => s.verdict === v).length,\n color: VERDICT_COLOR[v],\n })).filter((d) => d.count > 0),\n [submissions]\n );\n\n const acceptedRateByTopic = useMemo<TopicRateDatum[]>(() => {\n const buckets = new Map<string, { accepted: number; attempted: number }>();\n submissions.forEach((s) => {\n const topic = s.problem?.topic?.trim() || 'Untagged';\n const bucket = buckets.get(topic) ?? { accepted: 0, attempted: 0 };\n bucket.attempted += 1;\n if (s.verdict === 'ACCEPTED') bucket.accepted += 1;\n buckets.set(topic, bucket);\n });\n return [...buckets.entries()]\n .map(([topic, bucket]) => ({\n topic,\n accepted: bucket.accepted,\n attempted: bucket.attempted,\n rate: Math.round((bucket.accepted / bucket.attempted) * 100),\n }))\n .sort((a, b) => b.rate - a.rate || b.attempted - a.attempted);\n }, [submissions]);\n\n const activityData = useMemo<ActivityDatum[]>(() => {\n const buckets = new Map<number, ActivityDatum>();\n const add = (iso: string, key: 'submissions' | 'enrollments') => {\n const { key: bucketKey, label } = weekBucket(iso);\n const existing = buckets.get(bucketKey) ?? { label, submissions: 0, enrollments: 0 };\n existing[key] += 1;\n buckets.set(bucketKey, existing);\n };\n submissions.forEach((s) => add(s.createdAt, 'submissions'));\n enrollments.forEach((e) => add(e.createdAt, 'enrollments'));\n return [...buckets.entries()]\n .sort((a, b) => a[0] - b[0])\n .slice(-8)\n .map(([, v]) => v);\n }, [submissions, enrollments]);\n\n const hasActivity =\n enrollments.length > 0 ||\n submissions.length > 0 ||\n sessions.length > 0 ||\n certificates.length > 0;\n\n const learnerLoading =\n enrollmentsQ.loading || submissionsQ.loading || sessionsQ.loading || certificatesQ.loading;\n const learnerError =\n enrollmentsQ.error || submissionsQ.error || sessionsQ.error || certificatesQ.error;\n const showLoading = learnerLoading && !hasActivity;\n\n const workspaceOverview = (\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Workspace overview</h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n Live content available across the TechnoSpam learning workspace right now.\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard label=\"Published courses\" value={coursesTotal} icon={Library} tone=\"blue\" />\n <StatCard label=\"Published problems\" value={problemsTotal} icon={Code2} tone=\"indigo\" />\n <StatCard label=\"Hackathons\" value={hackathonsTotal} icon={Trophy} tone=\"purple\" />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title=\"Analytics\"\n description=\"Your real learning performance across courses, problems, mentoring and certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title=\"Sign in to view your analytics\"\n description=\"Your personal learning metrics appear here once you're signed in.\"\n />\n {workspaceOverview}\n </div>\n ) : showLoading ? (\n <div className=\"space-y-6\">\n <LoadingGrid count={4} height=\"h-24\" />\n <LoadingRows count={2} height=\"h-72\" />\n </div>\n ) : learnerError && !hasActivity ? (\n <ErrorView title=\"Couldn't load your analytics\" error={learnerError} />\n ) : (\n <div className=\"space-y-8\">\n <DateRangeControls\n start={rangeStart}\n end={rangeEnd}\n onStart={setRangeStart}\n onEnd={setRangeEnd}\n onPreset={applyPreset}\n onClear={clearRange}\n />\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard\n label=\"Courses enrolled\"\n value={enrollments.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label=\"Courses completed\"\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label=\"Avg progress\"\n value={`${metrics.avgProgress}%`}\n icon={TrendingUp}\n tone=\"purple\"\n />\n <StatCard\n label=\"Problems solved\"\n value={metrics.problemsSolved}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Acceptance rate\"\n value={`${metrics.acceptanceRate}%`}\n icon={Percent}\n tone=\"teal\"\n />\n <StatCard label=\"Mentor sessions\" value={sessions.length} icon={Users} tone=\"blue\" />\n <StatCard label=\"Certificates\" value={certificates.length} icon={Award} tone=\"amber\" />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title=\"No learning activity yet\"\n description=\"Enroll in a course or solve a problem and your progress charts will build here. TechnoSpam is pre-launch — start learning to see your data.\"\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>Browse courses</Button>\n }\n />\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Enrollments by status\"\n subtitle=\"How your courses are progressing\"\n icon={BookOpen}\n tone=\"blue\"\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel=\"No enrollments yet\"\n >\n <PieChart>\n <Pie\n data={enrollmentsByStatus}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={60}\n outerRadius={92}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {enrollmentsByStatus.map((d) => (\n <Cell key={d.name} fill={d.color} />\n ))}\n </Pie>\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n </PieChart>\n </ChartCard>\n\n <ChartCard\n title=\"Submissions by verdict\"\n subtitle=\"Outcomes across your attempts\"\n icon={Code2}\n tone=\"purple\"\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel=\"No submissions yet\"\n >\n <BarChart\n data={submissionsByVerdict}\n margin={{ top: 8, right: 8, left: -18, bottom: 0 }}\n >\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip\n contentStyle={TOOLTIP_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-accent-primary)', fillOpacity: 0.08 }}\n />\n <Bar dataKey=\"count\" name=\"Submissions\" radius={[6, 6, 0, 0]} maxBarSize={56}>\n {submissionsByVerdict.map((d) => (\n <Cell key={d.label} fill={d.color} />\n ))}\n </Bar>\n </BarChart>\n </ChartCard>\n </div>\n\n <TopicAcceptanceCard data={acceptedRateByTopic} />\n\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Activity}\n title=\"Learning activity over time\"\n subtitle=\"Submissions and enrollments per week\"\n tone=\"teal\"\n />\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n No dated activity yet\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart\n data={activityData}\n margin={{ top: 8, right: 12, left: -18, bottom: 0 }}\n >\n <defs>\n <linearGradient id=\"tec-grad-submissions\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0}\n />\n </linearGradient>\n <linearGradient id=\"tec-grad-enrollments\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n <Area\n type=\"monotone\"\n dataKey=\"submissions\"\n name=\"Submissions\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-submissions)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"enrollments\"\n name=\"Enrollments\"\n stroke=\"var(--color-accent-teal)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-enrollments)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </div>\n )}\n\n {workspaceOverview}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -99,7 +99,7 @@ function V({ onAdded: t }) {
99
99
  });
100
100
  }
101
101
  function H({ onAdded: t }) {
102
- let { currentUser: n } = e(), r = n?.id, { data: i } = l(r ? { learnerId: r } : {}), [a, { loading: c }] = s(), u = (i?.tecListEnrollments.items ?? []).filter((e) => e.status === "COMPLETED");
102
+ let { currentUser: n } = e(), r = n?.id, { data: i } = l(r ? { learnerId: r } : {}), [a, { loading: c }] = s(), u = (i?.tecListEnrollments?.items ?? []).filter((e) => e.status === "COMPLETED");
103
103
  return u.length === 0 ? /* @__PURE__ */ O("p", {
104
104
  className: "text-xs text-text-muted",
105
105
  children: "No completed courses yet."
@@ -126,7 +126,7 @@ function H({ onAdded: t }) {
126
126
  });
127
127
  }
128
128
  function U({ onAdded: t }) {
129
- let { currentUser: n } = e(), r = n?.id, { data: i } = u({}), a = i?.tecListHackathons.items ?? [], [c, l] = g(""), { data: d } = m(c), [f, { loading: p }] = s(), h = (d?.tecListTeams.items ?? []).filter((e) => (e.status === "SUBMITTED" || e.status === "WINNER") && (e.captainId === r || e.memberIds.includes(r ?? "")));
129
+ let { currentUser: n } = e(), r = n?.id, { data: i } = u({}), a = i?.tecListHackathons?.items ?? [], [c, l] = g(""), { data: d } = m(c), [f, { loading: p }] = s(), h = (d?.tecListTeams?.items ?? []).filter((e) => (e.status === "SUBMITTED" || e.status === "WINNER") && (e.captainId === r || e.memberIds.includes(r ?? "")));
130
130
  return /* @__PURE__ */ k("div", {
131
131
  className: "space-y-2",
132
132
  children: [
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsPortfolioPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsPortfolioPagePage.tsx"],"sourcesContent":["/**\n * Learner Portfolio (BOFF-4432).\n *\n * Aggregates real completed work — issued certificates, completed courses,\n * and submitted hackathon projects — into a single showcase the learner\n * controls. Every item's title/description/link/completion date is copied\n * server-side from the real source record at add-time; nothing here is\n * free-form/fabricated.\n *\n * DEFERRED SCOPE (documented, not silently dropped): the backend already\n * exposes a public, unauthenticated `tecPublicPortfolio(slug)` field\n * (@rbac public: true) that returns real published-portfolio data. This\n * page generates the slug and lets the learner copy the public link, but\n * there is no unauthenticated *page* yet that renders it — the MFE only\n * mounts inside the authenticated app shell, and alpha gates every web\n * hostname behind Cloudflare Access. Standing up a real public viewer\n * needs app-shell + CF Access allow-list work coordinated with\n * infra/tf-cloudflare, which is out of scope for this pass.\n */\nimport { useState } from 'react';\nimport {\n Award,\n Briefcase,\n Check,\n Copy,\n ExternalLink,\n GraduationCap,\n Link2,\n Plus,\n Trash2,\n Trophy,\n X,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { EmptyView, ErrorView, LoadingRows, PageLayout } from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n type TecPortfolioItem,\n type TecPortfolioItemKind,\n useAddPortfolioItem,\n useCertificatesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useMyPortfolioQuery,\n usePublishPortfolio,\n useRemovePortfolioItem,\n useTeamsQuery,\n useUnpublishPortfolio,\n} from '../api/technospamApi';\nimport { formatDate } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst KIND_ICON: Record<TecPortfolioItemKind, typeof Award> = {\n HACKATHON_PROJECT: Trophy,\n CERTIFICATE: Award,\n COURSE_COMPLETION: GraduationCap,\n};\n\nconst KIND_LABEL: Record<TecPortfolioItemKind, string> = {\n HACKATHON_PROJECT: 'Hackathon project',\n CERTIFICATE: 'Certificate',\n COURSE_COMPLETION: 'Completed course',\n};\n\nfunction ItemCard({ item, onRemove }: { item: TecPortfolioItem; onRemove: () => void }) {\n const Icon = KIND_ICON[item.kind];\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-purple/15 text-accent-purple ring-1 ring-accent-purple/20\">\n <Icon size={18} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">{item.title}</span>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {KIND_LABEL[item.kind]}\n </span>\n </div>\n {item.description ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{item.description}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>Completed {formatDate(item.completionDate)}</span>\n {item.link ? (\n <a\n href={item.link}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-accent-primary hover:underline\"\n >\n <ExternalLink size={12} /> View\n </a>\n ) : null}\n </div>\n </div>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 text-status-error-text hover:text-status-error-text\"\n onClick={onRemove}\n >\n <Trash2 size={14} />\n </Button>\n </GlassCard>\n );\n}\n\nfunction AddFromCertificates({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useCertificatesQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const certificates = (data?.tecListCertificates ?? []).filter((c) => c.status === 'ISSUED');\n\n if (certificates.length === 0) {\n return <p className=\"text-xs text-text-muted\">No issued certificates yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {certificates.map((c) => (\n <div\n key={c.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {c.course?.title ?? 'Certificate'} · Serial {c.serial}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'CERTIFICATE', sourceId: c.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromCourses({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const completed = (data?.tecListEnrollments.items ?? []).filter((e) => e.status === 'COMPLETED');\n\n if (completed.length === 0) {\n return <p className=\"text-xs text-text-muted\">No completed courses yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {completed.map((e) => (\n <div\n key={e.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'COURSE_COMPLETION', sourceId: e.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromHackathons({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data: hackathonData } = useHackathonsQuery({});\n const hackathons = hackathonData?.tecListHackathons.items ?? [];\n const [hackathonId, setHackathonId] = useState<string>('');\n const { data: teamsData } = useTeamsQuery(hackathonId);\n const [addItem, { loading }] = useAddPortfolioItem();\n\n const myTeams = (teamsData?.tecListTeams.items ?? []).filter(\n (t) =>\n (t.status === 'SUBMITTED' || t.status === 'WINNER') &&\n (t.captainId === learnerId || t.memberIds.includes(learnerId ?? ''))\n );\n\n return (\n <div className=\"space-y-2\">\n <Select value={hackathonId} onValueChange={setHackathonId}>\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder=\"Choose a hackathon\" />\n </SelectTrigger>\n <SelectContent>\n {hackathons.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {hackathonId && myTeams.length === 0 ? (\n <p className=\"text-xs text-text-muted\">No submitted team of yours in this hackathon.</p>\n ) : null}\n {myTeams.map((t) => (\n <div\n key={t.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {t.projectName || t.name}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'HACKATHON_PROJECT', sourceId: t.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nexport function ApplicationsPortfolioPagePage() {\n const { data, loading, error, refetch } = useMyPortfolioQuery();\n const [removeItem] = useRemovePortfolioItem();\n const [publish, { loading: publishing }] = usePublishPortfolio();\n const [unpublish, { loading: unpublishing }] = useUnpublishPortfolio();\n const [showAdd, setShowAdd] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const portfolio = data?.tecMyPortfolio;\n const items = portfolio?.items ?? [];\n\n const publicUrl = portfolio?.publicSlug\n ? `${typeof window !== 'undefined' ? window.location.origin : ''}/technospam/portfolio/${portfolio.publicSlug}`\n : null;\n\n const handleRemove = async (id: string, title: string) => {\n const confirmed = await nativeConfirm({\n title: 'Remove from portfolio',\n message: `Remove \"${title}\" from your portfolio? This does not delete the underlying record.`,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await removeItem({ variables: { id } });\n if (res.data?.tecRemovePortfolioItem) notify.success('Removed from portfolio');\n };\n\n const handleTogglePublish = async () => {\n if (!portfolio) return;\n if (portfolio.isPublished) {\n const res = await unpublish();\n if (res.data?.tecUnpublishPortfolio) notify.success('Portfolio unpublished');\n } else {\n const res = await publish();\n if (res.data?.tecPublishPortfolio) notify.success('Portfolio published');\n }\n };\n\n const handleCopyLink = async () => {\n if (!publicUrl) return;\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <PageLayout\n hero\n title=\"Portfolio\"\n description=\"Showcase your real completed courses, certificates, and hackathon projects.\"\n actions={\n portfolio ? (\n <Button\n variant={portfolio.isPublished ? 'outline' : 'default'}\n onClick={() => void handleTogglePublish()}\n disabled={publishing || unpublishing || (!portfolio.isPublished && items.length === 0)}\n >\n {portfolio.isPublished ? 'Unpublish' : 'Publish'}\n </Button>\n ) : null\n }\n >\n {portfolio?.isPublished && publicUrl ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-xs text-text-secondary\">\n <Link2 size={14} className=\"shrink-0 text-accent-teal\" />\n <span className=\"truncate\">{publicUrl}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void handleCopyLink()}>\n {copied ? <Check size={14} /> : <Copy size={14} />} {copied ? 'Copied' : 'Copy link'}\n </Button>\n </GlassCard>\n ) : null}\n\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Your items</h2>\n <Button\n size=\"sm\"\n variant={showAdd ? 'outline' : 'default'}\n onClick={() => setShowAdd((v) => !v)}\n >\n {showAdd ? <X size={14} /> : <Plus size={14} />} {showAdd ? 'Close' : 'Add item'}\n </Button>\n </div>\n\n {showAdd ? (\n <GlassCard className=\"mb-6 space-y-4 p-4\">\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From certificates\n </h3>\n <AddFromCertificates onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From completed courses\n </h3>\n <AddFromCourses onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From hackathon projects\n </h3>\n <AddFromHackathons onAdded={() => void refetch()} />\n </div>\n </GlassCard>\n ) : null}\n\n {loading && !portfolio ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your portfolio\" error={error} />\n ) : items.length === 0 ? (\n <EmptyView\n icon={<Briefcase size={26} />}\n title=\"Your portfolio is empty\"\n description=\"Add a certificate, completed course, or submitted hackathon project to start building your showcase.\"\n action={\n <Button onClick={() => setShowAdd(true)}>\n <Plus size={15} /> Add your first item\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((item) => (\n <ItemCard\n key={item.id}\n item={item}\n onRemove={() => void handleRemove(item.id, item.title)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApplicationsPortfolioPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsPortfolioPagePage.tsx"],"sourcesContent":["/**\n * Learner Portfolio (BOFF-4432).\n *\n * Aggregates real completed work — issued certificates, completed courses,\n * and submitted hackathon projects — into a single showcase the learner\n * controls. Every item's title/description/link/completion date is copied\n * server-side from the real source record at add-time; nothing here is\n * free-form/fabricated.\n *\n * DEFERRED SCOPE (documented, not silently dropped): the backend already\n * exposes a public, unauthenticated `tecPublicPortfolio(slug)` field\n * (@rbac public: true) that returns real published-portfolio data. This\n * page generates the slug and lets the learner copy the public link, but\n * there is no unauthenticated *page* yet that renders it — the MFE only\n * mounts inside the authenticated app shell, and alpha gates every web\n * hostname behind Cloudflare Access. Standing up a real public viewer\n * needs app-shell + CF Access allow-list work coordinated with\n * infra/tf-cloudflare, which is out of scope for this pass.\n */\nimport { useState } from 'react';\nimport {\n Award,\n Briefcase,\n Check,\n Copy,\n ExternalLink,\n GraduationCap,\n Link2,\n Plus,\n Trash2,\n Trophy,\n X,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { EmptyView, ErrorView, LoadingRows, PageLayout } from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n type TecPortfolioItem,\n type TecPortfolioItemKind,\n useAddPortfolioItem,\n useCertificatesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useMyPortfolioQuery,\n usePublishPortfolio,\n useRemovePortfolioItem,\n useTeamsQuery,\n useUnpublishPortfolio,\n} from '../api/technospamApi';\nimport { formatDate } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst KIND_ICON: Record<TecPortfolioItemKind, typeof Award> = {\n HACKATHON_PROJECT: Trophy,\n CERTIFICATE: Award,\n COURSE_COMPLETION: GraduationCap,\n};\n\nconst KIND_LABEL: Record<TecPortfolioItemKind, string> = {\n HACKATHON_PROJECT: 'Hackathon project',\n CERTIFICATE: 'Certificate',\n COURSE_COMPLETION: 'Completed course',\n};\n\nfunction ItemCard({ item, onRemove }: { item: TecPortfolioItem; onRemove: () => void }) {\n const Icon = KIND_ICON[item.kind];\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-purple/15 text-accent-purple ring-1 ring-accent-purple/20\">\n <Icon size={18} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">{item.title}</span>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {KIND_LABEL[item.kind]}\n </span>\n </div>\n {item.description ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{item.description}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>Completed {formatDate(item.completionDate)}</span>\n {item.link ? (\n <a\n href={item.link}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-accent-primary hover:underline\"\n >\n <ExternalLink size={12} /> View\n </a>\n ) : null}\n </div>\n </div>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 text-status-error-text hover:text-status-error-text\"\n onClick={onRemove}\n >\n <Trash2 size={14} />\n </Button>\n </GlassCard>\n );\n}\n\nfunction AddFromCertificates({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useCertificatesQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const certificates = (data?.tecListCertificates ?? []).filter((c) => c.status === 'ISSUED');\n\n if (certificates.length === 0) {\n return <p className=\"text-xs text-text-muted\">No issued certificates yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {certificates.map((c) => (\n <div\n key={c.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {c.course?.title ?? 'Certificate'} · Serial {c.serial}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'CERTIFICATE', sourceId: c.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromCourses({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const completed = (data?.tecListEnrollments?.items ?? []).filter((e) => e.status === 'COMPLETED');\n\n if (completed.length === 0) {\n return <p className=\"text-xs text-text-muted\">No completed courses yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {completed.map((e) => (\n <div\n key={e.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'COURSE_COMPLETION', sourceId: e.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromHackathons({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data: hackathonData } = useHackathonsQuery({});\n const hackathons = hackathonData?.tecListHackathons?.items ?? [];\n const [hackathonId, setHackathonId] = useState<string>('');\n const { data: teamsData } = useTeamsQuery(hackathonId);\n const [addItem, { loading }] = useAddPortfolioItem();\n\n const myTeams = (teamsData?.tecListTeams?.items ?? []).filter(\n (t) =>\n (t.status === 'SUBMITTED' || t.status === 'WINNER') &&\n (t.captainId === learnerId || t.memberIds.includes(learnerId ?? ''))\n );\n\n return (\n <div className=\"space-y-2\">\n <Select value={hackathonId} onValueChange={setHackathonId}>\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder=\"Choose a hackathon\" />\n </SelectTrigger>\n <SelectContent>\n {hackathons.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {hackathonId && myTeams.length === 0 ? (\n <p className=\"text-xs text-text-muted\">No submitted team of yours in this hackathon.</p>\n ) : null}\n {myTeams.map((t) => (\n <div\n key={t.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {t.projectName || t.name}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'HACKATHON_PROJECT', sourceId: t.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nexport function ApplicationsPortfolioPagePage() {\n const { data, loading, error, refetch } = useMyPortfolioQuery();\n const [removeItem] = useRemovePortfolioItem();\n const [publish, { loading: publishing }] = usePublishPortfolio();\n const [unpublish, { loading: unpublishing }] = useUnpublishPortfolio();\n const [showAdd, setShowAdd] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const portfolio = data?.tecMyPortfolio;\n const items = portfolio?.items ?? [];\n\n const publicUrl = portfolio?.publicSlug\n ? `${typeof window !== 'undefined' ? window.location.origin : ''}/technospam/portfolio/${portfolio.publicSlug}`\n : null;\n\n const handleRemove = async (id: string, title: string) => {\n const confirmed = await nativeConfirm({\n title: 'Remove from portfolio',\n message: `Remove \"${title}\" from your portfolio? This does not delete the underlying record.`,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await removeItem({ variables: { id } });\n if (res.data?.tecRemovePortfolioItem) notify.success('Removed from portfolio');\n };\n\n const handleTogglePublish = async () => {\n if (!portfolio) return;\n if (portfolio.isPublished) {\n const res = await unpublish();\n if (res.data?.tecUnpublishPortfolio) notify.success('Portfolio unpublished');\n } else {\n const res = await publish();\n if (res.data?.tecPublishPortfolio) notify.success('Portfolio published');\n }\n };\n\n const handleCopyLink = async () => {\n if (!publicUrl) return;\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <PageLayout\n hero\n title=\"Portfolio\"\n description=\"Showcase your real completed courses, certificates, and hackathon projects.\"\n actions={\n portfolio ? (\n <Button\n variant={portfolio.isPublished ? 'outline' : 'default'}\n onClick={() => void handleTogglePublish()}\n disabled={publishing || unpublishing || (!portfolio.isPublished && items.length === 0)}\n >\n {portfolio.isPublished ? 'Unpublish' : 'Publish'}\n </Button>\n ) : null\n }\n >\n {portfolio?.isPublished && publicUrl ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-xs text-text-secondary\">\n <Link2 size={14} className=\"shrink-0 text-accent-teal\" />\n <span className=\"truncate\">{publicUrl}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void handleCopyLink()}>\n {copied ? <Check size={14} /> : <Copy size={14} />} {copied ? 'Copied' : 'Copy link'}\n </Button>\n </GlassCard>\n ) : null}\n\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Your items</h2>\n <Button\n size=\"sm\"\n variant={showAdd ? 'outline' : 'default'}\n onClick={() => setShowAdd((v) => !v)}\n >\n {showAdd ? <X size={14} /> : <Plus size={14} />} {showAdd ? 'Close' : 'Add item'}\n </Button>\n </div>\n\n {showAdd ? (\n <GlassCard className=\"mb-6 space-y-4 p-4\">\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From certificates\n </h3>\n <AddFromCertificates onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From completed courses\n </h3>\n <AddFromCourses onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From hackathon projects\n </h3>\n <AddFromHackathons onAdded={() => void refetch()} />\n </div>\n </GlassCard>\n ) : null}\n\n {loading && !portfolio ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your portfolio\" error={error} />\n ) : items.length === 0 ? (\n <EmptyView\n icon={<Briefcase size={26} />}\n title=\"Your portfolio is empty\"\n description=\"Add a certificate, completed course, or submitted hackathon project to start building your showcase.\"\n action={\n <Button onClick={() => setShowAdd(true)}>\n <Plus size={15} /> Add your first item\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((item) => (\n <ItemCard\n key={item.id}\n item={item}\n onRemove={() => void handleRemove(item.id, item.title)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsProblemDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"names":[],"mappings":"AAiNA,wBAAgB,kCAAkC,gCAyUjD"}
1
+ {"version":3,"file":"ApplicationsProblemDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"names":[],"mappings":"AA8EA,wBAAgB,kCAAkC,gCAyUjD"}