Phone view update

This commit is contained in:
Simon Pocrnjič
2026-06-20 23:42:43 +02:00
parent 8ffc60aba5
commit ea9376c713
4 changed files with 144 additions and 234 deletions
+36 -227
View File
@@ -13,27 +13,10 @@ import {
import { Skeleton } from "@/Components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
import { InfiniteScroll, router } from "@inertiajs/vue3";
import {
computed,
defineComponent,
h,
onMounted,
onUnmounted,
ref,
watch,
} from "vue";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { useDebounceFn } from "@vueuse/core";
import {
CalendarDays,
CheckCircle2,
ChevronRight,
ClipboardList,
MapPin,
Phone,
SlidersHorizontal,
Wallet,
} from "lucide-vue-next";
import { fmtDateDMY } from "@/Utilities/functions";
import { CheckCircle2, ClipboardList, SlidersHorizontal } from "lucide-vue-next";
import JobCard from "@/Pages/Phone/Partials/JobCard.vue";
const props = defineProps({
pendingJobs: { type: Object, default: null },
@@ -75,9 +58,7 @@ function performFilter() {
preserveState: true,
preserveScroll: false,
only,
reset: isCompleted.value
? ["completedJobs"]
: ["pendingJobs", "processedJobs"],
reset: isCompleted.value ? ["completedJobs"] : ["pendingJobs", "processedJobs"],
onSuccess: () => {
isFiltering.value = false;
},
@@ -125,16 +106,6 @@ const pendingCount = computed(() => props.pendingJobs?.total ?? 0);
const processedCount = computed(() => props.processedJobs?.total ?? 0);
// ── Helpers ──────────────────────────────────────────────────────────────────
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,
});
}
function getCaseUuid(job) {
return (
job?.contract?.client_case?.uuid || job?.client_case?.uuid || job?.case_uuid || null
@@ -149,182 +120,6 @@ function jobHref(job) {
completed: isCompleted.value ? 1 : undefined,
});
}
// ── JobCard component ────────────────────────────────────────────────────────
const JobCard = defineComponent({
name: "JobCard",
props: {
job: { type: Object, required: true },
href: { type: String, default: null },
accentClass: { type: String, default: "border-l-blue-500" },
showLastActivity: { type: Boolean, default: false },
},
setup(p) {
return () => {
const j = p.job;
const person = j.contract?.client_case?.person;
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;
const inner = h("div", { class: `border-l-4 ${p.accentClass}` }, [
h(
"div",
{
class: "px-4 pt-4 pb-2 flex items-start justify-between gap-3",
},
[
h("div", { class: "flex-1 min-w-0" }, [
h(
"p",
{
class:
"font-bold text-base text-gray-900 dark:text-gray-100 truncate leading-tight",
},
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 || "—"
),
clientName
? h(
"p",
{
class:
"text-xs text-indigo-600 dark:text-indigo-400 mt-0.5 truncate",
},
clientName
)
: null,
]),
j.priority
? 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",
},
"Prioriteta"
)
: null,
]
),
address || phone
? h("div", { class: "px-4 pb-3 space-y-1.5" }, [
address
? h(
"div",
{
class: "flex items-center gap-2 text-gray-500 dark:text-gray-400",
},
[
h(MapPin, {
class: "w-3.5 h-3.5 shrink-0 text-gray-400",
}),
h("span", { class: "text-xs truncate" }, address),
]
)
: null,
phone
? h(
"div",
{
class: "flex items-center gap-2 text-gray-500 dark:text-gray-400",
},
[
h(Phone, {
class: "w-3.5 h-3.5 shrink-0 text-gray-400",
}),
h("span", { class: "text-xs font-medium" }, phone),
]
)
: null,
])
: null,
balance != null
? 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",
},
[
h(Wallet, {
class: "w-4 h-4 text-red-500 shrink-0",
}),
h(
"span",
{
class: "font-bold text-red-600 dark:text-red-400 text-sm",
},
`${formatAmount(balance)}`
),
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",
},
[
h(
"div",
{
class: "flex items-center gap-1.5 text-xs text-gray-400",
},
[
h(CalendarDays, { class: "w-3.5 h-3.5" }),
h(
"span",
{},
p.showLastActivity && j.last_activity
? fmtDateDMY(j.last_activity)
: fmtDateDMY(j.assigned_at)
),
]
),
p.href
? h(
"div",
{
class: "flex items-center gap-0.5 text-primary font-semibold text-sm",
},
["Odpri", h(ChevronRight, { class: "w-4 h-4" })]
)
: h("span", { class: "text-xs text-gray-400 italic" }, "Manjka primer"),
]
),
]);
return p.href
? h(
"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",
},
inner
)
: h(
"div",
{
class:
"rounded-xl border shadow-sm overflow-hidden bg-white dark:bg-card opacity-60",
},
inner
);
};
},
});
</script>
<template>
@@ -399,30 +194,38 @@ const JobCard = defineComponent({
<!-- 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">
<TabsList class="w-full grid grid-cols-2 bg-zinc-200/50">
<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="h-4 px-1 text-xs">
<div class="flex flex-row items-center gap-1 p-1">
<ClipboardList :size="16" />
<span>Novo</span>
<Badge
v-if="pendingCount"
variant="outline"
class="text-blue-500 font-bold text-sm"
>
{{ pendingCount }}
</Badge>
</span>
</div>
</TabsTrigger>
<TabsTrigger value="processed">
<span class="inline-flex flex-row items-center gap-1">
<CheckCircle2 class="w-3.5 h-3.5 shrink-0" />
<div class="flex flex-row items-center gap-1 p-1">
<CheckCircle2 :size="16" />
Obdelano
<Badge v-if="processedCount" variant="secondary" class="h-4 px-1 text-xs">
<Badge
v-if="processedCount"
variant="outline"
class="text-green-500 font-bold text-sm"
>
{{ processedCount }}
</Badge>
</span>
</div>
</TabsTrigger>
</TabsList>
<!-- Pending tab -->
<TabsContent value="pending" class="space-y-3">
<InfiniteScroll data="pendingJobs" only-next>
<InfiniteScroll data="pendingJobs" class="space-y-2" only-next>
<template #default="{ loading }">
<template v-if="props.pendingJobs?.data?.length">
<JobCard
@@ -430,17 +233,21 @@ const JobCard = defineComponent({
:key="job.id"
:job="job"
:href="jobHref(job)"
accent-class="border-l-blue-500"
accent-class="border-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" />
<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>
@@ -455,7 +262,7 @@ const JobCard = defineComponent({
<!-- Processed tab -->
<TabsContent value="processed" class="space-y-3">
<InfiniteScroll data="processedJobs" only-next>
<InfiniteScroll data="processedJobs" class="space-y-2" only-next>
<template #default="{ loading }">
<template v-if="props.processedJobs?.data?.length">
<JobCard
@@ -463,7 +270,7 @@ const JobCard = defineComponent({
:key="job.id"
:job="job"
:href="jobHref(job)"
accent-class="border-l-green-500"
accent-class="border-green-500"
:show-last-activity="true"
/>
</template>
@@ -471,7 +278,9 @@ const JobCard = defineComponent({
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" />
<CheckCircle2
class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto"
/>
<p class="text-sm">
{{
search || clientFilter !== "all"
@@ -501,7 +310,7 @@ const JobCard = defineComponent({
:key="job.id"
:job="job"
:href="jobHref(job)"
accent-class="border-l-purple-500"
accent-class="border-secondary-500"
:show-last-activity="true"
/>
</template>