Skip to content

Commit 4807951

Browse files
authored
Merge pull request #37 from codegasms/feat/add-listings-and-editors
Add tables for problems, contests and add mock data for users
2 parents 734b8f3 + 2169106 commit 4807951

File tree

11 files changed

+376
-30
lines changed

11 files changed

+376
-30
lines changed

app/[orgId]/contests/mockData.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export interface Contest {
2+
id: number;
3+
name: string;
4+
nameId: string;
5+
description: string;
6+
startTime: string;
7+
endTime: string;
8+
problems: string;
9+
}
10+
11+
export const mockContests: Contest[] = [
12+
{
13+
id: 1,
14+
name: "Weekly Programming Challenge #1",
15+
nameId: "wpc-1",
16+
description:
17+
"A weekly programming contest featuring algorithmic problems of varying difficulty levels.",
18+
startTime: "2024-01-15",
19+
endTime: "2024-01-15",
20+
problems: "1,2,3",
21+
},
22+
{
23+
id: 2,
24+
name: "Data Structures Deep Dive",
25+
nameId: "dsa-deep",
26+
description:
27+
"Test your knowledge of advanced data structures with this challenging contest.",
28+
startTime: "2024-01-20",
29+
endTime: "2024-01-20",
30+
problems: "4,5",
31+
},
32+
{
33+
id: 3,
34+
name: "Beginner's Coding Contest",
35+
nameId: "bg-contest",
36+
description:
37+
"Perfect for newcomers! Simple problems to help you get started with competitive programming.",
38+
startTime: "2024-01-25",
39+
endTime: "2024-01-25",
40+
problems: "6",
41+
},
42+
];

app/[orgId]/contests/page.tsx

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,99 @@
1-
import { ContestListPage } from "@/components/contestlist";
1+
"use client";
22

