mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
The sticky chip bar added noise: users already see their active filters highlighted in the accordion columns (with rep-entry--selected styling and the active-count badge on each toggle). Removing chips shifts focus back to the accordions, consistent with the expectation that users return to the dropdowns to adjust filters.
185 lines
7.4 KiB
PHP
185 lines
7.4 KiB
PHP
<?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 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']
|
|
));
|
|
}
|
|
|
|
?>
|
|
<div id="repertoire-index" class="repertoire-index">
|
|
|
|
<?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>
|