165 lines
5.3 KiB
Vue
165 lines
5.3 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<!-- Backdrop -->
|
|
<Transition name="backdrop">
|
|
<div
|
|
v-if="modelValue && orgId != null"
|
|
class="fixed inset-0 z-[1500]"
|
|
style="background: rgba(26,34,56,0.55);"
|
|
@click="close"
|
|
aria-hidden="true"
|
|
/>
|
|
</Transition>
|
|
|
|
<!-- Modal centré -->
|
|
<Transition name="modal">
|
|
<div
|
|
v-if="modelValue && orgId != null"
|
|
class="fixed z-[1501] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
|
|
style="
|
|
width: min(768px, 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"
|
|
aria-modal="true"
|
|
:aria-label="org?.nom ?? 'Fiche organisation'"
|
|
@keydown.esc="close"
|
|
>
|
|
<!-- Header modal -->
|
|
<div
|
|
class="flex items-center justify-between px-5 py-3 shrink-0 border-b"
|
|
style="background: var(--nav-surface); border-color: var(--nav-bg-alt);"
|
|
>
|
|
<span class="text-sm font-semibold" style="color: var(--nav-text-muted);">Fiche détaillée</span>
|
|
<div class="flex items-center gap-2">
|
|
<!-- Lien fiche complète -->
|
|
<a
|
|
v-if="orgId"
|
|
:href="`/fiche/${orgId}`"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-opacity hover:opacity-70"
|
|
style="background: var(--nav-bg-alt); color: var(--nav-text);"
|
|
>
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
|
<polyline points="15 3 21 3 21 9"/>
|
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
</svg>
|
|
Ouvrir
|
|
</a>
|
|
<!-- Fermer -->
|
|
<button
|
|
@click="close"
|
|
class="w-8 h-8 rounded-lg flex items-center justify-center transition-colors"
|
|
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
|
|
aria-label="Fermer"
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
|
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contenu scrollable -->
|
|
<div class="flex-1 overflow-y-auto px-5 py-5">
|
|
<!-- Chargement -->
|
|
<div v-if="pending" class="py-12 text-center text-sm" style="color: var(--nav-text-muted);">
|
|
Chargement de la fiche…
|
|
</div>
|
|
|
|
<!-- Erreur -->
|
|
<div v-else-if="error" class="py-12 text-center">
|
|
<p class="text-base font-semibold mb-2" style="color: var(--nav-text);">Fiche introuvable</p>
|
|
<p class="text-sm" style="color: var(--nav-text-muted);">L'organisation demandée n'existe pas ou a été supprimée.</p>
|
|
</div>
|
|
|
|
<!-- Contenu -->
|
|
<template v-else-if="org">
|
|
<FicheDetail :org="org" />
|
|
<div class="mb-5" style="height: 1px; background: var(--nav-bg-alt);"></div>
|
|
<CommentSection :org-id="org.Id" :refresh="commentRefreshTick" />
|
|
<CommentForm :org-id="org.Id" @submitted="onCommentSubmitted" />
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Org } from '~/types/org'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
orgId: number | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
}>()
|
|
|
|
function close() {
|
|
emit('update:modelValue', false)
|
|
}
|
|
|
|
// Fermeture Esc globale
|
|
onMounted(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape' && props.modelValue) close()
|
|
}
|
|
window.addEventListener('keydown', handler)
|
|
onUnmounted(() => window.removeEventListener('keydown', handler))
|
|
})
|
|
|
|
// Fetch fiche quand orgId change
|
|
const org = ref<Org | null>(null)
|
|
const pending = ref(false)
|
|
const error = ref(false)
|
|
|
|
watch(() => props.orgId, async (id) => {
|
|
if (id == null) return
|
|
pending.value = true
|
|
error.value = false
|
|
org.value = null
|
|
try {
|
|
org.value = await $fetch<Org>(`/api/fiche/${id}`)
|
|
} catch {
|
|
error.value = true
|
|
} finally {
|
|
pending.value = false
|
|
}
|
|
}, { immediate: true })
|
|
|
|
const commentRefreshTick = ref(0)
|
|
|
|
function onCommentSubmitted() {
|
|
commentRefreshTick.value++
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Backdrop */
|
|
.backdrop-enter-active, .backdrop-leave-active { transition: opacity 0.2s ease; }
|
|
.backdrop-enter-from, .backdrop-leave-to { opacity: 0; }
|
|
|
|
/* Modal */
|
|
.modal-enter-active, .modal-leave-active {
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
.modal-enter-from, .modal-leave-to {
|
|
opacity: 0;
|
|
transform: translate(-50%, -52%);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.backdrop-enter-active, .backdrop-leave-active { transition: none; }
|
|
.modal-enter-active, .modal-leave-active { transition: none; }
|
|
}
|
|
</style>
|