From ddfc79ffe84add74fa50f0ef71fda805aa5455d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pocrnji=C4=8D?= Date: Tue, 14 Oct 2025 21:00:26 +0200 Subject: [PATCH] front end updates --- resources/js/Layouts/AdminLayout.vue | 38 +- resources/js/Pages/Admin/Index.vue | 25 +- .../js/Pages/Admin/MailProfiles/Index.vue | 111 ++++- .../js/Pages/Cases/Partials/ActivityTable.vue | 7 +- resources/js/Pages/FieldJob/Index.vue | 381 +++++++++--------- 5 files changed, 338 insertions(+), 224 deletions(-) diff --git a/resources/js/Layouts/AdminLayout.vue b/resources/js/Layouts/AdminLayout.vue index 544493d..9d42617 100644 --- a/resources/js/Layouts/AdminLayout.vue +++ b/resources/js/Layouts/AdminLayout.vue @@ -11,6 +11,10 @@ import { faGears, faKey, faEnvelope, + faEnvelopeOpenText, + faAt, + faInbox, + faFileLines, } from "@fortawesome/free-solid-svg-icons"; import Dropdown from "@/Components/Dropdown.vue"; import DropdownLink from "@/Components/DropdownLink.vue"; @@ -44,7 +48,7 @@ function toggleSidebar() { const logout = () => router.post(route("logout")); const page = usePage(); -// Categorized admin navigation groups (removed global 'Nastavitve') +// Categorized admin navigation groups with distinct icons const navGroups = computed(() => [ { key: "core", @@ -97,29 +101,35 @@ const navGroups = computed(() => [ icon: faFileWord, active: ["admin.document-templates.index"], }, - { - key: "admin.email-templates.index", - label: "Email predloge", - route: "admin.email-templates.index", - icon: faEnvelope, - active: [ - "admin.email-templates.index", - "admin.email-templates.create", - "admin.email-templates.edit", - ], - }, + ], + }, + { + key: "email", + label: "Email", + items: [ + { + key: "admin.email-templates.index", + label: "Email predloge", + route: "admin.email-templates.index", + icon: faEnvelopeOpenText, + active: [ + "admin.email-templates.index", + "admin.email-templates.create", + "admin.email-templates.edit", + ], + }, { key: "admin.email-logs.index", label: "Email dnevniki", route: "admin.email-logs.index", - icon: faEnvelope, + icon: faInbox, active: ["admin.email-logs.index", "admin.email-logs.show"], }, { key: "admin.mail-profiles.index", label: "Mail profili", route: "admin.mail-profiles.index", - icon: faGears, + icon: faAt, active: ["admin.mail-profiles.index"], }, ], diff --git a/resources/js/Pages/Admin/Index.vue b/resources/js/Pages/Admin/Index.vue index 0aaa95d..34558ff 100644 --- a/resources/js/Pages/Admin/Index.vue +++ b/resources/js/Pages/Admin/Index.vue @@ -2,7 +2,7 @@ import AdminLayout from '@/Layouts/AdminLayout.vue' import { Link } from '@inertiajs/vue3' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' -import { faUserGroup, faKey, faGears, faFileWord } from '@fortawesome/free-solid-svg-icons' +import { faUserGroup, faKey, faGears, faFileWord, faEnvelopeOpenText, faInbox, faAt } from '@fortawesome/free-solid-svg-icons' const cards = [ { @@ -39,6 +39,29 @@ const cards = [ }, ], }, + { + category: 'Email', + items: [ + { + title: 'Email predloge', + description: 'Upravljanje HTML / tekst email predlog', + route: 'admin.email-templates.index', + icon: faEnvelopeOpenText, + }, + { + title: 'Email dnevniki', + description: 'Pregled poslanih emailov in statusov', + route: 'admin.email-logs.index', + icon: faInbox, + }, + { + title: 'Mail profili', + description: 'SMTP profili, nastavitve in testiranje povezave', + route: 'admin.mail-profiles.index', + icon: faAt, + }, + ], + }, ] diff --git a/resources/js/Pages/Admin/MailProfiles/Index.vue b/resources/js/Pages/Admin/MailProfiles/Index.vue index 077f211..e9cd7b2 100644 --- a/resources/js/Pages/Admin/MailProfiles/Index.vue +++ b/resources/js/Pages/Admin/MailProfiles/Index.vue @@ -18,8 +18,9 @@ const props = defineProps({ profiles: { type: Array, default: () => [] }, }); -const createOpen = ref(false); -const editTarget = ref(null); +const createOpen = ref(false); // create modal +const editOpen = ref(false); // edit modal +const editTarget = ref(null); // profile being edited const form = useForm({ name: "", @@ -39,6 +40,22 @@ function openCreate() { editTarget.value = null; } +function openEdit(p) { + // populate form with existing profile data (exclude password which is write-only) + form.reset(); + form.name = p.name || ""; + form.host = p.host || ""; + form.port = p.port || 587; + form.encryption = p.encryption || ""; + form.username = p.username || ""; + form.password = ""; // empty -> keep existing unless user fills + form.from_address = p.from_address || ""; + form.from_name = p.from_name || ""; + form.priority = p.priority ?? 10; + editTarget.value = p; + editOpen.value = true; +} + function closeCreate() { if (form.processing) return; createOpen.value = false; @@ -53,6 +70,37 @@ function submitCreate() { }); } +function closeEdit() { + if (form.processing) return; + editOpen.value = false; + editTarget.value = null; +} + +function submitEdit() { + if (!editTarget.value) return; + // Build payload excluding empty password + const payload = { + name: form.name, + host: form.host, + port: form.port, + encryption: form.encryption || null, + username: form.username || null, + from_address: form.from_address, + from_name: form.from_name || null, + priority: form.priority, + }; + if (form.password && form.password.trim() !== "") { + payload.password = form.password.trim(); + } + form.transform(() => payload).put(route("admin.mail-profiles.update", editTarget.value.id), { + preserveScroll: true, + onSuccess: () => { + editOpen.value = false; + editTarget.value = null; + }, + }); +} + function toggleActive(p) { window.axios .post(route("admin.mail-profiles.toggle", p.id)) @@ -160,9 +208,11 @@ const statusClass = (p) => { Pošlji test @@ -241,6 +291,61 @@ const statusClass = (p) => { + + + + + + diff --git a/resources/js/Pages/Cases/Partials/ActivityTable.vue b/resources/js/Pages/Cases/Partials/ActivityTable.vue index 3d769d2..79bc8b4 100644 --- a/resources/js/Pages/Cases/Partials/ActivityTable.vue +++ b/resources/js/Pages/Cases/Partials/ActivityTable.vue @@ -112,12 +112,7 @@ const confirmDeleteAction = () => { {{ row.contract.reference }} diff --git a/resources/js/Pages/FieldJob/Index.vue b/resources/js/Pages/FieldJob/Index.vue index e4d31e6..19cf2f3 100644 --- a/resources/js/Pages/FieldJob/Index.vue +++ b/resources/js/Pages/FieldJob/Index.vue @@ -2,6 +2,7 @@ import AppLayout from "@/Layouts/AppLayout.vue"; import { Link, useForm } from "@inertiajs/vue3"; import { computed, ref, watch } from "vue"; +import DataTableClient from "@/Components/DataTable/DataTableClient.vue"; const props = defineProps({ setting: Object, @@ -138,39 +139,129 @@ const assignedContractsFiltered = computed(() => { return list.filter(matchesSearch); }); -// Pagination state +// DataTableClient state per table +const unassignedSort = ref({ key: null, direction: null }); const unassignedPage = ref(1); -const unassignedPerPage = ref(10); +const unassignedPageSize = ref(10); +const assignedSort = ref({ key: null, direction: null }); const assignedPage = ref(1); -const assignedPerPage = ref(10); +const assignedPageSize = ref(10); -// Reset pages when filters change -watch([search], () => { +watch([search, assignedFilterUserId], () => { unassignedPage.value = 1; assignedPage.value = 1; }); -watch([assignedFilterUserId], () => { - assignedPage.value = 1; -}); -// Paginated lists -const unassignedTotal = computed(() => unassignedFiltered.value.length); -const unassignedTotalPages = computed(() => - Math.max(1, Math.ceil(unassignedTotal.value / unassignedPerPage.value)) -); -const unassignedPageItems = computed(() => { - const start = (unassignedPage.value - 1) * unassignedPerPage.value; - return unassignedFiltered.value.slice(start, start + unassignedPerPage.value); -}); +// Column definitions for DataTableClient +const unassignedColumns = [ + { key: "reference", label: "Pogodba", sortable: true, class: "w-32" }, + { + key: "case_person", + label: "Primer", + sortable: true, + formatter: (c) => c.client_case?.person?.full_name || "-", + }, + { + key: "address", + label: "Naslov", + sortable: true, + formatter: (c) => primaryCaseAddress(c), + }, + { + key: "client_person", + label: "Stranka", + sortable: true, + formatter: (c) => c.client?.person?.full_name || "-", + }, + { + key: "start_date", + label: "Začetek", + sortable: true, + formatter: (c) => formatDate(c.start_date), + }, + { + key: "balance_amount", + label: "Stanje", + align: "right", + sortable: true, + formatter: (c) => formatCurrencyEUR(c.account?.balance_amount), + }, + { key: "_actions", label: "Dejanje", class: "w-32" }, +]; -const assignedTotal = computed(() => assignedContractsFiltered.value.length); -const assignedTotalPages = computed(() => - Math.max(1, Math.ceil(assignedTotal.value / assignedPerPage.value)) +const assignedColumns = [ + { key: "reference", label: "Pogodba", sortable: true, class: "w-32" }, + { + key: "case_person", + label: "Primer", + sortable: true, + formatter: (c) => c.client_case?.person?.full_name || "-", + }, + { + key: "address", + label: "Naslov", + sortable: true, + formatter: (c) => primaryCaseAddress(c), + }, + { + key: "client_person", + label: "Stranka", + sortable: true, + formatter: (c) => c.client?.person?.full_name || "-", + }, + { + key: "assigned_at", + label: "Dodeljeno dne", + sortable: true, + formatter: (c) => formatDate(props.assignments?.[c.uuid]?.assigned_at), + }, + { + key: "assigned_to", + label: "Dodeljeno komu", + sortable: true, + formatter: (c) => assignedTo(c) || "-", + }, + { + key: "balance_amount", + label: "Stanje", + align: "right", + sortable: true, + formatter: (c) => formatCurrencyEUR(c.account?.balance_amount), + }, + { key: "_actions", label: "Dejanje", class: "w-32" }, +]; + +// Provide derived row arrays for DataTable (already filtered) +// Add a flat numeric property `balance_amount` so the generic table sorter can sort by value +// (original data nests it under account.balance_amount which the sorter cannot reach). +const unassignedRows = computed(() => + unassignedFiltered.value.map((c) => ({ + ...c, + // Ensure numeric so sorter treats it as number (server often returns string) + balance_amount: + c?.account?.balance_amount === null || c?.account?.balance_amount === undefined + ? null + : Number(c.account.balance_amount), + // Flatten derived text fields so DataTable sorting/searching works + case_person: c.client_case?.person?.full_name || null, + client_person: c.client?.person?.full_name || null, + address: primaryCaseAddress(c) || null, + assigned_to: null, // not assigned yet + })) +); +const assignedRows = computed(() => + assignedContractsFiltered.value.map((c) => ({ + ...c, + balance_amount: + c?.account?.balance_amount === null || c?.account?.balance_amount === undefined + ? null + : Number(c.account.balance_amount), + case_person: c.client_case?.person?.full_name || null, + client_person: c.client?.person?.full_name || null, + address: primaryCaseAddress(c) || null, + assigned_to: assignedTo(c) || null, + })) ); -const assignedPageItems = computed(() => { - const start = (assignedPage.value - 1) * assignedPerPage.value; - return assignedContractsFiltered.value.slice(start, start + assignedPerPage.value); -});