Boîtes à outils enrichies : ContextMapper, SocioElection, WorkflowMilestones
- ContextMapper : 4 questions contexte → méthode de décision optimale (advice process Laloux, vote inertiel WoT, consentement sociocratique, Smith…) - SocioElection : guide élection sociocratique 6 étapes + advice process + clarté de rôle - WorkflowMilestones : 11 jalons de protocole (7 essentiels), durées recommandées, principes Ostrom - WorkspaceSelector : sélecteur de collectif multi-site dans le header - SectionLayout : toolbox en USlideover droit sur mobile, sidebar sticky desktop - Décisions : ContextMapper intégré + guide consentement - Mandats : SocioElection intégré + cycle de mandat - Documents : guide inertie 4 niveaux + structure + IPFS - Protocoles : WorkflowMilestones + protocole élection sociocratique ajouté - Renommage projet Glibredecision → libreDecision (dossier + sources) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
272
frontend/app/components/WorkspaceSelector.vue
Normal file
272
frontend/app/components/WorkspaceSelector.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* WorkspaceSelector — Sélecteur de collectif / espace de travail.
|
||||
* Compartimentage multi-collectifs, multi-sites.
|
||||
* UI-only pour l'instant, prêt pour le backend (collective_id sur toutes les entités).
|
||||
*/
|
||||
|
||||
interface Workspace {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
icon: string
|
||||
role?: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
// Mock data — sera remplacé par le store collectifs
|
||||
const workspaces: Workspace[] = [
|
||||
{
|
||||
id: 'g1-main',
|
||||
name: 'Duniter G1',
|
||||
slug: 'duniter-g1',
|
||||
icon: 'i-lucide-coins',
|
||||
role: 'Membre',
|
||||
color: 'accent',
|
||||
},
|
||||
{
|
||||
id: 'axiom',
|
||||
name: 'Axiom Team',
|
||||
slug: 'axiom-team',
|
||||
icon: 'i-lucide-layers',
|
||||
role: 'Admin',
|
||||
color: 'secondary',
|
||||
},
|
||||
]
|
||||
|
||||
const activeId = ref('g1-main')
|
||||
const isOpen = ref(false)
|
||||
|
||||
const active = computed(() => workspaces.find(w => w.id === activeId.value) ?? workspaces[0])
|
||||
|
||||
function selectWorkspace(id: string) {
|
||||
activeId.value = id
|
||||
isOpen.value = false
|
||||
// TODO: store.setActiveCollective(id) + refetch all data
|
||||
}
|
||||
|
||||
// Close on outside click
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', (e) => {
|
||||
if (containerRef.value && !containerRef.value.contains(e.target as Node)) {
|
||||
isOpen.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef" class="ws">
|
||||
<button class="ws__trigger" :class="{ 'ws__trigger--open': isOpen }" @click="isOpen = !isOpen">
|
||||
<div class="ws__icon" :class="`ws__icon--${active.color}`">
|
||||
<UIcon :name="active.icon" />
|
||||
</div>
|
||||
<span class="ws__name">{{ active.name }}</span>
|
||||
<UIcon name="i-lucide-chevrons-up-down" class="ws__caret" />
|
||||
</button>
|
||||
|
||||
<Transition name="dropdown">
|
||||
<div v-if="isOpen" class="ws__dropdown">
|
||||
<div class="ws__dropdown-header">
|
||||
Espace de travail
|
||||
</div>
|
||||
<div class="ws__items">
|
||||
<button
|
||||
v-for="ws in workspaces"
|
||||
:key="ws.id"
|
||||
class="ws__item"
|
||||
:class="{ 'ws__item--active': ws.id === activeId }"
|
||||
@click="selectWorkspace(ws.id)"
|
||||
>
|
||||
<div class="ws__item-icon" :class="`ws__icon--${ws.color}`">
|
||||
<UIcon :name="ws.icon" />
|
||||
</div>
|
||||
<div class="ws__item-info">
|
||||
<span class="ws__item-name">{{ ws.name }}</span>
|
||||
<span v-if="ws.role" class="ws__item-role">{{ ws.role }}</span>
|
||||
</div>
|
||||
<UIcon v-if="ws.id === activeId" name="i-lucide-check" class="ws__item-check" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="ws__dropdown-footer">
|
||||
<button class="ws__new-btn" disabled>
|
||||
<UIcon name="i-lucide-plus" />
|
||||
Nouveau collectif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ws {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ws__trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.375rem 0.625rem;
|
||||
background: var(--mood-accent-soft);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s ease;
|
||||
min-height: 2rem;
|
||||
max-width: 11rem;
|
||||
}
|
||||
|
||||
.ws__trigger:hover {
|
||||
background: color-mix(in srgb, var(--mood-accent-soft) 80%, var(--mood-accent) 20%);
|
||||
}
|
||||
|
||||
.ws__trigger--open {
|
||||
background: color-mix(in srgb, var(--mood-accent-soft) 60%, var(--mood-accent) 40%);
|
||||
}
|
||||
|
||||
.ws__icon {
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.ws__icon--accent { background: var(--mood-accent); color: var(--mood-accent-text); }
|
||||
.ws__icon--secondary {
|
||||
background: color-mix(in srgb, var(--mood-secondary, var(--mood-accent)) 20%, transparent);
|
||||
color: var(--mood-secondary, var(--mood-accent));
|
||||
}
|
||||
|
||||
.ws__name {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: var(--mood-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ws__caret {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mood-text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
.ws__dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.375rem);
|
||||
left: 0;
|
||||
min-width: 13rem;
|
||||
background: var(--mood-surface);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 8px 32px var(--mood-shadow);
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ws__dropdown-header {
|
||||
padding: 0.625rem 0.875rem 0.375rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.ws__items {
|
||||
padding: 0.25rem 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ws__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
padding: 0.625rem 0.625rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s ease;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ws__item:hover { background: var(--mood-accent-soft); }
|
||||
|
||||
.ws__item--active { background: var(--mood-accent-soft); }
|
||||
|
||||
.ws__item-icon {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.ws__item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ws__item-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text);
|
||||
}
|
||||
|
||||
.ws__item-role {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.ws__item-check {
|
||||
color: var(--mood-accent);
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ws__dropdown-footer {
|
||||
padding: 0.5rem;
|
||||
border-top: 1px solid var(--mood-accent-soft);
|
||||
}
|
||||
|
||||
.ws__new-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 10px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
background: none;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Transition */
|
||||
.dropdown-enter-active, .dropdown-leave-active {
|
||||
transition: all 0.15s ease;
|
||||
transform-origin: top left;
|
||||
}
|
||||
.dropdown-enter-from, .dropdown-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-4px);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user