Files
service-finder/frontend/src/components/vehicles/tabs/FinancialsTab.vue
2026-06-23 21:11:21 +00:00

709 lines
40 KiB
Vue

<template>
<div class="space-y-6">
<!-- -->
<!-- SECTION 1: Purchasing & Leasing -->
<!-- -->
<div class="rounded-2xl border border-slate-700 bg-slate-900/70 backdrop-blur-md p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="text-base font-bold text-white flex items-center gap-2">
<span>&#128184;</span> {{ t('finance.purchasing_section_title') || 'Beszerzés & Lízing' }}
</h3>
<button
@click="openFinancialsModal"
class="rounded-lg bg-sf-accent/20 px-3 py-1.5 text-xs font-medium text-sf-accent hover:bg-sf-accent/30 transition-colors cursor-pointer"
>
{{ t('common.edit') || 'Szerkesztés' }}
</button>
</div>
<!-- Loading State -->
<div v-if="loadingFinancials" class="flex items-center justify-center py-8">
<svg class="animate-spin h-6 w-6 text-sf-accent" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
</div>
<!-- Empty State -->
<div v-else-if="!financials" class="flex flex-col items-center justify-center py-8 text-slate-400">
<svg class="h-10 w-10 mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="text-sm">{{ t('finance.no_financials') || 'Még nincsenek pénzügyi adatok rögzítve.' }}</p>
<button
@click="openFinancialsModal"
class="mt-3 rounded-lg bg-sf-accent px-4 py-2 text-xs font-medium text-white hover:bg-sf-accent/90 transition-colors cursor-pointer"
>
{{ t('finance.add_financials') || 'Pénzügyi adatok hozzáadása' }}
</button>
</div>
<!-- Data Grid -->
<div v-else class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.purchase_price_gross') || 'Bruttó vételár' }}</p>
<p class="text-sm font-semibold text-white">
{{ formatCurrency(financials.purchase_price_gross, financials.currency) }}
</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.purchase_price_net') || 'Nettó vételár' }}</p>
<p class="text-sm font-semibold text-white">
{{ formatCurrency(financials.purchase_price_net, financials.currency) }}
</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.down_payment') || 'Önerő' }}</p>
<p class="text-sm font-semibold text-white">
{{ formatCurrency(financials.down_payment, financials.currency) }}
</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.monthly_installment') || 'Havi törlesztő' }}</p>
<p class="text-sm font-semibold text-white">
{{ formatCurrency(financials.monthly_installment, financials.currency) }}
</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.contract_number') || 'Szerződésszám' }}</p>
<p class="text-sm font-semibold text-white">{{ financials.contract_number || '—' }}</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.financing_provider') || 'Finanszírozó' }}</p>
<p class="text-sm font-semibold text-white">{{ financials.financing_provider || '—' }}</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.interest_rate') || 'Kamat' }}</p>
<p class="text-sm font-semibold text-white">{{ financials.interest_rate != null ? `${financials.interest_rate}%` : '—' }}</p>
</div>
<div class="rounded-xl bg-slate-800/50 p-3">
<p class="text-xs text-slate-400 mb-1">{{ t('finance.residual_value') || 'Maradványérték' }}</p>
<p class="text-sm font-semibold text-white">
{{ formatCurrency(financials.residual_value, financials.currency) }}
</p>
</div>
</div>
<!-- Contract Dates -->
<div v-if="financials?.contract_start_date || financials?.contract_end_date" class="mt-3 flex flex-wrap gap-4 text-xs text-slate-400">
<span v-if="financials.contract_start_date">
{{ t('finance.contract_start_date') || 'Szerződés kezdete' }}: <span class="text-slate-300">{{ formatDate(financials.contract_start_date) }}</span>
</span>
<span v-if="financials.contract_end_date">
{{ t('finance.contract_end_date') || 'Szerződés vége' }}: <span class="text-slate-300">{{ formatDate(financials.contract_end_date) }}</span>
</span>
</div>
</div>
<!-- -->
<!-- SECTION 2: Insurances & Taxes -->
<!-- -->
<div class="rounded-2xl border border-slate-700 bg-slate-900/70 backdrop-blur-md p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="text-base font-bold text-white flex items-center gap-2">
<span>&#128737;</span> {{ t('finance.insurance_section_title') || 'Biztosítások & Adók' }}
</h3>
<div class="flex gap-2">
<button
@click="openInsuranceModal"
class="rounded-lg bg-sf-accent/20 px-3 py-1.5 text-xs font-medium text-sf-accent hover:bg-sf-accent/30 transition-colors cursor-pointer"
>
{{ t('finance.add_insurance') || '+ Biztosítás' }}
</button>
<button
@click="openTaxModal"
class="rounded-lg bg-amber-500/20 px-3 py-1.5 text-xs font-medium text-amber-400 hover:bg-amber-500/30 transition-colors cursor-pointer"
>
{{ t('finance.add_tax') || '+ Adó' }}
</button>
</div>
</div>
<!-- Loading State -->
<div v-if="loadingInsurance" class="flex items-center justify-center py-8">
<svg class="animate-spin h-6 w-6 text-sf-accent" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
</div>
<!-- Empty State -->
<div v-else-if="insurancePolicies.length === 0 && taxObligations.length === 0" class="flex flex-col items-center justify-center py-8 text-slate-400">
<svg class="h-10 w-10 mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p class="text-sm">{{ t('finance.no_insurance_taxes') || 'Még nincsenek biztosítások vagy adók rögzítve.' }}</p>
</div>
<div v-else class="space-y-4">
<!-- Insurance Cards -->
<div v-for="policy in insurancePolicies" :key="policy.id" class="rounded-xl border border-slate-700/50 bg-slate-800/30 p-4">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="rounded-full bg-blue-500/20 px-2.5 py-0.5 text-xs font-medium text-blue-400">
{{ policy.policy_type === 'kgfb' ? 'KGFB' : policy.policy_type === 'casco' ? 'CASCO' : policy.policy_type === 'both' ? 'KGFB + CASCO' : (policy.policy_type || t('finance.insurance') || 'Biztosítás') }}
</span>
<span class="text-sm font-semibold text-white">{{ policy.insurer_name || '—' }}</span>
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs">
<div>
<span class="text-slate-400">{{ t('finance.policy_number') || 'Kötvényszám' }}:</span>
<span class="ml-1 text-slate-200">{{ policy.policy_number || '—' }}</span>
</div>
<div>
<span class="text-slate-400">{{ t('finance.premium') || 'Díj' }}:</span>
<span class="ml-1 text-slate-200">{{ formatCurrency(policy.premium, policy.currency) }}</span>
</div>
<div>
<span class="text-slate-400">{{ t('finance.valid_from') || 'Érvényes' }}:</span>
<span class="ml-1 text-slate-200">{{ formatDate(policy.valid_from) }} {{ formatDate(policy.valid_until) }}</span>
</div>
</div>
</div>
<button
@click="deleteInsurance(policy)"
class="ml-2 rounded-lg p-1.5 text-slate-500 hover:bg-red-500/20 hover:text-red-400 transition-colors cursor-pointer"
:title="t('common.delete') || 'Törlés'"
>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
<!-- Tax Cards -->
<div v-for="tax in taxObligations" :key="tax.id" class="rounded-xl border border-slate-700/50 bg-slate-800/30 p-4">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="rounded-full bg-amber-500/20 px-2.5 py-0.5 text-xs font-medium text-amber-400">
{{ tax.tax_type || (t('finance.tax') || 'Adó') }}
</span>
<span class="text-sm font-semibold text-white">{{ formatCurrency(tax.amount, tax.currency) }}</span>
<span v-if="tax.paid" class="rounded-full bg-green-500/20 px-2 py-0.5 text-xs font-medium text-green-400">
{{ t('finance.paid') || 'Fizetve' }}
</span>
<span v-else class="rounded-full bg-red-500/20 px-2 py-0.5 text-xs font-medium text-red-400">
{{ t('finance.unpaid') || 'Fizetendő' }}
</span>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 text-xs">
<div>
<span class="text-slate-400">{{ t('finance.due_date') || 'Esedékesség' }}:</span>
<span class="ml-1 text-slate-200">{{ formatDate(tax.due_date) }}</span>
</div>
</div>
</div>
<button
@click="deleteTax(tax)"
class="ml-2 rounded-lg p-1.5 text-slate-500 hover:bg-red-500/20 hover:text-red-400 transition-colors cursor-pointer"
:title="t('common.delete') || 'Törlés'"
>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- -->
<!-- SECTION 3: TCO / Expenses -->
<!-- -->
<div class="rounded-2xl border border-slate-700 bg-slate-900/70 backdrop-blur-md p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="text-base font-bold text-white flex items-center gap-2">
<span>&#128202;</span> {{ t('finance.tco_section_title') || 'TCO / Költségek' }}
</h3>
<button
@click="openCostModal"
class="rounded-lg bg-sf-accent/20 px-3 py-1.5 text-xs font-medium text-sf-accent hover:bg-sf-accent/30 transition-colors cursor-pointer"
>
{{ t('finance.add_cost') || '+ Költség' }}
</button>
</div>
<!-- Placeholder for TCO / Expenses -->
<div class="flex flex-col items-center justify-center py-8 text-slate-400">
<svg class="h-10 w-10 mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<p class="text-sm">{{ t('finance.tco_placeholder') || 'A TCO / költségadatok itt jelennek meg.' }}</p>
</div>
</div>
<!-- -->
<!-- FINANCIALS MODAL (inline edit) -->
<!-- -->
<Teleport to="body">
<div v-if="showFinancialsModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4" @click.self="closeFinancialsModal">
<div class="w-full max-w-lg rounded-2xl border border-slate-700 bg-slate-900 p-6 shadow-2xl">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-bold text-white">{{ t('finance.edit_financials') || 'Beszerzés & Lízing adatok' }}</h3>
<button @click="closeFinancialsModal" class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-800 hover:text-white transition-colors cursor-pointer">
<svg class="h-5 w-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>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.purchase_price_net') || 'Nettó vételár' }}</label>
<input v-model.number="financialsForm.purchase_price_net" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.purchase_price_gross') || 'Bruttó vételár' }}</label>
<input v-model.number="financialsForm.purchase_price_gross" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.down_payment') || 'Önerő' }}</label>
<input v-model.number="financialsForm.down_payment" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.monthly_installment') || 'Havi törlesztő' }}</label>
<input v-model.number="financialsForm.monthly_installment" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.contract_number') || 'Szerződésszám' }}</label>
<input v-model="financialsForm.contract_number" type="text" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.financing_provider') || 'Finanszírozó' }}</label>
<input v-model="financialsForm.financing_provider" type="text" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.interest_rate') || 'Kamat (%)' }}</label>
<input v-model.number="financialsForm.interest_rate" type="number" step="0.01" min="0" max="100" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.residual_value') || 'Maradványérték' }}</label>
<input v-model.number="financialsForm.residual_value" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.contract_start_date') || 'Szerződés kezdete' }}</label>
<input v-model="financialsForm.contract_start_date" type="date" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.contract_end_date') || 'Szerződés vége' }}</label>
<input v-model="financialsForm.contract_end_date" type="date" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
</div>
<div class="mt-6 flex justify-end gap-3">
<button @click="closeFinancialsModal" class="rounded-xl border border-slate-600 px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 transition-colors cursor-pointer">
{{ t('common.cancel') || 'Mégse' }}
</button>
<button @click="saveFinancials" :disabled="savingFinancials" class="rounded-xl bg-sf-accent px-4 py-2 text-sm font-medium text-white hover:bg-sf-accent/90 disabled:opacity-50 transition-colors cursor-pointer">
{{ savingFinancials ? (t('common.saving') || 'Mentés...') : (t('common.save') || 'Mentés') }}
</button>
</div>
</div>
</div>
</Teleport>
<!-- -->
<!-- INSURANCE MODAL -->
<!-- -->
<Teleport to="body">
<div v-if="showInsuranceModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4" @click.self="closeInsuranceModal">
<div class="w-full max-w-md rounded-2xl border border-slate-700 bg-slate-900 p-6 shadow-2xl">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-bold text-white">{{ t('finance.add_insurance') || 'Új biztosítás' }}</h3>
<button @click="closeInsuranceModal" class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-800 hover:text-white transition-colors cursor-pointer">
<svg class="h-5 w-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>
<div class="space-y-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.insurer_name') || 'Biztosító neve' }}</label>
<input v-model="insuranceForm.insurer_name" type="text" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.policy_number') || 'Kötvényszám' }}</label>
<input v-model="insuranceForm.policy_number" type="text" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.policy_type') || 'Biztosítás típusa' }}</label>
<select v-model="insuranceForm.policy_type" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20 appearance-none cursor-pointer">
<option value="kgfb">KGFB</option>
<option value="casco">CASCO</option>
<option value="both">KGFB + CASCO</option>
</select>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.premium') || 'Díj' }}</label>
<input v-model.number="insuranceForm.premium" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.currency') || 'Pénznem' }}</label>
<select v-model="insuranceForm.currency" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20 appearance-none cursor-pointer">
<option value="HUF">HUF</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.valid_from') || 'Érvényesség kezdete' }}</label>
<input v-model="insuranceForm.valid_from" type="date" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.valid_until') || 'Érvényesség vége' }}</label>
<input v-model="insuranceForm.valid_until" type="date" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
</div>
<div class="mt-6 flex justify-end gap-3">
<button @click="closeInsuranceModal" class="rounded-xl border border-slate-600 px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 transition-colors cursor-pointer">
{{ t('common.cancel') || 'Mégse' }}
</button>
<button @click="saveInsurance" :disabled="savingInsurance" class="rounded-xl bg-sf-accent px-4 py-2 text-sm font-medium text-white hover:bg-sf-accent/90 disabled:opacity-50 transition-colors cursor-pointer">
{{ savingInsurance ? (t('common.saving') || 'Mentés...') : (t('common.save') || 'Mentés') }}
</button>
</div>
</div>
</div>
</Teleport>
<!-- -->
<!-- TAX MODAL -->
<!-- -->
<Teleport to="body">
<div v-if="showTaxModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4" @click.self="closeTaxModal">
<div class="w-full max-w-md rounded-2xl border border-slate-700 bg-slate-900 p-6 shadow-2xl">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-bold text-white">{{ t('finance.add_tax') || 'Új adó' }}</h3>
<button @click="closeTaxModal" class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-800 hover:text-white transition-colors cursor-pointer">
<svg class="h-5 w-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>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.tax_type') || 'Adó típusa' }}</label>
<input v-model="taxForm.tax_type" type="text" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.amount') || 'Összeg' }}</label>
<input v-model.number="taxForm.amount" type="number" step="0.01" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white placeholder-slate-500 focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.currency') || 'Pénznem' }}</label>
<select v-model="taxForm.currency" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20 appearance-none cursor-pointer">
<option value="HUF">HUF</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
</select>
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-slate-400">{{ t('finance.due_date') || 'Esedékesség' }}</label>
<input v-model="taxForm.due_date" type="date" class="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2 text-sm text-white focus:border-sf-accent focus:outline-none focus:ring-2 focus:ring-sf-accent/20" />
</div>
</div>
<div class="flex items-center gap-2">
<input v-model="taxForm.paid" type="checkbox" id="tax-paid" class="rounded border-slate-600 bg-slate-800 text-sf-accent focus:ring-sf-accent/20 cursor-pointer" />
<label for="tax-paid" class="text-sm text-slate-300 cursor-pointer">{{ t('finance.paid') || 'Fizetve' }}</label>
</div>
</div>
<div class="mt-6 flex justify-end gap-3">
<button @click="closeTaxModal" class="rounded-xl border border-slate-600 px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 transition-colors cursor-pointer">
{{ t('common.cancel') || 'Mégse' }}
</button>
<button @click="saveTax" :disabled="savingTax" class="rounded-xl bg-sf-accent px-4 py-2 text-sm font-medium text-white hover:bg-sf-accent/90 disabled:opacity-50 transition-colors cursor-pointer">
{{ savingTax ? (t('common.saving') || 'Mentés...') : (t('common.save') || 'Mentés') }}
</button>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useVehicleStore } from '@/stores/vehicle'
import type { AssetFinancials, VehicleInsurancePolicy, VehicleTaxObligation } from '@/types/vehicle'
const { t } = useI18n()
const vehicleStore = useVehicleStore()
const props = defineProps<{ vehicleId: string }>()
// ── Reactive State ────────────────────────────────────────────────────────
const financials = ref<AssetFinancials | null>(null)
const insurancePolicies = ref<VehicleInsurancePolicy[]>([])
const taxObligations = ref<VehicleTaxObligation[]>([])
const loadingFinancials = ref(false)
const loadingInsurance = ref(false)
const savingFinancials = ref(false)
const savingInsurance = ref(false)
const savingTax = ref(false)
const showFinancialsModal = ref(false)
const showInsuranceModal = ref(false)
const showTaxModal = ref(false)
// ── Form Objects ──────────────────────────────────────────────────────────
const financialsForm = reactive<Partial<AssetFinancials>>({
purchase_price_net: null,
purchase_price_gross: null,
down_payment: null,
monthly_installment: null,
contract_number: '',
financing_provider: '',
interest_rate: null,
residual_value: null,
contract_start_date: '',
contract_end_date: '',
})
const insuranceForm = reactive<Partial<VehicleInsurancePolicy>>({
insurer_name: '',
policy_number: '',
policy_type: 'kgfb',
premium: null,
currency: 'HUF',
valid_from: '',
valid_until: '',
})
const taxForm = reactive<Partial<VehicleTaxObligation>>({
tax_type: '',
amount: null,
currency: 'HUF',
due_date: '',
paid: false,
})
// ── Helper Methods ────────────────────────────────────────────────────────
function formatCurrency(value: number | null | undefined, currency?: string): string {
if (value == null) return '—'
const cur = currency || 'HUF'
try {
return new Intl.NumberFormat('hu-HU', { style: 'currency', currency: cur }).format(value)
} catch {
return `${value.toLocaleString('hu-HU')} ${cur}`
}
}
function formatDate(date: string | null | undefined): string {
if (!date) return '—'
try {
return new Intl.DateTimeFormat('hu-HU', { year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(date))
} catch {
return date
}
}
// ── Data Loading ──────────────────────────────────────────────────────────
async function loadData() {
if (!props.vehicleId) return
loadingFinancials.value = true
loadingInsurance.value = true
try {
const [finResult, insResult, taxResult] = await Promise.all([
vehicleStore.fetchAssetFinancials(props.vehicleId),
vehicleStore.fetchInsurancePolicies(props.vehicleId),
vehicleStore.fetchTaxObligations(props.vehicleId),
])
financials.value = finResult
insurancePolicies.value = insResult
taxObligations.value = taxResult
} catch (err) {
console.error('[FinancialsTab] Failed to load data:', err)
} finally {
loadingFinancials.value = false
loadingInsurance.value = false
}
}
// ── Financials Modal ──────────────────────────────────────────────────────
function openFinancialsModal() {
if (financials.value) {
Object.assign(financialsForm, {
purchase_price_net: financials.value.purchase_price_net ?? null,
purchase_price_gross: financials.value.purchase_price_gross ?? null,
down_payment: financials.value.down_payment ?? null,
monthly_installment: financials.value.monthly_installment ?? null,
contract_number: financials.value.contract_number || '',
financing_provider: financials.value.financing_provider || '',
interest_rate: financials.value.interest_rate ?? null,
residual_value: financials.value.residual_value ?? null,
contract_start_date: financials.value.contract_start_date || '',
contract_end_date: financials.value.contract_end_date || '',
})
} else {
Object.assign(financialsForm, {
purchase_price_net: null,
purchase_price_gross: null,
down_payment: null,
monthly_installment: null,
contract_number: '',
financing_provider: '',
interest_rate: null,
residual_value: null,
contract_start_date: '',
contract_end_date: '',
})
}
showFinancialsModal.value = true
}
function closeFinancialsModal() {
showFinancialsModal.value = false
}
async function saveFinancials() {
if (!props.vehicleId) return
savingFinancials.value = true
try {
const result = await vehicleStore.saveAssetFinancials(props.vehicleId, { ...financialsForm })
if (result) {
financials.value = result
closeFinancialsModal()
}
} catch (err) {
console.error('[FinancialsTab] Failed to save financials:', err)
} finally {
savingFinancials.value = false
}
}
// ── Insurance Modal ───────────────────────────────────────────────────────
function openInsuranceModal() {
Object.assign(insuranceForm, {
insurer_name: '',
policy_number: '',
policy_type: 'kgfb',
premium: null,
currency: 'HUF',
valid_from: '',
valid_until: '',
})
showInsuranceModal.value = true
}
function closeInsuranceModal() {
showInsuranceModal.value = false
}
async function saveInsurance() {
if (!props.vehicleId) return
savingInsurance.value = true
try {
const result = await vehicleStore.createInsurancePolicy(props.vehicleId, { ...insuranceForm })
if (result) {
insurancePolicies.value.push(result)
closeInsuranceModal()
}
} catch (err) {
console.error('[FinancialsTab] Failed to save insurance:', err)
} finally {
savingInsurance.value = false
}
}
async function deleteInsurance(policy: VehicleInsurancePolicy) {
if (!props.vehicleId || !policy.id) return
if (!confirm(t('common.confirm_delete') || 'Biztosan törlöd?')) return
try {
const success = await vehicleStore.deleteInsurancePolicy(props.vehicleId, policy.id)
if (success) {
insurancePolicies.value = insurancePolicies.value.filter(p => p.id !== policy.id)
}
} catch (err) {
console.error('[FinancialsTab] Failed to delete insurance:', err)
}
}
// ── Tax Modal ─────────────────────────────────────────────────────────────
function openTaxModal() {
Object.assign(taxForm, {
tax_type: '',
amount: null,
currency: 'HUF',
due_date: '',
paid: false,
})
showTaxModal.value = true
}
function closeTaxModal() {
showTaxModal.value = false
}
async function saveTax() {
if (!props.vehicleId) return
savingTax.value = true
try {
const result = await vehicleStore.createTaxObligation(props.vehicleId, { ...taxForm })
if (result) {
taxObligations.value.push(result)
closeTaxModal()
}
} catch (err) {
console.error('[FinancialsTab] Failed to save tax:', err)
} finally {
savingTax.value = false
}
}
async function deleteTax(tax: VehicleTaxObligation) {
if (!props.vehicleId || !tax.id) return
if (!confirm(t('common.confirm_delete') || 'Biztosan törlöd?')) return
try {
const success = await vehicleStore.deleteTaxObligation(props.vehicleId, tax.id)
if (success) {
taxObligations.value = taxObligations.value.filter(t => t.id !== tax.id)
}
} catch (err) {
console.error('[FinancialsTab] Failed to delete tax:', err)
}
}
// ── TCO / Cost Modal (placeholder) ────────────────────────────────────────
function openCostModal() {
// TODO: Implement cost modal when TCO module is ready
console.warn('[FinancialsTab] TCO cost modal not yet implemented')
}
// ── Lifecycle ─────────────────────────────────────────────────────────────
onMounted(() => {
loadData()
})
</script>
<style scoped>
/* All styling is handled by Tailwind utility classes */
</style>