RAI / client /src /components /ScenarioComponent.tsx
Soha85's picture
Simulator and scenarios added and fixed
bf7a424 verified
Raw History Blame Contribute Delete
9.21 kB
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { CheckCircle, XCircle, AlertCircle, ChevronRight, BookOpen } from 'lucide-react';
import { Scenario, DecisionPoint, ScenarioOption } from '@/data/scenariosData';
interface ScenarioComponentProps {
scenario: Scenario;
onComplete: (score: number) => void;
}
export function ScenarioComponent({ scenario, onComplete }: ScenarioComponentProps) {
const [currentDecisionIndex, setCurrentDecisionIndex] = useState(0);
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const [answered, setAnswered] = useState(false);
const [score, setScore] = useState(0);
const [decisions, setDecisions] = useState<Record<string, string>>({});
const currentDecision = scenario.decisionPoints[currentDecisionIndex];
const progress = ((currentDecisionIndex + 1) / scenario.decisionPoints.length) * 100;
const handleSelectOption = (optionId: string) => {
if (!answered) {
setSelectedOption(optionId);
}
};
const handleSubmitDecision = () => {
if (!selectedOption) return;
const option = currentDecision.options.find(o => o.id === selectedOption);
if (option?.isCorrect) {
setScore(score + 1);
}
setDecisions({
...decisions,
[currentDecision.id]: selectedOption
});
setAnswered(true);
};
const handleNext = () => {
if (currentDecisionIndex < scenario.decisionPoints.length - 1) {
setCurrentDecisionIndex(currentDecisionIndex + 1);
setSelectedOption(null);
setAnswered(false);
} else {
// Count the last decision correctly
const lastDecisionCorrect = selectedOptionData?.isCorrect ? 1 : 0;
const finalScore = Math.round(((score + lastDecisionCorrect) / scenario.decisionPoints.length) * 100);
onComplete(finalScore);
}
};
const selectedOptionData = currentDecision.options.find(o => o.id === selectedOption);
const correctOption = currentDecision.options.find(o => o.isCorrect);
return (
<div className="space-y-6">
{/* Progress */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-slate-600">
Decision {currentDecisionIndex + 1} of {scenario.decisionPoints.length}
</span>
<span className="text-sm font-medium text-blue-600">
{Math.round(progress)}%
</span>
</div>
<Progress value={progress} className="h-2" />
</div>
{/* Scenario Context */}
<Card className="p-6 bg-gradient-to-br from-blue-50 to-teal-50 border-blue-200 space-y-3">
<div className="flex items-start gap-3">
<BookOpen className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<div>
<h3 className="font-semibold text-slate-900 mb-1">{scenario.title}</h3>
<p className="text-sm text-slate-700">{currentDecision.context}</p>
</div>
</div>
</Card>
{/* Decision Point */}
<Card className="p-8 space-y-6 border-slate-200">
{/* Decision Header */}
<div className="space-y-3">
<div className="flex flex-wrap items-center gap-2">
{currentDecision.relatedPillars.map(pillar => (
<span key={pillar} className="px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs font-semibold">
{pillar}
</span>
))}
</div>
<h2 className="text-2xl font-bold text-slate-900">
{currentDecision.question}
</h2>
</div>
{/* Options */}
<div className="space-y-3">
{currentDecision.options.map((option) => {
const isSelected = selectedOption === option.id;
const isCorrect = option.isCorrect;
const showResult = answered && (isSelected || isCorrect);
return (
<button
key={option.id}
onClick={() => handleSelectOption(option.id)}
disabled={answered}
className={`w-full text-left p-4 rounded-lg border-2 transition-all ${
isSelected && !answered
? 'border-blue-500 bg-blue-50'
: answered && isCorrect
? 'border-green-500 bg-green-50'
: answered && isSelected && !isCorrect
? 'border-red-500 bg-red-50'
: 'border-slate-200 hover:border-slate-300 hover:bg-slate-50'
} ${answered ? 'cursor-default' : 'cursor-pointer'}`}
>
<div className="flex items-start gap-3">
<div className={`flex-shrink-0 w-6 h-6 rounded-full border-2 flex items-center justify-center mt-0.5 ${
isSelected && !answered
? 'border-blue-500 bg-blue-500'
: answered && isCorrect
? 'border-green-500 bg-green-500'
: answered && isSelected && !isCorrect
? 'border-red-500 bg-red-500'
: 'border-slate-300'
}`}>
{showResult && (
isCorrect ? (
<CheckCircle className="w-5 h-5 text-white" />
) : (
<XCircle className="w-5 h-5 text-white" />
)
)}
</div>
<div className="flex-1">
<p className={`font-medium ${
isSelected && !answered
? 'text-blue-900'
: answered && isCorrect
? 'text-green-900'
: answered && isSelected && !isCorrect
? 'text-red-900'
: 'text-slate-900'
}`}>
{option.text}
</p>
{option.riskLevel && answered && (
<div className="flex items-center gap-2 mt-2">
<AlertCircle className="w-4 h-4 text-amber-600" />
<span className={`text-xs font-semibold capitalize ${
option.riskLevel === 'critical' ? 'text-red-700' :
option.riskLevel === 'high' ? 'text-orange-700' :
option.riskLevel === 'medium' ? 'text-amber-700' :
'text-green-700'
}`}>
Risk: {option.riskLevel}
</span>
</div>
)}
</div>
</div>
</button>
);
})}
</div>
{/* Explanation */}
{answered && selectedOptionData && (
<div className={`p-4 rounded-lg ${
selectedOptionData.isCorrect
? 'bg-green-50 border border-green-200'
: 'bg-amber-50 border border-amber-200'
}`}>
<p className={`text-sm font-semibold mb-2 ${
selectedOptionData.isCorrect
? 'text-green-900'
: 'text-amber-900'
}`}>
{selectedOptionData.isCorrect ? '✓ Optimal Decision' : 'Why This Isn\'t Optimal:'}
</p>
<p className={`text-sm mb-3 ${
selectedOptionData.isCorrect
? 'text-green-800'
: 'text-amber-800'
}`}>
{selectedOptionData.explanation}
</p>
{!selectedOptionData.isCorrect && correctOption && (
<div className="mt-3 pt-3 border-t border-amber-200">
<p className="text-sm font-semibold text-green-900 mb-1">Better Approach:</p>
<p className="text-sm text-green-800">{correctOption.explanation}</p>
</div>
)}
</div>
)}
{/* Action Buttons */}
<div className="flex gap-3 pt-4">
{!answered ? (
<Button
onClick={handleSubmitDecision}
disabled={selectedOption === null}
className="flex-1"
>
Submit Decision
</Button>
) : (
<Button
onClick={handleNext}
className="flex-1 gap-2"
>
{currentDecisionIndex < scenario.decisionPoints.length - 1 ? 'Next Decision' : 'Complete Scenario'}
<ChevronRight size={18} />
</Button>
)}
</div>
</Card>
{/* Score Summary */}
{answered && (
<div className="bg-gradient-to-r from-blue-50 to-teal-50 rounded-lg p-4 border border-blue-200">
<p className="text-sm text-slate-600">
Decisions Made: <span className="font-bold text-blue-600">{currentDecisionIndex + 1} / {scenario.decisionPoints.length}</span>
</p>
</div>
)}
</div>
);
}