Teren-app/resources/js/Layouts/Partials/NotificationsBell.vue
2025-10-13 21:14:10 +02:00

167 lines
5.3 KiB
Vue

<script setup>
import { computed, onMounted, ref, watch } from "vue";
import { usePage, Link } from "@inertiajs/vue3";
import Dropdown from "@/Components/Dropdown.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faBell } from "@fortawesome/free-solid-svg-icons";
const page = usePage();
const due = computed(
() => page.props.notifications?.dueToday || { count: 0, items: [], date: null }
);
// Local, optimistically-updated list of items and derived count
const items = ref([]);
const count = computed(() => items.value.length);
function fmtDate(d) {
if (!d) return "";
try {
return new Date(d).toLocaleDateString("sl-SI");
} catch {
return String(d);
}
}
function fmtEUR(value) {
if (value === null || value === undefined) {
return "—";
}
const num = typeof value === "string" ? Number(value) : value;
if (Number.isNaN(num)) {
return String(value);
}
// de-DE locale: dot thousands, comma decimals, trailing Euro symbol
const formatted = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(num);
// Replace non-breaking space with normal space for consistency
return formatted.replace("\u00A0", " ");
}
onMounted(() => {
items.value = [...(due.value.items || [])];
});
watch(
() => due.value.items,
(val) => {
items.value = [...(val || [])];
}
);
async function markRead(item) {
const idx = items.value.findIndex((i) => i.id === item.id);
if (idx === -1) {
return;
}
// Optimistically remove
const removed = items.value.splice(idx, 1)[0];
try {
await window.axios.post(route("notifications.activity.read"), {
activity_id: item.id,
});
} catch (e) {
// Rollback on failure
items.value.splice(idx, 0, removed);
}
}
</script>
<template>
<Dropdown
align="right"
width="72"
:content-classes="['p-0', 'bg-white', 'max-h-96', 'overflow-hidden']"
>
<template #trigger>
<button
type="button"
class="relative inline-flex items-center justify-center w-9 h-9 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100"
aria-label="Notifications"
>
<FontAwesomeIcon :icon="faBell" class="w-5 h-5" />
<span
v-if="count"
class="absolute -top-1 -right-1 inline-flex items-center justify-center h-5 min-w-[1.25rem] px-1 rounded-full text-[11px] bg-red-600 text-white"
>{{ count }}</span
>
</button>
</template>
<template #content>
<div class="px-3 py-2 text-xs text-gray-400 border-b sticky top-0 bg-white z-10 flex items-center justify-between">
<span>Zapadejo danes</span>
<Link :href="route('notifications.unread')" class="text-indigo-600 hover:text-indigo-700">Vsa obvestila</Link>
</div>
<!-- Scrollable content area with max height -->
<div class="max-h-80 overflow-auto">
<div v-if="!count" class="px-3 py-3 text-sm text-gray-500">
Ni zapadlih aktivnosti danes.
</div>
<ul v-else class="divide-y">
<li
v-for="item in items"
:key="item.id"
class="px-3 py-2 text-sm flex items-start gap-2"
>
<div class="flex-1 min-w-0">
<div class="font-medium text-gray-800 truncate">
<template v-if="item.contract?.uuid">
Pogodba:
<Link
v-if="item.contract?.client_case?.uuid"
:href="
route('clientCase.show', {
client_case: item.contract.client_case.uuid,
})
"
class="text-indigo-600 hover:text-indigo-700 hover:underline"
>
{{ item.contract?.reference || "—" }}
</Link>
<span v-else>{{ item.contract?.reference || "—" }}</span>
</template>
<template v-else>
Primer:
<Link
v-if="item.client_case?.uuid"
:href="
route('clientCase.show', { client_case: item.client_case.uuid })
"
class="text-indigo-600 hover:text-indigo-700 hover:underline"
>
{{ item.client_case?.person?.full_name || "—" }}
</Link>
<span v-else>{{ item.client_case?.person?.full_name || "—" }}</span>
</template>
</div>
<div class="text-gray-600 truncate" v-if="item.contract">
{{ fmtEUR(item.contract?.account?.balance_amount) }}
</div>
</div>
<div class="flex flex-col items-end gap-1">
<div class="text-xs text-gray-500 whitespace-nowrap">
{{ fmtDate(item.due_date) }}
</div>
<button
type="button"
class="text-[11px] text-gray-400 hover:text-gray-600"
@click.stop="markRead(item)"
title="Skrij obvestilo"
>
Skrij
</button>
</div>
</li>
</ul>
</div>
</template>
</Dropdown>
</template>