Updated Application icon and notifcation pagination items per page, and updated NotificationsBell

This commit is contained in:
Simon Pocrnjič 2026-01-18 19:49:48 +01:00
parent cc4c07717e
commit 068bbdf583
6 changed files with 83 additions and 99 deletions

View File

@ -19,7 +19,7 @@ public function unread(Request $request)
} }
$today = now()->toDateString(); $today = now()->toDateString();
$perPage = max(1, min(100, (int) $request->integer('perPage', 15))); $perPage = max(1, min(100, (int) $request->integer('per_page', 15)));
$search = trim((string) $request->input('search', '')); $search = trim((string) $request->input('search', ''));
$clientUuid = trim((string) $request->input('client', '')); $clientUuid = trim((string) $request->input('client', ''));
$clientId = null; $clientId = null;

View File

@ -251,19 +251,17 @@ function isActive(patterns) {
: 'sticky top-0 h-screen overflow-y-auto', : 'sticky top-0 h-screen overflow-y-auto',
]" ]"
> >
<div <div class="h-16 px-4 flex items-center border-b border-sidebar-border bg-sidebar">
class="h-16 px-4 flex items-center justify-between border-b border-gray-200 bg-white"
>
<Link <Link
:href="route('dashboard')" :href="route('dashboard')"
class="flex items-center gap-2 hover:opacity-80 transition-opacity" class="flex items-center gap-1 hover:opacity-80 transition-opacity"
> >
<ApplicationMark /> <ApplicationMark />
<span <span
v-if="!sidebarCollapsed" v-if="!sidebarCollapsed"
class="text-sm font-semibold text-gray-900 transition-opacity" class="text-lg font-semibold text-sidebar-foreground transition-opacity"
> >
Admin Administrator
</span> </span>
</Link> </Link>
</div> </div>

View File

@ -10,19 +10,6 @@ import GlobalSearch from "./Partials/GlobalSearch.vue";
import NotificationsBell from "./Partials/NotificationsBell.vue"; import NotificationsBell from "./Partials/NotificationsBell.vue";
import ToastContainer from "@/Components/Toast/ToastContainer.vue"; import ToastContainer from "@/Components/Toast/ToastContainer.vue";
import { Button } from "@/Components/ui/button"; import { Button } from "@/Components/ui/button";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
faMobileScreenButton,
faGaugeHigh,
faLayerGroup,
faUserGroup,
faFolderOpen,
faFileImport,
faTableList,
faFileCirclePlus,
faMap,
faGear,
} from "@fortawesome/free-solid-svg-icons";
import { MenuIcon } from "lucide-vue-next"; import { MenuIcon } from "lucide-vue-next";
import { SearchIcon } from "lucide-vue-next"; import { SearchIcon } from "lucide-vue-next";
import { ChevronDownIcon } from "lucide-vue-next"; import { ChevronDownIcon } from "lucide-vue-next";
@ -310,18 +297,18 @@ function isActive(patterns) {
]" ]"
> >
<div <div
class="h-16 px-4 flex items-center justify-between border-b border-sidebar-border bg-sidebar" class="h-16 px-4 flex items-center border-b border-sidebar-border bg-sidebar"
> >
<Link <Link
:href="route('dashboard')" :href="route('dashboard')"
class="flex items-center gap-2 hover:opacity-80 transition-opacity" class="flex items-center gap-1 hover:opacity-80 transition-opacity"
> >
<ApplicationMark /> <ApplicationMark />
<span <span
v-if="!sidebarCollapsed" v-if="!sidebarCollapsed"
class="text-sm font-semibold text-sidebar-foreground transition-opacity" class="text-lg font-semibold text-sidebar-foreground transition-opacity"
> >
Teren Aplikacija
</span> </span>
</Link> </Link>
</div> </div>

View File

@ -149,14 +149,14 @@ const closeSearch = () => (searchOpen.value = false);
> >
<Link <Link
:href="route('phone.index')" :href="route('phone.index')"
class="flex items-center gap-2 hover:opacity-80 transition-opacity" class="flex items-center gap-1 hover:opacity-80 transition-opacity"
> >
<ApplicationMark /> <ApplicationMark />
<span <span
v-if="showLabels" v-if="showLabels"
class="text-sm font-semibold text-sidebar-foreground transition-opacity" class="text-lg font-semibold text-sidebar-foreground transition-opacity"
> >
Teren Mobitel
</span> </span>
</Link> </Link>
</div> </div>

View File

