290 lines
9.4 KiB
TypeScript
290 lines
9.4 KiB
TypeScript
import React, { useRef, useState, useEffect } from "react";
|
|
import {
|
|
ArrowDown,
|
|
Check,
|
|
BookOpen,
|
|
Target,
|
|
BarChart3,
|
|
Calculator,
|
|
} from "lucide-react";
|
|
import DataModifierWidget from "../../../components/lessons/DataModifierWidget";
|
|
import BoxPlotComparisonWidget from "../../../components/lessons/BoxPlotComparisonWidget";
|
|
import ConfidenceIntervalWidget from "../../../components/lessons/ConfidenceIntervalWidget";
|
|
import StudyDesignWidget from "../../../components/lessons/StudyDesignWidget";
|
|
import Quiz from "../../../components/lessons/Quiz";
|
|
import {
|
|
CENTER_SPREAD_QUIZ_DATA,
|
|
DISTRIBUTIONS_QUIZ_DATA,
|
|
INFERENCES_QUIZ_DATA,
|
|
} from "../../../utils/constants";
|
|
|
|
interface LessonProps {
|
|
onFinish?: () => void;
|
|
}
|
|
|
|
const DataAnalysisLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
|
const [activeSection, setActiveSection] = useState(0);
|
|
const sectionsRef = useRef<(HTMLElement | null)[]>([]);
|
|
|
|
const scrollToSection = (index: number) => {
|
|
setActiveSection(index);
|
|
sectionsRef.current[index]?.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "start",
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
const index = sectionsRef.current.indexOf(
|
|
entry.target as HTMLElement,
|
|
);
|
|
if (index !== -1) {
|
|
setActiveSection(index);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
{
|
|
rootMargin: "-20% 0px -60% 0px",
|
|
},
|
|
);
|
|
|
|
sectionsRef.current.forEach((section) => {
|
|
if (section) observer.observe(section);
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const SectionMarker = ({
|
|
index,
|
|
title,
|
|
icon: Icon,
|
|
}: {
|
|
index: number;
|
|
title: string;
|
|
icon: any;
|
|
}) => {
|
|
const isActive = activeSection === index;
|
|
const isPast = activeSection > index;
|
|
return (
|
|
<button
|
|
onClick={() => scrollToSection(index)}
|
|
className={`flex items-center gap-3 p-3 w-full rounded-lg transition-all ${
|
|
isActive
|
|
? "bg-white shadow-md border border-amber-100"
|
|
: "hover:bg-slate-100"
|
|
}`}
|
|
>
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 ${
|
|
isActive
|
|
? "bg-amber-600 text-white"
|
|
: isPast
|
|
? "bg-amber-400 text-white"
|
|
: "bg-slate-200 text-slate-500"
|
|
}`}
|
|
>
|
|
{isPast ? (
|
|
<Check className="w-4 h-4" />
|
|
) : (
|
|
<Icon className="w-4 h-4" />
|
|
)}
|
|
</div>
|
|
<div className="text-left">
|
|
<p
|
|
className={`text-sm font-bold ${isActive ? "text-amber-900" : "text-slate-600"}`}
|
|
>
|
|
{title}
|
|
</p>
|
|
</div>
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const DATA_ANALYSIS_QUIZ_DATA = [
|
|
...CENTER_SPREAD_QUIZ_DATA,
|
|
...DISTRIBUTIONS_QUIZ_DATA,
|
|
...INFERENCES_QUIZ_DATA,
|
|
];
|
|
|
|
return (
|
|
<div className="flex flex-col lg:flex-row min-h-screen">
|
|
<aside className="w-full lg:w-64 lg:fixed lg:top-20 lg:bottom-0 lg:overflow-y-auto p-4 border-r border-slate-200 bg-slate-50 lg:bg-transparent z-0 hidden lg:block">
|
|
<nav className="space-y-2">
|
|
<SectionMarker index={0} title="Data Changes" icon={Calculator} />
|
|
<SectionMarker index={1} title="Distributions" icon={BarChart3} />
|
|
<SectionMarker index={2} title="Inferences" icon={Target} />
|
|
<SectionMarker index={3} title="Study Design" icon={BookOpen} />
|
|
<SectionMarker index={4} title="Practice" icon={BookOpen} />
|
|
</nav>
|
|
</aside>
|
|
|
|
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto">
|
|
{/* Section 1: Data Changes */}
|
|
<section
|
|
ref={(el) => {
|
|
sectionsRef.current[0] = el;
|
|
}}
|
|
className="min-h-screen flex flex-col justify-center mb-24 pt-20 lg:pt-0"
|
|
>
|
|
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
|
|
Effects of Data Changes
|
|
</h2>
|
|
<div className="prose prose-slate text-lg text-slate-600 mb-8">
|
|
<p>
|
|
The SAT often asks how the Mean, Median, and Standard Deviation
|
|
change without doing the full math.
|
|
</p>
|
|
<ul className="list-disc pl-5 space-y-2">
|
|
<li>
|
|
<strong>Add Constant:</strong> Center shifts, Spread stays same.
|
|
</li>
|
|
<li>
|
|
<strong>Multiply Constant:</strong> Center and Spread both
|
|
scale.
|
|
</li>
|
|
<li>
|
|
<strong>Outliers:</strong> Pull the Mean strongly, but the
|
|
Median is resistant.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<DataModifierWidget />
|
|
|
|
<button
|
|
onClick={() => scrollToSection(1)}
|
|
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
|
|
>
|
|
Next: Comparing Distributions{" "}
|
|
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
|
|
</button>
|
|
</section>
|
|
|
|
{/* Section 2: Distributions */}
|
|
<section
|
|
ref={(el) => {
|
|
sectionsRef.current[1] = el;
|
|
}}
|
|
className="min-h-screen flex flex-col justify-center mb-24"
|
|
>
|
|
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
|
|
Comparing Distributions
|
|
</h2>
|
|
<div className="prose prose-slate text-lg text-slate-600 mb-8">
|
|
<p>
|
|
<strong>Box Plots</strong> are the fastest way to compare Median
|
|
and Spread (IQR & Range). Remember: The box contains the middle
|
|
50% of the data.
|
|
</p>
|
|
</div>
|
|
|
|
<BoxPlotComparisonWidget />
|
|
|
|
<button
|
|
onClick={() => scrollToSection(2)}
|
|
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
|
|
>
|
|
Next: Data Inferences{" "}
|
|
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
|
|
</button>
|
|
</section>
|
|
|
|
{/* Section 3: Inferences */}
|
|
<section
|
|
ref={(el) => {
|
|
sectionsRef.current[2] = el;
|
|
}}
|
|
className="min-h-screen flex flex-col justify-center mb-24"
|
|
>
|
|
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
|
|
Data Inferences & Margin of Error
|
|
</h2>
|
|
<div className="prose prose-slate text-lg text-slate-600 mb-8">
|
|
<p>
|
|
A <strong>Confidence Interval</strong> (Estimate ± Margin of
|
|
Error) gives a range where the true population value likely lies.
|
|
<br />
|
|
To compare two groups, check for <strong>overlap</strong>.
|
|
</p>
|
|
</div>
|
|
|
|
<ConfidenceIntervalWidget />
|
|
|
|
<button
|
|
onClick={() => scrollToSection(3)}
|
|
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
|
|
>
|
|
Next: Study Design{" "}
|
|
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
|
|
</button>
|
|
</section>
|
|
|
|
{/* Section 4: Study Design */}
|
|
<section
|
|
ref={(el) => {
|
|
sectionsRef.current[3] = el;
|
|
}}
|
|
className="min-h-screen flex flex-col justify-center mb-24"
|
|
>
|
|
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
|
|
Study Design: Conclusion Logic
|
|
</h2>
|
|
<div className="prose prose-slate text-lg text-slate-600 mb-8">
|
|
<p>
|
|
Two magic phrases determine what you can conclude:
|
|
<br />
|
|
1. <strong>Random Sampling</strong> allows Generalization.
|
|
<br />
|
|
2. <strong>Random Assignment</strong> allows Causation.
|
|
</p>
|
|
</div>
|
|
|
|
<StudyDesignWidget />
|
|
|
|
<button
|
|
onClick={() => scrollToSection(4)}
|
|
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
|
|
>
|
|
Next: Practice Quiz{" "}
|
|
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
|
|
</button>
|
|
</section>
|
|
|
|
{/* Section 5: Quiz */}
|
|
<section
|
|
ref={(el) => {
|
|
sectionsRef.current[4] = el;
|
|
}}
|
|
className="min-h-screen flex flex-col justify-center"
|
|
>
|
|
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
|
Practice Time
|
|
</h2>
|
|
{DATA_ANALYSIS_QUIZ_DATA.map((quiz) => (
|
|
<div key={quiz.id} className="mb-12">
|
|
<Quiz data={quiz} />
|
|
</div>
|
|
))}
|
|
<div className="p-8 bg-amber-900 rounded-2xl text-white text-center mt-12">
|
|
<h3 className="text-2xl font-bold mb-4">Topic Mastered!</h3>
|
|
<button
|
|
onClick={onFinish}
|
|
className="px-6 py-3 bg-white text-amber-900 font-bold rounded-full hover:bg-amber-50 transition-colors"
|
|
>
|
|
Finish Lesson ✓
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DataAnalysisLesson;
|