updates to UI and add archiving option

This commit is contained in:
Simon Pocrnjič
2025-10-05 19:45:49 +02:00
parent fe91c7e4bc
commit bab9d6561f
50 changed files with 3337 additions and 416 deletions
+261 -63
View File
@@ -73,6 +73,27 @@ function formatAmount(val) {
});
}
function formatDateShort(val) {
if (!val) return "";
try {
const d = new Date(val);
if (Number.isNaN(d.getTime())) return "";
return d.toLocaleDateString("sl-SI", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
} catch {
return "";
}
}
function activityActionLine(a) {
const base = a?.action?.name || "";
const decision = a?.decision?.name ? `${a.decision.name}` : "";
return base + decision;
}
// Activity drawer state
const drawerAddActivity = ref(false);
const activityContractUuid = ref(null);
@@ -139,6 +160,35 @@ const submitComplete = () => {
},
});
};
// Contracts objects (Predmeti) modal state
const objectsModal = reactive({ open: false, items: [], contract: null });
function getContractObjects(c) {
if (!c) return [];
// Try a few common property names; fallback empty
return c.objects || c.contract_objects || c.items || [];
}
function openObjectsModal(c) {
objectsModal.contract = c;
objectsModal.items = getContractObjects(c) || [];
objectsModal.open = true;
}
function closeObjectsModal() {
objectsModal.open = false;
objectsModal.items = [];
objectsModal.contract = null;
}
// Client details (Stranka) summary
const clientSummary = computed(() => {
const p = props.client?.person || {};
return {
name: p.full_name || p.name || "—",
tax: p.tax_number || p.davcna || p.tax || null,
emso: p.emso || p.ems || null,
trr: p.trr || p.bank_account || null,
};
});
</script>
<template>
@@ -172,10 +222,13 @@ const submitComplete = () => {
<!-- 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">
<h3
class="text-base font-semibold text-gray-900 leading-tight flex items-center gap-2"
>
<span class="truncate">{{ clientSummary.name }}</span>
<span class="chip-base chip-indigo">Naročnik</span>
</h3>
<div class="mt-4 pt-4 border-t border-dashed">
<PersonDetailPhone
:types="types"
:person="client.person"
@@ -188,14 +241,17 @@ const submitComplete = () => {
<!-- 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">
<h3
class="text-base font-semibold text-gray-900 leading-tight flex items-center gap-2"
>
<span class="truncate">{{ client_case.person.full_name }}</span>
<span class="chip-base chip-indigo">Primer</span>
</h3>
<div class="mt-4 pt-4 border-t border-dashed">
<PersonDetailPhone
:types="types"
:person="client_case.person"
default-tab="phones"
default-tab="addresses"
/>
</div>
</div>
@@ -211,48 +267,82 @@ const submitComplete = () => {
<div
v-for="c in contracts"
:key="c.uuid || c.id"
class="rounded border p-3 sm:p-4"
class="rounded border p-3 sm:p-4 bg-white shadow-sm"
>
<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) }}
<!-- Header Row -->
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<p
class="font-semibold text-gray-900 text-sm leading-tight truncate"
>
{{ c.reference || c.uuid }}
</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)"
<span
v-if="c.type?.name"
class="inline-flex items-center px-2 py-0.5 rounded-full bg-indigo-100 text-indigo-700 text-[11px] font-medium"
>
+ 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)"
{{ c.type.name }}
</span>
</div>
<div v-if="c.account" class="mt-2 flex items-baseline gap-2">
<span class="uppercase tracking-wide text-[11px] text-gray-400"
>Odprto</span
>
<span
class="text-lg font-semibold text-gray-900 leading-none tracking-tight"
>{{ formatAmount(c.account.balance_amount) }} </span
>
+ Dokument
</button>
</div>
</div>
<div class="flex flex-col gap-1.5 w-32 text-right shrink-0">
<button
type="button"
class="text-sm px-3 py-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 active:scale-[.97] transition shadow"
@click="openDrawerAddActivity(c)"
>
+ Aktivnost
</button>
<button
type="button"
class="text-sm px-3 py-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 active:scale-[.97] transition shadow"
@click="openDocDialog(c)"
>
+ Dokument
</button>
<!--button
type="button"
:disabled="!getContractObjects(c).length"
@click="openObjectsModal(c)"
class="relative text-sm px-3 py-2 rounded-md flex items-center justify-center transition disabled:cursor-not-allowed disabled:opacity-50 bg-slate-600 text-white hover:bg-slate-700 active:scale-[.97] shadow"
>
Predmeti
<span
class="ml-1 inline-flex items-center justify-center min-w-[1.1rem] h-5 text-[11px] px-1.5 rounded-full bg-white/90 text-slate-700 font-medium"
>{{ getContractObjects(c).length }}</span
>
</button-->
</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"
<!-- Subject / Last Object -->
<div v-if="c.last_object" class="mt-3 border-t pt-3">
<p class="text-[11px] uppercase tracking-wide text-gray-400 mb-1">
Zadnji predmet
</p>
<div class="text-sm font-medium text-gray-800">
{{ c.last_object.name || c.last_object.reference }}
<span
v-if="c.last_object.type"
class="ml-2 text-xs font-normal text-gray-500"
>({{ c.last_object.type }})</span
>
</p>
<p v-if="c.last_object.description" class="text-gray-600 mt-1">
</div>
<div
v-if="c.last_object.description"
class="mt-1 text-sm text-gray-600 leading-snug"
>
{{ c.last_object.description }}
</p>
</div>
</div>
</div>
<p v-if="!contracts?.length" class="text-sm text-gray-600">
@@ -270,40 +360,66 @@ const submitComplete = () => {
<template #title>Aktivnosti</template>
</SectionTitle>
<button
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
class="text-xs font-medium px-3 py-2 rounded-md bg-indigo-600 text-white shadow-sm active:scale-[.98] 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 class="mt-3 space-y-3">
<div
v-for="a in activities"
:key="a.id"
class="rounded-md border border-gray-200 bg-gray-50/70 px-3 py-3 shadow-sm text-[13px]"
>
<!-- Top line: action + date/user -->
<div class="flex items-start justify-between gap-3">
<div class="font-medium text-gray-800 leading-snug truncate">
{{ activityActionLine(a) || "Aktivnost" }}
</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
class="shrink-0 text-right text-[11px] text-gray-500 leading-tight"
>
<div v-if="a.created_at">{{ formatDateShort(a.created_at) }}</div>
<div v-if="(a.user && a.user.name) || a.user_name" class="truncate">
{{ a.user?.name || a.user_name }}
</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"
<!-- Badges row -->
<div class="mt-2 flex flex-wrap gap-1.5">
<span
v-if="a.contract"
class="inline-flex items-center rounded-full bg-indigo-100 text-indigo-700 px-2 py-0.5 text-[10px] font-medium"
>Pogodba: {{ a.contract.reference }}</span
>
<span
v-if="a.due_date"
class="inline-flex items-center rounded-full bg-amber-100 text-amber-700 px-2 py-0.5 text-[10px] font-medium"
>Zapadlost: {{ formatDateShort(a.due_date) || a.due_date }}</span
>
<span
v-if="a.amount != null"
class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-700 px-2 py-0.5 text-[10px] font-medium"
>Znesek: {{ formatAmount(a.amount) }} </span
>
<span
v-if="a.status"
class="inline-flex items-center rounded-full bg-gray-200 text-gray-700 px-2 py-0.5 text-[10px] font-medium"
>{{ a.status }}</span
>
</div>
<!-- Note -->
<div v-if="a.note" class="mt-2 text-gray-700 leading-snug">
{{ a.note }}
</div>
</div>
<div v-if="!activities?.length" class="text-gray-600 py-2">
<div
v-if="!activities?.length"
class="text-gray-600 text-sm py-2 text-center"
>
Ni aktivnosti.
</div>
</div>
@@ -423,6 +539,65 @@ const submitComplete = () => {
</template>
</ConfirmationModal>
<!-- Contract Objects (Predmeti) Modal -->
<DialogModal :show="objectsModal.open" @close="closeObjectsModal">
<template #title>
Predmeti
<span
v-if="objectsModal.contract"
class="block text-xs font-normal text-gray-500 mt-0.5"
>
{{ objectsModal.contract.reference || objectsModal.contract.uuid }}
</span>
</template>
<template #content>
<div
v-if="objectsModal.items.length"
class="space-y-3 max-h-[60vh] overflow-y-auto pr-1"
>
<div
v-for="(o, idx) in objectsModal.items"
:key="o.id || o.uuid || idx"
class="rounded border border-gray-200 bg-gray-50 px-3 py-2 text-sm"
>
<div class="font-medium text-gray-800 truncate">
{{ o.name || o.reference || "#" + (o.id || o.uuid || idx + 1) }}
</div>
<div class="mt-0.5 text-xs text-gray-500 flex flex-wrap gap-x-2 gap-y-0.5">
<span
v-if="o.type"
class="inline-flex items-center bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded-full"
>{{ o.type }}</span
>
<span
v-if="o.status"
class="inline-flex items-center bg-gray-200 text-gray-700 px-1.5 py-0.5 rounded-full"
>{{ o.status }}</span
>
<span
v-if="o.amount != null"
class="inline-flex items-center bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded-full"
>{{ formatAmount(o.amount) }} </span
>
</div>
<div v-if="o.description" class="mt-1 text-gray-600 leading-snug">
{{ o.description }}
</div>
</div>
</div>
<div v-else class="text-gray-600 text-sm">Ni predmetov.</div>
</template>
<template #footer>
<button
type="button"
class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200"
@click="closeObjectsModal"
>
Zapri
</button>
</template>
</DialogModal>
<!-- Upload Document Modal -->
<DialogModal :show="docDialogOpen" @close="closeDocDialog">
<template #title>Dodaj dokument</template>
@@ -493,4 +668,27 @@ const submitComplete = () => {
</AppPhoneLayout>
</template>
<style scoped></style>
<style scoped>
/* Using basic CSS since @apply is not processed in this scoped block by default */
.chip-base {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem; /* py-0.5 px-2 */
border-radius: 9999px;
font-size: 11px;
font-weight: 500;
line-height: 1.1;
}
.chip-indigo {
background: #eef2ff;
color: #3730a3;
} /* approx indigo-50 / indigo-700 */
.chip-default {
background: #f1f5f9;
color: #334155;
} /* slate-100 / slate-700 */
.chip-emerald {
background: #ecfdf5;
color: #047857;
} /* emerald-50 / emerald-700 */
</style>