- Systeme de themes adaptatifs : Peps (light chaud), Zen (light calme), Chagrine (dark violet), Grave (dark ambre) avec CSS custom properties - Dashboard d'accueil orienté onboarding avec cartes-portes et teaser boite a outils - SectionLayout reutilisable : liste + sidebar toolbox + status pills cliquables (En prepa / En vote / En vigueur / Clos) - ToolboxVignette : vignettes Contexte / Tutos / Choisir / Demarrer - Seed : Acte engagement certification + forgeron, Runtime Upgrade (decision on-chain), 3 modalites de vote (majoritaire, quadratique, permanent) - Backend adapte SQLite (Uuid portable, 204 fix, pool conditionnel) - Correction noms composants (pathPrefix: false), pinia/nuxt ^0.11 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
4.5 KiB
Vue
139 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* Visual timeline for mandate lifecycle steps.
|
|
*
|
|
* Displays each step with its type, status, and visual indicators.
|
|
* Similar pattern to DecisionWorkflow but with mandate-specific step types.
|
|
*/
|
|
import type { MandateStep } from '~/stores/mandates'
|
|
|
|
const props = defineProps<{
|
|
steps: MandateStep[]
|
|
currentStatus: string
|
|
}>()
|
|
|
|
const sortedSteps = computed(() => {
|
|
return [...props.steps].sort((a, b) => a.step_order - b.step_order)
|
|
})
|
|
|
|
const stepTypeLabel = (stepType: string) => {
|
|
switch (stepType) {
|
|
case 'candidacy': return 'Candidature'
|
|
case 'voting': return 'Vote'
|
|
case 'active': return 'Actif'
|
|
case 'reporting': return 'Rapport'
|
|
case 'completed': return 'Termine'
|
|
default: return stepType
|
|
}
|
|
}
|
|
|
|
const stepTypeIcon = (stepType: string) => {
|
|
switch (stepType) {
|
|
case 'candidacy': return 'i-lucide-user-plus'
|
|
case 'voting': return 'i-lucide-vote'
|
|
case 'active': return 'i-lucide-shield-check'
|
|
case 'reporting': return 'i-lucide-file-text'
|
|
case 'completed': return 'i-lucide-check-circle'
|
|
default: return 'i-lucide-circle'
|
|
}
|
|
}
|
|
|
|
function formatDate(dateStr: string): string {
|
|
return new Date(dateStr).toLocaleDateString('fr-FR', {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric',
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div v-if="sortedSteps.length === 0" class="text-center py-8">
|
|
<UIcon name="i-lucide-list-checks" class="text-4xl text-gray-400 mb-3" />
|
|
<p class="text-gray-500">Aucune etape definie pour ce mandat</p>
|
|
</div>
|
|
|
|
<div v-else class="relative">
|
|
<!-- Timeline line -->
|
|
<div class="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-200 dark:bg-gray-700" />
|
|
|
|
<!-- Steps -->
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="step in sortedSteps"
|
|
:key="step.id"
|
|
class="relative pl-12"
|
|
>
|
|
<!-- Timeline dot -->
|
|
<div
|
|
class="absolute left-2 w-5 h-5 rounded-full border-2 flex items-center justify-center"
|
|
:class="{
|
|
'bg-green-500 border-green-500': step.status === 'completed',
|
|
'bg-primary border-primary': step.status === 'active' || step.status === 'in_progress',
|
|
'bg-yellow-400 border-yellow-400': step.status === 'pending',
|
|
'bg-red-500 border-red-500': step.status === 'revoked',
|
|
'bg-white dark:bg-gray-900 border-gray-300 dark:border-gray-600': step.status === 'draft',
|
|
}"
|
|
>
|
|
<UIcon
|
|
v-if="step.status === 'completed'"
|
|
name="i-lucide-check"
|
|
class="text-white text-xs"
|
|
/>
|
|
<UIcon
|
|
v-else-if="step.status === 'revoked'"
|
|
name="i-lucide-x"
|
|
class="text-white text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<UCard>
|
|
<div class="space-y-2">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<UIcon :name="stepTypeIcon(step.step_type)" class="text-gray-500" />
|
|
<span class="text-sm font-mono text-gray-400">Etape {{ step.step_order }}</span>
|
|
<UBadge variant="subtle" color="neutral" size="xs">
|
|
{{ stepTypeLabel(step.step_type) }}
|
|
</UBadge>
|
|
</div>
|
|
<StatusBadge :status="step.status" type="mandate" />
|
|
</div>
|
|
|
|
<h3 v-if="step.title" class="font-medium text-gray-900 dark:text-white">
|
|
{{ step.title }}
|
|
</h3>
|
|
|
|
<p v-if="step.description" class="text-sm text-gray-600 dark:text-gray-400">
|
|
{{ step.description }}
|
|
</p>
|
|
|
|
<div class="text-xs text-gray-500">
|
|
Cree le {{ formatDate(step.created_at) }}
|
|
</div>
|
|
|
|
<div v-if="step.outcome" class="flex items-center gap-2 mt-2">
|
|
<UIcon name="i-lucide-flag" class="text-gray-400" />
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">
|
|
Resultat : {{ step.outcome }}
|
|
</span>
|
|
</div>
|
|
|
|
<div v-if="step.vote_session_id" class="mt-2">
|
|
<UButton
|
|
size="xs"
|
|
variant="soft"
|
|
color="primary"
|
|
icon="i-lucide-vote"
|
|
label="Voir la session de vote"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|