Changes to post|put|patch|delete
This commit is contained in:
parent
63e0958b66
commit
fd9f26d82a
|
|
@ -121,7 +121,7 @@ public function store(Request $request)
|
|||
});
|
||||
}
|
||||
|
||||
return to_route('client.show', $client);
|
||||
return back()->with('success', 'Client created.')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function storeContract(ClientCase $clientCase, StoreContractRequest $request)
|
||||
|
|
@ -156,7 +156,7 @@ public function storeContract(ClientCase $clientCase, StoreContractRequest $requ
|
|||
// Preserve segment filter if present
|
||||
$segment = request('segment');
|
||||
|
||||
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
|
||||
return back()->with('success', 'Contract created.')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function updateContract(ClientCase $clientCase, string $uuid, UpdateContractRequest $request)
|
||||
|
|
@ -222,7 +222,7 @@ public function updateContract(ClientCase $clientCase, string $uuid, UpdateContr
|
|||
// Preserve segment filter if present
|
||||
$segment = request('segment');
|
||||
|
||||
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
|
||||
return back()->with('success', 'Contract updated.')->with('flash_method', 'PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -322,7 +322,7 @@ public function storeActivity(ClientCase $clientCase, Request $request)
|
|||
|
||||
// Stay on the current page (desktop or phone) instead of forcing a redirect to the desktop route.
|
||||
// Use 303 to align with Inertia's recommended POST/Redirect/GET behavior.
|
||||
return back(303)->with('success', 'Successful created!');
|
||||
return back(303)->with('success', 'Successful created!')->with('flash_method', 'POST');
|
||||
} catch (QueryException $e) {
|
||||
logger()->error('Database error occurred:', ['error' => $e->getMessage()]);
|
||||
|
||||
|
|
@ -357,7 +357,7 @@ public function deleteContract(ClientCase $clientCase, string $uuid, Request $re
|
|||
// Preserve segment filter if present
|
||||
$segment = request('segment');
|
||||
|
||||
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
|
||||
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment])->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
public function updateContractSegment(ClientCase $clientCase, string $uuid, Request $request)
|
||||
|
|
@ -395,7 +395,7 @@ public function updateContractSegment(ClientCase $clientCase, string $uuid, Requ
|
|||
}
|
||||
});
|
||||
|
||||
return back()->with('success', 'Contract segment updated.');
|
||||
return back()->with('success', 'Contract segment updated.')->with('flash_method', 'PATCH');
|
||||
}
|
||||
|
||||
public function attachSegment(ClientCase $clientCase, Request $request)
|
||||
|
|
@ -446,7 +446,7 @@ public function attachSegment(ClientCase $clientCase, Request $request)
|
|||
}
|
||||
});
|
||||
|
||||
return back()->with('success', 'Segment attached to case.');
|
||||
return back()->with('success', 'Segment attached to case.')->with('flash_method', 'PATCH');
|
||||
}
|
||||
|
||||
public function storeDocument(ClientCase $clientCase, Request $request)
|
||||
|
|
@ -500,7 +500,7 @@ public function storeDocument(ClientCase $clientCase, Request $request)
|
|||
\App\Jobs\GenerateDocumentPreview::dispatch($doc->id);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Document uploaded.');
|
||||
return back()->with('success', 'Document uploaded.')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function updateDocument(ClientCase $clientCase, Document $document, Request $request)
|
||||
|
|
@ -583,7 +583,7 @@ public function updateDocument(ClientCase $clientCase, Document $document, Reque
|
|||
$document->save();
|
||||
|
||||
// Refresh documents list on page
|
||||
return back()->with('success', __('Document updated.'));
|
||||
return back()->with('success', 'Document updated.')->with('flash_method', 'PUT');
|
||||
}
|
||||
|
||||
public function viewDocument(ClientCase $clientCase, Document $document, Request $request)
|
||||
|
|
@ -960,9 +960,7 @@ public function deleteDocument(ClientCase $clientCase, Document $document, Reque
|
|||
|
||||
$document->delete(); // soft delete
|
||||
|
||||
return $request->wantsJson()
|
||||
? response()->json(['status' => 'ok'])
|
||||
: back()->with('success', 'Document deleted.');
|
||||
return back()->with('success', 'Document deleted.')->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -979,9 +977,7 @@ public function deleteContractDocument(Contract $contract, Document $document, R
|
|||
|
||||
$document->delete();
|
||||
|
||||
return $request->wantsJson()
|
||||
? response()->json(['status' => 'ok'])
|
||||
: back()->with('success', 'Document deleted.');
|
||||
return back()->with('success', 'Document deleted.')->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1162,7 +1158,7 @@ public function archiveContract(ClientCase $clientCase, string $uuid, Request $r
|
|||
|
||||
$message = $reactivateRequested ? __('contracts.reactivated') : __('contracts.archived');
|
||||
|
||||
return back()->with('success', $message);
|
||||
return back()->with('success', $message)->with('flash_method', 'PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -197,14 +197,14 @@ public function store(Request $request)
|
|||
|
||||
// \App\Models\Person\PersonAddress::create($address);
|
||||
|
||||
return to_route('client');
|
||||
return back()->with('success', 'Client created')->with('flash_method', 'POST');
|
||||
|
||||
}
|
||||
|
||||
public function update(Client $client, Request $request)
|
||||
{
|
||||
|
||||
return to_route('client.show', $client);
|
||||
return back()->with('success', 'Client updated')->with('flash_method', 'PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public function store(Request $request)
|
|||
});
|
||||
}
|
||||
|
||||
return to_route('clientCase.show', $clientCase);
|
||||
return back()->with('success', 'Contract created')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function update(Contract $contract, Request $request){
|
||||
|
|
@ -56,6 +56,6 @@ public function update(Contract $contract, Request $request){
|
|||
'type_id' => $request->input('type_id')
|
||||
]);
|
||||
|
||||
|
||||
return back()->with('success', 'Contract updated')->with('flash_method', 'PUT');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,18 +26,10 @@ public function update(Person $person, Request $request)
|
|||
|
||||
$person->update($attributes);
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'Person updated');
|
||||
}
|
||||
return back()->with('success', 'Person updated')->with('flash_method', 'PUT');
|
||||
|
||||
|
||||
|
||||
return response()->json([
|
||||
'person' => [
|
||||
'full_name' => $person->full_name,
|
||||
'tax_number' => $person->tax_number,
|
||||
'social_security_number' => $person->social_security_number,
|
||||
'description' => $person->description,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function createAddress(Person $person, Request $request)
|
||||
|
|
@ -60,13 +52,8 @@ public function createAddress(Person $person, Request $request)
|
|||
], $attributes);
|
||||
|
||||
// Support Inertia form submissions (redirect back) and JSON (for API/axios)
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'Address created');
|
||||
}
|
||||
return back()->with('success', 'Address created')->with('flash_method', 'POST');
|
||||
|
||||
return response()->json([
|
||||
'address' => \App\Models\Person\PersonAddress::with(['type'])->findOrFail($address->id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateAddress(Person $person, int $address_id, Request $request)
|
||||
|
|
@ -84,13 +71,8 @@ public function updateAddress(Person $person, int $address_id, Request $request)
|
|||
|
||||
$address->update($attributes);
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'Address updated');
|
||||
}
|
||||
return back()->with('success', 'Address updated')->with('flash_method', 'PUT');
|
||||
|
||||
return response()->json([
|
||||
'address' => $address,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteAddress(Person $person, int $address_id, Request $request)
|
||||
|
|
@ -98,11 +80,8 @@ public function deleteAddress(Person $person, int $address_id, Request $request)
|
|||
$address = $person->addresses()->findOrFail($address_id);
|
||||
$address->delete(); // soft delete
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'Address deleted');
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
return back()->with('success', 'Address deleted')->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
public function createPhone(Person $person, Request $request)
|
||||
|
|
@ -122,7 +101,7 @@ public function createPhone(Person $person, Request $request)
|
|||
'country_code' => $attributes['country_code'] ?? null,
|
||||
], $attributes);
|
||||
|
||||
return back()->with('success', 'Phone added successfully');
|
||||
return back()->with('success', 'Phone added successfully')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function updatePhone(Person $person, int $phone_id, Request $request)
|
||||
|
|
@ -140,7 +119,7 @@ public function updatePhone(Person $person, int $phone_id, Request $request)
|
|||
|
||||
$phone->update($attributes);
|
||||
|
||||
return back()->with('success', 'Phone updated successfully');
|
||||
return back()->with('success', 'Phone updated successfully')->with('flash_method', 'PUT');
|
||||
}
|
||||
|
||||
public function deletePhone(Person $person, int $phone_id, Request $request)
|
||||
|
|
@ -148,7 +127,7 @@ public function deletePhone(Person $person, int $phone_id, Request $request)
|
|||
$phone = $person->phones()->findOrFail($phone_id);
|
||||
$phone->delete(); // soft delete
|
||||
|
||||
return back()->with('success', 'Phone deleted');
|
||||
return back()->with('success', 'Phone deleted')->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
public function createEmail(Person $person, Request $request)
|
||||
|
|
@ -170,7 +149,7 @@ public function createEmail(Person $person, Request $request)
|
|||
'value' => $attributes['value'],
|
||||
], $attributes);
|
||||
|
||||
return back()->with('success', 'Email added successfully');
|
||||
return back()->with('success', 'Email added successfully')->with('flash_method', 'POST');
|
||||
}
|
||||
|
||||
public function updateEmail(Person $person, int $email_id, Request $request)
|
||||
|
|
@ -191,7 +170,7 @@ public function updateEmail(Person $person, int $email_id, Request $request)
|
|||
|
||||
$email->update($attributes);
|
||||
|
||||
return back()->with('success', 'Email updated successfully');
|
||||
return back()->with('success', 'Email updated successfully')->with('flash_method', 'PUT');
|
||||
}
|
||||
|
||||
public function deleteEmail(Person $person, int $email_id, Request $request)
|
||||
|
|
@ -203,7 +182,7 @@ public function deleteEmail(Person $person, int $email_id, Request $request)
|
|||
return back()->with('success', 'Email deleted');
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
return back()->with('success', 'Email deleted')->with('flash_method', 'DELETE');
|
||||
}
|
||||
|
||||
// TRR (bank account) CRUD
|
||||
|
|
@ -225,13 +204,10 @@ public function createTrr(Person $person, Request $request)
|
|||
// Create without dedup (IBAN may be null or vary); could dedup by IBAN if provided
|
||||
$trr = $person->bankAccounts()->create($attributes);
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'TRR added successfully');
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'trr' => BankAccount::findOrFail($trr->id),
|
||||
]);
|
||||
return back()->with('success', 'TRR added successfully')->with('flash_method', 'POST');
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function updateTrr(Person $person, int $trr_id, Request $request)
|
||||
|
|
@ -253,13 +229,8 @@ public function updateTrr(Person $person, int $trr_id, Request $request)
|
|||
$trr = $person->bankAccounts()->findOrFail($trr_id);
|
||||
$trr->update($attributes);
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'TRR updated successfully');
|
||||
}
|
||||
return back()->with('success', 'TRR updated successfully')->with('flash_method', 'PUT');
|
||||
|
||||
return response()->json([
|
||||
'trr' => $trr,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteTrr(Person $person, int $trr_id, Request $request)
|
||||
|
|
@ -267,10 +238,8 @@ public function deleteTrr(Person $person, int $trr_id, Request $request)
|
|||
$trr = $person->bankAccounts()->findOrFail($trr_id);
|
||||
$trr->delete();
|
||||
|
||||
if ($request->header('X-Inertia')) {
|
||||
return back()->with('success', 'TRR deleted');
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
return back()->with('success', 'TRR deleted')->with('flash_method', 'DELETE');
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ public function share(Request $request): array
|
|||
'error' => fn () => $request->session()->get('error'),
|
||||
'warning' => fn () => $request->session()->get('warning'),
|
||||
'info' => fn () => $request->session()->get('info'),
|
||||
'method' => fn () => $request->session()->get('flash_method'), // HTTP method for toast styling
|
||||
],
|
||||
'notifications' => function () use ($request) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
<script setup>
|
||||
import UpdateDialog from '@/Components/Dialogs/UpdateDialog.vue'
|
||||
import InputLabel from '@/Components/InputLabel.vue'
|
||||
import TextInput from '@/Components/TextInput.vue'
|
||||
import { useForm } from '@inertiajs/vue3'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
client_case_uuid: { type: String, required: true },
|
||||
document: { type: Object, default: null },
|
||||
contracts: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'saved'])
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
description: '',
|
||||
is_public: false,
|
||||
contract_uuid: null,
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.document,
|
||||
(d) => {
|
||||
if (!d) return
|
||||
form.name = d.name || d.original_name || ''
|
||||
form.description = d.description || ''
|
||||
form.is_public = !!d.is_public
|
||||
// Pre-fill contract selection if this doc belongs to a contract
|
||||
const isContract = (d?.documentable_type || '').toLowerCase().includes('contract')
|
||||
form.contract_uuid = isContract ? d.contract_uuid || null : null
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const submit = () => {
|
||||
if (!props.document) return
|
||||
form.patch(
|
||||
route('clientCase.document.update', {
|
||||
client_case: props.client_case_uuid,
|
||||
document: props.document.uuid,
|
||||
}),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
emit('saved')
|
||||
emit('close')
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
submit()
|
||||
}
|
||||
|
||||
const contractOptions = computed(() => {
|
||||
return props.contracts || []
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UpdateDialog
|
||||
:show="show"
|
||||
title="Uredi dokument"
|
||||
:processing="form.processing"
|
||||
@close="$emit('close')"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<InputLabel for="docName" value="Ime" />
|
||||
<TextInput id="docName" v-model="form.name" class="mt-1 block w-full" />
|
||||
<div v-if="form.errors.name" class="text-sm text-red-600 mt-1">{{ form.errors.name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="docDesc" value="Opis" />
|
||||
<TextInput id="docDesc" v-model="form.description" class="mt-1 block w-full" />
|
||||
<div v-if="form.errors.description" class="text-sm text-red-600 mt-1">{{ form.errors.description }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="docPublic" type="checkbox" v-model="form.is_public" />
|
||||
<InputLabel for="docPublic" value="Javno" />
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="docContract" value="Pogodba" />
|
||||
<select id="docContract" v-model="form.contract_uuid" class="mt-1 block w-full rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option :value="null">— Brez — (dok. pri primeru)</option>
|
||||
<option v-for="c in contractOptions" :key="c.uuid || c.id" :value="c.uuid">{{ c.reference || c.uuid }}</option>
|
||||
</select>
|
||||
<div v-if="form.errors.contract_uuid" class="text-sm text-red-600 mt-1">{{ form.errors.contract_uuid }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</UpdateDialog>
|
||||
</template>
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
<script setup>
|
||||
import CreateDialog from '@/Components/Dialogs/CreateDialog.vue'
|
||||
import PrimaryButton from '@/Components/PrimaryButton.vue'
|
||||
import SecondaryButton from '@/Components/SecondaryButton.vue'
|
||||
import ActionMessage from '@/Components/ActionMessage.vue'
|
||||
import InputLabel from '@/Components/InputLabel.vue'
|
||||
import TextInput from '@/Components/TextInput.vue'
|
||||
import { useForm } from '@inertiajs/vue3'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
postUrl: { type: String, required: true },
|
||||
// Optional list of contracts to allow attaching the document directly to a contract
|
||||
// Each item should have at least: { uuid, reference }
|
||||
contracts: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['close', 'uploaded'])
|
||||
|
||||
const MAX_SIZE = 25 * 1024 * 1024 // 25MB
|
||||
const ALLOWED_EXTS = ['doc','docx','pdf','txt','csv','xls','xlsx','jpeg','jpg','png']
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
description: '',
|
||||
file: null,
|
||||
is_public: true,
|
||||
contract_uuid: null,
|
||||
})
|
||||
const localError = ref('')
|
||||
|
||||
watch(() => props.show, (v) => {
|
||||
if (!v) return
|
||||
localError.value = ''
|
||||
})
|
||||
|
||||
const onFileChange = (e) => {
|
||||
localError.value = ''
|
||||
const f = e.target.files?.[0]
|
||||
if (!f) { form.file = null; return }
|
||||
const ext = (f.name.split('.').pop() || '').toLowerCase()
|
||||
if (!ALLOWED_EXTS.includes(ext)) {
|
||||
localError.value = 'Unsupported file type. Allowed: ' + ALLOWED_EXTS.join(', ')
|
||||
e.target.value = ''
|
||||
form.file = null
|
||||
return
|
||||
}
|
||||
if (f.size > MAX_SIZE) {
|
||||
localError.value = 'File is too large. Maximum size is 25MB.'
|
||||
e.target.value = ''
|
||||
form.file = null
|
||||
return
|
||||
}
|
||||
form.file = f
|
||||
if (!form.name) {
|
||||
form.name = f.name.replace(/\.[^.]+$/, '')
|
||||
}
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
localError.value = ''
|
||||
if (!form.file) {
|
||||
localError.value = 'Please choose a file.'
|
||||
return
|
||||
}
|
||||
const ext = (form.file.name.split('.').pop() || '').toLowerCase()
|
||||
if (!ALLOWED_EXTS.includes(ext)) {
|
||||
localError.value = 'Unsupported file type. Allowed: ' + ALLOWED_EXTS.join(', ')
|
||||
return
|
||||
}
|
||||
if (form.file.size > MAX_SIZE) {
|
||||
localError.value = 'File is too large. Maximum size is 25MB.'
|
||||
return
|
||||
}
|
||||
form.post(props.postUrl, {
|
||||
forceFormData: true,
|
||||
onSuccess: () => {
|
||||
emit('uploaded')
|
||||
close()
|
||||
form.reset()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => emit('close')
|
||||
|
||||
const onConfirm = () => {
|
||||
submit()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CreateDialog
|
||||
:show="props.show"
|
||||
title="Dodaj dokument"
|
||||
max-width="lg"
|
||||
confirm-text="Naloži"
|
||||
:processing="form.processing"
|
||||
:disabled="!form.file"
|
||||
@close="close"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div v-if="props.contracts && props.contracts.length" class="grid grid-cols-1 gap-2">
|
||||
<InputLabel for="doc_attach" value="Pripiši k" />
|
||||
<select id="doc_attach" v-model="form.contract_uuid" class="mt-1 block w-full rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option :value="null">Primer</option>
|
||||
<option v-for="c in props.contracts" :key="c.uuid" :value="c.uuid">Pogodba: {{ c.reference }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="doc_name" value="Name" />
|
||||
<TextInput id="doc_name" class="mt-1 block w-full" v-model="form.name" />
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="doc_desc" value="Description" />
|
||||
<textarea id="doc_desc" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" rows="3" v-model="form.description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="doc_file" value="File (max 25MB)" />
|
||||
<input id="doc_file" type="file" class="mt-1 block w-full" @change="onFileChange" accept=".doc,.docx,.pdf,.txt,.csv,.xls,.xlsx,.jpeg,.jpg,.png" />
|
||||
<div v-if="localError" class="text-sm text-red-600 mt-1">{{ localError }}</div>
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" v-model="form.is_public" class="rounded" />
|
||||
Public
|
||||
</label>
|
||||
</div>
|
||||
</CreateDialog>
|
||||
</template>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<script setup>
|
||||
import DialogModal from '@/Components/DialogModal.vue'
|
||||
import SecondaryButton from '@/Components/SecondaryButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
src: { type: String, default: '' },
|
||||
title: { type: String, default: 'Document' }
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogModal :show="props.show" @close="$emit('close')" maxWidth="4xl">
|
||||
<template #title>{{ props.title }}</template>
|
||||
<template #content>
|
||||
<div class="h-[70vh]">
|
||||
<iframe v-if="props.src" :src="props.src" class="w-full h-full rounded border" />
|
||||
<div v-else class="text-sm text-gray-500">No document to display.</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<SecondaryButton type="button" @click="$emit('close')">Close</SecondaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</template>
|
||||
181
resources/js/Components/DocumentsTable/DocumentEditDialog.vue
Normal file
181
resources/js/Components/DocumentsTable/DocumentEditDialog.vue
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<script setup>
|
||||
import UpdateDialog from '@/Components/Dialogs/UpdateDialog.vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { router } from '@inertiajs/vue3'
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/Components/ui/form'
|
||||
import { Input } from '@/Components/ui/input'
|
||||
import { Textarea } from '@/Components/ui/textarea'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/Components/ui/select'
|
||||
import { Checkbox } from '@/Components/ui/checkbox'
|
||||
import { Switch } from '@/Components/ui/switch'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
client_case_uuid: { type: String, required: true },
|
||||
document: { type: Object, default: null },
|
||||
contracts: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'saved'])
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
name: z.string().min(1, 'Ime je obvezno'),
|
||||
description: z.string().optional(),
|
||||
is_public: z.boolean().default(false),
|
||||
contract_uuid: z.string().nullable().optional(),
|
||||
}))
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
|
||||
const processing = ref(false)
|
||||
|
||||
const update = async () => {
|
||||
if (!props.document) return
|
||||
|
||||
processing.value = true
|
||||
const { values } = form
|
||||
|
||||
router.patch(
|
||||
route('clientCase.document.update', {
|
||||
client_case: props.client_case_uuid,
|
||||
document: props.document.uuid,
|
||||
}),
|
||||
values,
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
emit('saved')
|
||||
close()
|
||||
},
|
||||
onError: (errors) => {
|
||||
// Map Inertia errors to VeeValidate field errors
|
||||
Object.keys(errors).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors[field])
|
||||
? errors[field]
|
||||
: [errors[field]]
|
||||
form.setFieldError(field, errorMessages[0])
|
||||
})
|
||||
processing.value = false
|
||||
},
|
||||
onFinish: () => {
|
||||
processing.value = false
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
processing.value = false
|
||||
}
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
update()
|
||||
})
|
||||
|
||||
const onConfirm = () => {
|
||||
onSubmit()
|
||||
}
|
||||
|
||||
const contractOptions = computed(() => {
|
||||
return props.contracts || []
|
||||
})
|
||||
|
||||
// Watch for dialog opening and document changes
|
||||
watch(
|
||||
() => [props.show, props.document],
|
||||
() => {
|
||||
if (!props.show) {
|
||||
return
|
||||
}
|
||||
// When dialog opens, reset form with document values
|
||||
if (props.document) {
|
||||
form.resetForm({
|
||||
values: {
|
||||
name: props.document.name || props.document.original_name || '',
|
||||
description: props.document.description || '',
|
||||
is_public: !!props.document.is_public,
|
||||
contract_uuid: (props.document?.documentable_type || '').toLowerCase().includes('contract') ? (props.document.contract_uuid || null) : null,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UpdateDialog
|
||||
:show="show"
|
||||
title="Uredi dokument"
|
||||
:processing="processing"
|
||||
@close="close"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Ime</FormLabel>
|
||||
<FormControl>
|
||||
<Input id="docName" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="description">
|
||||
<FormItem>
|
||||
<FormLabel>Opis</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea id="docDesc" v-bind="componentField" rows="3" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="is_public">
|
||||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
:model-value="value"
|
||||
@update:model-value="handleChange"
|
||||
/>
|
||||
</FormControl>
|
||||
<div class="space-y-1 leading-none">
|
||||
<FormLabel>Javno</FormLabel>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="contract_uuid">
|
||||
<FormItem>
|
||||
<FormLabel>Pogodba</FormLabel>
|
||||
<Select :model-value="value" @update:model-value="handleChange">
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="— Brez — (dok. pri primeru)" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">— Brez — (dok. pri primeru)</SelectItem>
|
||||
<SelectItem
|
||||
v-for="c in contractOptions"
|
||||
:key="c.uuid || c.id"
|
||||
:value="c.uuid"
|
||||
>
|
||||
{{ c.reference || c.uuid }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</form>
|
||||
</UpdateDialog>
|
||||
</template>
|
||||
218
resources/js/Components/DocumentsTable/DocumentUploadDialog.vue
Normal file
218
resources/js/Components/DocumentsTable/DocumentUploadDialog.vue
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
<script setup>
|
||||
import CreateDialog from '@/Components/Dialogs/CreateDialog.vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
import { ref, watch } from 'vue'
|
||||
import { router } from '@inertiajs/vue3'
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/Components/ui/form'
|
||||
import { Input } from '@/Components/ui/input'
|
||||
import { Textarea } from '@/Components/ui/textarea'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/Components/ui/select'
|
||||
import { Checkbox } from '@/Components/ui/checkbox'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
postUrl: { type: String, required: true },
|
||||
// Optional list of contracts to allow attaching the document directly to a contract
|
||||
// Each item should have at least: { uuid, reference }
|
||||
contracts: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['close', 'uploaded'])
|
||||
|
||||
const MAX_SIZE = 25 * 1024 * 1024 // 25MB
|
||||
const ALLOWED_EXTS = ['doc','docx','pdf','txt','csv','xls','xlsx','jpeg','jpg','png']
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
name: z.string().min(1, 'Ime je obvezno'),
|
||||
description: z.string().optional(),
|
||||
file: z.instanceof(File).refine((file) => file.size > 0, 'Izberite datoteko'),
|
||||
is_public: z.boolean().default(true),
|
||||
contract_uuid: z.string().nullable().optional(),
|
||||
}))
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
name: '',
|
||||
description: '',
|
||||
file: null,
|
||||
is_public: true,
|
||||
contract_uuid: null,
|
||||
},
|
||||
})
|
||||
|
||||
const localError = ref('')
|
||||
|
||||
watch(() => props.show, (v) => {
|
||||
if (!v) return
|
||||
localError.value = ''
|
||||
form.resetForm()
|
||||
})
|
||||
|
||||
const onFileChange = (e) => {
|
||||
localError.value = ''
|
||||
const f = e.target.files?.[0]
|
||||
if (!f) {
|
||||
form.setFieldValue('file', null)
|
||||
return
|
||||
}
|
||||
const ext = (f.name.split('.').pop() || '').toLowerCase()
|
||||
if (!ALLOWED_EXTS.includes(ext)) {
|
||||
localError.value = 'Nepodprta vrsta datoteke. Dovoljeno: ' + ALLOWED_EXTS.join(', ')
|
||||
e.target.value = ''
|
||||
form.setFieldValue('file', null)
|
||||
return
|
||||
}
|
||||
if (f.size > MAX_SIZE) {
|
||||
localError.value = 'Datoteka je prevelika. Največja velikost je 25MB.'
|
||||
e.target.value = ''
|
||||
form.setFieldValue('file', null)
|
||||
return
|
||||
}
|
||||
form.setFieldValue('file', f)
|
||||
if (!form.values.name) {
|
||||
form.setFieldValue('name', f.name.replace(/\.[^.]+$/, ''))
|
||||
}
|
||||
}
|
||||
|
||||
const submit = form.handleSubmit(async (values) => {
|
||||
localError.value = ''
|
||||
if (!values.file) {
|
||||
localError.value = 'Prosimo izberite datoteko.'
|
||||
return
|
||||
}
|
||||
const ext = (values.file.name.split('.').pop() || '').toLowerCase()
|
||||
if (!ALLOWED_EXTS.includes(ext)) {
|
||||
localError.value = 'Nepodprta vrsta datoteke. Dovoljeno: ' + ALLOWED_EXTS.join(', ')
|
||||
return
|
||||
}
|
||||
if (values.file.size > MAX_SIZE) {
|
||||
localError.value = 'Datoteka je prevelika. Največja velikost je 25MB.'
|
||||
return
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('name', values.name)
|
||||
formData.append('description', values.description || '')
|
||||
formData.append('file', values.file)
|
||||
formData.append('is_public', values.is_public ? '1' : '0')
|
||||
if (values.contract_uuid) {
|
||||
formData.append('contract_uuid', values.contract_uuid)
|
||||
}
|
||||
|
||||
router.post(props.postUrl, formData, {
|
||||
forceFormData: true,
|
||||
onSuccess: () => {
|
||||
emit('uploaded')
|
||||
emit('close')
|
||||
form.resetForm()
|
||||
},
|
||||
onError: (errors) => {
|
||||
// Set form errors if any
|
||||
if (errors.name) form.setFieldError('name', errors.name)
|
||||
if (errors.description) form.setFieldError('description', errors.description)
|
||||
if (errors.file) form.setFieldError('file', errors.file)
|
||||
if (errors.contract_uuid) form.setFieldError('contract_uuid', errors.contract_uuid)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const close = () => emit('close')
|
||||
|
||||
const onConfirm = () => {
|
||||
submit()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CreateDialog
|
||||
:show="props.show"
|
||||
title="Dodaj dokument"
|
||||
max-width="lg"
|
||||
confirm-text="Naloži"
|
||||
:processing="!!form.isSubmitting.value"
|
||||
:disabled="!form.values.file"
|
||||
@close="close"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<form @submit.prevent="submit" class="space-y-4">
|
||||
<FormField v-if="props.contracts && props.contracts.length" v-slot="{ value, handleChange }" name="contract_uuid">
|
||||
<FormItem>
|
||||
<FormLabel>Pripiši k</FormLabel>
|
||||
<Select :model-value="value" @update:model-value="handleChange">
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Primer" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">Primer</SelectItem>
|
||||
<SelectItem
|
||||
v-for="c in props.contracts"
|
||||
:key="c.uuid"
|
||||
:value="c.uuid"
|
||||
>
|
||||
Pogodba: {{ c.reference }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Ime</FormLabel>
|
||||
<FormControl>
|
||||
<Input id="doc_name" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="description">
|
||||
<FormItem>
|
||||
<FormLabel>Opis</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea id="doc_desc" v-bind="componentField" rows="3" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="file">
|
||||
<FormItem>
|
||||
<FormLabel>Datoteka (max 25MB)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="doc_file"
|
||||
type="file"
|
||||
@change="onFileChange"
|
||||
accept=".doc,.docx,.pdf,.txt,.csv,.xls,.xlsx,.jpeg,.jpg,.png"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<div v-if="localError" class="text-sm text-red-600 mt-1">{{ localError }}</div>
|
||||
<div v-if="value" class="text-sm text-gray-600 mt-1">
|
||||
Izbrana datoteka: {{ value.name }} ({{ (value.size / 1024).toFixed(2) }} KB)
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="is_public">
|
||||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
:checked="value"
|
||||
@update:checked="handleChange"
|
||||
/>
|
||||
</FormControl>
|
||||
<div class="space-y-1 leading-none">
|
||||
<FormLabel>Javno</FormLabel>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</form>
|
||||
</CreateDialog>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<script setup>
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/Components/ui/dialog'
|
||||
import { Button } from '@/Components/ui/button'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
src: { type: String, default: '' },
|
||||
title: { type: String, default: 'Dokument' }
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="show" @update:open="(open) => !open && $emit('close')">
|
||||
<DialogContent class="max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ props.title }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="h-[70vh]">
|
||||
<iframe v-if="props.src" :src="props.src" class="w-full h-full rounded border" />
|
||||
<div v-else class="text-sm text-gray-500">Ni dokumenta za prikaz.</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<Button type="button" variant="outline" @click="$emit('close')">Zapri</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
|
@ -13,11 +13,11 @@ import {
|
|||
faTrash,
|
||||
faFileAlt,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { ref, computed } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import DataTable from "./DataTable/DataTable.vue";
|
||||
import DataTable from "../DataTable/DataTable.vue";
|
||||
import Dropdown from "@/Components/Dropdown.vue";
|
||||
import DeleteDialog from "./Dialogs/DeleteDialog.vue";
|
||||
import DeleteDialog from "../Dialogs/DeleteDialog.vue";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
|
||||
const props = defineProps({
|
||||
|
|
@ -253,7 +253,7 @@ function closeActions() {
|
|||
:hoverable="true"
|
||||
:show-actions="true"
|
||||
row-key="uuid"
|
||||
empty-text="No documents."
|
||||
empty-text="Ni dokumentov."
|
||||
empty-icon="faFileAlt"
|
||||
>
|
||||
<!-- Name column -->
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import axios from "axios";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
|
||||
import PersonUpdateForm from "./PersonUpdateForm.vue";
|
||||
|
|
@ -151,54 +150,63 @@ const closeConfirm = () => {
|
|||
|
||||
const onConfirmDelete = async () => {
|
||||
const { type, id } = confirm.value;
|
||||
try {
|
||||
if (type === "email") {
|
||||
await axios.delete(
|
||||
route("person.email.delete", { person: props.person, email_id: id })
|
||||
|
||||
if (type === "email") {
|
||||
router.delete(
|
||||
route("person.email.delete", { person: props.person, email_id: id }),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeConfirm();
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error("Delete failed", errors);
|
||||
closeConfirm();
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (type === "trr") {
|
||||
router.delete(
|
||||
route("person.trr.delete", { person: props.person, trr_id: id }),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeConfirm();
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error("Delete failed", errors);
|
||||
closeConfirm();
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (type === "address") {
|
||||
router.delete(
|
||||
route("person.address.delete", { person: props.person, address_id: id }),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeConfirm();
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error("Delete failed", errors);
|
||||
closeConfirm();
|
||||
},
|
||||
}
|
||||
);
|
||||
const list = props.person.emails || [];
|
||||
const idx = list.findIndex((e) => e.id === id);
|
||||
if (idx !== -1) list.splice(idx, 1);
|
||||
closeConfirm();
|
||||
} else if (type === "trr") {
|
||||
await axios.delete(
|
||||
route("person.trr.delete", { person: props.person, trr_id: id })
|
||||
);
|
||||
let list =
|
||||
props.person.trrs ||
|
||||
props.person.bank_accounts ||
|
||||
props.person.accounts ||
|
||||
props.person.bankAccounts ||
|
||||
[];
|
||||
const idx = list.findIndex((a) => a.id === id);
|
||||
if (idx !== -1) list.splice(idx, 1);
|
||||
closeConfirm();
|
||||
} else if (type === "address") {
|
||||
await axios.delete(
|
||||
route("person.address.delete", { person: props.person, address_id: id })
|
||||
);
|
||||
const list = props.person.addresses || [];
|
||||
const idx = list.findIndex((a) => a.id === id);
|
||||
if (idx !== -1) list.splice(idx, 1);
|
||||
closeConfirm();
|
||||
} else if (type === "phone") {
|
||||
router.delete(
|
||||
route("person.phone.delete", { person: props.person, phone_id: id }),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeConfirm();
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error("Delete failed", errors);
|
||||
closeConfirm();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Delete failed", e?.response || e);
|
||||
closeConfirm();
|
||||
} else if (type === "phone") {
|
||||
router.delete(
|
||||
route("person.phone.delete", { person: props.person, phone_id: id }),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeConfirm();
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error("Delete failed", errors);
|
||||
closeConfirm();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import SectionTitle from '@/Components/SectionTitle.vue';
|
|||
import { useForm, Field as FormField } from "vee-validate";
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import * as z from "zod";
|
||||
import axios from 'axios';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
FormControl,
|
||||
|
|
@ -64,31 +64,30 @@ const updatePerson = async () => {
|
|||
processingUpdate.value = true;
|
||||
const { values } = form;
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'put',
|
||||
url: route('person.update', props.person),
|
||||
data: values
|
||||
});
|
||||
|
||||
props.person.full_name = response.data.person.full_name;
|
||||
props.person.tax_number = response.data.person.tax_number;
|
||||
props.person.social_security_number = response.data.person.social_security_number;
|
||||
props.person.description = response.data.person.description;
|
||||
|
||||
processingUpdate.value = false;
|
||||
close();
|
||||
} catch (reason) {
|
||||
const errors = reason.response?.data?.errors || {};
|
||||
// Map axios errors to VeeValidate field errors
|
||||
Object.keys(errors).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors[field])
|
||||
? errors[field]
|
||||
: [errors[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
processingUpdate.value = false;
|
||||
}
|
||||
router.put(
|
||||
route('person.update', props.person),
|
||||
values,
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
processingUpdate.value = false;
|
||||
close();
|
||||
},
|
||||
onError: (errors) => {
|
||||
// Map Inertia errors to VeeValidate field errors
|
||||
Object.keys(errors).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors[field])
|
||||
? errors[field]
|
||||
: [errors[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
processingUpdate.value = false;
|
||||
},
|
||||
onFinish: () => {
|
||||
processingUpdate.value = false;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const onSubmit = form.handleSubmit(() => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ref, watch } from 'vue';
|
|||
import { useForm, Field as FormField } from "vee-validate";
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import * as z from "zod";
|
||||
import axios from 'axios';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import CreateDialog from '../Dialogs/CreateDialog.vue';
|
||||
import UpdateDialog from '../Dialogs/UpdateDialog.vue';
|
||||
import SectionTitle from '../SectionTitle.vue';
|
||||
|
|
@ -95,26 +95,32 @@ const create = async () => {
|
|||
errors.value = {};
|
||||
const { values } = form;
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(route('person.trr.create', props.person), values);
|
||||
if (!Array.isArray(props.person.trrs)) props.person.trrs = (props.person.bank_accounts || props.person.accounts || props.person.bankAccounts || []);
|
||||
(props.person.trrs).push(data.trr);
|
||||
processing.value = false;
|
||||
close();
|
||||
resetForm();
|
||||
} catch (e) {
|
||||
errors.value = e?.response?.data?.errors || {};
|
||||
// Map axios errors to VeeValidate field errors
|
||||
if (errors.value) {
|
||||
Object.keys(errors.value).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors.value[field])
|
||||
? errors.value[field]
|
||||
: [errors.value[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
router.post(
|
||||
route('person.trr.create', props.person),
|
||||
values,
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
processing.value = false;
|
||||
close();
|
||||
resetForm();
|
||||
},
|
||||
onError: (inertiaErrors) => {
|
||||
errors.value = inertiaErrors || {};
|
||||
// Map Inertia errors to VeeValidate field errors
|
||||
Object.keys(errors.value).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors.value[field])
|
||||
? errors.value[field]
|
||||
: [errors.value[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
processing.value = false;
|
||||
},
|
||||
onFinish: () => {
|
||||
processing.value = false;
|
||||
},
|
||||
}
|
||||
processing.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const update = async () => {
|
||||
|
|
@ -122,27 +128,32 @@ const update = async () => {
|
|||
errors.value = {};
|
||||
const { values } = form;
|
||||
|
||||
try {
|
||||
const { data } = await axios.put(route('person.trr.update', { person: props.person, trr_id: props.id }), values);
|
||||
let list = props.person.trrs || props.person.bank_accounts || props.person.accounts || props.person.bankAccounts || [];
|
||||
const idx = list.findIndex(a => a.id === data.trr.id);
|
||||
if (idx !== -1) list[idx] = data.trr;
|
||||
processing.value = false;
|
||||
close();
|
||||
resetForm();
|
||||
} catch (e) {
|
||||
errors.value = e?.response?.data?.errors || {};
|
||||
// Map axios errors to VeeValidate field errors
|
||||
if (errors.value) {
|
||||
Object.keys(errors.value).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors.value[field])
|
||||
? errors.value[field]
|
||||
: [errors.value[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
router.put(
|
||||
route('person.trr.update', { person: props.person, trr_id: props.id }),
|
||||
values,
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
processing.value = false;
|
||||
close();
|
||||
resetForm();
|
||||
},
|
||||
onError: (inertiaErrors) => {
|
||||
errors.value = inertiaErrors || {};
|
||||
// Map Inertia errors to VeeValidate field errors
|
||||
Object.keys(errors.value).forEach((field) => {
|
||||
const errorMessages = Array.isArray(errors.value[field])
|
||||
? errors.value[field]
|
||||
: [errors.value[field]];
|
||||
form.setFieldError(field, errorMessages[0]);
|
||||
});
|
||||
processing.value = false;
|
||||
},
|
||||
onFinish: () => {
|
||||
processing.value = false;
|
||||
},
|
||||
}
|
||||
processing.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,63 @@
|
|||
<script setup>
|
||||
import { watch, onMounted, onUnmounted } from 'vue';
|
||||
import { watch, onMounted, onUnmounted, h } from 'vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { Toaster } from '@/Components/ui/sonner';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import {
|
||||
faCircleCheck,
|
||||
faPen,
|
||||
faPencil,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
library.add(faCircleCheck, faPen, faPencil, faTrash);
|
||||
|
||||
const page = usePage();
|
||||
|
||||
// Helper function to get toast config based on HTTP method
|
||||
const getToastConfig = (method, message) => {
|
||||
const methodUpper = method?.toUpperCase() || '';
|
||||
|
||||
switch (methodUpper) {
|
||||
case 'POST':
|
||||
return {
|
||||
type: 'success',
|
||||
message: message,
|
||||
icon: () => h(FontAwesomeIcon, { icon: faCircleCheck, class: 'text-green-600' }),
|
||||
description: 'Ustvarjeno',
|
||||
className: 'border-green-500 bg-green-50',
|
||||
};
|
||||
case 'PUT':
|
||||
return {
|
||||
type: 'info',
|
||||
message: message,
|
||||
icon: () => h(FontAwesomeIcon, { icon: faPen, class: 'text-blue-600' }),
|
||||
description: 'Posodobljeno',
|
||||
className: 'border-blue-500 bg-blue-50',
|
||||
};
|
||||
case 'PATCH':
|
||||
return {
|
||||
type: 'info',
|
||||
message: message,
|
||||
icon: () => h(FontAwesomeIcon, { icon: faPencil, class: 'text-indigo-600' }),
|
||||
description: 'Spremenjeno',
|
||||
className: 'border-indigo-500 bg-indigo-50',
|
||||
};
|
||||
case 'DELETE':
|
||||
return {
|
||||
type: 'error',
|
||||
message: message,
|
||||
icon: () => h(FontAwesomeIcon, { icon: faTrash, class: 'text-red-600' }),
|
||||
description: 'Izbrisano',
|
||||
className: 'border-red-500 bg-red-50',
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Watch for flash messages from Inertia
|
||||
watch(
|
||||
() => page.props.flash,
|
||||
|
|
@ -13,21 +65,80 @@ watch(
|
|||
if (!flash) return;
|
||||
|
||||
const flashTypes = ['success', 'error', 'warning', 'info'];
|
||||
const method = flash.method?.toUpperCase();
|
||||
|
||||
for (const type of flashTypes) {
|
||||
if (flash[type]) {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
toast.success(flash[type]);
|
||||
break;
|
||||
case 'error':
|
||||
toast.error(flash[type]);
|
||||
break;
|
||||
case 'warning':
|
||||
toast.warning(flash[type]);
|
||||
break;
|
||||
case 'info':
|
||||
toast.info(flash[type]);
|
||||
break;
|
||||
const message = flash[type];
|
||||
|
||||
// If method is provided, use custom styling
|
||||
if (method) {
|
||||
const config = getToastConfig(method, message);
|
||||
if (config) {
|
||||
// Use toast.custom or the appropriate type with options
|
||||
switch (config.type) {
|
||||
case 'success':
|
||||
toast.success(message, {
|
||||
description: config.description,
|
||||
icon: config.icon,
|
||||
class: config.className,
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
toast.error(message, {
|
||||
description: config.description,
|
||||
icon: config.icon,
|
||||
class: config.className,
|
||||
});
|
||||
break;
|
||||
case 'info':
|
||||
toast.info(message, {
|
||||
description: config.description,
|
||||
icon: config.icon,
|
||||
class: config.className,
|
||||
});
|
||||
break;
|
||||
case 'warning':
|
||||
toast.warning(message, {
|
||||
description: config.description,
|
||||
icon: config.icon,
|
||||
class: config.className,
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Fallback to default behavior
|
||||
switch (type) {
|
||||
case 'success':
|
||||
toast.success(message);
|
||||
break;
|
||||
case 'error':
|
||||
toast.error(message);
|
||||
break;
|
||||
case 'warning':
|
||||
toast.warning(message);
|
||||
break;
|
||||
case 'info':
|
||||
toast.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default behavior when no method is specified
|
||||
switch (type) {
|
||||
case 'success':
|
||||
toast.success(message);
|
||||
break;
|
||||
case 'error':
|
||||
toast.error(message);
|
||||
break;
|
||||
case 'warning':
|
||||
toast.warning(message);
|
||||
break;
|
||||
case 'info':
|
||||
toast.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
resources/js/Components/ui/switch/Switch.vue
Normal file
46
resources/js/Components/ui/switch/Switch.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { SwitchRoot, SwitchThumb, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: Boolean, required: false },
|
||||
modelValue: { type: [Boolean, null], required: false },
|
||||
disabled: { type: Boolean, required: false },
|
||||
id: { type: String, required: false },
|
||||
value: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
name: { type: String, required: false },
|
||||
required: { type: Boolean, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SwitchRoot
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<SwitchThumb
|
||||
:class="
|
||||
cn(
|
||||
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot name="thumb" />
|
||||
</SwitchThumb>
|
||||
</SwitchRoot>
|
||||
</template>
|
||||
1
resources/js/Components/ui/switch/index.js
Normal file
1
resources/js/Components/ui/switch/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as Switch } from "./Switch.vue";
|
||||
|
|
@ -16,14 +16,14 @@ const props = defineProps({
|
|||
edit: Boolean,
|
||||
});
|
||||
|
||||
const columns = computed(() => [
|
||||
{ key: "decision_dot", label: "", class: "w-[6%]" },
|
||||
const columns = [
|
||||
{ key: "decision_dot", label: " ", class: "w-[6%]" },
|
||||
{ key: "contract", label: "Pogodba", class: "w-[14%]" },
|
||||
{ key: "decision", label: "Odločitev", class: "w-[26%]" },
|
||||
{ key: "note", label: "Opomba", class: "w-[14%]" },
|
||||
{ key: "promise", label: "Obljuba", class: "w-[20%]" },
|
||||
{ key: "user", label: "Dodal", class: "w-[10%]" },
|
||||
]);
|
||||
];
|
||||
|
||||
const rows = computed(() => props.activities?.data || []);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import ContractDrawer from "./Partials/ContractDrawer.vue";
|
|||
import ContractTable from "./Partials/ContractTable.vue";
|
||||
import ActivityDrawer from "./Partials/ActivityDrawer.vue";
|
||||
import ActivityTable from "./Partials/ActivityTable.vue";
|
||||
import DocumentsTable from "@/Components/DocumentsTable.vue";
|
||||
import DocumentEditDialog from "@/Components/DocumentEditDialog.vue";
|
||||
import DocumentUploadDialog from "@/Components/DocumentUploadDialog.vue";
|
||||
import DocumentViewerDialog from "@/Components/DocumentViewerDialog.vue";
|
||||
import DocumentsTable from "@/Components/DocumentsTable/DocumentsTable.vue";
|
||||
import DocumentEditDialog from "@/Components/DocumentsTable/DocumentEditDialog.vue";
|
||||
import DocumentUploadDialog from "@/Components/DocumentsTable/DocumentUploadDialog.vue";
|
||||
import DocumentViewerDialog from "@/Components/DocumentsTable/DocumentViewerDialog.vue";
|
||||
import { classifyDocument } from "@/Services/documents";
|
||||
import { router, useForm, usePage } from "@inertiajs/vue3";
|
||||
import { AngleDownIcon, AngleUpIcon } from "@/Utilities/Icons";
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import SectionTitle from "@/Components/SectionTitle.vue";
|
|||
import PersonDetailPhone from "@/Components/PersonDetailPhone.vue";
|
||||
// Removed table-based component for phone; render a list instead
|
||||
// import DocumentsTable from '@/Components/DocumentsTable.vue';
|
||||
import DocumentViewerDialog from "@/Components/DocumentViewerDialog.vue";
|
||||
import DocumentViewerDialog from "@/Components/DocumentsTable/DocumentViewerDialog.vue";
|
||||
import { classifyDocument } from "@/Services/documents";
|
||||
import { reactive, ref, computed, watch, onMounted } from "vue";
|
||||
import DialogModal from "@/Components/DialogModal.vue";
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user