709 lines
40 KiB
Vue
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>💸</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>🛡</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>📊</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> |