feat(frontend): Dashboard + Orders UI + Vehicle Picker + Vehicles list
- 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>
This commit is contained in:
132
frontend/src/components/orders/OrderLineForm.vue
Normal file
132
frontend/src/components/orders/OrderLineForm.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">Adauga linie</h4>
|
||||
<form @submit.prevent="handleSubmit" class="space-y-3">
|
||||
<!-- Tip -->
|
||||
<div class="flex gap-3">
|
||||
<label class="flex items-center gap-1.5 text-sm">
|
||||
<input v-model="form.tip" type="radio" value="manopera" class="text-blue-600" />
|
||||
Manopera
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm">
|
||||
<input v-model="form.tip" type="radio" value="material" class="text-blue-600" />
|
||||
Material
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Descriere -->
|
||||
<div>
|
||||
<input
|
||||
v-model="form.descriere"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Descriere operatiune / material"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Manopera fields -->
|
||||
<div v-if="form.tip === 'manopera'" class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Ore</label>
|
||||
<input
|
||||
v-model.number="form.ore"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Pret/ora (RON)</label>
|
||||
<input
|
||||
v-model.number="form.pret_ora"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Material fields -->
|
||||
<div v-if="form.tip === 'material'" class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Cantitate</label>
|
||||
<input
|
||||
v-model.number="form.cantitate"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Pret unitar (RON)</label>
|
||||
<input
|
||||
v-model.number="form.pret_unitar"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">UM</label>
|
||||
<input
|
||||
v-model="form.um"
|
||||
type="text"
|
||||
placeholder="buc"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total preview + submit -->
|
||||
<div class="flex items-center justify-between pt-2">
|
||||
<span class="text-sm text-gray-600">
|
||||
Total: <strong>{{ computedTotal.toFixed(2) }} RON</strong>
|
||||
</span>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-blue-600 text-white text-sm rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Adauga
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
|
||||
const emit = defineEmits(['add'])
|
||||
|
||||
const form = reactive({
|
||||
tip: 'manopera',
|
||||
descriere: '',
|
||||
ore: 0,
|
||||
pret_ora: 0,
|
||||
cantitate: 0,
|
||||
pret_unitar: 0,
|
||||
um: 'buc',
|
||||
})
|
||||
|
||||
const computedTotal = computed(() => {
|
||||
if (form.tip === 'manopera') return (form.ore || 0) * (form.pret_ora || 0)
|
||||
return (form.cantitate || 0) * (form.pret_unitar || 0)
|
||||
})
|
||||
|
||||
function handleSubmit() {
|
||||
if (!form.descriere) return
|
||||
emit('add', { ...form })
|
||||
// Reset
|
||||
form.descriere = ''
|
||||
form.ore = 0
|
||||
form.pret_ora = 0
|
||||
form.cantitate = 0
|
||||
form.pret_unitar = 0
|
||||
form.um = 'buc'
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user