Skip to content

Commit 8245205

Browse files
committed
Enhance assignment detail view with scores histogram and improve layout responsiveness
1 parent f01050a commit 8245205

File tree

3 files changed

+145
-30
lines changed

3 files changed

+145
-30
lines changed

frontend/src/routes/-components/assignmentDetailLists.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ import {
8787
TooltipTrigger,
8888
} from "@/components/ui/tooltip"
8989
import { Textarea } from "@/components/ui/textarea"
90+
import { AssignmentScoresHistogram } from "./courseDetailLists"
91+
import { cn } from "@/lib/utils"
9092

9193
const route = getRouteApi("/_authenticated/assignments/$assignmentId")
9294

@@ -115,21 +117,40 @@ export function SubmissionsTable() {
115117
<>
116118
<div className="container mx-auto py-2">
117119
{locationListType !== "detail" && (
118-
<>
119-
<div className="flex flex-row flex-wrap justify-between items-end">
120-
<h1 className="text-5xl font-bold my-8">{assignment?.name}</h1>
120+
<div className="grid gap-8 md:grid-cols-4 grid-cols-1">
121+
{/* <div className="flex flex-row flex-wrap justify-between items-center"> */}
122+
<div className={
123+
cn(
124+
"flex flex-row flex-wrap justify-evenly items-center col-span-2",
125+
assignment.submission_count === 0 && "md:col-span-4 justify-center gap-8"
126+
)
127+
}
128+
>
129+
<h1 className="text-5xl font-bold md:my-8">{assignment.name}</h1>
121130
{assignment.canvas_id && (
122131
<CanvasDialogWithTrigger
123132
assignment={assignment}
124133
submissions={submissions}
125134
/>
126135
)}
127136
</div>
137+
{assignment.submission_count > 0 && (
138+
<Card className="p-8 sm:p-10 md:mt-8 col-span-2 md:row-span-3 md:col-start-3 md:col-end-5 md:row-start-1 order-last">
139+
<AssignmentScoresHistogram
140+
assignmentId={assignment.id}
141+
className="mx-auto w-[100%] h-[100%]"
142+
showXTicks={true}
143+
showYTicks={true}
144+
filled={false}
145+
/>
146+
</Card>
147+
)}
148+
{/* </div> */}
128149
<AssignmentDetailCards
129150
assignment={assignment}
130151
submissions={submissions}
131152
/>
132-
</>
153+
</div>
133154
)}
134155
<DataTable
135156
columns={columnsOfAssignment}
@@ -158,7 +179,7 @@ function CanvasDialogWithTrigger({
158179
return (
159180
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
160181
<DialogTrigger asChild>
161-
<Button variant="outline" className="mb-8 border border-primary">
182+
<Button variant="outline" className="border border-primary my-auto">
162183
Canvas
163184
</Button>
164185
</DialogTrigger>
@@ -351,8 +372,8 @@ function AssignmentDetailCards({
351372
} satisfies ChartConfig
352373

353374
return (
354-
<div className="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
355-
<Card>
375+
<>
376+
<Card className="md:col-span-2 lg:col-span-1">
356377
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
357378
<CardTitle className="text-sm font-medium">Submissions</CardTitle>
358379
<LuFile className="h-4 w-4 text-muted-foreground" />
@@ -369,7 +390,7 @@ function AssignmentDetailCards({
369390
</div>
370391
</CardContent>
371392
</Card>
372-
<Card>
393+
<Card className="md:col-span-2 lg:col-span-1">
373394
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
374395
<CardTitle className="text-sm font-medium">Grades</CardTitle>
375396
<LuFileBarChart2 className="h-4 w-4 text-muted-foreground" />
@@ -407,7 +428,7 @@ function AssignmentDetailCards({
407428
</div>
408429
</CardContent>
409430
</Card>
410-
<Card>
431+
<Card className="col-span-2 lg:col-span-1">
411432
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
412433
<CardTitle className="text-sm font-medium">Tasks</CardTitle>
413434
<FaTasks className="h-4 w-4 text-muted-foreground" />
@@ -435,7 +456,7 @@ function AssignmentDetailCards({
435456
)}
436457
</CardContent>
437458
</Card>
438-
<Card>
459+
<Card className="col-span-2 lg:col-span-1">
439460
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
440461
<CardTitle className="text-sm font-medium">Automations</CardTitle>
441462
<LuFileScan className="h-4 w-4 text-muted-foreground" />
@@ -468,7 +489,7 @@ function AssignmentDetailCards({
468489
</div>
469490
</CardContent>
470491
</Card>
471-
</div>
492+
</>
472493
)
473494
}
474495

@@ -1587,7 +1608,7 @@ function InfoExtractForm({
15871608
description: "",
15881609
assignment_id: assignment.id,
15891610
pattern: "",
1590-
pages: [1,],
1611+
pages: [1],
15911612
}
15921613
// array of InfoField objects
15931614
const formSchema = z.object({

frontend/src/routes/-components/courseDetailLists.tsx

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { Badge } from "@/components/ui/badge"
88
import { assignmentScoresQueryOptions } from "@/utils/queryOptions"
99

1010
import { ChartContainer, type ChartConfig } from "@/components/ui/chart"
11-
import { Bar, BarChart } from "recharts"
11+
import { Bar, BarChart, CartesianGrid } from "recharts"
1212
import { useSuspenseQuery } from "@tanstack/react-query"
13+
import { cn } from "@/lib/utils"
1314

1415
export function SectionList({ sections }: { sections: Section[] }) {
1516
return (
@@ -164,7 +165,11 @@ function AssignmentListElement({ assignment }: { assignment: Assignment }) {
164165
</p>
165166
</div>
166167
{assignment.submission_count > 0 && (
167-
<AssignmentScoresHistogram assignmentId={assignment.id} />
168+
<AssignmentScoresHistogram
169+
assignmentId={assignment.id}
170+
className="h-[40px] w-1/2 mx-auto"
171+
maxGrade={assignment.max_score}
172+
/>
168173
)}
169174
<div className="ml-auto font-medium mr-4 flex items-center gap-1">
170175
{assignment.submission_count > 0 && (
@@ -202,26 +207,111 @@ const chartConfig = {
202207
},
203208
} satisfies ChartConfig
204209

205-
function AssignmentScoresHistogram({ assignmentId }: { assignmentId: number }) {
210+
export function AssignmentScoresHistogram({
211+
assignmentId,
212+
className,
213+
showXTicks = false, // existing optional prop
214+
showYTicks = false, // new optional prop
215+
filled = true,
216+
maxGrade,
217+
}: {
218+
assignmentId: number
219+
className?: string
220+
showXTicks?: boolean
221+
showYTicks?: boolean
222+
filled?: boolean
223+
maxGrade?: number
224+
}) {
206225
const { data: scores } = useSuspenseQuery(
207226
assignmentScoresQueryOptions(assignmentId)
208227
)
209-
if (!scores) {
210-
return null
228+
// if scores is null or undefined, return null
229+
if (!scores) return null
230+
231+
const scoresEmpty = scores.length === 0
232+
let scoreCountsData: { total: number; score: number }[]
233+
let yTicks: number[] = []
234+
235+
if (scoresEmpty) {
236+
// When there are no grades, use the provided maxGrade or a fallback of 10.
237+
const effectiveMaxScore = maxGrade ?? 10
238+
const bins = effectiveMaxScore + 1
239+
scoreCountsData = Array.from({ length: bins }, (_, index) => ({
240+
total: 0,
241+
score: index,
242+
}))
243+
// No yTicks when there are no grades
244+
yTicks = []
245+
} else {
246+
const maxScore = Math.max(...scores)
247+
const bins = Math.min(maxScore + 1, 10)
248+
const scoreCounts = Array.from({ length: bins }, () => 0)
249+
scores.forEach((score) => {
250+
const bin = Math.floor((score / maxScore) * bins)
251+
scoreCounts[bin] = (scoreCounts[bin] || 0) + 1
252+
})
253+
scoreCountsData = scoreCounts.map((total, index) => ({
254+
total,
255+
score: index,
256+
}))
257+
// Compute Y ticks if enabled - using 5 tick marks (from max to 0)
258+
const maxCount = Math.max(...scoreCounts)
259+
const tickCount = 5
260+
yTicks = Array.from({ length: tickCount }, (_, i) =>
261+
Math.round((maxCount * (tickCount - i - 1)) / (tickCount - 1))
262+
)
211263
}
212-
// should be replaced with assignment.max_score
213-
const maxScore = Math.max(...scores)
214-
// should be replaced with (a max of) ten bins
215-
const scoreCounts = Array.from({ length: maxScore + 1 }, () => 0)
216-
scores.forEach((score) => {
217-
scoreCounts[score] += 1
218-
})
219-
const scoreCountsData = scoreCounts.map((total, score) => ({ total, score }))
264+
220265
return (
221-
<ChartContainer config={chartConfig} className="h-[40px] w-1/4 ml-auto">
222-
<BarChart accessibilityLayer data={scoreCountsData}>
223-
<Bar dataKey="total" fill="var(--color-total)" radius={4} />
224-
</BarChart>
225-
</ChartContainer>
266+
<div className={className}>
267+
{showYTicks && !scoresEmpty ? (
268+
<div className="flex h-full">
269+
<div className="flex flex-col justify-between pr-2">
270+
{yTicks.map((tick, i) => (
271+
<span key={i} className="text-xs">
272+
{tick}
273+
</span>
274+
))}
275+
</div>
276+
<ChartContainer
277+
config={chartConfig}
278+
className={cn("h-[40px]", className)}
279+
>
280+
<BarChart accessibilityLayer data={scoreCountsData}>
281+
<CartesianGrid vertical={false} />
282+
<Bar
283+
dataKey="total"
284+
fill={chartConfig.total.color}
285+
opacity={filled ? 1 : 0.5}
286+
radius={4}
287+
/>
288+
</BarChart>
289+
</ChartContainer>
290+
</div>
291+
) : (
292+
<ChartContainer
293+
config={chartConfig}
294+
className={cn("h-[40px]", className)}
295+
>
296+
<BarChart accessibilityLayer data={scoreCountsData}>
297+
{/* Show grid only if there are grades */}
298+
{!scoresEmpty && <CartesianGrid vertical={false} />}
299+
<Bar
300+
dataKey="total"
301+
fill={chartConfig.total.color}
302+
opacity={filled ? 1 : 0.4}
303+
radius={4}
304+
/>
305+
</BarChart>
306+
</ChartContainer>
307+
)}
308+
{showXTicks && (
309+
<div className="flex justify-between text-xs mt-2">
310+
{scoreCountsData.map((data) => (
311+
<span key={data.score}>{data.score}</span>
312+
))}
313+
</div>
314+
)}
315+
</div>
226316
)
227317
}

frontend/src/routes/-components/submissionLeftSidebar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export function InfoExtractedSidebar({
6666
</AccordionTrigger>
6767
<AccordionContent>
6868
<div className="flex flex-col gap-2">
69+
{/* Write the description only once under the title */}
70+
<p className="text-xs text-gray-500">
71+
{fields[0].info_field.description}
72+
</p>
6973
{fields
7074
.slice()
7175
.sort((a, b) => {

0 commit comments

Comments
 (0)