496 lines
16 KiB
Vue
496 lines
16 KiB
Vue
<script setup>
|
|
import AppLayout from "@/Layouts/AppLayout.vue";
|
|
import { Link, useForm, router } from "@inertiajs/vue3";
|
|
import { ref, computed } from "vue";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/Components/ui/card";
|
|
import { Button } from "@/Components/ui/button";
|
|
import { Badge } from "@/Components/ui/badge";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "@/Components/ui/alert-dialog";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/Components/ui/dialog";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/Components/ui/select";
|
|
import InputLabel from "@/Components/InputLabel.vue";
|
|
import { Separator } from "@/Components/ui/separator";
|
|
import AppCard from "@/Components/app/ui/card/AppCard.vue";
|
|
import { ListIndentIncreaseIcon, DownloadIcon, UploadIcon } from "lucide-vue-next";
|
|
import TableActions from "@/Components/DataTable/TableActions.vue";
|
|
import ActionMenuItem from "@/Components/DataTable/ActionMenuItem.vue";
|
|
import {
|
|
faPencil,
|
|
faTrash,
|
|
faFileExport,
|
|
faFileImport,
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
|
|
// Non-blocking confirm modal state
|
|
const confirmOpen = ref(false);
|
|
const confirmUuid = ref(null);
|
|
const deleteForm = useForm({});
|
|
|
|
// Import modal state
|
|
const importModalOpen = ref(false);
|
|
const importForm = useForm({
|
|
file: null,
|
|
client_uuid: null,
|
|
segment_id: null,
|
|
decision_id: null,
|
|
action_id: null,
|
|
activity_action_id: null,
|
|
activity_decision_id: null,
|
|
});
|
|
|
|
const fileInputRef = ref(null);
|
|
const importedData = ref(null);
|
|
|
|
function requestDelete(uuid) {
|
|
confirmUuid.value = uuid;
|
|
confirmOpen.value = true;
|
|
}
|
|
|
|
function performDelete() {
|
|
if (!confirmUuid.value) return;
|
|
deleteForm.delete(route("importTemplates.destroy", { template: confirmUuid.value }), {
|
|
preserveScroll: true,
|
|
onFinish: () => {
|
|
confirmOpen.value = false;
|
|
confirmUuid.value = null;
|
|
},
|
|
});
|
|
}
|
|
|
|
function cancelDelete() {
|
|
confirmOpen.value = false;
|
|
confirmUuid.value = null;
|
|
}
|
|
|
|
function exportTemplate(uuid) {
|
|
window.location.href = route("importTemplates.export", { template: uuid });
|
|
}
|
|
|
|
function openImportModal() {
|
|
importModalOpen.value = true;
|
|
}
|
|
|
|
function cancelImport() {
|
|
importModalOpen.value = false;
|
|
importForm.reset();
|
|
importedData.value = null;
|
|
}
|
|
|
|
function handleFileChange(event) {
|
|
const file = event.target.files[0];
|
|
importForm.file = file;
|
|
|
|
// Parse JSON to show existing IDs
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
try {
|
|
const json = JSON.parse(e.target.result);
|
|
importedData.value = json;
|
|
} catch (error) {
|
|
console.error("Failed to parse JSON:", error);
|
|
importedData.value = null;
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
}
|
|
|
|
function performImport() {
|
|
// Convert string IDs to integers before submitting
|
|
const formData = {
|
|
...importForm.data(),
|
|
segment_id: importForm.segment_id ? parseInt(importForm.segment_id) : null,
|
|
decision_id: importForm.decision_id ? parseInt(importForm.decision_id) : null,
|
|
action_id: importForm.action_id ? parseInt(importForm.action_id) : null,
|
|
activity_action_id: importForm.activity_action_id
|
|
? parseInt(importForm.activity_action_id)
|
|
: null,
|
|
activity_decision_id: importForm.activity_decision_id
|
|
? parseInt(importForm.activity_decision_id)
|
|
: null,
|
|
};
|
|
|
|
importForm
|
|
.transform(() => formData)
|
|
.post(route("importTemplates.import"), {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
importModalOpen.value = false;
|
|
importForm.reset();
|
|
importedData.value = null;
|
|
},
|
|
});
|
|
}
|
|
|
|
const props = defineProps({
|
|
templates: Array,
|
|
clients: Array,
|
|
segments: Array,
|
|
decisions: Array,
|
|
actions: Array,
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout title="Uvozne predloge">
|
|
<template #header>
|
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Uvozne predloge</h2>
|
|
</template>
|
|
|
|
<div class="py-6">
|
|
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
|
<AppCard
|
|
title=""
|
|
padding="none"
|
|
class="p-0! gap-0"
|
|
header-class="py-3! px-4 gap-0 text-muted-foreground"
|
|
body-class=""
|
|
>
|
|
<template #header>
|
|
<div class="flex items-center gap-2">
|
|
<ListIndentIncreaseIcon size="18" />
|
|
<CardTitle class="uppercase">Predloge uvoza</CardTitle>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="flex items-center justify-between border-t border-b py-2 px-4">
|
|
<div>
|
|
<CardDescription>
|
|
Skupaj {{ props.templates?.length || 0 }} predlog{{
|
|
props.templates?.length === 1 ? "a" : ""
|
|
}}
|
|
</CardDescription>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<Button variant="outline" @click="openImportModal">
|
|
<UploadIcon class="w-4 h-4 mr-2" />
|
|
Uvozi predlogo
|
|
</Button>
|
|
<Button as-child>
|
|
<Link :href="route('importTemplates.create')"> Nova predloga </Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="!props.templates || props.templates.length === 0"
|
|
class="p-8 text-center text-muted-foreground"
|
|
>
|
|
Ni predlog uvoza.
|
|
</div>
|
|
<div v-else class="grid gap-4 p-4">
|
|
<Card
|
|
v-for="t in props.templates"
|
|
:key="t.uuid"
|
|
class="hover:shadow-md transition-shadow px-0! p-4"
|
|
>
|
|
<CardHeader>
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1">
|
|
<CardTitle class="text-base">{{ t.name }}</CardTitle>
|
|
<CardDescription class="mt-1">
|
|
{{ t.description }}
|
|
</CardDescription>
|
|
</div>
|
|
<TableActions align="right">
|
|
<template #default>
|
|
<ActionMenuItem
|
|
:icon="faPencil"
|
|
label="Uredi"
|
|
@click="
|
|
$inertia.visit(
|
|
route('importTemplates.edit', { template: t.uuid })
|
|
)
|
|
"
|
|
/>
|
|
<ActionMenuItem
|
|
:icon="faFileExport"
|
|
label="Izvozi"
|
|
@click="exportTemplate(t.uuid)"
|
|
/>
|
|
<ActionMenuItem
|
|
:icon="faTrash"
|
|
label="Izbriši"
|
|
danger
|
|
@click="requestDelete(t.uuid)"
|
|
/>
|
|
</template>
|
|
</TableActions>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent class="pt-0">
|
|
<div class="flex items-center gap-2">
|
|
<Badge variant="outline" class="text-xs">
|
|
{{ t.client?.name || "Globalno" }}
|
|
</Badge>
|
|
<Badge variant="secondary" class="text-xs">
|
|
{{ t.source_type.toUpperCase() }}
|
|
</Badge>
|
|
<Badge :variant="t.is_active ? 'default' : 'secondary'" class="text-xs">
|
|
{{ t.is_active ? "Aktivno" : "Neaktivno" }}
|
|
</Badge>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</AppCard>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Confirm Delete Dialog -->
|
|
<AlertDialog
|
|
:open="confirmOpen"
|
|
@update:open="
|
|
(val) => {
|
|
if (!val) cancelDelete();
|
|
}
|
|
"
|
|
>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Izbrišem predlogo?</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
Tega dejanja ni mogoče razveljaviti. Vse preslikave te predloge bodo
|
|
izbrisane.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel @click="cancelDelete" :disabled="deleteForm.processing">
|
|
Prekliči
|
|
</AlertDialogCancel>
|
|
<Button
|
|
@click="performDelete"
|
|
:disabled="deleteForm.processing"
|
|
class="bg-destructive hover:bg-destructive/90"
|
|
>
|
|
<span v-if="deleteForm.processing">Brisanje…</span>
|
|
<span v-else>Izbriši</span>
|
|
</Button>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
|
|
<!-- Import Template Dialog -->
|
|
<Dialog v-model:open="importModalOpen">
|
|
<DialogContent class="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Uvozi predlogo uvoza</DialogTitle>
|
|
<DialogDescription>
|
|
Izberite JSON datoteko za uvoz predloge uvoza. Po potrebi zamenjajte ID-je z
|
|
lokalnimi vrednostmi.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div class="space-y-4 py-4">
|
|
<div>
|
|
<InputLabel>Datoteka</InputLabel>
|
|
<input
|
|
ref="fileInputRef"
|
|
type="file"
|
|
accept=".json,application/json"
|
|
@change="handleFileChange"
|
|
class="w-full mt-1"
|
|
/>
|
|
<p v-if="importForm.errors.file" class="text-sm text-destructive mt-2">
|
|
{{ importForm.errors.file }}
|
|
</p>
|
|
</div>
|
|
|
|
<Separator v-if="importedData?.meta" />
|
|
|
|
<!-- Show ID mapping options if file is loaded and has meta -->
|
|
<div v-if="importedData?.meta" class="space-y-4">
|
|
<p class="text-sm text-muted-foreground">
|
|
Predloga vsebuje naslednje ID-je. Izberite nove vrednosti, če želite
|
|
zamenjati originalne ID-je:
|
|
</p>
|
|
|
|
<!-- Client -->
|
|
<div v-if="importedData.meta.client_id || clients.length > 0">
|
|
<InputLabel for="client_uuid">Stranka</InputLabel>
|
|
<Select v-model="importForm.client_uuid">
|
|
<SelectTrigger id="client_uuid">
|
|
<SelectValue placeholder="Izberi stranko (opcijsko)" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="client in clients"
|
|
:key="client.uuid"
|
|
:value="client.uuid"
|
|
>
|
|
{{ client.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Segment -->
|
|
<div v-if="importedData.meta.segment_id">
|
|
<InputLabel for="segment_id">
|
|
Segment
|
|
<span class="text-muted-foreground"
|
|
>(trenutno: {{ importedData.meta.segment_id }})</span
|
|
>
|
|
</InputLabel>
|
|
<Select v-model="importForm.segment_id">
|
|
<SelectTrigger id="segment_id">
|
|
<SelectValue placeholder="Obdrži original ali izberi nov" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="segment in segments"
|
|
:key="segment.id"
|
|
:value="segment.id.toString()"
|
|
>
|
|
{{ segment.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Decision -->
|
|
<div v-if="importedData.meta.decision_id">
|
|
<InputLabel for="decision_id">
|
|
Odločitev
|
|
<span class="text-muted-foreground"
|
|
>(trenutno: {{ importedData.meta.decision_id }})</span
|
|
>
|
|
</InputLabel>
|
|
<Select v-model="importForm.decision_id">
|
|
<SelectTrigger id="decision_id">
|
|
<SelectValue placeholder="Obdrži original ali izberi nov" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="decision in decisions"
|
|
:key="decision.id"
|
|
:value="decision.id.toString()"
|
|
>
|
|
{{ decision.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Action -->
|
|
<div v-if="importedData.meta.action_id">
|
|
<InputLabel for="action_id">
|
|
Akcija
|
|
<span class="text-muted-foreground"
|
|
>(trenutno: {{ importedData.meta.action_id }})</span
|
|
>
|
|
</InputLabel>
|
|
<Select v-model="importForm.action_id">
|
|
<SelectTrigger id="action_id">
|
|
<SelectValue placeholder="Obdrži original ali izberi nov" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="action in actions"
|
|
:key="action.id"
|
|
:value="action.id.toString()"
|
|
>
|
|
{{ action.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Activity Action -->
|
|
<div v-if="importedData.meta.activity_action_id">
|
|
<InputLabel for="activity_action_id">
|
|
Aktivnost - Akcija
|
|
<span class="text-muted-foreground"
|
|
>(trenutno: {{ importedData.meta.activity_action_id }})</span
|
|
>
|
|
</InputLabel>
|
|
<Select v-model="importForm.activity_action_id">
|
|
<SelectTrigger id="activity_action_id">
|
|
<SelectValue placeholder="Obdrži original ali izberi nov" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="action in actions"
|
|
:key="action.id"
|
|
:value="action.id.toString()"
|
|
>
|
|
{{ action.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Activity Decision -->
|
|
<div v-if="importedData.meta.activity_decision_id">
|
|
<InputLabel for="activity_decision_id">
|
|
Aktivnost - Odločitev
|
|
<span class="text-muted-foreground"
|
|
>(trenutno: {{ importedData.meta.activity_decision_id }})</span
|
|
>
|
|
</InputLabel>
|
|
<Select v-model="importForm.activity_decision_id">
|
|
<SelectTrigger id="activity_decision_id">
|
|
<SelectValue placeholder="Obdrži original ali izberi nov" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem
|
|
v-for="decision in decisions"
|
|
:key="decision.id"
|
|
:value="decision.id.toString()"
|
|
>
|
|
{{ decision.name }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
variant="outline"
|
|
@click="cancelImport"
|
|
:disabled="importForm.processing"
|
|
>
|
|
Prekliči
|
|
</Button>
|
|
<Button
|
|
@click="performImport"
|
|
:disabled="importForm.processing || !importForm.file"
|
|
>
|
|
<span v-if="importForm.processing">Uvažam…</span>
|
|
<span v-else>Uvozi</span>
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</AppLayout>
|
|
</template>
|