- 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>
109 lines
3.3 KiB
Vue
109 lines
3.3 KiB
Vue
<template>
|
|
<div class="relative">
|
|
<label v-if="label" class="block text-sm font-medium text-gray-700 mb-1">{{ label }}</label>
|
|
<input
|
|
v-model="query"
|
|
type="text"
|
|
:placeholder="placeholder"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
@input="onSearch"
|
|
@focus="showDropdown = true"
|
|
/>
|
|
<!-- Selected vehicle display -->
|
|
<div v-if="selected" class="mt-1 text-sm text-gray-600">
|
|
{{ selected.nr_inmatriculare }} - {{ selected.client_nume }}
|
|
<span v-if="selected.marca_denumire"> ({{ selected.marca_denounire }} {{ selected.model_denumire }})</span>
|
|
<button @click="clear" class="ml-2 text-red-500 hover:text-red-700 text-xs">Sterge</button>
|
|
</div>
|
|
<!-- Dropdown results -->
|
|
<ul
|
|
v-if="showDropdown && results.length > 0"
|
|
class="absolute z-10 mt-1 w-full bg-white border border-gray-200 rounded-md shadow-lg max-h-48 overflow-auto"
|
|
>
|
|
<li
|
|
v-for="v in results"
|
|
:key="v.id"
|
|
class="px-3 py-2 hover:bg-blue-50 cursor-pointer text-sm"
|
|
@mousedown="selectVehicle(v)"
|
|
>
|
|
<span class="font-medium">{{ v.nr_inmatriculare }}</span>
|
|
<span class="text-gray-500 ml-2">{{ v.client_nume }}</span>
|
|
<span v-if="v.marca_denumire" class="text-gray-400 ml-1">
|
|
({{ v.marca_denumire }} {{ v.model_denumire }})
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
<!-- No results -->
|
|
<div
|
|
v-if="showDropdown && query.length >= 2 && results.length === 0 && !loading"
|
|
class="absolute z-10 mt-1 w-full bg-white border border-gray-200 rounded-md shadow-lg p-3 text-sm text-gray-500"
|
|
>
|
|
Niciun vehicul gasit
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
import { useVehiclesStore } from '../../stores/vehicles.js'
|
|
|
|
const props = defineProps({
|
|
modelValue: { type: String, default: null },
|
|
label: { type: String, default: 'Vehicul' },
|
|
placeholder: { type: String, default: 'Cauta dupa nr. inmatriculare sau client...' },
|
|
})
|
|
const emit = defineEmits(['update:modelValue', 'select'])
|
|
|
|
const vehiclesStore = useVehiclesStore()
|
|
|
|
const query = ref('')
|
|
const results = ref([])
|
|
const selected = ref(null)
|
|
const showDropdown = ref(false)
|
|
const loading = ref(false)
|
|
|
|
let searchTimeout = null
|
|
|
|
function onSearch() {
|
|
if (searchTimeout) clearTimeout(searchTimeout)
|
|
searchTimeout = setTimeout(async () => {
|
|
if (query.value.length < 2) {
|
|
results.value = []
|
|
return
|
|
}
|
|
loading.value = true
|
|
results.value = await vehiclesStore.search(query.value)
|
|
loading.value = false
|
|
}, 200)
|
|
}
|
|
|
|
function selectVehicle(v) {
|
|
selected.value = v
|
|
query.value = v.nr_inmatriculare
|
|
showDropdown.value = false
|
|
emit('update:modelValue', v.id)
|
|
emit('select', v)
|
|
}
|
|
|
|
function clear() {
|
|
selected.value = null
|
|
query.value = ''
|
|
emit('update:modelValue', null)
|
|
emit('select', null)
|
|
}
|
|
|
|
// Load initial vehicle if modelValue is set
|
|
watch(() => props.modelValue, async (id) => {
|
|
if (id && !selected.value) {
|
|
const v = await vehiclesStore.getById(id)
|
|
if (v) {
|
|
selected.value = v
|
|
query.value = v.nr_inmatriculare
|
|
}
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// Close dropdown on outside click
|
|
function onClickOutside() { showDropdown.value = false }
|
|
</script>
|