fix(taff): 3 corrections UI — modal z-index, axes flex, cards layout

- Modal z-index 1501→10001 (au-dessus du header 9999)
- Axes modal: grid→flex avec flex-basis 130px (plus de wrap PRATIQUES PRO)
- Cartes: layout restructuré — tag / nom / axes / desc 3 lignes / footer séparé

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jules Neny
2026-05-07 00:48:56 +02:00
parent f0696a8fb3
commit 0378f2bd72
2 changed files with 181 additions and 91 deletions

View File

@@ -1,41 +1,26 @@
<template> <template>
<button <button
type="button" type="button"
class="w-full text-left rounded-xl border transition-all duration-200 hover:shadow-md focus-visible:outline-none" class="taff-card"
:style="` :style="`border-left-color: ${tagConfig.accent};`"
background: var(--nav-surface);
border-color: ${tagBorderColor};
border-left: 4px solid ${tagAccentColor};
`"
@click="$emit('open', plateforme)" @click="$emit('open', plateforme)"
> >
<!-- Header --> <!-- Ligne 1 : tag + badge AO + lien -->
<div class="flex items-start justify-between gap-2 px-4 pt-4 pb-2"> <div class="taff-card-top">
<div class="flex-1 min-w-0"> <div class="flex items-center gap-2 flex-wrap">
<div class="flex items-center gap-2 flex-wrap mb-0.5"> <span class="taff-tag" :style="`background: ${tagConfig.bg}; color: ${tagConfig.text};`">
<span {{ tagConfig.emoji }} {{ tagConfig.label }}
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold shrink-0" </span>
:style="`background: ${tagBgColor}; color: ${tagTextColor};`" <span
> v-if="plateforme.type === 'appel-offre-public'"
<span>{{ tagEmoji }}</span> class="taff-badge-ao"
<span>{{ tagLabel }}</span> >AO public</span>
</span>
<span
v-if="plateforme.type === 'appel-offre-public'"
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium shrink-0"
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
>AO public</span>
</div>
<h3 class="font-semibold text-base leading-snug" style="color: var(--nav-text);">
{{ plateforme.nom }}
</h3>
</div> </div>
<a <a
:href="plateforme.url" :href="plateforme.url"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="shrink-0 flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-opacity hover:opacity-70" class="taff-visit-btn"
style="background: var(--nav-bg-alt); color: var(--nav-text);"
@click.stop @click.stop
title="Visiter le site" title="Visiter le site"
> >
@@ -48,42 +33,35 @@
</a> </a>
</div> </div>
<!-- Description courte --> <!-- Ligne 2 : nom -->
<p class="px-4 pb-3 text-sm leading-relaxed line-clamp-2" style="color: var(--nav-text-muted);"> <div class="taff-card-name">{{ plateforme.nom }}</div>
{{ plateforme.description_courte }}
</p>
<!-- Scoring axes --> <!-- Ligne 3 : axes (icône + score, compacts) -->
<div class="px-4 pb-3 flex items-center gap-2 flex-wrap"> <div class="taff-card-axes">
<template v-for="axe in axes" :key="axe.id"> <template v-for="axe in AXES" :key="axe.id">
<span <span
v-if="plateforme.scoring[axe.id] !== null" v-if="plateforme.scoring[axe.id] !== null"
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium" class="taff-axe-chip"
:style="`background: ${axeScoreBg(plateforme.scoring[axe.id])}; color: ${axeScoreText(plateforme.scoring[axe.id])};`" :style="`background: ${axeScoreBg(plateforme.scoring[axe.id])}; color: ${axeScoreText(plateforme.scoring[axe.id])};`"
:title="axe.label" :title="axe.label"
> >{{ axe.icon }} {{ plateforme.scoring[axe.id] }}</span>
<span>{{ axe.icon }}</span>
<span>{{ plateforme.scoring[axe.id] }}</span>
</span>
</template> </template>
</div> </div>
<!-- Footer: secteurs + coût --> <!-- Ligne 4 : description (3 lignes max, lisible) -->
<div class="px-4 pb-3 flex items-center gap-2 flex-wrap"> <p class="taff-card-desc">{{ plateforme.description_courte }}</p>
<span
v-for="s in plateforme.secteurs_servis.slice(0, 3)" <!-- Ligne 5 : secteurs + coût -->
:key="s" <div class="taff-card-footer">
class="inline-block px-2 py-0.5 rounded-full text-xs" <div class="flex items-center gap-1.5 flex-wrap">
style="background: var(--nav-bg); color: var(--nav-text-muted); border: 1px solid var(--nav-bg-alt);" <span
>{{ secteurLabel(s) }}</span> v-for="s in plateforme.secteurs_servis.slice(0, 3)"
<span :key="s"
v-if="plateforme.secteurs_servis.length > 3" class="taff-secteur-chip"
class="text-xs" >{{ SECTEUR_LABELS[s] ?? s }}</span>
style="color: var(--nav-text-muted);" <span v-if="plateforme.secteurs_servis.length > 3" class="taff-more">+{{ plateforme.secteurs_servis.length - 3 }}</span>
>+{{ plateforme.secteurs_servis.length - 3 }}</span> </div>
<span class="ml-auto text-xs font-medium" style="color: var(--nav-text-muted);"> <span class="taff-cout">{{ COUT_LABELS[plateforme.cout_entree] ?? plateforme.cout_entree }}</span>
{{ coutLabel(plateforme.cout_entree) }}
</span>
</div> </div>
</button> </button>
</template> </template>
@@ -94,27 +72,21 @@ import type { PlateformeTaff } from '~/types/plateforme-taff'
const props = defineProps<{ plateforme: PlateformeTaff }>() const props = defineProps<{ plateforme: PlateformeTaff }>()
defineEmits<{ open: [p: PlateformeTaff] }>() defineEmits<{ open: [p: PlateformeTaff] }>()
const axes = [ const AXES = [
{ id: 'remuneration' as const, icon: '🪙', label: 'Rémunération' }, { id: 'remuneration' as const, icon: '🪙', label: 'Rémunération' },
{ id: 'transparence' as const, icon: '🔍', label: 'Transparence' }, { id: 'transparence' as const, icon: '🔍', label: 'Transparence' },
{ id: 'pratiques' as const, icon: '⚖️', label: 'Pratiques pro' }, { id: 'pratiques' as const, icon: '⚖️', label: 'Pratiques pro' },
{ id: 'ecologie' as const, icon: '🌿', label: 'Écologie' }, { id: 'ecologie' as const, icon: '🌿', label: 'Écologie' },
{ id: 'matching' as const, icon: '🎯', label: 'Matching' }, { id: 'matching' as const, icon: '🎯', label: 'Matching' },
] ]
const TAG_CONFIG = { const TAG_CONFIG = {
'recommande': { emoji: '✅', label: 'Recommandé AEP', accent: '#5a7a4a', bg: 'rgba(90,122,74,0.12)', text: '#3d5534', border: 'rgba(90,122,74,0.25)' }, 'recommande': { emoji: '✅', label: 'Recommandé AEP', accent: '#5a7a4a', bg: 'rgba(90,122,74,0.12)', text: '#3d5534' },
'sous-reserve': { emoji: '⚠️', label: 'Sous réserve', accent: '#c4a472', bg: 'rgba(196,164,114,0.15)', text: '#7a5f2a', border: 'rgba(196,164,114,0.35)' }, 'sous-reserve': { emoji: '⚠️', label: 'Sous réserve', accent: '#c4a472', bg: 'rgba(196,164,114,0.15)', text: '#7a5f2a' },
'a-eviter': { emoji: '❌', label: 'À éviter', accent: '#a85d3e', bg: 'rgba(168,93,62,0.12)', text: '#7a3322', border: 'rgba(168,93,62,0.25)' }, 'a-eviter': { emoji: '❌', label: 'À éviter', accent: '#a85d3e', bg: 'rgba(168,93,62,0.12)', text: '#7a3322' },
} }
const tagConfig = computed(() => TAG_CONFIG[props.plateforme.scoring.tag_global] ?? TAG_CONFIG['sous-reserve']) const tagConfig = computed(() => TAG_CONFIG[props.plateforme.scoring.tag_global] ?? TAG_CONFIG['sous-reserve'])
const tagEmoji = computed(() => tagConfig.value.emoji)
const tagLabel = computed(() => tagConfig.value.label)
const tagAccentColor = computed(() => tagConfig.value.accent)
const tagBgColor = computed(() => tagConfig.value.bg)
const tagTextColor = computed(() => tagConfig.value.text)
const tagBorderColor = computed(() => tagConfig.value.border)
function axeScoreBg(score: string | null) { function axeScoreBg(score: string | null) {
if (score === '✅') return 'rgba(90,122,74,0.12)' if (score === '✅') return 'rgba(90,122,74,0.12)'
@@ -122,7 +94,6 @@ function axeScoreBg(score: string | null) {
if (score === '❌') return 'rgba(168,93,62,0.12)' if (score === '❌') return 'rgba(168,93,62,0.12)'
return 'var(--nav-bg-alt)' return 'var(--nav-bg-alt)'
} }
function axeScoreText(score: string | null) { function axeScoreText(score: string | null) {
if (score === '✅') return '#3d5534' if (score === '✅') return '#3d5534'
if (score === '⚠️') return '#7a5f2a' if (score === '⚠️') return '#7a5f2a'
@@ -131,24 +102,143 @@ function axeScoreText(score: string | null) {
} }
const SECTEUR_LABELS: Record<string, string> = { const SECTEUR_LABELS: Record<string, string> = {
'renovation': 'Rénovation', 'renovation': 'Rénovation', 'construction-neuve': 'Neuf', 'urbanisme': 'Urbanisme',
'construction-neuve': 'Neuf', 'architecture-interieure': 'Archi intérieure', 'paysage': 'Paysage',
'urbanisme': 'Urbanisme', 'mar-conseil': 'MAR/Conseil', 'transversal': 'Transversal',
'architecture-interieure': 'Archi intérieure',
'paysage': 'Paysage',
'mar-conseil': 'MAR/Conseil',
'transversal': 'Transversal',
} }
function secteurLabel(s: string) { return SECTEUR_LABELS[s] ?? s }
const COUT_LABELS: Record<string, string> = { const COUT_LABELS: Record<string, string> = {
'gratuit': 'Gratuit', 'gratuit': 'Gratuit', 'freemium': 'Freemium', 'abonnement': 'Abonnement',
'freemium': 'Freemium', 'lead-paye': 'Lead payant', 'commission': 'Commission',
'abonnement': 'Abonnement', }
'lead-paye': 'Lead payant', </script>
'commission': 'Commission',
<style scoped>
.taff-card {
width: 100%;
text-align: left;
border-radius: 12px;
border: 1px solid var(--nav-bg-alt);
border-left: 4px solid;
background: var(--nav-surface);
display: flex;
flex-direction: column;
transition: box-shadow 0.2s;
cursor: pointer;
}
.taff-card:hover { box-shadow: 0 4px 16px rgba(26,34,56,0.1); }
.taff-card:focus-visible { outline: 2px solid var(--nav-accent); outline-offset: 2px; }
.taff-card-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 1rem 1rem 0.5rem;
} }
function coutLabel(c: string) { return COUT_LABELS[c] ?? c } .taff-tag {
</script> display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.625rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 700;
}
.taff-badge-ao {
display: inline-flex;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.6875rem;
font-weight: 500;
background: var(--nav-bg-alt);
color: var(--nav-text-muted);
}
.taff-visit-btn {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 500;
background: var(--nav-bg-alt);
color: var(--nav-text);
white-space: nowrap;
flex-shrink: 0;
transition: opacity 0.15s;
}
.taff-visit-btn:hover { opacity: 0.7; }
.taff-card-name {
padding: 0.25rem 1rem 0.75rem;
font-size: 1.0625rem;
font-weight: 700;
color: var(--nav-text);
line-height: 1.3;
}
.taff-card-axes {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0 1rem 0.875rem;
flex-wrap: wrap;
}
.taff-axe-chip {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.625rem;
border-radius: 9999px;
font-size: 0.8125rem;
font-weight: 600;
}
.taff-card-desc {
padding: 0 1rem 1rem;
font-size: 0.875rem;
line-height: 1.65;
color: var(--nav-text-muted);
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
flex: 1;
}
.taff-card-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-top: 1px solid var(--nav-bg-alt);
flex-wrap: wrap;
}
.taff-secteur-chip {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
background: var(--nav-bg);
color: var(--nav-text-muted);
border: 1px solid var(--nav-bg-alt);
}
.taff-more {
font-size: 0.75rem;
color: var(--nav-text-muted);
}
.taff-cout {
font-size: 0.75rem;
font-weight: 600;
color: var(--nav-text-muted);
white-space: nowrap;
}
</style>

View File

@@ -148,7 +148,7 @@
<Transition name="taff-backdrop"> <Transition name="taff-backdrop">
<div <div
v-if="modalPlateforme" v-if="modalPlateforme"
class="fixed inset-0 z-[1500]" class="fixed inset-0 z-[10000]"
style="background: rgba(26,34,56,0.55);" style="background: rgba(26,34,56,0.55);"
@click="closeModal" @click="closeModal"
aria-hidden="true" aria-hidden="true"
@@ -157,7 +157,7 @@
<Transition name="taff-modal"> <Transition name="taff-modal">
<div <div
v-if="modalPlateforme" v-if="modalPlateforme"
class="fixed z-[1501] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col" class="fixed z-[10001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
style="width: min(760px, 92vw); max-height: 90vh; background: var(--nav-bg); border-radius: 16px; box-shadow: 0 16px 64px rgba(26,34,56,0.28); overflow: hidden;" style="width: min(760px, 92vw); max-height: 90vh; background: var(--nav-bg); border-radius: 16px; box-shadow: 0 16px 64px rgba(26,34,56,0.28); overflow: hidden;"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
@@ -457,8 +457,8 @@ const parsedDescription = computed(() => {
/* Modal body helpers */ /* Modal body helpers */
.modal-label { font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--nav-text-muted); margin-bottom: 0.75rem; } .modal-label { font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--nav-text-muted); margin-bottom: 0.75rem; }
.modal-axes { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 0.5rem; } .modal-axes { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.modal-axe { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; border-radius: 8px; } .modal-axe { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.875rem; border-radius: 8px; flex: 1 1 130px; min-width: 130px; }
.modal-meta-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; } .modal-meta-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; }
.modal-meta-item { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.6rem 0.875rem; border-radius: 8px; background: var(--nav-bg-alt); } .modal-meta-item { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.6rem 0.875rem; border-radius: 8px; background: var(--nav-bg-alt); }
.modal-meta-key { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; color: var(--nav-text-muted); } .modal-meta-key { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; color: var(--nav-text-muted); }