Skip to content

Commit 5d20ce9

Browse files
Edwin ChanEdwin Chan
authored andcommitted
sss
1 parent 4987da0 commit 5d20ce9

12 files changed

Lines changed: 40 additions & 30 deletions

File tree

app/admin/create/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export default function CreateSetPage() {
131131
const [slug, setSlug] = useState("");
132132
const [slugManual, setSlugManual] = useState(false);
133133
const [description, setDescription] = useState("");
134-
const [order, setOrder] = useState(1);
134+
const [order, setOrder] = useState("1");
135135
const [difficulty, setDifficulty] = useState(1);
136136
const [status, setStatus] = useState<"DRAFT" | "PUBLISHED">("DRAFT");
137137
const [topicTags, setTopicTags] = useState("");
@@ -376,10 +376,9 @@ export default function CreateSetPage() {
376376
<label htmlFor="set-order">Order</label>
377377
<input
378378
id="set-order"
379-
type="number"
380-
min={0}
379+
type="text"
381380
value={order}
382-
onChange={(e) => setOrder(Number(e.target.value))}
381+
onChange={(e) => setOrder(e.target.value)}
383382
/>
384383
</div>
385384

app/admin/sets/[id]/set-edit-form.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type SetData = {
3939
title: string;
4040
slug: string;
4141
description: string;
42-
order: number;
42+
order: string;
4343
status: "DRAFT" | "PUBLISHED" | "ARCHIVED";
4444
difficulty: number;
4545
topicTags: string[];
@@ -454,10 +454,9 @@ export function SetEditForm({ set }: { set: SetData }) {
454454
<span className="form-label">Order</span>
455455
<input
456456
className="form-input"
457-
type="number"
458-
min={1}
457+
type="text"
459458
value={order}
460-
onChange={(e) => setOrder(Number(e.target.value))}
459+
onChange={(e) => setOrder(e.target.value)}
461460
/>
462461
</label>
463462

app/api/admin/create-set/route.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,16 @@ export async function POST(req: Request) {
6363
);
6464
}
6565

66-
let finalOrder = order;
66+
let finalOrder = order ?? "";
6767
if (!finalOrder) {
68-
const maxOrderResult = await prisma.problemSet.aggregate({ _max: { order: true } });
69-
finalOrder = (maxOrderResult._max.order ?? 0) + 1;
68+
const existingSets = await prisma.problemSet.findMany({
69+
select: { order: true },
70+
orderBy: { order: "desc" },
71+
take: 1,
72+
});
73+
const maxOrder = existingSets[0]?.order ?? "0";
74+
const parsed = parseInt(maxOrder, 10);
75+
finalOrder = String((Number.isFinite(parsed) ? parsed : 0) + 1);
7076
}
7177

7278
let problemFileId: string | null = null;

app/problem-sets/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type SetRow = {
2020
id: string;
2121
slug: string;
2222
title: string;
23-
order: number;
23+
order: string;
2424
createdAt: Date;
2525
categories: string[];
2626
tags: string[];
@@ -250,30 +250,30 @@ export default async function ProblemSetsPage({
250250

251251
const orderedRows = [...setRows].sort((a, b) => {
252252
if (sortMode === "solved") {
253-
return b.solvedCount - a.solvedCount || a.order - b.order || a.title.localeCompare(b.title);
253+
return b.solvedCount - a.solvedCount || a.order.localeCompare(b.order) || a.title.localeCompare(b.title);
254254
}
255255

256256
if (sortMode === "name") {
257-
return a.title.localeCompare(b.title) || a.order - b.order;
257+
return a.title.localeCompare(b.title) || a.order.localeCompare(b.order);
258258
}
259259

260260
if (sortMode === "latest") {
261261
return b.createdAt.getTime() - a.createdAt.getTime() || a.title.localeCompare(b.title);
262262
}
263263

264264
if (sortMode === "weakest") {
265-
return b.weakMatch - a.weakMatch || a.order - b.order || a.title.localeCompare(b.title);
265+
return b.weakMatch - a.weakMatch || a.order.localeCompare(b.order) || a.title.localeCompare(b.title);
266266
}
267267

268268
if (sortMode === "recommended") {
269269
return (
270270
b.recommendationScore - a.recommendationScore ||
271271
b.weakMatch - a.weakMatch ||
272-
a.order - b.order
272+
a.order.localeCompare(b.order)
273273
);
274274
}
275275

276-
return a.order - b.order || a.title.localeCompare(b.title);
276+
return a.order.localeCompare(b.order) || a.title.localeCompare(b.title);
277277
});
278278

279279
const viewRows = orderedRows.filter((set) => {

docs/admin-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
1. Click **Create Set** in the sidebar.
1111
2. Fill out the set title, slug, topic tags, and settings.
12-
- **Order ID:** If you leave the `order` as `0` or blank, the system automatically assigns the next available number.
12+
- **Order ID:** The identifier shown in the set grid (e.g. `1`, `2`, `20212`, `A1`). Accepts any text. If left blank, the system assigns the next available number.
1313
3. Add problems one-by-one, including statement, answer type, and answer key.
1414
- Each problem has an **ID** field (the short label shown to students, e.g. `1`, `2a`, `A1`). It accepts any text — letters, digits, or a mix. Problems are sorted by ASCII order of their ID.
1515
- If you leave it as-is, new problems default to sequential numbers (`1`, `2`, `3`, …).

docs/import-format.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Practice mode only shows tags that belong to more than 10 published questions.
1717
"title": "MO Set 001 - Algebra Basics",
1818
"description": "Introductory answer-only algebra practice.",
1919
"statementFormat": "LATEX",
20-
"order": 1,
20+
"order": "1",
2121
"status": "PUBLISHED",
2222
"topicTags": ["algebra", "starter"],
2323
"difficulty": 2,
@@ -64,7 +64,7 @@ In the example above:
6464
| `title` | string | Yes | - | Display name of the set. |
6565
| `description` | string | No | `""` | Optional set description. |
6666
| `statementFormat` | string | No | `"LATEX"` | Statement format for all problems unless overridden per problem. One of `"LATEX"` or `"HTML"`. |
67-
| `order` | integer | No | next free order | Controls sort order. If omitted or `0`, the system assigns the next available order. |
67+
| `order` | string | No | next free order | Controls sort order. Supports any string value (sorted by ASCII). If omitted or empty, the system assigns the next available numeric order. Integer values are also accepted and coerced to strings. |
6868
| `status` | string | No | `"DRAFT"` | One of `"DRAFT"`, `"PUBLISHED"`, `"ARCHIVED"`. |
6969
| `visibleFrom` | ISO datetime string | No | `null` | Set release time. |
7070
| `visibleTo` | ISO datetime string | No | `null` | Set close time. |

lib/import/json-import.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const jsonProblemSetSchema = z.object({
5959
.refine((value) => value === undefined || isSupportedProblemContentFormat(value), {
6060
message: "Invalid statementFormat. Use LATEX or HTML.",
6161
}),
62-
order: z.coerce.number().int().optional().default(0),
62+
order: z.coerce.string().trim().optional().default(""),
6363
status: z.string().optional().default("DRAFT"),
6464
visibleFrom: z.string().datetime().nullable().optional(),
6565
visibleTo: z.string().datetime().nullable().optional(),
@@ -182,9 +182,15 @@ export async function importProblemSetJson(
182182
const warnings = dryRun.issues.filter((issue) => issue.level === "warning");
183183

184184
let finalOrder = data.order;
185-
if (typeof finalOrder !== "number" || finalOrder <= 0) {
186-
const maxOrderResult = await prisma.problemSet.aggregate({ _max: { order: true } });
187-
finalOrder = (maxOrderResult._max.order ?? 0) + 1;
185+
if (!finalOrder) {
186+
const existingSets = await prisma.problemSet.findMany({
187+
select: { order: true },
188+
orderBy: { order: "desc" },
189+
take: 1,
190+
});
191+
const maxOrder = existingSets[0]?.order ?? "0";
192+
const parsed = parseInt(maxOrder, 10);
193+
finalOrder = String((Number.isFinite(parsed) ? parsed : 0) + 1);
188194
}
189195

190196
const problemSet = await prisma.problemSet.create({

lib/import/manifest-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const manifestSchema = z.object({
1212
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
1313
title: z.string().min(1),
1414
description: z.string().default(""),
15-
order: z.number().int().positive(),
15+
order: z.coerce.string().trim(),
1616
status: z.enum(["draft", "published", "archived"]).default("draft"),
1717
allowedGroups: z.array(z.string().min(1)).default([]),
1818
topicTags: z.array(z.string().min(1)).default([]),

lib/import/problem-set-json-export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type ExportableProblemSet = {
44
slug: string;
55
title: string;
66
description: string;
7-
order: number;
7+
order: string;
88
status: ProblemSetStatus;
99
visibleFrom: Date | null;
1010
visibleTo: Date | null;

lib/problem-set-authoring.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const createProblemSetAuthoringSchema = z.object({
3939
message: "Slug must use lowercase letters, numbers, and single hyphens.",
4040
}),
4141
description: z.string().optional().default(""),
42-
order: z.coerce.number().int().positive().optional(),
42+
order: z.coerce.string().trim().optional(),
4343
difficulty: z.coerce.number().int().min(1).max(5).optional().default(1),
4444
status: z.nativeEnum(ProblemSetStatus).optional().default("DRAFT"),
4545
topicTags: z.array(z.string()).optional().default([]),
@@ -52,7 +52,7 @@ export const patchProblemSetAuthoringSchema = z.object({
5252
title: z.string().trim().min(1).optional(),
5353
description: z.string().optional(),
5454
status: z.nativeEnum(ProblemSetStatus).optional(),
55-
order: z.coerce.number().int().positive().optional(),
55+
order: z.coerce.string().trim().optional(),
5656
difficulty: z.coerce.number().int().min(1).max(5).optional(),
5757
topicTags: z.array(z.string().min(1)).optional(),
5858
videoUrl: z.string().url().nullable().optional(),

0 commit comments

Comments
 (0)