- Pinia stores: orders (CRUD, line management, totals recalc, stats) and vehicles (CRUD, search, marca/model cascade) - useSync composable: auto-sync on window focus + periodic 60s interval - VehiclePicker component: debounced autocomplete search by nr. inmatriculare or client name - OrderLineForm component: manopera/material toggle with live total preview - DashboardView: stats cards (orders, vehicles, revenue), recent orders list - OrdersListView: filterable table (all/draft/validat/facturat), clickable rows - OrderCreateView: vehicle picker + inline new vehicle form, tip deviz select, km/observatii - OrderDetailView: order info, lines table with add/remove, totals, validate action - VehiclesListView: searchable table, inline create form with marca/model cascade - AppLayout: mobile hamburger menu with slide-in sidebar overlay Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.8 KiB
Vue
110 lines
3.8 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gray-50">
|
|
<!-- Mobile sidebar overlay -->
|
|
<div
|
|
v-if="mobileMenuOpen"
|
|
class="md:hidden fixed inset-0 z-40 bg-black/50"
|
|
@click="mobileMenuOpen = false"
|
|
/>
|
|
|
|
<!-- Sidebar (desktop always visible, mobile slide-in) -->
|
|
<aside
|
|
class="fixed inset-y-0 z-50 flex w-56 flex-col transition-transform md:translate-x-0"
|
|
:class="mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'"
|
|
>
|
|
<div class="flex flex-col flex-grow bg-gray-900 overflow-y-auto">
|
|
<div class="px-4 py-5 flex items-center justify-between">
|
|
<h1 class="text-xl font-bold text-white">ROA AUTO</h1>
|
|
<button @click="mobileMenuOpen = false" class="md:hidden text-gray-400 hover:text-white">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<nav class="flex-1 px-2 space-y-1">
|
|
<router-link
|
|
v-for="item in navItems"
|
|
:key="item.path"
|
|
:to="item.path"
|
|
class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-300 hover:bg-gray-800 hover:text-white"
|
|
active-class="!bg-gray-800 !text-white"
|
|
@click="mobileMenuOpen = false"
|
|
>
|
|
{{ item.label }}
|
|
</router-link>
|
|
</nav>
|
|
<div class="px-4 py-3 border-t border-gray-700">
|
|
<div class="text-xs text-gray-400 mb-1">{{ auth.plan }} plan</div>
|
|
<SyncIndicator />
|
|
<button
|
|
@click="logout"
|
|
class="mt-2 w-full text-left text-sm text-gray-400 hover:text-white"
|
|
>
|
|
Deconectare
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main content -->
|
|
<div class="md:pl-56">
|
|
<!-- Mobile header -->
|
|
<header class="md:hidden bg-white border-b border-gray-200 px-4 py-3 flex items-center justify-between">
|
|
<button @click="mobileMenuOpen = true" class="text-gray-600 hover:text-gray-900">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
|
|
</button>
|
|
<h1 class="text-lg font-bold text-gray-900">ROA AUTO</h1>
|
|
<SyncIndicator />
|
|
</header>
|
|
|
|
<main class="p-4 md:p-6 pb-20 md:pb-6">
|
|
<slot />
|
|
</main>
|
|
|
|
<!-- Mobile bottom nav -->
|
|
<nav class="md:hidden fixed bottom-0 inset-x-0 bg-white border-t border-gray-200 flex z-30">
|
|
<router-link
|
|
v-for="item in mobileNavItems"
|
|
:key="item.path"
|
|
:to="item.path"
|
|
class="flex-1 flex flex-col items-center py-2 text-xs text-gray-500"
|
|
active-class="!text-blue-600"
|
|
>
|
|
{{ item.label }}
|
|
</router-link>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useAuthStore } from '../stores/auth.js'
|
|
import SyncIndicator from '../components/common/SyncIndicator.vue'
|
|
|
|
const router = useRouter()
|
|
const auth = useAuthStore()
|
|
const mobileMenuOpen = ref(false)
|
|
|
|
const navItems = [
|
|
{ path: '/dashboard', label: 'Dashboard' },
|
|
{ path: '/orders', label: 'Comenzi' },
|
|
{ path: '/vehicles', label: 'Vehicule' },
|
|
{ path: '/appointments', label: 'Programari' },
|
|
{ path: '/catalog', label: 'Catalog' },
|
|
{ path: '/settings', label: 'Setari' },
|
|
]
|
|
|
|
const mobileNavItems = [
|
|
{ path: '/dashboard', label: 'Acasa' },
|
|
{ path: '/orders', label: 'Comenzi' },
|
|
{ path: '/vehicles', label: 'Vehicule' },
|
|
{ path: '/appointments', label: 'Programari' },
|
|
{ path: '/settings', label: 'Setari' },
|
|
]
|
|
|
|
function logout() {
|
|
auth.logout()
|
|
router.push('/login')
|
|
}
|
|
</script>
|