333 lines
10 KiB
Vue
333 lines
10 KiB
Vue
<script setup>
|
|
import AdminLayout from "@/Layouts/AdminLayout.vue";
|
|
import DialogModal from "@/Components/DialogModal.vue";
|
|
import { Head, useForm, Link, router } from "@inertiajs/vue3";
|
|
import { computed, ref } from "vue";
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import { faPlus, faPen, faTrash, faPaperPlane } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
const props = defineProps({
|
|
initialTemplates: { type: Array, default: () => [] },
|
|
profiles: { type: Array, default: () => [] },
|
|
senders: { type: Array, default: () => [] },
|
|
});
|
|
|
|
const templates = computed(() => props.initialTemplates || []);
|
|
const profilesById = computed(() =>
|
|
Object.fromEntries((props.profiles || []).map((p) => [p.id, p]))
|
|
);
|
|
const sendersById = computed(() =>
|
|
Object.fromEntries((props.senders || []).map((s) => [s.id, s]))
|
|
);
|
|
const sendersByProfile = computed(() => {
|
|
const map = {};
|
|
(props.senders || []).forEach((s) => {
|
|
if (!map[s.profile_id]) map[s.profile_id] = [];
|
|
map[s.profile_id].push(s);
|
|
});
|
|
return map;
|
|
});
|
|
|
|
// No manual reload; Inertia visits will refresh props
|
|
|
|
// Create/Edit modal
|
|
const editOpen = ref(false);
|
|
const editing = ref(null);
|
|
const form = useForm({
|
|
name: "",
|
|
slug: "",
|
|
content: "",
|
|
variables_json: {},
|
|
is_active: true,
|
|
default_profile_id: null,
|
|
default_sender_id: null,
|
|
});
|
|
|
|
function openCreate() {
|
|
editing.value = null;
|
|
form.reset();
|
|
form.is_active = true;
|
|
editOpen.value = true;
|
|
}
|
|
|
|
function openEdit(t) {
|
|
editing.value = t;
|
|
form.reset();
|
|
form.name = t.name;
|
|
form.slug = t.slug;
|
|
form.content = t.content;
|
|
form.variables_json = t.variables_json || {};
|
|
form.is_active = !!t.is_active;
|
|
form.default_profile_id = t.default_profile_id || null;
|
|
form.default_sender_id = t.default_sender_id || null;
|
|
editOpen.value = true;
|
|
}
|
|
|
|
async function submitEdit() {
|
|
try {
|
|
form.processing = true;
|
|
const payload = {
|
|
name: form.name,
|
|
slug: form.slug,
|
|
content: form.content,
|
|
variables_json: form.variables_json || {},
|
|
is_active: !!form.is_active,
|
|
default_profile_id: form.default_profile_id || null,
|
|
default_sender_id: form.default_sender_id || null,
|
|
};
|
|
if (editing.value) {
|
|
await router.put(route("admin.sms-templates.update", editing.value.id), payload, {
|
|
preserveScroll: true,
|
|
});
|
|
} else {
|
|
await router.post(route("admin.sms-templates.store"), payload, {
|
|
preserveScroll: true,
|
|
});
|
|
}
|
|
editOpen.value = false;
|
|
} finally {
|
|
form.processing = false;
|
|
}
|
|
}
|
|
|
|
async function destroyTemplate(t) {
|
|
if (!confirm(`Izbrišem SMS predlogo "${t.name}"?`)) return;
|
|
await router.delete(route("admin.sms-templates.destroy", t.id), {
|
|
preserveScroll: true,
|
|
});
|
|
}
|
|
|
|
// Test send modal
|
|
const testOpen = ref(false);
|
|
const testTarget = ref(null);
|
|
const testForm = useForm({
|
|
to: "",
|
|
variables: {},
|
|
profile_id: null,
|
|
sender_id: null,
|
|
country_code: null,
|
|
delivery_report: true,
|
|
});
|
|
const testResult = ref(null);
|
|
|
|
function openTest(t) {
|
|
testTarget.value = t;
|
|
testForm.reset();
|
|
testForm.delivery_report = true;
|
|
testForm.profile_id = t.default_profile_id || null;
|
|
testForm.sender_id = t.default_sender_id || null;
|
|
testResult.value = null;
|
|
testOpen.value = true;
|
|
}
|
|
|
|
async function submitTest() {
|
|
if (!testTarget.value) return;
|
|
const payload = {
|
|
to: testForm.to,
|
|
variables: testForm.variables || {},
|
|
profile_id: testForm.profile_id || null,
|
|
sender_id: testForm.sender_id || null,
|
|
country_code: testForm.country_code || null,
|
|
delivery_report: !!testForm.delivery_report,
|
|
};
|
|
await router.post(
|
|
route("admin.sms-templates.send-test", testTarget.value.id),
|
|
payload,
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
testOpen.value = false;
|
|
testResult.value = null;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
function currentSenders() {
|
|
const pid = form.default_profile_id;
|
|
if (!pid) return [];
|
|
return (sendersByProfile.value[pid] || []).filter((s) => s.active);
|
|
}
|
|
|
|
function currentSendersForTest() {
|
|
const pid = testForm.profile_id;
|
|
if (!pid) return [];
|
|
return (sendersByProfile.value[pid] || []).filter((s) => s.active);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AdminLayout title="SMS predloge">
|
|
<Head title="SMS predloge" />
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-xl font-semibold text-gray-800">SMS predloge</h1>
|
|
<Link
|
|
:href="route('admin.sms-templates.create')"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-500 shadow"
|
|
>
|
|
<FontAwesomeIcon :icon="faPlus" class="w-4 h-4" /> Nova predloga
|
|
</Link>
|
|
</div>
|
|
|
|
<div class="rounded-lg border bg-white overflow-hidden shadow-sm">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-gray-50 text-gray-600 text-xs uppercase tracking-wider">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left">Ime</th>
|
|
<th class="px-3 py-2 text-left">Slug</th>
|
|
<th class="px-3 py-2 text-left">Privzet profil/sender</th>
|
|
<th class="px-3 py-2">Aktivno</th>
|
|
<th class="px-3 py-2">Akcije</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="t in templates"
|
|
:key="t.id"
|
|
class="border-t last:border-b hover:bg-gray-50"
|
|
>
|
|
<td class="px-3 py-2 font-medium text-gray-800">{{ t.name }}</td>
|
|
<td class="px-3 py-2 text-gray-600">{{ t.slug }}</td>
|
|
<td class="px-3 py-2 text-gray-600">
|
|
<span>{{ profilesById[t.default_profile_id]?.name || "—" }}</span>
|
|
<span v-if="t.default_sender_id">
|
|
/ {{ sendersById[t.default_sender_id]?.sname }}</span
|
|
>
|
|
</td>
|
|
<td class="px-3 py-2 text-center">
|
|
<span :class="t.is_active ? 'text-emerald-600' : 'text-rose-600'">{{
|
|
t.is_active ? "Da" : "Ne"
|
|
}}</span>
|
|
</td>
|
|
<td class="px-3 py-2 flex items-center gap-2">
|
|
<Link
|
|
:href="route('admin.sms-templates.edit', t.id)"
|
|
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-indigo-700 border-indigo-300 bg-indigo-50 hover:bg-indigo-100"
|
|
>
|
|
<FontAwesomeIcon :icon="faPen" class="w-3.5 h-3.5" /> Uredi
|
|
</Link>
|
|
<button
|
|
@click="openTest(t)"
|
|
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-emerald-700 border-emerald-300 bg-emerald-50 hover:bg-emerald-100"
|
|
>
|
|
<FontAwesomeIcon :icon="faPaperPlane" class="w-3.5 h-3.5" /> Test
|
|
</button>
|
|
<button
|
|
@click="destroyTemplate(t)"
|
|
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-rose-700 border-rose-300 bg-rose-50 hover:bg-rose-100"
|
|
>
|
|
<FontAwesomeIcon :icon="faTrash" class="w-3.5 h-3.5" /> Izbriši
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Edit/Create now handled on dedicated page -->
|
|
|
|
<!-- Test Send Modal -->
|
|
<DialogModal :show="testOpen" max-width="2xl" @close="() => (testOpen = false)">
|
|
<template #title> Testno pošiljanje </template>
|
|
<template #content>
|
|
<div class="space-y-4">
|
|
<div class="grid gap-4 grid-cols-2">
|
|
<div>
|
|
<label class="label">Prejemnik (E.164)</label>
|
|
<input
|
|
v-model="testForm.to"
|
|
type="text"
|
|
class="input"
|
|
placeholder="+386..."
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="label">Državna koda</label>
|
|
<input
|
|
v-model="testForm.country_code"
|
|
type="text"
|
|
class="input"
|
|
placeholder="SI"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="label">Profil</label>
|
|
<select v-model="testForm.profile_id" class="input">
|
|
<option :value="null">(privzeti)</option>
|
|
<option v-for="p in props.profiles" :key="p.id" :value="p.id">
|
|
{{ p.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="label">Sender</label>
|
|
<select
|
|
v-model="testForm.sender_id"
|
|
class="input"
|
|
:disabled="!testForm.profile_id"
|
|
>
|
|
<option :value="null">(privzeti)</option>
|
|
<option v-for="s in currentSendersForTest()" :key="s.id" :value="s.id">
|
|
{{ s.sname }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="label">Dostavna poročila</label>
|
|
<select v-model="testForm.delivery_report" class="input">
|
|
<option :value="true">Da</option>
|
|
<option :value="false">Ne</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<!-- Result details removed in favor of flash messages after redirect -->
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<button
|
|
type="button"
|
|
@click="() => (testOpen = false)"
|
|
class="px-4 py-2 text-sm rounded-md border bg-white hover:bg-gray-50"
|
|
>
|
|
Zapri
|
|
</button>
|
|
<button
|
|
type="button"
|
|
@click="submitTest"
|
|
:disabled="testForm.processing || !testTarget"
|
|
class="px-4 py-2 text-sm rounded-md bg-emerald-600 text-white hover:bg-emerald-500 disabled:opacity-50"
|
|
>
|
|
<FontAwesomeIcon :icon="faPaperPlane" class="w-3.5 h-3.5 mr-1" /> Pošlji test
|
|
</button>
|
|
</template>
|
|
</DialogModal>
|
|
</AdminLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.input {
|
|
width: 100%;
|
|
border-radius: 0.375rem;
|
|
border: 1px solid var(--tw-color-gray-300, #d1d5db);
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: 0.875rem;
|
|
line-height: 1.25rem;
|
|
}
|
|
.input:focus {
|
|
outline: 2px solid transparent;
|
|
outline-offset: 2px;
|
|
--tw-ring-color: #6366f1;
|
|
border-color: #6366f1;
|
|
box-shadow: 0 0 0 1px #6366f1;
|
|
}
|
|
.label {
|
|
display: block;
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.05em;
|
|
text-transform: uppercase;
|
|
color: #6b7280;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
</style>
|