Teren-app/resources/js/Pages/Admin/DocumentTemplates/Edit.vue
Simon Pocrnjič e0303ece74 documents
2025-10-12 12:24:17 +02:00

411 lines
16 KiB
Vue

<template>
<AdminLayout title="Uredi predlogo">
<div class="mb-6 flex flex-col lg:flex-row lg:items-start gap-6">
<div class="flex-1 min-w-[320px]">
<div class="flex items-start justify-between gap-4 mb-4">
<div>
<h1 class="text-2xl font-semibold tracking-tight">{{ template.name }}</h1>
<p class="text-xs text-gray-500 mt-1 flex flex-wrap gap-3">
<span class="inline-flex items-center gap-1"
><span class="text-gray-400">Slug:</span
><span class="font-medium">{{ template.slug }}</span></span
>
<span class="inline-flex items-center gap-1"
><span class="text-gray-400">Verzija:</span
><span class="font-medium">v{{ template.version }}</span></span
>
<span
class="inline-flex items-center gap-1"
:class="template.active ? 'text-emerald-600' : 'text-gray-400'"
><span
class="w-1.5 h-1.5 rounded-full"
:class="template.active ? 'bg-emerald-500' : 'bg-gray-300'"
/>
{{ template.active ? "Aktivna" : "Neaktivna" }}</span
>
</p>
</div>
<form @submit.prevent="toggleActive" class="flex items-center gap-2">
<button
type="submit"
:class="[btnBase, template.active ? btnWarn : btnOutline]"
:disabled="toggleForm.processing"
>
<span v-if="toggleForm.processing">...</span>
<span v-else>{{ template.active ? "Deaktiviraj" : "Aktiviraj" }}</span>
</button>
<Link
:href="route('admin.document-templates.show', template.id)"
:class="[btnBase, btnOutline]"
>Ogled</Link
>
</form>
</div>
<form @submit.prevent="submit" class="space-y-8">
<!-- Osnovno -->
<div class="bg-white border rounded-lg shadow-sm p-5 space-y-5">
<div class="flex items-center justify-between">
<h2 class="text-sm font-semibold tracking-wide text-gray-700 uppercase">
Osnovne nastavitve
</h2>
</div>
<div class="grid md:grid-cols-2 gap-6">
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600"
>Izlazna datoteka (pattern)</span
>
<input
v-model="form.output_filename_pattern"
type="text"
class="input input-bordered w-full input-sm"
placeholder="POVRACILO_{contract.reference}"
/>
<span class="text-[11px] text-gray-500"
>Tokens npr. {contract.reference}</span
>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600"
>Privzeti format datuma</span
>
<input
v-model="form.date_format"
type="text"
class="input input-bordered w-full input-sm"
placeholder="d.m.Y"
/>
</label>
</div>
<label class="flex items-center gap-2 text-xs font-medium text-gray-600">
<input
id="fail_on_unresolved"
type="checkbox"
v-model="form.fail_on_unresolved"
class="checkbox checkbox-xs"
/>
<span>Prekini če token ni rešen (fail on unresolved)</span>
</label>
</div>
<!-- Formatiranje -->
<div class="bg-white border rounded-lg shadow-sm p-5 space-y-5">
<h2 class="text-sm font-semibold tracking-wide text-gray-700 uppercase">
Formatiranje
</h2>
<div class="grid md:grid-cols-3 gap-5">
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Decimalna mesta</span>
<input
v-model.number="form.number_decimals"
type="number"
min="0"
max="6"
class="input input-bordered w-full input-sm"
/>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Decimalni separator</span>
<input
v-model="form.decimal_separator"
type="text"
maxlength="2"
class="input input-bordered w-full input-sm"
/>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Tisocice separator</span>
<input
v-model="form.thousands_separator"
type="text"
maxlength="2"
class="input input-bordered w-full input-sm"
/>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Znak valute</span>
<input
v-model="form.currency_symbol"
type="text"
maxlength="8"
class="input input-bordered w-full input-sm"
/>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Pozicija valute</span>
<select
v-model="form.currency_position"
class="select select-bordered select-sm w-full"
>
<option :value="null">(privzeto)</option>
<option value="before">Pred</option>
<option value="after">Za</option>
</select>
</label>
<label
class="flex items-center gap-2 space-y-0 pt-6 text-xs font-medium text-gray-600"
>
<input
id="currency_space"
type="checkbox"
v-model="form.currency_space"
class="checkbox checkbox-xs"
/>
<span>Presledek pred/za valuto</span>
</label>
</div>
</div>
<!-- Aktivnost -->
<div class="bg-white border rounded-lg shadow-sm p-5 space-y-5">
<h2 class="text-sm font-semibold tracking-wide text-gray-700 uppercase">
Aktivnost
</h2>
<div class="grid md:grid-cols-2 gap-6">
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Akcija</span>
<select
v-model="form.action_id"
class="select select-bordered select-sm w-full"
@change="handleActionChange"
>
<option :value="null">(brez)</option>
<option v-for="a in actions" :key="a.id" :value="a.id">
{{ a.name }}
</option>
</select>
</label>
<label class="space-y-1 block">
<span class="text-xs font-medium text-gray-600">Odločitev</span>
<select
v-model="form.decision_id"
class="select select-bordered select-sm w-full"
:disabled="!currentActionDecisions.length"
>
<option :value="null">(brez)</option>
<option v-for="d in currentActionDecisions" :key="d.id" :value="d.id">
{{ d.name }}
</option>
</select>
</label>
<label class="space-y-1 md:col-span-2 block">
<span class="text-xs font-medium text-gray-600"
>Predloga opombe aktivnosti</span
>
<textarea
v-model="form.activity_note_template"
rows="3"
class="textarea textarea-bordered w-full text-xs"
placeholder="Besedilo aktivnosti..."
/>
<span class="text-[11px] text-gray-500"
>Tokeni npr. {contract.reference}</span
>
</label>
</div>
</div>
<!-- Custom tokens defaults -->
<div class="bg-white border rounded-lg shadow-sm p-5 space-y-5">
<h2 class="text-sm font-semibold tracking-wide text-gray-700 uppercase">
Custom tokens (privzete vrednosti)
</h2>
<div class="space-y-3">
<div class="flex items-center gap-2">
<button type="button" :class="[btnBase, btnOutline]" @click="addCustomDefault">
Dodaj vrstico
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div
v-for="(row, idx) in customRows"
:key="idx"
class="grid grid-cols-12 items-center gap-2"
>
<input
v-model="row.key"
type="text"
class="input input-bordered input-sm w-full col-span-4"
placeholder="custom ključ (npr. order_id)"
/>
<input
v-model="row.value"
type="text"
class="input input-bordered input-sm w-full col-span-5"
placeholder="privzeta vrednost"
/>
<select v-model="row.type" class="select select-bordered select-sm w-full col-span-2">
<option value="string">string</option>
<option value="number">number</option>
<option value="date">date</option>
</select>
<button type="button" class="btn btn-ghost btn-xs col-span-1" @click="removeCustomDefault(idx)">✕</button>
</div>
</div>
<p class="text-[11px] text-gray-500">
Uporabite v predlogi kot <code v-pre>{{custom.your_key}}</code>. Manjkajoče vrednosti se privzeto izpraznijo.
</p>
</div>
</div>
<div class="flex items-center gap-3 pt-2">
<button
type="submit"
:class="[btnBase, btnPrimary]"
:disabled="form.processing"
>
<span v-if="form.processing">Shranjevanje…</span>
<span v-else>Shrani spremembe</span>
</button>
<Link
:href="route('admin.document-templates.show', template.id)"
:class="[btnBase, btnOutline]"
>Prekliči</Link
>
</div>
</form>
</div>
<!-- Side meta panel -->
<aside class="w-full lg:w-72 space-y-6">
<div class="bg-white border rounded-lg shadow-sm p-4 space-y-3">
<h3 class="text-xs font-semibold tracking-wide text-gray-600 uppercase">
Meta
</h3>
<ul class="text-xs text-gray-600 space-y-1">
<li>
<span class="text-gray-400">Velikost:</span>
<span class="font-medium"
>{{ (template.file_size / 1024).toFixed(1) }} KB</span
>
</li>
<li>
<span class="text-gray-400">Hash:</span>
<span class="font-mono">{{ template.file_hash?.substring(0, 12) }}…</span>
</li>
<li>
<span class="text-gray-400">Engine:</span>
<span class="font-medium">{{ template.engine }}</span>
</li>
</ul>
<a
:href="'/storage/' + template.file_path"
target="_blank"
class="text-[11px] inline-flex items-center gap-1 text-indigo-600 hover:underline"
>Prenesi izvorni DOCX →</a
>
</div>
<div
v-if="template.tokens?.length"
class="bg-white border rounded-lg shadow-sm p-4"
>
<h3 class="text-xs font-semibold tracking-wide text-gray-600 uppercase mb-2">
Tokens ({{ template.tokens.length }})
</h3>
<div class="flex flex-wrap gap-1.5 max-h-48 overflow-auto pr-1">
<span
v-for="t in template.tokens"
:key="t"
class="px-1.5 py-0.5 bg-gray-100 rounded text-[11px] font-mono"
>{{ t }}</span
>
</div>
</div>
</aside>
</div>
</AdminLayout>
</template>
<script setup>
import { computed, reactive } from "vue";
import { useForm, Link, router } from "@inertiajs/vue3";
import AdminLayout from "@/Layouts/AdminLayout.vue";
// Button style utility classes
const btnBase =
"inline-flex items-center justify-center gap-1 rounded-md border text-xs font-medium px-3 py-1.5 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 disabled:opacity-50 disabled:cursor-not-allowed";
const btnPrimary = "bg-indigo-600 border-indigo-600 text-white hover:bg-indigo-500";
const btnOutline = "bg-white text-gray-700 border-gray-300 hover:bg-gray-50";
const btnWarn = "bg-amber-500 border-amber-500 text-white hover:bg-amber-400";
const props = defineProps({
template: Object,
actions: Array,
});
const form = useForm({
output_filename_pattern: props.template.output_filename_pattern || "",
date_format: props.template.date_format || "",
fail_on_unresolved: props.template.fail_on_unresolved ?? false,
number_decimals: props.template.formatting_options?.number_decimals ?? 2,
decimal_separator: props.template.formatting_options?.decimal_separator ?? ",",
thousands_separator: props.template.formatting_options?.thousands_separator ?? ".",
currency_symbol: props.template.formatting_options?.currency_symbol ?? "€",
currency_position: props.template.formatting_options?.currency_position ?? "after",
currency_space: props.template.formatting_options?.currency_space ?? true,
action_id: props.template.action_id ?? null,
decision_id: props.template.decision_id ?? null,
activity_note_template: props.template.activity_note_template || "",
// meta will include custom_defaults on submit
meta: props.template.meta || {},
});
const toggleForm = useForm({});
const currentActionDecisions = computed(() => {
if (!form.action_id) {
return [];
}
const a = props.actions.find((a) => a.id === form.action_id);
return a ? a.decisions : [];
});
function handleActionChange() {
if (!currentActionDecisions.value.some((d) => d.id === form.decision_id)) {
form.decision_id = null;
}
}
function submit() {
// Build meta.custom_defaults object from rows
const entries = customRows
.filter((r) => (r.key || "").trim() !== "")
.reduce((acc, r) => {
acc[r.key.trim()] = r.value ?? "";
return acc;
}, {});
const types = customRows
.filter((r) => (r.key || "").trim() !== "")
.reduce((acc, r) => {
acc[r.key.trim()] = r.type || 'string';
return acc;
}, {});
form.meta = Object.assign({}, form.meta || {}, { custom_defaults: entries, custom_default_types: types });
form.put(route("admin.document-templates.settings.update", props.template.id));
}
function toggleActive() {
toggleForm.post(route("admin.document-templates.toggle", props.template.id), {
preserveScroll: true,
});
}
// Custom defaults rows state
const baseDefaults = (props.template.meta && props.template.meta.custom_defaults) || {};
const baseTypes = (props.template.meta && props.template.meta.custom_default_types) || {};
const customRows = reactive(
Object.keys(baseDefaults).length
? Object.entries(baseDefaults).map(([k, v]) => ({ key: k, value: v, type: baseTypes[k] || 'string' }))
: [{ key: "", value: "", type: 'string' }]
);
function addCustomDefault() {
customRows.push({ key: "", value: "", type: 'string' });
}
function removeCustomDefault(idx) {
customRows.splice(idx, 1);
if (!customRows.length) customRows.push({ key: "", value: "" });
}
</script>