From 3729c1d1e166e80eb1b7e1ab5f208c3e08055397 Mon Sep 17 00:00:00 2001 From: Prakhar Ojha <68009969+prakhar728@users.noreply.github.com> Date: Sat, 4 Apr 2026 03:31:09 +0530 Subject: [PATCH] Feat: Now the submissions page shows more metadata(fixes #12) - api/routes.py: Stamps _submitted_at on every submission at receipt time New admin-only GET /submissions endpoin lib/types.ts: Added SubmissionMeta interface. - lib/api.ts: Added getSubmissions() call. - dashboard/[id]/page.tsx: Fetches real submission metadata on admin token load and after runAnalysis --- api/routes.py | 25 ++++++++++ client/apps/web/app/dashboard/[id]/page.tsx | 53 ++++++++++++++------- client/apps/web/lib/api.ts | 9 ++++ client/apps/web/lib/types.ts | 9 ++++ 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/api/routes.py b/api/routes.py index 2d134ea..8787600 100644 --- a/api/routes.py +++ b/api/routes.py @@ -4,6 +4,7 @@ import secrets import traceback import uuid +from datetime import datetime from functools import partial from fastapi import APIRouter, File, HTTPException, Request, UploadFile @@ -327,6 +328,7 @@ async def submit(submission: dict, request: Request): sid = validated.submission_id submission = validated.model_dump() # ensure stored dict is normalized + submission["_submitted_at"] = datetime.utcnow().isoformat() + "Z" _submissions[instance_id][sid] = submission token_info["submission_ids"].add(sid) @@ -361,6 +363,29 @@ def get_my_submissions(request: Request): return {"submission_ids": list(token_info["submission_ids"])} +@router.get("/submissions") +def get_submissions(request: Request): + """Return per-submission metadata for the instance. Admin only. No raw content.""" + token_info = _resolve_token(request) + if token_info["role"] != "admin": + raise HTTPException(status_code=403, detail="Only admin can view submission metadata") + + instance_id = token_info["instance_id"] + subs = _submissions.get(instance_id, {}) + + meta = [] + for sub in subs.values(): + meta.append({ + "submission_id": sub.get("submission_id", ""), + "submitted_at": sub.get("_submitted_at"), + "has_text": bool(sub.get("idea_text")), + "has_file": bool(sub.get("idea_file")), + "has_repo": bool(sub.get("repo_summary")), + }) + + return {"submissions": meta} + + @router.post("/upload") async def upload_file(request: Request): """ diff --git a/client/apps/web/app/dashboard/[id]/page.tsx b/client/apps/web/app/dashboard/[id]/page.tsx index 6768f15..d6d65d1 100644 --- a/client/apps/web/app/dashboard/[id]/page.tsx +++ b/client/apps/web/app/dashboard/[id]/page.tsx @@ -20,7 +20,7 @@ import { ProcurementScorecard } from "@/components/procurement-scorecard" import { NegotiationPanel } from "@/components/negotiation-panel" import { ReleaseTokenCard } from "@/components/release-token-card" import { api } from "@/lib/api" -import type { DisplayMap, NoveltyResult, ProcurementResult } from "@/lib/types" +import type { DisplayMap, NoveltyResult, ProcurementResult, SubmissionMeta } from "@/lib/types" import { cn } from "@workspace/ui/lib/utils" import Link from "next/link" import { ArrowLeft } from "@phosphor-icons/react" @@ -37,6 +37,7 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string // Hackathon state const [results, setResults] = React.useState([]) const [display, setDisplay] = React.useState({}) + const [submissionMetas, setSubmissionMetas] = React.useState([]) const [triggering, setTriggering] = React.useState(false) const [triggered, setTriggered] = React.useState(false) @@ -93,6 +94,9 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string setTriggered(true) } }).catch(() => {}) + api.getSubmissions(adminToken).then((r) => { + if (r.submissions.length > 0) setSubmissionMetas(r.submissions) + }).catch(() => {}) } }).catch(() => {}) }, [adminToken]) @@ -101,8 +105,12 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string if (!adminToken) return setTriggering(true) await api.trigger(adminToken) - const r = await api.getAllResults(adminToken) + const [r, s] = await Promise.all([ + api.getAllResults(adminToken), + api.getSubmissions(adminToken), + ]) setResults(r.results) + setSubmissionMetas(s.submissions) setTriggered(true) setTriggering(false) } @@ -422,20 +430,33 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string — )) - : Array.from({ length: subCount }).map((_, i) => ( - - {i + 1} - - {new Date(Date.now() - i * 3600000).toLocaleString()} - - - — - - - received - - - )) + : submissionMetas.length > 0 + ? submissionMetas.map((s, i) => ( + + {i + 1} + + {s.submitted_at ? new Date(s.submitted_at).toLocaleString() : "—"} + + {s.has_text ? : } + {s.has_file ? : } + {s.has_repo ? : } + + received + + + )) + : Array.from({ length: subCount }).map((_, i) => ( + + {i + 1} + — + — + — + — + + received + + + )) } diff --git a/client/apps/web/lib/api.ts b/client/apps/web/lib/api.ts index 8f204d6..9f6f924 100644 --- a/client/apps/web/lib/api.ts +++ b/client/apps/web/lib/api.ts @@ -15,6 +15,7 @@ import type { ReleaseToken, SettlementState, SkillCard, + SubmissionMeta, SubmitResponse, SupplierSubmission, } from "./types" @@ -482,6 +483,14 @@ export const api = { return get("/results", token) }, + getSubmissions: async (token: string): Promise<{ submissions: SubmissionMeta[] }> => { + if (MOCK) { + await delay(300) + return { submissions: [] } + } + return get("/submissions", token) + }, + // --- Procurement --- submitDataset: async ( token: string, diff --git a/client/apps/web/lib/types.ts b/client/apps/web/lib/types.ts index e73f036..0a0dcc6 100644 --- a/client/apps/web/lib/types.ts +++ b/client/apps/web/lib/types.ts @@ -60,6 +60,15 @@ export interface SubmitResponse { threshold?: number } +// /submissions +export interface SubmissionMeta { + submission_id: string + submitted_at: string | null + has_text: boolean + has_file: boolean + has_repo: boolean +} + // /results export interface NoveltyResult { submission_id: string