321 lines
12 KiB
Vue
321 lines
12 KiB
Vue
<script setup>
|
|
import { ref } from "vue";
|
|
import { router } from "@inertiajs/vue3";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/Components/ui/accordion";
|
|
import { Card, CardContent } from "@/Components/ui/card";
|
|
import { Label } from "@/Components/ui/label";
|
|
import { Input } from "@/Components/ui/input";
|
|
import { Textarea } from "@/Components/ui/textarea";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/Components/ui/select";
|
|
import { Button } from "@/Components/ui/button";
|
|
import { Badge } from "@/Components/ui/badge";
|
|
import { ArrowUp, ArrowDown } from "lucide-vue-next";
|
|
|
|
const props = defineProps({
|
|
entities: { type: Array, default: () => [] },
|
|
entityOptions: { type: Array, default: () => [] },
|
|
fieldOptions: { type: Object, default: () => ({}) },
|
|
mappings: { type: Array, default: () => [] },
|
|
templateUuid: { type: String, required: true },
|
|
allSourceColumns: { type: Array, default: () => [] },
|
|
entityAliases: { type: Object, default: () => ({}) },
|
|
actions: { type: Array, default: () => [] },
|
|
decisions: { type: Array, default: () => [] },
|
|
});
|
|
|
|
const emit = defineEmits(["refresh"]);
|
|
|
|
const newRows = ref({});
|
|
const bulkRows = ref({});
|
|
|
|
function addRow(entity) {
|
|
const row = newRows.value[entity];
|
|
if (!row || !row.source || !row.field) return;
|
|
const target_field = `${entity}.${row.field}`;
|
|
const opts = {};
|
|
if (row.group) opts.group = row.group;
|
|
if (row.field === "meta" && row.metaKey) {
|
|
opts.key = String(row.metaKey).trim();
|
|
if (row.metaType) opts.type = String(row.metaType).trim();
|
|
}
|
|
const payload = {
|
|
source_column: row.source,
|
|
target_field,
|
|
transform: row.transform || "",
|
|
apply_mode: row.apply_mode || "both",
|
|
options: Object.keys(opts).length ? opts : null,
|
|
position: (props.mappings?.length || 0) + 1,
|
|
};
|
|
router.post(
|
|
route("importTemplates.mappings.add", { template: props.templateUuid }),
|
|
payload,
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
newRows.value[entity] = {};
|
|
emit("refresh");
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
function updateMapping(m) {
|
|
const payload = {
|
|
source_column: m.source_column,
|
|
target_field: m.target_field,
|
|
transform: m.transform || "",
|
|
apply_mode: m.apply_mode || "both",
|
|
options: m.options || null,
|
|
position: m.position,
|
|
};
|
|
router.put(
|
|
route("importTemplates.mappings.update", {
|
|
template: props.templateUuid,
|
|
mapping: m.id,
|
|
}),
|
|
payload,
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => emit("refresh"),
|
|
}
|
|
);
|
|
}
|
|
|
|
function deleteMapping(m) {
|
|
router.delete(
|
|
route("importTemplates.mappings.delete", {
|
|
template: props.templateUuid,
|
|
mapping: m.id,
|
|
}),
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => emit("refresh"),
|
|
}
|
|
);
|
|
}
|
|
|
|
function reorder(entity, direction, m) {
|
|
const all = [...props.mappings];
|
|
const aliases = (props.entityAliases[entity] || [entity]).map((a) => a + ".");
|
|
const entityMaps = all.filter((x) => {
|
|
const tf = x.target_field || "";
|
|
return aliases.some((prefix) => tf.startsWith(prefix));
|
|
});
|
|
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];
|
|
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]];
|
|
router.post(
|
|
route("importTemplates.mappings.reorder", { template: props.templateUuid }),
|
|
{ order: ordered },
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => emit("refresh"),
|
|
}
|
|
);
|
|
}
|
|
|
|
function getEntityMappings(entity) {
|
|
const aliases = (props.entityAliases[entity] || [entity]).map((a) => a + ".");
|
|
return (props.mappings || []).filter((m) => {
|
|
const tf = m.target_field || "";
|
|
return aliases.some((prefix) => tf.startsWith(prefix));
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Card>
|
|
<CardContent class="p-0">
|
|
<Accordion type="multiple" collapsible class="w-full">
|
|
<AccordionItem v-for="entity in entities" :key="entity" :value="entity">
|
|
<AccordionTrigger class="px-4 hover:no-underline">
|
|
<span class="font-medium">
|
|
{{ entityOptions.find((e) => e.key === entity)?.label || entity }}
|
|
</span>
|
|
</AccordionTrigger>
|
|
<AccordionContent class="px-4 pb-4 space-y-4">
|
|
<!-- Existing mappings -->
|
|
<div v-if="getEntityMappings(entity).length > 0" class="space-y-2">
|
|
<div
|
|
v-for="m in getEntityMappings(entity)"
|
|
:key="m.id"
|
|
class="p-3 border rounded-lg bg-muted/30"
|
|
>
|
|
<div class="grid grid-cols-1 sm:grid-cols-6 gap-2 items-center">
|
|
<div class="space-y-1">
|
|
<Label class="text-xs">Izvor</Label>
|
|
<Input v-model="m.source_column" class="text-sm" />
|
|
</div>
|
|
<div class="space-y-1">
|
|
<Label class="text-xs">Cilj</Label>
|
|
<Input v-model="m.target_field" class="text-sm" />
|
|
</div>
|
|
<div class="space-y-1">
|
|
<Label class="text-xs">Transform</Label>
|
|
<Select v-model="m.transform">
|
|
<SelectTrigger class="text-sm">
|
|
<SelectValue placeholder="Brez" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="trim">trim</SelectItem>
|
|
<SelectItem value="upper">upper</SelectItem>
|
|
<SelectItem value="lower">lower</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<Label class="text-xs">Način</Label>
|
|
<Select v-model="m.apply_mode">
|
|
<SelectTrigger class="text-sm">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="both">both</SelectItem>
|
|
<SelectItem value="insert">insert</SelectItem>
|
|
<SelectItem value="update">update</SelectItem>
|
|
<SelectItem value="keyref">keyref</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<Label class="text-xs">Group</Label>
|
|
<Input
|
|
:value="m.options?.group ?? ''"
|
|
@input="e => {
|
|
if (!m.options) m.options = {};
|
|
m.options.group = e.target.value || null;
|
|
}"
|
|
class="text-sm"
|
|
placeholder="1, 2, ..."
|
|
/>
|
|
</div>
|
|
<div class="flex items-end gap-2">
|
|
<div class="flex flex-col gap-1">
|
|
<Button
|
|
size="icon"
|
|
variant="outline"
|
|
class="h-6 w-6"
|
|
@click="reorder(entity, 'up', m)"
|
|
>
|
|
<ArrowUp class="h-3 w-3" />
|
|
</Button>
|
|
<Button
|
|
size="icon"
|
|
variant="outline"
|
|
class="h-6 w-6"
|
|
@click="reorder(entity, 'down', m)"
|
|
>
|
|
<ArrowDown class="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<Button size="sm" @click="updateMapping(m)">Shrani</Button>
|
|
<Button size="sm" variant="destructive" @click="deleteMapping(m)">
|
|
Izbriši
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-sm text-muted-foreground py-4 text-center">
|
|
Ni definiranih preslikav za to entiteto.
|
|
</div>
|
|
|
|
<!-- Add new mapping -->
|
|
<div class="p-3 bg-muted/50 rounded-lg border">
|
|
<div class="space-y-3">
|
|
<div class="text-sm font-medium">Dodaj novo preslikavo</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-5 gap-3">
|
|
<div class="space-y-2">
|
|
<Label class="text-xs">Izvorno polje</Label>
|
|
<Input
|
|
v-model="(newRows[entity] ||= {}).source"
|
|
placeholder="npr.: reference"
|
|
list="`src-opts-${entity}`"
|
|
/>
|
|
<datalist :id="`src-opts-${entity}`">
|
|
<option v-for="s in allSourceColumns" :key="s" :value="s" />
|
|
</datalist>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label class="text-xs">Polje</Label>
|
|
<Select v-model="(newRows[entity] ||= {}).field">
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="f in fieldOptions[entity] || []"
|
|
:key="f"
|
|
:value="f"
|
|
>
|
|
{{ f }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label class="text-xs">Transform</Label>
|
|
<Select v-model="(newRows[entity] ||= {}).transform">
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Brez" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="trim">trim</SelectItem>
|
|
<SelectItem value="upper">upper</SelectItem>
|
|
<SelectItem value="lower">lower</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label class="text-xs">Način</Label>
|
|
<Select v-model="(newRows[entity] ||= {}).apply_mode">
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="both" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="both">both</SelectItem>
|
|
<SelectItem value="insert">insert</SelectItem>
|
|
<SelectItem value="update">update</SelectItem>
|
|
<SelectItem value="keyref">keyref</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label class="text-xs">Group</Label>
|
|
<Input
|
|
v-model="(newRows[entity] ||= {}).group"
|
|
placeholder="1, 2, ..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Button @click="addRow(entity)" size="sm">Dodaj preslikavo</Button>
|
|
</div>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</CardContent>
|
|
</Card>
|
|
</template>
|