@ -1,12 +1,12 @@
<script setup> <script setup>
import { computed, onMounted, ref, watch } from "vue"; import { computed, onMounted, ref, watch } from "vue";
import { usePage, Link, router } from "@inertiajs/vue3"; import { usePage, Link, router } from "@inertiajs/vue3";
import Dropdown from "@/Components/Dropdown.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faBell } from "@fortawesome/free-solid-svg-icons";
import { BellIcon } from "lucide-vue-next"; import { BellIcon } from "lucide-vue-next";
import { Badge } from "@/Components/ui/badge"; import { Badge } from "@/Components/ui/badge";
import { Button } from "@/Components/ui/button"; import { Button } from "@/Components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/Components/ui/popover";
import { ScrollArea } from "@/Components/ui/scroll-area";
import { Separator } from "@/Components/ui/separator";
const page = usePage(); const page = usePage();
const due = computed( const due = computed(
@ -83,12 +83,8 @@ function markRead(item) {
</script> </script>
<template> <template>
<Dropdown <Popover>
align="right" <PopoverTrigger as-child>
width="72"
:content-classes="['p-0', 'bg-white', 'max-h-96', 'overflow-hidden']"
>
<template #trigger>
<Button variant="ghost" size="default" class="relative"> <Button variant="ghost" size="default" class="relative">
<BellIcon /> <BellIcon />
@ -100,32 +96,30 @@ function markRead(item) {
{{ count }} {{ count }}
</Badge> </Badge>
</Button> </Button>
</template> </PopoverTrigger>
<template #content> <PopoverContent align="end" class="w-96 p-0">
<div <div class="px-4 py-3 flex items-center justify-between border-b">
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 class="text-sm font-medium">Zapadejo danes</span>
>
<span>Zapadejo danes</span>
<Link <Link
:href="route('notifications.unread')" :href="route('notifications.unread')"
class="text-indigo-600 hover:text-indigo-700" class="text-sm text-primary hover:underline"
>Vsa obvestila</Link >Vsa obvestila</Link
> >
</div> </div>
<!-- Scrollable content area with max height -->
<div class="max-h-80 overflow-auto"> <ScrollArea class="h-72">
<div v-if="!count" class="px-3 py-3 text-sm text-gray-500"> <div v-if="!count" class="px-4 py-8 text-center">
Ni zapadlih aktivnosti danes. <p class="text-sm text-muted-foreground">Ni zapadlih aktivnosti danes.</p>
</div> </div>
<ul v-else class="divide-y"> <div v-else class="divide-y">
<li <div
v-for="item in items" v-for="item in items"
:key="item.id" :key="item.id"
class="px-3 py-2 text-sm flex items-start gap-2" class="px-4 py-3 flex items-start gap-3 hover:bg-accent/50 transition-colors"
> >
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0 space-y-1">
<div class="font-medium text-gray-800 truncate"> <div class="font-medium truncate">
<template v-if="item.contract?.uuid"> <template v-if="item.contract?.uuid">
Pogodba: Pogodba:
<Link <Link
@ -135,7 +129,7 @@ function markRead(item) {
client_case: item.contract.client_case.uuid, client_case: item.contract.client_case.uuid,
}) })
" "
class="text-indigo-600 hover:text-indigo-700 hover:underline" class="text-primary hover:underline"
> >
{{ item.contract?.reference || "—" }} {{ item.contract?.reference || "—" }}
</Link> </Link>
@ -148,7 +142,7 @@ function markRead(item) {
:href=" :href="
route('clientCase.show', { client_case: item.client_case.uuid }) route('clientCase.show', { client_case: item.client_case.uuid })
" "
class="text-indigo-600 hover:text-indigo-700 hover:underline" class="text-primary hover:underline"
> >
{{ item.client_case?.person?.full_name || "—" }} {{ item.client_case?.person?.full_name || "—" }}
</Link> </Link>
@ -157,37 +151,38 @@ function markRead(item) {
</div> </div>
<!-- Partner / Client full name (use contract.client when available; fallback to case.client) --> <!-- Partner / Client full name (use contract.client when available; fallback to case.client) -->
<div <div
class="text-xs text-gray-500 truncate" class="text-xs text-muted-foreground truncate"
v-if="item.contract?.client?.person?.full_name" v-if="item.contract?.client?.person?.full_name"
> >
Partner: {{ item.contract.client.person.full_name }} Partner: {{ item.contract.client.person.full_name }}
</div> </div>
<div <div
class="text-xs text-gray-500 truncate" class="text-xs text-muted-foreground truncate"
v-else-if="item.client_case?.client?.person?.full_name" v-else-if="item.client_case?.client?.person?.full_name"
> >
Partner: {{ item.client_case.client.person.full_name }} Partner: {{ item.client_case.client.person.full_name }}
</div> </div>
<div class="text-gray-600 truncate" v-if="item.contract"> <div class="text-sm truncate" v-if="item.contract">
{{ fmtEUR(item.contract?.account?.balance_amount) }} {{ fmtEUR(item.contract?.account?.balance_amount) }}
</div> </div>
</div> </div>
<div class="flex flex-col items-end gap-1"> <div class="flex flex-col items-end gap-1.5 shrink-0">
<div class="text-xs text-gray-500 whitespace-nowrap"> <div class="text-xs text-muted-foreground whitespace-nowrap">
{{ fmtDate(item.due_date) }} {{ fmtDate(item.due_date) }}
</div> </div>
<button <Button
type="button" variant="ghost"
class="text-[11px] text-gray-400 hover:text-gray-600" size="sm"
class="h-6 px-2 text-xs"
@click.stop="markRead(item)" @click.stop="markRead(item)"
title="Skrij obvestilo" title="Skrij obvestilo"
> >
Skrij Skrij
</button> </Button>
</div> </div>
</li> </div>
</ul> </div>
</div> </ScrollArea>
</template> </PopoverContent>
</Dropdown> </Popover>
</template> </template>

View File

@ -265,28 +265,30 @@ const update = () => {
// Transform actions from array of IDs to array of objects // Transform actions from array of IDs to array of objects
const actionsPayload = form.actions const actionsPayload = form.actions
.map(id => { .map((id) => {
const action = props.actions.find(a => a.id === Number(id) || a.id === id); const action = props.actions.find((a) => a.id === Number(id) || a.id === id);
if (!action) { if (!action) {
console.warn('Action not found for id:', id); console.warn("Action not found for id:", id);
return null; return null;
} }
return { id: action.id, name: action.name }; return { id: action.id, name: action.name };
}) })
.filter(Boolean); // Remove null entries .filter(Boolean); // Remove null entries
form.transform((data) => ({ form
...data, .transform((data) => ({
actions: actionsPayload ...data,
})).put(route("settings.decisions.update", { id: form.id }), { actions: actionsPayload,
onSuccess: () => { }))
closeEditDrawer(); .put(route("settings.decisions.update", { id: form.id }), {
}, onSuccess: () => {
onError: (errors) => { closeEditDrawer();
// preserve server errors for display },
scrollToFirstEventError(form.errors, "edit"); onError: (errors) => {
}, // preserve server errors for display
}); scrollToFirstEventError(form.errors, "edit");
},
});
}; };
const store = () => { const store = () => {
@ -299,27 +301,29 @@ const store = () => {
// Transform actions from array of IDs to array of objects // Transform actions from array of IDs to array of objects
const actionsPayload = createForm.actions const actionsPayload = createForm.actions
.map(id => { .map((id) => {
const action = props.actions.find(a => a.id === Number(id) || a.id === id); const action = props.actions.find((a) => a.id === Number(id) || a.id === id);
if (!action) { if (!action) {
console.warn('Action not found for id:', id); console.warn("Action not found for id:", id);
return null; return null;
} }
return { id: action.id, name: action.name }; return { id: action.id, name: action.name };
}) })
.filter(Boolean); // Remove null entries .filter(Boolean); // Remove null entries
createForm.transform((data) => ({ createForm
...data, .transform((data) => ({
actions: actionsPayload ...data,
})).post(route("settings.decisions.store"), { actions: actionsPayload,
onSuccess: () => { }))
closeCreateDrawer(); .post(route("settings.decisions.store"), {
}, onSuccess: () => {
onError: () => { closeCreateDrawer();
scrollToFirstEventError(createForm.errors, "create"); },
}, onError: () => {
}); scrollToFirstEventError(createForm.errors, "create");
},
});
}; };
function validateEventsClientSide(events) { function validateEventsClientSide(events) {
@ -665,7 +669,7 @@ const destroyDecision = () => {
</div> </div>
<div class="flex items-center gap-2 self-end"> <div class="flex items-center gap-2 self-end">
<label class="flex items-center gap-2 text-sm"> <label class="flex items-center gap-2 text-sm">
<Checkbox v-model:checked="ev.active" /> <Checkbox v-model="ev.active" />
Aktivno Aktivno
</label> </label>
<Button <Button
@ -703,7 +707,7 @@ const destroyDecision = () => {
</div> </div>
<div class="flex items-end"> <div class="flex items-end">
<label class="flex items-center gap-2 text-sm mt-6"> <label class="flex items-center gap-2 text-sm mt-6">
<Checkbox v-model:checked="ev.config.deactivate_previous" /> <Checkbox v-model="ev.config.deactivate_previous" />
Deaktiviraj prejšnje Deaktiviraj prejšnje
</label> </label>
</div> </div>