Changes to import / template pages frontend updated design

This commit is contained in:
Simon Pocrnjič
2025-12-22 20:52:45 +01:00
parent ee641586c3
commit f8623a6071
30 changed files with 2349 additions and 1839 deletions
+291 -235
View File
@@ -1,9 +1,29 @@
<script setup>
import AppLayout from "@/Layouts/AppLayout.vue";
import { ref } from "vue";
import { ref, computed, watch } from "vue";
import { useForm } from "@inertiajs/vue3";
import Multiselect from "vue-multiselect";
import { computed, watch } from "vue";
import AppMultiSelect from "@/Components/app/ui/AppMultiSelect.vue";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/Components/ui/card";
import { Label } from "@/Components/ui/label";
import { Input } from "@/Components/ui/input";
import { Textarea } from "@/Components/ui/textarea";
import { Button } from "@/Components/ui/button";
import { Checkbox } from "@/Components/ui/checkbox";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/Components/ui/select";
import { Badge } from "@/Components/ui/badge";
import { Separator } from "@/Components/ui/separator";
const props = defineProps({
clients: Array,
@@ -28,8 +48,8 @@ const form = useForm({
delimiter: "",
// Payments import mode
payments_import: false,
// History import mode
history_import: false,
// History import mode
history_import: false,
// For payments mode: how to locate Contract - use single key 'reference'
contract_key_mode: null,
},
@@ -88,7 +108,14 @@ watch(
form.meta.payments_import = false;
form.meta.contract_key_mode = null;
}
const allowed = ["person", "person_addresses", "person_phones", "contracts", "activities", "client_cases"];
const allowed = [
"person",
"person_addresses",
"person_phones",
"contracts",
"activities",
"client_cases",
];
if (enabled) {
const current = Array.isArray(form.entities) ? [...form.entities] : [];
let filtered = current.filter((e) => allowed.includes(e));
@@ -103,7 +130,12 @@ watch(
watch(
() => form.entities,
(vals) => {
if (form.meta.history_import && Array.isArray(vals) && vals.includes("contracts") && ! vals.includes("accounts")) {
if (
form.meta.history_import &&
Array.isArray(vals) &&
vals.includes("contracts") &&
!vals.includes("accounts")
) {
form.entities = [...vals, "accounts"];
}
}
@@ -111,254 +143,278 @@ watch(
</script>
<template>
<AppLayout title="Create Import Template">
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Create Import Template
</h2>
</template>
<AppLayout title="Ustvari predlogo uvoza">
<template #header> </template>
<div class="py-6">
<div class="max-w-3xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg p-6 space-y-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700"
>Client (optional)</label
>
<Multiselect
v-model="form.client_uuid"
:options="props.clients || []"
:reduce="(c) => c.uuid"
track-by="uuid"
label="name"
placeholder="Global (no client)"
:searchable="true"
:allow-empty="true"
class="mt-1"
/>
<p class="text-xs text-gray-500 mt-1">
Leave empty to make this template global (visible to all clients).
</p>
</div>
<div>
<div class="flex items-center justify-between">
<label class="block text-sm font-medium text-gray-700"
>Entities (tables)</label
>
<div class="flex items-center gap-4 text-sm">
<label class="inline-flex items-center gap-2">
<input
type="checkbox"
v-model="form.meta.history_import"
class="rounded"
/>
<span>History import</span>
</label>
<label class="inline-flex items-center gap-2">
<input
type="checkbox"
v-model="form.meta.payments_import"
class="rounded"
/>
<span>Payments import</span>
</label>
</div>
<Card>
<CardHeader>
<CardTitle>Konfiguracija predloge</CardTitle>
<CardDescription
>Določite nastavitve predloge uvoza in ciljne entitete</CardDescription
>
</CardHeader>
<CardContent class="space-y-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="client">Stranka (izbirno)</Label>
<Select v-model="form.client_uuid">
<SelectTrigger id="client">
<SelectValue placeholder="Globalno (brez stranke)" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="null">Globalno (brez stranke)</SelectItem>
<SelectItem
v-for="c in props.clients || []"
:key="c.uuid"
:value="c.uuid"
>
{{ c.name }}
</SelectItem>
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">
Pustite prazno za globalno predlogo (vidno vsem strankam).
</p>
</div>
<template v-if="!form.meta.payments_import">
<Multiselect
v-model="form.entities"
:options="[
{ value: 'person', label: 'Person' },
{ value: 'person_addresses', label: 'Person Addresses' },
{ value: 'person_phones', label: 'Person Phones' },
{ value: 'client_cases', label: 'Client Cases' },
{ value: 'emails', label: 'Emails' },
{ value: 'accounts', label: 'Accounts' },
{ value: 'contracts', label: 'Contracts' },
{ value: 'case_objects', label: 'Case Objects' },
{ value: 'payments', label: 'Payments' },
{ value: 'activities', label: 'Activities' },
]"
:multiple="true"
track-by="value"
label="label"
:reduce="(o) => o.value"
placeholder="Select one or more entities"
:searchable="false"
class="mt-1"
/>
</template>
<template v-else>
<div class="mt-1">
<div class="space-y-2">
<Label>Entitete (tabele)</Label>
<template v-if="!form.meta.payments_import">
<AppMultiSelect
v-model="form.entities"
:items="[
{ value: 'person', label: 'Oseba' },
{ value: 'person_addresses', label: 'Naslov' },
{ value: 'person_phones', label: 'Telefon' },
{ value: 'client_cases', label: 'Primer' },
{ value: 'emails', label: 'E-pošta' },
{ value: 'accounts', label: 'Računi' },
{ value: 'contracts', label: 'Pogodbe' },
{ value: 'case_objects', label: 'Predmet' },
{ value: 'payments', label: 'Plačilo' },
{ value: 'activities', label: 'Aktivnost' },
]"
placeholder="Izberite eno ali več entitet"
search-placeholder="Iskanje entitet..."
content-class="p-0 w-full"
/>
</template>
<template v-else>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Contracts</span>
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Accounts</span>
<span class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-800 px-3 py-1 text-xs font-medium">Payments</span>
<Badge variant="secondary">Pogodbe</Badge>
<Badge variant="secondary">Računi</Badge>
<Badge variant="secondary">Plačila</Badge>
</div>
</template>
<p class="text-xs text-muted-foreground mt-1">
Izberite katere tabele ta predloga cilja. Preslikave stolpcev lahko
dodate kasneje.
</p>
<!-- Import Mode Toggles -->
<div class="flex items-center gap-6 pt-2">
<label class="inline-flex items-center gap-2">
<Checkbox
:checked="form.meta.history_import"
@update:checked="form.meta.history_import = $event"
/>
<span class="text-sm">Uvoz zgodovine</span>
</label>
<label class="inline-flex items-center gap-2">
<Checkbox
:checked="form.meta.payments_import"
@update:checked="form.meta.payments_import = $event"
/>
<span class="text-sm">Uvoz plačil</span>
</label>
</div>
</template>
<p class="text-xs text-gray-500 mt-1">
Choose which tables this template targets. You can still define per-column
mappings later.
</p>
<div v-if="form.meta.history_import" class="mt-2 text-xs text-gray-600">
History mode allows only person/address/phone/contracts/activities/client cases. Accounts are auto-added when contracts are present and balances stay unchanged.
</div>
<div v-if="form.meta.payments_import" class="mt-2 text-xs text-gray-600">
Payments mode locks entities to:
<span class="font-medium">Contracts Accounts Payments</span> and
optimizes matching for payments import.
</div>
<div v-if="form.meta.payments_import" class="mt-3">
<label class="block text-sm font-medium text-gray-700"
>Contract match key</label
<div
v-if="form.meta.history_import"
class="mt-2 text-xs text-muted-foreground"
>
<select
v-model="form.meta.contract_key_mode"
class="mt-1 block w-full border rounded p-2"
Način zgodovine dovoljuje samo
oseba/naslovi/telefoni/pogodbe/aktivnosti/primeri strank. Računi so
samodejno dodani, ko so prisotne pogodbe in stanja ostanejo
nespremenjena.
</div>
<div
v-if="form.meta.payments_import"
class="mt-2 text-xs text-muted-foreground"
>
<option value="reference">
Reference (use only contract.reference to locate records)
</option>
</select>
<p class="text-xs text-gray-500 mt-1">
When importing payments, Contract records are located using the selected
key. Use your CSV mapping to map the appropriate column to the contract
reference.
Način plačil zaklene entitete na:
<span class="font-medium">Pogodbe Računi Plačila</span> in
optimizira ujemanje za uvoz plačil.
</div>
<div v-if="form.meta.payments_import" class="mt-3 space-y-2">
<Label>Ključ ujemanja pogodb</Label>
<Select v-model="form.meta.contract_key_mode">
<SelectTrigger>
<SelectValue placeholder="Izberite ključ pogodbe" />
</SelectTrigger>
<SelectContent>
<SelectItem value="reference">
Referenca (uporabi samo contract.reference za iskanje zapisov)
</SelectItem>
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">
Pri uvozu plačil se zapisi pogodb najdejo z uporabo izbranega ključa.
Uporabite CSV preslikavo za povezavo ustreznega stolpca z referenco
pogodbe.
</p>
</div>
</div>
</div>
<Separator />
<!-- Defaults: Segment / Decision / Action -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="space-y-2">
<Label>Privzeti segment</Label>
<Select v-model="form.meta.segment_id">
<SelectTrigger>
<SelectValue placeholder="(brez)" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="null">(brez)</SelectItem>
<SelectItem
v-for="s in props.segments || []"
:key="s.id"
:value="s.id"
>
{{ s.name }}
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label>Privzeto dejanje (za aktivnost)</Label>
<Select v-model="form.meta.action_id">
<SelectTrigger>
<SelectValue placeholder="(brez)" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="null">(brez)</SelectItem>
<SelectItem
v-for="a in props.actions || []"
:key="a.id"
:value="a.id"
>
{{ a.name }}
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label>Privzeta odločitev</Label>
<Select v-model="form.meta.decision_id" :disabled="!form.meta.action_id">
<SelectTrigger>
<SelectValue placeholder="(brez)" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="null">(brez)</SelectItem>
<SelectItem
v-for="d in decisionsForSelectedAction"
:key="d.id"
:value="d.id"
>
{{ d.name }}
</SelectItem>
</SelectContent>
</Select>
<p v-if="!form.meta.action_id" class="text-xs text-muted-foreground">
Izberite dejanje za ogled njegovih odločitev.
</p>
</div>
</div>
</div>
<!-- Defaults: Segment / Decision / Action -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700"
>Default Segment</label
>
<select
v-model="form.meta.segment_id"
class="mt-1 block w-full border rounded p-2"
>
<option :value="null">(none)</option>
<option v-for="s in props.segments || []" :key="s.id" :value="s.id">
{{ s.name }}
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700"
>Default Action (for Activity)</label
>
<select
v-model="form.meta.action_id"
class="mt-1 block w-full border rounded p-2"
>
<option :value="null">(none)</option>
<option v-for="a in props.actions || []" :key="a.id" :value="a.id">
{{ a.name }}
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700"
>Default Decision</label
>
<select
v-model="form.meta.decision_id"
class="mt-1 block w-full border rounded p-2"
:disabled="!form.meta.action_id"
>
<option :value="null">(none)</option>
<option v-for="d in decisionsForSelectedAction" :key="d.id" :value="d.id">
{{ d.name }}
</option>
</select>
<p v-if="!form.meta.action_id" class="text-xs text-gray-500 mt-1">
Select an Action to see its Decisions.
</p>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Name</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">Description</label>
<textarea
v-model="form.description"
class="mt-1 block w-full border rounded p-2"
rows="3"
/>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">Source Type</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"
>Default Record Type (optional)</label
>
<input
v-model="form.default_record_type"
<Separator />
<div class="space-y-2">
<Label for="name">Ime</Label>
<Input
id="name"
v-model="form.name"
type="text"
class="mt-1 block w-full border rounded p-2"
placeholder="e.g., account, person"
placeholder="Vnesite ime predloge"
/>
</div>
</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"
>Active</label
>
<div class="flex items-center gap-2 ml-6">
<input id="reactivate" v-model="form.reactivate" type="checkbox" class="rounded" />
<label for="reactivate" class="text-sm font-medium text-gray-700">Reactivation import</label>
<div class="space-y-2">
<Label for="description">Opis</Label>
<Textarea
id="description"
v-model="form.description"
placeholder="Vnesite opis predloge"
rows="3"
/>
</div>
</div>
<div class="pt-4">
<button
@click.prevent="submit"
class="px-4 py-2 bg-emerald-600 text-white rounded"
:disabled="form.processing"
>
{{ form.processing ? "Saving…" : "Create Template" }}
</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="source-type">Tip vira</Label>
<Select v-model="form.source_type">
<SelectTrigger id="source-type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="csv">CSV</SelectItem>
<SelectItem value="xml">XML</SelectItem>
<SelectItem value="xls">XLS</SelectItem>
<SelectItem value="xlsx">XLSX</SelectItem>
<SelectItem value="json">JSON</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="record-type">Privzeti tip zapisa (izbirno)</Label>
<Input
id="record-type"
v-model="form.default_record_type"
type="text"
placeholder="npr. račun, oseba"
/>
</div>
</div>
<div
v-if="form.errors && Object.keys(form.errors).length"
class="text-sm text-red-600"
>
<div v-for="(msg, key) in form.errors" :key="key">{{ msg }}</div>
</div>
</div>
<div class="flex items-center gap-6">
<div class="flex items-center gap-2">
<Checkbox
id="is_active"
:checked="form.is_active"
@update:checked="form.is_active = $event"
/>
<Label for="is_active" class="cursor-pointer">Aktivno</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox
id="reactivate"
:checked="form.reactivate"
@update:checked="form.reactivate = $event"
/>
<Label for="reactivate" class="cursor-pointer">Uvoz reaktivacije</Label>
</div>
</div>
<Separator />
<div class="flex items-center justify-between pt-2">
<div
v-if="form.errors && Object.keys(form.errors).length"
class="text-sm text-destructive"
>
<div v-for="(msg, key) in form.errors" :key="key">{{ msg }}</div>
</div>
<Button @click.prevent="submit" :disabled="form.processing" class="ml-auto">
{{ form.processing ? "Shranjevanje…" : "Ustvari predlogo" }}
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
</AppLayout>