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>
This commit is contained in:
161
resources/js/Components/MetroMap/FloatingActions.vue
Normal file
161
resources/js/Components/MetroMap/FloatingActions.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user