Feat: microlearning implementation
This commit is contained in:
@@ -1,83 +1,123 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Loader, BookOpen, Target, Layers, MessageCircle } from 'lucide-react';
|
||||
import { useMicroLearnings } from '../../hooks/useMicroLearnings';
|
||||
import MicroLearningContainer from './MicroLearningContainer';
|
||||
import Button from '../ui/Button';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const TYPE_LABELS = {
|
||||
'concept_explainer': 'Concept Explainer',
|
||||
'scenario_quiz': 'Scenario Quiz',
|
||||
'flashcard_set': 'Flashcard Set',
|
||||
'reflection_prompt': 'Reflection Prompt'
|
||||
};
|
||||
|
||||
const TYPE_DESCRIPTIONS = {
|
||||
'concept_explainer': 'Read a structured explanation to understand the concept.',
|
||||
'scenario_quiz': 'Apply your knowledge in a realistic workplace scenario.',
|
||||
'flashcard_set': 'Test your recall with a set of quick flashcards.',
|
||||
'reflection_prompt': 'Connect the topic to your own professional experience.'
|
||||
};
|
||||
const TYPES = [
|
||||
{
|
||||
key: 'concept_explainer',
|
||||
label: 'Concept Explainer',
|
||||
description: 'Read a structured explanation to understand the concept.',
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
key: 'scenario_quiz',
|
||||
label: 'Scenario Quiz',
|
||||
description: 'Apply your knowledge in a realistic workplace scenario.',
|
||||
icon: Target,
|
||||
},
|
||||
{
|
||||
key: 'flashcard_set',
|
||||
label: 'Flashcard Set',
|
||||
description: 'Test your recall with a set of quick flashcards.',
|
||||
icon: Layers,
|
||||
},
|
||||
{
|
||||
key: 'reflection_prompt',
|
||||
label: 'Reflection Prompt',
|
||||
description: 'Connect the topic to your own professional experience.',
|
||||
icon: MessageCircle,
|
||||
},
|
||||
];
|
||||
|
||||
export default function MicroLearningSelector({ topicId, sessionWeek, onTopicCompleted }) {
|
||||
const { getMicroLearningsByTopic } = useMicroLearnings();
|
||||
const [availableMLs, setAvailableMLs] = useState([]);
|
||||
const { getOrGenerate } = useMicroLearnings();
|
||||
const [selectedML, setSelectedML] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMLs = async () => {
|
||||
setLoading(true);
|
||||
const data = await getMicroLearningsByTopic(topicId);
|
||||
setAvailableMLs(data);
|
||||
const handleSelection = async (type) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const record = await getOrGenerate(topicId, type);
|
||||
setSelectedML(record);
|
||||
} catch (err) {
|
||||
console.error('[MicroLearningSelector] Generation failed:', err);
|
||||
setError(err.message || 'Failed to generate content. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
};
|
||||
if (topicId) {
|
||||
fetchMLs();
|
||||
}
|
||||
}, [topicId]);
|
||||
|
||||
const handleSelection = (ml) => {
|
||||
setSelectedML(ml);
|
||||
};
|
||||
|
||||
if (loading) return <div className="text-slate-500 text-center py-8">Loading learning formats...</div>;
|
||||
|
||||
if (availableMLs.length === 0) {
|
||||
return <div className="text-slate-500 text-center py-8">No micro learnings available for this topic yet.</div>;
|
||||
// Loading state while AI generates
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className="w-full text-center py-16">
|
||||
<Loader size={48} className="mx-auto text-teal animate-spin mb-4" />
|
||||
<p className="font-medium text-lg">AI is generating your learning module…</p>
|
||||
<p className="text-fg-muted text-sm mt-2">This may take 10–30 seconds.</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="w-full border border-red-200 bg-red-50 text-red-900 p-6">
|
||||
<p className="font-bold mb-1">Generation failed</p>
|
||||
<p className="text-sm mb-4">{error}</p>
|
||||
<button
|
||||
onClick={() => setError(null)}
|
||||
className="text-sm font-medium text-red-700 hover:text-red-900 underline"
|
||||
>
|
||||
← Back to selection
|
||||
</button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Render selected micro learning
|
||||
if (selectedML) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => setSelectedML(null)}
|
||||
<button
|
||||
onClick={() => setSelectedML(null)}
|
||||
className="text-fg-muted hover:text-teal mb-4 text-sm font-medium"
|
||||
>
|
||||
← Back to selection
|
||||
</button>
|
||||
<MicroLearningContainer
|
||||
microLearning={selectedML}
|
||||
sessionWeek={sessionWeek}
|
||||
onCompletedSuccessfully={onTopicCompleted}
|
||||
<MicroLearningContainer
|
||||
microLearning={selectedML}
|
||||
sessionWeek={sessionWeek}
|
||||
onCompletedSuccessfully={onTopicCompleted}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Type selection menu — always shows all 4 types
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<div className="mb-6 pb-4 border-b border-bg-warm">
|
||||
<h2 className="text-xl font-bold">Choose a Learning Format</h2>
|
||||
<p className="text-sm text-fg-muted mt-1">Select how you want to engage with this topic.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{availableMLs.map((ml) => (
|
||||
<div
|
||||
key={ml.id}
|
||||
className="cursor-pointer border-2 border-bg-warm rounded-[var(--r-md)] p-6 hover:border-teal hover:bg-teal/5 transition-all"
|
||||
onClick={() => handleSelection(ml)}
|
||||
{TYPES.map(({ key, label, description, icon: Icon }) => (
|
||||
<div
|
||||
key={key}
|
||||
className="cursor-pointer border-2 border-bg-warm rounded-[var(--r-md)] p-6 hover:border-teal hover:bg-teal/5 transition-all group"
|
||||
onClick={() => handleSelection(key)}
|
||||
>
|
||||
<h3 className="font-bold text-lg mb-2 text-teal">{TYPE_LABELS[ml.type] || ml.type}</h3>
|
||||
<p className="text-sm text-fg-muted">{TYPE_DESCRIPTIONS[ml.type]}</p>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-10 h-10 rounded-full bg-teal/10 flex items-center justify-center flex-shrink-0 group-hover:bg-teal/20 transition-colors">
|
||||
<Icon size={20} className="text-teal" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg text-teal">{label}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-fg-muted">{description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user