changes 0328092025
This commit is contained in:
@@ -16,7 +16,8 @@ let header = [
|
||||
C_TD.make('Odločitev', 'header'),
|
||||
C_TD.make('Opomba', 'header'),
|
||||
C_TD.make('Datum zapadlosti', 'header'),
|
||||
C_TD.make('Znesek obljube', 'header')
|
||||
C_TD.make('Znesek obljube', 'header'),
|
||||
C_TD.make('Dodal', 'header')
|
||||
];
|
||||
|
||||
const createBody = (data) => {
|
||||
@@ -25,6 +26,7 @@ const createBody = (data) => {
|
||||
data.forEach((p) => {
|
||||
const createdDate = new Date(p.created_at).toLocaleDateString('de');
|
||||
const dueDate = (p.due_date) ? new Date().toLocaleDateString('de') : null;
|
||||
const userName = (p.user && p.user.name) ? p.user.name : (p.user_name || '');
|
||||
|
||||
const cols = [
|
||||
C_TD.make(p.contract?.reference ?? ''),
|
||||
@@ -33,7 +35,8 @@ const createBody = (data) => {
|
||||
C_TD.make(p.decision.name, 'body'),
|
||||
C_TD.make(p.note, 'body' ),
|
||||
C_TD.make(dueDate, 'body' ),
|
||||
C_TD.make(Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p.amount), 'body' )
|
||||
C_TD.make(Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p.amount), 'body' ),
|
||||
C_TD.make(userName, 'body')
|
||||
];
|
||||
|
||||
body.push(
|
||||
|
||||
@@ -9,7 +9,8 @@ import { useForm } from '@inertiajs/vue3'
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
client_case: { type: Object, required: true },
|
||||
contract: { type: Object, required: true },
|
||||
// Contract can initially be null while dialog is hidden; make it optional to avoid prop warning
|
||||
contract: { type: Object, default: null },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'created'])
|
||||
@@ -23,6 +24,10 @@ const form = useForm({
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
if (!props.contract) {
|
||||
// No contract selected; do nothing safely
|
||||
return
|
||||
}
|
||||
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() },
|
||||
|
||||
@@ -16,7 +16,7 @@ const items = () => Array.isArray(props.contract?.objects) ? props.contract.obje
|
||||
<template>
|
||||
<DialogModal :show="show" @close="close">
|
||||
<template #title>
|
||||
Premeti
|
||||
Predmeti
|
||||
<span v-if="contract" class="ml-2 text-sm text-gray-500">(Pogodba: {{ contract.reference }})</span>
|
||||
</template>
|
||||
<template #content>
|
||||
|
||||
@@ -14,6 +14,7 @@ const props = defineProps({
|
||||
client_case: Object,
|
||||
show: { type: Boolean, default: false },
|
||||
types: Array,
|
||||
account_types: { type: Array, default: () => [] },
|
||||
// Optional: when provided, drawer acts as edit mode
|
||||
contract: { type: Object, default: null },
|
||||
});
|
||||
@@ -40,6 +41,7 @@ const formContract = useForm({
|
||||
// nested account fields, if exists
|
||||
initial_amount: props.contract?.account?.initial_amount ?? null,
|
||||
balance_amount: props.contract?.account?.balance_amount ?? null,
|
||||
account_type_id: props.contract?.account?.type_id ?? null,
|
||||
});
|
||||
|
||||
// keep form in sync when switching between create and edit
|
||||
@@ -51,6 +53,7 @@ const applyContract = (c) => {
|
||||
formContract.description = c?.description ?? ''
|
||||
formContract.initial_amount = c?.account?.initial_amount ?? null
|
||||
formContract.balance_amount = c?.account?.balance_amount ?? null
|
||||
formContract.account_type_id = c?.account?.type_id ?? null
|
||||
}
|
||||
|
||||
watch(() => props.contract, (c) => {
|
||||
@@ -160,6 +163,18 @@ const storeOrUpdate = () => {
|
||||
Račun
|
||||
</template>
|
||||
</SectionTitle>
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<InputLabel for="accountTypeSelect" value="Tip računa"/>
|
||||
<select
|
||||
id="accountTypeSelect"
|
||||
v-model="formContract.account_type_id"
|
||||
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
|
||||
>
|
||||
<option :value="null">—</option>
|
||||
<option v-for="at in account_types" :key="at.id" :value="at.id">{{ at.name ?? ('#' + at.id) }}</option>
|
||||
</select>
|
||||
<InputError :message="formContract.errors.account_type_id" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<InputLabel for="initialAmount" value="Predani znesek"/>
|
||||
|
||||
@@ -1,250 +1,463 @@
|
||||
<script setup>
|
||||
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'
|
||||
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,
|
||||
faClock,
|
||||
faEllipsisVertical,
|
||||
faPenToSquare,
|
||||
faTrash,
|
||||
faListCheck,
|
||||
faPlus,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const props = defineProps({
|
||||
client_case: Object,
|
||||
contract_types: Array,
|
||||
contracts: { type: Array, default: () => [] },
|
||||
segments: { type: Array, default: () => [] },
|
||||
all_segments: { type: Array, default: () => [] },
|
||||
})
|
||||
client_case: Object,
|
||||
contract_types: Array,
|
||||
contracts: { type: Array, default: () => [] },
|
||||
segments: { type: Array, default: () => [] },
|
||||
all_segments: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['edit', 'delete', 'add-activity'])
|
||||
const emit = defineEmits(["edit", "delete", "add-activity"]);
|
||||
|
||||
const formatDate = (d) => {
|
||||
if (!d) return '-'
|
||||
const dt = new Date(d)
|
||||
return isNaN(dt.getTime()) ? '-' : dt.toLocaleDateString('de')
|
||||
}
|
||||
if (!d) return "-";
|
||||
const dt = new Date(d);
|
||||
return isNaN(dt.getTime()) ? "-" : dt.toLocaleDateString("de");
|
||||
};
|
||||
|
||||
const hasDesc = (c) => {
|
||||
const d = c?.description
|
||||
return typeof d === 'string' && d.trim().length > 0
|
||||
}
|
||||
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)
|
||||
const onEdit = (c) => emit("edit", c);
|
||||
const onDelete = (c) => emit("delete", c);
|
||||
const onAddActivity = (c) => emit("add-activity", c);
|
||||
|
||||
// CaseObject dialog state
|
||||
import { ref, computed } from 'vue'
|
||||
import { router } from '@inertiajs/vue3'
|
||||
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 }
|
||||
import { ref, computed } from "vue";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
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;
|
||||
};
|
||||
|
||||
// Promise date helpers
|
||||
const todayStr = computed(() => {
|
||||
const d = new Date();
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const dd = String(d.getDate()).padStart(2, "0");
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
});
|
||||
|
||||
const getPromiseDate = (c) => c?.account?.promise_date || null;
|
||||
const promiseStatus = (c) => {
|
||||
const p = getPromiseDate(c);
|
||||
if (!p) return null;
|
||||
if (p > todayStr.value) return "future";
|
||||
if (p === todayStr.value) return "today";
|
||||
return "past";
|
||||
};
|
||||
const promiseColorClass = (c) => {
|
||||
const s = promiseStatus(c);
|
||||
if (s === "future") return "text-green-600";
|
||||
if (s === "today") return "text-yellow-500";
|
||||
if (s === "past") return "text-red-600";
|
||||
return "text-gray-400";
|
||||
};
|
||||
|
||||
// Segment helpers
|
||||
const contractActiveSegment = (c) => {
|
||||
const arr = c?.segments || []
|
||||
return arr.find(s => s.pivot?.active) || arr[0] || null
|
||||
}
|
||||
const segmentName = (id) => props.segments.find(s => s.id === id)?.name || ''
|
||||
const confirmChange = ref({ show: false, contract: null, segmentId: null, fromAll: false })
|
||||
const arr = c?.segments || [];
|
||||
return arr.find((s) => s.pivot?.active) || arr[0] || null;
|
||||
};
|
||||
const segmentName = (id) => props.segments.find((s) => s.id === id)?.name || "";
|
||||
const confirmChange = ref({
|
||||
show: false,
|
||||
contract: null,
|
||||
segmentId: null,
|
||||
fromAll: false,
|
||||
});
|
||||
const askChangeSegment = (c, segmentId, fromAll = false) => {
|
||||
confirmChange.value = { show: true, contract: c, segmentId, fromAll }
|
||||
}
|
||||
const closeConfirm = () => { confirmChange.value = { show: false, contract: null, segmentId: null } }
|
||||
confirmChange.value = { show: true, contract: c, segmentId, fromAll };
|
||||
};
|
||||
const closeConfirm = () => {
|
||||
confirmChange.value = { show: false, contract: null, segmentId: null };
|
||||
};
|
||||
const doChangeSegment = () => {
|
||||
const { contract, segmentId, fromAll } = confirmChange.value
|
||||
if (!contract || !segmentId) return closeConfirm()
|
||||
if (fromAll) {
|
||||
router.post(route('clientCase.segments.attach', props.client_case), {
|
||||
segment_id: segmentId,
|
||||
contract_uuid: contract.uuid,
|
||||
make_active_for_contract: true,
|
||||
}, {
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'segments'],
|
||||
onFinish: () => closeConfirm(),
|
||||
})
|
||||
} else {
|
||||
router.post(route('clientCase.contract.updateSegment', { client_case: props.client_case.uuid, uuid: contract.uuid }), { segment_id: segmentId }, {
|
||||
preserveScroll: true,
|
||||
only: ['contracts'],
|
||||
onFinish: () => closeConfirm(),
|
||||
})
|
||||
}
|
||||
}
|
||||
const { contract, segmentId, fromAll } = confirmChange.value;
|
||||
if (!contract || !segmentId) return closeConfirm();
|
||||
if (fromAll) {
|
||||
router.post(
|
||||
route("clientCase.segments.attach", props.client_case),
|
||||
{
|
||||
segment_id: segmentId,
|
||||
contract_uuid: contract.uuid,
|
||||
make_active_for_contract: true,
|
||||
},
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ["contracts", "segments"],
|
||||
onFinish: () => closeConfirm(),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
router.post(
|
||||
route("clientCase.contract.updateSegment", {
|
||||
client_case: props.client_case.uuid,
|
||||
uuid: contract.uuid,
|
||||
}),
|
||||
{ segment_id: segmentId },
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ["contracts"],
|
||||
onFinish: () => closeConfirm(),
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">Segment</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>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-700">{{ contractActiveSegment(c)?.name || '-' }}</span>
|
||||
<Dropdown width="64" align="left">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-7 w-7 rounded-full hover:bg-gray-100"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': !segments || segments.length === 0 }"
|
||||
:title="segments && segments.length ? 'Change segment' : 'No segments available for this case'"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPenToSquare" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="py-1">
|
||||
<template v-if="segments && segments.length">
|
||||
<button v-for="s in segments" :key="s.id" type="button" class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50" @click="askChangeSegment(c, s.id)">
|
||||
<span>{{ s.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="all_segments && all_segments.length">
|
||||
<div class="px-3 py-2 text-xs text-gray-500">Ni segmentov v tem primeru. Dodaj in nastavi segment:</div>
|
||||
<button v-for="s in all_segments" :key="s.id" type="button" class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50" @click="askChangeSegment(c, s.id, true)">
|
||||
<span>{{ s.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="px-3 py-2 text-sm text-gray-500">No segments configured.</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</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>
|
||||
<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"
|
||||
>Segment
|
||||
</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>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-700">{{
|
||||
contractActiveSegment(c)?.name || "-"
|
||||
}}</span>
|
||||
<Dropdown width="64" align="left">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-7 w-7 rounded-full hover:bg-gray-100"
|
||||
:class="{
|
||||
'opacity-50 cursor-not-allowed':
|
||||
!segments || segments.length === 0,
|
||||
}"
|
||||
:title="
|
||||
segments && segments.length
|
||||
? 'Change segment'
|
||||
: 'No segments available for this case'
|
||||
"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faPenToSquare"
|
||||
class="h-4 w-4 text-gray-600"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="py-1">
|
||||
<template v-if="segments && segments.length">
|
||||
<button
|
||||
v-for="s in segments"
|
||||
:key="s.id"
|
||||
type="button"
|
||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50"
|
||||
@click="askChangeSegment(c, s.id)"
|
||||
>
|
||||
<span>{{ s.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="all_segments && all_segments.length">
|
||||
<div class="px-3 py-2 text-xs text-gray-500">
|
||||
Ni segmentov v tem primeru. Dodaj in nastavi segment:
|
||||
</div>
|
||||
<button
|
||||
v-for="s in all_segments"
|
||||
:key="s.id"
|
||||
type="button"
|
||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50"
|
||||
@click="askChangeSegment(c, s.id, true)"
|
||||
>
|
||||
<span>{{ s.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="px-3 py-2 text-sm text-gray-500">
|
||||
No segments configured.
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</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">
|
||||
<div class="inline-flex items-center justify-center gap-0.5">
|
||||
<Dropdown width="64" align="left">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-5 w-5 rounded-full"
|
||||
:title="'Pokaži opis'"
|
||||
:disabled="!hasDesc(c)"
|
||||
:class="hasDesc(c) ? 'hover:bg-gray-100 focus:outline-none' : text-gray-400"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faCircleInfo"
|
||||
class="h-4 w-4"
|
||||
:class="hasDesc(c) ? 'text-gray-700' : 'text-gray-400'"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!-- Promise date indicator -->
|
||||
<Dropdown width="64" align="left">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-5 w-5 rounded-full hover:bg-gray-100 focus:outline-none"
|
||||
:title="
|
||||
getPromiseDate(c)
|
||||
? 'Obljubljen datum plačila'
|
||||
: 'Ni obljubljenega datuma'
|
||||
"
|
||||
:disabled="!getPromiseDate(c)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faClock"
|
||||
class="h-4 w-4"
|
||||
:class="promiseColorClass(c)"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="px-3 py-2 text-sm text-gray-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
:icon="faClock"
|
||||
class="h-4 w-4"
|
||||
:class="promiseColorClass(c)"
|
||||
/>
|
||||
<span class="font-medium">Obljubljeno plačilo</span>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<span class="text-gray-500">Datum:</span>
|
||||
<span class="ml-1">{{ formatDate(getPromiseDate(c)) }}</span>
|
||||
</div>
|
||||
<div class="mt-1" v-if="promiseStatus(c) === 'future'">
|
||||
<span class="text-green-600">V prihodnosti</span>
|
||||
</div>
|
||||
<div class="mt-1" v-else-if="promiseStatus(c) === 'today'">
|
||||
<span class="text-yellow-600">Danes</span>
|
||||
</div>
|
||||
<div class="mt-1" v-else-if="promiseStatus(c) === 'past'">
|
||||
<span class="text-red-600">Zapadlo</span>
|
||||
</div>
|
||||
<div class="mt-1 text-gray-500" v-else>
|
||||
Ni nastavljenega datuma.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</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-5 w-5 rounded-full hover:bg-gray-100 focus:outline-none"
|
||||
:title="'Actions'"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faEllipsisVertical"
|
||||
class="h-4 w-4 text-gray-700"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</FwbTableBody>
|
||||
</FwbTable>
|
||||
<div v-if="!contracts || contracts.length === 0" class="p-6 text-center text-sm text-gray-500">No contracts.</div>
|
||||
<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>Predmeti</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>
|
||||
<!-- Confirm change segment -->
|
||||
<div v-if="confirmChange.show" class="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
|
||||
<div class="bg-white rounded-lg shadow-lg p-4 w-full max-w-sm">
|
||||
<div class="text-sm text-gray-800">
|
||||
Ali želite spremeniti segment za pogodbo <span class="font-medium">{{ confirmChange.contract?.reference }}</span>?
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button class="px-4 py-2 rounded bg-gray-200 hover:bg-gray-300" @click="closeConfirm">Prekliči</button>
|
||||
<button class="px-4 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700" @click="doChangeSegment">Potrdi</button>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<!-- Confirm change segment -->
|
||||
<div
|
||||
v-if="confirmChange.show"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/30"
|
||||
>
|
||||
<div class="bg-white rounded-lg shadow-lg p-4 w-full max-w-sm">
|
||||
<div class="text-sm text-gray-800">
|
||||
Ali želite spremeniti segment za pogodbo
|
||||
<span class="font-medium">{{ confirmChange.contract?.reference }}</span
|
||||
>?
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button
|
||||
class="px-4 py-2 rounded bg-gray-200 hover:bg-gray-300"
|
||||
@click="closeConfirm"
|
||||
>
|
||||
Prekliči
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
|
||||
@click="doChangeSegment"
|
||||
>
|
||||
Potrdi
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -24,6 +24,7 @@ const props = defineProps({
|
||||
contracts: Array,
|
||||
activities: Object,
|
||||
contract_types: Array,
|
||||
account_types: { type: Array, default: () => [] },
|
||||
actions: Array,
|
||||
types: Object,
|
||||
documents: Array,
|
||||
@@ -42,15 +43,19 @@ const onUploaded = () => {
|
||||
const viewer = ref({ open: false, src: '', title: '' });
|
||||
const openViewer = (doc) => {
|
||||
const kind = classifyDocument(doc)
|
||||
const isContractDoc = (doc?.documentable_type || '').toLowerCase().includes('contract')
|
||||
if (kind === 'preview') {
|
||||
const url = route('clientCase.document.view', { client_case: props.client_case.uuid, document: doc.uuid })
|
||||
viewer.value = { open: true, src: url, title: doc.original_name || doc.name };
|
||||
const url = isContractDoc && doc.contract_uuid
|
||||
? route('contract.document.view', { contract: doc.contract_uuid, document: doc.uuid })
|
||||
: route('clientCase.document.view', { client_case: props.client_case.uuid, document: doc.uuid })
|
||||
viewer.value = { open: true, src: url, title: doc.original_name || doc.name }
|
||||
} else {
|
||||
const url = route('clientCase.document.download', { client_case: props.client_case.uuid, document: doc.uuid })
|
||||
// immediate download: navigate to URL
|
||||
const url = isContractDoc && doc.contract_uuid
|
||||
? route('contract.document.download', { contract: doc.contract_uuid, document: doc.uuid })
|
||||
: route('clientCase.document.download', { client_case: props.client_case.uuid, document: doc.uuid })
|
||||
window.location.href = url
|
||||
}
|
||||
};
|
||||
}
|
||||
const closeViewer = () => { viewer.value.open = false; viewer.value.src = ''; };
|
||||
|
||||
const clientDetails = ref(false);
|
||||
@@ -245,7 +250,12 @@ const submitAttachSegment = () => {
|
||||
<DocumentsTable
|
||||
:documents="documents"
|
||||
@view="openViewer"
|
||||
:download-url-builder="doc => route('clientCase.document.download', { client_case: client_case.uuid, document: doc.uuid })"
|
||||
:download-url-builder="doc => {
|
||||
const isContractDoc = (doc?.documentable_type || '').toLowerCase().includes('contract')
|
||||
return isContractDoc && doc.contract_uuid
|
||||
? route('contract.document.download', { contract: doc.contract_uuid, document: doc.uuid })
|
||||
: route('clientCase.document.download', { client_case: client_case.uuid, document: doc.uuid })
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -256,6 +266,7 @@ const submitAttachSegment = () => {
|
||||
@close="closeUpload"
|
||||
@uploaded="onUploaded"
|
||||
:post-url="route('clientCase.document.store', client_case)"
|
||||
:contracts="contracts"
|
||||
/>
|
||||
<DocumentViewerDialog :show="viewer.open" :src="viewer.src" :title="viewer.title" @close="closeViewer" />
|
||||
</AppLayout>
|
||||
@@ -263,6 +274,7 @@ const submitAttachSegment = () => {
|
||||
:show="drawerCreateContract"
|
||||
@close="closeDrawer"
|
||||
:types="contract_types"
|
||||
:account_types="account_types"
|
||||
:client_case="client_case"
|
||||
:contract="contractEditing"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<script setup>
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import { Link, useForm } from '@inertiajs/vue3'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
setting: Object,
|
||||
contracts: Array,
|
||||
users: Array,
|
||||
assignments: Object,
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
contract_uuid: null,
|
||||
assigned_user_id: null,
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
});
|
||||
|
||||
// Format helpers (Slovenian formatting)
|
||||
function formatDate(value) {
|
||||
if (!value) { return '-'; }
|
||||
const d = new Date(value);
|
||||
if (isNaN(d)) { return value; }
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const yyyy = d.getFullYear();
|
||||
return `${dd}.${mm}.${yyyy}`;
|
||||
}
|
||||
|
||||
function formatCurrencyEUR(value) {
|
||||
if (value === null || value === undefined) { return '-'; }
|
||||
const n = Number(value);
|
||||
if (isNaN(n)) { return String(value); }
|
||||
// Thousands separator as dot, decimal as comma, with € suffix
|
||||
return n.toLocaleString('sl-SI', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||||
}
|
||||
|
||||
function assign(contract) {
|
||||
form.contract_uuid = contract.uuid
|
||||
// minimal UX: if no user selected yet, just post will fail with error; page can be enhanced later with dropdown.
|
||||
form.post(route('fieldjobs.assign'))
|
||||
}
|
||||
|
||||
function cancelAssignment(contract) {
|
||||
const payload = { contract_uuid: contract.uuid }
|
||||
form.transform(() => payload).post(route('fieldjobs.cancel'))
|
||||
}
|
||||
|
||||
function isAssigned(contract) {
|
||||
return !!(props.assignments && props.assignments[contract.uuid])
|
||||
}
|
||||
|
||||
function assignedTo(contract) {
|
||||
return props.assignments?.[contract.uuid]?.assigned_to?.name || null
|
||||
}
|
||||
|
||||
function assignedBy(contract) {
|
||||
return props.assignments?.[contract.uuid]?.assigned_by?.name || null
|
||||
}
|
||||
|
||||
// removed window.open behavior; default SPA navigation via Inertia Link
|
||||
|
||||
// Derived lists
|
||||
const unassignedContracts = computed(() => {
|
||||
return (props.contracts || []).filter(c => !isAssigned(c))
|
||||
})
|
||||
|
||||
const assignedContracts = computed(() => {
|
||||
return (props.contracts || []).filter(c => isAssigned(c))
|
||||
})
|
||||
|
||||
// Filter for assigned table
|
||||
const assignedFilterUserId = ref('')
|
||||
const assignedContractsFiltered = computed(() => {
|
||||
const list = assignedContracts.value
|
||||
if (!assignedFilterUserId.value) {
|
||||
return list
|
||||
}
|
||||
return list.filter(c => {
|
||||
const uid = props.assignments?.[c.uuid]?.assigned_to?.id
|
||||
return String(uid) === String(assignedFilterUserId.value)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Dodeljevanje terenskih opravil">
|
||||
<template #header></template>
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div v-if="!setting" class="bg-yellow-50 border border-yellow-200 text-yellow-800 rounded p-4 mb-6">
|
||||
Nastavitev za terenska opravila ni najdena. Najprej jo ustvarite v Nastavitve → Nastavitve terenskih opravil.
|
||||
</div>
|
||||
<!-- Unassigned (Assignable) Contracts -->
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6 mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Pogodbe (nedodeljene)</h2>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Dodeli uporabniku</label>
|
||||
<select v-model="form.assigned_user_id" class="border rounded px-3 py-2 w-full max-w-xs">
|
||||
<option :value="null" disabled>Izberite uporabnika</option>
|
||||
<option v-for="u in users || []" :key="u.id" :value="u.id">{{ u.name }}</option>
|
||||
</select>
|
||||
<div v-if="form.errors.assigned_user_id" class="text-red-600 text-sm mt-1">{{ form.errors.assigned_user_id }}</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
<th class="py-2 pr-4">Sklic</th>
|
||||
<th class="py-2 pr-4">Stranka</th>
|
||||
<th class="py-2 pr-4">Vrsta</th>
|
||||
<th class="py-2 pr-4">Začetek</th>
|
||||
<th class="py-2 pr-4">Konec</th>
|
||||
<th class="py-2 pr-4">Stanje</th>
|
||||
<th class="py-2 pr-4">Dejanje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="c in unassignedContracts" :key="c.uuid" class="border-b last:border-0">
|
||||
<td class="py-2 pr-4">{{ c.reference }}</td>
|
||||
<td class="py-2 pr-4">
|
||||
<Link
|
||||
v-if="c.client_case?.uuid"
|
||||
:href="route('clientCase.show', { client_case: c.client_case.uuid })"
|
||||
class="text-indigo-600 hover:underline"
|
||||
>
|
||||
{{ c.client_case?.person?.full_name || 'Primer stranke' }}
|
||||
</Link>
|
||||
<span v-else>{{ c.client_case?.person?.full_name || '-' }}</span>
|
||||
</td>
|
||||
<td class="py-2 pr-4">{{ c.type?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ formatDate(c.start_date) }}</td>
|
||||
<td class="py-2 pr-4">{{ formatDate(c.end_date) }}</td>
|
||||
<td class="py-2 pr-4">{{ formatCurrencyEUR(c.account?.balance_amount) }}</td>
|
||||
<td class="py-2 pr-4 flex items-center gap-2">
|
||||
<button
|
||||
class="px-3 py-1 text-sm rounded bg-indigo-600 text-white"
|
||||
@click="assign(c)"
|
||||
>Dodeli</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assigned Contracts -->
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold">Dodeljene pogodbe</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-700">Filter po uporabniku</label>
|
||||
<select v-model="assignedFilterUserId" class="border rounded px-3 py-2">
|
||||
<option value="">Vsi</option>
|
||||
<option v-for="u in users || []" :key="u.id" :value="u.id">{{ u.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
<th class="py-2 pr-4">Sklic</th>
|
||||
<th class="py-2 pr-4">Stranka</th>
|
||||
<th class="py-2 pr-4">Dodeljeno dne</th>
|
||||
<th class="py-2 pr-4">Dodeljeno komu</th>
|
||||
<th class="py-2 pr-4">Stanje</th>
|
||||
<th class="py-2 pr-4">Dejanje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="c in assignedContractsFiltered" :key="c.uuid" class="border-b last:border-0">
|
||||
<td class="py-2 pr-4">{{ c.reference }}</td>
|
||||
<td class="py-2 pr-4">
|
||||
<Link
|
||||
v-if="c.client_case?.uuid"
|
||||
:href="route('clientCase.show', { client_case: c.client_case.uuid })"
|
||||
class="text-indigo-600 hover:underline"
|
||||
>
|
||||
{{ c.client_case?.person?.full_name || 'Primer stranke' }}
|
||||
</Link>
|
||||
<span v-else>{{ c.client_case?.person?.full_name || '-' }}</span>
|
||||
</td>
|
||||
<td class="py-2 pr-4">{{ formatDate(props.assignments?.[c.uuid]?.assigned_at) }}</td>
|
||||
<td class="py-2 pr-4">{{ assignedTo(c) || '-' }}</td>
|
||||
<td class="py-2 pr-4">{{ formatCurrencyEUR(c.account?.balance_amount) }}</td>
|
||||
<td class="py-2 pr-4">
|
||||
<button class="px-3 py-1 text-sm rounded bg-red-600 text-white" @click="cancelAssignment(c)">Prekliči</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="assignedContractsFiltered.length === 0">
|
||||
<td colspan="6" class="py-4 text-gray-500">Ni dodeljenih pogodb za izbran filter.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,339 @@
|
||||
<script setup>
|
||||
import AppPhoneLayout from '@/Layouts/AppPhoneLayout.vue';
|
||||
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 { classifyDocument } from '@/Services/documents';
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
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 BasicButton from '@/Components/buttons/BasicButton.vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import ActivityDrawer from '@/Pages/Cases/Partials/ActivityDrawer.vue';
|
||||
import ConfirmationModal from '@/Components/ConfirmationModal.vue';
|
||||
|
||||
const props = defineProps({
|
||||
client: Object,
|
||||
client_case: Object,
|
||||
contracts: Array,
|
||||
documents: Array,
|
||||
types: Object,
|
||||
actions: Array,
|
||||
activities: Array,
|
||||
});
|
||||
|
||||
const viewer = reactive({ open: false, src: '', title: '' });
|
||||
function openViewer(doc) {
|
||||
const kind = classifyDocument(doc);
|
||||
const isContractDoc = (doc?.documentable_type || '').toLowerCase().includes('contract');
|
||||
if (kind === 'preview') {
|
||||
const url = isContractDoc && doc.contract_uuid
|
||||
? route('contract.document.view', { contract: doc.contract_uuid, document: doc.uuid })
|
||||
: route('clientCase.document.view', { client_case: props.client_case.uuid, document: doc.uuid });
|
||||
viewer.open = true; viewer.src = url; viewer.title = doc.original_name || doc.name;
|
||||
} else {
|
||||
const url = isContractDoc && doc.contract_uuid
|
||||
? route('contract.document.download', { contract: doc.contract_uuid, document: doc.uuid })
|
||||
: route('clientCase.document.download', { client_case: props.client_case.uuid, document: doc.uuid });
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
function closeViewer() { viewer.open = false; viewer.src = ''; }
|
||||
|
||||
function formatAmount(val) {
|
||||
if (val === null || val === undefined) return '0,00';
|
||||
const num = typeof val === 'number' ? val : parseFloat(val);
|
||||
if (Number.isNaN(num)) return String(val);
|
||||
return num.toLocaleString('sl-SI', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
// Activity drawer state
|
||||
const drawerAddActivity = ref(false);
|
||||
const activityContractUuid = ref(null);
|
||||
const openDrawerAddActivity = (c = null) => {
|
||||
activityContractUuid.value = c?.uuid ?? null;
|
||||
drawerAddActivity.value = true;
|
||||
};
|
||||
const closeDrawer = () => { drawerAddActivity.value = false; };
|
||||
|
||||
// Document upload state
|
||||
const docDialogOpen = ref(false);
|
||||
const docForm = useForm({
|
||||
file: null,
|
||||
name: '',
|
||||
description: '',
|
||||
is_public: true,
|
||||
contract_uuid: null,
|
||||
});
|
||||
const onPickDocument = (e) => {
|
||||
const f = e?.target?.files?.[0];
|
||||
if (f) { docForm.file = f; }
|
||||
};
|
||||
const openDocDialog = (c = null) => {
|
||||
docForm.contract_uuid = c?.uuid ?? null;
|
||||
docDialogOpen.value = true;
|
||||
};
|
||||
const closeDocDialog = () => { docDialogOpen.value = false; };
|
||||
const submitDocument = () => {
|
||||
if (!docForm.file) { return; }
|
||||
docForm.post(route('clientCase.document.store', { client_case: props.client_case.uuid }), {
|
||||
forceFormData: true,
|
||||
onSuccess: () => {
|
||||
closeDocDialog();
|
||||
docForm.reset('file', 'name', 'description', 'is_public', 'contract_uuid');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const selectedContract = computed(() => {
|
||||
if (!docForm.contract_uuid) return null;
|
||||
return props.contracts?.find(c => c.uuid === docForm.contract_uuid) || null;
|
||||
});
|
||||
|
||||
// Complete flow
|
||||
const confirmComplete = ref(false);
|
||||
const submitComplete = () => {
|
||||
// POST to phone.case.complete and redirect handled by server
|
||||
// Use a small form post via Inertia
|
||||
const form = useForm({});
|
||||
form.post(route('phone.case.complete', { client_case: props.client_case.uuid }), {
|
||||
onFinish: () => { confirmComplete.value = false; },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppPhoneLayout :title="`Primer: ${client_case?.person?.full_name || ''}`">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a :href="route('phone.index')" class="text-sm text-blue-600 hover:underline shrink-0">← Nazaj</a>
|
||||
<h2 class="font-semibold text-xl text-gray-800 truncate">{{ client_case?.person?.full_name }}</h2>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 rounded bg-green-600 text-white hover:bg-green-700"
|
||||
@click="confirmComplete = true"
|
||||
>Zaključi</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="py-4 sm:py-6">
|
||||
<div class="mx-auto max-w-5xl px-2 sm:px-4">
|
||||
<!-- Client details (account holder) -->
|
||||
<div class="bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Stranka</template>
|
||||
</SectionTitle>
|
||||
<div class="mt-2">
|
||||
<PersonDetailPhone :types="types" :person="client.person" default-tab="phones" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Person (case person) -->
|
||||
<div class="bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Primer - oseba</template>
|
||||
</SectionTitle>
|
||||
<div class="mt-2">
|
||||
<PersonDetailPhone :types="types" :person="client_case.person" default-tab="phones" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contracts assigned to me -->
|
||||
<div class="mt-4 sm:mt-6 bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Pogodbe</template>
|
||||
</SectionTitle>
|
||||
<div class="mt-3 space-y-3">
|
||||
<div
|
||||
v-for="c in contracts"
|
||||
:key="c.uuid || c.id"
|
||||
class="rounded border p-3 sm:p-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900">{{ c.reference || c.uuid }}</p>
|
||||
<p class="text-sm text-gray-600">Tip: {{ c.type?.name || '—' }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="space-y-2">
|
||||
<p v-if="c.account" class="text-sm text-gray-700">Odprto: {{ formatAmount(c.account.balance_amount) }} €</p>
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700 w-full sm:w-auto"
|
||||
@click="openDrawerAddActivity(c)"
|
||||
>+ Aktivnost</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700 w-full sm:w-auto"
|
||||
@click="openDocDialog(c)"
|
||||
>+ Dokument</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="c.last_object" class="mt-2 text-sm text-gray-700">
|
||||
<p class="font-medium">Predmet:</p>
|
||||
<p>
|
||||
<span class="text-gray-900">{{ c.last_object.name || c.last_object.reference }}</span>
|
||||
<span v-if="c.last_object.type" class="ml-2 text-gray-500">({{ c.last_object.type }})</span>
|
||||
</p>
|
||||
<p v-if="c.last_object.description" class="text-gray-600 mt-1">{{ c.last_object.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="!contracts?.length" class="text-sm text-gray-600">Ni pogodbenih obveznosti dodeljenih vam za ta primer.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents (case + assigned contracts) -->
|
||||
<div class="mt-4 sm:mt-6 bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<SectionTitle>
|
||||
<template #title>Dokumenti</template>
|
||||
</SectionTitle>
|
||||
<button
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
|
||||
@click="openDocDialog()"
|
||||
>Dodaj</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 divide-y">
|
||||
<div
|
||||
v-for="d in documents"
|
||||
:key="d.uuid || d.id"
|
||||
class="py-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-gray-900 truncate">{{ d.name || d.original_name }}</div>
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
<span v-if="d.contract_reference">Pogodba: {{ d.contract_reference }}</span>
|
||||
<span v-else>Primer</span>
|
||||
<span v-if="d.created_at" class="ml-2">· {{ new Date(d.created_at).toLocaleDateString('sl-SI') }}</span>
|
||||
</div>
|
||||
<div v-if="d.description" class="text-gray-600 text-sm mt-1 line-clamp-2">{{ d.description }}</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex flex-col items-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs px-2 py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-800"
|
||||
@click="openViewer(d)"
|
||||
>Ogled</button>
|
||||
<a
|
||||
class="text-xs px-2 py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-800"
|
||||
:href="(() => { const isC = (d?.documentable_type || '').toLowerCase().includes('contract'); return isC && d.contract_uuid ? route('contract.document.download', { contract: d.contract_uuid, document: d.uuid }) : route('clientCase.document.download', { client_case: client_case.uuid, document: d.uuid }); })()"
|
||||
>Prenesi</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!documents?.length" class="text-gray-600 text-sm py-2">Ni dokumentov.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Activities -->
|
||||
<div class="mt-4 sm:mt-6 bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<SectionTitle>
|
||||
<template #title>Aktivnosti</template>
|
||||
</SectionTitle>
|
||||
<button
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
|
||||
@click="openDrawerAddActivity()"
|
||||
>Nova</button>
|
||||
</div>
|
||||
<div class="mt-2 divide-y">
|
||||
<div v-for="a in activities" :key="a.id" class="py-2 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-gray-800">{{ a.action?.name }}<span v-if="a.decision"> → {{ a.decision?.name }}</span></div>
|
||||
<div class="text-right text-gray-500">
|
||||
<div v-if="a.contract">Pogodba: {{ a.contract.reference }}</div>
|
||||
<div class="text-xs" v-if="a.created_at || a.user || a.user_name">
|
||||
<span v-if="a.created_at">{{ new Date(a.created_at).toLocaleDateString('sl-SI') }}</span>
|
||||
<span v-if="(a.user && a.user.name) || a.user_name" class="ml-1">· {{ a.user?.name || a.user_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="a.note" class="text-gray-600">{{ a.note }}</div>
|
||||
<div class="text-gray-500">
|
||||
<span v-if="a.due_date">Zapadlost: {{ a.due_date }}</span>
|
||||
<span v-if="a.amount != null" class="ml-2">Znesek: {{ formatAmount(a.amount) }} €</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!activities?.length" class="text-gray-600 py-2">Ni aktivnosti.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DocumentViewerDialog :show="viewer.open" :src="viewer.src" :title="viewer.title" @close="closeViewer" />
|
||||
<ActivityDrawer :show="drawerAddActivity" @close="closeDrawer" :client_case="client_case" :actions="actions" :contract-uuid="activityContractUuid" />
|
||||
|
||||
<ConfirmationModal :show="confirmComplete" @close="confirmComplete = false">
|
||||
<template #title>Potrditev</template>
|
||||
<template #content>
|
||||
Ali ste prepričani da želite že zaključit stranko?
|
||||
</template>
|
||||
<template #footer>
|
||||
<button type="button" class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200" @click="confirmComplete = false">Prekliči</button>
|
||||
<button type="button" class="px-3 py-2 rounded bg-green-600 text-white hover:bg-green-700 ml-2" @click="submitComplete">Potrdi</button>
|
||||
</template>
|
||||
</ConfirmationModal>
|
||||
|
||||
<!-- Upload Document Modal -->
|
||||
<DialogModal :show="docDialogOpen" @close="closeDocDialog">
|
||||
<template #title>Dodaj dokument</template>
|
||||
<template #content>
|
||||
<div class="space-y-4">
|
||||
<div v-if="selectedContract" class="text-sm text-gray-700">
|
||||
Dokument bo dodan k pogodbi: <span class="font-medium">{{ selectedContract.reference || selectedContract.uuid }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="docFile" value="Datoteka" />
|
||||
<input id="docFile" type="file" class="mt-1 block w-full" @change="onPickDocument" />
|
||||
<div v-if="docForm.errors.file" class="text-sm text-red-600 mt-1">{{ docForm.errors.file }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="docName" value="Ime" />
|
||||
<TextInput id="docName" v-model="docForm.name" class="mt-1 block w-full" />
|
||||
<div v-if="docForm.errors.name" class="text-sm text-red-600 mt-1">{{ docForm.errors.name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="docDesc" value="Opis" />
|
||||
<TextInput id="docDesc" v-model="docForm.description" class="mt-1 block w-full" />
|
||||
<div v-if="docForm.errors.description" class="text-sm text-red-600 mt-1">{{ docForm.errors.description }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="docPublic" type="checkbox" v-model="docForm.is_public" />
|
||||
<InputLabel for="docPublic" value="Javno" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200" @click="closeDocDialog">Prekliči</button>
|
||||
<button type="button" class="px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700" :disabled="docForm.processing || !docForm.file" @click="submitDocument">Naloži</button>
|
||||
</div>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</AppPhoneLayout>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import AppPhoneLayout from '@/Layouts/AppPhoneLayout.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
jobs: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const items = computed(() => props.jobs || []);
|
||||
|
||||
// Search filter (contract reference or person full name)
|
||||
const search = ref('');
|
||||
const filteredJobs = computed(() => {
|
||||
const term = search.value.trim().toLowerCase();
|
||||
if (!term) return items.value;
|
||||
return items.value.filter(job => {
|
||||
const refStr = (job.contract?.reference || job.contract?.uuid || '').toString().toLowerCase();
|
||||
const nameStr = (job.contract?.client_case?.person?.full_name || '').toLowerCase();
|
||||
return refStr.includes(term) || nameStr.includes(term);
|
||||
});
|
||||
});
|
||||
|
||||
function formatDateDMY(d) {
|
||||
if (!d) return '-';
|
||||
// Handle date-only strings from Laravel JSON casts (YYYY-MM-DD...)
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(d)) {
|
||||
const [y, m, rest] = d.split('-');
|
||||
const day = (rest || '').slice(0, 2) || '01';
|
||||
return `${day}.${m}.${y}`;
|
||||
}
|
||||
const dt = new Date(d);
|
||||
if (Number.isNaN(dt.getTime())) return String(d);
|
||||
const dd = String(dt.getDate()).padStart(2, '0');
|
||||
const mm = String(dt.getMonth() + 1).padStart(2, '0');
|
||||
const yyyy = dt.getFullYear();
|
||||
return `${dd}.${mm}.${yyyy}`;
|
||||
}
|
||||
|
||||
function formatAmount(val) {
|
||||
if (val === null || val === undefined) return '0,00';
|
||||
const num = typeof val === 'number' ? val : parseFloat(val);
|
||||
if (Number.isNaN(num)) return String(val);
|
||||
return num.toLocaleString('sl-SI', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppPhoneLayout title="Phone">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Moja terenska opravila</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-4 sm:py-8">
|
||||
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
|
||||
<div class="mb-4 flex items-center gap-2">
|
||||
<input
|
||||
v-model="search"
|
||||
type="text"
|
||||
placeholder="Išči po referenci ali imenu..."
|
||||
class="flex-1 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
/>
|
||||
<button
|
||||
v-if="search"
|
||||
type="button"
|
||||
@click="search = ''"
|
||||
class="text-xs px-2 py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-600"
|
||||
>Počisti</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3 sm:gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<template v-if="filteredJobs.length">
|
||||
<div v-for="job in filteredJobs" :key="job.id" class="bg-white rounded-lg shadow border p-3 sm:p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500">Dodeljeno: <span class="font-medium text-gray-700">{{ formatDateDMY(job.assigned_at) }}</span></p>
|
||||
<span v-if="job.priority" class="inline-block text-xs px-2 py-0.5 rounded bg-amber-100 text-amber-700">Prioriteta</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<p class="text-base sm:text-lg font-semibold text-gray-800">
|
||||
{{ job.contract?.client_case?.person?.full_name || '—' }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 truncate">Kontrakt: {{ job.contract?.reference || job.contract?.uuid }}</p>
|
||||
<p class="text-sm text-gray-600">Tip: {{ job.contract?.type?.name || '—' }}</p>
|
||||
<p class="text-sm text-gray-600" v-if="job.contract?.account && job.contract.account.balance_amount !== null && job.contract.account.balance_amount !== undefined">
|
||||
Odprto: {{ formatAmount(job.contract.account.balance_amount) }} €
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 text-sm text-gray-600">
|
||||
<p>
|
||||
<span class="font-medium">Naslov:</span>
|
||||
{{ job.contract?.client_case?.person?.addresses?.[0]?.address || '—' }}
|
||||
</p>
|
||||
<p>
|
||||
<span class="font-medium">Telefon:</span>
|
||||
{{ job.contract?.client_case?.person?.phones?.[0]?.nu || '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<a :href="route('phone.case', { client_case: job.contract?.client_case?.uuid })" class="inline-flex-1 flex-1 text-center px-3 py-2 rounded-md bg-blue-600 text-white text-sm hover:bg-blue-700">Odpri primer</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="col-span-full bg-white rounded-lg shadow border p-6 text-center text-gray-600">
|
||||
<span v-if="search">Ni zadetkov za podani filter.</span>
|
||||
<span v-else>Trenutno nimate dodeljenih terenskih opravil.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppPhoneLayout>
|
||||
</template>
|
||||
@@ -15,6 +15,8 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const showCreate = ref(false);
|
||||
const showEdit = ref(false);
|
||||
const editingId = ref(null);
|
||||
const segmentOptions = ref([]);
|
||||
const decisionOptions = ref([]);
|
||||
|
||||
@@ -26,8 +28,11 @@ onMounted(() => {
|
||||
const form = useForm({
|
||||
segment_id: null,
|
||||
initial_decision_id: null,
|
||||
asign_decision_id: null,
|
||||
assign_decision_id: null,
|
||||
complete_decision_id: null,
|
||||
cancel_decision_id: null,
|
||||
return_segment_id: null,
|
||||
queue_segment_id: null,
|
||||
});
|
||||
|
||||
const openCreate = () => {
|
||||
@@ -46,6 +51,45 @@ const store = () => {
|
||||
onSuccess: () => closeCreate(),
|
||||
});
|
||||
};
|
||||
|
||||
const editForm = useForm({
|
||||
segment_id: null,
|
||||
initial_decision_id: null,
|
||||
assign_decision_id: null,
|
||||
complete_decision_id: null,
|
||||
cancel_decision_id: null,
|
||||
return_segment_id: null,
|
||||
queue_segment_id: null,
|
||||
});
|
||||
|
||||
const openEdit = (row) => {
|
||||
editingId.value = row.id;
|
||||
editForm.segment_id = row.segment_id ?? row.segment?.id ?? null;
|
||||
editForm.initial_decision_id = row.initial_decision_id ?? row.initial_decision?.id ?? row.initialDecision?.id ?? null;
|
||||
editForm.assign_decision_id = row.assign_decision_id ?? row.assign_decision?.id ?? row.assignDecision?.id ?? null;
|
||||
editForm.complete_decision_id = row.complete_decision_id ?? row.complete_decision?.id ?? row.completeDecision?.id ?? null;
|
||||
editForm.cancel_decision_id = row.cancel_decision_id ?? row.cancel_decision?.id ?? row.cancelDecision?.id ?? null;
|
||||
editForm.return_segment_id = row.return_segment_id ?? row.return_segment?.id ?? row.returnSegment?.id ?? null;
|
||||
editForm.queue_segment_id = row.queue_segment_id ?? row.queue_segment?.id ?? row.queueSegment?.id ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const closeEdit = () => {
|
||||
showEdit.value = false;
|
||||
editingId.value = null;
|
||||
editForm.reset();
|
||||
editForm.clearErrors();
|
||||
};
|
||||
|
||||
const update = () => {
|
||||
if (!editingId.value) {
|
||||
return;
|
||||
}
|
||||
editForm.put(route('settings.fieldjob.update', { setting: editingId.value }), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => closeEdit(),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -100,7 +144,7 @@ const store = () => {
|
||||
<InputLabel for="assignDecision" value="Assign Decision" />
|
||||
<multiselect
|
||||
id="assignDecision"
|
||||
v-model="form.asign_decision_id"
|
||||
v-model="form.assign_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
@@ -108,7 +152,7 @@ const store = () => {
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="form.errors.asign_decision_id" class="mt-1" />
|
||||
<InputError :message="form.errors.assign_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
@@ -125,6 +169,51 @@ const store = () => {
|
||||
/>
|
||||
<InputError :message="form.errors.complete_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="cancelDecision" value="Cancel Decision" />
|
||||
<multiselect
|
||||
id="cancelDecision"
|
||||
v-model="form.cancel_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select cancel decision (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="form.errors.cancel_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="returnSegment" value="Return Segment" />
|
||||
<multiselect
|
||||
id="returnSegment"
|
||||
v-model="form.return_segment_id"
|
||||
:options="segmentOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select return segment (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (segmentOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="form.errors.return_segment_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="queueSegment" value="Queue Segment" />
|
||||
<multiselect
|
||||
id="queueSegment"
|
||||
v-model="form.queue_segment_id"
|
||||
:options="segmentOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select queue segment (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (segmentOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="form.errors.queue_segment_id" class="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
@@ -134,6 +223,126 @@ const store = () => {
|
||||
</form>
|
||||
</template>
|
||||
</DialogModal>
|
||||
<DialogModal :show="showEdit" @close="closeEdit">
|
||||
<template #title>
|
||||
Edit Field Job Setting
|
||||
</template>
|
||||
<template #content>
|
||||
<form @submit.prevent="update">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<InputLabel for="edit-segment" value="Segment" />
|
||||
<multiselect
|
||||
id="edit-segment"
|
||||
v-model="editForm.segment_id"
|
||||
:options="segmentOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select segment"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (segmentOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.segment_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit-initialDecision" value="Initial Decision" />
|
||||
<multiselect
|
||||
id="edit-initialDecision"
|
||||
v-model="editForm.initial_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select initial decision"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.initial_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit-assignDecision" value="Assign Decision" />
|
||||
<multiselect
|
||||
id="edit-assignDecision"
|
||||
v-model="editForm.assign_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select assign decision"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.assign_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="edit-completeDecision" value="Complete Decision" />
|
||||
<multiselect
|
||||
id="edit-completeDecision"
|
||||
v-model="editForm.complete_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select complete decision"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.complete_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="edit-cancelDecision" value="Cancel Decision" />
|
||||
<multiselect
|
||||
id="edit-cancelDecision"
|
||||
v-model="editForm.cancel_decision_id"
|
||||
:options="decisionOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select cancel decision (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (decisionOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.cancel_decision_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="edit-returnSegment" value="Return Segment" />
|
||||
<multiselect
|
||||
id="edit-returnSegment"
|
||||
v-model="editForm.return_segment_id"
|
||||
:options="segmentOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select return segment (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (segmentOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.return_segment_id" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<InputLabel for="edit-queueSegment" value="Queue Segment" />
|
||||
<multiselect
|
||||
id="edit-queueSegment"
|
||||
v-model="editForm.queue_segment_id"
|
||||
:options="segmentOptions.map(o=>o.id)"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select queue segment (optional)"
|
||||
:append-to-body="true"
|
||||
:custom-label="(opt) => (segmentOptions.find(o=>o.id===opt)?.name || '')"
|
||||
/>
|
||||
<InputError :message="editForm.errors.queue_segment_id" class="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" @click="closeEdit" class="px-4 py-2 rounded bg-gray-200 hover:bg-gray-300">Cancel</button>
|
||||
<PrimaryButton :disabled="editForm.processing">Save</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
</DialogModal>
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
@@ -142,6 +351,10 @@ const store = () => {
|
||||
<th class="py-2 pr-4">Initial Decision</th>
|
||||
<th class="py-2 pr-4">Assign Decision</th>
|
||||
<th class="py-2 pr-4">Complete Decision</th>
|
||||
<th class="py-2 pr-4">Cancel Decision</th>
|
||||
<th class="py-2 pr-4">Return Segment</th>
|
||||
<th class="py-2 pr-4">Queue Segment</th>
|
||||
<th class="py-2 pr-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -149,8 +362,16 @@ const store = () => {
|
||||
<td class="py-2 pr-4">{{ row.id }}</td>
|
||||
<td class="py-2 pr-4">{{ row.segment?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.initial_decision?.name || row.initialDecision?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.asign_decision?.name || row.asignDecision?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.assign_decision?.name || row.assignDecision?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.complete_decision?.name || row.completeDecision?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.cancel_decision?.name || row.cancelDecision?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.return_segment?.name || row.returnSegment?.name }}</td>
|
||||
<td class="py-2 pr-4">{{ row.queue_segment?.name || row.queueSegment?.name }}</td>
|
||||
<td class="py-2 pr-4">
|
||||
<button @click="openEdit(row)" class="px-3 py-1 rounded bg-indigo-600 text-white hover:bg-indigo-700">
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user