Added call later, option to limit auto mail so for a client person email you can limit which decision activity will be send to that specific email and moved SMS packages from admin panel to default app view

This commit is contained in:
Simon Pocrnjič
2026-03-08 21:42:39 +01:00
parent c16dd51199
commit b0d2aa93ab
32 changed files with 1103 additions and 174 deletions
+297
View File
@@ -0,0 +1,297 @@
<script setup>
import AppLayout from "@/Layouts/AppLayout.vue";
import { Link, router } from "@inertiajs/vue3";
import { computed, ref } from "vue";
import DataTable from "@/Components/DataTable/DataTableNew2.vue";
import AppCard from "@/Components/app/ui/card/AppCard.vue";
import CardTitle from "@/Components/ui/card/CardTitle.vue";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import InputLabel from "@/Components/InputLabel.vue";
import Pagination from "@/Components/Pagination.vue";
import {
PhoneCallIcon,
CheckIcon,
Filter,
ExternalLinkIcon,
MoreHorizontalIcon,
} from "lucide-vue-next";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/Components/ui/dropdown-menu";
import AppPopover from "@/Components/app/ui/AppPopover.vue";
const props = defineProps({
callLaters: Object,
filters: Object,
});
const search = ref(props.filters?.search || "");
const dateFrom = ref(props.filters?.date_from || "");
const dateTo = ref(props.filters?.date_to || "");
const filterPopoverOpen = ref(false);
const appliedFilterCount = computed(() => {
let count = 0;
if (search.value?.trim()) count += 1;
if (dateFrom.value) count += 1;
if (dateTo.value) count += 1;
return count;
});
function applyFilters() {
filterPopoverOpen.value = false;
const params = {};
if (search.value?.trim()) {
params.search = search.value.trim();
}
if (dateFrom.value) {
params.date_from = dateFrom.value;
}
if (dateTo.value) {
params.date_to = dateTo.value;
}
router.get(route("callLaters.index"), params, {
preserveState: true,
replace: true,
preserveScroll: true,
});
}
function clearFilters() {
search.value = "";
dateFrom.value = "";
dateTo.value = "";
applyFilters();
}
function markDone(item) {
router.patch(
route("callLaters.complete", item.id),
{},
{
preserveScroll: true,
}
);
}
function openAndComplete(item) {
router.patch(
route("callLaters.complete", item.id),
{},
{
preserveScroll: false,
onSuccess: () => {
if (item.client_case?.uuid) {
router.visit(route("clientCase.show", { client_case: item.client_case.uuid }));
}
},
}
);
}
function isOverdue(item) {
if (!item.call_back_at) return false;
return new Date(item.call_back_at) < new Date();
}
function fmtDateTime(value) {
if (!value) return "-";
const d = new Date(value);
if (isNaN(d.getTime())) return value;
const day = String(d.getDate()).padStart(2, "0");
const month = String(d.getMonth() + 1).padStart(2, "0");
const year = d.getFullYear();
const hours = String(d.getHours()).padStart(2, "0");
const minutes = String(d.getMinutes()).padStart(2, "0");
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
const columns = [
{ key: "person", label: "Stranka / Primer", sortable: false },
{ key: "contract", label: "Pogodba", sortable: false },
{ key: "call_back_at", label: "Datum klica", sortable: false },
{ key: "user", label: "Agent", sortable: false },
{ key: "note", label: "Opomba", sortable: false },
{ key: "actions", label: "", sortable: false, class: "w-12" },
];
</script>
<template>
<AppLayout title="Pokliči kasneje">
<template #header></template>
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<AppCard
title=""
padding="none"
class="p-0! gap-0"
header-class="py-3! px-4 gap-0 text-muted-foreground"
body-class=""
>
<template #header>
<div class="flex items-center gap-2">
<PhoneCallIcon :size="18" />
<CardTitle class="uppercase">Pokliči kasneje</CardTitle>
</div>
</template>
<DataTable
:columns="columns"
:data="callLaters.data || []"
:meta="callLaters"
:search="search"
route-name="callLaters.index"
:show-toolbar="true"
:show-pagination="false"
:hoverable="true"
row-key="id"
empty-text="Ni zakazanih klicev."
:row-class="(row) => (isOverdue(row) ? 'bg-red-50 dark:bg-red-950/20' : '')"
>
<template #toolbar-filters>
<AppPopover
v-model:open="filterPopoverOpen"
align="start"
content-class="w-[420px]"
>
<template #trigger>
<Button variant="outline" size="sm" class="gap-2">
<Filter class="h-4 w-4" />
Filtri
<span
v-if="appliedFilterCount > 0"
class="ml-1 rounded-full bg-primary px-2 py-0.5 text-xs text-primary-foreground"
>
{{ appliedFilterCount }}
</span>
</Button>
</template>
<div class="space-y-4">
<div class="space-y-2">
<h4 class="font-medium text-sm">Filtri klicev</h4>
</div>
<div class="space-y-3">
<div class="space-y-1.5">
<InputLabel>Iskanje (stranka)</InputLabel>
<Input
v-model="search"
type="text"
placeholder="Ime stranke..."
@keydown.enter="applyFilters"
/>
</div>
<div class="space-y-1.5">
<InputLabel>Datum od</InputLabel>
<Input v-model="dateFrom" type="date" />
</div>
<div class="space-y-1.5">
<InputLabel>Datum do</InputLabel>
<Input v-model="dateTo" type="date" />
</div>
<div class="flex justify-end gap-2 pt-2 border-t">
<Button
type="button"
variant="outline"
size="sm"
:disabled="appliedFilterCount === 0"
@click="clearFilters"
>
Počisti
</Button>
<Button type="button" size="sm" @click="applyFilters">
Uporabi
</Button>
</div>
</div>
</div>
</AppPopover>
</template>
<template #cell-person="{ row }">
<div>
<Link
v-if="row.client_case"
:href="route('clientCase.show', { client_case: row.client_case.uuid })"
class="font-medium text-indigo-600 hover:underline"
>
{{ row.client_case.person?.full_name || "-" }}
</Link>
<span v-else class="text-muted-foreground">-</span>
</div>
</template>
<template #cell-contract="{ row }">
<span v-if="row.contract">{{ row.contract.reference }}</span>
<span v-else class="text-muted-foreground">-</span>
</template>
<template #cell-call_back_at="{ row }">
<span
:class="[
'font-medium',
isOverdue(row) ? 'text-red-600 dark:text-red-400' : '',
]"
>
{{ fmtDateTime(row.call_back_at) }}
</span>
<span v-if="isOverdue(row)" class="ml-2 text-xs text-red-500 font-semibold">
Zamuda
</span>
</template>
<template #cell-user="{ row }">
<span v-if="row.user">{{ row.user.name }}</span>
<span v-else class="text-muted-foreground">-</span>
</template>
<template #cell-note="{ row }">
<span class="line-clamp-2 text-sm text-muted-foreground">
{{ row.activity?.note || "-" }}
</span>
</template>
<template #cell-actions="{ row }">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button size="icon" variant="ghost" class="h-8 w-8">
<MoreHorizontalIcon class="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem @click="markDone(row)">
<CheckIcon class="mr-2 h-4 w-4" />
Opravljeno
</DropdownMenuItem>
<DropdownMenuItem
v-if="row.client_case?.uuid"
@click="openAndComplete(row)"
>
<ExternalLinkIcon class="mr-2 h-4 w-4" />
Odpri in opravi
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
</DataTable>
<div class="border-t border-gray-200 p-4">
<Pagination
:links="callLaters.links"
:from="callLaters.from"
:to="callLaters.to"
:total="callLaters.total"
:per-page="callLaters.per_page || 50"
:last-page="callLaters.last_page"
:current-page="callLaters.current_page"
/>
</div>
</AppCard>
</div>
</div>
</AppLayout>
</template>