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:
@@ -0,0 +1,337 @@
|
||||
<script setup>
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import { Link, router } from "@inertiajs/vue3";
|
||||
import { onMounted, onUnmounted, ref, computed } from "vue";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/Components/ui/card";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
import { Separator } from "@/Components/ui/separator";
|
||||
import DataTableNew2 from "@/Components/DataTable/DataTableNew2.vue";
|
||||
import Pagination from "@/Components/Pagination.vue";
|
||||
import {
|
||||
PackageIcon,
|
||||
ArrowLeftIcon,
|
||||
PlayIcon,
|
||||
XCircleIcon,
|
||||
RefreshCwIcon,
|
||||
CopyIcon,
|
||||
} from "lucide-vue-next";
|
||||
import AppCard from "@/Components/app/ui/card/AppCard.vue";
|
||||
|
||||
const props = defineProps({
|
||||
package: { type: Object, required: true },
|
||||
items: { type: Object, required: true },
|
||||
preview: { type: [Object, null], default: null },
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ accessorKey: "id", header: "ID" },
|
||||
{ accessorKey: "target", header: "Prejemnik" },
|
||||
{ accessorKey: "message", header: "Sporočilo" },
|
||||
{ accessorKey: "status", header: "Status" },
|
||||
{ accessorKey: "last_error", header: "Napaka" },
|
||||
{ accessorKey: "provider_message_id", header: "Provider ID" },
|
||||
{ accessorKey: "cost", header: "Cena" },
|
||||
{ accessorKey: "currency", header: "Valuta" },
|
||||
];
|
||||
|
||||
function getStatusVariant(status) {
|
||||
if (["queued", "processing"].includes(status)) return "secondary";
|
||||
if (status === "sent") return "default";
|
||||
if (status === "failed") return "destructive";
|
||||
return "outline";
|
||||
}
|
||||
|
||||
const refreshing = ref(false);
|
||||
let timer = null;
|
||||
|
||||
const isRunning = computed(() => ["queued", "running"].includes(props.package.status));
|
||||
|
||||
// Derive a summary of payload/template/body from the first item on the page.
|
||||
// Assumption: payload is the same across items in a package (both flows use a common payload).
|
||||
const firstItem = computed(() =>
|
||||
props.items?.data && props.items.data.length ? props.items.data[0] : null
|
||||
);
|
||||
const firstPayload = computed(() =>
|
||||
firstItem.value ? firstItem.value.payload_json || {} : {}
|
||||
);
|
||||
const messageBody = computed(() => {
|
||||
const b = firstPayload.value?.body;
|
||||
if (typeof b === "string") {
|
||||
const t = b.trim();
|
||||
return t.length ? t : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const payloadSummary = computed(() => ({
|
||||
profile_id: firstPayload.value?.profile_id ?? null,
|
||||
sender_id: firstPayload.value?.sender_id ?? null,
|
||||
template_id: firstPayload.value?.template_id ?? null,
|
||||
delivery_report: !!firstPayload.value?.delivery_report,
|
||||
}));
|
||||
|
||||
function reload() {
|
||||
refreshing.value = true;
|
||||
router.reload({
|
||||
only: ["package", "items"],
|
||||
onFinish: () => (refreshing.value = false),
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchPkg() {
|
||||
router.post(
|
||||
route("packages.dispatch", props.package.id),
|
||||
{},
|
||||
{ onSuccess: reload }
|
||||
);
|
||||
}
|
||||
function cancelPkg() {
|
||||
router.post(
|
||||
route("packages.cancel", props.package.id),
|
||||
{},
|
||||
{ onSuccess: reload }
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (isRunning.value) {
|
||||
timer = setInterval(reload, 3000);
|
||||
}
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
async function copyText(text) {
|
||||
if (!text) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch (e) {
|
||||
// Fallback for older browsers
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
} catch (_) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout :title="`Paket #${package.id}`">
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<PackageIcon class="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<CardTitle>Paket #{{ package.id }}</CardTitle>
|
||||
<CardDescription class="font-mono"
|
||||
>UUID: {{ package.uuid }}</CardDescription
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" as-child>
|
||||
<Link :href="route('packages.index')">
|
||||
<ArrowLeftIcon class="h-4 w-4 mr-2" />
|
||||
Nazaj
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="['draft', 'failed'].includes(package.status)"
|
||||
@click="dispatchPkg"
|
||||
size="sm"
|
||||
>
|
||||
<PlayIcon class="h-4 w-4 mr-2" />
|
||||
Zaženi
|
||||
</Button>
|
||||
<Button v-if="isRunning" @click="cancelPkg" variant="destructive" size="sm">
|
||||
<XCircleIcon class="h-4 w-4 mr-2" />
|
||||
Prekliči
|
||||
</Button>
|
||||
<Button v-if="!isRunning" @click="reload" variant="outline" size="sm">
|
||||
<RefreshCwIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<div class="grid sm:grid-cols-4 gap-3 mb-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-2">
|
||||
<CardDescription>Status</CardDescription>
|
||||
<CardTitle class="text-xl uppercase">{{ package.status }}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader class="pb-2">
|
||||
<CardDescription>Skupaj</CardDescription>
|
||||
<CardTitle class="text-xl">{{ package.total_items }}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader class="pb-2">
|
||||
<CardDescription>Poslano</CardDescription>
|
||||
<CardTitle class="text-xl text-emerald-700">{{ package.sent_count }}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader class="pb-2">
|
||||
<CardDescription>Neuspešno</CardDescription>
|
||||
<CardTitle class="text-xl text-rose-700">{{ package.failed_count }}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Payload / Message preview -->
|
||||
<div class="mb-4 grid gap-3 sm:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-base">Sporočilo</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<template v-if="preview && preview.content">
|
||||
<div class="text-sm whitespace-pre-wrap mb-3">{{ preview.content }}</div>
|
||||
<Button @click="copyText(preview.content)" size="sm" variant="outline">
|
||||
<CopyIcon class="h-3.5 w-3.5 mr-2" />
|
||||
Kopiraj
|
||||
</Button>
|
||||
<p
|
||||
v-if="preview.source === 'template' && preview.template"
|
||||
class="mt-3 text-xs text-muted-foreground"
|
||||
>
|
||||
Predloga: {{ preview.template.name }} (#{{ preview.template.id }})
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="messageBody" class="text-sm whitespace-pre-wrap">
|
||||
{{ messageBody }}
|
||||
</div>
|
||||
<div v-else class="text-sm text-muted-foreground">
|
||||
<template v-if="payloadSummary.template_id">
|
||||
Uporabljena bo predloga #{{ payloadSummary.template_id }}.
|
||||
</template>
|
||||
<template v-else> Vsebina sporočila ni določena. </template>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-base">Meta / Nastavitve pošiljanja</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<dl class="text-sm grid grid-cols-3 gap-y-2">
|
||||
<dt class="col-span-1 text-muted-foreground">Profil</dt>
|
||||
<dd class="col-span-2">{{ payloadSummary.profile_id ?? "—" }}</dd>
|
||||
<dt class="col-span-1 text-muted-foreground">Pošiljatelj</dt>
|
||||
<dd class="col-span-2">{{ payloadSummary.sender_id ?? "—" }}</dd>
|
||||
<dt class="col-span-1 text-muted-foreground">Predloga</dt>
|
||||
<dd class="col-span-2">{{ payloadSummary.template_id ?? "—" }}</dd>
|
||||
<dt class="col-span-1 text-muted-foreground">Delivery report</dt>
|
||||
<dd class="col-span-2">{{ payloadSummary.delivery_report ? "da" : "ne" }}</dd>
|
||||
</dl>
|
||||
<div
|
||||
v-if="
|
||||
package.meta && (package.meta.source || package.meta.skipped !== undefined)
|
||||
"
|
||||
class="mt-3 pt-3 border-t text-xs text-muted-foreground"
|
||||
>
|
||||
<span v-if="package.meta.source" class="mr-3"
|
||||
>Vir: {{ package.meta.source }}</span
|
||||
>
|
||||
<span v-if="package.meta.skipped !== undefined"
|
||||
>Preskočeno: {{ package.meta.skipped }}</span
|
||||
>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<PackageIcon size="18" />
|
||||
<CardTitle class="uppercase">Uvozi</CardTitle>
|
||||
</div>
|
||||
</template>
|
||||
<DataTableNew2
|
||||
:columns="columns"
|
||||
:data="items.data"
|
||||
:meta="items"
|
||||
route-name="packages.show"
|
||||
:route-params="{ id: package.id }"
|
||||
>
|
||||
<template #cell-target="{ row }">
|
||||
<span class="text-sm">{{
|
||||
(row.target_json && row.target_json.number) || "—"
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-message="{ row }">
|
||||
<div class="flex items-start gap-2">
|
||||
<div class="text-xs max-w-[420px] line-clamp-2 whitespace-pre-wrap">
|
||||
{{ row.rendered_preview || "—" }}
|
||||
</div>
|
||||
<Button
|
||||
v-if="row.rendered_preview"
|
||||
@click="copyText(row.rendered_preview)"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-status="{ row }">
|
||||
<Badge :variant="getStatusVariant(row.status)">{{ row.status }}</Badge>
|
||||
</template>
|
||||
|
||||
<template #cell-last_error="{ row }">
|
||||
<span class="text-xs text-rose-700">{{ row.last_error ?? "—" }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-provider_message_id="{ row }">
|
||||
<span class="font-mono text-xs text-muted-foreground">{{
|
||||
row.provider_message_id ?? "—"
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-cost="{ row }">
|
||||
<span class="text-sm">{{ row.cost ?? "—" }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-currency="{ row }">
|
||||
<span class="text-sm">{{ row.currency ?? "—" }}</span>
|
||||
</template>
|
||||
</DataTableNew2>
|
||||
</AppCard>
|
||||
|
||||
<div v-if="refreshing" class="mt-2 text-xs text-muted-foreground">
|
||||
Osveževanje ...
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
Reference in New Issue
Block a user