Option to edit contract metadata
This commit is contained in:
parent
edbdb64102
commit
44f9f8f9fa
|
|
@ -396,6 +396,21 @@ public function updateContractSegment(ClientCase $clientCase, string $uuid, Requ
|
|||
return back()->with('success', 'Contract segment updated.');
|
||||
}
|
||||
|
||||
public function patchContractMeta(ClientCase $clientCase, string $uuid, Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'meta' => ['required', 'array'],
|
||||
]);
|
||||
|
||||
$contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail();
|
||||
|
||||
$contract->update([
|
||||
'meta' => $validated['meta'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Meta podatki so bili posodobljeni.'));
|
||||
}
|
||||
|
||||
public function attachSegment(ClientCase $clientCase, Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
<script setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { useForm } from "@inertiajs/vue3";
|
||||
import DialogModal from "@/Components/DialogModal.vue";
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
contract: { type: Object, default: null },
|
||||
clientCase: { type: Object, required: true },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
const metaFields = ref([]);
|
||||
|
||||
// Extract meta fields when contract changes
|
||||
watch(
|
||||
() => props.contract,
|
||||
(c) => {
|
||||
if (!c) {
|
||||
metaFields.value = [];
|
||||
return;
|
||||
}
|
||||
metaFields.value = extractMetaFields(c?.meta || {});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function extractMetaFields(meta, parentKey = "") {
|
||||
const results = [];
|
||||
const visit = (node, path) => {
|
||||
if (node === null || node === undefined) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach((el, idx) => visit(el, `${path}[${idx}]`));
|
||||
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 || path || "Meta").toString().trim();
|
||||
const type = node.type || (typeof node.value === "number" ? "number" : "text");
|
||||
results.push({
|
||||
path,
|
||||
title,
|
||||
value: node.value ?? "",
|
||||
type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (const [k, v] of Object.entries(node)) {
|
||||
const newPath = path ? `${path}.${k}` : k;
|
||||
visit(v, newPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (path) {
|
||||
results.push({ path, title: path, value: node, type: "text" });
|
||||
}
|
||||
};
|
||||
visit(meta, "");
|
||||
return results;
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
meta: {},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(val) => {
|
||||
if (val && props.contract) {
|
||||
// Rebuild meta structure for form
|
||||
const metaObj = {};
|
||||
metaFields.value.forEach((field) => {
|
||||
setNestedValue(metaObj, field.path, {
|
||||
title: field.title,
|
||||
value: field.value,
|
||||
type: field.type,
|
||||
});
|
||||
});
|
||||
form.meta = metaObj;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function setNestedValue(obj, path, value) {
|
||||
const parts = path.split(/\.|\[|\]/).filter(Boolean);
|
||||
let current = obj;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i];
|
||||
if (!current[part]) {
|
||||
const nextPart = parts[i + 1];
|
||||
current[part] = /^\d+$/.test(nextPart) ? [] : {};
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
current[parts[parts.length - 1]] = value;
|
||||
}
|
||||
|
||||
function updateFieldValue(field, newValue) {
|
||||
field.value = newValue;
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
emit("close");
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
if (!props.contract?.uuid) return;
|
||||
|
||||
// Rebuild meta object from fields
|
||||
const metaObj = {};
|
||||
metaFields.value.forEach((field) => {
|
||||
setNestedValue(metaObj, field.path, {
|
||||
title: field.title,
|
||||
value: field.value,
|
||||
type: field.type,
|
||||
});
|
||||
});
|
||||
|
||||
form.meta = metaObj;
|
||||
|
||||
form.patch(
|
||||
route("clientCase.contract.patchMeta", {
|
||||
client_case: props.clientCase.uuid,
|
||||
uuid: props.contract.uuid,
|
||||
}),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeDialog();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function formatInputType(type) {
|
||||
if (type === "date") return "date";
|
||||
if (type === "number") return "number";
|
||||
return "text";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogModal :show="show" max-width="2xl" @close="closeDialog">
|
||||
<template #title>
|
||||
Uredi meta podatke
|
||||
<span v-if="contract" class="text-gray-500 font-normal">
|
||||
- {{ contract.reference }}
|
||||
</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<div v-if="metaFields.length === 0" class="text-sm text-gray-500">
|
||||
Ni meta podatkov za urejanje.
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="(field, idx) in metaFields"
|
||||
:key="idx"
|
||||
class="grid grid-cols-3 gap-3 items-start"
|
||||
>
|
||||
<div class="col-span-1">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
{{ field.title }}
|
||||
</label>
|
||||
<div class="text-xs text-gray-400 mt-0.5">{{ field.path }}</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
v-if="
|
||||
field.type !== 'text' || field.type === 'date' || field.type === 'number'
|
||||
"
|
||||
:type="formatInputType(field.type)"
|
||||
v-model="field.value"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 text-sm"
|
||||
/>
|
||||
<textarea
|
||||
v-else
|
||||
v-model="field.value"
|
||||
rows="2"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 text-sm"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="form.errors.meta" class="mt-3 text-sm text-red-600">
|
||||
{{ form.errors.meta }}
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 text-sm rounded-md border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 mr-2"
|
||||
@click="closeDialog"
|
||||
>
|
||||
Prekliči
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 text-sm rounded-md bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50"
|
||||
:disabled="form.processing || metaFields.length === 0"
|
||||
@click="submitForm"
|
||||
>
|
||||
Shrani
|
||||
</button>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
|
@ -11,6 +11,7 @@ import Dropdown from "@/Components/Dropdown.vue";
|
|||
import CaseObjectCreateDialog from "./CaseObjectCreateDialog.vue";
|
||||
import CaseObjectsDialog from "./CaseObjectsDialog.vue";
|
||||
import PaymentDialog from "./PaymentDialog.vue";
|
||||
import ContractMetaPatchDialogForm from "./ContractMetaPatchDialogForm.vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import ViewPaymentsDialog from "./ViewPaymentsDialog.vue";
|
||||
import {
|
||||
|
|
@ -445,6 +446,18 @@ const closePaymentsDialog = () => {
|
|||
showPaymentsDialog.value = false;
|
||||
selectedContract.value = null;
|
||||
};
|
||||
|
||||
// Meta edit dialog state
|
||||
const showMetaDialog = ref(false);
|
||||
const metaContract = ref(null);
|
||||
const openMetaDialog = (c) => {
|
||||
metaContract.value = c;
|
||||
showMetaDialog.value = true;
|
||||
};
|
||||
const closeMetaDialog = () => {
|
||||
showMetaDialog.value = false;
|
||||
metaContract.value = null;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -632,20 +645,44 @@ const closePaymentsDialog = () => {
|
|||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="min-w-[200px] max-w-xs px-3 py-2 text-sm text-gray-700">
|
||||
<template v-if="hasMeta(c)">
|
||||
<div
|
||||
v-for="(m, idx) in getMetaEntries(c)"
|
||||
:key="idx"
|
||||
class="py-1"
|
||||
<div class="min-w-[200px] max-w-xs text-sm text-gray-700">
|
||||
<!-- Edit button in top right -->
|
||||
<div
|
||||
v-if="edit && hasMeta(c)"
|
||||
class="px-3 pt-2 pb-1 flex items-center justify-between border-b border-gray-100"
|
||||
>
|
||||
<span class="text-xs font-medium text-gray-500"
|
||||
>META PODATKI</span
|
||||
>
|
||||
<div class="text-gray-500 text-xs mb-0.5">{{ m.title }}</div>
|
||||
<div class="text-gray-800 font-medium break-all">{{ formatMetaValue(m) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text-gray-500">Ni meta podatkov.</div>
|
||||
</template>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-6 w-6 rounded hover:bg-gray-100"
|
||||
@click="openMetaDialog(c)"
|
||||
title="Uredi meta podatke"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faPenToSquare"
|
||||
class="h-3.5 w-3.5 text-gray-600"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-3 py-2">
|
||||
<template v-if="hasMeta(c)">
|
||||
<div
|
||||
v-for="(m, idx) in getMetaEntries(c)"
|
||||
:key="idx"
|
||||
class="py-1"
|
||||
>
|
||||
<div class="text-gray-500 text-xs mb-0.5">{{ m.title }}</div>
|
||||
<div class="text-gray-800 font-medium break-all">
|
||||
{{ formatMetaValue(m) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text-gray-500">Ni meta podatkov.</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
@ -1160,4 +1197,12 @@ const closePaymentsDialog = () => {
|
|||
</button>
|
||||
</template>
|
||||
</DialogModal>
|
||||
|
||||
<!-- Meta Edit Dialog -->
|
||||
<ContractMetaPatchDialogForm
|
||||
:show="showMetaDialog"
|
||||
:contract="metaContract"
|
||||
:client-case="client_case"
|
||||
@close="closeMetaDialog"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@
|
|||
Route::middleware('permission:contract-edit')->group(function () {
|
||||
Route::post('client-cases/{client_case:uuid}/contract', [ClientCaseContoller::class, 'storeContract'])->name('clientCase.contract.store');
|
||||
Route::put('client-cases/{client_case:uuid}/contract/{uuid}', [ClientCaseContoller::class, 'updateContract'])->name('clientCase.contract.update');
|
||||
Route::patch('client-cases/{client_case:uuid}/contract/{uuid}/meta', [ClientCaseContoller::class, 'patchContractMeta'])->name('clientCase.contract.patchMeta');
|
||||
Route::delete('client-cases/{client_case:uuid}/contract/{uuid}', [ClientCaseContoller::class, 'deleteContract'])->name('clientCase.contract.delete');
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user