Major change update laravel, inertia v2 -> v3, other changes
This commit is contained in:
@@ -4,7 +4,7 @@ import { Card, CardContent } from "@/Components/ui/card";
|
||||
const props = defineProps({
|
||||
label: String,
|
||||
value: [String, Number],
|
||||
icon: Object,
|
||||
icon: [Object, Function],
|
||||
iconBg: {
|
||||
type: String,
|
||||
default: "bg-primary/10",
|
||||
|
||||
@@ -148,7 +148,7 @@ function formatDateTimeNoSeconds(value) {
|
||||
last_page: imports?.meta?.last_page,
|
||||
from: imports?.meta?.from,
|
||||
to: imports?.meta?.to,
|
||||
links: imports?.links,
|
||||
links: imports?.meta?.links,
|
||||
}"
|
||||
route-name="imports.index"
|
||||
:only-props="['imports']"
|
||||
|
||||
@@ -163,9 +163,7 @@ const props = defineProps({
|
||||
|
||||
<template>
|
||||
<AppLayout title="Uvozne predloge">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Uvozne predloge</h2>
|
||||
</template>
|
||||
<template #header> </template>
|
||||
|
||||
<div class="py-6">
|
||||
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||||
|
||||
@@ -56,9 +56,6 @@ import {
|
||||
Download,
|
||||
Eye,
|
||||
Building2,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Activity,
|
||||
} from "lucide-vue-next";
|
||||
|
||||
@@ -284,16 +281,11 @@ const clientSummary = computed(() => {
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="router.visit(route('phone.index'))"
|
||||
class="shrink-0"
|
||||
>
|
||||
<ArrowLeft class="w-4 h-4 mr-1" />
|
||||
<Button variant="outline" size="sm" @click="router.visit(route('phone.index'))">
|
||||
<ArrowLeft />
|
||||
Nazaj
|
||||
</Button>
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-100 truncate">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100 truncate">
|
||||
{{ client_case?.person?.full_name }}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -303,7 +295,7 @@ const clientSummary = computed(() => {
|
||||
variant="secondary"
|
||||
class="bg-emerald-100 text-emerald-700 hover:bg-emerald-100"
|
||||
>
|
||||
<CheckCircle2 class="w-3 h-3 mr-1" />
|
||||
<CheckCircle2 class="w-4 h-4" />
|
||||
Zaključeno danes
|
||||
</Badge>
|
||||
<Button
|
||||
@@ -311,25 +303,25 @@ const clientSummary = computed(() => {
|
||||
@click="confirmComplete = true"
|
||||
class="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
<CheckCircle2 class="w-4 h-4 mr-2" />
|
||||
<CheckCircle2 class="w-4 h-4" />
|
||||
Zaključi
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="py-4 sm:py-6">
|
||||
<div class="py-4 sm:py-2">
|
||||
<div class="mx-auto max-w-5xl px-2 sm:px-4 space-y-4">
|
||||
<!-- Client details (account holder) -->
|
||||
<Card class="gap-3">
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-3 gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<Building2 class="w-5 h-5 text-gray-500" />
|
||||
<span class="truncate">{{ clientSummary.name }}</span>
|
||||
<Badge variant="secondary">Naročnik</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent class="px-3">
|
||||
<Separator class="mb-4" />
|
||||
<PersonDetailPhone
|
||||
:types="types"
|
||||
@@ -340,8 +332,8 @@ const clientSummary = computed(() => {
|
||||
</Card>
|
||||
|
||||
<!-- Person (case person) -->
|
||||
<Card class="gap-3">
|
||||
<CardHeader class="px-3">
|
||||
<Card class="p-0 py-3 gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<User class="w-5 h-5 text-gray-500" />
|
||||
<span class="truncate">{{ client_case.person.full_name }}</span>
|
||||
@@ -353,6 +345,13 @@ const clientSummary = computed(() => {
|
||||
>
|
||||
{{ client_case.person.description }}
|
||||
</CardDescription>
|
||||
<p
|
||||
v-if="client_case?.person?.employer"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1 mt-1"
|
||||
>
|
||||
<Building2 class="w-3.5 h-3.5 shrink-0" />
|
||||
{{ client_case.person.employer }}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent class="px-3">
|
||||
<Separator class="mb-4" />
|
||||
@@ -365,21 +364,21 @@ const clientSummary = computed(() => {
|
||||
</Card>
|
||||
|
||||
<!-- Contracts assigned to me -->
|
||||
<Card class="p-0 pt-3 gap-1">
|
||||
<CardHeader class="px-4">
|
||||
<Card class="p-0 py-3 gap-1">
|
||||
<CardHeader class="px-3 py-2 pb-0">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="w-5 h-5" />
|
||||
Pogodbe
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="p-2">
|
||||
<CardContent class="p-2 space-y-1">
|
||||
<Card
|
||||
v-for="c in contracts"
|
||||
:key="c.uuid || c.id"
|
||||
class="overflow-hidden p-0 gap-3"
|
||||
class="overflow-hidden p-0 gap-2"
|
||||
>
|
||||
<!-- Contract header: reference + type badge -->
|
||||
<CardHeader class="p-3 pb-2">
|
||||
<CardHeader class="p-3 pb-2 gap-0">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<CardTitle class="text-base font-semibold">
|
||||
{{ c.reference || "Šifra pogodbe ni določena" }}
|
||||
@@ -408,12 +407,20 @@ const clientSummary = computed(() => {
|
||||
<!-- Collapsibles: description, meta, last object -->
|
||||
<CardContent
|
||||
v-if="
|
||||
c.description || c.last_object || (c.meta && Object.keys(c.meta).length)
|
||||
c.description ||
|
||||
c.latest_object ||
|
||||
(c.meta && Object.keys(c.meta).length)
|
||||
"
|
||||
class="pt-0 px-0 space-y-0"
|
||||
>
|
||||
<!-- Description + Meta Accordion -->
|
||||
<template v-if="c.description || (c.meta && Object.keys(c.meta).length)">
|
||||
<!-- Description + Meta + Latest Object Accordion -->
|
||||
<template
|
||||
v-if="
|
||||
c.description ||
|
||||
(c.meta && Object.keys(c.meta).length) ||
|
||||
c.latest_object
|
||||
"
|
||||
>
|
||||
<Separator />
|
||||
<Accordion type="multiple" class="w-full">
|
||||
<AccordionItem
|
||||
@@ -479,43 +486,48 @@ const clientSummary = computed(() => {
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</template>
|
||||
|
||||
<!-- Last object -->
|
||||
<template v-if="c.last_object">
|
||||
<Separator
|
||||
class="mb-3"
|
||||
:class="
|
||||
c.description || (c.meta && Object.keys(c.meta).length)
|
||||
? 'mt-2'
|
||||
: 'mt-0'
|
||||
"
|
||||
/>
|
||||
<div class="py-1 space-y-1">
|
||||
<p class="text-xs text-gray-400 uppercase tracking-wide font-medium">
|
||||
Zadnji predmet
|
||||
</p>
|
||||
<p class="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{{ c.last_object.name || c.last_object.reference }}
|
||||
<span
|
||||
v-if="c.last_object.type"
|
||||
class="ml-1.5 text-xs font-normal text-gray-400"
|
||||
>({{ c.last_object.type }})</span
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
v-if="c.last_object.description"
|
||||
class="text-xs text-gray-500 dark:text-gray-400"
|
||||
<AccordionItem
|
||||
v-if="c.latest_object"
|
||||
value="latest_object"
|
||||
class="border-b-0"
|
||||
:class="
|
||||
c.description || (c.meta && Object.keys(c.meta).length)
|
||||
? 'border-t'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ c.last_object.description }}
|
||||
</p>
|
||||
</div>
|
||||
<AccordionTrigger
|
||||
class="px-3 py-2 text-xs font-medium uppercase tracking-wide hover:no-underline"
|
||||
>
|
||||
Zadnji predmet
|
||||
</AccordionTrigger>
|
||||
<AccordionContent class="px-3 pb-3">
|
||||
<div
|
||||
class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line rounded-lg bg-gray-50 dark:bg-gray-800/50 px-3 py-2.5"
|
||||
>
|
||||
<p class="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{{ c.latest_object.name || c.latest_object.reference }}
|
||||
<span
|
||||
v-if="c.latest_object.type"
|
||||
class="ml-1.5 text-xs font-normal text-gray-400"
|
||||
>({{ c.latest_object.type }})</span
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
v-if="c.latest_object.description"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1"
|
||||
>
|
||||
{{ c.latest_object.description }}
|
||||
</p>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</template>
|
||||
</CardContent>
|
||||
|
||||
<!-- Action buttons: full-width row at bottom -->
|
||||
<div class="grid grid-cols-2 gap-0 border-t mt-1">
|
||||
<div class="grid grid-cols-2 gap-0 border-t mt-0">
|
||||
<button
|
||||
class="flex items-center justify-center gap-2 py-3 text-sm font-medium text-primary hover:bg-primary/5 active:bg-primary/10 transition-colors border-r"
|
||||
@click="openDrawerAddActivity(c)"
|
||||
@@ -542,27 +554,27 @@ const clientSummary = computed(() => {
|
||||
</Card>
|
||||
|
||||
<!-- Activities -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-2 gap-2">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Activity class="w-5 h-5" />
|
||||
Aktivnosti
|
||||
</CardTitle>
|
||||
<Button size="sm" @click="openDrawerAddActivity()">
|
||||
<Plus class="w-4 h-4 mr-1" />
|
||||
<Plus class="w-4 h-4" />
|
||||
Nova
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<CardContent class="space-y-1 px-2">
|
||||
<Card
|
||||
v-for="a in activities"
|
||||
:key="a.id"
|
||||
class="bg-gray-50/70 dark:bg-gray-800/50"
|
||||
class="bg-gray-50/70 dark:bg-gray-800/50 p-0 py-2 gap-2"
|
||||
>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-start justify-between">
|
||||
<CardTitle class="text-sm font-medium truncate">
|
||||
{{ activityActionLine(a) || "Aktivnost" }}
|
||||
</CardTitle>
|
||||
@@ -583,7 +595,7 @@ const clientSummary = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="pt-0 space-y-2">
|
||||
<CardContent class="p-2 pt-0 space-y-2">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Badge v-if="a.contract" variant="secondary" class="text-[10px]">
|
||||
<FileText class="w-3 h-3 mr-1" />
|
||||
@@ -609,7 +621,10 @@ const clientSummary = computed(() => {
|
||||
{{ a.status }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p v-if="a.note" class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<p
|
||||
v-if="a.note"
|
||||
class="text-sm text-gray-900 dark:text-gray-300 whitespace-pre-line rounded-lg bg-secondary dark:bg-gray-800/50 p-2"
|
||||
>
|
||||
{{ a.note }}
|
||||
</p>
|
||||
</CardContent>
|
||||
@@ -624,8 +639,8 @@ const clientSummary = computed(() => {
|
||||
</Card>
|
||||
|
||||
<!-- Documents (case + assigned contracts) -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-2 gap-2">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="w-5 h-5" />
|
||||
@@ -649,7 +664,7 @@ const clientSummary = computed(() => {
|
||||
{{ d.name || d.original_name }}
|
||||
</div>
|
||||
<div
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2 flex-wrap"
|
||||
>
|
||||
<Badge
|
||||
v-if="d.contract_reference"
|
||||
@@ -659,6 +674,11 @@ const clientSummary = computed(() => {
|
||||
Pogodba: {{ d.contract_reference }}
|
||||
</Badge>
|
||||
<Badge v-else variant="outline" class="text-[10px]"> Primer </Badge>
|
||||
<span
|
||||
v-if="d.mime_type"
|
||||
class="text-[10px] text-gray-400 font-mono"
|
||||
>{{ d.mime_type }}</span
|
||||
>
|
||||
<span v-if="d.created_at" class="flex items-center gap-1">
|
||||
<Calendar class="w-3 h-3" />
|
||||
{{ new Date(d.created_at).toLocaleDateString("sl-SI") }}
|
||||
|
||||
@@ -12,8 +12,16 @@ import {
|
||||
} from "@/Components/ui/select";
|
||||
import { Skeleton } from "@/Components/ui/skeleton";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { InfiniteScroll, router } from "@inertiajs/vue3";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
h,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import {
|
||||
CalendarDays,
|
||||
@@ -67,8 +75,10 @@ function performFilter() {
|
||||
preserveState: true,
|
||||
preserveScroll: false,
|
||||
only,
|
||||
reset: isCompleted.value
|
||||
? ["completedJobs"]
|
||||
: ["pendingJobs", "processedJobs"],
|
||||
onSuccess: () => {
|
||||
resetLists();
|
||||
isFiltering.value = false;
|
||||
},
|
||||
onError: () => {
|
||||
@@ -83,92 +93,6 @@ function clearFilters() {
|
||||
clientFilter.value = "all";
|
||||
}
|
||||
|
||||
// ── Infinite scroll lists ────────────────────────────────────────────────────
|
||||
const pendingList = ref(props.pendingJobs?.data ?? []);
|
||||
const processedList = ref(props.processedJobs?.data ?? []);
|
||||
const completedList = ref(props.completedJobs?.data ?? []);
|
||||
|
||||
const pendingPage = ref(props.pendingJobs?.current_page ?? 1);
|
||||
const processedPage = ref(props.processedJobs?.current_page ?? 1);
|
||||
const completedPage = ref(props.completedJobs?.current_page ?? 1);
|
||||
|
||||
const pendingLastPage = ref(props.pendingJobs?.last_page ?? 1);
|
||||
const processedLastPage = ref(props.processedJobs?.last_page ?? 1);
|
||||
const completedLastPage = ref(props.completedJobs?.last_page ?? 1);
|
||||
|
||||
const loadingPending = ref(false);
|
||||
const loadingProcessed = ref(false);
|
||||
const loadingCompleted = ref(false);
|
||||
|
||||
const pendingSentinel = ref(null);
|
||||
const processedSentinel = ref(null);
|
||||
const completedSentinel = ref(null);
|
||||
|
||||
function resetLists() {
|
||||
pendingList.value = props.pendingJobs?.data ?? [];
|
||||
processedList.value = props.processedJobs?.data ?? [];
|
||||
completedList.value = props.completedJobs?.data ?? [];
|
||||
pendingPage.value = props.pendingJobs?.current_page ?? 1;
|
||||
processedPage.value = props.processedJobs?.current_page ?? 1;
|
||||
completedPage.value = props.completedJobs?.current_page ?? 1;
|
||||
pendingLastPage.value = props.pendingJobs?.last_page ?? 1;
|
||||
processedLastPage.value = props.processedJobs?.last_page ?? 1;
|
||||
completedLastPage.value = props.completedJobs?.last_page ?? 1;
|
||||
clientFilter.value = props.filters.client || "all";
|
||||
}
|
||||
|
||||
function appendUnique(list, newItems) {
|
||||
const ids = new Set(list.value.map((i) => i.id));
|
||||
list.value.push(...newItems.filter((i) => !ids.has(i.id)));
|
||||
}
|
||||
|
||||
function buildPageUrl(pageParam, pageNum) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set(pageParam, pageNum);
|
||||
return `${window.location.pathname}?${params.toString()}`;
|
||||
}
|
||||
|
||||
function loadMore(listRef, pageRef, lastPageRef, loadingRef, propKey, pageParam) {
|
||||
if (loadingRef.value) return;
|
||||
if (pageRef.value >= lastPageRef.value) return;
|
||||
|
||||
const nextPage = pageRef.value + 1;
|
||||
loadingRef.value = true;
|
||||
|
||||
router.get(
|
||||
buildPageUrl(pageParam, nextPage),
|
||||
{},
|
||||
{
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
only: [propKey],
|
||||
onSuccess: () => {
|
||||
const newData = props[propKey]?.data ?? [];
|
||||
appendUnique(listRef, newData);
|
||||
pageRef.value = nextPage;
|
||||
lastPageRef.value = props[propKey]?.last_page ?? lastPageRef.value;
|
||||
loadingRef.value = false;
|
||||
},
|
||||
onError: () => {
|
||||
loadingRef.value = false;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function makeObserver(sentinelRef, loadFn) {
|
||||
const obs = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) loadFn();
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
if (sentinelRef.value) obs.observe(sentinelRef.value);
|
||||
return obs;
|
||||
}
|
||||
|
||||
let observers = [];
|
||||
|
||||
// ── Scroll-hide title ────────────────────────────────────────────────────────
|
||||
const scrolled = ref(false);
|
||||
let stopNavigateListener = null;
|
||||
@@ -189,43 +113,10 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
observers.push(
|
||||
makeObserver(pendingSentinel, () =>
|
||||
loadMore(
|
||||
pendingList,
|
||||
pendingPage,
|
||||
pendingLastPage,
|
||||
loadingPending,
|
||||
"pendingJobs",
|
||||
"pending"
|
||||
)
|
||||
),
|
||||
makeObserver(processedSentinel, () =>
|
||||
loadMore(
|
||||
processedList,
|
||||
processedPage,
|
||||
processedLastPage,
|
||||
loadingProcessed,
|
||||
"processedJobs",
|
||||
"processed"
|
||||
)
|
||||
),
|
||||
makeObserver(completedSentinel, () =>
|
||||
loadMore(
|
||||
completedList,
|
||||
completedPage,
|
||||
completedLastPage,
|
||||
loadingCompleted,
|
||||
"completedJobs",
|
||||
"completed"
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (stopNavigateListener) stopNavigateListener();
|
||||
observers.forEach((o) => o.disconnect());
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
});
|
||||
|
||||
@@ -531,47 +422,91 @@ const JobCard = defineComponent({
|
||||
|
||||
<!-- Pending tab -->
|
||||
<TabsContent value="pending" class="space-y-3">
|
||||
<template v-if="pendingList.length">
|
||||
<JobCard
|
||||
v-for="job in pendingList"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-blue-500"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingPending"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="pendingSentinel" class="h-px" />
|
||||
<div v-if="loadingPending" class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
<InfiniteScroll data="pendingJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.pendingJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in props.pendingJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-blue-500"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</TabsContent>
|
||||
|
||||
<!-- Processed tab -->
|
||||
<TabsContent value="processed" class="space-y-3">
|
||||
<template v-if="processedList.length">
|
||||
<InfiniteScroll data="processedJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.processedJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in props.processedJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-green-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Ni obdelanih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Completed-today mode: single scroll list -->
|
||||
<div v-else class="px-4 pt-4 space-y-3">
|
||||
<InfiniteScroll data="completedJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.completedJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in processedList"
|
||||
v-for="job in props.completedJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-green-500"
|
||||
accent-class="border-l-purple-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingProcessed"
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
@@ -579,49 +514,17 @@ const JobCard = defineComponent({
|
||||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Ni obdelanih opravil"
|
||||
: "Danes ni zaključenih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="processedSentinel" class="h-px" />
|
||||
<div v-if="loadingProcessed" class="space-y-3">
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Completed-today mode: single scroll list -->
|
||||
<div v-else class="px-4 pt-4 space-y-3">
|
||||
<template v-if="completedList.length">
|
||||
<JobCard
|
||||
v-for="job in completedList"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-purple-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingCompleted"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Danes ni zaključenih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="completedSentinel" class="h-px" />
|
||||
<div v-if="loadingCompleted" class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
</AppPhoneLayout>
|
||||
|
||||
Reference in New Issue
Block a user