changes 0129092025 laptop
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user