feat(lessons): add lessons from client db

This commit is contained in:
shafin-r
2026-03-01 20:24:14 +06:00
parent 2eaf77e13c
commit 2a00c44157
152 changed files with 74587 additions and 222 deletions

View File

@ -0,0 +1,173 @@
import React, { useState, useRef } from 'react';
const InequalityRegionWidget: React.FC = () => {
// State for Inequalities: y > or < mx + b
// isGreater: true for >=, false for <=
const [ineq1, setIneq1] = useState({ m: 1, b: 1, isGreater: true });
const [ineq2, setIneq2] = useState({ m: -0.5, b: -2, isGreater: false });
const [testPoint, setTestPoint] = useState({ x: 0, y: 0 });
const isDragging = useRef(false);
const svgRef = useRef<SVGSVGElement>(null);
// Viewport
const range = 10;
const size = 300;
const scale = size / (range * 2);
const center = size / 2;
// Helpers
const toPx = (val: number, isY = false) => {
return isY ? center - val * scale : center + val * scale;
};
const fromPx = (px: number, isY = false) => {
return isY ? (center - px) / scale : (px - center) / scale;
};
// Generate polygon points for shading
const getRegionPoints = (m: number, b: number, isGreater: boolean) => {
const xMin = -range;
const xMax = range;
const yAtMin = m * xMin + b;
const yAtMax = m * xMax + b;
// y limit is the top (range) or bottom (-range) of the graph
const limitY = isGreater ? range : -range;
const p1 = { x: xMin, y: yAtMin };
const p2 = { x: xMax, y: yAtMax };
const p3 = { x: xMax, y: limitY };
const p4 = { x: xMin, y: limitY };
return `${toPx(p1.x)},${toPx(p1.y, true)} ${toPx(p2.x)},${toPx(p2.y, true)} ${toPx(p3.x)},${toPx(p3.y, true)} ${toPx(p4.x)},${toPx(p4.y, true)}`;
};
const getLinePath = (m: number, b: number) => {
const x1 = -range;
const y1 = m * x1 + b;
const x2 = range;
const y2 = m * x2 + b;
return `M ${toPx(x1)} ${toPx(y1, true)} L ${toPx(x2)} ${toPx(y2, true)}`;
};
// Interaction
const handleInteraction = (e: React.MouseEvent) => {
if (!svgRef.current) return;
const rect = svgRef.current.getBoundingClientRect();
const x = fromPx(e.clientX - rect.left);
const y = fromPx(e.clientY - rect.top, true);
// Clamp
const cx = Math.max(-range, Math.min(range, x));
const cy = Math.max(-range, Math.min(range, y));
setTestPoint({ x: cx, y: cy });
};
// Logic Check
const check1 = ineq1.isGreater ? testPoint.y >= ineq1.m * testPoint.x + ineq1.b : testPoint.y <= ineq1.m * testPoint.x + ineq1.b;
const check2 = ineq2.isGreater ? testPoint.y >= ineq2.m * testPoint.x + ineq2.b : testPoint.y <= ineq2.m * testPoint.x + ineq2.b;
const isSolution = check1 && check2;
return (
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
<div className="flex flex-col md:flex-row gap-8">
{/* Controls */}
<div className="w-full md:w-1/3 space-y-6">
{/* Inequality 1 */}
<div className={`p-4 rounded-lg border ${check1 ? 'bg-indigo-50 border-indigo-200' : 'bg-slate-50 border-slate-200'}`}>
<div className="flex justify-between items-center mb-2">
<span className="font-bold text-indigo-800 text-sm">Region 1 (Blue)</span>
<button
onClick={() => setIneq1(p => ({...p, isGreater: !p.isGreater}))}
className="text-xs bg-white border border-indigo-200 px-2 py-1 rounded font-bold text-indigo-600"
>
{ineq1.isGreater ? 'y ≥ ...' : 'y ≤ ...'}
</button>
</div>
<div className="space-y-3">
<div>
<div className="flex justify-between text-xs text-slate-500"><span>Slope</span><span>{ineq1.m}</span></div>
<input type="range" min="-4" max="4" step="0.5" value={ineq1.m} onChange={e => setIneq1({...ineq1, m: parseFloat(e.target.value)})} className="w-full h-1 bg-indigo-200 rounded accent-indigo-600"/>
</div>
<div>
<div className="flex justify-between text-xs text-slate-500"><span>Y-Int</span><span>{ineq1.b}</span></div>
<input type="range" min="-8" max="8" step="1" value={ineq1.b} onChange={e => setIneq1({...ineq1, b: parseFloat(e.target.value)})} className="w-full h-1 bg-indigo-200 rounded accent-indigo-600"/>
</div>
</div>
</div>
{/* Inequality 2 */}
<div className={`p-4 rounded-lg border ${check2 ? 'bg-rose-50 border-rose-200' : 'bg-slate-50 border-slate-200'}`}>
<div className="flex justify-between items-center mb-2">
<span className="font-bold text-rose-800 text-sm">Region 2 (Red)</span>
<button
onClick={() => setIneq2(p => ({...p, isGreater: !p.isGreater}))}
className="text-xs bg-white border border-rose-200 px-2 py-1 rounded font-bold text-rose-600"
>
{ineq2.isGreater ? 'y ≥ ...' : 'y ≤ ...'}
</button>
</div>
<div className="space-y-3">
<div>
<div className="flex justify-between text-xs text-slate-500"><span>Slope</span><span>{ineq2.m}</span></div>
<input type="range" min="-4" max="4" step="0.5" value={ineq2.m} onChange={e => setIneq2({...ineq2, m: parseFloat(e.target.value)})} className="w-full h-1 bg-rose-200 rounded accent-rose-600"/>
</div>
<div>
<div className="flex justify-between text-xs text-slate-500"><span>Y-Int</span><span>{ineq2.b}</span></div>
<input type="range" min="-8" max="8" step="1" value={ineq2.b} onChange={e => setIneq2({...ineq2, b: parseFloat(e.target.value)})} className="w-full h-1 bg-rose-200 rounded accent-rose-600"/>
</div>
</div>
</div>
<div className={`p-3 rounded-lg text-center font-bold text-sm border-2 transition-colors ${isSolution ? 'bg-emerald-100 border-emerald-400 text-emerald-800' : 'bg-slate-100 border-slate-300 text-slate-500'}`}>
Test Point: ({testPoint.x.toFixed(1)}, {testPoint.y.toFixed(1)}) <br/>
{isSolution ? "SOLUTION FOUND" : "NOT A SOLUTION"}
</div>
</div>
{/* Graph */}
<div className="flex-1 flex justify-center">
<div className="relative w-[300px] h-[300px] bg-white border border-slate-200 rounded-xl overflow-hidden cursor-crosshair">
<svg
ref={svgRef}
width="300" height="300" viewBox="0 0 300 300"
onMouseDown={(e) => { isDragging.current = true; handleInteraction(e); }}
onMouseMove={(e) => { if(isDragging.current) handleInteraction(e); }}
onMouseUp={() => isDragging.current = false}
onMouseLeave={() => isDragging.current = false}
>
<defs>
<pattern id="grid-ineq" width="15" height="15" patternUnits="userSpaceOnUse">
<path d="M 15 0 L 0 0 0 15" fill="none" stroke="#f8fafc" strokeWidth="1"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid-ineq)" />
{/* Axes */}
<line x1="0" y1={center} x2={size} y2={center} stroke="#cbd5e1" strokeWidth="2" />
<line x1={center} y1="0" x2={center} y2={size} stroke="#cbd5e1" strokeWidth="2" />
{/* Region 1 */}
<polygon points={getRegionPoints(ineq1.m, ineq1.b, ineq1.isGreater)} fill="rgba(99, 102, 241, 0.2)" />
<path d={getLinePath(ineq1.m, ineq1.b)} stroke="#4f46e5" strokeWidth="2" />
{/* Region 2 */}
<polygon points={getRegionPoints(ineq2.m, ineq2.b, ineq2.isGreater)} fill="rgba(225, 29, 72, 0.2)" />
<path d={getLinePath(ineq2.m, ineq2.b)} stroke="#e11d48" strokeWidth="2" />
{/* Test Point */}
<circle
cx={toPx(testPoint.x)} cy={toPx(testPoint.y, true)} r="6"
fill={isSolution ? "#10b981" : "#64748b"} stroke="white" strokeWidth="2" className="shadow-sm"
/>
</svg>
</div>
</div>
</div>
</div>
);
};
export default InequalityRegionWidget;