161 lines
7.8 KiB
TypeScript
161 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Header } from "@/components/layout/header";
|
|
import { Footer } from "@/components/layout/footer";
|
|
import { PracticeSection } from "@/components/practice/practice-section";
|
|
import type { Difficulty, MathProblem } from "@/lib/problems/types";
|
|
|
|
import { generateFractionAddSubtract, generateFractionMultiply, generateFractionDivide, generateFractionOfQuantity, generateWholeFromFraction } from "@/lib/problems/generators/fraction-problems";
|
|
import { generateDecimalCompareOrder, generateDecimalRounding, generateStandardForm, generateDecimalAddSubtract, generateDecimalMultiplyDivide, generateDecimalConversion } from "@/lib/problems/generators/decimal-problems";
|
|
import { generateSimplifyRatio, generateDivideInRatio, generateRatioWordProblem } from "@/lib/problems/generators/ratio-problems";
|
|
|
|
interface TopicGenerator {
|
|
unitSlug: string;
|
|
topicSlug: string;
|
|
label: string;
|
|
generator: (difficulty: Difficulty) => MathProblem;
|
|
unitColor: "unit-1" | "unit-2" | "unit-3" | "unit-4";
|
|
}
|
|
|
|
type UnitColor = TopicGenerator["unitColor"];
|
|
|
|
const TOPIC_GENERATORS: TopicGenerator[] = [
|
|
{ unitSlug: "unit-1-fractions", topicSlug: "add-subtract", label: "Add & Subtract Fractions", generator: generateFractionAddSubtract, unitColor: "unit-1" },
|
|
{ unitSlug: "unit-1-fractions", topicSlug: "multiply", label: "Multiply Fractions", generator: generateFractionMultiply, unitColor: "unit-1" },
|
|
{ unitSlug: "unit-1-fractions", topicSlug: "divide", label: "Divide Fractions", generator: generateFractionDivide, unitColor: "unit-1" },
|
|
{ unitSlug: "unit-1-fractions", topicSlug: "fraction-of-quantity", label: "Fraction of a Quantity", generator: generateFractionOfQuantity, unitColor: "unit-1" },
|
|
{ unitSlug: "unit-1-fractions", topicSlug: "whole-from-fractions", label: "Find the Whole", generator: generateWholeFromFraction, unitColor: "unit-1" },
|
|
{ unitSlug: "unit-2-decimals", topicSlug: "compare-order", label: "Compare & Order Decimals", generator: generateDecimalCompareOrder, unitColor: "unit-2" },
|
|
{ unitSlug: "unit-2-decimals", topicSlug: "approximate", label: "Approximate Decimals", generator: generateDecimalRounding, unitColor: "unit-2" },
|
|
{ unitSlug: "unit-2-decimals", topicSlug: "standard-form", label: "Standard Form", generator: generateStandardForm, unitColor: "unit-2" },
|
|
{ unitSlug: "unit-3-decimal-operations", topicSlug: "convert", label: "Convert Decimals & Fractions", generator: generateDecimalConversion, unitColor: "unit-3" },
|
|
{ unitSlug: "unit-3-decimal-operations", topicSlug: "add-subtract", label: "Add & Subtract Decimals", generator: generateDecimalAddSubtract, unitColor: "unit-3" },
|
|
{ unitSlug: "unit-3-decimal-operations", topicSlug: "multiply-divide", label: "Multiply & Divide Decimals", generator: generateDecimalMultiplyDivide, unitColor: "unit-3" },
|
|
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "simplify-ratios", label: "Simplify Ratios", generator: generateSimplifyRatio, unitColor: "unit-4" },
|
|
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "divide-in-ratio", label: "Divide in a Ratio", generator: generateDivideInRatio, unitColor: "unit-4" },
|
|
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "word-problems", label: "Ratio Word Problems", generator: generateRatioWordProblem, unitColor: "unit-4" },
|
|
];
|
|
|
|
const unitColors: Record<UnitColor, { activeFilter: string; inactiveFilter: string; label: string }> = {
|
|
"unit-1": {
|
|
activeFilter: "border-unit-1 bg-unit-1 text-white shadow-[var(--shadow-sm)]",
|
|
inactiveFilter: "border-unit-1/40 text-unit-1-dark hover:bg-unit-1-light",
|
|
label: "Fractions",
|
|
},
|
|
"unit-2": {
|
|
activeFilter: "border-unit-2 bg-unit-2 text-white shadow-[var(--shadow-sm)]",
|
|
inactiveFilter: "border-unit-2/40 text-unit-2-dark hover:bg-unit-2-light",
|
|
label: "Decimals",
|
|
},
|
|
"unit-3": {
|
|
activeFilter: "border-unit-3 bg-unit-3 text-white shadow-[var(--shadow-sm)]",
|
|
inactiveFilter: "border-unit-3/40 text-unit-3-dark hover:bg-unit-3-light",
|
|
label: "Decimal Operations",
|
|
},
|
|
"unit-4": {
|
|
activeFilter: "border-unit-4 bg-unit-4 text-white shadow-[var(--shadow-sm)]",
|
|
inactiveFilter: "border-unit-4/40 text-unit-4-dark hover:bg-unit-4-light",
|
|
label: "Ratio & Proportion",
|
|
},
|
|
};
|
|
|
|
export default function PracticePage() {
|
|
const [selectedTopic, setSelectedTopic] = useState<TopicGenerator | null>(null);
|
|
const [filterUnit, setFilterUnit] = useState<UnitColor | null>(null);
|
|
|
|
const filtered = filterUnit
|
|
? TOPIC_GENERATORS.filter((t) => t.unitColor === filterUnit)
|
|
: TOPIC_GENERATORS;
|
|
|
|
return (
|
|
<div className="playground-bg flex min-h-screen flex-col">
|
|
<Header />
|
|
<main className="playground-frame mx-auto w-full max-w-6xl flex-1 bg-white">
|
|
<div className="mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
|
|
<h1 className="mb-2 text-3xl font-extrabold tracking-tight">Practice</h1>
|
|
<p className="mb-8 text-muted">
|
|
Pick a topic and build your confidence with fresh questions each round.
|
|
</p>
|
|
|
|
{/* Unit filters */}
|
|
<div className="mb-6 flex flex-wrap gap-2">
|
|
<button
|
|
onClick={() => setFilterUnit(null)}
|
|
className={`rounded-full border-2 px-4 py-1.5 text-sm font-extrabold transition-all duration-200 ${
|
|
filterUnit === null
|
|
? "border-foreground bg-foreground text-background shadow-[var(--shadow-sm)]"
|
|
: "border-border text-muted hover:text-foreground"
|
|
}`}
|
|
>
|
|
All Topics
|
|
</button>
|
|
{(["unit-1", "unit-2", "unit-3", "unit-4"] as const).map((uc) => (
|
|
<button
|
|
key={uc}
|
|
onClick={() => setFilterUnit(filterUnit === uc ? null : uc)}
|
|
className={`rounded-full border-2 px-4 py-1.5 text-sm font-extrabold transition-all duration-200 ${
|
|
filterUnit === uc
|
|
? unitColors[uc].activeFilter
|
|
: unitColors[uc].inactiveFilter
|
|
}`}
|
|
>
|
|
{unitColors[uc].label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Topic grid */}
|
|
{!selectedTopic && (
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
{filtered.map((topic) => (
|
|
<Card
|
|
key={`${topic.unitSlug}-${topic.topicSlug}`}
|
|
hover
|
|
className="cursor-pointer"
|
|
accent={topic.unitColor}
|
|
onClick={() => setSelectedTopic(topic)}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-bold">{topic.label}</span>
|
|
<Badge variant={topic.unitColor}>
|
|
{unitColors[topic.unitColor].label}
|
|
</Badge>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Practice area */}
|
|
{selectedTopic && (
|
|
<div>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={() => setSelectedTopic(null)}
|
|
className="mb-6"
|
|
>
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
Back to Topics
|
|
</Button>
|
|
|
|
<PracticeSection
|
|
title={`Practice: ${selectedTopic.label}`}
|
|
generator={selectedTopic.generator}
|
|
unitColor={selectedTopic.unitColor}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|