feat(codev): skip fiche + annuaire table sticky + page QR code

This commit is contained in:
Jules Neny
2026-05-07 00:04:42 +02:00
parent 606b9f0a47
commit 142e5cf787
3 changed files with 226 additions and 17 deletions

View File

@@ -9,12 +9,12 @@
{{ fiches.length }} fiche{{ fiches.length !== 1 ? 's' : '' }} - clique sur un nom pour voir le detail {{ fiches.length }} fiche{{ fiches.length !== 1 ? 's' : '' }} - clique sur un nom pour voir le detail
</template> </template>
</p> </p>
<NuxtLink to="/codev/qr" class="qr-link" title="QR Code">[ QR ]</NuxtLink>
</header> </header>
<div class="codev-tabs"> <div class="codev-tabs">
<button :class="{ active: tab === 'carto' }" @click="tab = 'carto'" type="button">Carto</button> <button :class="{ active: tab === 'carto' }" @click="tab = 'carto'" type="button">Carto</button>
<button :class="{ active: tab === 'besoins' }" @click="tab = 'besoins'" type="button">Besoins</button> <button :class="{ active: tab === 'annuaire' }" @click="tab = 'annuaire'" type="button">Annuaire</button>
<button :class="{ active: tab === 'competences' }" @click="tab = 'competences'" type="button">Compétences</button>
</div> </div>
<div v-if="tab === 'carto'"> <div v-if="tab === 'carto'">
@@ -81,22 +81,32 @@
</div> </div>
</div> </div>
<div v-else-if="tab === 'besoins'" class="list-view"> <div v-else-if="tab === 'annuaire'" class="annuaire-wrap">
<div v-for="f in fiches" :key="f.id" class="list-card">
<div class="list-card-name">{{ f.nom }}</div> <div v-if="fiches.length === 0" class="list-empty">
<p class="list-card-text">{{ f.besoin }}</p> Aucune fiche. <NuxtLink to="/codev/fiche">Ajouter la mienne</NuxtLink>
<NuxtLink :to="`/codev/fiche?id=${f.id}`" class="list-card-link">Modifier</NuxtLink>
</div>
<div v-if="fiches.length === 0" class="list-empty">Aucune fiche. <NuxtLink to="/codev/fiche">Ajouter la mienne</NuxtLink></div>
</div> </div>
<div v-else-if="tab === 'competences'" class="list-view"> <div v-else class="annuaire-scroll">
<div v-for="f in fiches" :key="f.id" class="list-card"> <table class="annuaire-table">
<div class="list-card-name">{{ f.nom }}</div> <thead>
<p class="list-card-text">{{ f.offre }}</p> <tr>
<NuxtLink :to="`/codev/fiche?id=${f.id}`" class="list-card-link">Modifier</NuxtLink> <th class="col-nom">Prénom</th>
<th class="col-besoin">Besoin</th>
<th class="col-offre">Ce que j'offre</th>
</tr>
</thead>
<tbody>
<tr v-for="f in fiches" :key="f.id" @click="navigateTo(`/codev/fiche?id=${f.id}`)" class="annuaire-row">
<td class="col-nom">{{ f.nom }}</td>
<td class="col-besoin">{{ f.besoin }}</td>
<td class="col-offre">{{ f.offre }}</td>
</tr>
</tbody>
</table>
</div> </div>
<div v-if="fiches.length === 0" class="list-empty">Aucune fiche. <NuxtLink to="/codev/fiche">Ajouter la mienne</NuxtLink></div>
<p class="annuaire-hint">Clique sur une ligne pour modifier la fiche</p>
</div> </div>
<!-- FAB ajouter une fiche --> <!-- FAB ajouter une fiche -->
@@ -141,7 +151,7 @@ const fiches = computed(() => data.value?.list ?? [])
const matches = ref<CodevMatch[]>([]) const matches = ref<CodevMatch[]>([])
const mode = ref<'none' | 'solution' | 'alliance' | 'surprise'>('none') const mode = ref<'none' | 'solution' | 'alliance' | 'surprise'>('none')
const showLabels = ref(false) const showLabels = ref(false)
const tab = ref<'carto' | 'besoins' | 'competences'>('carto') const tab = ref<'carto' | 'annuaire'>('carto')
const selectedFiche = ref<CodevFiche | null>(null) const selectedFiche = ref<CodevFiche | null>(null)
const isMobileView = typeof window !== 'undefined' ? window.innerWidth < 600 : false const isMobileView = typeof window !== 'undefined' ? window.innerWidth < 600 : false
@@ -403,6 +413,96 @@ function onSelectFiche(id: number) {
.sheet-enter-active, .sheet-leave-active { transition: opacity 0.2s; } .sheet-enter-active, .sheet-leave-active { transition: opacity 0.2s; }
.sheet-enter-from, .sheet-leave-to { opacity: 0; } .sheet-enter-from, .sheet-leave-to { opacity: 0; }
/* ── QR link ── */
.qr-link {
font-size: 0.75rem;
color: #9ca3af;
text-decoration: none;
align-self: flex-end;
}
.qr-link:hover { color: #6b7280; }
/* ── Annuaire ── */
.annuaire-wrap {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.annuaire-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border: 1px solid #e5e7eb;
border-radius: 10px;
}
.annuaire-table {
width: 100%;
border-collapse: collapse;
min-width: 480px;
}
.annuaire-table thead tr {
background: #f9fafb;
border-bottom: 2px solid #e5e7eb;
}
.annuaire-table th {
padding: 10px 14px;
text-align: left;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6b7280;
white-space: nowrap;
}
.annuaire-table td {
padding: 12px 14px;
font-size: 0.875rem;
color: #374151;
vertical-align: top;
border-bottom: 1px solid #f3f4f6;
line-height: 1.5;
}
.annuaire-row {
cursor: pointer;
transition: background 0.12s;
}
.annuaire-row:hover { background: #f9fafb; }
.annuaire-row:last-child td { border-bottom: none; }
.col-nom {
position: sticky;
left: 0;
background: inherit;
z-index: 1;
font-weight: 600;
color: #1a1a2e !important;
white-space: nowrap;
min-width: 80px;
border-right: 1px solid #e5e7eb;
}
.annuaire-row:hover .col-nom { background: #f9fafb; }
thead tr .col-nom { background: #f9fafb; }
.col-besoin { min-width: 200px; max-width: 260px; }
.col-offre { min-width: 200px; max-width: 260px; }
.annuaire-hint {
font-size: 0.75rem;
color: #9ca3af;
text-align: center;
margin: 0;
}
/* ── Mobile ── */ /* ── Mobile ── */
@media (max-width: 600px) { @media (max-width: 600px) {

View File

@@ -109,6 +109,10 @@
{{ isEdit ? (loading ? 'Modification...' : 'Enregistrer les modifications') : (loading ? 'Envoi en cours...' : 'Ajouter ma fiche') }} {{ isEdit ? (loading ? 'Modification...' : 'Enregistrer les modifications') : (loading ? 'Envoi en cours...' : 'Ajouter ma fiche') }}
</button> </button>
<NuxtLink to="/codev/carto" class="skip-link">
Voir la carte sans créer de fiche →
</NuxtLink>
</form> </form>
</div> </div>
@@ -390,6 +394,17 @@ async function submit() {
cursor: not-allowed; cursor: not-allowed;
} }
.skip-link {
display: block;
text-align: center;
font-size: 0.825rem;
color: var(--nav-text-muted, #9ca3af);
text-decoration: none;
margin-top: 0.5rem;
padding: 0.5rem;
}
.skip-link:hover { color: var(--nav-text, #1a1a2e); }
/* ── Responsive ── */ /* ── Responsive ── */
@media (max-width: 480px) { @media (max-width: 480px) {

94
pages/codev/qr.vue Normal file
View File

@@ -0,0 +1,94 @@
<template>
<div class="qr-page">
<div class="qr-card">
<h1>Co-développement</h1>
<p class="qr-subtitle">Scanne pour rejoindre la session</p>
<img
:src="`https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=${encodeURIComponent(APP_URL)}&bgcolor=ffffff&color=1B4436&margin=2`"
alt="QR code aep.trans-former.fr/codev"
class="qr-img"
width="280"
height="280"
/>
<p class="qr-url">{{ APP_URL }}</p>
<p class="qr-password">Mot de passe : <strong>merci</strong></p>
<a :href="`https://api.qrserver.com/v1/create-qr-code/?size=600x600&data=${encodeURIComponent(APP_URL)}&bgcolor=ffffff&color=1B4436&margin=2`"
download="codev-qr.png"
class="qr-download"
target="_blank"
>
Télécharger le QR code
</a>
</div>
</div>
</template>
<script setup lang="ts">
const APP_URL = 'https://aep.trans-former.fr/codev'
useHead({ title: 'QR Code — Co-développement' })
</script>
<style scoped>
.qr-page {
min-height: 100vh;
background: var(--nav-bg, #fafafa);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.qr-card {
background: white;
border-radius: 16px;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
max-width: 360px;
width: 100%;
box-shadow: 0 4px 24px rgba(0,0,0,0.08);
text-align: center;
}
.qr-card h1 {
font-size: 1.25rem;
font-weight: 700;
color: #1a1a2e;
margin: 0;
}
.qr-subtitle {
font-size: 0.9rem;
color: #6b7280;
margin: 0;
}
.qr-img {
border-radius: 8px;
border: 2px solid #e5e7eb;
}
.qr-url {
font-size: 0.8rem;
color: #9ca3af;
margin: 0;
font-family: monospace;
}
.qr-password {
font-size: 0.95rem;
color: #374151;
margin: 0;
}
.qr-download {
display: inline-block;
padding: 10px 20px;
background: #1B4436;
color: white;
border-radius: 8px;
text-decoration: none;
font-size: 0.875rem;
font-weight: 600;
transition: opacity 0.15s;
}
.qr-download:hover { opacity: 0.88; }
</style>