654 lines
23 KiB
Vue
654 lines
23 KiB
Vue
<script setup>
|
||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||
import { computed, ref, onMounted } from "vue";
|
||
import { usePage, Link } from "@inertiajs/vue3";
|
||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||
import {
|
||
faUsers,
|
||
faUserPlus,
|
||
faClipboardList,
|
||
faFileLines,
|
||
faCloudArrowUp,
|
||
faArrowUpRightFromSquare,
|
||
} from "@fortawesome/free-solid-svg-icons";
|
||
import { faFileContract } from "@fortawesome/free-solid-svg-icons";
|
||
|
||
const props = defineProps({
|
||
kpis: Object,
|
||
activities: Array,
|
||
trends: Object,
|
||
systemHealth: Object,
|
||
staleCases: Array,
|
||
fieldJobsAssignedToday: Array,
|
||
importsInProgress: Array,
|
||
activeTemplates: Array,
|
||
});
|
||
|
||
const kpiDefs = [
|
||
{ key: "clients_total", label: "Vse stranke", icon: faUsers, route: "client" },
|
||
{ key: "clients_new_7d", label: "Nove (7d)", icon: faUserPlus, route: "client" },
|
||
{
|
||
key: "field_jobs_today",
|
||
label: "Terenske danes",
|
||
icon: faClipboardList,
|
||
route: "fieldjobs.index",
|
||
},
|
||
{
|
||
key: "documents_today",
|
||
label: "Dokumenti danes",
|
||
icon: faFileLines,
|
||
route: "clientCase",
|
||
},
|
||
{
|
||
key: "active_imports",
|
||
label: "Aktivni uvozi",
|
||
icon: faCloudArrowUp,
|
||
route: "imports.index",
|
||
},
|
||
{
|
||
key: "active_contracts",
|
||
label: "Aktivne pogodbe",
|
||
icon: faFileContract,
|
||
route: "clientCase",
|
||
},
|
||
];
|
||
|
||
const page = usePage();
|
||
|
||
// Simple sparkline path generator
|
||
function sparkline(values) {
|
||
if (!values || !values.length) {
|
||
return "";
|
||
}
|
||
const max = Math.max(...values) || 1;
|
||
const h = 24;
|
||
const w = 60;
|
||
const step = w / (values.length - 1 || 1);
|
||
return values
|
||
.map(
|
||
(v, i) =>
|
||
`${i === 0 ? "M" : "L"}${(i * step).toFixed(2)},${(h - (v / max) * h).toFixed(2)}`
|
||
)
|
||
.join(" ");
|
||
}
|
||
|
||
// Remove single relatedTarget helper and replace with multi-link builder
|
||
function buildRelated(a) {
|
||
const links = [];
|
||
// Only client case link (other routes not defined yet)
|
||
if (a.client_case_uuid || a.client_case_id) {
|
||
const caseParam = a.client_case_uuid || a.client_case_id;
|
||
try {
|
||
// Prefer Ziggy when available and force stringification here
|
||
const href = String(route("clientCase.show", { client_case: caseParam }));
|
||
links.push({
|
||
type: "client_case",
|
||
label: "Primer",
|
||
href,
|
||
});
|
||
} catch (e) {
|
||
// Safe fallback to a best-effort URL to avoid breaking render
|
||
links.push({
|
||
type: "client_case",
|
||
label: "Primer",
|
||
href: `/client-cases/${caseParam}`,
|
||
});
|
||
}
|
||
}
|
||
return links;
|
||
}
|
||
|
||
const activityItems = computed(() =>
|
||
(props.activities || []).map((a) => ({ ...a, links: buildRelated(a) }))
|
||
);
|
||
|
||
// Format stale days label: never negative; '<1 dan' if 0<=value<1; else integer with proper suffix.
|
||
function formatStaleDaysLabel(value) {
|
||
const num = Number.parseFloat(value);
|
||
if (Number.isNaN(num)) {
|
||
return "—";
|
||
}
|
||
if (num < 1) {
|
||
return "<1 dan";
|
||
}
|
||
const whole = Math.floor(num);
|
||
return whole === 1 ? "1 dan" : whole + " dni";
|
||
}
|
||
|
||
// Robust time formatter to avoid fixed 02:00:00 (timezone / fallback issues)
|
||
function formatJobTime(ts) {
|
||
if (!ts) return "";
|
||
try {
|
||
const d = new Date(ts);
|
||
if (isNaN(d.getTime())) return "";
|
||
// Show HH:MM (24h) and seconds only if non-zero seconds
|
||
const pad = (n) => n.toString().padStart(2, "0");
|
||
const h = pad(d.getHours());
|
||
const m = pad(d.getMinutes());
|
||
const s = d.getSeconds();
|
||
return s ? `${h}:${m}:${pad(s)}` : `${h}:${m}`;
|
||
} catch (e) {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
// Safely build a client case href using Ziggy when available, with a plain fallback.
|
||
function safeCaseHref(uuid, segment = null) {
|
||
if (!uuid) {
|
||
return "#";
|
||
}
|
||
try {
|
||
const params = { client_case: uuid };
|
||
if (segment != null) {
|
||
params.segment = segment;
|
||
}
|
||
return String(route("clientCase.show", params));
|
||
} catch (e) {
|
||
return segment != null
|
||
? `/client-cases/${uuid}?segment=${segment}`
|
||
: `/client-cases/${uuid}`;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<AppLayout title="Nadzorna plošča">
|
||
<template #header> </template>
|
||
|
||
<div class="max-w-7xl mx-auto space-y-10 py-6">
|
||
<!-- KPI Cards with trends -->
|
||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||
<Link
|
||
v-for="k in kpiDefs"
|
||
:key="k.key"
|
||
:href="route(k.route)"
|
||
class="group relative bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm px-4 py-5 flex flex-col gap-3 hover:border-indigo-300 hover:shadow transition focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||
>
|
||
<div class="flex items-center justify-between">
|
||
<span
|
||
class="inline-flex items-center justify-center h-10 w-10 rounded-md bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 group-hover:bg-indigo-100 dark:group-hover:bg-indigo-800/40"
|
||
>
|
||
<FontAwesomeIcon :icon="k.icon" class="w-5 h-5" />
|
||
</span>
|
||
<span
|
||
class="text-[11px] text-gray-400 dark:text-gray-500 uppercase tracking-wide"
|
||
>{{ k.label }}</span
|
||
>
|
||
</div>
|
||
<div class="flex items-end gap-2">
|
||
<span
|
||
class="text-2xl font-semibold tracking-tight text-gray-900 dark:text-gray-100"
|
||
>{{ props.kpis?.[k.key] ?? "—" }}</span
|
||
>
|
||
<span
|
||
class="text-[10px] text-indigo-500 opacity-0 group-hover:opacity-100 transition"
|
||
>Odpri →</span
|
||
>
|
||
</div>
|
||
<div v-if="trends" class="mt-1 h-6">
|
||
<svg
|
||
v-if="k.key === 'clients_new_7d'"
|
||
:viewBox="'0 0 60 24'"
|
||
class="w-full h-6 overflow-visible"
|
||
>
|
||
<path
|
||
:d="sparkline(trends.clients_new)"
|
||
fill="none"
|
||
class="stroke-indigo-400"
|
||
stroke-width="2"
|
||
stroke-linejoin="round"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
<svg
|
||
v-else-if="k.key === 'documents_today'"
|
||
:viewBox="'0 0 60 24'"
|
||
class="w-full h-6"
|
||
>
|
||
<path
|
||
:d="sparkline(trends.documents_new)"
|
||
fill="none"
|
||
class="stroke-emerald-400"
|
||
stroke-width="2"
|
||
stroke-linejoin="round"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
<svg
|
||
v-else-if="k.key === 'field_jobs_today'"
|
||
:viewBox="'0 0 60 24'"
|
||
class="w-full h-6"
|
||
>
|
||
<path
|
||
:d="sparkline(trends.field_jobs)"
|
||
fill="none"
|
||
class="stroke-amber-400"
|
||
stroke-width="2"
|
||
stroke-linejoin="round"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
<svg
|
||
v-else-if="k.key === 'active_imports'"
|
||
:viewBox="'0 0 60 24'"
|
||
class="w-full h-6"
|
||
>
|
||
<path
|
||
:d="sparkline(trends.imports_new)"
|
||
fill="none"
|
||
class="stroke-fuchsia-400"
|
||
stroke-width="2"
|
||
stroke-linejoin="round"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
</Link>
|
||
</div>
|
||
|
||
<div class="grid lg:grid-cols-3 gap-8">
|
||
<!-- Activity Feed -->
|
||
<div class="lg:col-span-1 space-y-4">
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-5 flex flex-col gap-4"
|
||
>
|
||
<div class="flex items-center justify-between">
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase"
|
||
>
|
||
Aktivnost
|
||
</h3>
|
||
</div>
|
||
<ul
|
||
class="divide-y divide-gray-100 dark:divide-gray-700 text-sm"
|
||
v-if="activities"
|
||
>
|
||
<li
|
||
v-for="a in activityItems"
|
||
:key="a.id"
|
||
class="py-2 flex items-start gap-3"
|
||
>
|
||
<span class="w-2 h-2 mt-2 rounded-full bg-indigo-400" />
|
||
<div class="flex-1 min-w-0 space-y-1">
|
||
<p class="text-gray-700 dark:text-gray-300 line-clamp-2">
|
||
{{ a.note || "Dogodek" }}
|
||
</p>
|
||
<div class="flex flex-wrap items-center gap-2">
|
||
<span class="text-[11px] text-gray-400 dark:text-gray-500">{{
|
||
new Date(a.created_at).toLocaleString()
|
||
}}</span>
|
||
<Link
|
||
v-for="l in a.links"
|
||
:key="l.type + l.href"
|
||
:href="l.href"
|
||
class="text-[10px] px-2 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-800/60 font-medium tracking-wide"
|
||
>{{ l.label }}</Link
|
||
>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
<li
|
||
v-if="!activities?.length"
|
||
class="py-4 text-xs text-gray-500 text-center dark:text-gray-500"
|
||
>
|
||
Ni zabeleženih aktivnosti.
|
||
</li>
|
||
</ul>
|
||
<ul v-else class="animate-pulse space-y-2">
|
||
<li
|
||
v-for="n in 5"
|
||
:key="n"
|
||
class="h-5 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</ul>
|
||
<div class="pt-1 flex justify-between items-center text-[11px]">
|
||
<Link
|
||
:href="route('dashboard')"
|
||
class="inline-flex items-center gap-1 font-medium text-indigo-600 dark:text-indigo-400 hover:underline"
|
||
>Več kmalu
|
||
<FontAwesomeIcon :icon="faArrowUpRightFromSquare" class="w-3 h-3"
|
||
/></Link>
|
||
<span v-if="systemHealth" class="text-gray-400 dark:text-gray-500"
|
||
>Posodobljeno
|
||
{{ new Date(systemHealth.generated_at).toLocaleTimeString() }}</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right side panels -->
|
||
<div class="lg:col-span-2 space-y-8">
|
||
<!-- System Health -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
System Health
|
||
</h3>
|
||
<div
|
||
v-if="systemHealth"
|
||
class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4 text-sm"
|
||
>
|
||
<div class="flex flex-col gap-1">
|
||
<span class="text-[11px] uppercase text-gray-400 dark:text-gray-500"
|
||
>Queue backlog</span
|
||
>
|
||
<span class="font-semibold text-gray-800 dark:text-gray-100">{{
|
||
systemHealth.queue_backlog ?? "—"
|
||
}}</span>
|
||
</div>
|
||
<div class="flex flex-col gap-1">
|
||
<span class="text-[11px] uppercase text-gray-400 dark:text-gray-500"
|
||
>Failed jobs</span
|
||
>
|
||
<span class="font-semibold text-gray-800 dark:text-gray-100">{{
|
||
systemHealth.failed_jobs ?? "—"
|
||
}}</span>
|
||
</div>
|
||
<div class="flex flex-col gap-1">
|
||
<span class="text-[11px] uppercase text-gray-400 dark:text-gray-500"
|
||
>Last activity (min)</span
|
||
>
|
||
<span
|
||
class="font-semibold text-gray-800 dark:text-gray-100"
|
||
:title="
|
||
systemHealth.last_activity_iso
|
||
? new Date(systemHealth.last_activity_iso).toLocaleString()
|
||
: ''
|
||
"
|
||
>{{
|
||
Math.max(0, parseInt(systemHealth.last_activity_minutes ?? 0))
|
||
}}</span
|
||
>
|
||
</div>
|
||
<div class="flex flex-col gap-1">
|
||
<span class="text-[11px] uppercase text-gray-400 dark:text-gray-500"
|
||
>Generated</span
|
||
>
|
||
<span class="font-semibold text-gray-800 dark:text-gray-100">{{
|
||
new Date(systemHealth.generated_at).toLocaleTimeString()
|
||
}}</span>
|
||
</div>
|
||
</div>
|
||
<div v-else class="grid sm:grid-cols-4 gap-4 animate-pulse">
|
||
<div
|
||
v-for="n in 4"
|
||
:key="n"
|
||
class="h-10 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Completed Field Jobs Trend (7 dni) -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
Zaključena terenska dela (7 dni)
|
||
</h3>
|
||
<div v-if="trends" class="h-24">
|
||
<svg viewBox="0 0 140 60" class="w-full h-full">
|
||
<defs>
|
||
<linearGradient id="fjc" x1="0" x2="0" y1="0" y2="1">
|
||
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.35" />
|
||
<stop offset="100%" stop-color="#6366f1" stop-opacity="0" />
|
||
</linearGradient>
|
||
</defs>
|
||
<path
|
||
v-if="trends.field_jobs_completed"
|
||
:d="sparkline(trends.field_jobs_completed)"
|
||
stroke="#6366f1"
|
||
stroke-width="2"
|
||
fill="none"
|
||
stroke-linejoin="round"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
<div class="mt-2 flex gap-2 text-[10px] text-gray-400 dark:text-gray-500">
|
||
<span
|
||
v-for="(l, i) in trends.labels"
|
||
:key="i"
|
||
class="flex-1 truncate text-center"
|
||
>{{ l.slice(5) }}</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<div v-else class="h-24 animate-pulse bg-gray-100 dark:bg-gray-700 rounded" />
|
||
</div>
|
||
|
||
<!-- Stale Cases -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
Stari primeri brez aktivnosti
|
||
</h3>
|
||
<ul
|
||
v-if="staleCases"
|
||
class="divide-y divide-gray-100 dark:divide-gray-700 text-sm"
|
||
>
|
||
<li
|
||
v-for="c in staleCases"
|
||
:key="c.id"
|
||
class="py-2 flex items-center justify-between"
|
||
>
|
||
<div class="min-w-0">
|
||
<Link
|
||
v-if="c?.uuid"
|
||
:href="safeCaseHref(c.uuid)"
|
||
class="text-indigo-600 dark:text-indigo-400 hover:underline font-medium"
|
||
>{{ c.client_ref || c.uuid.slice(0, 8) }}</Link
|
||
>
|
||
<span v-else class="text-gray-700 dark:text-gray-300 font-medium">{{
|
||
c.client_ref || "Primer"
|
||
}}</span>
|
||
<p class="text-[11px] text-gray-400 dark:text-gray-500">
|
||
Brez aktivnosti:
|
||
{{ formatStaleDaysLabel(c.days_without_activity ?? c.days_stale) }}
|
||
</p>
|
||
</div>
|
||
<span
|
||
class="text-[10px] px-2 py-0.5 rounded bg-amber-50 dark:bg-amber-900/30 text-amber-600 dark:text-amber-300"
|
||
>Stale</span
|
||
>
|
||
</li>
|
||
<li
|
||
v-if="!staleCases.length"
|
||
class="py-4 text-xs text-gray-500 text-center"
|
||
>
|
||
Ni starih primerov.
|
||
</li>
|
||
</ul>
|
||
<div v-else class="space-y-2 animate-pulse">
|
||
<div
|
||
v-for="n in 5"
|
||
:key="n"
|
||
class="h-5 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Field Jobs Assigned Today -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
Današnje dodelitve terenskih
|
||
</h3>
|
||
<ul
|
||
v-if="fieldJobsAssignedToday"
|
||
class="divide-y divide-gray-100 dark:divide-gray-700 text-sm"
|
||
>
|
||
<li
|
||
v-for="f in fieldJobsAssignedToday"
|
||
:key="f.id"
|
||
class="py-2 flex items-start justify-between gap-3"
|
||
>
|
||
<div class="min-w-0 flex-1">
|
||
<p class="text-gray-700 dark:text-gray-300 text-sm font-medium">
|
||
#{{ f.id }}
|
||
<template v-if="f.contract">
|
||
·
|
||
<Link
|
||
v-if="f.contract.client_case_uuid"
|
||
:href="
|
||
safeCaseHref(f.contract.client_case_uuid, f.contract.segment_id)
|
||
"
|
||
class="text-indigo-600 dark:text-indigo-400 hover:underline"
|
||
>
|
||
{{ f.contract.reference || f.contract.uuid?.slice(0, 8) }}
|
||
</Link>
|
||
<span v-else class="text-gray-700 dark:text-gray-300">{{
|
||
f.contract.reference || f.contract.uuid?.slice(0, 8)
|
||
}}</span>
|
||
<span
|
||
v-if="f.contract.person_full_name"
|
||
class="text-gray-500 dark:text-gray-400"
|
||
>
|
||
– {{ f.contract.person_full_name }}
|
||
</span>
|
||
</template>
|
||
</p>
|
||
<p class="text-[11px] text-gray-400 dark:text-gray-500">
|
||
{{ formatJobTime(f.created_at) }}
|
||
</p>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<span
|
||
v-if="f.priority"
|
||
class="text-[10px] px-2 py-0.5 rounded bg-rose-50 dark:bg-rose-900/30 text-rose-600 dark:text-rose-300"
|
||
>Prioriteta</span
|
||
>
|
||
</div>
|
||
</li>
|
||
<li
|
||
v-if="!fieldJobsAssignedToday.length"
|
||
class="py-4 text-xs text-gray-500 text-center"
|
||
>
|
||
Ni dodelitev.
|
||
</li>
|
||
</ul>
|
||
<div v-else class="space-y-2 animate-pulse">
|
||
<div
|
||
v-for="n in 5"
|
||
:key="n"
|
||
class="h-5 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Imports In Progress -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
Uvozi v teku
|
||
</h3>
|
||
<ul
|
||
v-if="importsInProgress"
|
||
class="divide-y divide-gray-100 dark:divide-gray-700 text-sm"
|
||
>
|
||
<li v-for="im in importsInProgress" :key="im.id" class="py-2 space-y-1">
|
||
<div class="flex items-center justify-between">
|
||
<p class="font-medium text-gray-700 dark:text-gray-300 truncate">
|
||
{{ im.file_name }}
|
||
</p>
|
||
<span
|
||
class="text-[10px] px-2 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-300"
|
||
>{{ im.status }}</span
|
||
>
|
||
</div>
|
||
<div
|
||
class="w-full h-2 bg-gray-100 dark:bg-gray-700 rounded overflow-hidden"
|
||
>
|
||
<div
|
||
class="h-full bg-indigo-500 dark:bg-indigo-400"
|
||
:style="{ width: (im.progress_pct || 0) + '%' }"
|
||
></div>
|
||
</div>
|
||
<p class="text-[10px] text-gray-400 dark:text-gray-500">
|
||
{{ im.imported_rows }}/{{ im.total_rows }} (veljavnih:
|
||
{{ im.valid_rows }}, neveljavnih: {{ im.invalid_rows }})
|
||
</p>
|
||
</li>
|
||
<li
|
||
v-if="!importsInProgress.length"
|
||
class="py-4 text-xs text-gray-500 text-center"
|
||
>
|
||
Ni aktivnih uvozov.
|
||
</li>
|
||
</ul>
|
||
<div v-else class="space-y-2 animate-pulse">
|
||
<div
|
||
v-for="n in 4"
|
||
:key="n"
|
||
class="h-5 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Active Document Templates -->
|
||
<div
|
||
class="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl shadow-sm p-6"
|
||
>
|
||
<h3
|
||
class="text-sm font-semibold tracking-wide text-gray-700 dark:text-gray-200 uppercase mb-4"
|
||
>
|
||
Aktivne predloge dokumentov
|
||
</h3>
|
||
<ul
|
||
v-if="activeTemplates"
|
||
class="divide-y divide-gray-100 dark:divide-gray-700 text-sm"
|
||
>
|
||
<li
|
||
v-for="t in activeTemplates"
|
||
:key="t.id"
|
||
class="py-2 flex items-center justify-between"
|
||
>
|
||
<div class="min-w-0">
|
||
<p class="text-gray-700 dark:text-gray-300 font-medium truncate">
|
||
{{ t.name }}
|
||
</p>
|
||
<p class="text-[11px] text-gray-400 dark:text-gray-500">
|
||
v{{ t.version }} · {{ new Date(t.updated_at).toLocaleDateString() }}
|
||
</p>
|
||
</div>
|
||
<Link
|
||
:href="route('admin.document-templates.edit', t.id)"
|
||
class="text-[10px] px-2 py-0.5 rounded bg-indigo-50 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-800/60"
|
||
>Uredi</Link
|
||
>
|
||
</li>
|
||
<li
|
||
v-if="!activeTemplates.length"
|
||
class="py-4 text-xs text-gray-500 text-center"
|
||
>
|
||
Ni aktivnih predlog.
|
||
</li>
|
||
</ul>
|
||
<div v-else class="space-y-2 animate-pulse">
|
||
<div
|
||
v-for="n in 5"
|
||
:key="n"
|
||
class="h-5 bg-gray-100 dark:bg-gray-700 rounded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ...end of right side panels -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</AppLayout>
|
||
</template>
|