496 lines
25 KiB
Vue
496 lines
25 KiB
Vue
<script setup>
|
||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||
import { ref, computed } from 'vue';
|
||
import { useForm, Link } from '@inertiajs/vue3';
|
||
import Multiselect from 'vue-multiselect';
|
||
|
||
const props = defineProps({
|
||
template: Object,
|
||
clients: Array,
|
||
});
|
||
|
||
const form = useForm({
|
||
name: props.template.name,
|
||
description: props.template.description,
|
||
source_type: props.template.source_type,
|
||
default_record_type: props.template.default_record_type || '',
|
||
is_active: props.template.is_active,
|
||
client_uuid: props.template.client_uuid || null,
|
||
});
|
||
|
||
const entities = computed(() => (props.template.meta?.entities || []));
|
||
const hasMappings = computed(() => (props.template.mappings?.length || 0) > 0);
|
||
const canChangeClient = computed(() => !hasMappings.value); // guard reassignment when mappings exist (optional rule)
|
||
|
||
// Local state for adding a new mapping row per entity accordion
|
||
const newRows = ref({});
|
||
const bulkRows = ref({}); // per-entity textarea and options
|
||
const bulkGlobal = ref({ entity: '', sources: '', default_field: '', transform: '', apply_mode: 'both' });
|
||
const unassigned = computed(() => (props.template.mappings || []).filter(m => !m.target_field));
|
||
const unassignedSourceColumns = computed(() => {
|
||
const set = new Set();
|
||
for (const m of unassigned.value) {
|
||
if (m.source_column) set.add(m.source_column);
|
||
}
|
||
return Array.from(set).sort((a,b)=>a.localeCompare(b));
|
||
});
|
||
const unassignedState = ref({});
|
||
|
||
function saveUnassigned(m) {
|
||
const st = unassignedState.value[m.id] || {};
|
||
if (st.entity && st.field) {
|
||
m.target_field = `${st.entity}.${st.field}`;
|
||
} else {
|
||
m.target_field = null;
|
||
}
|
||
updateMapping(m);
|
||
}
|
||
|
||
const entityOptions = [
|
||
{ key: 'person', label: 'Person' },
|
||
{ key: 'person_addresses', label: 'Person Addresses' },
|
||
{ key: 'person_phones', label: 'Person Phones' },
|
||
{ key: 'emails', label: 'Emails' },
|
||
{ key: 'accounts', label: 'Accounts' },
|
||
{ key: 'contracts', label: 'Contracts' },
|
||
];
|
||
|
||
const fieldOptions = {
|
||
person: [
|
||
'first_name', 'last_name', 'full_name', 'gender', 'birthday', 'tax_number', 'social_security_number', 'description'
|
||
],
|
||
person_addresses: [ 'address', 'country', 'type_id', 'description' ],
|
||
person_phones: [ 'nu', 'country_code', 'type_id', 'description' ],
|
||
emails: [ 'email', 'is_primary' ],
|
||
accounts: [ 'reference', 'balance_amount', 'contract_id', 'contract_reference' ],
|
||
contracts: [ 'reference', 'start_date', 'end_date', 'description', 'type_id', 'client_case_id' ],
|
||
};
|
||
|
||
function toggle(entity) {
|
||
const el = document.getElementById(`acc-${entity}`);
|
||
if (el) el.open = !el.open;
|
||
}
|
||
|
||
function addRow(entity) {
|
||
const row = newRows.value[entity];
|
||
if (!row || !row.source || !row.field) return;
|
||
const target_field = `${entity}.${row.field}`;
|
||
const payload = {
|
||
source_column: row.source,
|
||
target_field,
|
||
transform: row.transform || null,
|
||
apply_mode: row.apply_mode || 'both',
|
||
position: (props.template.mappings?.length || 0) + 1,
|
||
};
|
||
useForm(payload).post(route('importTemplates.mappings.add', { template: props.template.uuid }), {
|
||
preserveScroll: true,
|
||
onSuccess: () => { newRows.value[entity] = {}; },
|
||
});
|
||
}
|
||
|
||
function updateMapping(m) {
|
||
const payload = {
|
||
source_column: m.source_column,
|
||
target_field: m.target_field,
|
||
transform: m.transform,
|
||
apply_mode: m.apply_mode,
|
||
position: m.position,
|
||
};
|
||
useForm(payload).put(route('importTemplates.mappings.update', { template: props.template.uuid, mapping: m.id }), {
|
||
preserveScroll: true,
|
||
});
|
||
}
|
||
|
||
function deleteMapping(m) {
|
||
useForm({}).delete(route('importTemplates.mappings.delete', { template: props.template.uuid, mapping: m.id }), {
|
||
preserveScroll: true,
|
||
});
|
||
}
|
||
|
||
function reorder(entity, direction, m) {
|
||
// Build new order across all mappings, swapping positions for this entity scope
|
||
const all = [...props.template.mappings];
|
||
const entityMaps = all.filter(x => x.target_field?.startsWith(entity + '.'));
|
||
const idx = entityMaps.findIndex(x => x.id === m.id);
|
||
if (idx < 0) return;
|
||
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
|
||
if (swapIdx < 0 || swapIdx >= entityMaps.length) return;
|
||
const a = entityMaps[idx];
|
||
const b = entityMaps[swapIdx];
|
||
// Build final ordered ids list using current order, swapping a/b positions
|
||
const byId = Object.fromEntries(all.map(x => [x.id, x]));
|
||
const ordered = all.map(x => x.id);
|
||
const ai = ordered.indexOf(a.id);
|
||
const bi = ordered.indexOf(b.id);
|
||
if (ai < 0 || bi < 0) return;
|
||
[ordered[ai], ordered[bi]] = [ordered[bi], ordered[ai]];
|
||
useForm({ order: ordered }).post(route('importTemplates.mappings.reorder', { template: props.template.uuid }), {
|
||
preserveScroll: true,
|
||
});
|
||
}
|
||
|
||
// Save basic
|
||
const save = () => {
|
||
const payload = { ...form.data() };
|
||
if (!canChangeClient.value) {
|
||
// drop client change when blocked
|
||
delete payload.client_uuid;
|
||
}
|
||
useForm(payload).put(route('importTemplates.update', { template: props.template.uuid }), { preserveScroll: true });
|
||
};
|
||
// Non-blocking confirm modal state for delete
|
||
const deleteConfirmOpen = ref(false);
|
||
const deleteForm = useForm({});
|
||
function openDeleteConfirm() { deleteConfirmOpen.value = true; }
|
||
function cancelDelete() { deleteConfirmOpen.value = false; }
|
||
function performDelete() {
|
||
deleteForm.delete(route('importTemplates.destroy', { template: props.template.uuid }), {
|
||
onFinish: () => { deleteConfirmOpen.value = false; },
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<AppLayout :title="`Edit Template: ${props.template.name}`">
|
||
<template #header>
|
||
<div class="flex items-center justify-between">
|
||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Uredi uvozno predlogo</h2>
|
||
<div class="flex items-center gap-2">
|
||
<Link :href="route('importTemplates.index')" class="px-3 py-1.5 border rounded text-sm">Nazaj</Link>
|
||
<button class="px-3 py-1.5 border rounded text-sm text-red-700 border-red-300 hover:bg-red-50" @click.prevent="openDeleteConfirm">Izbriši predlogo</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="py-6">
|
||
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||
<div class="bg-white shadow sm:rounded-lg p-6 space-y-6">
|
||
<!-- Basic info -->
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700">Ime predloge</label>
|
||
<input v-model="form.name" type="text" class="mt-1 block w-full border rounded p-2" />
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700">Vir</label>
|
||
<select v-model="form.source_type" class="mt-1 block w-full border rounded p-2">
|
||
<option value="csv">CSV</option>
|
||
<option value="xml">XML</option>
|
||
<option value="xls">XLS</option>
|
||
<option value="xlsx">XLSX</option>
|
||
<option value="json">JSON</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700">Privzeti tip zapisa</label>
|
||
<input v-model="form.default_record_type" type="text" class="mt-1 block w-full border rounded p-2" placeholder="npr.: account, person" />
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700">Naročnik (opcijsko)</label>
|
||
<Multiselect
|
||
v-model="form.client_uuid"
|
||
:options="props.clients || []"
|
||
:reduce="c => c.uuid"
|
||
track-by="uuid"
|
||
label="name"
|
||
placeholder="Global (brez naročnika)"
|
||
:searchable="true"
|
||
:allow-empty="true"
|
||
class="mt-1"
|
||
:disabled="!canChangeClient"
|
||
/>
|
||
<p v-if="!canChangeClient" class="text-xs text-amber-600 mt-1">Ni mogoče spremeniti naročnika, ker ta predloga že vsebuje preslikave.</p>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<input id="is_active" v-model="form.is_active" type="checkbox" class="rounded" />
|
||
<label for="is_active" class="text-sm font-medium text-gray-700">Aktivna</label>
|
||
<button @click.prevent="save" class="ml-auto px-3 py-2 bg-indigo-600 text-white rounded">Shrani</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sample headers viewer/editor -->
|
||
<div class="p-3 bg-gray-50 rounded border">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<div class="text-sm font-medium text-gray-700">Vzorčni glavi stolpcev</div>
|
||
<button class="text-xs px-2 py-1 border rounded" @click.prevent="form.sample_headers = (form.sample_headers || []).concat([''])">Dodaj stolpec</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||
<div v-for="(h, i) in (form.sample_headers = form.sample_headers ?? props.template.sample_headers ?? [])" :key="i" class="flex items-center gap-2">
|
||
<input v-model="form.sample_headers[i]" type="text" class="flex-1 border rounded p-2" placeholder="npr.: reference" />
|
||
<button class="px-2 py-1 border rounded" @click.prevent="form.sample_headers.splice(i,1)">✕</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Global Bulk Add Mappings -->
|
||
<div class="p-3 bg-indigo-50 rounded border border-indigo-200">
|
||
<div class="mb-2 text-sm font-medium text-indigo-900">Bulk dodajanje preslikav</div>
|
||
<div class="grid grid-cols-1 sm:grid-cols-6 gap-3 items-start">
|
||
<div class="sm:col-span-3">
|
||
<label class="block text-xs text-indigo-900">Source columns (CSV ali po vrsticah)</label>
|
||
<textarea v-model="bulkGlobal.sources" rows="3" class="mt-1 w-full border rounded p-2" placeholder="npr.: reference,first name,last name,amount"></textarea>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-indigo-900">Entity (opcijsko)</label>
|
||
<select v-model="bulkGlobal.entity" class="mt-1 w-full border rounded p-2">
|
||
<option value="">(brez – pusti target prazno)</option>
|
||
<option v-for="opt in entityOptions" :key="opt.key" :value="opt.key">{{ opt.label }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-indigo-900">Field (opcijsko, za vse)</label>
|
||
<select v-model="bulkGlobal.default_field" class="mt-1 w-full border rounded p-2" :disabled="!bulkGlobal.entity">
|
||
<option value="">(auto from source)</option>
|
||
<option v-for="f in (fieldOptions[bulkGlobal.entity] || [])" :key="f" :value="f">{{ f }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-indigo-900">Transform (za vse)</label>
|
||
<select v-model="bulkGlobal.transform" class="mt-1 w-full border rounded p-2">
|
||
<option value="">None</option>
|
||
<option value="trim">trim</option>
|
||
<option value="upper">upper</option>
|
||
<option value="lower">lower</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-indigo-900">Apply (za vse)</label>
|
||
<select v-model="bulkGlobal.apply_mode" class="mt-1 w-full border rounded p-2">
|
||
<option value="both">both</option>
|
||
<option value="insert">insert</option>
|
||
<option value="update">update</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="mt-3">
|
||
<button
|
||
@click.prevent="(() => {
|
||
if (!bulkGlobal.sources || !bulkGlobal.sources.trim()) return;
|
||
useForm({
|
||
sources: bulkGlobal.sources,
|
||
entity: bulkGlobal.entity || null,
|
||
default_field: bulkGlobal.default_field || null,
|
||
transform: bulkGlobal.transform || null,
|
||
apply_mode: bulkGlobal.apply_mode || 'both',
|
||
}).post(route('importTemplates.mappings.bulk', { template: props.template.uuid }), {
|
||
preserveScroll: true,
|
||
onSuccess: () => { bulkGlobal.entity=''; bulkGlobal.sources=''; bulkGlobal.default_field=''; bulkGlobal.transform=''; bulkGlobal.apply_mode='both'; },
|
||
});
|
||
})()"
|
||
class="px-3 py-2 bg-indigo-600 text-white rounded"
|
||
>Dodaj več</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Unassigned mappings (no target_field) -->
|
||
<div v-if="unassigned.length" class="p-3 bg-amber-50 rounded border border-amber-200">
|
||
<div class="mb-2 text-sm font-medium text-amber-900">Nedodeljene preslikave ({{ unassigned.length }})</div>
|
||
<div class="space-y-2">
|
||
<div v-for="m in unassigned" :key="m.id" class="p-2 bg-white/60 border rounded">
|
||
<div class="grid grid-cols-1 sm:grid-cols-6 gap-2 items-center">
|
||
<div class="text-sm">
|
||
<div class="text-gray-500 text-xs">Source</div>
|
||
<div class="font-medium">{{ m.source_column }}</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Entity</label>
|
||
<select v-model="(unassignedState[m.id] ||= {}).entity" class="mt-1 w-full border rounded p-2">
|
||
<option value="">(izberi)</option>
|
||
<option v-for="opt in entityOptions" :key="opt.key" :value="opt.key">{{ opt.label }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Field</label>
|
||
<select v-model="(unassignedState[m.id] ||= {}).field" class="mt-1 w-full border rounded p-2" :disabled="!(unassignedState[m.id]||{}).entity">
|
||
<option value="">(izberi)</option>
|
||
<option v-for="f in (fieldOptions[(unassignedState[m.id]||{}).entity] || [])" :key="f" :value="f">{{ f }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Transform</label>
|
||
<select v-model="m.transform" class="mt-1 w-full border rounded p-2">
|
||
<option value="">None</option>
|
||
<option value="trim">trim</option>
|
||
<option value="upper">upper</option>
|
||
<option value="lower">lower</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Apply</label>
|
||
<select v-model="m.apply_mode" class="mt-1 w-full border rounded p-2">
|
||
<option value="both">both</option>
|
||
<option value="insert">insert</option>
|
||
<option value="update">update</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<button class="px-3 py-1.5 bg-emerald-600 text-white rounded text-sm" @click.prevent="saveUnassigned(m)">Shrani</button>
|
||
<button class="px-3 py-1.5 bg-red-600 text-white rounded text-sm" @click.prevent="deleteMapping(m)">Izbriši</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Entities accordion -->
|
||
<div class="divide-y">
|
||
<details v-for="entity in entities" :key="entity" :id="`acc-${entity}`" class="py-3">
|
||
<summary class="cursor-pointer select-none flex items-center justify-between">
|
||
<span class="font-medium">{{ entityOptions.find(e=>e.key===entity)?.label || entity }}</span>
|
||
<span class="text-xs text-gray-500">Klikni za razširitev</span>
|
||
</summary>
|
||
<div class="mt-4 space-y-4">
|
||
<!-- Existing mappings for this entity -->
|
||
<div v-if="props.template.mappings && props.template.mappings.length" class="space-y-2">
|
||
<div v-for="m in props.template.mappings.filter(m=>m.target_field?.startsWith(entity + '.'))" :key="m.id" class="flex items-center justify-between p-2 border rounded gap-3">
|
||
<div class="grid grid-cols-1 sm:grid-cols-5 gap-2 flex-1 items-center">
|
||
<input v-model="m.source_column" class="border rounded p-2 text-sm" />
|
||
<input v-model="m.target_field" class="border rounded p-2 text-sm" />
|
||
<select v-model="m.transform" class="border rounded p-2 text-sm">
|
||
<option value="">None</option>
|
||
<option value="trim">trim</option>
|
||
<option value="upper">upper</option>
|
||
<option value="lower">lower</option>
|
||
</select>
|
||
<select v-model="m.apply_mode" class="border rounded p-2 text-sm">
|
||
<option value="both">both</option>
|
||
<option value="insert">insert</option>
|
||
<option value="update">update</option>
|
||
</select>
|
||
<div class="flex items-center gap-2">
|
||
<button class="px-2 py-1 text-xs border rounded" @click.prevent="reorder(entity, 'up', m)">▲</button>
|
||
<button class="px-2 py-1 text-xs border rounded" @click.prevent="reorder(entity, 'down', m)">▼</button>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<button class="px-3 py-1.5 bg-emerald-600 text-white rounded text-sm" @click.prevent="updateMapping(m)">Shrani</button>
|
||
<button class="px-3 py-1.5 bg-red-600 text-white rounded text-sm" @click.prevent="deleteMapping(m)">Izbriši</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="text-sm text-gray-500">Ni definiranih preslikav za to entiteto.</div>
|
||
|
||
<!-- Add new mapping row -->
|
||
<div class="p-3 bg-gray-50 rounded border">
|
||
<div class="grid grid-cols-1 sm:grid-cols-5 gap-2 items-end">
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Source column (ne-dodeljene)</label>
|
||
<select v-model="(newRows[entity] ||= {}).source" class="mt-1 w-full border rounded p-2">
|
||
<option value="" disabled>(izberi)</option>
|
||
<option v-for="s in unassignedSourceColumns" :key="s" :value="s">{{ s }}</option>
|
||
</select>
|
||
<p v-if="!unassignedSourceColumns.length" class="text-xs text-gray-500 mt-1">Ni nedodeljenih virov. Uporabi Bulk ali najprej dodaj vire.</p>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Field</label>
|
||
<select v-model="(newRows[entity] ||= {}).field" class="mt-1 w-full border rounded p-2">
|
||
<option v-for="f in (fieldOptions[entity] || [])" :key="f" :value="f">{{ f }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Transform</label>
|
||
<select v-model="(newRows[entity] ||= {}).transform" class="mt-1 w-full border rounded p-2">
|
||
<option value="">None</option>
|
||
<option value="trim">trim</option>
|
||
<option value="upper">upper</option>
|
||
<option value="lower">lower</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Apply</label>
|
||
<select v-model="(newRows[entity] ||= {}).apply_mode" class="mt-1 w-full border rounded p-2">
|
||
<option value="both">both</option>
|
||
<option value="insert">insert</option>
|
||
<option value="update">update</option>
|
||
</select>
|
||
</div>
|
||
<div class="sm:col-span-1">
|
||
<button @click.prevent="addRow(entity)" class="w-full sm:w-auto px-3 py-2 bg-emerald-600 text-white rounded">Dodaj preslikavo</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bulk add mapping rows -->
|
||
<div class="p-3 bg-gray-50 rounded border">
|
||
<div class="mb-2 text-xs text-gray-600">Dodaj več stolpcev naenkrat (ločeno z vejicami ali novimi vrsticami). Če polje ne izbereš, bo target nastavljen na entity + ime stolpca.</div>
|
||
<div class="grid grid-cols-1 sm:grid-cols-5 gap-2 items-start">
|
||
<div class="sm:col-span-2">
|
||
<label class="block text-xs text-gray-600">Source columns (CSV ali po vrsticah)</label>
|
||
<textarea v-model="(bulkRows[entity] ||= {}).sources" rows="3" class="mt-1 w-full border rounded p-2" placeholder="npr.: reference,first name,last name"></textarea>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Field (opcijsko, za vse)</label>
|
||
<select v-model="(bulkRows[entity] ||= {}).default_field" class="mt-1 w-full border rounded p-2">
|
||
<option value="">(auto from source)</option>
|
||
<option v-for="f in (fieldOptions[entity] || [])" :key="f" :value="f">{{ f }}</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Transform (za vse)</label>
|
||
<select v-model="(bulkRows[entity] ||= {}).transform" class="mt-1 w-full border rounded p-2">
|
||
<option value="">None</option>
|
||
<option value="trim">trim</option>
|
||
<option value="upper">upper</option>
|
||
<option value="lower">lower</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-600">Apply (za vse)</label>
|
||
<select v-model="(bulkRows[entity] ||= {}).apply_mode" class="mt-1 w-full border rounded p-2">
|
||
<option value="both">both</option>
|
||
<option value="insert">insert</option>
|
||
<option value="update">update</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="mt-2">
|
||
<button
|
||
@click.prevent="(() => {
|
||
const b = bulkRows[entity] ||= {};
|
||
if (!b.sources || !b.sources.trim()) return;
|
||
useForm({
|
||
sources: b.sources,
|
||
entity,
|
||
default_field: b.default_field || null,
|
||
transform: b.transform || null,
|
||
apply_mode: b.apply_mode || 'both',
|
||
}).post(route('importTemplates.mappings.bulk', { template: props.template.uuid }), {
|
||
preserveScroll: true,
|
||
onSuccess: () => { bulkRows[entity] = {}; },
|
||
});
|
||
})()"
|
||
class="px-3 py-2 bg-indigo-600 text-white rounded"
|
||
>Dodaj več</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Delete Confirmation Modal -->
|
||
<div v-if="deleteConfirmOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||
<div class="absolute inset-0 bg-black/30" @click="cancelDelete"></div>
|
||
<div class="relative bg-white rounded shadow-lg w-96 max-w-[90%] p-5">
|
||
<div class="text-lg font-semibold mb-2">Izbrišem predlogo?</div>
|
||
<p class="text-sm text-gray-600 mb-4">Tega dejanja ni mogoče razveljaviti. Vse preslikave te predloge bodo izbrisane.</p>
|
||
<div class="flex items-center justify-end gap-2">
|
||
<button class="px-3 py-1.5 border rounded" @click.prevent="cancelDelete" :disabled="deleteForm.processing">Prekliči</button>
|
||
<button class="px-3 py-1.5 rounded text-white bg-red-600 disabled:opacity-60" @click.prevent="performDelete" :disabled="deleteForm.processing">
|
||
<span v-if="deleteForm.processing">Brisanje…</span>
|
||
<span v-else>Izbriši</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</AppLayout>
|
||
</template>
|
||
|
||
<style>
|
||
.fade-enter-active, .fade-leave-active { transition: opacity .2s; }
|
||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||
</style>
|
||
|
||
<!-- moved modal into main template to avoid multiple <template> blocks -->
|