Files
innovatieplatform/resources/js/Components/MetroMap/FloatingActions.vue
znetsixe f0aca26642 Sprint 2: Live data, CRUD modals, commitments, document upload
Frontend:
- Connect MetroMap to live Inertia props (replace hardcoded demo data)
- Drill-down navigation via router.visit for project-level maps
- Reactive breadcrumb based on map level
- Empty state when no projects exist
- Reusable Modal component with retro styling
- ProjectForm and CommitmentForm with Inertia useForm
- FormInput reusable component (text, date, textarea, select)
- FloatingActions FAB button for creating projects/themes

Backend:
- CommitmentService + CommitmentController (CRUD, mark complete, overdue)
- DocumentService + DocumentController (upload, download, delete)
- MapController now passes users and speerpunten to frontend
- 7 new routes (4 commitment, 3 document)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:02:38 +02:00

162 lines
3.6 KiB
Vue

<script setup>
import { ref } from 'vue'
const emit = defineEmits(['create-project', 'create-theme'])
const menuOpen = ref(false)
const toggle = () => {
menuOpen.value = !menuOpen.value
}
const handleCreateProject = () => {
menuOpen.value = false
emit('create-project')
}
const handleCreateTheme = () => {
menuOpen.value = false
emit('create-theme')
}
</script>
<template>
<div class="fab-container">
<!-- Expanded menu -->
<Transition name="fab-menu">
<div v-if="menuOpen" class="fab-menu">
<button class="fab-menu-item" @click="handleCreateProject">
<span class="fab-menu-icon">+</span>
<span class="fab-menu-label">Nieuw project</span>
</button>
<button class="fab-menu-item" @click="handleCreateTheme">
<span class="fab-menu-icon">+</span>
<span class="fab-menu-label">Nieuw thema</span>
</button>
</div>
</Transition>
<!-- Main FAB button -->
<button
class="fab-btn"
:class="{ 'fab-btn--open': menuOpen }"
:title="menuOpen ? 'Sluiten' : 'Nieuw aanmaken'"
@click="toggle"
>
<span class="fab-icon">{{ menuOpen ? '[X]' : '[+]' }}</span>
</button>
</div>
</template>
<style scoped>
.fab-container {
position: fixed;
bottom: 64px; /* above the CLI bar */
right: 20px;
z-index: 150;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
}
.fab-btn {
width: 48px;
height: 48px;
border-radius: 50%;
background: #0f3460;
border: 2px solid #00d2ff;
color: #00d2ff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 0 12px rgba(0, 210, 255, 0.35),
0 0 24px rgba(0, 210, 255, 0.15);
transition: all 0.2s ease;
}
.fab-btn:hover {
background: rgba(0, 210, 255, 0.15);
box-shadow:
0 0 20px rgba(0, 210, 255, 0.5),
0 0 40px rgba(0, 210, 255, 0.2);
transform: scale(1.08);
}
.fab-btn--open {
border-color: #e94560;
color: #e94560;
box-shadow:
0 0 12px rgba(233, 69, 96, 0.35),
0 0 24px rgba(233, 69, 96, 0.15);
}
.fab-btn--open:hover {
box-shadow:
0 0 20px rgba(233, 69, 96, 0.5),
0 0 40px rgba(233, 69, 96, 0.2);
}
.fab-icon {
font-family: 'VT323', monospace;
font-size: 18px;
line-height: 1;
text-shadow: 0 0 8px currentColor;
}
.fab-menu {
display: flex;
flex-direction: column;
gap: 6px;
align-items: flex-end;
}
.fab-menu-item {
display: flex;
align-items: center;
gap: 8px;
background: #16213e;
border: 1px solid rgba(0, 210, 255, 0.4);
border-radius: 4px;
padding: 8px 14px;
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
box-shadow: 0 0 10px rgba(0, 210, 255, 0.1);
}
.fab-menu-item:hover {
background: rgba(0, 210, 255, 0.1);
border-color: #00d2ff;
box-shadow: 0 0 16px rgba(0, 210, 255, 0.25);
}
.fab-menu-icon {
font-family: 'VT323', monospace;
font-size: 16px;
color: #00d2ff;
text-shadow: 0 0 6px rgba(0, 210, 255, 0.5);
}
.fab-menu-label {
font-family: 'VT323', monospace;
font-size: 16px;
color: #e8e8e8;
letter-spacing: 0.5px;
}
/* Menu transition */
.fab-menu-enter-active,
.fab-menu-leave-active {
transition: opacity 0.15s ease, transform 0.15s ease;
}
.fab-menu-enter-from,
.fab-menu-leave-to {
opacity: 0;
transform: translateY(8px) scale(0.97);
}
</style>