- 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>
133 lines
4.1 KiB
Vue
133 lines
4.1 KiB
Vue
<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>
|