- 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>
273 lines
6.0 KiB
Vue
273 lines
6.0 KiB
Vue
<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>
|