Changes to phone view, fixed infinity scroll issues with page refresh, updated design a bit
This commit is contained in:
parent
8f8c5c5a12
commit
92f54f7103
|
|
@ -17,6 +17,11 @@ public function index(Request $request): \Inertia\Response
|
|||
$search = $request->input('search');
|
||||
$clientFilter = $request->input('client');
|
||||
|
||||
// On full page loads, always start from page 1
|
||||
if (! $request->header('X-Inertia-Partial-Data')) {
|
||||
$request->merge(['pending' => 1, 'processed' => 1]);
|
||||
}
|
||||
|
||||
$eagerLoad = [
|
||||
'contract' => function ($q) {
|
||||
$q->with([
|
||||
|
|
@ -97,6 +102,11 @@ public function completedToday(Request $request): \Inertia\Response
|
|||
$search = $request->input('search');
|
||||
$clientFilter = $request->input('client');
|
||||
|
||||
// On full page loads, always start from page 1
|
||||
if (! $request->header('X-Inertia-Partial-Data')) {
|
||||
$request->merge(['completed' => 1]);
|
||||
}
|
||||
|
||||
$start = now()->startOfDay();
|
||||
$end = now()->endOfDay();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from "@/Components/ui/select";
|
||||
import { Switch } from "@/Components/ui/switch";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { ScrollArea } from "@/Components/ui/scroll-area";
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
|
|
@ -452,7 +453,8 @@ const open = computed({
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<ScrollArea class="max-h-[65vh] pr-1">
|
||||
<form @submit.prevent="onSubmit" class="space-y-4 pr-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField v-slot="{ value, handleChange }" name="profile_id">
|
||||
<FormItem>
|
||||
|
|
@ -582,8 +584,8 @@ const open = computed({
|
|||
</span>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-500 leading-snug">
|
||||
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo znake, ki
|
||||
ne zahtevajo enkodiranja. Če npr. želite pošiljati šumnike, ki niso del
|
||||
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo znake,
|
||||
ki ne zahtevajo enkodiranja. Če npr. želite pošiljati šumnike, ki niso del
|
||||
7-bitne abecede GSM, morate uporabiti Unicode enkodiranje (UCS‑2). V tem
|
||||
primeru je največja dolžina enega SMS sporočila 70 znakov (pri daljših
|
||||
sporočilih 67 znakov na del), medtem ko je pri GSM‑7 160 znakov (pri daljših
|
||||
|
|
@ -604,6 +606,7 @@ const open = computed({
|
|||
</FormItem>
|
||||
</FormField>
|
||||
</form>
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="closeSmsDialog" :disabled="processing">
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ const closeSearch = () => (searchOpen.value = false);
|
|||
</div>
|
||||
|
||||
<!-- Page Heading -->
|
||||
<header v-if="$slots.header" class="bg-white border-b border-gray-200 shadow-sm">
|
||||
<header v-if="$slots.header" class="sticky top-16 z-20 bg-white border-b border-gray-200 shadow-sm dark:bg-gray-900 dark:border-gray-700">
|
||||
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8 space-y-2">
|
||||
<Breadcrumbs
|
||||
v-if="$page.props.breadcrumbs && $page.props.breadcrumbs.length"
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ import { Button } from "@/Components/ui/button";
|
|||
import { Badge } from "@/Components/ui/badge";
|
||||
import { Separator } from "@/Components/ui/separator";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/Components/ui/collapsible";
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/Components/ui/accordion";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -46,7 +47,6 @@ import { Checkbox } from "@/Components/ui/checkbox";
|
|||
import {
|
||||
ArrowLeft,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
FileText,
|
||||
Calendar,
|
||||
Euro,
|
||||
|
|
@ -321,7 +321,7 @@ const clientSummary = computed(() => {
|
|||
<div class="py-4 sm:py-6">
|
||||
<div class="mx-auto max-w-5xl px-2 sm:px-4 space-y-4">
|
||||
<!-- Client details (account holder) -->
|
||||
<Card>
|
||||
<Card class="gap-3">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<Building2 class="w-5 h-5 text-gray-500" />
|
||||
|
|
@ -340,8 +340,8 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Person (case person) -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="gap-3">
|
||||
<CardHeader class="px-3">
|
||||
<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>
|
||||
|
|
@ -354,7 +354,7 @@ const clientSummary = computed(() => {
|
|||
{{ client_case.person.description }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent class="px-3">
|
||||
<Separator class="mb-4" />
|
||||
<PersonDetailPhone
|
||||
:types="types"
|
||||
|
|
@ -365,35 +365,32 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Contracts assigned to me -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="p-0 pt-3 gap-1">
|
||||
<CardHeader class="px-4">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="w-5 h-5" />
|
||||
Pogodbe
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<CardContent class="p-2">
|
||||
<Card
|
||||
v-for="c in contracts"
|
||||
:key="c.uuid || c.id"
|
||||
class="border-l-4 border-l-indigo-500 overflow-hidden"
|
||||
class="overflow-hidden p-0 gap-3"
|
||||
>
|
||||
<!-- Contract header: reference + type badge -->
|
||||
<CardHeader class="pb-2">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<CardHeader class="p-3 pb-2">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<CardTitle class="text-base font-semibold">
|
||||
{{ c.reference || c.uuid }}
|
||||
{{ c.reference || "Šifra pogodbe ni določena" }}
|
||||
</CardTitle>
|
||||
<Badge v-if="c.type?.name" variant="secondary" class="text-[11px]">
|
||||
{{ c.type.name }}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<!-- Balance row -->
|
||||
<div
|
||||
v-if="c.account"
|
||||
class="mx-4 mb-3 rounded-xl bg-red-50 dark:bg-red-950/20 border border-red-100 dark:border-red-900 px-4 py-3 flex items-center justify-between"
|
||||
class="mx-3 rounded-xl bg-red-50 dark:bg-red-950/20 border border-red-100 dark:border-red-900 px-2 py-2 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-red-500">
|
||||
<Euro class="w-4 h-4 shrink-0" />
|
||||
|
|
@ -413,50 +410,50 @@ const clientSummary = computed(() => {
|
|||
v-if="
|
||||
c.description || c.last_object || (c.meta && Object.keys(c.meta).length)
|
||||
"
|
||||
class="pt-0 px-4 space-y-0"
|
||||
class="pt-0 px-0 space-y-0"
|
||||
>
|
||||
<!-- Description -->
|
||||
<template v-if="c.description">
|
||||
<Separator class="mb-3" />
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger
|
||||
class="flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 group w-full py-1"
|
||||
<!-- Description + Meta Accordion -->
|
||||
<template v-if="c.description || (c.meta && Object.keys(c.meta).length)">
|
||||
<Separator />
|
||||
<Accordion type="multiple" class="w-full">
|
||||
<AccordionItem
|
||||
v-if="c.description"
|
||||
value="description"
|
||||
class="border-b-0"
|
||||
>
|
||||
<ChevronDown
|
||||
class="w-3.5 h-3.5 transition-transform duration-200 group-data-[state=open]:rotate-180 shrink-0"
|
||||
/>
|
||||
<span class="uppercase tracking-wide font-medium">Opis</span>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<AccordionTrigger
|
||||
class="px-3 py-2 text-xs font-medium uppercase tracking-wide hover:no-underline"
|
||||
>
|
||||
Opis
|
||||
</AccordionTrigger>
|
||||
<AccordionContent class="px-3 pb-3">
|
||||
<p
|
||||
class="mt-1.5 mb-2 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"
|
||||
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"
|
||||
>
|
||||
{{ c.description }}
|
||||
</p>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</template>
|
||||
|
||||
<!-- Meta -->
|
||||
<template v-if="c.meta && Object.keys(c.meta).length">
|
||||
<Separator class="mb-3" :class="c.description ? 'mt-2' : 'mt-0'" />
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger
|
||||
class="flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 group w-full py-1"
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
v-if="c.meta && Object.keys(c.meta).length"
|
||||
value="meta"
|
||||
class="border-b-0"
|
||||
:class="c.description ? 'border-t' : ''"
|
||||
>
|
||||
<ChevronDown
|
||||
class="w-3.5 h-3.5 transition-transform duration-200 group-data-[state=open]:rotate-180 shrink-0"
|
||||
/>
|
||||
<span class="uppercase tracking-wide font-medium"
|
||||
>Dodatni podatki</span
|
||||
<AccordionTrigger
|
||||
class="px-3 py-2 text-xs font-medium uppercase tracking-wide hover:no-underline"
|
||||
>
|
||||
<span class="ml-auto text-gray-400 font-normal">{{
|
||||
Object.keys(c.meta).length
|
||||
}}</span>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div>
|
||||
<span class="mr-1">Dodatni podatki</span>
|
||||
<Badge
|
||||
class="bg-blue-500 text-white dark:bg-blue-600 h-5 min-w-5 rounded-full px-2 font-mono tabular-nums"
|
||||
>{{ Object.keys(c.meta).length }}</Badge
|
||||
>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent class="pb-2">
|
||||
<div
|
||||
class="mt-1.5 mb-2 divide-y divide-gray-100 dark:divide-gray-700 rounded-lg border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
class="divide-y divide-gray-100 dark:divide-gray-700 rounded-lg border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div
|
||||
v-for="(val, key) in c.meta"
|
||||
|
|
@ -480,8 +477,9 @@ const clientSummary = computed(() => {
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</template>
|
||||
|
||||
<!-- Last object -->
|
||||
|
|
|
|||
|
|
@ -11,22 +11,9 @@ import {
|
|||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
import { Skeleton } from "@/Components/ui/skeleton";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/Components/ui/tabs";
|
||||
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 { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import {
|
||||
CalendarDays,
|
||||
|
|
@ -55,7 +42,9 @@ const isCompleted = computed(() => props.view_mode === "completed-today");
|
|||
const search = ref(props.filters.search || "");
|
||||
const clientFilter = ref(props.filters.client || "all");
|
||||
const isFiltering = ref(false);
|
||||
const showFilters = ref(!!(props.filters.search || (props.filters.client && props.filters.client !== "all")));
|
||||
const showFilters = ref(
|
||||
!!(props.filters.search || (props.filters.client && props.filters.client !== "all"))
|
||||
);
|
||||
|
||||
const debouncedFilter = useDebounceFn(() => performFilter(), 500);
|
||||
watch(search, () => debouncedFilter());
|
||||
|
|
@ -85,7 +74,7 @@ function performFilter() {
|
|||
onError: () => {
|
||||
isFiltering.value = false;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -157,14 +146,13 @@ function loadMore(listRef, pageRef, lastPageRef, loadingRef, propKey, pageParam)
|
|||
const newData = props[propKey]?.data ?? [];
|
||||
appendUnique(listRef, newData);
|
||||
pageRef.value = nextPage;
|
||||
lastPageRef.value =
|
||||
props[propKey]?.last_page ?? lastPageRef.value;
|
||||
lastPageRef.value = props[propKey]?.last_page ?? lastPageRef.value;
|
||||
loadingRef.value = false;
|
||||
},
|
||||
onError: () => {
|
||||
loadingRef.value = false;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +161,7 @@ function makeObserver(sentinelRef, loadFn) {
|
|||
(entries) => {
|
||||
if (entries[0].isIntersecting) loadFn();
|
||||
},
|
||||
{ rootMargin: "200px" },
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
if (sentinelRef.value) obs.observe(sentinelRef.value);
|
||||
return obs;
|
||||
|
|
@ -181,7 +169,26 @@ function makeObserver(sentinelRef, loadFn) {
|
|||
|
||||
let observers = [];
|
||||
|
||||
// ── Scroll-hide title ────────────────────────────────────────────────────────
|
||||
const scrolled = ref(false);
|
||||
let stopNavigateListener = null;
|
||||
function onScroll() {
|
||||
if (!scrolled.value && window.scrollY > 50) {
|
||||
scrolled.value = true;
|
||||
} else if (scrolled.value && window.scrollY < 10) {
|
||||
scrolled.value = false;
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
let trackedPath = window.location.pathname;
|
||||
stopNavigateListener = router.on("navigate", (event) => {
|
||||
const newPath = new URL(event.detail.page.url, window.location.origin).pathname;
|
||||
if (newPath !== trackedPath) {
|
||||
scrolled.value = false;
|
||||
trackedPath = newPath;
|
||||
}
|
||||
});
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
observers.push(
|
||||
makeObserver(pendingSentinel, () =>
|
||||
loadMore(
|
||||
|
|
@ -190,8 +197,8 @@ onMounted(() => {
|
|||
pendingLastPage,
|
||||
loadingPending,
|
||||
"pendingJobs",
|
||||
"pending",
|
||||
),
|
||||
"pending"
|
||||
)
|
||||
),
|
||||
makeObserver(processedSentinel, () =>
|
||||
loadMore(
|
||||
|
|
@ -200,8 +207,8 @@ onMounted(() => {
|
|||
processedLastPage,
|
||||
loadingProcessed,
|
||||
"processedJobs",
|
||||
"processed",
|
||||
),
|
||||
"processed"
|
||||
)
|
||||
),
|
||||
makeObserver(completedSentinel, () =>
|
||||
loadMore(
|
||||
|
|
@ -210,14 +217,16 @@ onMounted(() => {
|
|||
completedLastPage,
|
||||
loadingCompleted,
|
||||
"completedJobs",
|
||||
"completed",
|
||||
),
|
||||
),
|
||||
"completed"
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (stopNavigateListener) stopNavigateListener();
|
||||
observers.forEach((o) => o.disconnect());
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
});
|
||||
|
||||
// ── Counts ───────────────────────────────────────────────────────────────────
|
||||
|
|
@ -237,10 +246,7 @@ function formatAmount(val) {
|
|||
|
||||
function getCaseUuid(job) {
|
||||
return (
|
||||
job?.contract?.client_case?.uuid ||
|
||||
job?.client_case?.uuid ||
|
||||
job?.case_uuid ||
|
||||
null
|
||||
job?.contract?.client_case?.uuid || job?.client_case?.uuid || job?.case_uuid || null
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -266,8 +272,7 @@ const JobCard = defineComponent({
|
|||
return () => {
|
||||
const j = p.job;
|
||||
const person = j.contract?.client_case?.person;
|
||||
const clientName =
|
||||
j.contract?.client_case?.client?.person?.full_name;
|
||||
const clientName = j.contract?.client_case?.client?.person?.full_name;
|
||||
const address = person?.address?.address;
|
||||
const phone = person?.phones?.[0]?.nu;
|
||||
const balance = j.contract?.account?.balance_amount;
|
||||
|
|
@ -283,26 +288,26 @@ const JobCard = defineComponent({
|
|||
h(
|
||||
"p",
|
||||
{
|
||||
class: "font-bold text-base text-gray-900 dark:text-gray-100 truncate leading-tight",
|
||||
class:
|
||||
"font-bold text-base text-gray-900 dark:text-gray-100 truncate leading-tight",
|
||||
},
|
||||
person?.full_name || "—",
|
||||
person?.full_name || "—"
|
||||
),
|
||||
h(
|
||||
"p",
|
||||
{
|
||||
class: "text-xs text-gray-500 dark:text-gray-400 mt-0.5 truncate",
|
||||
},
|
||||
j.contract?.reference ||
|
||||
j.contract?.uuid ||
|
||||
"—",
|
||||
j.contract?.reference || j.contract?.uuid || "—"
|
||||
),
|
||||
clientName
|
||||
? h(
|
||||
"p",
|
||||
{
|
||||
class: "text-xs text-indigo-600 dark:text-indigo-400 mt-0.5 truncate",
|
||||
class:
|
||||
"text-xs text-indigo-600 dark:text-indigo-400 mt-0.5 truncate",
|
||||
},
|
||||
clientName,
|
||||
clientName
|
||||
)
|
||||
: null,
|
||||
]),
|
||||
|
|
@ -310,12 +315,13 @@ const JobCard = defineComponent({
|
|||
? h(
|
||||
"span",
|
||||
{
|
||||
class: "shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400 animate-pulse",
|
||||
class:
|
||||
"shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400 animate-pulse",
|
||||
},
|
||||
"Prioriteta",
|
||||
"Prioriteta"
|
||||
)
|
||||
: null,
|
||||
],
|
||||
]
|
||||
),
|
||||
address || phone
|
||||
? h("div", { class: "px-4 pb-3 space-y-1.5" }, [
|
||||
|
|
@ -329,12 +335,8 @@ const JobCard = defineComponent({
|
|||
h(MapPin, {
|
||||
class: "w-3.5 h-3.5 shrink-0 text-gray-400",
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ class: "text-xs truncate" },
|
||||
address,
|
||||
),
|
||||
],
|
||||
h("span", { class: "text-xs truncate" }, address),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
phone
|
||||
|
|
@ -347,12 +349,8 @@ const JobCard = defineComponent({
|
|||
h(Phone, {
|
||||
class: "w-3.5 h-3.5 shrink-0 text-gray-400",
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ class: "text-xs font-medium" },
|
||||
phone,
|
||||
),
|
||||
],
|
||||
h("span", { class: "text-xs font-medium" }, phone),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
])
|
||||
|
|
@ -361,7 +359,8 @@ const JobCard = defineComponent({
|
|||
? h(
|
||||
"div",
|
||||
{
|
||||
class: "mx-4 mb-3 px-3 py-2 bg-red-50 dark:bg-red-950/20 rounded-lg flex items-center gap-2 border border-red-100 dark:border-red-900",
|
||||
class:
|
||||
"mx-4 mb-3 px-3 py-2 bg-red-50 dark:bg-red-950/20 rounded-lg flex items-center gap-2 border border-red-100 dark:border-red-900",
|
||||
},
|
||||
[
|
||||
h(Wallet, {
|
||||
|
|
@ -372,20 +371,17 @@ const JobCard = defineComponent({
|
|||
{
|
||||
class: "font-bold text-red-600 dark:text-red-400 text-sm",
|
||||
},
|
||||
`${formatAmount(balance)} €`,
|
||||
`${formatAmount(balance)} €`
|
||||
),
|
||||
h(
|
||||
"span",
|
||||
{ class: "text-xs text-red-400" },
|
||||
"odprto",
|
||||
),
|
||||
],
|
||||
h("span", { class: "text-xs text-red-400" }, "odprto"),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class: "px-4 py-3 border-t bg-gray-50/60 dark:bg-gray-900/40 flex items-center justify-between",
|
||||
class:
|
||||
"px-4 py-3 border-t bg-gray-50/60 dark:bg-gray-900/40 flex items-center justify-between",
|
||||
},
|
||||
[
|
||||
h(
|
||||
|
|
@ -400,9 +396,9 @@ const JobCard = defineComponent({
|
|||
{},
|
||||
p.showLastActivity && j.last_activity
|
||||
? fmtDateDMY(j.last_activity)
|
||||
: fmtDateDMY(j.assigned_at),
|
||||
: fmtDateDMY(j.assigned_at)
|
||||
),
|
||||
],
|
||||
]
|
||||
),
|
||||
p.href
|
||||
? h(
|
||||
|
|
@ -410,17 +406,10 @@ const JobCard = defineComponent({
|
|||
{
|
||||
class: "flex items-center gap-0.5 text-primary font-semibold text-sm",
|
||||
},
|
||||
[
|
||||
"Odpri",
|
||||
h(ChevronRight, { class: "w-4 h-4" }),
|
||||
],
|
||||
["Odpri", h(ChevronRight, { class: "w-4 h-4" })]
|
||||
)
|
||||
: h(
|
||||
"span",
|
||||
{ class: "text-xs text-gray-400 italic" },
|
||||
"Manjka primer",
|
||||
),
|
||||
],
|
||||
: h("span", { class: "text-xs text-gray-400 italic" }, "Manjka primer"),
|
||||
]
|
||||
),
|
||||
]);
|
||||
|
||||
|
|
@ -429,16 +418,18 @@ const JobCard = defineComponent({
|
|||
"a",
|
||||
{
|
||||
href: p.href,
|
||||
class: "block rounded-xl border shadow-sm overflow-hidden bg-white dark:bg-card active:scale-[0.99] transition-transform duration-100",
|
||||
class:
|
||||
"block rounded-xl border shadow-sm overflow-hidden bg-white dark:bg-card active:scale-[0.99] transition-transform duration-100",
|
||||
},
|
||||
inner,
|
||||
inner
|
||||
)
|
||||
: h(
|
||||
"div",
|
||||
{
|
||||
class: "rounded-xl border shadow-sm overflow-hidden bg-white dark:bg-card opacity-60",
|
||||
class:
|
||||
"rounded-xl border shadow-sm overflow-hidden bg-white dark:bg-card opacity-60",
|
||||
},
|
||||
inner,
|
||||
inner
|
||||
);
|
||||
};
|
||||
},
|
||||
|
|
@ -449,17 +440,14 @@ const JobCard = defineComponent({
|
|||
<AppPhoneLayout title="Phone">
|
||||
<template #header>
|
||||
<h2
|
||||
class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"
|
||||
class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight overflow-hidden transition-all duration-200 ease-in-out"
|
||||
:class="scrolled ? 'max-h-0 opacity-0 mb-0' : 'max-h-12 opacity-100 mb-0'"
|
||||
>
|
||||
{{ isCompleted ? "Zaključeno danes" : "Terenska opravila" }}
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<div class="pb-8">
|
||||
<!-- Filter bar -->
|
||||
<div
|
||||
class="sticky top-16 z-20 bg-white/95 dark:bg-gray-950/95 backdrop-blur border-b px-4 py-3 space-y-3"
|
||||
>
|
||||
<div class="pt-2 space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1">
|
||||
<Input
|
||||
|
|
@ -478,9 +466,7 @@ const JobCard = defineComponent({
|
|||
size="icon"
|
||||
class="shrink-0"
|
||||
:class="
|
||||
showFilters || clientFilter !== 'all'
|
||||
? 'bg-primary/10 border-primary'
|
||||
: ''
|
||||
showFilters || clientFilter !== 'all' ? 'bg-primary/10 border-primary' : ''
|
||||
"
|
||||
title="Filter po naročniku"
|
||||
@click="showFilters = !showFilters"
|
||||
|
|
@ -498,7 +484,7 @@ const JobCard = defineComponent({
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="showFilters || clientFilter !== 'all'" class="pb-1">
|
||||
<div v-if="showFilters || clientFilter !== 'all'">
|
||||
<Select v-model="clientFilter">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue placeholder="Vsi naročniki" />
|
||||
|
|
@ -516,32 +502,30 @@ const JobCard = defineComponent({
|
|||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="pb-8">
|
||||
<!-- Assigned mode: two tabs (Novo / Obdelano) -->
|
||||
<div v-if="!isCompleted" class="px-4 pt-4">
|
||||
<Tabs default-value="pending" class="w-full">
|
||||
<TabsList class="w-full grid grid-cols-2 mb-4">
|
||||
<TabsTrigger value="pending" class="gap-1.5">
|
||||
<ClipboardList class="w-4 h-4" />
|
||||
<TabsTrigger value="pending">
|
||||
<span class="inline-flex flex-row items-center gap-1">
|
||||
<ClipboardList class="w-3.5 h-3.5 shrink-0" />
|
||||
Novo
|
||||
<Badge
|
||||
v-if="pendingCount"
|
||||
variant="secondary"
|
||||
class="ml-1 h-5 px-1.5 text-xs"
|
||||
>
|
||||
<Badge v-if="pendingCount" variant="secondary" class="h-4 px-1 text-xs">
|
||||
{{ pendingCount }}
|
||||
</Badge>
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="processed" class="gap-1.5">
|
||||
<CheckCircle2 class="w-4 h-4" />
|
||||
<TabsTrigger value="processed">
|
||||
<span class="inline-flex flex-row items-center gap-1">
|
||||
<CheckCircle2 class="w-3.5 h-3.5 shrink-0" />
|
||||
Obdelano
|
||||
<Badge
|
||||
v-if="processedCount"
|
||||
variant="secondary"
|
||||
class="ml-1 h-5 px-1.5 text-xs"
|
||||
>
|
||||
<Badge v-if="processedCount" variant="secondary" class="h-4 px-1 text-xs">
|
||||
{{ processedCount }}
|
||||
</Badge>
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
|
@ -560,25 +544,17 @@ const JobCard = defineComponent({
|
|||
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"
|
||||
/>
|
||||
<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"
|
||||
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"
|
||||
/>
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
|
@ -598,12 +574,10 @@ const JobCard = defineComponent({
|
|||
v-else-if="!loadingProcessed"
|
||||
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"
|
||||
/>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== 'all'
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Ni obdelanih opravil"
|
||||
}}
|
||||
|
|
@ -612,11 +586,7 @@ const JobCard = defineComponent({
|
|||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="processedSentinel" class="h-px" />
|
||||
<div v-if="loadingProcessed" class="space-y-3">
|
||||
<Skeleton
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="h-36 rounded-xl"
|
||||
/>
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
@ -638,12 +608,10 @@ const JobCard = defineComponent({
|
|||
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"
|
||||
/>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== 'all'
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Danes ni zaključenih opravil"
|
||||
}}
|
||||
|
|
@ -652,14 +620,9 @@ const JobCard = defineComponent({
|
|||
<!-- 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"
|
||||
/>
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppPhoneLayout>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user