feat: Configuration des compétences par cycle [#16]

This commit is contained in:
N3WT DE COMPET
2025-05-18 00:45:49 +02:00
parent 2888f8dcce
commit 4e5aab6db7
29 changed files with 1001 additions and 82 deletions

View File

@ -0,0 +1,144 @@
import React, {
useState,
useEffect,
forwardRef,
useImperativeHandle,
} from 'react';
import { CheckCircle, Circle } from 'lucide-react';
const TreeView = forwardRef(function TreeView(
{ data, expandAll, onSelectionChange },
ref
) {
const [openDomains, setOpenDomains] = useState({});
const [openCategories, setOpenCategories] = useState({});
const [selectedCompetencies, setSelectedCompetencies] = useState({}); // { [competence_id]: true }
// N'ouvre ou ne ferme tout que si expandAll change explicitement
useEffect(() => {
if (!data) return;
const allDomains = {};
const allCategories = {};
if (expandAll) {
data.forEach((domaine) => {
allDomains[domaine.domaine_id] = true;
domaine.categories.forEach((cat) => {
allCategories[cat.categorie_id] = true;
});
});
setOpenDomains(allDomains);
setOpenCategories(allCategories);
} else {
// On ne ferme tout que si l'utilisateur décoche explicitement "Tout dérouler"
setOpenDomains({});
setOpenCategories({});
}
}, [expandAll]); // <-- uniquement expandAll
// Appelle le callback à chaque changement de sélection
useEffect(() => {
if (onSelectionChange) {
const selected = Object.entries(selectedCompetencies)
.filter(([_, selected]) => selected)
.map(([id]) => id);
onSelectionChange(selected);
}
}, [selectedCompetencies, onSelectionChange]);
const toggleDomain = (id) =>
setOpenDomains((prev) => ({ ...prev, [id]: !prev[id] }));
const toggleCategory = (id) =>
setOpenCategories((prev) => ({ ...prev, [id]: !prev[id] }));
const handleCompetenceClick = (competence) => {
if (competence.state === 'required') return;
setSelectedCompetencies((prev) => {
const next = {
...prev,
[competence.competence_id]: !prev[competence.competence_id],
};
console.log(competence);
return next;
});
};
// Pour exposer la sélection au parent
useImperativeHandle(ref, () => ({
getSelectedCompetencies: () => {
const selected = Object.entries(selectedCompetencies)
.filter(([_, selected]) => selected)
.map(([id]) => id);
return selected;
},
clearSelection: () => setSelectedCompetencies({}),
}));
return (
<div>
{data.map((domaine) => (
<div key={domaine.domaine_id} className="mb-4">
<button
className="w-full text-left px-3 py-2 bg-emerald-100 hover:bg-emerald-200 rounded font-semibold text-emerald-800"
onClick={() => toggleDomain(domaine.domaine_id)}
>
{openDomains[domaine.domaine_id] ? '▼' : '►'} {domaine.domaine_nom}
</button>
{openDomains[domaine.domaine_id] && (
<div className="ml-4">
{domaine.categories.map((categorie) => (
<div key={categorie.categorie_id} className="mb-2">
<button
className="w-full text-left px-2 py-1 bg-emerald-50 hover:bg-emerald-100 rounded text-emerald-700"
onClick={() => toggleCategory(categorie.categorie_id)}
>
{openCategories[categorie.categorie_id] ? '▼' : '►'}
{categorie.categorie_nom}
</button>
{openCategories[categorie.categorie_id] && (
<ul className="ml-4">
{categorie.competences.map((competence) => {
const isSelected =
selectedCompetencies[competence.competence_id];
return (
<li
key={competence.competence_id}
className={`py-1 flex items-center gap-2 ${
competence.state === 'required'
? 'text-emerald-700 font-bold'
: competence.state === 'custom'
? isSelected
? 'text-gray-500 cursor-pointer hover:text-emerald-600'
: 'text-emerald-600 font-semibold cursor-pointer'
: isSelected
? 'text-emerald-600 font-semibold cursor-pointer'
: 'text-gray-500 cursor-pointer hover:text-emerald-600'
}`}
onClick={() => handleCompetenceClick(competence)}
style={{
cursor:
competence.state === 'required'
? 'default'
: 'pointer',
userSelect: 'none',
}}
>
{competence.state === 'required' && (
<CheckCircle className="w-4 h-4 text-emerald-500" />
)}
{competence.nom}
</li>
);
})}
</ul>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
);
});
export default TreeView;