Backend: genesis_json sur Document, section_tag/inertia_preset/is_permanent_vote sur DocumentItem Frontend: 5 nouveaux composants pour vue detail document enrichie - GenesisBlock: sources, outils, synthese forum, contributeurs (depliable) - InertiaSlider: visualisation inertie 4 niveaux avec params formule G/M - MiniVoteBoard: tableau vote compact (barre seuil, pour/contre, participation) - EngagementCard: carte item enrichie integrant vote + inertie + actions - DocumentTuto: modal pedagogique vote permanent/inertie/seuils Seed et page [slug] enrichis pour exploiter les nouveaux champs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
253 lines
5.8 KiB
Vue
253 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* EngagementCard — Enhanced item card with inline mini vote board,
|
|
* inertia indicator, and action buttons.
|
|
*
|
|
* Replaces the basic ItemCard for the document detail view.
|
|
*/
|
|
import type { DocumentItem } from '~/stores/documents'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
item: DocumentItem
|
|
documentSlug: string
|
|
showActions?: boolean
|
|
showVoteBoard?: boolean
|
|
}>(), {
|
|
showActions: false,
|
|
showVoteBoard: true,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
propose: [item: DocumentItem]
|
|
}>()
|
|
|
|
const isSection = computed(() => props.item.item_type === 'section')
|
|
const isPreamble = computed(() => props.item.item_type === 'preamble')
|
|
|
|
const itemTypeIcon = computed(() => {
|
|
switch (props.item.item_type) {
|
|
case 'clause': return 'i-lucide-shield-check'
|
|
case 'rule': return 'i-lucide-scale'
|
|
case 'verification': return 'i-lucide-check-circle'
|
|
case 'preamble': return 'i-lucide-scroll-text'
|
|
case 'section': return 'i-lucide-layout-list'
|
|
default: return 'i-lucide-file-text'
|
|
}
|
|
})
|
|
|
|
const itemTypeLabel = computed(() => {
|
|
switch (props.item.item_type) {
|
|
case 'clause': return 'Engagement'
|
|
case 'rule': return 'Regle'
|
|
case 'verification': return 'Verification'
|
|
case 'preamble': return 'Preambule'
|
|
case 'section': return 'Titre'
|
|
default: return props.item.item_type
|
|
}
|
|
})
|
|
|
|
function navigateToItem() {
|
|
navigateTo(`/documents/${props.documentSlug}/items/${props.item.id}`)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Section header (visual separator, not a card) -->
|
|
<div v-if="isSection" class="engagement-section">
|
|
<div class="engagement-section__line" />
|
|
<div class="engagement-section__content">
|
|
<h3 class="engagement-section__title">
|
|
{{ item.title }}
|
|
</h3>
|
|
<p class="engagement-section__text">
|
|
{{ item.current_text }}
|
|
</p>
|
|
<InertiaSlider
|
|
:preset="item.inertia_preset"
|
|
compact
|
|
class="mt-2 max-w-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Regular item card -->
|
|
<div
|
|
v-else
|
|
class="engagement-card"
|
|
:class="{
|
|
'engagement-card--preamble': isPreamble,
|
|
}"
|
|
>
|
|
<!-- Card header -->
|
|
<div class="engagement-card__header" @click="navigateToItem">
|
|
<div class="flex items-center gap-2.5 min-w-0">
|
|
<div class="engagement-card__position">
|
|
{{ item.position }}
|
|
</div>
|
|
<UIcon :name="itemTypeIcon" class="text-sm shrink-0" style="color: var(--mood-accent)" />
|
|
<span v-if="item.title" class="engagement-card__title">
|
|
{{ item.title }}
|
|
</span>
|
|
<UBadge variant="subtle" color="neutral" size="xs" class="shrink-0">
|
|
{{ itemTypeLabel }}
|
|
</UBadge>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Item text -->
|
|
<div class="engagement-card__body" @click="navigateToItem">
|
|
<MarkdownRenderer :content="item.current_text" />
|
|
</div>
|
|
|
|
<!-- Mini vote board -->
|
|
<div v-if="showVoteBoard" class="engagement-card__vote">
|
|
<MiniVoteBoard
|
|
:votes-for="10"
|
|
:votes-against="1"
|
|
:wot-size="7224"
|
|
:is-permanent="item.is_permanent_vote"
|
|
:inertia-preset="item.inertia_preset"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Inertia indicator -->
|
|
<div class="engagement-card__inertia">
|
|
<InertiaSlider
|
|
:preset="item.inertia_preset"
|
|
compact
|
|
/>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div v-if="showActions" class="engagement-card__actions">
|
|
<UButton
|
|
label="Proposer une alternative"
|
|
icon="i-lucide-pen-line"
|
|
variant="soft"
|
|
color="primary"
|
|
size="xs"
|
|
@click.stop="emit('propose', item)"
|
|
/>
|
|
<UButton
|
|
label="Voter"
|
|
icon="i-lucide-vote"
|
|
variant="soft"
|
|
color="success"
|
|
size="xs"
|
|
@click.stop="navigateToItem"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Section separator */
|
|
.engagement-section {
|
|
display: flex;
|
|
gap: 1rem;
|
|
padding: 1.5rem 0 0.5rem;
|
|
}
|
|
|
|
.engagement-section__line {
|
|
width: 4px;
|
|
background: var(--mood-accent);
|
|
border-radius: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.engagement-section__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.engagement-section__title {
|
|
font-size: 1.125rem;
|
|
font-weight: 800;
|
|
color: var(--mood-text);
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.engagement-section__text {
|
|
font-size: 0.8125rem;
|
|
color: var(--mood-text-muted);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Card */
|
|
.engagement-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--mood-surface);
|
|
border-radius: 14px;
|
|
overflow: hidden;
|
|
transition: box-shadow 0.15s, transform 0.15s;
|
|
}
|
|
|
|
.engagement-card:hover {
|
|
box-shadow: 0 2px 12px color-mix(in srgb, var(--mood-accent) 12%, transparent);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.engagement-card--preamble {
|
|
border-left: 4px solid color-mix(in srgb, var(--mood-accent) 40%, transparent);
|
|
}
|
|
|
|
.engagement-card__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0.875rem 1rem 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.engagement-card__position {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 2rem;
|
|
height: 1.625rem;
|
|
padding: 0 0.5rem;
|
|
border-radius: 8px;
|
|
background: var(--mood-accent);
|
|
color: white;
|
|
font-size: 0.6875rem;
|
|
font-weight: 800;
|
|
letter-spacing: 0.02em;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.engagement-card__title {
|
|
font-size: 0.875rem;
|
|
font-weight: 700;
|
|
color: var(--mood-text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.engagement-card__body {
|
|
padding: 0.5rem 1rem 0.75rem;
|
|
cursor: pointer;
|
|
font-size: 0.8125rem;
|
|
line-height: 1.6;
|
|
color: var(--mood-text);
|
|
}
|
|
|
|
.engagement-card__vote {
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.engagement-card__inertia {
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
|
|
.engagement-card__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.625rem 1rem;
|
|
border-top: 1px solid color-mix(in srgb, var(--mood-text) 6%, transparent);
|
|
}
|
|
</style>
|