Files
xamxam/app/templates/partials/repertoire-index.php
Pontoporeia ecb90ba5dd Add accordion + active-filter chip bar for mobile repertoire
- repertoire-index.php: wrap each filter column in rep-accordion with toggle
  button, chevron, badge (active filter count); add rep-chip-bar with
  removable active-filter chips above the columns
- repertoire.css: mobile (≤640px) accordion mode — columns collapse to
  single-open accordion sections with 48px touch targets; chip bar becomes
  sticky; desktop/tablet layout unchanged via display:none on toggle elements
- repertoire.php: JS for single-accordion-open behavior on mobile, HTMX
  re-init after swap, resize-breakpoint cleanup
- docs/repertoire-mobile-propositions.md: analysis + 4 architecture proposals
2026-06-22 16:32:26 +02:00

225 lines
9.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Partial: répertoire index columns.
* Rendered both on full page load and as HTMX partial swap.
*
* Expected variables:
* $repData array output of Database::getRepertoireFilterData()
* $activeFilters array{years:int[], ap:string[], or:string[], fi:string[], kw:string[]}
*/
$activeSets = [
'years' => array_map('strval', $activeFilters['years'] ?? []),
'ap' => $activeFilters['ap'] ?? [],
'or' => $activeFilters['or'] ?? [],
'fi' => $activeFilters['fi'] ?? [],
'kw' => $activeFilters['kw'] ?? [],
];
// ── Students ────────────────────────────────────────────────────────────────
$studentWorks = [];
foreach ($repData['students'] as $s) {
if (empty($s['authors'])) continue;
foreach (explode(',', $s['authors']) as $name) {
$name = trim($name);
if ($name === '') continue;
$studentWorks[$name][] = (int)$s['id'];
}
}
ksort($studentWorks);
// ── Shared helpers ──────────────────────────────────────────────────────────
// AP abbreviation mapping (cf. maquette: diminutifs entre crochets)
const AP_ABBREVIATIONS = [
'Atelier Pratiques Situées' => '[APS]',
'Design et Politique du Multiple' => '[DPM]',
'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes' => '[L.I.E.N.S.]',
];
function formatApDisplay(string $name): string {
$abbr = AP_ABBREVIATIONS[$name] ?? '';
return $abbr !== '' ? "$name $abbr" : $name;
}
function repToggleUrl(array $sets, string $dim, string $value): string {
if (in_array($value, $sets[$dim], true)) {
$sets[$dim] = array_values(array_filter($sets[$dim], fn($v) => $v !== $value));
} else {
$sets[$dim][] = $value;
}
$params = [];
foreach ($sets['years'] as $v) $params[] = 'fy[]=' . urlencode((string)$v);
foreach ($sets['ap'] as $v) $params[] = 'ap[]=' . urlencode($v);
foreach ($sets['or'] as $v) $params[] = 'or[]=' . urlencode($v);
foreach ($sets['fi'] as $v) $params[] = 'fi[]=' . urlencode($v);
foreach ($sets['kw'] as $v) $params[] = 'kw[]=' . urlencode($v);
$qs = implode('&', $params);
return '/repertoire' . ($qs ? '?' . $qs : '');
}
function repFilterEntry(
array $item,
string $dim,
array $activeSets,
bool $anyActive,
bool $colHasMatch,
string $hx,
): void {
$val = (string)$item['value'];
$isActive = in_array($val, $activeSets[$dim], true);
$isFaded = $anyActive && $colHasMatch && !$item['matched'] && !$isActive;
$cls = 'rep-entry'
. ($isActive ? ' rep-entry--selected' : '')
. ($isFaded ? ' rep-entry--faded' : '');
$url = repToggleUrl($activeSets, $dim, $val);
?>
<li>
<button type="button" class="<?= $cls ?>"
aria-pressed="<?= $isActive ? 'true' : 'false' ?>"
<?= $isFaded ? 'disabled' : "hx-get=\"" . htmlspecialchars($url) . "\" $hx" ?>>
<?= htmlspecialchars($dim === 'ap' ? formatApDisplay($val) : $val) ?>
</button>
</li>
<?php
}
// ── Column definitions ──────────────────────────────────────────────────────
$hx = 'hx-target="#repertoire-index" hx-swap="outerHTML" hx-push-url="true" hx-indicator="#rep-indicator"';
$anyActive = !empty($activeSets['years']) || !empty($activeSets['ap'])
|| !empty($activeSets['or']) || !empty($activeSets['fi'])
|| !empty($activeSets['kw']);
$filterColumns = [
['dataKey' => 'years', 'dim' => 'years', 'heading' => 'Années'],
['dataKey' => 'ap_programs', 'dim' => 'ap', 'heading' => 'Ateliers Pluridisciplinaires'],
['dataKey' => 'orientations', 'dim' => 'or', 'heading' => 'Orientations'],
['dataKey' => 'finality_types', 'dim' => 'fi', 'heading' => 'Finalité du&nbsp;Master'],
['dataKey' => 'keywords', 'dim' => 'kw', 'heading' => 'Mots-clés'],
];
$colHasMatches = [];
foreach ($filterColumns as $col) {
$colHasMatches[$col['dim']] = !empty(array_filter(
$repData[$col['dataKey']],
fn($i) => $i['matched']
));
}
// ── Active filter chips ─────────────────────────────────────────────────────
// Build a flat list of {dim, label, value, url} for every active filter entry.
$activeChips = [];
$dimLabels = [
'years' => 'Année',
'ap' => 'AP',
'or' => 'Orientation',
'fi' => 'Finalité',
'kw' => 'Mot-clé',
];
foreach (['years', 'ap', 'or', 'fi', 'kw'] as $dim) {
foreach ($activeSets[$dim] as $val) {
$activeChips[] = [
'dim' => $dim,
'label' => $dimLabels[$dim],
'value' => $val,
'url' => repToggleUrl($activeSets, $dim, $val),
];
}
}
?>
<div id="repertoire-index" class="repertoire-index">
<?php if ($anyActive): ?>
<!-- Active filter chips bar -->
<div class="rep-chip-bar" role="status" aria-label="Filtres actifs">
<span class="rep-chip-bar__label">Filtres&nbsp;:</span>
<?php foreach ($activeChips as $chip): ?>
<button type="button" class="rep-chip"
hx-get="<?= htmlspecialchars($chip['url']) ?>"
hx-target="#repertoire-index"
hx-swap="outerHTML"
hx-push-url="true"
hx-indicator="#rep-indicator"
aria-label="Retirer le filtre <?= htmlspecialchars($chip['label']) ?> : <?= htmlspecialchars($chip['value']) ?>">
<span class="rep-chip__dim"><?= htmlspecialchars($chip['label']) ?></span>
<span class="rep-chip__value"><?= htmlspecialchars($chip['value']) ?></span>
<span class="rep-chip__remove" aria-hidden="true">×</span>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
// Render filter columns in the correct left-to-right order.
// Students column (non-filter) is inserted between keywords and AP/or/fi/years.
$renderOrder = ['years', 'ap', 'or', 'fi', 'students', 'kw'];
foreach ($renderOrder as $colKey):
if ($colKey === 'students'): ?>
<!-- ÉTUDIANTES -->
<section class="repertoire-col rep-accordion" data-col="students">
<h2>
<span class="rep-accordion__heading-text">Étudiant·es</span>
<button type="button" class="rep-accordion__toggle" aria-expanded="false">
Étudiant·es
<span class="rep-accordion__chevron" aria-hidden="true"></span>
</button>
</h2>
<div class="rep-accordion__panel">
<ul>
<?php if (empty($studentWorks)): ?>
<li class="rep-empty">—</li>
<?php else: ?>
<?php foreach ($studentWorks as $name => $ids): ?>
<?php
$firstId = $ids[0];
$targetUrl = count($ids) === 1 ? '/tfe?id=' . $firstId : '#';
$previewUrl = '/repertoire/student-preview?name=' . urlencode($name);
?>
<li class="student-entry">
<a href="<?= htmlspecialchars($targetUrl) ?>"
class="rep-entry rep-entry--link"
data-student-name="<?= htmlspecialchars($name) ?>"
hx-get="<?= htmlspecialchars($previewUrl) ?>"
hx-target="#student-popover"
hx-trigger="mouseenter"
hx-swap="innerHTML">
<?= htmlspecialchars($name) ?>
</a>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
</section>
<?php else:
$col = array_values(array_filter($filterColumns, fn($c) => $c['dim'] === $colKey))[0];
// Count active filters in this column for the badge
$activeCount = count($activeSets[$col['dim']]);
?>
<section class="repertoire-col rep-accordion" data-col="<?= $col['dim'] ?>">
<h2>
<span class="rep-accordion__heading-text"><?= $col['heading'] ?></span>
<button type="button" class="rep-accordion__toggle" aria-expanded="false">
<?= $col['heading'] ?>
<?php if ($activeCount > 0): ?>
<span class="rep-accordion__badge"><?= $activeCount ?></span>
<?php endif; ?>
<span class="rep-accordion__chevron" aria-hidden="true"></span>
</button>
</h2>
<div class="rep-accordion__panel">
<ul>
<?php foreach ($repData[$col['dataKey']] as $item):
repFilterEntry($item, $col['dim'], $activeSets, $anyActive, $colHasMatches[$col['dim']], $hx);
endforeach; ?>
</ul>
</div>
</section>
<?php endif;
endforeach; ?>
</div>