This commit is contained in:
Simon Pocrnjič
2025-09-28 00:30:18 +02:00
parent 7227c888d4
commit a913cfc381
44 changed files with 2123 additions and 587 deletions
@@ -3,6 +3,7 @@ import ActionMessage from '@/Components/ActionMessage.vue';
import BasicButton from '@/Components/buttons/BasicButton.vue';
import DialogModal from '@/Components/DialogModal.vue';
import InputLabel from '@/Components/InputLabel.vue';
import DatePickerField from '@/Components/DatePickerField.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SectionTitle from '@/Components/SectionTitle.vue';
import TextInput from '@/Components/TextInput.vue';
@@ -18,7 +19,9 @@ const props = defineProps({
default: false
},
client_case: Object,
actions: Array
actions: Array,
// optionally pre-select a contract to attach the activity to
contractUuid: { type: String, default: null },
});
const decisions = ref(props.actions[0].decisions);
@@ -36,7 +39,8 @@ const form = useForm({
amount: null,
note: '',
action_id: props.actions[0].id,
decision_id: props.actions[0].decisions[0].id
decision_id: props.actions[0].decisions[0].id,
contract_uuid: props.contractUuid,
});
watch(
@@ -52,14 +56,20 @@ watch(
(due_date) => {
if (due_date) {
let date = new Date(form.due_date).toISOString().split('T')[0];
console.table({old: due_date, new: date});
console.table({ old: due_date, new: date });
}
}
);
// keep contract_uuid synced if the prop changes while the drawer is open
watch(
() => props.contractUuid,
(cu) => { form.contract_uuid = cu || null; }
);
const store = () => {
console.table({
console.table({
due_date: form.due_date,
action_id: form.action_id,
decision_id: form.decision_id,
@@ -68,10 +78,10 @@ const store = () => {
});
form.post(route('clientCase.activity.store', props.client_case), {
onBefore: () => {
if(form.due_date) {
if (form.due_date) {
form.due_date = new Date(form.due_date).toISOString().split('T')[0];
}
},
},
onSuccess: () => {
close();
form.reset();
@@ -87,75 +97,46 @@ const store = () => {
</script>
<template>
<DialogModal
:show="show"
@close="close"
>
<DialogModal :show="show" @close="close">
<template #title>Dodaj aktivnost</template>
<template #content>
<form @submit.prevent="store">
<div class="col-span-6 sm:col-span-4">
<InputLabel for="activityAction" value="Akcija"/>
<select
<InputLabel for="activityAction" value="Akcija" />
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="activityAction"
ref="activityActionSelect"
v-model="form.action_id"
>
id="activityAction" ref="activityActionSelect" v-model="form.action_id">
<option v-for="a in actions" :value="a.id">{{ a.name }}</option>
<!-- ... -->
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="activityDecision" value="Odločitev"/>
<select
<InputLabel for="activityDecision" value="Odločitev" />
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="activityDecision"
ref="activityDecisionSelect"
v-model="form.decision_id"
>
id="activityDecision" ref="activityDecisionSelect" v-model="form.decision_id">
<option v-for="d in decisions" :value="d.id">{{ d.name }}</option>
<!-- ... -->
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<FwbTextarea
label="Opomba"
id="activityNote"
ref="activityNoteTextarea"
v-model="form.note"
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
/>
<FwbTextarea label="Opomba" id="activityNote" ref="activityNoteTextarea" v-model="form.note"
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" />
</div>
<DatePickerField id="activityDueDate" label="Datum zapadlosti" v-model="form.due_date"
format="dd.MM.yyyy" :enable-time-picker="false" :auto-position="true" :teleport-target="'body'"
:inline="false" :auto-apply="false" :fixed="false" :close-on-auto-apply="true"
:close-on-scroll="true" />
<div class="col-span-6 sm:col-span-4">
<InputLabel for="activityDueDate" value="Datum zapadlosti"/>
<vue-date-picker
id="activityDueDate"
:enable-time-picker="false"
format="dd.MM.yyyy"
class="mt-1 block w-full"
v-model="form.due_date"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="activityAmount" value="Znesek"/>
<TextInput
id="activityAmount"
ref="activityAmountinput"
v-model="form.amount"
type="number"
class="mt-1 block w-full"
autocomplete="0.00"
/>
<InputLabel for="activityAmount" value="Znesek" />
<TextInput id="activityAmount" ref="activityAmountinput" v-model="form.amount" type="number"
class="mt-1 block w-full" autocomplete="0.00" />
</div>
<div class="flex justify-end mt-4">
<ActionMessage :on="form.recentlySuccessful" class="me-3">
Shranjuje.
</ActionMessage>
<BasicButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
<BasicButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Shrani
</BasicButton>
</div>
@@ -10,6 +10,7 @@ const props = defineProps({
let header = [
C_TD.make('Pogodba', 'header'),
C_TD.make('Datum', 'header'),
C_TD.make('Akcija', 'header'),
C_TD.make('Odločitev', 'header'),
@@ -26,6 +27,7 @@ const createBody = (data) => {
const dueDate = (p.due_date) ? new Date().toLocaleDateString('de') : null;
const cols = [
C_TD.make(p.contract?.reference ?? ''),
C_TD.make(createdDate, 'body' ),
C_TD.make(p.action.name, 'body'),
C_TD.make(p.decision.name, 'body'),
@@ -0,0 +1,65 @@
<script setup>
import DialogModal from '@/Components/DialogModal.vue'
import InputLabel from '@/Components/InputLabel.vue'
import TextInput from '@/Components/TextInput.vue'
import PrimaryButton from '@/Components/PrimaryButton.vue'
import SectionTitle from '@/Components/SectionTitle.vue'
import { useForm } from '@inertiajs/vue3'
const props = defineProps({
show: { type: Boolean, default: false },
client_case: { type: Object, required: true },
contract: { type: Object, required: true },
})
const emit = defineEmits(['close', 'created'])
const close = () => emit('close')
const form = useForm({
reference: '',
name: '',
description: '',
type: '',
})
const submit = () => {
form.post(route('clientCase.contract.object.store', { client_case: props.client_case.uuid, uuid: props.contract.uuid }), {
preserveScroll: true,
onSuccess: () => { emit('created'); form.reset(); close() },
})
}
</script>
<template>
<DialogModal :show="show" @close="close">
<template #title>Dodaj premet</template>
<template #content>
<form @submit.prevent="submit">
<SectionTitle class="mt-2 border-b mb-4">
<template #title>Premet</template>
</SectionTitle>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<InputLabel for="objRef" value="Referenca" />
<TextInput id="objRef" v-model="form.reference" type="text" class="mt-1 block w-full" />
</div>
<div>
<InputLabel for="objType" value="Tip" />
<TextInput id="objType" v-model="form.type" type="text" class="mt-1 block w-full" />
</div>
</div>
<div class="mt-4">
<InputLabel for="objName" value="Naziv" />
<TextInput id="objName" v-model="form.name" type="text" class="mt-1 block w-full" required />
</div>
<div class="mt-4">
<InputLabel for="objDesc" value="Opis" />
<textarea id="objDesc" v-model="form.description" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" rows="3" />
</div>
<div class="flex justify-end mt-6">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">Shrani</PrimaryButton>
</div>
</form>
</template>
</DialogModal>
</template>
@@ -0,0 +1,52 @@
<script setup>
import DialogModal from '@/Components/DialogModal.vue'
const props = defineProps({
show: { type: Boolean, default: false },
client_case: { type: Object, required: true },
contract: { type: Object, default: null },
})
const emit = defineEmits(['close'])
const close = () => emit('close')
const items = () => Array.isArray(props.contract?.objects) ? props.contract.objects : []
</script>
<template>
<DialogModal :show="show" @close="close">
<template #title>
Premeti
<span v-if="contract" class="ml-2 text-sm text-gray-500">(Pogodba: {{ contract.reference }})</span>
</template>
<template #content>
<div class="mt-1 max-h-[60vh] overflow-y-auto">
<div v-if="items().length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="o in items()" :key="o.id" class="rounded-lg border border-gray-200 bg-white shadow-sm">
<div class="p-4">
<div class="flex items-start justify-between">
<div>
<div class="text-xs uppercase text-gray-500">Ref.</div>
<div class="font-semibold text-gray-900">{{ o.reference || '-' }}</div>
</div>
<span class="ml-3 inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700">{{ o.type || '—' }}</span>
</div>
<div class="mt-3">
<div class="text-xs uppercase text-gray-500">Naziv</div>
<div class="text-gray-900">{{ o.name || '-' }}</div>
</div>
<div class="mt-3">
<div class="text-xs uppercase text-gray-500">Opis</div>
<div class="text-gray-700 whitespace-pre-wrap">{{ o.description || '' }}</div>
</div>
</div>
</div>
</div>
<div v-else class="text-center text-gray-500 py-3">Ni predmetov.</div>
</div>
<div class="mt-4 flex justify-end">
<button type="button" class="px-4 py-2 rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50" @click="close">Zapri</button>
</div>
</template>
</DialogModal>
</template>
@@ -5,15 +5,16 @@ import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SectionTitle from '@/Components/SectionTitle.vue';
import TextInput from '@/Components/TextInput.vue';
import DatePickerField from '@/Components/DatePickerField.vue';
import { useForm } from '@inertiajs/vue3';
import { watch } from 'vue';
const props = defineProps({
client_case: Object,
show: {
type: Boolean,
default: false
},
types: Array
show: { type: Boolean, default: false },
types: Array,
// Optional: when provided, drawer acts as edit mode
contract: { type: Object, default: null },
});
console.log(props.types);
@@ -24,25 +25,59 @@ const close = () => {
emit('close');
}
//store contract
// form state for create or edit
const formContract = useForm({
client_case_uuid: props.client_case.uuid,
reference: '',
start_date: new Date().toISOString(),
type_id: props.types[0].id
uuid: props.contract?.uuid ?? null,
reference: props.contract?.reference ?? '',
start_date: props.contract?.start_date ?? new Date().toISOString(),
type_id: (props.contract?.type_id ?? props.contract?.type?.id) ?? props.types[0].id,
description: props.contract?.description ?? '',
// nested account fields, if exists
initial_amount: props.contract?.account?.initial_amount ?? null,
balance_amount: props.contract?.account?.balance_amount ?? null,
});
const storeContract = () => {
formContract.post(route('clientCase.contract.store', props.client_case), {
// keep form in sync when switching between create and edit
const applyContract = (c) => {
formContract.uuid = c?.uuid ?? null
formContract.reference = c?.reference ?? ''
formContract.start_date = c?.start_date ?? new Date().toISOString()
formContract.type_id = (c?.type_id ?? c?.type?.id) ?? props.types[0].id
formContract.description = c?.description ?? ''
formContract.initial_amount = c?.account?.initial_amount ?? null
formContract.balance_amount = c?.account?.balance_amount ?? null
}
watch(() => props.contract, (c) => {
applyContract(c)
})
watch(() => props.show, (open) => {
if (open && !props.contract) {
// reset for create
applyContract(null)
}
})
const storeOrUpdate = () => {
const isEdit = !!formContract.uuid
const options = {
onBefore: () => {
formContract.start_date = formContract.start_date;
formContract.start_date = formContract.start_date
},
onSuccess: () => {
close();
formContract.reset();
close()
// keep state clean; reset to initial
if (!isEdit) formContract.reset()
},
preserveScroll: true
});
preserveScroll: true,
}
if (isEdit) {
formContract.put(route('clientCase.contract.update', { client_case: props.client_case.uuid, uuid: formContract.uuid }), options)
} else {
formContract.post(route('clientCase.contract.store', props.client_case), options)
}
}
</script>
@@ -52,9 +87,9 @@ const storeContract = () => {
:show="show"
@close="close"
>
<template #title>Dodaj pogodbo</template>
<template #title>{{ formContract.uuid ? 'Uredi pogodbo' : 'Dodaj pogodbo' }}</template>
<template #content>
<form @submit.prevent="storeContract">
<form @submit.prevent="storeOrUpdate">
<SectionTitle class="mt-4 border-b mb-4">
<template #title>
Pogodba
@@ -71,10 +106,13 @@ const storeContract = () => {
autocomplete="contract-reference"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="contractStartDate" value="Datum pričetka"/>
<vue-date-picker id="contractStartDate" :enable-time-picker="false" format="dd.MM.yyyy" class="mt-1 block w-full" v-model="formContract.start_date"></vue-date-picker>
</div>
<DatePickerField
id="contractStartDate"
label="Datum pričetka"
v-model="formContract.start_date"
format="dd.MM.yyyy"
:enable-time-picker="false"
/>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="contractTypeSelect" value="Tip"/>
<select
@@ -86,13 +124,51 @@ const storeContract = () => {
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4 mt-4">
<InputLabel for="contractDescription" value="Opis"/>
<textarea
id="contractDescription"
v-model="formContract.description"
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
rows="3"
/>
</div>
<SectionTitle class="mt-6 border-b mb-4">
<template #title>
Račun
</template>
</SectionTitle>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<InputLabel for="initialAmount" value="Predani znesek"/>
<TextInput
id="initialAmount"
v-model.number="formContract.initial_amount"
type="number"
step="0.01"
class="mt-1 block w-full"
autocomplete="off"
/>
</div>
<div>
<InputLabel for="balanceAmount" value="Odprti znesek"/>
<TextInput
id="balanceAmount"
v-model.number="formContract.balance_amount"
type="number"
step="0.01"
class="mt-1 block w-full"
autocomplete="off"
/>
</div>
</div>
<div class="flex justify-end mt-4">
<ActionMessage :on="formContract.recentlySuccessful" class="me-3">
Shranjuje.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': formContract.processing }" :disabled="formContract.processing">
Shrani
{{ formContract.uuid ? 'Posodobi' : 'Shrani' }}
</PrimaryButton>
</div>
</form>
@@ -1,87 +1,164 @@
<script setup>
import BasicTable from '@/Components/BasicTable.vue';
import { LinkOptions as C_LINK, TableColumn as C_TD, TableRow as C_TR} from '@/Shared/AppObjects';
import { FwbTable, FwbTableHead, FwbTableHeadCell, FwbTableBody, FwbTableRow, FwbTableCell } from 'flowbite-vue'
import Dropdown from '@/Components/Dropdown.vue'
import CaseObjectCreateDialog from './CaseObjectCreateDialog.vue'
import CaseObjectsDialog from './CaseObjectsDialog.vue'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faCircleInfo, faEllipsisVertical, faPenToSquare, faTrash, faListCheck, faPlus } from '@fortawesome/free-solid-svg-icons'
const props = defineProps({
client_case: Object,
contract_types: Array,
contracts: Array
});
contracts: { type: Array, default: () => [] },
})
//Contract table
let tableContractHeader = [
C_TD.make('Ref.', 'header'),
C_TD.make('Datum začetka', 'header'),
C_TD.make('Tip', 'header')
];
const emit = defineEmits(['edit', 'delete', 'add-activity'])
const tableOptions = {
editor_data: {
form: {
route: {name: 'clientCase.contract.update', params: {uuid: props.client_case.uuid}},
route_remove: {name: 'clientCase.contract.delete', params: {uuid: props.client_case.uuid}},
values: {
uuid: null,
reference: '',
type_id: 1
},
key: 'uuid',
index: {uuid: null},
el: [
{
id: 'contractRefU',
ref: 'contractRefUInput',
bind: 'reference',
type: 'text',
label: 'Referenca',
autocomplete: 'contract-reference'
},
{
id: 'contractTypeU',
ref: 'contractTypeSelectU',
bind: 'type_id',
type: 'select',
label: 'Tip',
selectOptions: props.contract_types.map(item => new Object({val: item.id, desc: item.name}))
}
]
},
title: 'contract'
}
const formatDate = (d) => {
if (!d) return '-'
const dt = new Date(d)
return isNaN(dt.getTime()) ? '-' : dt.toLocaleDateString('de')
}
const createContractTableBody = (data) => {
let tableContractBody = [];
data.forEach((p) => {
let startDate = new Date(p.start_date).toLocaleDateString('de');
const cols = [
C_TD.make(p.reference, 'body' ),
C_TD.make(startDate, 'body' ),
C_TD.make(p.type.name, 'body' ),
];
tableContractBody.push(
C_TR.make(
cols,
{
class: '',
title: p.reference,
edit: true,
ref: {key: 'uuid', val: p.uuid},
editable: {
reference: p.reference,
type_id: p.type.id
}
}
)
)
});
return tableContractBody;
const hasDesc = (c) => {
const d = c?.description
return typeof d === 'string' && d.trim().length > 0
}
const onEdit = (c) => emit('edit', c)
const onDelete = (c) => emit('delete', c)
const onAddActivity = (c) => emit('add-activity', c)
// CaseObject dialog state
import { ref } from 'vue'
const showObjectDialog = ref(false)
const showObjectsList = ref(false)
const selectedContract = ref(null)
const openObjectDialog = (c) => { selectedContract.value = c; showObjectDialog.value = true }
const closeObjectDialog = () => { showObjectDialog.value = false; selectedContract.value = null }
const openObjectsList = (c) => { selectedContract.value = c; showObjectsList.value = true }
const closeObjectsList = () => { showObjectsList.value = false; selectedContract.value = null }
</script>
<template>
<BasicTable :options="tableOptions" :header="tableContractHeader" :editor="true" :body="createContractTableBody(contracts)" />
<div class="relative overflow-x-auto rounded-lg border border-gray-200 bg-white shadow-sm">
<FwbTable hoverable striped class="text-sm">
<FwbTableHead class="sticky top-0 z-10 bg-gray-50/90 backdrop-blur border-b border-gray-200 shadow-sm">
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Ref.</FwbTableHeadCell>
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Datum začetka</FwbTableHeadCell>
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Tip</FwbTableHeadCell>
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 text-right">Predano</FwbTableHeadCell>
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 text-right">Odprto</FwbTableHeadCell>
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 text-center">Opis</FwbTableHeadCell>
<FwbTableHeadCell class="w-px" />
</FwbTableHead>
<FwbTableBody>
<template v-for="(c, i) in contracts" :key="c.uuid || i">
<FwbTableRow>
<FwbTableCell>{{ c.reference }}</FwbTableCell>
<FwbTableCell>{{ formatDate(c.start_date) }}</FwbTableCell>
<FwbTableCell>{{ c?.type?.name }}</FwbTableCell>
<FwbTableCell class="text-right">{{ Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(c?.account?.initial_amount ?? 0) }}</FwbTableCell>
<FwbTableCell class="text-right">{{ Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(c?.account?.balance_amount ?? 0) }}</FwbTableCell>
<FwbTableCell class="text-center">
<Dropdown v-if="hasDesc(c)" width="64" align="left">
<template #trigger>
<button
type="button"
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 focus:outline-none"
:title="'Pokaži opis'"
>
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-700" />
</button>
</template>
<template #content>
<div class="max-w-sm px-3 py-2 text-sm text-gray-700 whitespace-pre-wrap">
{{ c.description }}
</div>
</template>
</Dropdown>
<button
v-else
type="button"
disabled
class="inline-flex items-center justify-center h-8 w-8 rounded-full text-gray-400 cursor-not-allowed"
:title="'Ni opisa'"
>
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4" />
</button>
</FwbTableCell>
<FwbTableCell class="text-right whitespace-nowrap">
<Dropdown align="right" width="56">
<template #trigger>
<button
type="button"
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 focus:outline-none"
:title="'Actions'"
>
<FontAwesomeIcon :icon="faEllipsisVertical" class="h-4 w-4 text-gray-700" />
</button>
</template>
<template #content>
<button
type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="onEdit(c)"
>
<FontAwesomeIcon :icon="faPenToSquare" class="h-4 w-4 text-gray-600" />
<span>Edit</span>
</button>
<button
type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="openObjectsList(c)"
>
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-600" />
<span>Predmeti</span>
</button>
<button
type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="openObjectDialog(c)"
>
<FontAwesomeIcon :icon="faPlus" class="h-4 w-4 text-gray-600" />
<span>Premet</span>
</button>
<button
type="button"
class="w-full px-3 py-2 text-left text-sm text-red-700 hover:bg-red-50 flex items-center gap-2"
@click="onDelete(c)"
>
<FontAwesomeIcon :icon="faTrash" class="h-4 w-4 text-red-600" />
<span>Briši</span>
</button>
<div class="my-1 border-t border-gray-100" />
<button
type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="onAddActivity(c)"
>
<FontAwesomeIcon :icon="faListCheck" class="h-4 w-4 text-gray-600" />
<span>Aktivnost</span>
</button>
</template>
</Dropdown>
</FwbTableCell>
</FwbTableRow>
</template>
</FwbTableBody>
</FwbTable>
<div v-if="!contracts || contracts.length === 0" class="p-6 text-center text-sm text-gray-500">No contracts.</div>
</div>
<CaseObjectCreateDialog
:show="showObjectDialog"
@close="closeObjectDialog"
:client_case="client_case"
:contract="selectedContract"
/>
<CaseObjectsDialog
:show="showObjectsList"
@close="closeObjectsList"
:client_case="client_case"
:contract="selectedContract"
/>
</template>
+67 -27
View File
@@ -15,6 +15,7 @@ import { classifyDocument } from "@/Services/documents";
import { router } from '@inertiajs/vue3';
import { AngleDownIcon, AngleUpIcon } from "@/Utilities/Icons";
import Pagination from "@/Components/Pagination.vue";
import ConfirmDialog from "@/Components/ConfirmDialog.vue";
const props = defineProps({
client: Object,
@@ -49,22 +50,42 @@ const openViewer = (doc) => {
};
const closeViewer = () => { viewer.value.open = false; viewer.value.src = ''; };
const clientDetails = ref(true);
const clientDetails = ref(false);
//Drawer add new contract
// Contract drawer (create/edit)
const drawerCreateContract = ref(false);
const contractEditing = ref(null);
const openDrawerCreateContract = () => {
contractEditing.value = null;
drawerCreateContract.value = true;
};
const openDrawerEditContract = (c) => {
contractEditing.value = c;
drawerCreateContract.value = true;
};
//Drawer add new activity
const drawerAddActivity = ref(false);
const activityContractUuid = ref(null);
const openDrawerAddActivity = () => {
const openDrawerAddActivity = (c = null) => {
activityContractUuid.value = c?.uuid ?? null;
drawerAddActivity.value = true;
};
// delete confirmation
const confirmDelete = ref({ show: false, contract: null })
const requestDeleteContract = (c) => { confirmDelete.value = { show: true, contract: c } }
const closeConfirmDelete = () => { confirmDelete.value.show = false; confirmDelete.value.contract = null }
const doDeleteContract = () => {
const c = confirmDelete.value.contract
if (!c) return closeConfirmDelete()
router.delete(route('clientCase.contract.delete', { client_case: props.client_case.uuid, uuid: c.uuid }), {
preserveScroll: true,
onFinish: () => closeConfirmDelete(),
})
}
//Close drawer (all)
const closeDrawer = () => {
drawerCreateContract.value = false;
@@ -154,34 +175,15 @@ const hideClietnDetails = () => {
:client_case="client_case"
:contracts="contracts"
:contract_types="contract_types"
@edit="openDrawerEditContract"
@delete="requestDeleteContract"
@add-activity="openDrawerAddActivity"
/>
</div>
</div>
</div>
</div>
<!-- Documents section -->
<div class="pt-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-4">
<SectionTitle>
<template #title>Dokumenti</template>
</SectionTitle>
<FwbButton @click="openUpload">Dodaj</FwbButton>
</div>
<DocumentsTable :documents="documents" @view="openViewer" />
</div>
</div>
</div>
</div>
<DocumentUploadDialog
:show="showUpload"
@close="closeUpload"
@uploaded="onUploaded"
:post-url="route('clientCase.document.store', client_case)"
/>
<DocumentViewerDialog :show="viewer.open" :src="viewer.src" :title="viewer.title" @close="closeViewer" />
<div class="pt-12 pb-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4">
@@ -199,17 +201,55 @@ const hideClietnDetails = () => {
</div>
</div>
</div>
<!-- Documents section -->
<div class="pt-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-4">
<SectionTitle>
<template #title>Dokumenti</template>
</SectionTitle>
<FwbButton @click="openUpload">Dodaj</FwbButton>
</div>
<DocumentsTable
:documents="documents"
@view="openViewer"
:download-url-builder="doc => route('clientCase.document.download', { client_case: client_case.uuid, document: doc.uuid })"
/>
</div>
</div>
</div>
</div>
<DocumentUploadDialog
:show="showUpload"
@close="closeUpload"
@uploaded="onUploaded"
:post-url="route('clientCase.document.store', client_case)"
/>
<DocumentViewerDialog :show="viewer.open" :src="viewer.src" :title="viewer.title" @close="closeViewer" />
</AppLayout>
<ContractDrawer
:show="drawerCreateContract"
@close="closeDrawer"
:types="contract_types"
:client_case="client_case"
:contract="contractEditing"
/>
<ActivityDrawer
:show="drawerAddActivity"
@close="closeDrawer"
:client_case="client_case"
:actions="actions"
:contract-uuid="activityContractUuid"
/>
<ConfirmDialog
:show="confirmDelete.show"
title="Izbriši pogodbo"
message="Ali ste prepričani, da želite izbrisati pogodbo?"
confirm-text="Izbriši"
:danger="true"
@close="closeConfirmDelete"
@confirm="doDeleteContract"
/>
</template>
+1 -1
View File
@@ -133,7 +133,7 @@ async function uploadAndPreview() {
processResult.value = null;
const fd = new window.FormData();
fd.append('file', form.file);
if (Number.isFinite(form.import_template_id)) {
if (form.import_template_id !== null && form.import_template_id !== undefined && String(form.import_template_id).trim() !== '') {
fd.append('import_template_id', String(form.import_template_id));
}
if (form.client_uuid) {
+5 -4
View File
@@ -8,6 +8,7 @@ const props = defineProps({
import: Object,
templates: Array,
clients: Array,
client: Object
});
const importId = ref(props.import?.id || null);
@@ -227,13 +228,13 @@ const fieldOptionsByEntity = {
// Local state for selects
const form = ref({
client_uuid: null,
client_uuid: props.client.uuid,
import_template_id: props.import?.import_template_id || null,
});
// Initialize client_uuid from numeric client_id using provided clients list
if (props.import?.client_id) {
const found = (props.clients || []).find(c => c.id === props.import.client_id);
// Initialize client_uuid from import.client_uuid (preferred) using provided clients list
if (props.import?.client_uuid) {
const found = (props.clients || []).find(c => c.uuid === props.import.client_uuid);
form.value.client_uuid = found ? found.uuid : null;
}
+1 -1
View File
@@ -47,7 +47,7 @@ function statusBadge(status) {
<td class="p-2 whitespace-nowrap">{{ new Date(imp.created_at).toLocaleString() }}</td>
<td class="p-2">{{ imp.original_name }}</td>
<td class="p-2"><span :class="['px-2 py-0.5 rounded text-xs', statusBadge(imp.status)]">{{ imp.status }}</span></td>
<td class="p-2">{{ imp.client?.uuid ?? '—' }}</td>
<td class="p-2">{{ imp.client?.person?.full_name ?? '—' }}</td>
<td class="p-2">{{ imp.template?.name ?? '—' }}</td>
<td class="p-2 space-x-2">
<Link :href="route('imports.continue', { import: imp.uuid })" class="px-2 py-1 rounded bg-gray-200 text-gray-800 text-xs">Poglej</Link>
@@ -181,6 +181,7 @@ const store = () => {
:searchable="true"
:taggable="false"
placeholder="Izberi segment"
:append-to-body="true"
:custom-label="(opt) => (segmentOptions.find(s=>s.id===opt)?.name || '')"
/>
</div>
@@ -196,6 +197,7 @@ const store = () => {
track-by="id"
:taggable="true"
placeholder="Dodaj odločitev"
:append-to-body="true"
label="name"
/>
</div>
@@ -256,6 +258,7 @@ const store = () => {
:searchable="true"
:taggable="false"
placeholder="Izberi segment"
:append-to-body="true"
:custom-label="(opt) => (segmentOptions.find(s=>s.id===opt)?.name || '')"
/>
</div>
@@ -270,6 +273,7 @@ const store = () => {
track-by="id"
:taggable="true"
placeholder="Dodaj odločitev"
:append-to-body="true"
label="name"
/>
</div>
@@ -159,6 +159,7 @@ const store = () => {
track-by="id"
:taggable="true"
placeholder="Dodaj akcijo"
:append-to-body="true"
label="name"
/>
</div>
@@ -219,6 +220,7 @@ const store = () => {
track-by="id"
:taggable="true"
placeholder="Dodaj akcijo"
:append-to-body="true"
label="name"
/>
</div>