production #1
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user