changes
This commit is contained in:
@@ -2,16 +2,24 @@
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import { useForm, router } from "@inertiajs/vue3";
|
||||
import { ref, computed } from "vue";
|
||||
import Multiselect from "vue-multiselect";
|
||||
import axios from "axios";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Label } from "@/Components/ui/label";
|
||||
import { Checkbox } from "@/Components/ui/checkbox";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
|
||||
// Props: provided by controller (clients + templates collections)
|
||||
const props = defineProps({
|
||||
templates: Array,
|
||||
clients: Array,
|
||||
});
|
||||
|
||||
// Basic create form (rest of workflow handled on the Continue page)
|
||||
const form = useForm({
|
||||
client_uuid: null,
|
||||
import_template_id: null,
|
||||
@@ -19,36 +27,12 @@ const form = useForm({
|
||||
file: null,
|
||||
});
|
||||
|
||||
// Multiselect bridge: client
|
||||
const selectedClientOption = computed({
|
||||
get() {
|
||||
if (!form.client_uuid) return null;
|
||||
return (props.clients || []).find((c) => c.uuid === form.client_uuid) || null;
|
||||
},
|
||||
set(val) {
|
||||
form.client_uuid = val ? val.uuid : null;
|
||||
},
|
||||
});
|
||||
|
||||
// Multiselect bridge: template
|
||||
const selectedTemplateOption = computed({
|
||||
get() {
|
||||
if (form.import_template_id == null) return null;
|
||||
return (props.templates || []).find((t) => t.id === form.import_template_id) || null;
|
||||
},
|
||||
set(val) {
|
||||
form.import_template_id = val ? val.id : null;
|
||||
},
|
||||
});
|
||||
|
||||
// Filter templates: show globals when no client; when client selected show only that client's templates (no mixing to avoid confusion)
|
||||
// Filter templates: show globals when no client; when client selected show only that client's templates
|
||||
const filteredTemplates = computed(() => {
|
||||
const cuuid = form.client_uuid;
|
||||
const list = props.templates || [];
|
||||
if (!cuuid) {
|
||||
return list.filter((t) => t.client_id == null);
|
||||
if (!form.client_uuid) {
|
||||
return props.templates.filter((t) => !t.client_id);
|
||||
}
|
||||
return list.filter((t) => t.client_uuid === cuuid || t.client_id == null);
|
||||
return props.templates.filter((t) => t.client_uuid === form.client_uuid || !t.client_id);
|
||||
});
|
||||
|
||||
const uploading = ref(false);
|
||||
@@ -57,7 +41,7 @@ const uploadError = ref(null);
|
||||
|
||||
function onFileChange(e) {
|
||||
const files = e.target.files;
|
||||
if (files && files.length) {
|
||||
if (files?.length) {
|
||||
form.file = files[0];
|
||||
uploadError.value = null;
|
||||
}
|
||||
@@ -65,50 +49,51 @@ function onFileChange(e) {
|
||||
|
||||
function onFileDrop(e) {
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length) {
|
||||
if (files?.length) {
|
||||
form.file = files[0];
|
||||
uploadError.value = null;
|
||||
}
|
||||
dragActive.value = false;
|
||||
}
|
||||
|
||||
function clearFile() {
|
||||
form.file = null;
|
||||
uploadError.value = null;
|
||||
}
|
||||
|
||||
async function startImport() {
|
||||
uploadError.value = null;
|
||||
if (!form.file) {
|
||||
uploadError.value = "Najprej izberite datoteko."; // "Select a file first."
|
||||
uploadError.value = "Najprej izberite datoteko.";
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append("file", form.file);
|
||||
if (form.import_template_id != null) {
|
||||
if (form.import_template_id) {
|
||||
fd.append("import_template_id", String(form.import_template_id));
|
||||
}
|
||||
if (form.client_uuid) {
|
||||
fd.append("client_uuid", form.client_uuid);
|
||||
}
|
||||
fd.append("has_header", form.has_header ? "1" : "0");
|
||||
|
||||
const { data } = await axios.post(route("imports.store"), fd, {
|
||||
headers: { Accept: "application/json" },
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
if (data?.uuid) {
|
||||
router.visit(route("imports.continue", { import: data.uuid }));
|
||||
return;
|
||||
}
|
||||
if (data?.id) {
|
||||
// Fallback if only numeric id returned
|
||||
} else if (data?.id) {
|
||||
router.visit(route("imports.continue", { import: data.id }));
|
||||
return;
|
||||
}
|
||||
uploadError.value = "Nepričakovan odgovor strežnika."; // Unexpected server response.
|
||||
} catch (e) {
|
||||
if (e.response?.data?.message) {
|
||||
uploadError.value = e.response.data.message;
|
||||
} else {
|
||||
uploadError.value = "Nalaganje ni uspelo."; // Upload failed.
|
||||
uploadError.value = "Nepričakovan odgovor strežnika.";
|
||||
}
|
||||
} catch (e) {
|
||||
uploadError.value = e.response?.data?.message || "Nalaganje ni uspelo.";
|
||||
console.error("Import upload failed", e.response?.status, e.response?.data || e);
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
@@ -119,13 +104,14 @@ async function startImport() {
|
||||
<template>
|
||||
<AppLayout title="Nov uvoz">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Nov uvoz</h2>
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">Nov uvoz</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow sm:rounded-lg p-6 space-y-8">
|
||||
<!-- Intro / guidance -->
|
||||
<div class="text-sm text-gray-600 leading-relaxed">
|
||||
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8">
|
||||
<div class="space-y-8 rounded-lg bg-white p-6 shadow sm:rounded-lg">
|
||||
<!-- Intro -->
|
||||
<div class="text-sm leading-relaxed text-gray-600">
|
||||
<p class="mb-2">
|
||||
1) Izberite stranko (opcijsko) in predlogo (če obstaja), 2) izberite
|
||||
datoteko (CSV, TXT, XLSX*) in 3) kliknite Začni uvoz. Nadaljnje preslikave
|
||||
@@ -137,130 +123,138 @@ async function startImport() {
|
||||
</div>
|
||||
|
||||
<!-- Client & Template selection -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Stranka</label>
|
||||
<Multiselect
|
||||
v-model="selectedClientOption"
|
||||
:options="clients"
|
||||
track-by="uuid"
|
||||
label="name"
|
||||
placeholder="Poišči stranko..."
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Predloga</label>
|
||||
<Multiselect
|
||||
v-model="selectedTemplateOption"
|
||||
:options="filteredTemplates"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="Poišči predlogo..."
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
<span
|
||||
class="ml-2 text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600"
|
||||
>{{ option.client_id ? "Client" : "Global" }}</span
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<!-- Client Select -->
|
||||
<div class="space-y-2">
|
||||
<Label>Stranka</Label>
|
||||
<Select v-model="form.client_uuid">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Izberite stranko..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="client in clients"
|
||||
:key="client.uuid"
|
||||
:value="client.uuid"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<p class="text-xs text-gray-500 mt-1" v-if="!form.client_uuid">
|
||||
{{ client.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p class="text-xs text-gray-500">
|
||||
Če stranka ni izbrana, bo uvoz globalen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Template Select -->
|
||||
<div class="space-y-2">
|
||||
<Label>Predloga</Label>
|
||||
<Select v-model="form.import_template_id">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Izberite predlogo..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="template in filteredTemplates"
|
||||
:key="template.id"
|
||||
:value="template.id"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<span class="truncate">{{ template.name }}</span>
|
||||
<span
|
||||
class="ml-2 rounded bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-600"
|
||||
>
|
||||
{{ template.client_id ? "Client" : "Global" }}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p v-if="!form.client_uuid" class="mt-1 text-xs text-gray-500">
|
||||
Prikazane so samo globalne predloge dokler ne izberete stranke.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File + Header -->
|
||||
<div class="grid grid-cols-1 gap-6 items-start">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Datoteka</label>
|
||||
<div
|
||||
class="border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors"
|
||||
:class="{
|
||||
'border-indigo-400 bg-indigo-50': dragActive,
|
||||
'border-gray-300 hover:border-gray-400': !dragActive,
|
||||
}"
|
||||
@dragover.prevent="dragActive = true"
|
||||
@dragleave.prevent="dragActive = false"
|
||||
@drop.prevent="onFileDrop"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
class="hidden"
|
||||
id="import-file-input"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<label for="import-file-input" class="block cursor-pointer select-none">
|
||||
<div v-if="!form.file" class="text-sm text-gray-600">
|
||||
Povlecite datoteko sem ali
|
||||
<span class="text-indigo-600 underline">kliknite za izbiro</span>
|
||||
</div>
|
||||
<div v-else class="text-sm text-gray-800 flex flex-col gap-1">
|
||||
<span class="font-medium">{{ form.file.name }}</span>
|
||||
<span class="text-xs text-gray-500"
|
||||
>{{ (form.file.size / 1024).toFixed(1) }} kB</span
|
||||
>
|
||||
<span
|
||||
class="text-[10px] inline-block bg-gray-100 px-1.5 py-0.5 rounded"
|
||||
>Zamenjaj</span
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center gap-2 text-sm font-medium text-gray-700">
|
||||
<input type="checkbox" v-model="form.has_header" class="rounded" />
|
||||
<span>Prva vrstica je glava</span>
|
||||
<!-- File Upload -->
|
||||
<div class="space-y-4">
|
||||
<Label>Datoteka</Label>
|
||||
<div
|
||||
class="cursor-pointer rounded-md border-2 border-dashed p-6 text-center transition-colors"
|
||||
:class="{
|
||||
'border-indigo-400 bg-indigo-50': dragActive,
|
||||
'border-gray-300 hover:border-gray-400': !dragActive,
|
||||
}"
|
||||
@dragover.prevent="dragActive = true"
|
||||
@dragleave.prevent="dragActive = false"
|
||||
@drop.prevent="onFileDrop"
|
||||
>
|
||||
<input
|
||||
id="import-file-input"
|
||||
type="file"
|
||||
class="hidden"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<label for="import-file-input" class="block cursor-pointer select-none">
|
||||
<div v-if="!form.file" class="text-sm text-gray-600">
|
||||
Povlecite datoteko sem ali
|
||||
<span class="text-indigo-600 underline">kliknite za izbiro</span>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-1 text-sm text-gray-800">
|
||||
<span class="font-medium">{{ form.file.name }}</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ (form.file.size / 1024).toFixed(1) }} kB
|
||||
</span>
|
||||
<span class="inline-block rounded bg-gray-100 px-1.5 py-0.5 text-[10px]">
|
||||
Zamenjaj
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<div class="text-xs text-gray-500 leading-relaxed">
|
||||
Če ni označeno, bodo stolpci poimenovani po zaporedju (A, B, C ...).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Has Header Checkbox -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="has-header" v-model:checked="form.has_header" />
|
||||
<Label
|
||||
for="has-header"
|
||||
class="cursor-pointer text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Prva vrstica je glava
|
||||
</Label>
|
||||
</div>
|
||||
<p class="text-xs leading-relaxed text-gray-500">
|
||||
Če ni označeno, bodo stolpci poimenovani po zaporedju (A, B, C ...).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Errors -->
|
||||
<div v-if="uploadError" class="text-sm text-red-600">
|
||||
<!-- Error Message -->
|
||||
<div v-if="uploadError" class="rounded-md bg-red-50 p-3 text-sm text-red-600">
|
||||
{{ uploadError }}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-wrap justify-end gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
form.file = null;
|
||||
uploadError = null;
|
||||
}
|
||||
"
|
||||
<div class="flex flex-wrap justify-end gap-3 border-t pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
:disabled="uploading || !form.file"
|
||||
class="px-4 py-2 text-sm rounded border bg-white disabled:opacity-50"
|
||||
@click="clearFile"
|
||||
>
|
||||
Počisti
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="startImport"
|
||||
:disabled="uploading"
|
||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded bg-indigo-600 disabled:bg-indigo-300 text-white text-sm font-medium shadow-sm"
|
||||
>
|
||||
</Button>
|
||||
<Button :disabled="uploading" @click="startImport">
|
||||
<span
|
||||
v-if="uploading"
|
||||
class="h-4 w-4 border-2 border-white/60 border-t-transparent rounded-full animate-spin"
|
||||
></span>
|
||||
<span>{{ uploading ? "Nalagam..." : "Začni uvoz" }}</span>
|
||||
</button>
|
||||
class="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-white/60 border-t-transparent"
|
||||
/>
|
||||
{{ uploading ? "Nalagam..." : "Začni uvoz" }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-400 pt-4 border-t">
|
||||
<div class="border-t pt-4 text-xs text-gray-400">
|
||||
Po nalaganju boste preusmerjeni na nadaljevanje uvoza, kjer lahko izvedete
|
||||
preslikave, simulacijo in končno obdelavo.
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user