235 lines
10 KiB
JavaScript
235 lines
10 KiB
JavaScript
// ===================== ACCUEIL =====================
|
|
|
|
function copyCode(code, el) {
|
|
navigator.clipboard.writeText(code).then(() => {
|
|
const icon = el.querySelector('.gms-code-copy-icon');
|
|
if (!icon) return;
|
|
icon.src = 'assets/images/Check.svg';
|
|
setTimeout(() => { icon.src = 'assets/images/file-copy-line.svg'; }, 1500);
|
|
});
|
|
}
|
|
|
|
const CLASS_COLORS = ['#E8922A','#534bac','#2884aa','#1a7c50','#9200a6','#d55600'];
|
|
|
|
function classInitials(name) {
|
|
return name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0,2);
|
|
}
|
|
|
|
function classColor(id) {
|
|
const idx = parseInt(id.replace(/\D/g,''), 10) || 0;
|
|
return CLASS_COLORS[idx % CLASS_COLORS.length];
|
|
}
|
|
|
|
function classProgressSummary(c) {
|
|
const started = c.students.filter(s => {
|
|
const p = DB.progression[s.id]; return p && totalSteps(p) > 0;
|
|
}).length;
|
|
const finished = c.students.filter(s => {
|
|
const p = DB.progression[s.id]; return p && totalSteps(p) >= 16;
|
|
}).length;
|
|
const n = c.students.length;
|
|
if (!n) return '<span class="gms-muted">Aucun élève</span>';
|
|
if (finished === n) return '<span class="status-badge success">✓ Tous terminés</span>';
|
|
if (started === 0) return '<span class="status-badge draft">Pas commencé</span>';
|
|
return `<span class="status-badge ongoing">${started}/${n} en cours</span>`;
|
|
}
|
|
|
|
function goToInterface() {
|
|
const card = document.getElementById('gmFlipCard');
|
|
const acceder = document.getElementById('gmAccederTab');
|
|
const tab = document.getElementById('gmGuideTab');
|
|
const nav = document.getElementById('gmNavTabs');
|
|
if (!card) return;
|
|
|
|
// Guide overlay ouvert sur une page interface (ex: une-classe) → juste re-flipper
|
|
if (S.route !== 'accueil') {
|
|
const clsTabs = document.getElementById('gmClassTabs');
|
|
if (acceder) acceder.classList.add('tab-down');
|
|
setTimeout(() => {
|
|
if (acceder) acceder.classList.add('tab-gone');
|
|
card.classList.add('is-flipped');
|
|
setTimeout(() => {
|
|
if (tab) { tab.classList.remove('tab-gone'); tab.classList.remove('tab-down'); }
|
|
if (nav) { nav.classList.remove('tab-gone'); nav.classList.remove('tab-down'); }
|
|
if (clsTabs) { clsTabs.classList.remove('tab-gone'); clsTabs.classList.remove('tab-down'); }
|
|
}, 500);
|
|
}, 400);
|
|
return;
|
|
}
|
|
|
|
// Page /guide avec historique → retour à la page précédente
|
|
if (S.history.length) {
|
|
S.back();
|
|
return;
|
|
}
|
|
|
|
// Page /guide, première visite → flip + mise à jour URL vers /mes-classes
|
|
S.history.push({ route: S.route, params: { ...S.params } });
|
|
S.params = { ...S.params, _flipped: true };
|
|
history.pushState(
|
|
{ route: S.route, params: S.params, hist: [...S.history] },
|
|
'',
|
|
BASE + '/mes-classes'
|
|
);
|
|
if (acceder) acceder.classList.add('tab-down');
|
|
setTimeout(() => {
|
|
if (acceder) acceder.classList.add('tab-gone');
|
|
card.classList.add('is-flipped');
|
|
setTimeout(() => {
|
|
if (tab) { tab.classList.remove('tab-gone'); tab.classList.remove('tab-down'); }
|
|
if (nav) { nav.classList.remove('tab-gone'); nav.classList.remove('tab-down'); }
|
|
}, 500);
|
|
}, 400);
|
|
}
|
|
|
|
function toggleFlip() {
|
|
const card = document.getElementById('gmFlipCard');
|
|
const tab = document.getElementById('gmGuideTab');
|
|
const acceder = document.getElementById('gmAccederTab');
|
|
const nav = document.getElementById('gmNavTabs');
|
|
const clsTabs = document.getElementById('gmClassTabs');
|
|
if (!card) return;
|
|
const wasFlipped = !!S.params._flipped;
|
|
if (tab) tab.classList.add('tab-down');
|
|
if (nav) nav.classList.add('tab-down');
|
|
if (clsTabs) clsTabs.classList.add('tab-down');
|
|
setTimeout(() => {
|
|
if (tab) tab.classList.add('tab-gone');
|
|
if (nav) nav.classList.add('tab-gone');
|
|
if (clsTabs) clsTabs.classList.add('tab-gone');
|
|
card.classList.remove('is-flipped');
|
|
setTimeout(() => {
|
|
if (acceder) { acceder.classList.remove('tab-gone'); acceder.classList.remove('tab-down'); }
|
|
// Si on avait pushé un état pour le flip, revenir en arrière (restaure /guide)
|
|
if (wasFlipped) history.back();
|
|
}, 500);
|
|
}, 400);
|
|
}
|
|
|
|
// Shell GMS partagé — utilisé par viewAccueil et viewUneClasse
|
|
// classTabs : { classes: [...], activeClassId: '...', tab: '...' } ou null
|
|
function renderGmsShell(frontContent, { startOnInterface = false, activeNavTab = 'classes', guideKey = 'classes', classTabs = null, wrapContent = true } = {}) {
|
|
const guideFns = { classes: guideClasses, modules: guideModules, suivi: guideSuivi };
|
|
const guideHtml = (guideFns[guideKey] || guideClasses)();
|
|
const navItems = [
|
|
{ id: 'classes', label: 'Mes classes', onclick: `S.navigate('accueil',{year:S.params.year,_flipped:true})` },
|
|
{ id: 'modules', label: 'Mes modules', onclick: `S.navigate('mes-activites',{year:S.params.year})` },
|
|
{ id: 'suivi', label: 'Suivi des élèves', onclick: `S.navigate('suivi-eleves',{year:S.params.year})` },
|
|
];
|
|
const tabHide = startOnInterface ? '' : ' tab-down tab-gone';
|
|
const accederHide = startOnInterface ? ' tab-down tab-gone' : '';
|
|
const cardClass = startOnInterface ? ' is-flipped' : '';
|
|
|
|
const classTabsHtml = classTabs ? `
|
|
<div class="gms-class-tabs" id="gmClassTabs">
|
|
${classTabs.classes.map(c => {
|
|
const isActive = c.id === classTabs.activeClassId;
|
|
return `<div class="gms-class-tab${isActive ? ' is-active' : ''}"
|
|
onclick="S.navigate('une-classe',{classId:'${c.id}',tab:'${classTabs.tab}'})">
|
|
<div class="gms-class-tab-name">${escHtml(c.name)}</div>
|
|
<div class="gms-class-tab-code">
|
|
<span class="gms-tab-code-wrap" onclick="copyCode(${jsSQ(c.code)},this);event.stopPropagation()">
|
|
<code>${escHtml(c.code || '—')}</code>
|
|
${c.code ? `<img class="gms-code-copy-icon" src="assets/images/file-copy-line.svg" alt="">` : ''}
|
|
</span>
|
|
</div>
|
|
</div>`;
|
|
}).join('')}
|
|
</div>` : '';
|
|
|
|
return `
|
|
<header class="gms-header">
|
|
<div class="gms-header-left">
|
|
<img src="assets/images/logo.png" onclick="S.navigate('accueil',{year:S.params.year})" style="cursor:pointer;display:block;height:auto;max-height:90px;object-fit:contain;" alt="Conquiers Ta Vie">
|
|
</div>
|
|
<div class="gms-header-right">
|
|
<button class="gms-user-btn" style="background:transparent;color:var(--gms-grey);border-color:var(--gms-grey-light);">
|
|
Alex Lefebvre
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="gms-page">
|
|
<div class="gms-guide-tab${tabHide}" id="gmGuideTab" onclick="toggleFlip()">
|
|
<img src="assets/images/question-line.svg" alt="Guide">
|
|
</div>
|
|
<div class="gms-guide-tab gms-acceder-tab${accederHide}" id="gmAccederTab" onclick="goToInterface()">
|
|
Accéder à l'interface
|
|
<img src="assets/images/play-circle-fill.svg" alt="">
|
|
</div>
|
|
${classTabsHtml}
|
|
<div class="gms-content-area">
|
|
<div class="gms-nav-tabs${tabHide}" id="gmNavTabs">
|
|
${navItems.map(n => `<div class="gms-nav-tab${n.id === activeNavTab ? ' is-active' : ''}" onclick="${n.onclick}">${n.label}</div>`).join('')}
|
|
</div>
|
|
<div class="gms-flip-card${cardClass}" id="gmFlipCard">
|
|
|
|
<div class="gms-flip-front">
|
|
${wrapContent ? `<div class="gms-page-content">${frontContent}</div>` : frontContent}
|
|
</div>
|
|
|
|
<div class="gms-flip-back">
|
|
${guideHtml}
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function viewAccueil() {
|
|
const fc = filteredClasses();
|
|
const frontContent = `
|
|
<div class="data-table">
|
|
<table class="data-table__table">
|
|
<thead>
|
|
<tr>
|
|
<th>Classe</th>
|
|
<th>Code</th>
|
|
<th>Élèves</th>
|
|
<th>Modules assignés</th>
|
|
<th>Progression</th>
|
|
<th class="actions-column">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${fc.length === 0 ? `
|
|
<tr><td colspan="6" style="text-align:center;padding:3rem;color:var(--color-grey);font-family:'Danzza',sans-serif;">
|
|
Aucune classe pour l'année ${S.params.year || '2025-2026'}.
|
|
<br><br>
|
|
<button class="btn btn--s btn--filled color-palette-ctv" onclick="showNewClassModal()">+ Nouvelle classe</button>
|
|
</td></tr>
|
|
` : fc.map(c => {
|
|
const assignedActs = DB.activities.filter(a => c.activities.includes(a.id) && a.status === 'published');
|
|
const color = classColor(c.id);
|
|
return `<tr onclick="S.navigate('une-classe',{classId:'${c.id}',tab:'activite'})" style="cursor:pointer;">
|
|
<td>
|
|
<div class="gms-class-link">
|
|
<div class="gms-class-thumbnail" style="background-color:${color};">${classInitials(c.name)}</div>
|
|
<span class="gms-class-name">${c.name}</span>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="gms-class-code-wrap" onclick="copyCode(${jsSQ(c.code)}, this); event.stopPropagation()">
|
|
<span class="gms-class-code">${c.code || '—'}</span>
|
|
<img class="gms-code-copy-icon" src="assets/images/file-copy-line.svg" alt="">
|
|
</span>
|
|
</td>
|
|
<td class="gms-muted">${c.students.length} élève${c.students.length !== 1 ? 's' : ''}</td>
|
|
<td class="gms-muted">
|
|
${assignedActs.length === 0
|
|
? '<span style="opacity:.5;">Aucun</span>'
|
|
: assignedActs.map(a => `<span style="margin-right:6px;">${a.name}</span>`).join('<span class="gms-dot">•</span>')}
|
|
</td>
|
|
<td>${classProgressSummary(c)}</td>
|
|
<td class="actions-cell">
|
|
<button class="btn btn--square btn--s btn--bordered color-palette-ctv" title="Renommer" onclick="showRenameClassModal('${c.id}');event.stopPropagation()"><i class="ri-pencil-line"></i></button>
|
|
<button class="btn btn--square btn--s btn--in-between color-palette-warning" title="Supprimer" onclick="confirmDeleteClass('${c.id}');event.stopPropagation()"><i class="ri-delete-bin-line"></i></button>
|
|
</td>
|
|
</tr>`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>`;
|
|
return renderGmsShell(frontContent, { startOnInterface: !!S.params._flipped, activeNavTab: 'classes' });
|
|
}
|