Teren-app/resources/js/Pages/Admin/MailProfiles/Index.vue
2025-10-07 21:57:10 +02:00

273 lines
8.4 KiB
Vue

<script setup>
import AdminLayout from "@/Layouts/AdminLayout.vue";
import DialogModal from "@/Components/DialogModal.vue";
import { Head, Link, useForm } from "@inertiajs/vue3";
import { ref, computed } from "vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
faPlus,
faFlask,
faBolt,
faArrowsRotate,
faToggleOn,
faToggleOff,
} from "@fortawesome/free-solid-svg-icons";
const props = defineProps({
profiles: { type: Array, default: () => [] },
});
const createOpen = ref(false);
const editTarget = ref(null);
const form = useForm({
name: "",
host: "",
port: 587,
encryption: "tls",
username: "",
password: "",
from_address: "",
from_name: "",
priority: 10,
});
function openCreate() {
form.reset();
createOpen.value = true;
editTarget.value = null;
}
function closeCreate() {
if (form.processing) return;
createOpen.value = false;
}
function submitCreate() {
form.post(route("admin.mail-profiles.store"), {
preserveScroll: true,
onSuccess: () => {
createOpen.value = false;
},
});
}
function toggleActive(p) {
window.axios
.post(route("admin.mail-profiles.toggle", p.id))
.then(() => window.location.reload());
}
function testConnection(p) {
window.axios
.post(route("admin.mail-profiles.test", p.id))
.then(() => window.location.reload());
}
const statusClass = (p) => {
if (p.test_status === "success") return "text-emerald-600";
if (p.test_status === "failed") return "text-rose-600";
if (p.test_status === "queued") return "text-amber-500";
return "text-gray-400";
};
</script>
<template>
<AdminLayout title="Mail profili">
<Head title="Mail profili" />
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl font-semibold text-gray-800 flex items-center gap-3">
Mail profili
<span class="text-xs font-medium text-gray-400">({{ profiles.length }})</span>
</h1>
<button
@click="openCreate"
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" /> Nov profil
</button>
</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">Host</th>
<th class="px-3 py-2">Port</th>
<th class="px-3 py-2">Enc</th>
<th class="px-3 py-2">Aktivno</th>
<th class="px-3 py-2">Test</th>
<th class="px-3 py-2">Zadnji uspeh</th>
<th class="px-3 py-2">Napaka</th>
<th class="px-3 py-2">Akcije</th>
</tr>
</thead>
<tbody>
<tr
v-for="p in profiles"
:key="p.id"
class="border-t last:border-b hover:bg-gray-50"
>
<td class="px-3 py-2 font-medium text-gray-800">{{ p.name }}</td>
<td class="px-3 py-2">{{ p.host }}</td>
<td class="px-3 py-2 text-center">{{ p.port }}</td>
<td class="px-3 py-2 text-center">{{ p.encryption || "—" }}</td>
<td class="px-3 py-2 text-center">
<button
@click="toggleActive(p)"
class="text-indigo-600 hover:text-indigo-800"
:title="p.active ? 'Onemogoči' : 'Omogoči'"
>
<FontAwesomeIcon
:icon="p.active ? faToggleOn : faToggleOff"
class="w-5 h-5"
/>
</button>
</td>
<td class="px-3 py-2 text-center">
<span :class="['font-medium', statusClass(p)]">{{
p.test_status || "—"
}}</span>
</td>
<td class="px-3 py-2 text-xs text-gray-500">
{{ p.last_success_at ? new Date(p.last_success_at).toLocaleString() : "—" }}
</td>
<td
class="px-3 py-2 text-xs text-rose-600 max-w-[160px] truncate"
:title="p.last_error_message"
>
{{ p.last_error_message || "—" }}
</td>
<td class="px-3 py-2 flex items-center gap-2">
<button
@click="testConnection(p)"
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-amber-600 border-amber-300 bg-amber-50 hover:bg-amber-100"
>
<FontAwesomeIcon :icon="faFlask" class="w-3.5 h-3.5" /> Test
</button>
<button
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-indigo-600 border-indigo-300 bg-indigo-50 hover:bg-indigo-100"
>
<FontAwesomeIcon :icon="faArrowsRotate" class="w-3.5 h-3.5" /> Shrani
</button>
</td>
</tr>
</tbody>
</table>
</div>
<DialogModal :show="createOpen" max-width="2xl" @close="closeCreate">
<template #title> Nov Mail profil </template>
<template #content>
<form @submit.prevent="submitCreate" id="create-mail-profile" class="space-y-5">
<div class="grid gap-4 grid-cols-2">
<div class="col-span-1">
<label class="label">Ime</label>
<input v-model="form.name" type="text" class="input" />
</div>
<div>
<label class="label">Host</label>
<input v-model="form.host" type="text" class="input" />
</div>
<div>
<label class="label">Port</label>
<input v-model="form.port" type="number" class="input" />
</div>
<div>
<label class="label">Encryption</label>
<select v-model="form.encryption" class="input">
<option value="">(None)</option>
<option value="tls">TLS</option>
<option value="ssl">SSL</option>
</select>
</div>
<div>
<label class="label">Username</label>
<input v-model="form.username" type="text" class="input" />
</div>
<div>
<label class="label">Password</label>
<input
v-model="form.password"
type="password"
class="input"
autocomplete="new-password"
/>
</div>
<div>
<label class="label">From naslov</label>
<input v-model="form.from_address" type="email" class="input" />
</div>
<div>
<label class="label">From ime</label>
<input v-model="form.from_name" type="text" class="input" />
</div>
<div>
<label class="label">Prioriteta</label>
<input v-model="form.priority" type="number" class="input" />
</div>
</div>
</form>
</template>
<template #footer>
<button
type="button"
@click="closeCreate"
class="px-4 py-2 text-sm rounded-md border bg-white hover:bg-gray-50"
>
Prekliči
</button>
<button
form="create-mail-profile"
type="submit"
:disabled="form.processing"
class="px-4 py-2 text-sm rounded-md bg-indigo-600 text-white hover:bg-indigo-500 disabled:opacity-50"
>
Shrani
</button>
</template>
</DialogModal>
</AdminLayout>
</template>
<style scoped>
/* Utility replacements for @apply not processed in SFC scope build pipeline */
.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;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
.animate-fade-in {
animation: fade-in 0.25s ease;
}
</style>