changes 0230092025
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { ref, watch, computed, onMounted } from 'vue';
|
||||
import { useForm, router } from '@inertiajs/vue3';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import axios from 'axios';
|
||||
@@ -23,6 +23,53 @@ const selectedMappingsCount = computed(() => mappingRows.value.filter(r => !r.sk
|
||||
const mappingError = ref('');
|
||||
const savingMappings = ref(false);
|
||||
|
||||
// Dynamic entity definitions and suggestions from API
|
||||
const entityDefs = ref([]);
|
||||
const entityOptions = computed(() => entityDefs.value.map(e => ({ value: e.key, label: e.label || e.key })));
|
||||
const fieldOptionsByEntity = computed(() => Object.fromEntries(entityDefs.value.map(e => [e.key, (e.fields || []).map(f => ({ value: f, label: f }))])));
|
||||
const canonicalRootByKey = computed(() => Object.fromEntries(entityDefs.value.map(e => [e.key, e.canonical_root || e.key])));
|
||||
const keyByCanonicalRoot = computed(() => {
|
||||
const m = {};
|
||||
for (const e of entityDefs.value) {
|
||||
if (e.canonical_root) {
|
||||
m[e.canonical_root] = e.key;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
});
|
||||
const suggestions = ref({});
|
||||
async function loadEntityDefs() {
|
||||
try {
|
||||
const { data } = await axios.get('/api/import-entities');
|
||||
entityDefs.value = data?.entities || [];
|
||||
} catch (e) {
|
||||
console.error('Failed to load import entity definitions', e);
|
||||
}
|
||||
}
|
||||
async function refreshSuggestions(columns) {
|
||||
const cols = Array.isArray(columns) ? columns : (detected.value.columns || []);
|
||||
if (!cols || cols.length === 0) { return; }
|
||||
try {
|
||||
const { data } = await axios.post('/api/import-entities/suggest', { columns: cols });
|
||||
suggestions.value = { ...suggestions.value, ...(data?.suggestions || {}) };
|
||||
} catch (e) {
|
||||
console.error('Failed to load suggestions', e);
|
||||
}
|
||||
}
|
||||
|
||||
function applySuggestionToRow(row) {
|
||||
const s = suggestions.value[row.source_column];
|
||||
if (!s) return false;
|
||||
if (!fieldOptionsByEntity.value[s.entity]) return false;
|
||||
row.entity = s.entity;
|
||||
row.field = s.field;
|
||||
// default transform on if missing
|
||||
if (!row.transform) { row.transform = 'trim'; }
|
||||
if (!row.apply_mode) { row.apply_mode = 'both'; }
|
||||
row.skip = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
client_uuid: null,
|
||||
import_template_id: null,
|
||||
@@ -120,6 +167,7 @@ async function fetchColumns() {
|
||||
position: idx,
|
||||
}));
|
||||
}
|
||||
await refreshSuggestions(detected.value.columns);
|
||||
// If there are mappings already (template applied or saved), load them to auto-assign
|
||||
await loadImportMappings();
|
||||
}
|
||||
@@ -203,9 +251,9 @@ async function loadImportMappings() {
|
||||
mappingRows.value = (mappingRows.value || []).map((r, idx) => {
|
||||
const m = bySource.get(r.source_column);
|
||||
if (!m) return r;
|
||||
// Parse target_field like 'person.first_name' into UI entity/field
|
||||
const [record, field] = String(m.target_field || '').split('.', 2);
|
||||
const entity = recordToEntityKey(record);
|
||||
// Parse target_field like 'person.first_name' into UI entity/field
|
||||
const [record, field] = String(m.target_field || '').split('.', 2);
|
||||
const entity = keyByCanonicalRoot.value[record] || record;
|
||||
return {
|
||||
...r,
|
||||
entity,
|
||||
@@ -244,61 +292,7 @@ async function processImport() {
|
||||
}
|
||||
}
|
||||
|
||||
const entityOptions = [
|
||||
{ value: 'person', label: 'Person' },
|
||||
{ value: 'person_addresses', label: 'Person Address' },
|
||||
{ value: 'person_phones', label: 'Person Phone' },
|
||||
{ value: 'emails', label: 'Email' },
|
||||
{ value: 'accounts', label: 'Account' },
|
||||
{ value: 'contracts', label: 'Contract' },
|
||||
];
|
||||
|
||||
const fieldOptionsByEntity = {
|
||||
person: [
|
||||
{ value: 'first_name', label: 'First name' },
|
||||
{ value: 'last_name', label: 'Last name' },
|
||||
{ value: 'full_name', label: 'Full name' },
|
||||
{ value: 'tax_number', label: 'Tax number' },
|
||||
{ value: 'social_security_number', label: 'SSN' },
|
||||
{ value: 'birthday', label: 'Birthday' },
|
||||
{ value: 'gender', label: 'Gender' },
|
||||
{ value: 'description', label: 'Description' },
|
||||
],
|
||||
person_addresses: [
|
||||
{ value: 'address', label: 'Address' },
|
||||
{ value: 'country', label: 'Country' },
|
||||
{ value: 'type_id', label: 'Address Type Id' },
|
||||
{ value: 'description', label: 'Description' },
|
||||
],
|
||||
person_phones: [
|
||||
{ value: 'nu', label: 'Phone number' },
|
||||
{ value: 'country_code', label: 'Country code' },
|
||||
{ value: 'type_id', label: 'Phone Type Id' },
|
||||
{ value: 'description', label: 'Description' },
|
||||
],
|
||||
emails: [
|
||||
{ value: 'value', label: 'Email address' },
|
||||
{ value: 'label', label: 'Label' },
|
||||
{ value: 'is_primary', label: 'Is primary' },
|
||||
],
|
||||
accounts: [
|
||||
{ value: 'reference', label: 'Reference' },
|
||||
{ value: 'description', label: 'Description' },
|
||||
{ value: 'contract_id', label: 'Contract Id' },
|
||||
{ value: 'contract_reference', label: 'Contract Reference' },
|
||||
{ value: 'type_id', label: 'Account Type Id' },
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'balance_amount', label: 'Balance Amount' },
|
||||
],
|
||||
contracts: [
|
||||
{ value: 'reference', label: 'Reference' },
|
||||
{ value: 'start_date', label: 'Start Date' },
|
||||
{ value: 'end_date', label: 'End Date' },
|
||||
{ value: 'description', label: 'Description' },
|
||||
{ value: 'client_case_id', label: 'Client Case Id' },
|
||||
{ value: 'type_id', label: 'Contract Type Id' },
|
||||
],
|
||||
};
|
||||
// entity options and fields are dynamic from API
|
||||
|
||||
async function saveMappings() {
|
||||
if (!importId.value) return;
|
||||
@@ -307,7 +301,7 @@ async function saveMappings() {
|
||||
.filter(r => !r.skip && r.entity && r.field)
|
||||
.map(r => ({
|
||||
source_column: r.source_column,
|
||||
target_field: `${entityKeyToRecord(r.entity)}.${r.field}`,
|
||||
target_field: `${(canonicalRootByKey.value[r.entity] || r.entity)}.${r.field}`,
|
||||
transform: r.transform || null,
|
||||
apply_mode: r.apply_mode || 'both',
|
||||
options: null,
|
||||
@@ -350,24 +344,9 @@ watch(mappingRows, () => {
|
||||
mappingError.value = '';
|
||||
}, { deep: true });
|
||||
|
||||
function entityKeyToRecord(key) {
|
||||
// Map UI entities to record_type nouns used by processor
|
||||
if (key === 'person_addresses') return 'address';
|
||||
if (key === 'person_phones') return 'phone';
|
||||
if (key === 'emails') return 'email';
|
||||
if (key === 'accounts') return 'account';
|
||||
if (key === 'contracts') return 'contract';
|
||||
return 'person';
|
||||
}
|
||||
|
||||
function recordToEntityKey(record) {
|
||||
if (record === 'address') return 'person_addresses';
|
||||
if (record === 'phone') return 'person_phones';
|
||||
if (record === 'email') return 'emails';
|
||||
if (record === 'account') return 'accounts';
|
||||
if (record === 'contract') return 'contracts';
|
||||
return 'person';
|
||||
}
|
||||
onMounted(async () => {
|
||||
await loadEntityDefs();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -472,7 +451,13 @@ function recordToEntityKey(record) {
|
||||
</div>
|
||||
|
||||
<div v-if="detected.columns.length" class="pt-4">
|
||||
<h3 class="font-semibold mb-2">Detected Columns ({{ detected.has_header ? 'header' : 'positional' }})</h3>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="font-semibold">Detected Columns ({{ detected.has_header ? 'header' : 'positional' }})</h3>
|
||||
<button
|
||||
class="px-3 py-1.5 border rounded text-sm"
|
||||
@click.prevent="(async () => { await refreshSuggestions(detected.columns); mappingRows.forEach(r => applySuggestionToRow(r)); })()"
|
||||
>Auto map suggestions</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full border bg-white">
|
||||
<thead>
|
||||
@@ -487,7 +472,15 @@ function recordToEntityKey(record) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, idx) in mappingRows" :key="idx" class="border-t">
|
||||
<td class="p-2 border text-sm">{{ row.source_column }}</td>
|
||||
<td class="p-2 border text-sm">
|
||||
<div>{{ row.source_column }}</div>
|
||||
<div class="text-xs mt-1" v-if="suggestions[row.source_column]">
|
||||
<span class="text-gray-500">Suggest:</span>
|
||||
<button class="ml-1 underline text-indigo-700 hover:text-indigo-900" @click.prevent="applySuggestionToRow(row)">
|
||||
{{ suggestions[row.source_column].entity }}.{{ suggestions[row.source_column].field }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-2 border">
|
||||
<select v-model="row.entity" class="border rounded p-1 w-full">
|
||||
<option value="">—</option>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,13 @@ import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
clients: Array,
|
||||
segments: Array,
|
||||
decisions: Array,
|
||||
actions: Array,
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
@@ -16,6 +20,22 @@ const form = useForm({
|
||||
is_active: true,
|
||||
client_uuid: null,
|
||||
entities: [],
|
||||
meta: {
|
||||
segment_id: null,
|
||||
decision_id: null,
|
||||
action_id: null,
|
||||
delimiter: '',
|
||||
},
|
||||
});
|
||||
|
||||
const decisionsForSelectedAction = computed(() => {
|
||||
const act = (props.actions || []).find(a => a.id === form.meta.action_id);
|
||||
return act?.decisions || [];
|
||||
});
|
||||
|
||||
watch(() => form.meta.action_id, () => {
|
||||
// Clear decision when action changes to enforce valid pair
|
||||
form.meta.decision_id = null;
|
||||
});
|
||||
|
||||
function submit() {
|
||||
@@ -75,6 +95,32 @@ function submit() {
|
||||
<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>
|
||||
</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 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>
|
||||
<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>
|
||||
<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" />
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup>
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useForm, Link } from '@inertiajs/vue3';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import axios from 'axios';
|
||||
import { computed as vComputed, watch as vWatch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
template: Object,
|
||||
clients: Array,
|
||||
segments: Array,
|
||||
decisions: Array,
|
||||
actions: Array,
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
@@ -24,6 +29,15 @@ const form = useForm({
|
||||
},
|
||||
});
|
||||
|
||||
const decisionsForSelectedAction = vComputed(() => {
|
||||
const act = (props.actions || []).find(a => a.id === form.meta.action_id);
|
||||
return act?.decisions || [];
|
||||
});
|
||||
|
||||
vWatch(() => form.meta.action_id, () => {
|
||||
form.meta.decision_id = 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)
|
||||
@@ -42,6 +56,31 @@ const unassignedSourceColumns = computed(() => {
|
||||
});
|
||||
const unassignedState = ref({});
|
||||
|
||||
// Dynamic Import Entity definitions and field options from API
|
||||
const entityDefs = ref([]);
|
||||
const entityOptions = computed(() => entityDefs.value.map(e => ({ key: e.key, label: e.label || e.key })));
|
||||
const fieldOptions = computed(() => Object.fromEntries(entityDefs.value.map(e => [e.key, e.fields || []])));
|
||||
const ENTITY_ALIASES = computed(() => {
|
||||
const map = {};
|
||||
for (const e of entityDefs.value) {
|
||||
const aliases = Array.isArray(e.aliases) ? [...e.aliases] : [];
|
||||
if (!aliases.includes(e.key)) {
|
||||
aliases.push(e.key);
|
||||
}
|
||||
map[e.key] = aliases;
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
async function loadEntityDefs() {
|
||||
try {
|
||||
const { data } = await axios.get('/api/import-entities');
|
||||
entityDefs.value = data?.entities || [];
|
||||
} catch (e) {
|
||||
console.error('Failed to load import entity definitions', e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveUnassigned(m) {
|
||||
const st = unassignedState.value[m.id] || {};
|
||||
if (st.entity && st.field) {
|
||||
@@ -52,25 +91,24 @@ function saveUnassigned(m) {
|
||||
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' },
|
||||
];
|
||||
// Suggestions powered by backend API
|
||||
const suggestions = ref({}); // { [sourceColumn]: { entity, field } }
|
||||
async function refreshSuggestions(columns) {
|
||||
const cols = Array.isArray(columns) ? columns : unassignedSourceColumns.value;
|
||||
if (!cols || cols.length === 0) { return; }
|
||||
try {
|
||||
const { data } = await axios.post('/api/import-entities/suggest', { columns: cols });
|
||||
suggestions.value = { ...suggestions.value, ...(data?.suggestions || {}) };
|
||||
} catch (e) {
|
||||
console.error('Failed to load suggestions', e);
|
||||
}
|
||||
}
|
||||
|
||||
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' ],
|
||||
};
|
||||
// entityOptions and fieldOptions now come from API (see computed above)
|
||||
|
||||
// ENTITY_ALIASES computed above from API definitions
|
||||
|
||||
// UI_PREFERRED no longer needed; UI keys are already the desired keys
|
||||
|
||||
function toggle(entity) {
|
||||
const el = document.getElementById(`acc-${entity}`);
|
||||
@@ -116,7 +154,11 @@ function deleteMapping(m) {
|
||||
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 aliases = (ENTITY_ALIASES.value[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;
|
||||
@@ -158,6 +200,18 @@ function performDelete() {
|
||||
onFinish: () => { deleteConfirmOpen.value = false; },
|
||||
});
|
||||
}
|
||||
|
||||
// Load entity definitions and initial suggestions
|
||||
onMounted(async () => {
|
||||
await loadEntityDefs();
|
||||
await refreshSuggestions();
|
||||
});
|
||||
|
||||
// Refresh suggestions when unassigned list changes
|
||||
watch(() => unassignedSourceColumns.value.join('|'), async () => {
|
||||
await refreshSuggestions();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -223,6 +277,28 @@ function performDelete() {
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">Pusti prazno za samodejno zaznavo. Uporabi, ko zaznavanje ne deluje pravilno.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Privzeti Segment</label>
|
||||
<select v-model="form.meta.segment_id" class="mt-1 block w-full border rounded p-2">
|
||||
<option :value="null">(brez)</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">Privzeta Odločitev</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">(brez)</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">Najprej izberi dejanje, nato odločitev.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Privzeto Dejanja (Activity)</label>
|
||||
<select v-model="form.meta.action_id" class="mt-1 block w-full border rounded p-2">
|
||||
<option :value="null">(brez)</option>
|
||||
<option v-for="a in (props.actions || [])" :key="a.id" :value="a.id">{{ a.name }}</option>
|
||||
</select>
|
||||
</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>
|
||||
@@ -313,6 +389,13 @@ function performDelete() {
|
||||
<div class="text-sm">
|
||||
<div class="text-gray-500 text-xs">Source</div>
|
||||
<div class="font-medium">{{ m.source_column }}</div>
|
||||
<div class="mt-1 text-xs" v-if="suggestions[m.source_column]">
|
||||
<span class="text-gray-500">Predlog:</span>
|
||||
<button
|
||||
class="ml-1 underline text-indigo-700 hover:text-indigo-900"
|
||||
@click.prevent="(() => { const s = suggestions[m.source_column]; if (!s) return; (unassignedState[m.id] ||= {}).entity = s.entity; (unassignedState[m.id] ||= {}).field = s.field; saveUnassigned(m); })()"
|
||||
>{{ suggestions[m.source_column].entity }}.{{ suggestions[m.source_column].field }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600">Entity</label>
|
||||
@@ -364,7 +447,10 @@ function performDelete() {
|
||||
<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 v-for="m in props.template.mappings.filter(m => {
|
||||
const aliases = ENTITY_ALIASES[entity] || [entity];
|
||||
return aliases.some(a => m.target_field?.startsWith(a + '.'));
|
||||
})" :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" />
|
||||
@@ -470,9 +556,10 @@ function performDelete() {
|
||||
@click.prevent="(() => {
|
||||
const b = bulkRows[entity] ||= {};
|
||||
if (!b.sources || !b.sources.trim()) return;
|
||||
const eKey = entity;
|
||||
useForm({
|
||||
sources: b.sources,
|
||||
entity,
|
||||
entity: eKey,
|
||||
default_field: b.default_field || null,
|
||||
transform: b.transform || null,
|
||||
apply_mode: b.apply_mode || 'both',
|
||||
@@ -483,6 +570,35 @@ function performDelete() {
|
||||
})()"
|
||||
class="px-3 py-2 bg-indigo-600 text-white rounded"
|
||||
>Dodaj več</button>
|
||||
<button
|
||||
class="ml-2 px-3 py-2 bg-emerald-600 text-white rounded"
|
||||
@click.prevent="(async () => {
|
||||
const b = bulkRows[entity] ||= {};
|
||||
if (!b.sources || !b.sources.trim()) return;
|
||||
const list = b.sources.split(/[\n,]+/).map(s=>s.trim()).filter(Boolean);
|
||||
try {
|
||||
const { data } = await axios.post('/api/import-entities/suggest', { columns: list });
|
||||
const sugg = data?.suggestions || {};
|
||||
for (const src of list) {
|
||||
const s = sugg[src];
|
||||
if (!s) continue;
|
||||
const aliases = ENTITY_ALIASES.value[entity] || [entity];
|
||||
if (!aliases.includes(s.entity)) continue; // only apply for this entity
|
||||
const payload = {
|
||||
source_column: src,
|
||||
target_field: `${s.entity}.${s.field}`,
|
||||
transform: b.transform || null,
|
||||
apply_mode: b.apply_mode || 'both',
|
||||
position: (props.template.mappings?.length || 0) + 1,
|
||||
};
|
||||
useForm(payload).post(route('importTemplates.mappings.add', { template: props.template.uuid }), { preserveScroll: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to auto-add suggestions', e);
|
||||
}
|
||||
bulkRows[entity] = {};
|
||||
})()"
|
||||
>Auto iz predlog</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user