changes 0129092025 laptop

This commit is contained in:
2025-09-29 17:35:54 +02:00
parent 30eee6c5b0
commit 1fddf959f0
7 changed files with 277 additions and 4 deletions
+74 -2
View File
@@ -25,6 +25,28 @@ const savingMappings = ref(false);
// Persisted mappings from backend (raw view regardless of detected columns)
const persistedMappings = ref([]);
const detectedNote = ref('');
// Delimiter selection (auto by default, can be overridden by template or user)
const delimiterState = ref({ mode: 'auto', custom: '' });
const effectiveDelimiter = computed(() => {
switch (delimiterState.value.mode) {
case 'auto': return null; // let backend detect
case 'comma': return ',';
case 'semicolon': return ';';
case 'tab': return '\t';
case 'pipe': return '|';
case 'space': return ' ';
case 'custom': return delimiterState.value.custom || null;
default: return null;
}
});
// Initialize delimiter from import meta if previously chosen
const initForced = props.import?.meta?.forced_delimiter || null;
if (initForced) {
const map = { ',': 'comma', ';': 'semicolon', '\t': 'tab', '|': 'pipe', ' ': 'space' };
const mode = map[initForced] || 'custom';
delimiterState.value.mode = mode;
if (mode === 'custom') delimiterState.value.custom = initForced;
}
// Logs
const events = ref([]);
const eventsLimit = ref(200);
@@ -276,7 +298,11 @@ const selectedMappingsCount = computed(() => mappingRows.value.filter(r => !r.sk
async function fetchColumns() {
if (!importId.value) return;
const url = route('imports.columns', { import: importId.value });
const { data } = await axios.get(url, { params: { has_header: hasHeader.value ? 1 : 0 } });
const params = { has_header: hasHeader.value ? 1 : 0 };
if (effectiveDelimiter.value) {
params.delimiter = effectiveDelimiter.value;
}
const { data } = await axios.get(url, { params });
// Normalize columns to strings for consistent rendering
const colsRaw = Array.isArray(data.columns) ? data.columns : [];
const normCols = colsRaw.map((c) => {
@@ -334,6 +360,17 @@ async function applyTemplateToImport() {
withCredentials: true,
});
templateApplied.value = true;
// If template has a default delimiter, adopt it and refetch columns
const tpl = selectedTemplateOption.value;
const tplDelim = tpl?.delimiter || tpl?.meta?.delimiter || null;
if (tplDelim) {
// map to known mode if possible, else set custom
const map = { ',': 'comma', ';': 'semicolon', '\t': 'tab', '|': 'pipe', ' ': 'space' };
const mode = map[tplDelim] || 'custom';
delimiterState.value.mode = mode;
if (mode === 'custom') delimiterState.value.custom = tplDelim;
await fetchColumns();
}
await loadImportMappings();
} catch (e) {
templateApplied.value = false;
@@ -520,6 +557,13 @@ watch(() => detected.value.columns, (cols) => {
}
});
// If user changes delimiter selection, refresh detected columns
watch(() => delimiterState.value, async () => {
if (importId.value) {
await fetchColumns();
}
}, { deep: true });
async function fetchEvents() {
if (!importId.value) return;
loadingEvents.value = true;
@@ -616,6 +660,34 @@ async function fetchEvents() {
</div>
</div>
<!-- Parsing options -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 items-end" v-if="!isCompleted">
<div>
<label class="block text-sm font-medium text-gray-700">Header row</label>
<select v-model="hasHeader" class="mt-1 block w-full border rounded p-2" @change="fetchColumns">
<option :value="true">Has header</option>
<option :value="false">No header (positional)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Delimiter</label>
<select v-model="delimiterState.mode" class="mt-1 block w-full border rounded p-2">
<option value="auto">Auto-detect</option>
<option value="comma">Comma ,</option>
<option value="semicolon">Semicolon ;</option>
<option value="tab">Tab \t</option>
<option value="pipe">Pipe |</option>
<option value="space">Space </option>
<option value="custom">Custom</option>
</select>
<p class="text-xs text-gray-500 mt-1">Template default: {{ selectedTemplateOption?.meta?.delimiter || 'auto' }}</p>
</div>
<div v-if="delimiterState.mode === 'custom'">
<label class="block text-sm font-medium text-gray-700">Custom delimiter</label>
<input v-model="delimiterState.custom" maxlength="4" placeholder="," class="mt-1 block w-full border rounded p-2" />
</div>
</div>
<div class="flex gap-3" v-if="!isCompleted">
<button
@click.prevent="applyTemplateToImport"
@@ -676,7 +748,7 @@ async function fetchEvents() {
<div v-if="!isCompleted && displayRows.length" class="pt-4">
<h3 class="font-semibold mb-2">
<template v-if="!isCompleted">Detected Columns ({{ detected.has_header ? 'header' : 'positional' }})
<span class="ml-2 text-xs text-gray-500">detected: {{ detected.columns.length }}, rows: {{ displayRows.length }}</span>
<span class="ml-2 text-xs text-gray-500">detected: {{ detected.columns.length }}, rows: {{ displayRows.length }}, delimiter: {{ detected.delimiter || 'auto' }}</span>
</template>
<template v-else>Detected Columns</template>
</h3>
@@ -16,6 +16,12 @@ const form = useForm({
default_record_type: props.template.default_record_type || '',
is_active: props.template.is_active,
client_uuid: props.template.client_uuid || null,
sample_headers: props.template.sample_headers || [],
// Add meta with default delimiter support
meta: {
...(props.template.meta || {}),
delimiter: (props.template.meta && props.template.meta.delimiter) || '',
},
});
const entities = computed(() => (props.template.meta?.entities || []));
@@ -136,6 +142,10 @@ const save = () => {
// drop client change when blocked
delete payload.client_uuid;
}
// Normalize empty delimiter: remove from meta to allow auto-detect
if (payload.meta && typeof payload.meta.delimiter === 'string' && payload.meta.delimiter.trim() === '') {
delete payload.meta.delimiter;
}
useForm(payload).put(route('importTemplates.update', { template: props.template.uuid }), { preserveScroll: true });
};
// Non-blocking confirm modal state for delete
@@ -201,6 +211,18 @@ function performDelete() {
/>
<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>
<label class="block text-sm font-medium text-gray-700">Privzeti ločilni znak (CSV)</label>
<select v-model="form.meta.delimiter" class="mt-1 block w-full border rounded p-2">
<option value="">(Auto-detect)</option>
<option value=",">Comma ,</option>
<option value=";">Semicolon ;</option>
<option value="\t">Tab \t</option>
<option value="|">Pipe |</option>
<option value=" ">Space </option>
</select>
<p class="text-xs text-gray-500 mt-1">Pusti prazno za samodejno zaznavo. Uporabi, ko zaznavanje ne deluje pravilno.</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>