Add more permissions
This commit is contained in:
@@ -319,7 +319,7 @@ watch(
|
||||
:auto-position="true"
|
||||
:teleport-target="'body'"
|
||||
:inline="false"
|
||||
:auto-apply="false"
|
||||
:auto-apply="true"
|
||||
:fixed="false"
|
||||
:close-on-auto-apply="true"
|
||||
:close-on-scroll="true"
|
||||
|
||||
@@ -14,6 +14,7 @@ library.add(faTrash, faEllipsisVertical, faCopy);
|
||||
const props = defineProps({
|
||||
client_case: Object,
|
||||
activities: Object,
|
||||
edit: Boolean,
|
||||
});
|
||||
|
||||
const fmtDate = (d) => {
|
||||
@@ -91,11 +92,11 @@ const copyToClipboard = async (text) => {
|
||||
// You could add a toast notification here if available
|
||||
} catch (err) {
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
@@ -115,7 +116,7 @@ const copyToClipboard = async (text) => {
|
||||
<th>Opomba</th>
|
||||
<th>Obljuba</th>
|
||||
<th>Dodal</th>
|
||||
<th class="w-8"></th>
|
||||
<th class="w-8" v-if="edit"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -174,7 +175,9 @@ const copyToClipboard = async (text) => {
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="relative" @click.stop>
|
||||
<div class="flex items-center justify-between p-1 border-b border-gray-200">
|
||||
<div
|
||||
class="flex items-center justify-between p-1 border-b border-gray-200"
|
||||
>
|
||||
<span class="text-xs font-medium text-gray-600">Opomba</span>
|
||||
<button
|
||||
@click="copyToClipboard(row.note)"
|
||||
@@ -226,7 +229,7 @@ const copyToClipboard = async (text) => {
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-2 pl-2 pr-2 align-middle text-right">
|
||||
<td class="py-2 pl-2 pr-2 align-middle text-right" v-if="edit">
|
||||
<Dropdown align="right" width="30">
|
||||
<template #trigger>
|
||||
<button
|
||||
|
||||
@@ -35,6 +35,7 @@ const props = defineProps({
|
||||
segments: { type: Array, default: () => [] },
|
||||
all_segments: { type: Array, default: () => [] },
|
||||
templates: { type: Array, default: () => [] }, // active document templates (latest per slug)
|
||||
edit: { type: Boolean, default: () => false },
|
||||
});
|
||||
|
||||
// Debug: log incoming contract balances (remove after fix)
|
||||
@@ -484,7 +485,7 @@ const closePaymentsDialog = () => {
|
||||
>
|
||||
Opis</FwbTableHeadCell
|
||||
>
|
||||
<FwbTableHeadCell class="w-px" />
|
||||
<FwbTableHeadCell class="w-px" v-if="edit" />
|
||||
</FwbTableHead>
|
||||
<FwbTableBody>
|
||||
<template v-for="(c, i) in contracts" :key="c.uuid || i">
|
||||
@@ -497,7 +498,7 @@ const closePaymentsDialog = () => {
|
||||
<span class="text-gray-700">{{
|
||||
contractActiveSegment(c)?.name || "-"
|
||||
}}</span>
|
||||
<Dropdown align="left">
|
||||
<Dropdown align="left" v-if="edit">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
@@ -701,7 +702,7 @@ const closePaymentsDialog = () => {
|
||||
</Dropdown>
|
||||
</div>
|
||||
</FwbTableCell>
|
||||
<FwbTableCell class="text-right whitespace-nowrap">
|
||||
<FwbTableCell class="text-right whitespace-nowrap" v-if="edit">
|
||||
<Dropdown align="right" width="56">
|
||||
<template #trigger>
|
||||
<button
|
||||
|
||||
@@ -13,11 +13,12 @@ import DocumentEditDialog from "@/Components/DocumentEditDialog.vue";
|
||||
import DocumentUploadDialog from "@/Components/DocumentUploadDialog.vue";
|
||||
import DocumentViewerDialog from "@/Components/DocumentViewerDialog.vue";
|
||||
import { classifyDocument } from "@/Services/documents";
|
||||
import { router, useForm } from "@inertiajs/vue3";
|
||||
import { router, useForm, usePage } from "@inertiajs/vue3";
|
||||
import { AngleDownIcon, AngleUpIcon } from "@/Utilities/Icons";
|
||||
import Pagination from "@/Components/Pagination.vue";
|
||||
import ConfirmDialog from "@/Components/ConfirmDialog.vue";
|
||||
import DialogModal from "@/Components/DialogModal.vue";
|
||||
import { hasPermission } from "@/Services/permissions";
|
||||
|
||||
const props = defineProps({
|
||||
client: Object,
|
||||
@@ -35,6 +36,7 @@ const props = defineProps({
|
||||
contract_doc_templates: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const showUpload = ref(false);
|
||||
const openUpload = () => {
|
||||
showUpload.value = true;
|
||||
@@ -47,20 +49,25 @@ const onUploaded = () => {
|
||||
router.reload({ only: ["documents"] });
|
||||
};
|
||||
|
||||
// Expose as a callable computed: use in templates as hasPerm('permission-slug')
|
||||
const hasPerm = computed(() => (permission) =>
|
||||
hasPermission(page.props.auth?.user, permission)
|
||||
);
|
||||
|
||||
// Document edit dialog state
|
||||
const showDocEdit = ref(false)
|
||||
const editingDoc = ref(null)
|
||||
const showDocEdit = ref(false);
|
||||
const editingDoc = ref(null);
|
||||
const openDocEdit = (doc) => {
|
||||
editingDoc.value = doc
|
||||
showDocEdit.value = true
|
||||
}
|
||||
editingDoc.value = doc;
|
||||
showDocEdit.value = true;
|
||||
};
|
||||
const closeDocEdit = () => {
|
||||
showDocEdit.value = false
|
||||
editingDoc.value = null
|
||||
}
|
||||
showDocEdit.value = false;
|
||||
editingDoc.value = null;
|
||||
};
|
||||
const onDocSaved = () => {
|
||||
router.reload({ only: ['documents'] })
|
||||
}
|
||||
router.reload({ only: ["documents"] });
|
||||
};
|
||||
|
||||
const viewer = ref({ open: false, src: "", title: "" });
|
||||
const openViewer = (doc) => {
|
||||
@@ -236,7 +243,11 @@ const submitAttachSegment = () => {
|
||||
class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4 border-blue-400"
|
||||
>
|
||||
<div class="mx-auto max-w-4x1 p-3">
|
||||
<PersonInfoGrid :types="types" :person="client.person" />
|
||||
<PersonInfoGrid
|
||||
:types="types"
|
||||
:person="client.person"
|
||||
:edit="hasPerm('client-edit')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -274,6 +285,7 @@ const submitAttachSegment = () => {
|
||||
:types="types"
|
||||
tab-color="red-600"
|
||||
:person="client_case.person"
|
||||
:person-edit="hasPerm('person-edit')"
|
||||
:enable-sms="true"
|
||||
:client-case-uuid="client_case.uuid"
|
||||
/>
|
||||
@@ -289,7 +301,7 @@ const submitAttachSegment = () => {
|
||||
<SectionTitle>
|
||||
<template #title> Pogodbe </template>
|
||||
</SectionTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2" v-if="hasPerm('contract-edit')">
|
||||
<FwbButton @click="openDrawerCreateContract">Nova</FwbButton>
|
||||
<FwbButton
|
||||
color="light"
|
||||
@@ -311,6 +323,7 @@ const submitAttachSegment = () => {
|
||||
:contract_types="contract_types"
|
||||
:segments="segments"
|
||||
:templates="contract_doc_templates"
|
||||
:edit="hasPerm('contract-edit')"
|
||||
@edit="openDrawerEditContract"
|
||||
@delete="requestDeleteContract"
|
||||
@add-activity="openDrawerAddActivity"
|
||||
@@ -330,7 +343,11 @@ const submitAttachSegment = () => {
|
||||
</SectionTitle>
|
||||
<FwbButton @click="openDrawerAddActivity">Nova</FwbButton>
|
||||
</div>
|
||||
<ActivityTable :client_case="client_case" :activities="activities" />
|
||||
<ActivityTable
|
||||
:client_case="client_case"
|
||||
:activities="activities"
|
||||
:edit="hasPerm('activity-edit')"
|
||||
/>
|
||||
<Pagination
|
||||
:links="activities.links"
|
||||
:from="activities.from"
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import PrimaryButton from "@/Components/PrimaryButton.vue";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import TextInput from "@/Components/TextInput.vue";
|
||||
import { Link, useForm, router } from "@inertiajs/vue3";
|
||||
import { Link, useForm, router, usePage } from "@inertiajs/vue3";
|
||||
import ActionMessage from "@/Components/ActionMessage.vue";
|
||||
import DialogModal from "@/Components/DialogModal.vue";
|
||||
import DataTableServer from "@/Components/DataTable/DataTableServer.vue";
|
||||
import { hasPermission } from "@/Services/permissions";
|
||||
|
||||
const props = defineProps({
|
||||
clients: Object,
|
||||
filters: Object,
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
// Expose as a callable computed: use in templates as hasPerm('permission-slug')
|
||||
const hasPerm = computed(() => (permission) =>
|
||||
hasPermission(page.props.auth?.user, permission)
|
||||
);
|
||||
|
||||
const Address = {
|
||||
address: "",
|
||||
country: "",
|
||||
@@ -87,7 +94,10 @@ const fmtCurrency = (v) => {
|
||||
<div class="px-3 bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||
<div class="mx-auto max-w-4x1 py-3 space-y-3">
|
||||
<!-- Top actions -->
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div
|
||||
class="flex items-center justify-between gap-3"
|
||||
v-if="hasPerm('client-edit')"
|
||||
>
|
||||
<PrimaryButton
|
||||
@click="openDrawerCreateClient"
|
||||
class="bg-blue-600 hover:bg-blue-700"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import PrimaryButton from "@/Components/PrimaryButton.vue";
|
||||
import { ref } from "vue";
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
import { computed, ref } from "vue";
|
||||
import { Link, usePage } from "@inertiajs/vue3";
|
||||
import SectionTitle from "@/Components/SectionTitle.vue";
|
||||
import PersonInfoGrid from "@/Components/PersonInfoGrid.vue";
|
||||
import FormCreateCase from "./Partials/FormCreateCase.vue";
|
||||
import DataTableServer from "@/Components/DataTable/DataTableServer.vue";
|
||||
import { hasPermission } from "@/Services/permissions";
|
||||
|
||||
const props = defineProps({
|
||||
client: Object,
|
||||
@@ -19,6 +20,11 @@ const props = defineProps({
|
||||
// Removed page-level search; DataTable or server can handle filtering elsewhere if needed
|
||||
// DataTable search state
|
||||
const search = ref(props.filters?.search || "");
|
||||
const page = usePage();
|
||||
// Expose as a callable computed: use in templates as hasPerm('permission-slug')
|
||||
const hasPerm = computed(() => (permission) =>
|
||||
hasPermission(page.props.auth?.user, permission)
|
||||
);
|
||||
|
||||
const drawerCreateCase = ref(false);
|
||||
|
||||
@@ -52,7 +58,7 @@ const openDrawerCreateCase = () => {
|
||||
'inline-flex items-center px-3 py-2 text-sm font-medium border-b-2',
|
||||
route().current('client.show')
|
||||
? 'text-indigo-600 border-indigo-600'
|
||||
: 'text-gray-600 border-transparent hover:text-gray-800 hover:border-gray-300'
|
||||
: 'text-gray-600 border-transparent hover:text-gray-800 hover:border-gray-300',
|
||||
]"
|
||||
>
|
||||
Primeri
|
||||
@@ -65,7 +71,7 @@ const openDrawerCreateCase = () => {
|
||||
'inline-flex items-center px-3 py-2 text-sm font-medium border-b-2',
|
||||
route().current('client.contracts')
|
||||
? 'text-indigo-600 border-indigo-600'
|
||||
: 'text-gray-600 border-transparent hover:text-gray-800 hover:border-gray-300'
|
||||
: 'text-gray-600 border-transparent hover:text-gray-800 hover:border-gray-300',
|
||||
]"
|
||||
>
|
||||
Pogodbe
|
||||
@@ -93,7 +99,10 @@ const openDrawerCreateCase = () => {
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="px-3 bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||
<div class="mx-auto max-w-4x1 py-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div
|
||||
class="flex items-center justify-between gap-3"
|
||||
v-if="hasPerm('case-edit')"
|
||||
>
|
||||
<PrimaryButton @click="openDrawerCreateCase" class="bg-blue-400"
|
||||
>Dodaj</PrimaryButton
|
||||
>
|
||||
|
||||
@@ -31,6 +31,8 @@ const toDelete = ref(null);
|
||||
const search = ref("");
|
||||
const selectedTemplateId = ref(null);
|
||||
const onlyAutoMail = ref(false);
|
||||
// Filter: selected events (multi-select)
|
||||
const selectedEvents = ref([]);
|
||||
|
||||
const actionOptions = ref([]);
|
||||
|
||||
@@ -214,6 +216,7 @@ function tryAdoptRaw(ev) {
|
||||
const filtered = computed(() => {
|
||||
const term = search.value?.toLowerCase() ?? "";
|
||||
const tplId = selectedTemplateId.value ? Number(selectedTemplateId.value) : null;
|
||||
const evIdSet = new Set((selectedEvents.value || []).map((e) => Number(e.id)));
|
||||
return (props.decisions || []).filter((d) => {
|
||||
const matchesSearch =
|
||||
!term ||
|
||||
@@ -221,7 +224,10 @@ const filtered = computed(() => {
|
||||
d.color_tag?.toLowerCase().includes(term);
|
||||
const matchesAuto = !onlyAutoMail.value || !!d.auto_mail;
|
||||
const matchesTemplate = !tplId || Number(d.email_template_id || 0) === tplId;
|
||||
return matchesSearch && matchesAuto && matchesTemplate;
|
||||
const rowEvents = Array.isArray(d.events) ? d.events : [];
|
||||
const matchesEvents =
|
||||
evIdSet.size === 0 || rowEvents.some((ev) => evIdSet.has(Number(ev.id)));
|
||||
return matchesSearch && matchesAuto && matchesTemplate && matchesEvents;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -343,28 +349,67 @@ const destroyDecision = () => {
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div class="flex flex-col sm:flex-row gap-2 items-start sm:items-center">
|
||||
<TextInput v-model="search" placeholder="Iskanje..." class="w-full sm:w-72" />
|
||||
<select
|
||||
v-model="selectedTemplateId"
|
||||
class="block w-full sm:w-64 border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
|
||||
>
|
||||
<option :value="null">Vse predloge</option>
|
||||
<option v-for="t in emailTemplates" :key="t.id" :value="t.id">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="onlyAutoMail"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500"
|
||||
/>
|
||||
Samo auto mail
|
||||
</label>
|
||||
<div class="p-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="w-full bg-gray-50 border rounded-md p-3">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-center">
|
||||
<!-- Search -->
|
||||
<div class="relative sm:col-span-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 21l-4.35-4.35m0 0A7.5 7.5 0 1010.5 18.5a7.5 7.5 0 006.15-1.85z"
|
||||
/>
|
||||
</svg>
|
||||
<TextInput v-model="search" placeholder="Iskanje..." class="w-full pl-9 h-10" />
|
||||
</div>
|
||||
<!-- Template select -->
|
||||
<div class="sm:col-span-3">
|
||||
<select
|
||||
v-model="selectedTemplateId"
|
||||
class="block w-full h-10 border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
|
||||
>
|
||||
<option :value="null">Vse predloge</option>
|
||||
<option v-for="t in emailTemplates" :key="t.id" :value="t.id">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Events multiselect -->
|
||||
<div class="sm:col-span-4">
|
||||
<multiselect
|
||||
v-model="selectedEvents"
|
||||
:options="availableEvents"
|
||||
:multiple="true"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="Filtriraj po dogodkih"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<!-- Only auto mail -->
|
||||
<div class="sm:col-span-2">
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="onlyAutoMail"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 h-4 w-4"
|
||||
/>
|
||||
Samo auto mail
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<PrimaryButton @click="openCreateDrawer">+ Dodaj odločitev</PrimaryButton>
|
||||
</div>
|
||||
<PrimaryButton @click="openCreateDrawer">+ Dodaj odločitev</PrimaryButton>
|
||||
</div>
|
||||
<div class="px-4 pb-4">
|
||||
<DataTableClient
|
||||
|
||||
Reference in New Issue
Block a user