feat: Display problem titles and links in submission lists and persist code drafts in local storage.
This commit is contained in:
parent
c1ac522574
commit
0a9c5aa5c9
@ -141,7 +141,11 @@ async def get_my_submissions(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
query = select(Submission).where(Submission.user_id == current_user.id)
|
||||
query = (
|
||||
select(Submission)
|
||||
.options(selectinload(Submission.problem))
|
||||
.where(Submission.user_id == current_user.id)
|
||||
)
|
||||
|
||||
if problem_id:
|
||||
query = query.where(Submission.problem_id == problem_id)
|
||||
@ -151,7 +155,25 @@ async def get_my_submissions(
|
||||
query = query.order_by(Submission.created_at.desc())
|
||||
|
||||
result = await db.execute(query)
|
||||
return result.scalars().all()
|
||||
submissions = result.scalars().all()
|
||||
|
||||
# Add problem_title to each submission
|
||||
return [
|
||||
SubmissionListResponse(
|
||||
id=s.id,
|
||||
problem_id=s.problem_id,
|
||||
problem_title=s.problem.title if s.problem else None,
|
||||
contest_id=s.contest_id,
|
||||
language_name=s.language_name,
|
||||
status=s.status,
|
||||
score=s.score,
|
||||
total_points=s.total_points,
|
||||
tests_passed=s.tests_passed,
|
||||
tests_total=s.tests_total,
|
||||
created_at=s.created_at,
|
||||
)
|
||||
for s in submissions
|
||||
]
|
||||
|
||||
|
||||
@router.get("/problem/{problem_id}", response_model=list[SubmissionListResponse])
|
||||
|
||||
@ -33,6 +33,8 @@ class SubmissionResponse(BaseModel):
|
||||
class SubmissionListResponse(BaseModel):
|
||||
id: int
|
||||
problem_id: int
|
||||
problem_title: str | None = None
|
||||
contest_id: int | None = None
|
||||
language_name: str | None
|
||||
status: str
|
||||
score: int
|
||||
|
||||
@ -220,6 +220,39 @@ export default function ProblemPage() {
|
||||
const selectedLanguage = languages.find((l) => l.id.toString() === selectedLanguageId);
|
||||
const isJudging = lastSubmission?.status === "pending" || lastSubmission?.status === "judging";
|
||||
|
||||
// localStorage key for this problem's draft
|
||||
const draftKey = `code_draft_problem_${problemId}`;
|
||||
|
||||
// Load saved draft from localStorage
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const saved = localStorage.getItem(draftKey);
|
||||
if (saved) {
|
||||
const draft = JSON.parse(saved);
|
||||
if (draft.code) setCode(draft.code);
|
||||
if (draft.languageId) setSelectedLanguageId(draft.languageId);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
}, [draftKey]);
|
||||
|
||||
// Save draft to localStorage when code or language changes
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined" && (code || selectedLanguageId)) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
draftKey,
|
||||
JSON.stringify({ code, languageId: selectedLanguageId })
|
||||
);
|
||||
} catch (e) {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
}, [code, selectedLanguageId, draftKey]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.getProblem(problemId),
|
||||
@ -230,7 +263,31 @@ export default function ProblemPage() {
|
||||
setProblem(problemData);
|
||||
setLanguages(languagesData);
|
||||
setSubmissions(submissionsData);
|
||||
// Default to Python
|
||||
|
||||
// Check if we have a saved language preference
|
||||
const savedDraft = typeof window !== "undefined"
|
||||
? localStorage.getItem(draftKey)
|
||||
: null;
|
||||
|
||||
if (savedDraft) {
|
||||
try {
|
||||
const draft = JSON.parse(savedDraft);
|
||||
if (draft.languageId) {
|
||||
// Verify the saved language still exists
|
||||
const savedLang = languagesData.find(
|
||||
(l) => l.id.toString() === draft.languageId
|
||||
);
|
||||
if (savedLang) {
|
||||
setSelectedLanguageId(draft.languageId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fall through to default
|
||||
}
|
||||
}
|
||||
|
||||
// Default to Python if no saved preference
|
||||
const python = languagesData.find((l) =>
|
||||
l.name.toLowerCase().includes("python")
|
||||
);
|
||||
@ -241,7 +298,7 @@ export default function ProblemPage() {
|
||||
toast.error("Ошибка загрузки задачи");
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [problemId]);
|
||||
}, [problemId, draftKey]);
|
||||
|
||||
// Poll for submission status
|
||||
useEffect(() => {
|
||||
@ -304,7 +361,11 @@ export default function ProblemPage() {
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (!confirm("Очистить код? Черновик будет удалён.")) return;
|
||||
setCode("");
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem(draftKey);
|
||||
}
|
||||
toast.info("Код очищен");
|
||||
};
|
||||
|
||||
|
||||
@ -333,6 +333,7 @@ export default function SubmissionsPage() {
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--color-border)]">
|
||||
<TableHead className="w-20 font-mono text-[var(--color-neon-cyan)]">ID</TableHead>
|
||||
<TableHead className="font-mono text-[var(--color-neon-cyan)]">Задача</TableHead>
|
||||
<TableHead className="font-mono text-[var(--color-neon-cyan)]">Время</TableHead>
|
||||
<TableHead className="font-mono text-[var(--color-neon-cyan)]">Язык</TableHead>
|
||||
<TableHead className="text-center font-mono text-[var(--color-neon-cyan)]">Статус</TableHead>
|
||||
@ -362,6 +363,18 @@ export default function SubmissionsPage() {
|
||||
<TableCell className="font-mono text-muted-foreground">
|
||||
<span className="text-[var(--color-neon-purple)]">#</span>{submission.id}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{submission.problem_title ? (
|
||||
<Link
|
||||
href={`/contests/${submission.contest_id}/problems/${submission.problem_id}`}
|
||||
className="text-[var(--color-neon-green)] hover:underline font-medium"
|
||||
>
|
||||
{submission.problem_title}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2 font-mono text-sm">
|
||||
<Clock className="h-4 w-4 text-[var(--color-neon-cyan)]" />
|
||||
|
||||
@ -93,6 +93,8 @@ export interface Submission {
|
||||
export interface SubmissionListItem {
|
||||
id: number;
|
||||
problem_id: number;
|
||||
problem_title: string | null;
|
||||
contest_id: number | null;
|
||||
language_name: string | null;
|
||||
status: string;
|
||||
score: number;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user