3-
export default function ContestList() {
4-
return <ContestListPage />;
3+
import { GenericListing, ColumnDef } from "@/mint/generic-listing";
4+
import { GenericEditor, Field } from "@/mint/generic-editor";
5+
import { Contest, mockContests } from "./mockData";
6+
import { useEffect, useState } from "react";
7+
import { z } from "zod";
8+
9+
const columns: ColumnDef<Contest>[] = [
10+
{ header: "Name", accessorKey: "name", sortable: true },
11+
{ header: "Description", accessorKey: "description" },
12+
{ header: "Start Time", accessorKey: "startTime", sortable: true },
13+
{ header: "End Time", accessorKey: "endTime", sortable: true },
14+
{ header: "Problems", accessorKey: "problems" },
15+
];
16+
17+
const fields: Field[] = [
18+
{ name: "name", label: "Name", type: "text" },
19+
{ name: "nameId", label: "Name ID", type: "text" },
20+
{ name: "description", label: "Description", type: "text" },
21+
{ name: "startTime", label: "Start Time", type: "date" },
22+
{ name: "endTime", label: "End Time", type: "date" },
23+
{ name: "problems", label: "Problems", type: "text" },
24+
];
25+
26+
const contestSchema = z.object({
27+
name: z.string().min(2).max(100),
28+
nameId: z.string().min(2).max(50),
29+
description: z.string(),
30+
startTime: z.string(),
31+
endTime: z.string(),
32+
problems: z.string(),
33+
});
34+
35+
export default function ContestsPage() {
36+
const [contests, setContests] = useState<Contest[]>([]);
37+
const [selectedContest, setSelectedContest] = useState<Contest | null>(null);
38+
const [isEditorOpen, setIsEditorOpen] = useState(false);
39+
40+
useEffect(() => {
41+
setContests(mockContests);
42+
}, []);
43+
44+
const deleteContest = async (contest: Contest) => {
45+
try {
46+
// Simulate API call
47+
await new Promise((resolve) => setTimeout(resolve, 500));
48+
49+
// Update the state after successful API call
50+
setContests((prevContests) =>
51+
prevContests.filter((c) => c.id !== contest.id),
52+
);
53+
return Promise.resolve();
54+
} catch (error) {
55+
return Promise.reject(error);
56+
}
57+
};
58+
59+
const saveContest = async (contest: Contest) => {
60+
if (selectedContest) {
61+
// Update existing contest
62+
setContests(contests.map((c) => (c.id === contest.id ? contest : c)));
63+
} else {
64+
// Add new contest
65+
setContests([...contests, { ...contest, id: Date.now() }]);
66+
}
67+
setIsEditorOpen(false);
68+
};
69+
70+
return (
71+
<>
72+
<GenericListing
73+
data={contests}
74+
columns={columns}
75+
title="Contests"
76+
searchableFields={["name", "description"]}
77+
onAdd={() => {
78+
setSelectedContest(null);
79+
setIsEditorOpen(true);
80+
}}
81+
onEdit={(contest) => {
82+
setSelectedContest(contest);
83+
setIsEditorOpen(true);
84+
}}
85+
onDelete={deleteContest}
86+
/>
87+
88+
<GenericEditor
89+
data={selectedContest}
90+
isOpen={isEditorOpen}
91+
onClose={() => setIsEditorOpen(false)}
92+
onSave={saveContest}
93+
schema={contestSchema}
94+
fields={fields}
95+
title="Contest"
96+
/>
97+
</>
98+
);
599
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export interface Problem {
2+
id: number;
3+
nameId: string; // 5 character alphanumeric code
4+
title: string;
5+
allowedLanguages: string[];
6+
createdAt: string;
7+
orgId: number;
8+
}
9+
10+
export const mockProblems: Problem[] = [
11+
{
12+
id: 1,
13+
nameId: "2SUM1",
14+
title:
15+
"Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.",
16+
allowedLanguages: ["python", "javascript", "java"],
17+
createdAt: "2024-01-01",
18+
orgId: 1,
19+
},
20+
{
21+
id: 2,
22+
nameId: "REVST",
23+
title:
24+
"Write a function that reverses a string. The input string is given as an array of characters.",
25+
allowedLanguages: ["python", "cpp", "javascript"],
26+
createdAt: "2024-01-02",
27+
orgId: 1,
28+
},
29+
{
30+
id: 3,
31+
nameId: "BSRCH",
32+
title:
33+
"Implement a binary search algorithm to find a target value in a sorted array.",
34+
allowedLanguages: ["python", "typescript", "java", "cpp"],
35+
createdAt: "2024-01-03",
36+
orgId: 1,
37+
},
38+
];

app/[orgId]/problems/page.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"use client";
2+
import { mockProblems, Problem } from "./mockProblems";
3+
import { useToast } from "@/hooks/use-toast";
4+
import { GenericListing, ColumnDef } from "@/mint/generic-listing";
5+
import { useEffect, useState } from "react";
6+
7+
const columns: ColumnDef<Problem>[] = [
8+
{ header: "Problem Code", accessorKey: "nameId", sortable: true },
9+
{ header: "Title", accessorKey: "title", sortable: true },
10+
{ header: "Allowed Languages", accessorKey: "allowedLanguages" },
11+
{ header: "Created At", accessorKey: "createdAt", sortable: true },
12+
];
13+
14+
export default function ProblemsPage({
15+
params,
16+
}: {
17+
params: { orgId: string };
18+
}) {
19+
const [problems, setProblems] = useState<Problem[]>([]);
20+
const [selectedProblem, setSelectedProblem] = useState<Problem | null>(null);
21+
// const [isEditorOpen, setIsEditorOpen] = useState(false);
22+
23+
const { toast } = useToast();
24+
25+
useEffect(() => {
26+
const fetchProblems = async () => {
27+
try {
28+
const response = await fetch(`/api/orgs/${params.orgId}/problems`);
29+
if (!response.ok) throw new Error("Failed to fetch problems");
30+
const data = await response.json();
31+
setProblems(data);
32+
} catch (error) {
33+
console.error("Error fetching problems:", error);
34+
toast({
35+
title: "Error fetching problems",
36+
description: "Using mock data as fallback",
37+
variant: "destructive",
38+
});
39+
setProblems(mockProblems);
40+
}
41+
};
42+
fetchProblems();
43+
}, [params.orgId]);
44+
45+
const handleAdd = () => {
46+
setSelectedProblem(null);
47+
setIsEditorOpen(true);
48+
};
49+
50+
const handleEdit = (problem: Problem) => {
51+
setSelectedProblem(problem);
52+
setIsEditorOpen(true);
53+
};
54+
55+
const handleDelete = async (problem: Problem) => {
56+
try {
57+
const response = await fetch(
58+
`/api/orgs/${params.orgId}/problems/${problem.id}`,
59+
{
60+
method: "DELETE",
61+
},
62+
);
63+
if (!response.ok) throw new Error("Failed to delete problem");
64+
setProblems((prev) => prev.filter((p) => p.id !== problem.id));
65+
} catch (error) {
66+
console.error("Error deleting problem:", error);
67+
// TODO: Add proper error handling
68+
}
69+
};
70+
71+
const handleSave = async (problem: Problem) => {
72+
try {
73+
const url = selectedProblem
74+
? `/api/orgs/${params.orgId}/problems/${selectedProblem.id}`
75+
: `/api/orgs/${params.orgId}/problems`;
76+
77+
const response = await fetch(url, {
78+
method: selectedProblem ? "PATCH" : "POST",
79+
headers: { "Content-Type": "application/json" },
80+
body: JSON.stringify({ ...problem, orgId: parseInt(params.orgId) }),
81+
});
82+
83+
if (!response.ok) throw new Error("Failed to save problem");
84+
const savedProblem = await response.json();
85+
86+
setProblems((prev) => {
87+
if (selectedProblem) {
88+
return prev.map((p) =>
89+
p.id === selectedProblem.id ? savedProblem : p,
90+
);
91+
}
92+
return [...prev, savedProblem];
93+
});
94+
95+
setIsEditorOpen(false);
96+
setSelectedProblem(null);
97+
} catch (error) {
98+
console.error("Error saving problem:", error);
99+
// TODO: Add proper error handling
100+
}
101+
};
102+
103+
return (
104+
<>
105+
<GenericListing
106+
data={problems}
107+
columns={columns}
108+
title="Problems"
109+
searchableFields={["nameId", "title"]}
110+
onAdd={handleAdd}
111+
onEdit={handleEdit}
112+
onDelete={handleDelete}
113+
allowDownload={true}
114+
addPage="new"
115+
/>
116+
</>
117+
);
118+
}

app/[orgId]/users/mockUsers.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
export interface User {
2+
id: number;
3+
name: string;
4+
nameId: string;
5+
avatar?: string;
6+
about?: string;
7+
role: "owner" | "organizer" | "member";
8+
joinedAt: string;
9+
}
10+
11+
export const mockUsers: User[] = [
12+
{
13+
id: 1,
14+
name: "John Smith",
15+
nameId: "john.smith",
16+
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=john",
17+
about: "Senior Software Engineer with passion for algorithms",
18+
role: "owner",
19+
joinedAt: "2024-01-01",
20+
},
21+
{
22+
id: 2,
23+
name: "Alice Johnson",
24+
nameId: "alice.j",
25+
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=alice",
26+
about: "Full-stack developer and competitive programmer",
27+
role: "organizer",
28+
joinedAt: "2024-01-05",
29+
},
30+
{
31+
id: 3,
32+
name: "Bob Wilson",
33+
nameId: "bob.wilson",
34+
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=bob",
35+
role: "member",
36+
joinedAt: "2024-01-10",
37+
},
38+
{
39+
id: 4,
40+
name: "Emma Davis",
41+
nameId: "emma.d",
42+
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=emma",
43+
about: "Computer Science student interested in AI/ML",
44+
role: "member",
45+
joinedAt: "2024-01-15",
46+
},
47+
{
48+
id: 5,
49+
name: "Michael Brown",
50+
nameId: "michael.b",
51+
role: "organizer",
52+
joinedAt: "2024-01-20",
53+
},
54+
];

app/[orgId]/users/new/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import CreateUserPage from "@/components/newuser-add";
1+
// import CreateUserPage from "@/components/newuser-add";
22

33
export default function UsersPage() {
4-
return <CreateUserPage />;
4+
// return <CreateUserPage />;
5+
return <h1>New User</h1>;
56
}

0 commit comments

Comments
 (0)