Changes to UI and other stuff
This commit is contained in:
@@ -1,12 +1,28 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, useSlots, watch, onMounted } from "vue";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import DataTable from "@/Components/DataTable/DataTable.vue";
|
||||
import DataTable from "@/Components/DataTable/DataTableNew2.vue";
|
||||
import DeleteDialog from "@/Components/Dialogs/DeleteDialog.vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faTrash, faEllipsisVertical, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import Dropdown from "@/Components/Dropdown.vue";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import TableActions from "@/Components/DataTable/TableActions.vue";
|
||||
import ActionMenuItem from "@/Components/DataTable/ActionMenuItem.vue";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/Components/ui/command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/Components/ui/popover";
|
||||
import { RangeCalendar } from "@/Components/ui/range-calendar";
|
||||
import { CalendarIcon, X, Filter, Check, ChevronsUpDown } from "lucide-vue-next";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DateFormatter, getLocalTimeZone, parseDate } from "@internationalized/date";
|
||||
|
||||
library.add(faTrash, faEllipsisVertical, faCopy);
|
||||
|
||||
@@ -14,15 +30,219 @@ const props = defineProps({
|
||||
client_case: Object,
|
||||
activities: Object,
|
||||
edit: Boolean,
|
||||
actions: Array,
|
||||
contracts: [Object, Array],
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
// Filter state
|
||||
const filters = ref({
|
||||
activity_action_id: null,
|
||||
activity_contract_uuid: null,
|
||||
activity_user_id: null,
|
||||
activity_date_from: null,
|
||||
activity_date_to: null,
|
||||
});
|
||||
|
||||
// Date range for calendar
|
||||
const dateRange = ref(undefined);
|
||||
|
||||
// Filter popover state
|
||||
const filterPopoverOpen = ref(false);
|
||||
const actionComboboxOpen = ref(false);
|
||||
const contractComboboxOpen = ref(false);
|
||||
const userComboboxOpen = ref(false);
|
||||
|
||||
// Date formatter
|
||||
const df = new DateFormatter("sl-SI", {
|
||||
dateStyle: "medium",
|
||||
});
|
||||
|
||||
// Get unique users from activities for filter dropdown
|
||||
const uniqueUsers = computed(() => {
|
||||
const users = new Map();
|
||||
if (props.activities?.data) {
|
||||
props.activities.data.forEach((activity) => {
|
||||
if (activity.user) {
|
||||
users.set(activity.user.id, activity.user);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Array.from(users.values());
|
||||
});
|
||||
|
||||
// Get contract options - handle both array and object with data property
|
||||
const contractOptions = computed(() => {
|
||||
if (!props.contracts) return [];
|
||||
if (Array.isArray(props.contracts)) {
|
||||
return props.contracts;
|
||||
}
|
||||
if (props.contracts.data && Array.isArray(props.contracts.data)) {
|
||||
return props.contracts.data;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Get action options with their decisions
|
||||
const actionOptions = computed(() => {
|
||||
if (!props.actions || !Array.isArray(props.actions)) return [];
|
||||
return props.actions;
|
||||
});
|
||||
|
||||
// Check if any filters are active
|
||||
const hasActiveFilters = computed(() => {
|
||||
return Object.values(filters.value).some((val) => val !== null && val !== "");
|
||||
});
|
||||
|
||||
// Selected items for combobox display
|
||||
const selectedAction = computed(() =>
|
||||
actionOptions.value.find((a) => a.id === filters.value.activity_action_id)
|
||||
);
|
||||
|
||||
const selectedContract = computed(() =>
|
||||
contractOptions.value.find((c) => c.uuid === filters.value.activity_contract_uuid)
|
||||
);
|
||||
|
||||
const selectedUser = computed(() =>
|
||||
uniqueUsers.value.find((u) => u.id === filters.value.activity_user_id)
|
||||
);
|
||||
|
||||
// Apply filters
|
||||
const applyFilters = () => {
|
||||
// Sync date range to filter values
|
||||
if (dateRange.value?.start) {
|
||||
filters.value.activity_date_from = dateRange.value.start.toString();
|
||||
}
|
||||
if (dateRange.value?.end) {
|
||||
filters.value.activity_date_to = dateRange.value.end.toString();
|
||||
}
|
||||
|
||||
// Build filter object with only non-null values
|
||||
const filterObj = {};
|
||||
if (filters.value.activity_action_id) {
|
||||
filterObj.action_id = filters.value.activity_action_id;
|
||||
}
|
||||
if (filters.value.activity_contract_uuid) {
|
||||
filterObj.contract_uuid = filters.value.activity_contract_uuid;
|
||||
}
|
||||
if (filters.value.activity_user_id) {
|
||||
filterObj.user_id = filters.value.activity_user_id;
|
||||
}
|
||||
if (filters.value.activity_date_from) {
|
||||
filterObj.date_from = filters.value.activity_date_from;
|
||||
}
|
||||
if (filters.value.activity_date_to) {
|
||||
filterObj.date_to = filters.value.activity_date_to;
|
||||
}
|
||||
|
||||
console.log("Applying filters:", filterObj);
|
||||
|
||||
// Build query params object
|
||||
const queryParams = {};
|
||||
|
||||
// Preserve existing query params (like segment)
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
if (key !== "filter_activities") {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add filter param if there are active filters
|
||||
if (Object.keys(filterObj).length > 0) {
|
||||
const compressed = btoa(JSON.stringify(filterObj));
|
||||
console.log("Compressed filter:", compressed);
|
||||
queryParams.filter_activities = compressed;
|
||||
}
|
||||
|
||||
console.log("Query params:", queryParams);
|
||||
|
||||
router.get(
|
||||
route("clientCase.show", {
|
||||
client_case: props.client_case.uuid,
|
||||
...queryParams,
|
||||
}),
|
||||
{},
|
||||
{
|
||||
only: ["activities"],
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
}
|
||||
);
|
||||
filterPopoverOpen.value = false;
|
||||
};
|
||||
|
||||
// Clear all filters
|
||||
const clearFilters = () => {
|
||||
filters.value = {
|
||||
activity_action_id: null,
|
||||
activity_contract_uuid: null,
|
||||
activity_user_id: null,
|
||||
activity_date_from: null,
|
||||
activity_date_to: null,
|
||||
};
|
||||
dateRange.value = undefined;
|
||||
filterPopoverOpen.value = false;
|
||||
applyFilters();
|
||||
};
|
||||
|
||||
// Load filters from URL on mount
|
||||
onMounted(() => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const filterParam = searchParams.get("filter_activities");
|
||||
|
||||
if (filterParam) {
|
||||
try {
|
||||
const decompressed = atob(filterParam);
|
||||
const filterObj = JSON.parse(decompressed);
|
||||
|
||||
if (filterObj) {
|
||||
filters.value.activity_action_id = filterObj.action_id || null;
|
||||
filters.value.activity_contract_uuid = filterObj.contract_uuid || null;
|
||||
filters.value.activity_user_id = filterObj.user_id || null;
|
||||
filters.value.activity_date_from = filterObj.date_from || null;
|
||||
filters.value.activity_date_to = filterObj.date_to || null;
|
||||
|
||||
// Parse dates for calendar
|
||||
if (filterObj.date_from && filterObj.date_to) {
|
||||
try {
|
||||
dateRange.value = {
|
||||
start: parseDate(filterObj.date_from),
|
||||
end: parseDate(filterObj.date_to),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to parse dates:", e);
|
||||
}
|
||||
} else if (filterObj.date_from) {
|
||||
try {
|
||||
dateRange.value = {
|
||||
start: parseDate(filterObj.date_from),
|
||||
end: parseDate(filterObj.date_from),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to parse date_from:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load filters from URL:", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ key: "decision_dot", label: " ", class: "w-[6%]" },
|
||||
{ key: "contract", label: "Pogodba", class: "w-[14%]" },
|
||||
{ key: "decision", label: "Odločitev", class: "w-[26%]" },
|
||||
{ key: "note", label: "Opomba", class: "w-[14%]" },
|
||||
{ key: "promise", label: "Obljuba", class: "w-[20%]" },
|
||||
{ key: "user", label: "Dodal", class: "w-[10%]" },
|
||||
{ key: "decision_dot", label: "", sortable: false, align: "center" },
|
||||
{ key: "contract", label: "Pogodba", sortable: false },
|
||||
{ key: "decision", label: "Odločitev", sortable: false },
|
||||
{ key: "note", label: "Opomba", sortable: false },
|
||||
{ key: "promise", label: "Obljuba", sortable: false },
|
||||
{ key: "user", label: "Dodal", sortable: false },
|
||||
{ key: "actions", label: "", sortable: false, hideable: false, align: "center" },
|
||||
];
|
||||
|
||||
const rows = computed(() => props.activities?.data || []);
|
||||
@@ -59,7 +279,9 @@ const fmtDateTime = (d) => {
|
||||
const fmtCurrency = (v) => {
|
||||
const n = Number(v ?? 0);
|
||||
try {
|
||||
return new Intl.NumberFormat("sl-SI", { style: "currency", currency: "EUR" }).format(n);
|
||||
return new Intl.NumberFormat("sl-SI", { style: "currency", currency: "EUR" }).format(
|
||||
n
|
||||
);
|
||||
} catch {
|
||||
return `${n.toFixed(2)} €`;
|
||||
}
|
||||
@@ -109,141 +331,450 @@ const copyToClipboard = async (text) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="space-y-4">
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:show-toolbar="true"
|
||||
:data="rows"
|
||||
:meta="activities"
|
||||
route-name="clientCase.show"
|
||||
:route-params="{ client_case: client_case.uuid }"
|
||||
:only-props="['activities']"
|
||||
:page-size="pageSize"
|
||||
:page-size-options="[10, 15, 25, 50, 100]"
|
||||
page-param-name="activities_page"
|
||||
per-page-param-name="activities_per_page"
|
||||
:show-pagination="false"
|
||||
:show-search="false"
|
||||
:show-page-size="false"
|
||||
:show-add="!!$slots.add"
|
||||
:show-toolbar="true"
|
||||
:hoverable="true"
|
||||
row-key="id"
|
||||
empty-text="Ni aktivnosti."
|
||||
class="border-0"
|
||||
>
|
||||
<template #toolbar-add>
|
||||
<slot name="add" />
|
||||
</template>
|
||||
<template #toolbar-filters>
|
||||
<!-- Filter Popover -->
|
||||
<Popover v-model:open="filterPopoverOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" size="sm" class="gap-2">
|
||||
<Filter class="h-4 w-4" />
|
||||
Filtri
|
||||
<span
|
||||
v-if="hasActiveFilters"
|
||||
class="ml-1 rounded-full bg-primary px-2 py-0.5 text-xs text-primary-foreground"
|
||||
>
|
||||
{{ Object.values(filters).filter((v) => v !== null && v !== "").length }}
|
||||
</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[400px]" align="start">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-medium text-sm">Filtri aktivnosti</h4>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Izberite filtre za prikaz aktivnosti
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<template #cell-decision_dot="{ row }">
|
||||
<div class="flex justify-center">
|
||||
<span
|
||||
v-if="row.decision?.color_tag"
|
||||
class="inline-block h-4 w-4 rounded-full ring-1 ring-gray-300"
|
||||
:style="{ backgroundColor: row.decision?.color_tag }"
|
||||
:title="row.decision?.color_tag"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-contract="{ row }">
|
||||
<span v-if="row.contract?.reference">{{ row.contract.reference }}</span>
|
||||
<span v-else class="text-gray-400">—</span>
|
||||
</template>
|
||||
|
||||
<template #cell-decision="{ row }">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
v-if="row.action?.name"
|
||||
class="inline-block w-fit px-2 py-0.5 rounded text-[10px] font-medium bg-indigo-100 text-indigo-700 tracking-wide uppercase"
|
||||
>
|
||||
{{ row.action.name }}
|
||||
</span>
|
||||
<span class="text-gray-800">{{ row.decision?.name || "" }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-note="{ row }">
|
||||
<div class="max-w-[280px] whitespace-pre-wrap break-words leading-snug">
|
||||
<template v-if="row.note && row.note.length <= 60">
|
||||
{{ row.note }}
|
||||
</template>
|
||||
<template v-else-if="row.note">
|
||||
<span>{{ row.note.slice(0, 60) }}… </span>
|
||||
<Dropdown align="left" width="56" :content-classes="['p-2', 'bg-white', 'shadow', 'max-w-xs']">
|
||||
<template #trigger>
|
||||
<button type="button" class="inline-flex items-center text-[11px] text-indigo-600 hover:underline">
|
||||
Več
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="relative" @click.stop>
|
||||
<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)"
|
||||
class="flex items-center gap-1 px-2 py-1 text-xs text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded transition-colors"
|
||||
title="Kopiraj v odložišče"
|
||||
<div class="space-y-3">
|
||||
<!-- Action Filter -->
|
||||
<div class="space-y-1.5">
|
||||
<label class="text-sm font-medium">Akcija</label>
|
||||
<Popover v-model:open="actionComboboxOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
:aria-expanded="actionComboboxOpen"
|
||||
class="w-full justify-between"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faCopy" class="w-3 h-3" />
|
||||
<span>Kopiraj</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="max-h-60 overflow-auto text-[12px] whitespace-pre-wrap break-words p-2">
|
||||
{{ row.note }}
|
||||
</div>
|
||||
{{ selectedAction?.name || "Vse akcije" }}
|
||||
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-full p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Išči akcijo..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>Akcija ni najdena.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
:value="'null'"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_action_id = null;
|
||||
actionComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_action_id === null
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
Vse akcije
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
v-for="action in actionOptions"
|
||||
:key="action.id"
|
||||
:value="action.name"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_action_id = action.id;
|
||||
actionComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_action_id === action.id
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
{{ action.name }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<!-- Contract Filter -->
|
||||
<div class="space-y-1.5">
|
||||
<label class="text-sm font-medium">Pogodba</label>
|
||||
<Popover v-model:open="contractComboboxOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
:aria-expanded="contractComboboxOpen"
|
||||
class="w-full justify-between"
|
||||
>
|
||||
{{ selectedContract?.reference || "Vse pogodbe" }}
|
||||
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-full p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Išči pogodbo..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>Pogodba ni najdena.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
:value="'null'"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_contract_uuid = null;
|
||||
contractComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_contract_uuid === null
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
Vse pogodbe
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
v-for="contract in contractOptions"
|
||||
:key="contract.uuid"
|
||||
:value="contract.reference"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_contract_uuid = contract.uuid;
|
||||
contractComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_contract_uuid === contract.uuid
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
{{ contract.reference }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<!-- User Filter -->
|
||||
<div class="space-y-1.5">
|
||||
<label class="text-sm font-medium">Uporabnik</label>
|
||||
<Popover v-model:open="userComboboxOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
:aria-expanded="userComboboxOpen"
|
||||
class="w-full justify-between"
|
||||
>
|
||||
{{ selectedUser?.name || "Vsi uporabniki" }}
|
||||
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-full p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Išči uporabnika..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>Uporabnik ni najden.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
:value="'null'"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_user_id = null;
|
||||
userComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_user_id === null
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
Vsi uporabniki
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
v-for="user in uniqueUsers"
|
||||
:key="user.id"
|
||||
:value="user.name"
|
||||
@select="
|
||||
() => {
|
||||
filters.activity_user_id = user.id;
|
||||
userComboboxOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<Check
|
||||
:class="
|
||||
cn(
|
||||
'mr-2 h-4 w-4',
|
||||
filters.activity_user_id === user.id
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
{{ user.name }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<!-- Date Range -->
|
||||
<div class="space-y-1.5">
|
||||
<label class="text-sm font-medium">Časovno obdobje</label>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="[
|
||||
'w-full justify-start text-left font-normal',
|
||||
!dateRange?.start && 'text-muted-foreground',
|
||||
]"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="dateRange?.start">
|
||||
<template v-if="dateRange?.end">
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
|
||||
-
|
||||
{{ df.format(dateRange.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else> Izberite obdobje </template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0" align="start">
|
||||
<RangeCalendar
|
||||
v-model="dateRange"
|
||||
:number-of-months="2"
|
||||
locale="sl-SI"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center justify-between pt-2 border-t">
|
||||
<Button
|
||||
v-if="hasActiveFilters"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="clearFilters"
|
||||
class="gap-2"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
Počisti
|
||||
</Button>
|
||||
<div v-else></div>
|
||||
<Button variant="default" size="sm" @click="applyFilters" class="gap-2">
|
||||
Uporabi
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<slot name="add" />
|
||||
</template>
|
||||
|
||||
<template #cell-decision_dot="{ row }">
|
||||
<div class="flex justify-center">
|
||||
<span
|
||||
v-if="row.decision?.color_tag"
|
||||
class="inline-block h-4 w-4 rounded-full ring-1 ring-gray-300"
|
||||
:style="{ backgroundColor: row.decision?.color_tag }"
|
||||
:title="row.decision?.color_tag"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-contract="{ row }">
|
||||
<span v-if="row.contract?.reference">{{ row.contract.reference }}</span>
|
||||
<span v-else class="text-gray-400">—</span>
|
||||
</template>
|
||||
|
||||
<template #cell-decision="{ row }">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
v-if="row.action?.name"
|
||||
class="inline-block w-fit px-2 py-0.5 rounded text-[10px] font-medium bg-indigo-100 text-indigo-700 tracking-wide uppercase"
|
||||
>
|
||||
{{ row.action.name }}
|
||||
</span>
|
||||
<span class="text-gray-800">{{ row.decision?.name || "" }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-note="{ row }">
|
||||
<div class="max-w-[280px] whitespace-pre-wrap wrap-break-word leading-snug">
|
||||
<template v-if="row.note && row.note.length <= 60">
|
||||
{{ row.note }}
|
||||
</template>
|
||||
<template v-else-if="row.note">
|
||||
<span>{{ row.note.slice(0, 60) }}… </span>
|
||||
<Dropdown
|
||||
align="left"
|
||||
width="56"
|
||||
:content-classes="['p-2', 'bg-white', 'shadow', 'max-w-xs']"
|
||||
>
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center text-[11px] text-indigo-600 hover:underline"
|
||||
>
|
||||
Več
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="relative" @click.stop>
|
||||
<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)"
|
||||
class="flex items-center gap-1 px-2 py-1 text-xs text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded transition-colors"
|
||||
title="Kopiraj v odložišče"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faCopy" class="w-3 h-3" />
|
||||
<span>Kopiraj</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-400">—</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
class="max-h-60 overflow-auto text-[12px] whitespace-pre-wrap wrap-break-word p-2"
|
||||
>
|
||||
{{ row.note }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-400">—</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-promise="{ row }">
|
||||
<div class="flex flex-col gap-1 text-[12px]">
|
||||
<div v-if="row.amount && Number(row.amount) !== 0" class="leading-tight">
|
||||
<span class="text-gray-500">Z:</span>
|
||||
<span class="font-medium ml-1">{{ fmtCurrency(row.amount) }}</span>
|
||||
</div>
|
||||
<div v-if="row.due_date" class="leading-tight">
|
||||
<span class="text-gray-500">D:</span>
|
||||
<span class="ml-1">{{ fmtDate(row.due_date) }}</span>
|
||||
</div>
|
||||
<div v-if="!row.due_date && (!row.amount || Number(row.amount) === 0)" class="text-gray-400">
|
||||
—
|
||||
</div>
|
||||
<template #cell-promise="{ row }">
|
||||
<div class="flex flex-col gap-1 text-[12px]">
|
||||
<div v-if="row.amount && Number(row.amount) !== 0" class="leading-tight">
|
||||
<span class="text-gray-500">Z:</span>
|
||||
<span class="font-medium ml-1">{{ fmtCurrency(row.amount) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="row.due_date" class="leading-tight">
|
||||
<span class="text-gray-500">D:</span>
|
||||
<span class="ml-1">{{ fmtDate(row.due_date) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!row.due_date && (!row.amount || Number(row.amount) === 0)"
|
||||
class="text-gray-400"
|
||||
>
|
||||
—
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-user="{ row }">
|
||||
<div class="text-gray-800 font-medium leading-tight">
|
||||
{{ row.user?.name || row.user_name || "" }}
|
||||
</div>
|
||||
<div v-if="row.created_at" class="mt-1">
|
||||
<span class="inline-block px-2 py-0.5 rounded text-[11px] bg-gray-100 text-gray-600 tracking-wide">
|
||||
{{ fmtDateTime(row.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-user="{ row }">
|
||||
<div class="text-gray-800 font-medium leading-tight">
|
||||
{{ row.user?.name || row.user_name || "" }}
|
||||
</div>
|
||||
<div v-if="row.created_at" class="mt-1">
|
||||
<span
|
||||
class="inline-block px-2 py-0.5 rounded text-[11px] bg-gray-100 text-gray-600 tracking-wide"
|
||||
>
|
||||
{{ fmtDateTime(row.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions="{ row }" v-if="edit">
|
||||
<Dropdown align="right" width="30">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 focus:outline-none"
|
||||
title="Možnosti"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faEllipsisVertical" class="h-4 w-4 text-gray-700" />
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-red-50 text-red-600"
|
||||
@click.stop="openDelete(row)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'trash']" class="w-4 h-4" />
|
||||
<span>Izbriši</span>
|
||||
</button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<template #cell-actions="{ row }" v-if="edit">
|
||||
<TableActions align="right">
|
||||
<template #default>
|
||||
<ActionMenuItem
|
||||
:icon="faTrash"
|
||||
label="Izbriši"
|
||||
danger
|
||||
@click="openDelete(row)"
|
||||
/>
|
||||
</template>
|
||||
</TableActions>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
@@ -256,4 +787,3 @@ const copyToClipboard = async (text) => {
|
||||
@confirm="confirmDeleteAction"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { router, useForm } from "@inertiajs/vue3";
|
||||
import DataTable from "@/Components/DataTable/DataTable.vue";
|
||||
import DataTable from "@/Components/DataTable/DataTableNew2.vue";
|
||||
import StatusBadge from "@/Components/DataTable/StatusBadge.vue";
|
||||
import TableActions from "@/Components/DataTable/TableActions.vue";
|
||||
import ActionMenuItem from "@/Components/DataTable/ActionMenuItem.vue";
|
||||
import Dropdown from "@/Components/Dropdown.vue";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/Components/ui/dropdown-menu";
|
||||
import CaseObjectCreateDialog from "./CaseObjectCreateDialog.vue";
|
||||
import CaseObjectsDialog from "./CaseObjectsDialog.vue";
|
||||
import PaymentDialog from "./PaymentDialog.vue";
|
||||
@@ -27,6 +32,7 @@ import {
|
||||
faFolderOpen,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import EmptyState from "@/Components/EmptyState.vue";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
|
||||
const props = defineProps({
|
||||
client: { type: Object, default: null },
|
||||
@@ -40,7 +46,7 @@ const props = defineProps({
|
||||
createDoc: { type: Boolean, default: () => false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["edit", "delete", "add-activity"]);
|
||||
const emit = defineEmits(["edit", "delete", "add-activity", "create", "attach-segment"]);
|
||||
|
||||
const formatDate = (d) => {
|
||||
if (!d) return "-";
|
||||
@@ -106,7 +112,8 @@ const getMetaEntries = (c) => {
|
||||
const hasValue = Object.prototype.hasOwnProperty.call(node, "value");
|
||||
const hasTitle = Object.prototype.hasOwnProperty.call(node, "title");
|
||||
if (hasValue || hasTitle) {
|
||||
const title = (node.title || keyName || "").toString().trim() || keyName || "Meta";
|
||||
const title =
|
||||
(node.title || keyName || "").toString().trim() || keyName || "Meta";
|
||||
results.push({ title, value: node.value, type: node.type });
|
||||
return;
|
||||
}
|
||||
@@ -428,36 +435,54 @@ const closePaymentsDialog = () => {
|
||||
|
||||
// Columns configuration
|
||||
const columns = computed(() => [
|
||||
{ key: "reference", label: "Ref.", sortable: false },
|
||||
{ key: "reference", label: "Ref.", sortable: false, align: "center" },
|
||||
{ key: "start_date", label: "Datum začetka", sortable: false },
|
||||
{ key: "type", label: "Tip", sortable: false },
|
||||
{ key: "segment", label: "Segment", sortable: false },
|
||||
{ key: "initial_amount", label: "Predano", sortable: false, align: "right" },
|
||||
{ key: "balance_amount", label: "Odprto", sortable: false, align: "right" },
|
||||
{ key: "meta_info", label: "Opis", sortable: false, align: "center" },
|
||||
{ key: "actions", label: "", sortable: false, hideable: false, align: "center" },
|
||||
]);
|
||||
|
||||
const onEdit = (c) => emit("edit", c);
|
||||
const onDelete = (c) => emit("delete", c);
|
||||
const onAddActivity = (c) => emit("add-activity", c);
|
||||
const onCreate = () => emit("create");
|
||||
const onAttachSegment = () => emit("attach-segment");
|
||||
|
||||
const availableSegmentsCount = computed(() => {
|
||||
const current = new Set((props.segments || []).map((s) => s.id));
|
||||
return (props.all_segments || []).filter((s) => !current.has(s.id)).length;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="contracts"
|
||||
:data="contracts"
|
||||
:empty-icon="faFolderOpen"
|
||||
empty-text="Ni pogodb"
|
||||
empty-description="Za ta primer še ni ustvarjenih pogodb. Ustvarite novo pogodbo za začetek."
|
||||
:show-pagination="false"
|
||||
:show-toolbar="false"
|
||||
:striped="true"
|
||||
:show-toolbar="true"
|
||||
:hoverable="true"
|
||||
>
|
||||
<!-- Toolbar Actions -->
|
||||
<template #toolbar-actions v-if="edit">
|
||||
<Button variant="outline" @click="onCreate"> Nova </Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="onAttachSegment"
|
||||
:disabled="availableSegmentsCount === 0"
|
||||
>
|
||||
{{ availableSegmentsCount ? "Dodaj segment" : "Ni razpoložljivih segmentov" }}
|
||||
</Button>
|
||||
</template>
|
||||
<!-- Reference -->
|
||||
<template #cell-reference="{ row }">
|
||||
<span class="font-medium text-gray-900">{{ row.reference }}</span>
|
||||
<span class="font-medium text-gray-900 px-2">{{ row.reference }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Start Date -->
|
||||
@@ -474,11 +499,11 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
<template #cell-segment="{ row }">
|
||||
<div class="flex items-center gap-2" @click.stop>
|
||||
<span class="text-gray-700">{{ contractActiveSegment(row)?.name || "-" }}</span>
|
||||
<Dropdown align="left" v-if="edit">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-7 w-7 rounded-full hover:bg-gray-100 transition-colors"
|
||||
<DropdownMenu v-if="edit">
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
:class="{
|
||||
'opacity-50 cursor-not-allowed':
|
||||
!segments || segments.length === 0 || !row.active,
|
||||
@@ -493,64 +518,68 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
:disabled="!row.active || !segments || !segments.length"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPenToSquare" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="py-1">
|
||||
<template v-if="segments && segments.length">
|
||||
<button
|
||||
v-for="s in sortedSegments"
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<template v-if="segments && segments.length">
|
||||
<DropdownMenuItem
|
||||
v-for="s in sortedSegments"
|
||||
:key="s.id"
|
||||
@click="askChangeSegment(row, s.id)"
|
||||
>
|
||||
{{ s.name }}
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="all_segments && all_segments.length">
|
||||
<div class="px-3 py-2 text-xs text-gray-500">
|
||||
Ni segmentov v tem primeru. Dodaj in nastavi segment:
|
||||
</div>
|
||||
<DropdownMenuItem
|
||||
v-for="s in sortedAllSegments"
|
||||
:key="s.id"
|
||||
type="button"
|
||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50"
|
||||
@click="askChangeSegment(row, s.id)"
|
||||
@click.stop
|
||||
@click="askChangeSegment(row, s.id, true)"
|
||||
>
|
||||
{{ s.name }}
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="all_segments && all_segments.length">
|
||||
<div class="px-3 py-2 text-xs text-gray-500">
|
||||
Ni segmentov v tem primeru. Dodaj in nastavi segment:
|
||||
</div>
|
||||
<button
|
||||
v-for="s in sortedAllSegments"
|
||||
:key="s.id"
|
||||
type="button"
|
||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-gray-50"
|
||||
@click="askChangeSegment(row, s.id, true)"
|
||||
>
|
||||
{{ s.name }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="px-3 py-2 text-sm text-gray-500">Ni konfiguriranih segmentov.</div>
|
||||
</template>
|
||||
<div class="px-3 py-2 text-sm text-gray-500">
|
||||
Ni konfiguriranih segmentov.
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<StatusBadge v-if="!row.active" status="Arhivirano" variant="default" size="sm" />
|
||||
</template>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<StatusBadge
|
||||
v-if="!row.active"
|
||||
status="Arhivirano"
|
||||
variant="default"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Initial Amount -->
|
||||
<template #cell-initial_amount="{ row }">
|
||||
<div class="text-right">{{ formatCurrency(row?.account?.initial_amount ?? 0) }}</div>
|
||||
<div class="text-right">
|
||||
{{ formatCurrency(row?.account?.initial_amount ?? 0) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Balance Amount -->
|
||||
<template #cell-balance_amount="{ row }">
|
||||
<div class="text-right">{{ formatCurrency(row?.account?.balance_amount ?? 0) }}</div>
|
||||
<div class="text-right">
|
||||
{{ formatCurrency(row?.account?.balance_amount ?? 0) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Meta Info -->
|
||||
<template #cell-meta_info="{ row }">
|
||||
<div class="inline-flex items-center justify-center gap-0.5" @click.stop>
|
||||
<!-- Description -->
|
||||
<Dropdown align="right">
|
||||
<template #trigger>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-5 w-5 rounded-full transition-colors"
|
||||
@@ -564,17 +593,17 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
>
|
||||
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4" />
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<div class="max-w-sm px-3 py-2 text-sm text-gray-700 whitespace-pre-wrap">
|
||||
{{ row.description }}
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<!-- Meta -->
|
||||
<Dropdown align="right">
|
||||
<template #trigger>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-5 w-5 rounded-full transition-colors"
|
||||
@@ -588,29 +617,33 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
>
|
||||
<FontAwesomeIcon :icon="faTags" class="h-4 w-4" />
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<div class="max-w-sm px-3 py-2 text-sm text-gray-700">
|
||||
<template v-if="hasMeta(row)">
|
||||
<div
|
||||
v-for="(m, idx) in getMetaEntries(row)"
|
||||
:key="idx"
|
||||
class="flex items-start gap-2 py-0.5"
|
||||
class="flex flex-col items-start gap-0.5 py-0.5 mb-0.5"
|
||||
>
|
||||
<span class="text-gray-500 whitespace-nowrap">{{ m.title }}:</span>
|
||||
<span class="text-gray-800">{{ formatMetaValue(m) }}</span>
|
||||
<span class="text-gray-500 text-xs whitespace-nowrap">{{
|
||||
m.title
|
||||
}}</span>
|
||||
<span class="text-gray-800 font-medium break-all">{{
|
||||
formatMetaValue(m)
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text-gray-500">Ni meta podatkov.</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<!-- Promise Date -->
|
||||
<Dropdown align="right">
|
||||
<template #trigger>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center h-5 w-5 rounded-full hover:bg-gray-100 focus:outline-none transition-colors"
|
||||
@@ -621,13 +654,21 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
"
|
||||
:disabled="!getPromiseDate(row)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faClock" class="h-4 w-4" :class="promiseColorClass(row)" />
|
||||
<FontAwesomeIcon
|
||||
:icon="faClock"
|
||||
class="h-4 w-4"
|
||||
:class="promiseColorClass(row)"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<div class="px-3 py-2 text-sm text-gray-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<FontAwesomeIcon :icon="faClock" class="h-4 w-4" :class="promiseColorClass(row)" />
|
||||
<FontAwesomeIcon
|
||||
:icon="faClock"
|
||||
class="h-4 w-4"
|
||||
:class="promiseColorClass(row)"
|
||||
/>
|
||||
<span class="font-medium">Obljubljeno plačilo</span>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
@@ -645,159 +686,174 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
</div>
|
||||
<div class="mt-1 text-gray-500" v-else>Ni nastavljenega datuma.</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #actions="{ row }">
|
||||
<template #cell-actions="{ row }">
|
||||
<div @click.stop>
|
||||
<TableActions align="right">
|
||||
<template #default="{ handleAction }">
|
||||
<!-- Editing -->
|
||||
<template v-if="edit">
|
||||
<div class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400">
|
||||
Urejanje
|
||||
</div>
|
||||
<template #default="{ handleAction }">
|
||||
<!-- Editing -->
|
||||
<template v-if="edit">
|
||||
<div
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
Urejanje
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faPenToSquare"
|
||||
label="Uredi"
|
||||
@click="onEdit(row)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Add Activity -->
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faPenToSquare"
|
||||
label="Uredi"
|
||||
@click="onEdit(row)"
|
||||
:icon="faListCheck"
|
||||
label="Dodaj aktivnost"
|
||||
@click="onAddActivity(row)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Add Activity -->
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faListCheck"
|
||||
label="Dodaj aktivnost"
|
||||
@click="onAddActivity(row)"
|
||||
/>
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<!-- Documents -->
|
||||
<template v-if="createDoc">
|
||||
<div
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
Dokument
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="generating[row.uuid] ? faSpinner : faFileWord"
|
||||
:label="
|
||||
generating[row.uuid]
|
||||
? 'Generiranje...'
|
||||
: templates && templates.length
|
||||
? 'Generiraj dokument'
|
||||
: 'Ni predlog'
|
||||
"
|
||||
:disabled="generating[row.uuid] || !templates || templates.length === 0"
|
||||
@click="openGenerateDialog(row)"
|
||||
/>
|
||||
<a
|
||||
v-if="generatedDocs[row.uuid]?.path"
|
||||
:href="'/storage/' + generatedDocs[row.uuid].path"
|
||||
target="_blank"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-left text-sm text-indigo-600 hover:bg-indigo-50"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faFileWord" class="h-4 w-4" />
|
||||
<span>Prenesi zadnji</span>
|
||||
</a>
|
||||
<div
|
||||
v-if="generationError[row.uuid]"
|
||||
class="px-3 py-2 text-xs text-rose-600 whitespace-pre-wrap"
|
||||
>
|
||||
{{ generationError[row.uuid] }}
|
||||
</div>
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
</template>
|
||||
|
||||
<!-- Documents -->
|
||||
<template v-if="createDoc">
|
||||
<div class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400">
|
||||
Dokument
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="generating[row.uuid] ? faSpinner : faFileWord"
|
||||
:label="
|
||||
generating[row.uuid]
|
||||
? 'Generiranje...'
|
||||
: templates && templates.length
|
||||
? 'Generiraj dokument'
|
||||
: 'Ni predlog'
|
||||
"
|
||||
:disabled="generating[row.uuid] || !templates || templates.length === 0"
|
||||
@click="openGenerateDialog(row)"
|
||||
/>
|
||||
<a
|
||||
v-if="generatedDocs[row.uuid]?.path"
|
||||
:href="'/storage/' + generatedDocs[row.uuid].path"
|
||||
target="_blank"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-left text-sm text-indigo-600 hover:bg-indigo-50"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faFileWord" class="h-4 w-4" />
|
||||
<span>Prenesi zadnji</span>
|
||||
</a>
|
||||
<!-- Objects -->
|
||||
<div
|
||||
v-if="generationError[row.uuid]"
|
||||
class="px-3 py-2 text-xs text-rose-600 whitespace-pre-wrap"
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
{{ generationError[row.uuid] }}
|
||||
</div>
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
</template>
|
||||
|
||||
<!-- Objects -->
|
||||
<div class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400">
|
||||
Predmeti
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="faCircleInfo"
|
||||
label="Seznam predmetov"
|
||||
@click="openObjectsList(row)"
|
||||
/>
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faPlus"
|
||||
label="Dodaj predmet"
|
||||
@click="openObjectDialog(row)"
|
||||
/>
|
||||
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
|
||||
<!-- Payments -->
|
||||
<div class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400">
|
||||
Plačila
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="faCircleInfo"
|
||||
label="Pokaži plačila"
|
||||
@click="openPaymentsDialog(row)"
|
||||
/>
|
||||
<ActionMenuItem
|
||||
v-if="row.active && row?.account"
|
||||
:icon="faPlus"
|
||||
label="Dodaj plačilo"
|
||||
@click="openPaymentDialog(row)"
|
||||
/>
|
||||
|
||||
<!-- Archive -->
|
||||
<template v-if="edit">
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<div class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400">
|
||||
{{ row.active ? "Arhiviranje" : "Ponovna aktivacija" }}
|
||||
Predmeti
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="faCircleInfo"
|
||||
label="Seznam predmetov"
|
||||
@click="openObjectsList(row)"
|
||||
/>
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faBoxArchive"
|
||||
:label="'Arhiviraj'"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: row.uuid,
|
||||
}),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
:icon="faPlus"
|
||||
label="Dodaj predmet"
|
||||
@click="openObjectDialog(row)"
|
||||
/>
|
||||
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
|
||||
<!-- Payments -->
|
||||
<div
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
Plačila
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
:icon="faCircleInfo"
|
||||
label="Pokaži plačila"
|
||||
@click="openPaymentsDialog(row)"
|
||||
/>
|
||||
<ActionMenuItem
|
||||
v-else
|
||||
:icon="faBoxArchive"
|
||||
label="Ponovno aktiviraj"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: row.uuid,
|
||||
}),
|
||||
{ reactivate: true },
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
v-if="row.active && row?.account"
|
||||
:icon="faPlus"
|
||||
label="Dodaj plačilo"
|
||||
@click="openPaymentDialog(row)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Delete -->
|
||||
<template v-if="edit">
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<ActionMenuItem :icon="faTrash" label="Izbriši" danger @click="onDelete(row)" />
|
||||
<!-- Archive -->
|
||||
<template v-if="edit">
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<div
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
{{ row.active ? "Arhiviranje" : "Ponovna aktivacija" }}
|
||||
</div>
|
||||
<ActionMenuItem
|
||||
v-if="row.active"
|
||||
:icon="faBoxArchive"
|
||||
:label="'Arhiviraj'"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: row.uuid,
|
||||
}),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
/>
|
||||
<ActionMenuItem
|
||||
v-else
|
||||
:icon="faBoxArchive"
|
||||
label="Ponovno aktiviraj"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: row.uuid,
|
||||
}),
|
||||
{ reactivate: true },
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Delete -->
|
||||
<template v-if="edit">
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<ActionMenuItem
|
||||
:icon="faTrash"
|
||||
label="Izbriši"
|
||||
danger
|
||||
@click="onDelete(row)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</TableActions>
|
||||
</TableActions>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
@@ -806,7 +862,9 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
<ConfirmationDialog
|
||||
:show="confirmChange.show"
|
||||
title="Spremeni segment"
|
||||
:message="`Ali želite spremeniti segment za pogodbo ${confirmChange.contract?.reference || ''}?`"
|
||||
:message="`Ali želite spremeniti segment za pogodbo ${
|
||||
confirmChange.contract?.reference || ''
|
||||
}?`"
|
||||
confirm-text="Potrdi"
|
||||
cancel-text="Prekliči"
|
||||
@close="closeConfirm"
|
||||
@@ -855,67 +913,67 @@ const onAddActivity = (c) => emit("add-activity", c);
|
||||
@confirm="submitGenerate"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Predloga</label>
|
||||
<select
|
||||
v-model="selectedTemplateSlug"
|
||||
@change="onTemplateChange"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option :value="null">Izberi predlogo...</option>
|
||||
<option v-for="t in templates" :key="t.slug" :value="t.slug">
|
||||
{{ t.name }} (v{{ t.version }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Predloga</label>
|
||||
<select
|
||||
v-model="selectedTemplateSlug"
|
||||
@change="onTemplateChange"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option :value="null">Izberi predlogo...</option>
|
||||
<option v-for="t in templates" :key="t.slug" :value="t.slug">
|
||||
{{ t.name }} (v{{ t.version }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Custom inputs -->
|
||||
<template v-if="customTokenList.length > 0">
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-3">Prilagojene vrednosti</h3>
|
||||
<div class="space-y-3">
|
||||
<div v-for="token in customTokenList" :key="token">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
{{ token.replace(/^custom\./, "") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="customInputs[token.replace(/^custom\./, '')]"
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<!-- Custom inputs -->
|
||||
<template v-if="customTokenList.length > 0">
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-3">Prilagojene vrednosti</h3>
|
||||
<div class="space-y-3">
|
||||
<div v-for="token in customTokenList" :key="token">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
{{ token.replace(/^custom\./, "") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="customInputs[token.replace(/^custom\./, '')]"
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Address overrides -->
|
||||
<div class="border-t border-gray-200 pt-4 space-y-3">
|
||||
<h3 class="text-sm font-medium text-gray-700">Naslovi</h3>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Naslov stranke</label>
|
||||
<select
|
||||
v-model="clientAddressSource"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option value="client">Stranka</option>
|
||||
<option value="case_person">Oseba primera</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Naslov osebe</label>
|
||||
<select
|
||||
v-model="personAddressSource"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option value="case_person">Oseba primera</option>
|
||||
<option value="client">Stranka</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="generationError[generateFor?.uuid]" class="text-sm text-red-600">
|
||||
{{ generationError[generateFor?.uuid] }}
|
||||
<!-- Address overrides -->
|
||||
<div class="border-t border-gray-200 pt-4 space-y-3">
|
||||
<h3 class="text-sm font-medium text-gray-700">Naslovi</h3>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Naslov stranke</label>
|
||||
<select
|
||||
v-model="clientAddressSource"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option value="client">Stranka</option>
|
||||
<option value="case_person">Oseba primera</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Naslov osebe</label>
|
||||
<select
|
||||
v-model="personAddressSource"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||
>
|
||||
<option value="case_person">Oseba primera</option>
|
||||
<option value="client">Stranka</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="generationError[generateFor?.uuid]" class="text-sm text-red-600">
|
||||
{{ generationError[generateFor?.uuid] }}
|
||||
</div>
|
||||
</div>
|
||||
</CreateDialog>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user