230 lines
6.6 KiB
Vue
230 lines
6.6 KiB
Vue
<script setup>
|
|
import { ref, watch } from "vue";
|
|
import { router } from "@inertiajs/vue3";
|
|
import DialogModal from "@/Components/DialogModal.vue";
|
|
import { Button } from "@/Components/ui/button";
|
|
import { Input } from "@/Components/ui/input";
|
|
import { Label } from "@/Components/ui/label";
|
|
import { ScrollArea } from "@/Components/ui/scroll-area";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/Components/ui/select";
|
|
import { Plus, Trash2 } from "lucide-vue-next";
|
|
|
|
const props = defineProps({
|
|
show: { type: Boolean, default: false },
|
|
client_case: { type: Object, required: true },
|
|
contract: { type: Object, default: null },
|
|
});
|
|
|
|
const emit = defineEmits(["close"]);
|
|
|
|
const processing = ref(false);
|
|
const metaEntries = ref([]);
|
|
|
|
// Extract meta entries from contract
|
|
function extractMetaEntries(contract) {
|
|
if (!contract?.meta) return [];
|
|
|
|
const results = [];
|
|
const visit = (node, keyName) => {
|
|
if (node === null || node === undefined) return;
|
|
if (Array.isArray(node)) {
|
|
node.forEach((el) => visit(el));
|
|
return;
|
|
}
|
|
if (typeof node === "object") {
|
|
const hasValue = Object.prototype.hasOwnProperty.call(node, "value");
|
|
const hasTitle = Object.prototype.hasOwnProperty.call(node, "title");
|
|
if (hasValue || hasTitle) {
|
|
const title = (node.title || keyName || "").toString().trim() || keyName || "";
|
|
results.push({
|
|
title,
|
|
value: node.value ?? "",
|
|
type: node.type || "string",
|
|
});
|
|
return;
|
|
}
|
|
for (const [k, v] of Object.entries(node)) {
|
|
visit(v, k);
|
|
}
|
|
return;
|
|
}
|
|
if (keyName) {
|
|
results.push({ title: keyName, value: node ?? "", type: "string" });
|
|
}
|
|
};
|
|
visit(contract.meta, undefined);
|
|
return results;
|
|
}
|
|
|
|
// Initialize meta entries when dialog opens
|
|
watch(
|
|
() => props.show,
|
|
(newVal) => {
|
|
if (newVal && props.contract) {
|
|
const entries = extractMetaEntries(props.contract);
|
|
metaEntries.value =
|
|
entries.length > 0 ? entries : [{ title: "", value: "", type: "string" }];
|
|
}
|
|
}
|
|
);
|
|
|
|
function addEntry() {
|
|
metaEntries.value.push({ title: "", value: "", type: "string" });
|
|
}
|
|
|
|
function removeEntry(index) {
|
|
metaEntries.value.splice(index, 1);
|
|
if (metaEntries.value.length === 0) {
|
|
metaEntries.value.push({ title: "", value: "", type: "string" });
|
|
}
|
|
}
|
|
|
|
function close() {
|
|
emit("close");
|
|
}
|
|
|
|
function submit() {
|
|
if (!props.contract?.uuid || processing.value) return;
|
|
|
|
// Filter out empty entries and build meta object
|
|
const validEntries = metaEntries.value.filter((e) => e.title && e.title.trim() !== "");
|
|
|
|
const meta = {};
|
|
validEntries.forEach((entry) => {
|
|
meta[entry.title] = {
|
|
title: entry.title,
|
|
value: entry.value,
|
|
type: entry.type,
|
|
};
|
|
});
|
|
|
|
processing.value = true;
|
|
|
|
router.patch(
|
|
route("clientCase.contract.patchMeta", {
|
|
client_case: props.client_case.uuid,
|
|
uuid: props.contract.uuid,
|
|
}),
|
|
{ meta },
|
|
{
|
|
preserveScroll: true,
|
|
only: ["contracts"],
|
|
onSuccess: () => {
|
|
close();
|
|
processing.value = false;
|
|
},
|
|
onError: () => {
|
|
processing.value = false;
|
|
},
|
|
onFinish: () => {
|
|
processing.value = false;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<DialogModal :show="show" max-width="3xl" @close="close">
|
|
<template #title>
|
|
<h3 class="text-lg font-semibold leading-6 text-foreground">Uredi Meta podatke</h3>
|
|
</template>
|
|
<template #description>
|
|
Posodobi meta podatke za pogodbo {{ contract?.reference }}
|
|
</template>
|
|
|
|
<template #content>
|
|
<form id="meta-edit-form" @submit.prevent="submit" class="space-y-4">
|
|
<ScrollArea class="h-[60vh]">
|
|
<div class="space-y-3 pr-4">
|
|
<div
|
|
v-for="(entry, index) in metaEntries"
|
|
:key="index"
|
|
class="flex items-start gap-2 p-3 border rounded-lg bg-muted/20"
|
|
>
|
|
<div class="flex-1 space-y-3">
|
|
<div>
|
|
<Label :for="`meta-title-${index}`">Naziv</Label>
|
|
<Input
|
|
:id="`meta-title-${index}`"
|
|
v-model="entry.title"
|
|
placeholder="Vnesi naziv..."
|
|
class="mt-1"
|
|
/>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<Label :for="`meta-type-${index}`">Tip</Label>
|
|
<Select v-model="entry.type">
|
|
<SelectTrigger :id="`meta-type-${index}`" class="mt-1">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="string">Tekst</SelectItem>
|
|
<SelectItem value="number">Številka</SelectItem>
|
|
<SelectItem value="date">Datum</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div>
|
|
<Label :for="`meta-value-${index}`">Vrednost</Label>
|
|
<Input
|
|
:id="`meta-value-${index}`"
|
|
v-model="entry.value"
|
|
:type="
|
|
entry.type === 'date'
|
|
? 'date'
|
|
: entry.type === 'number'
|
|
? 'number'
|
|
: 'text'
|
|
"
|
|
:step="entry.type === 'number' ? '0.01' : undefined"
|
|
placeholder="Vnesi vrednost..."
|
|
class="mt-1"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
@click="removeEntry(index)"
|
|
:disabled="metaEntries.length === 1"
|
|
class="mt-6"
|
|
>
|
|
<Trash2 class="h-4 w-4 text-destructive" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</ScrollArea>
|
|
|
|
<Button type="button" variant="outline" @click="addEntry" class="w-full">
|
|
<Plus class="h-4 w-4 mr-2" />
|
|
Dodaj vnos
|
|
</Button>
|
|
</form>
|
|
</template>
|
|
|
|
<template #footer>
|
|
<div class="flex flex-row gap-2">
|
|
<Button type="button" variant="ghost" @click="close" :disabled="processing">
|
|
Prekliči
|
|
</Button>
|
|
<Button type="submit" form="meta-edit-form" :disabled="processing">
|
|
{{ processing ? "Shranjujem..." : "Shrani" }}
|
|
</Button>
|
|
</div>
|
|
</template>
|
|
</DialogModal>
|
|
</template>
|