changes 0328092025
This commit is contained in:
@@ -3,4 +3,4 @@ REF-1001,John,Doe,"123 Maple St, Springfield",+1 555-0101,john.doe@example.com,2
|
||||
REF-1002,Jane,Smith,"456 Oak Ave, Metropolis",+44 20 7946 0958,jane.smith@example.co.uk,2025-09-05,2025-10-05,320.00
|
||||
REF-1003,Carlos,García,"Calle 12 #34, Madrid",+34 91 123 4567,carlos.garcia@example.es,2025-09-10,2025-10-10,78.99
|
||||
REF-1004,Anna,Müller,"Hauptstrasse 5, Berlin",+49 30 123456,anna.mueller@example.de,2025-09-12,2025-10-12,980.50
|
||||
REF-1005,Luka,Novak,"Ilica 10, Zagreb",+385 1 2345 678,luka.novak@example.hr,2025-09-15,2025-10-15,45.00
|
||||
REF-1005,Luka,Novak,"Ilica 10, Zagreb",+385 1 2345 678,luka.novak@example.hr,2025-09-15,2025-10-15,46.30
|
||||
|
||||
|
@@ -16,6 +16,11 @@ const props = defineProps({
|
||||
description: String,
|
||||
header: Array,
|
||||
body: Array,
|
||||
// Make table header sticky while body scrolls
|
||||
stickyHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
editor: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -115,12 +120,16 @@ const remove = () => {
|
||||
<p v-if="description" class="mt-1 text-sm text-gray-600">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div :class="['relative rounded-lg border border-gray-200 bg-white shadow-sm overflow-x-auto overflow-y-auto', bodyMaxHeight]">
|
||||
<div :class="['relative rounded-lg border border-gray-200 bg-white shadow-sm overflow-x-auto overflow-y-auto', bodyMaxHeight, stickyHeader ? 'table-sticky' : '']">
|
||||
<FwbTable hoverable striped class="text-sm">
|
||||
<FwbTableHead class="sticky top-0 z-10 bg-gray-50/90 backdrop-blur supports-[backdrop-filter]:bg-gray-50/80 border-b border-gray-200 shadow-sm">
|
||||
<FwbTableHeadCell v-for="(h, hIndex) in header" :key="hIndex" class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 first:pl-6 last:pr-6">{{ h.data }}</FwbTableHeadCell>
|
||||
<FwbTableHeadCell v-if="editor" class="w-px text-gray-700 py-3"></FwbTableHeadCell>
|
||||
<FwbTableHeadCell v-else class="w-px text-gray-700 py-3" />
|
||||
<FwbTableHeadCell
|
||||
v-for="(h, hIndex) in header"
|
||||
:key="hIndex"
|
||||
class="sticky top-0 z-10 uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 first:pl-6 last:pr-6 bg-gray-50/90"
|
||||
>{{ h.data }}</FwbTableHeadCell>
|
||||
<FwbTableHeadCell v-if="editor" class="sticky top-0 z-10 w-px text-gray-700 py-3 bg-gray-50/90"></FwbTableHeadCell>
|
||||
<FwbTableHeadCell v-else class="sticky top-0 z-10 w-px text-gray-700 py-3 bg-gray-50/90" />
|
||||
</FwbTableHead>
|
||||
<FwbTableBody>
|
||||
<FwbTableRow v-for="(row, key, parent_index) in body" :key="key" :class="row.options.class">
|
||||
@@ -208,4 +217,27 @@ const remove = () => {
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Ensure sticky header remains above scrollable body inside wrapper */
|
||||
:deep(.table-sticky thead) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
:deep(.table-sticky thead th) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: rgba(249, 250, 251, 0.9); /* gray-50/90 */
|
||||
backdrop-filter: saturate(180%) blur(5px);
|
||||
}
|
||||
|
||||
/* Maintain column widths alignment when scrollbar appears */
|
||||
.table-sticky {
|
||||
/* Make sure the header and body share the same scroll container */
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,9 @@ import { ref, watch } from 'vue'
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
postUrl: { type: String, required: true },
|
||||
// Optional list of contracts to allow attaching the document directly to a contract
|
||||
// Each item should have at least: { uuid, reference }
|
||||
contracts: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['close', 'uploaded'])
|
||||
|
||||
@@ -21,7 +24,8 @@ const form = useForm({
|
||||
name: '',
|
||||
description: '',
|
||||
file: null,
|
||||
is_public: false,
|
||||
is_public: true,
|
||||
contract_uuid: null,
|
||||
})
|
||||
const localError = ref('')
|
||||
|
||||
@@ -86,6 +90,13 @@ const close = () => emit('close')
|
||||
<template #title>Dodaj dokument</template>
|
||||
<template #content>
|
||||
<div class="space-y-4">
|
||||
<div v-if="props.contracts && props.contracts.length" class="grid grid-cols-1 gap-2">
|
||||
<InputLabel for="doc_attach" value="Pripiši k" />
|
||||
<select id="doc_attach" v-model="form.contract_uuid" class="mt-1 block w-full rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option :value="null">Primer</option>
|
||||
<option v-for="c in props.contracts" :key="c.uuid" :value="c.uuid">Pogodba: {{ c.reference }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="doc_name" value="Name" />
|
||||
<TextInput id="doc_name" class="mt-1 block w-full" v-model="form.name" />
|
||||
|
||||
@@ -11,6 +11,15 @@ const props = defineProps({
|
||||
// Optional: build a direct download URL for a document; if not provided, a 'download' event will be emitted
|
||||
downloadUrlBuilder: { type: Function, default: null },
|
||||
})
|
||||
// Derive a human-friendly source for a document: Case or Contract reference
|
||||
const sourceLabel = (doc) => {
|
||||
// Server can include optional documentable meta; fall back to type
|
||||
if (doc.documentable_type?.toLowerCase?.().includes('contract')) {
|
||||
return doc.contract_reference ? `Pogodba ${doc.contract_reference}` : 'Pogodba'
|
||||
}
|
||||
return 'Primer'
|
||||
}
|
||||
|
||||
const emit = defineEmits(['view', 'download'])
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
@@ -110,11 +119,11 @@ const handleDownload = (doc) => {
|
||||
<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">Name</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Type</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Size</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Added</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Drugo</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Naziv</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Vrsta</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Velikost</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Dodano</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Vir</FwbTableHeadCell>
|
||||
<FwbTableHeadCell class="w-px" />
|
||||
</FwbTableHead>
|
||||
<FwbTableBody>
|
||||
@@ -122,7 +131,7 @@ const handleDownload = (doc) => {
|
||||
<FwbTableRow>
|
||||
<FwbTableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="button" class="text-indigo-600 hover:underline" @click="$emit('view', doc)">{{ doc.original_name || doc.name }}</button>
|
||||
<button type="button" class="text-indigo-600 hover:underline" @click="$emit('view', doc)">{{ doc.name }}</button>
|
||||
<FwbBadge v-if="doc.is_public" type="green">Public</FwbBadge>
|
||||
</div>
|
||||
</FwbTableCell>
|
||||
@@ -134,6 +143,9 @@ const handleDownload = (doc) => {
|
||||
</FwbTableCell>
|
||||
<FwbTableCell>{{ formatSize(doc.size) }}</FwbTableCell>
|
||||
<FwbTableCell>{{ new Date(doc.created_at).toLocaleString() }}</FwbTableCell>
|
||||
<FwbTableCell>
|
||||
<FwbBadge type="purple">{{ sourceLabel(doc) }}</FwbBadge>
|
||||
</FwbTableCell>
|
||||
<FwbTableCell class="text-center">
|
||||
<button
|
||||
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
person: { type: Object, required: true },
|
||||
types: { type: Object, default: () => ({}) },
|
||||
// Allow overriding the default active tab: 'addresses' | 'phones' | 'emails' | 'bank'
|
||||
defaultTab: { type: String, default: 'addresses' },
|
||||
})
|
||||
|
||||
const phoneTypes = computed(() => {
|
||||
const arr = props.types?.phone_types || []
|
||||
const map = {}
|
||||
for (const t of arr) { map[t.id] = t.name }
|
||||
return map
|
||||
})
|
||||
|
||||
const displayName = computed(() => {
|
||||
const p = props.person || {}
|
||||
const full = p.full_name?.trim()
|
||||
if (full) { return full }
|
||||
const first = p.first_name?.trim() || ''
|
||||
const last = p.last_name?.trim() || ''
|
||||
return `${first} ${last}`.trim()
|
||||
})
|
||||
|
||||
const primaryAddress = computed(() => props.person?.addresses?.[0] || null)
|
||||
const primaryEmail = computed(() => props.person?.emails?.[0]?.value || null)
|
||||
// Backend phone model uses `nu` as the number
|
||||
const allPhones = computed(() => props.person?.phones || [])
|
||||
const allAddresses = computed(() => props.person?.addresses || [])
|
||||
const allEmails = computed(() => props.person?.emails || [])
|
||||
// Laravel serializes relation names to snake_case, so prefer bank_accounts, fallback to bankAccounts
|
||||
const allBankAccounts = computed(() => props.person?.bank_accounts || props.person?.bankAccounts || [])
|
||||
const bankIban = computed(() => allBankAccounts.value?.[0]?.iban || null)
|
||||
const taxNumber = computed(() => props.person?.tax_number || null)
|
||||
const ssn = computed(() => props.person?.social_security_number || null)
|
||||
|
||||
// Summary sizing
|
||||
const showMore = ref(false)
|
||||
const summaryPhones = computed(() => allPhones.value.slice(0, showMore.value ? 2 : 1))
|
||||
|
||||
// Tabs
|
||||
const activeTab = ref(props.defaultTab || 'addresses')
|
||||
watch(() => props.defaultTab, (val) => { if (val) activeTab.value = val })
|
||||
|
||||
function maskIban(iban) {
|
||||
if (!iban || typeof iban !== 'string') return null
|
||||
const clean = iban.replace(/\s+/g, '')
|
||||
if (clean.length <= 8) return clean
|
||||
return `${clean.slice(0, 4)} **** **** ${clean.slice(-4)}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-sm">
|
||||
<div v-if="displayName" class="font-medium text-gray-900">{{ displayName }}</div>
|
||||
|
||||
<div v-if="primaryAddress" class="mt-1 text-gray-700">
|
||||
<span>{{ primaryAddress.address }}</span>
|
||||
<span v-if="primaryAddress.country" class="text-gray-500 text-xs ml-1">({{ primaryAddress.country }})</span>
|
||||
</div>
|
||||
|
||||
<div v-if="summaryPhones?.length" class="mt-1 space-y-0.5">
|
||||
<div v-for="p in summaryPhones" :key="p.id" class="text-gray-700">
|
||||
<span>{{ p.nu }}</span>
|
||||
<span v-if="(p.type_id && phoneTypes[p.type_id]) || p.type?.name" class="text-gray-500 text-xs ml-1">({{ p.type?.name || phoneTypes[p.type_id] }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showMore && primaryEmail" class="mt-1 text-gray-700">{{ primaryEmail }}</div>
|
||||
|
||||
<div v-if="showMore && bankIban" class="mt-1 text-gray-700">TRR: <span class="font-mono">{{ maskIban(bankIban) }}</span></div>
|
||||
|
||||
<div v-if="showMore && taxNumber" class="mt-1 text-gray-700">Davčna: <span class="font-mono">{{ taxNumber }}</span></div>
|
||||
<div v-if="showMore && ssn" class="mt-1 text-gray-700">EMŠO: <span class="font-mono">{{ ssn }}</span></div>
|
||||
|
||||
<button type="button" class="mt-2 text-xs text-blue-600 hover:underline" @click="showMore = !showMore">
|
||||
{{ showMore ? 'Skrij' : 'Prikaži več' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="mt-3">
|
||||
<div class="flex gap-2 overflow-x-auto">
|
||||
<button type="button" @click="activeTab = 'addresses'" :class="['px-3 py-1 rounded text-xs inline-flex items-center gap-1', activeTab==='addresses' ? 'bg-gray-200 text-gray-900' : 'bg-white text-gray-700 border']">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a1 1 0 0 1 .832.445l6 8.5a1 1 0 0 1 .168.555V17a1 1 0 0 1-1 1h-4v-4H8v4H4a1 1 0 0 1-1-1v-5.5a1 1 0 0 1 .168-.555l6-8.5A1 1 0 0 1 10 2Z"/></svg>
|
||||
Naslovi ({{ allAddresses.length }})
|
||||
</button>
|
||||
<button type="button" @click="activeTab = 'phones'" :class="['px-3 py-1 rounded text-xs inline-flex items-center gap-1', activeTab==='phones' ? 'bg-gray-200 text-gray-900' : 'bg-white text-gray-700 border']">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M2.3 3.3c.6-1 1.9-1.3 2.9-.7l1.7 1a2 2 0 0 1 .9 2.5l-.5 1.2a2 2 0 0 0 .4 2.2l2.8 2.8a2 2 0 0 0 2.2.4l1.2-.5a2 2 0 0 1 2.5.9l1 1.7c.6 1 .3 2.3-.7 2.9-2 1.1-4.5 1.1-6.5 0-2.5-1.3-4.8-3.6-6.1-6.1-1.1-2-1.1-4.5 0-6.5Z"/></svg>
|
||||
Telefoni ({{ allPhones.length }})
|
||||
</button>
|
||||
<button type="button" @click="activeTab = 'emails'" :class="['px-3 py-1 rounded text-xs inline-flex items-center gap-1', activeTab==='emails' ? 'bg-gray-200 text-gray-900' : 'bg-white text-gray-700 border']">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M2.5 5A1.5 1.5 0 0 1 4 3.5h12A1.5 1.5 0 0 1 17.5 5v10A1.5 1.5 0 0 1 16 16.5H4A1.5 1.5 0 0 1 2.5 15V5Zm2.1.5 5.4 3.6a1 1 0 0 0 1.1 0l5.4-3.6V5H4.6Z"/></svg>
|
||||
E-pošta ({{ allEmails.length }})
|
||||
</button>
|
||||
<button type="button" @click="activeTab = 'bank'" :class="['px-3 py-1 rounded text-xs inline-flex items-center gap-1', activeTab==='bank' ? 'bg-gray-200 text-gray-900' : 'bg-white text-gray-700 border']">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2 2 6v2h16V6l-8-4Zm-6 7h12v7H4V9Zm-1 8h14v1H3v-1Z"/></svg>
|
||||
TRR ({{ allBankAccounts.length }})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<!-- Addresses -->
|
||||
<div v-if="activeTab==='addresses'">
|
||||
<div v-if="!allAddresses.length" class="text-gray-500 text-xs">Ni naslovov.</div>
|
||||
<div v-for="(a,idx) in allAddresses" :key="a.id || idx" class="py-1">
|
||||
<div class="text-gray-800">{{ a.address }}</div>
|
||||
<div v-if="a.country" class="text-gray-600 text-xs">{{ a.country }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phones -->
|
||||
<div v-else-if="activeTab==='phones'">
|
||||
<div v-if="!allPhones.length" class="text-gray-500 text-xs">Ni telefonov.</div>
|
||||
<div v-for="(p,idx) in allPhones" :key="p.id || idx" class="py-1">
|
||||
<div class="text-gray-800">{{ p.nu }} <span v-if="(p.type_id && phoneTypes[p.type_id]) || p.type?.name" class="text-gray-500 text-xs">({{ p.type?.name || phoneTypes[p.type_id] }})</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Emails -->
|
||||
<div v-else-if="activeTab==='emails'">
|
||||
<div v-if="!allEmails.length" class="text-gray-500 text-xs">Ni e-poštnih naslovov.</div>
|
||||
<div v-for="(e,idx) in allEmails" :key="e.id || idx" class="py-1">
|
||||
<div class="text-gray-800">{{ e.value }}<span v-if="e.label" class="text-gray-500 text-xs ml-1">({{ e.label }})</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bank accounts -->
|
||||
<div v-else>
|
||||
<div v-if="!allBankAccounts.length" class="text-gray-500 text-xs">Ni TRR računov.</div>
|
||||
<div v-for="(b,idx) in allBankAccounts" :key="b.id || idx" class="py-1">
|
||||
<div class="text-gray-800">{{ maskIban(b.iban) }}</div>
|
||||
<div v-if="b.bank_name" class="text-gray-600 text-xs">{{ b.bank_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -7,6 +7,8 @@ import Dropdown from '@/Components/Dropdown.vue';
|
||||
import DropdownLink from '@/Components/DropdownLink.vue';
|
||||
import Breadcrumbs from '@/Components/Breadcrumbs.vue';
|
||||
import GlobalSearch from './Partials/GlobalSearch.vue';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { faMobileScreenButton } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
@@ -184,6 +186,15 @@ watch(
|
||||
<span v-if="!sidebarCollapsed">Nova uvozna predloga</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link :href="route('fieldjobs.index')" :class="['flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-100', route().current('fieldjobs.index') ? 'bg-gray-100 text-gray-900' : 'text-gray-600']" title="Terenske naloge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.5-7.5 10.5-7.5 10.5S4.5 18 4.5 10.5a7.5 7.5 0 1115 0z" />
|
||||
</svg>
|
||||
<span v-if="!sidebarCollapsed">Terenske naloge</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link :href="route('settings')" :class="['flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-100', route().current('settings') || route().current('settings.*') ? 'bg-gray-100 text-gray-900' : 'text-gray-600']" title="Nastavitve">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
|
||||
@@ -222,8 +233,12 @@ watch(
|
||||
<kbd class="hidden sm:inline ml-2 text-[10px] px-1.5 py-0.5 rounded border bg-gray-50">Ctrl K</kbd>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- User drop menu --->
|
||||
<div class="flex items-center">
|
||||
<!-- Phone page quick access button -->
|
||||
<Link :href="route('phone.index')" class="inline-flex items-center justify-center w-9 h-9 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 mr-2" title="Phone">
|
||||
<FontAwesomeIcon :icon="faMobileScreenButton" class="h-5 w-5" />
|
||||
</Link>
|
||||
<div class="ms-3 relative">
|
||||
<Dropdown align="right" width="48">
|
||||
<template #trigger>
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
|
||||
import { Head, Link, router, usePage } from '@inertiajs/vue3';
|
||||
import ApplicationMark from '@/Components/ApplicationMark.vue';
|
||||
import Banner from '@/Components/Banner.vue';
|
||||
import Dropdown from '@/Components/Dropdown.vue';
|
||||
import DropdownLink from '@/Components/DropdownLink.vue';
|
||||
import GlobalSearch from './Partials/GlobalSearch.vue';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { faDesktop } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
});
|
||||
|
||||
// Sidebar + responsive behavior (same feel as AppLayout)
|
||||
const sidebarCollapsed = ref(false);
|
||||
const hasSavedSidebarPref = ref(false);
|
||||
const isMobile = ref(false);
|
||||
const mobileSidebarOpen = ref(false);
|
||||
function applyAutoCollapse() {
|
||||
if (typeof window === 'undefined') return;
|
||||
isMobile.value = window.innerWidth < 1024;
|
||||
sidebarCollapsed.value = isMobile.value;
|
||||
}
|
||||
function handleResize() {
|
||||
if (typeof window !== 'undefined') {
|
||||
isMobile.value = window.innerWidth < 1024;
|
||||
if (!isMobile.value) mobileSidebarOpen.value = false;
|
||||
}
|
||||
if (!hasSavedSidebarPref.value) applyAutoCollapse();
|
||||
}
|
||||
onMounted(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('sidebarCollapsed');
|
||||
if (saved !== null) {
|
||||
hasSavedSidebarPref.value = true;
|
||||
sidebarCollapsed.value = saved === '1';
|
||||
} else {
|
||||
applyAutoCollapse();
|
||||
}
|
||||
} catch {}
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
onUnmounted(() => window.removeEventListener('resize', handleResize));
|
||||
watch(sidebarCollapsed, (v) => {
|
||||
if (!hasSavedSidebarPref.value) return;
|
||||
try { localStorage.setItem('sidebarCollapsed', v ? '1' : '0'); } catch {}
|
||||
});
|
||||
|
||||
function toggleSidebar() {
|
||||
hasSavedSidebarPref.value = true;
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
||||
}
|
||||
function toggleMobileSidebar() {
|
||||
mobileSidebarOpen.value = !mobileSidebarOpen.value;
|
||||
}
|
||||
function handleSidebarToggleClick() {
|
||||
if (isMobile.value) toggleMobileSidebar();
|
||||
else toggleSidebar();
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
router.post(route('logout'));
|
||||
};
|
||||
|
||||
// Flash toast notifications (same as AppLayout for consistency)
|
||||
const page = usePage();
|
||||
const flash = computed(() => page.props.flash || {});
|
||||
const showToast = ref(false);
|
||||
const toastMessage = ref('');
|
||||
const toastType = ref('success');
|
||||
watch(
|
||||
() => [flash.value.success, flash.value.error, flash.value.warning, flash.value.info],
|
||||
([s, e, w, i]) => {
|
||||
const message = s || e || w || i;
|
||||
const type = s ? 'success' : e ? 'error' : w ? 'warning' : i ? 'info' : null;
|
||||
if (message && type) {
|
||||
toastMessage.value = message;
|
||||
toastType.value = type;
|
||||
showToast.value = true;
|
||||
setTimeout(() => (showToast.value = false), 3000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// Global search modal state
|
||||
const searchOpen = ref(false);
|
||||
const openSearch = () => (searchOpen.value = true);
|
||||
const closeSearch = () => (searchOpen.value = false);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Head :title="title" />
|
||||
|
||||
<Banner />
|
||||
|
||||
<div class="min-h-screen bg-gray-100 flex">
|
||||
<!-- Mobile backdrop -->
|
||||
<div v-if="isMobile && mobileSidebarOpen" class="fixed inset-0 z-40 bg-black/30" @click="mobileSidebarOpen=false"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside :class="[
|
||||
sidebarCollapsed ? 'w-16' : 'w-64',
|
||||
'bg-white border-r border-gray-200 transition-all duration-200 z-50',
|
||||
isMobile
|
||||
? ('fixed inset-y-0 left-0 transform ' + (mobileSidebarOpen ? 'translate-x-0' : '-translate-x-full'))
|
||||
: 'sticky top-0 h-screen overflow-y-auto'
|
||||
]">
|
||||
<div class="h-16 px-4 flex items-center justify-between border-b">
|
||||
<Link :href="route('phone.index')" class="flex items-center gap-2">
|
||||
<ApplicationMark class="h-8 w-auto" />
|
||||
<span v-if="!sidebarCollapsed" class="text-sm font-semibold">Teren</span>
|
||||
</Link>
|
||||
</div>
|
||||
<nav class="py-4">
|
||||
<ul class="space-y-1">
|
||||
<!-- Single phone link only -->
|
||||
<li>
|
||||
<Link :href="route('phone.index')" :class="['flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-100', route().current('phone.index') || route().current('phone.*') ? 'bg-gray-100 text-gray-900' : 'text-gray-600']" title="Opravila">
|
||||
<!-- clipboard-list icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75H7.5A2.25 2.25 0 005.25 9v9A2.25 2.25 0 007.5 20.25h9A2.25 2.25 0 0018.75 18v-9A2.25 2.25 0 0016.5 6.75H15M9 6.75A1.5 1.5 0 0010.5 5.25h3A1.5 1.5 0 0015 6.75M9 6.75A1.5 1.5 0 0110.5 8.25h3A1.5 1.5 0 0015 6.75M9 12h.008v.008H9V12zm0 3h.008v.008H9V15zm3-3h3m-3 3h3" />
|
||||
</svg>
|
||||
<span v-if="!sidebarCollapsed">Opravila</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main column -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<!-- Top bar -->
|
||||
<div class="h-16 bg-white border-b border-gray-100 px-4 flex items-center justify-between sticky top-0 z-30">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Sidebar toggle -->
|
||||
<button
|
||||
@click="handleSidebarToggleClick()"
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
||||
:title="sidebarCollapsed ? 'Razširi meni' : 'Skrči meni'"
|
||||
aria-label="Toggle sidebar"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Search trigger -->
|
||||
<button @click="openSearch" class="inline-flex items-center gap-2 px-3 py-2 text-sm rounded-md border border-gray-200 text-gray-500 hover:text-gray-700 hover:border-gray-300">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35m0 0A7.5 7.5 0 1010.5 18.5a7.5 7.5 0 006.15-1.85z" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">Globalni iskalnik</span>
|
||||
<kbd class="hidden sm:inline ml-2 text-[10px] px-1.5 py-0.5 rounded border bg-gray-50">Ctrl K</kbd>
|
||||
</button>
|
||||
</div>
|
||||
<!-- User drop menu + Desktop switch button -->
|
||||
<div class="flex items-center">
|
||||
<!-- Desktop page quick access button -->
|
||||
<Link :href="route('clientCase')" class="inline-flex items-center justify-center w-9 h-9 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 mr-2" title="Desktop">
|
||||
<FontAwesomeIcon :icon="faDesktop" class="h-5 w-5" />
|
||||
</Link>
|
||||
<div class="ms-3 relative">
|
||||
<Dropdown align="right" width="48">
|
||||
<template #trigger>
|
||||
<button v-if="$page.props.jetstream.managesProfilePhotos" class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition">
|
||||
<img class="h-8 w-8 rounded-full object-cover" :src="$page.props.auth.user.profile_photo_url" :alt="$page.props.auth.user.name">
|
||||
</button>
|
||||
|
||||
<span v-else class="inline-flex rounded-md">
|
||||
<button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none focus:bg-gray-50 active:bg-gray-50 transition ease-in-out duration-150">
|
||||
{{ $page.props.auth.user.name }}
|
||||
<svg class="ms-2 -me-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="block px-4 py-2 text-xs text-gray-400">Nastavitve računa</div>
|
||||
|
||||
<DropdownLink :href="route('profile.show')">Profil</DropdownLink>
|
||||
<DropdownLink v-if="$page.props.jetstream.hasApiFeatures" :href="route('api-tokens.index')">API Tokens</DropdownLink>
|
||||
|
||||
<div class="border-t border-gray-200" />
|
||||
|
||||
<form @submit.prevent="logout">
|
||||
<DropdownLink as="button">Izpis</DropdownLink>
|
||||
</form>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Heading -->
|
||||
<header v-if="$slots.header" class="bg-white border-b shadow-sm">
|
||||
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8 space-y-2">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="p-4">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Search Modal -->
|
||||
<GlobalSearch :open="searchOpen" @update:open="(v)=>searchOpen=v" />
|
||||
|
||||
<!-- Simple Toast -->
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="showToast"
|
||||
class="fixed bottom-4 right-4 z-[100] px-4 py-3 rounded shadow-lg text-white"
|
||||
:class="{
|
||||
'bg-emerald-600': toastType==='success',
|
||||
'bg-red-600': toastType==='error',
|
||||
'bg-amber-500': toastType==='warning',
|
||||
'bg-blue-600': toastType==='info',
|
||||
}"
|
||||
>
|
||||
{{ toastMessage }}
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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