admin frontend elkezdése, járművek tisztázása, frontend fejleszts

This commit is contained in:
Roo
2026-06-15 18:52:38 +00:00
parent ef8df9608c
commit 213ba3b0f1
80 changed files with 11615 additions and 2304 deletions

View File

@@ -119,8 +119,17 @@
<!-- Action Buttons -->
<div class="flex-1 flex flex-col gap-2.5 justify-center">
<!-- No vehicle warning -->
<div
v-if="!selectedActionVehicle"
class="flex items-center justify-center rounded-xl bg-amber-50 border border-amber-200 px-4 py-3 text-sm text-amber-700"
>
<span> Nincs kiválasztva jármű. Adj hozzá egy járművet először!</span>
</div>
<!-- Tankolás (Big, prominent) -->
<button
v-if="selectedActionVehicle"
@click="openFuelModal"
class="flex items-center gap-3 rounded-xl bg-gradient-to-r from-sf-accent to-emerald-500 px-4 py-3 text-sm font-bold text-white shadow-md transition-all hover:shadow-lg hover:scale-[1.02] active:scale-[0.98]"
>
@@ -131,6 +140,7 @@
<!-- 🅿 Parkolás / Útdíj (Medium) -->
<button
v-if="selectedActionVehicle"
@click="openFeeModal"
class="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-700 transition-all hover:bg-slate-100 hover:shadow-md active:scale-[0.98]"
>
@@ -141,6 +151,7 @@
<!-- További költségek (Medium) -->
<button
v-if="selectedActionVehicle"
@click="openComplexModal"
class="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-700 transition-all hover:bg-slate-100 hover:shadow-md active:scale-[0.98]"
>
@@ -318,8 +329,8 @@
<PrivateVehicleManager
v-if="activeCard === 'vehicles'"
:target-vehicle-id="selectedTargetId"
:edit-target-vehicle="editTargetVehicle"
@clear-edit-target="editTargetVehicle = null"
@add-new="handleAddNew"
@edit-vehicle="handleEditVehicle"
/>
<!-- Fallback for other cards -->
@@ -370,23 +381,49 @@
@edit="handleEditFromDetail"
@set-primary="handleSetPrimaryFromDetail"
/>
<!--
Vehicle Form Modal (Add) standalone, decoupled from Manager
-->
<VehicleFormModal
:is-open="isAddFormOpen"
@close="isAddFormOpen = false"
@saved="onAddVehicleSaved"
/>
<!--
Vehicle Form Modal (Edit) standalone, decoupled from Manager
-->
<VehicleFormModal
:is-open="isEditFormOpen"
:vehicle="editTargetVehicle"
@close="closeEditForm"
@saved="onEditVehicleSaved"
@deleted="onVehicleDeleted"
/>
</div><!-- end min-h-screen -->
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../stores/auth'
import { useVehicleStore } from '../stores/vehicle'
import type { VehicleData } from '../types/vehicle'
import api from '../api/axios'
import PrivateVehicleManager from '../components/dashboard/PrivateVehicleManager.vue'
import VehicleCardCompact from '../components/vehicle/VehicleCardCompact.vue'
import VehicleDetailModal from '../components/vehicle/VehicleDetailModal.vue'
import VehicleFormModal from '../components/dashboard/VehicleFormModal.vue'
import SimpleFuelModal from '../components/dashboard/SimpleFuelModal.vue'
import ComplexExpenseModal from '../components/dashboard/ComplexExpenseModal.vue'
const { t } = useI18n()
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const vehicleStore = useVehicleStore()
@@ -405,9 +442,6 @@ const openCard = (cardId: string, targetId: string | null = null) => {
const isVehicleDetailOpen = ref(false)
const detailVehicle = ref<VehicleData | null>(null)
// ── Task 3: Edit target for direct VehicleFormModal open ──
const editTargetVehicle = ref<VehicleData | null>(null)
function openVehicleDetail(vehicle: VehicleData) {
detailVehicle.value = vehicle
isVehicleDetailOpen.value = true
@@ -418,11 +452,115 @@ function closeVehicleDetail() {
detailVehicle.value = null
}
// ── Vehicle Form Modal State (standalone, decoupled from Manager) ──
const isAddFormOpen = ref(false)
const isEditFormOpen = ref(false)
const editTargetVehicle = ref<VehicleData | null>(null)
const isCheckingQuota = ref(false)
/**
* Return-State flag: when true, closing the edit form should re-open
* the VehicleDetailModal instead of returning to the dashboard.
*/
const returnToVehicleDetail = ref(false)
/**
* Preemptive quota check before opening the add-vehicle modal.
* Calls GET /api/v1/assets/vehicles/quota-status.
* If the user has reached their limit, shows a blocking toast/alert instead of opening the form.
*/
async function handleAddNew() {
if (isCheckingQuota.value) return
isCheckingQuota.value = true
try {
const res = await api.get('/assets/vehicles/quota-status')
const { can_add, current_count, limit } = res.data
if (!can_add) {
alert(
`Elérted a csomagodhoz tartozó járműlimitet (${current_count}/${limit})! ` +
'Kérjük, válts magasabb csomagra az új jármű rögzítéséhez.'
)
return
}
isAddFormOpen.value = true
} catch (err: any) {
console.error('[DashboardView] Quota check failed:', err)
isAddFormOpen.value = true
} finally {
isCheckingQuota.value = false
}
}
function onAddVehicleSaved() {
isAddFormOpen.value = false
}
function handleEditVehicle(vehicle: VehicleData) {
editTargetVehicle.value = vehicle ? { ...vehicle } : null
isEditFormOpen.value = true
}
function closeEditForm() {
isEditFormOpen.value = false
// If we came from the detail modal, return to it
if (returnToVehicleDetail.value) {
returnToVehicleDetail.value = false
const vehicle = editTargetVehicle.value
editTargetVehicle.value = null
if (vehicle) {
nextTick(() => {
detailVehicle.value = vehicle
isVehicleDetailOpen.value = true
})
}
} else {
editTargetVehicle.value = null
}
}
function onEditVehicleSaved(savedVehicle?: VehicleData) {
isEditFormOpen.value = false
// If we came from the detail modal, return to it
if (returnToVehicleDetail.value) {
returnToVehicleDetail.value = false
// Use the saved vehicle (with updated data) if available, otherwise fallback
const vehicle = savedVehicle || editTargetVehicle.value
editTargetVehicle.value = null
if (vehicle) {
nextTick(() => {
detailVehicle.value = vehicle
isVehicleDetailOpen.value = true
})
}
} else {
editTargetVehicle.value = null
}
}
/**
* Called when the edit VehicleFormModal emits 'deleted'.
* Completely resets the return-state and closes the edit form,
* then refreshes the vehicle list so the deleted vehicle disappears.
*/
function onVehicleDeleted() {
returnToVehicleDetail.value = false
isEditFormOpen.value = false
editTargetVehicle.value = null
vehicleStore.fetchVehicles()
}
// ── Bridge: Detail → Edit (opens edit modal directly, no Manager card) ──
function handleEditFromDetail(vehicle: VehicleData) {
returnToVehicleDetail.value = true
closeVehicleDetail()
// Task 3: Open the vehicles card AND pass the vehicle to edit directly
editTargetVehicle.value = vehicle
activeCard.value = 'vehicles'
// Use nextTick so detail modal closes before edit opens
nextTick(() => {
editTargetVehicle.value = vehicle ? { ...vehicle } : null
isEditFormOpen.value = true
})
}
function handleSetPrimaryFromDetail(vehicle: VehicleData) {
@@ -497,12 +635,28 @@ const badges = ref([
onMounted(() => {
vehicleStore.fetchVehicles()
authStore.fetchMyOrganizations()
// ── Handle query param card navigation ──────────────────────────
const cardParam = route.query.card as string | undefined
if (cardParam) {
openCard(cardParam)
// Clean up the query param so it doesn't re-trigger
router.replace({ query: {} })
}
})
// Watch for vehicles to load, then auto-select
watch(() => vehicleStore.vehicles.length, () => {
autoSelectVehicle()
}, { immediate: true })
// ── Watch for query param changes (hamburger menu navigation) ────
watch(() => route.query.card, (newCard) => {
if (newCard && typeof newCard === 'string') {
openCard(newCard)
router.replace({ query: {} })
}
})
</script>
<style scoped>