updates to UI and add archiving option
This commit is contained in:
@@ -73,11 +73,22 @@ const store = async () => {
|
||||
amount: form.amount,
|
||||
note: form.note,
|
||||
});
|
||||
// Helper to safely format a selected date (Date instance or parsable value) to YYYY-MM-DD
|
||||
const formatDateForSubmit = (value) => {
|
||||
if (!value) return null; // leave empty as null
|
||||
const d = value instanceof Date ? value : new Date(value);
|
||||
if (isNaN(d.getTime())) return null; // invalid date -> null
|
||||
// Avoid timezone shifting by constructing in local time
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`; // matches en-CA style YYYY-MM-DD
|
||||
};
|
||||
|
||||
form
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
due_date: new Date(data.due_date).toLocaleDateString("en-CA"),
|
||||
due_date: formatDateForSubmit(data.due_date),
|
||||
}))
|
||||
.post(route("clientCase.activity.store", props.client_case), {
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -179,17 +179,17 @@ const confirmDeleteAction = () => {
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-2 pl-2 pr-2 align-top text-right">
|
||||
<Dropdown align="right" width="30" :content-classes="['py-1', 'bg-white']">
|
||||
<td class="py-2 pl-2 pr-2 align-middle text-right">
|
||||
<Dropdown align="right" width="30">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center w-8 h-8 rounded hover:bg-gray-100"
|
||||
aria-haspopup="menu"
|
||||
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 focus:outline-none"
|
||||
:title="'Actions'"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'ellipsis-vertical']"
|
||||
class="text-gray-600 text-[20px]"
|
||||
:icon="faEllipsisVertical"
|
||||
class="h-4 w-4 text-gray-700"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
faTrash,
|
||||
faListCheck,
|
||||
faPlus,
|
||||
faBoxArchive,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -119,6 +120,10 @@ const confirmChange = ref({
|
||||
fromAll: false,
|
||||
});
|
||||
const askChangeSegment = (c, segmentId, fromAll = false) => {
|
||||
// Prevent segment change for archived contracts
|
||||
if (!c?.active) {
|
||||
return;
|
||||
}
|
||||
confirmChange.value = { show: true, contract: c, segmentId, fromAll };
|
||||
};
|
||||
const closeConfirm = () => {
|
||||
@@ -262,13 +267,16 @@ const closePaymentsDialog = () => {
|
||||
class="inline-flex items-center justify-center h-7 w-7 rounded-full hover:bg-gray-100"
|
||||
:class="{
|
||||
'opacity-50 cursor-not-allowed':
|
||||
!segments || segments.length === 0,
|
||||
!segments || segments.length === 0 || !c.active,
|
||||
}"
|
||||
:title="
|
||||
segments && segments.length
|
||||
!c.active
|
||||
? 'Segmenta ni mogoče spremeniti za arhivirano pogodbo'
|
||||
: segments && segments.length
|
||||
? 'Spremeni segment'
|
||||
: 'Ni segmentov na voljo za ta primer'
|
||||
"
|
||||
:disabled="!c.active || !segments || !segments.length"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="faPenToSquare"
|
||||
@@ -313,6 +321,11 @@ const closePaymentsDialog = () => {
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<span
|
||||
v-if="!c.active"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-semibold bg-gray-200 text-gray-700 uppercase tracking-wide"
|
||||
>Arhivirano</span
|
||||
>
|
||||
</div>
|
||||
</FwbTableCell>
|
||||
<FwbTableCell class="text-right">{{
|
||||
@@ -433,6 +446,7 @@ const closePaymentsDialog = () => {
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
v-if="c.active"
|
||||
@click="onEdit(c)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
@@ -444,6 +458,7 @@ const closePaymentsDialog = () => {
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
v-if="c.active"
|
||||
@click="onAddActivity(c)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faListCheck" class="h-4 w-4 text-gray-600" />
|
||||
@@ -468,6 +483,7 @@ const closePaymentsDialog = () => {
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
v-if="c.active"
|
||||
@click="openObjectDialog(c)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPlus" class="h-4 w-4 text-gray-600" />
|
||||
@@ -492,12 +508,62 @@ const closePaymentsDialog = () => {
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
v-if="c.active && c?.account"
|
||||
@click="openPaymentDialog(c)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPlus" class="h-4 w-4 text-gray-600" />
|
||||
<span>Dodaj plačilo</span>
|
||||
</button>
|
||||
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<!-- Arhiviranje / Ponovna aktivacija -->
|
||||
<div
|
||||
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
{{ c.active ? "Arhiviranje" : "Ponovna aktivacija" }}
|
||||
</div>
|
||||
<button
|
||||
v-if="c.active"
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: c.uuid,
|
||||
}),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faBoxArchive" class="h-4 w-4 text-gray-600" />
|
||||
<span>Arhiviraj</span>
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
||||
@click="
|
||||
router.post(
|
||||
route('clientCase.contract.archive', {
|
||||
client_case: client_case.uuid,
|
||||
uuid: c.uuid,
|
||||
}),
|
||||
{ reactivate: true },
|
||||
{
|
||||
preserveScroll: true,
|
||||
only: ['contracts', 'activities', 'documents'],
|
||||
}
|
||||
)
|
||||
"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faBoxArchive" class="h-4 w-4 text-gray-600" />
|
||||
<span>Ponovno aktiviraj</span>
|
||||
</button>
|
||||
<div class="my-1 border-t border-gray-100" />
|
||||
<!-- Destruktivno -->
|
||||
<button
|
||||
|
||||
@@ -373,7 +373,14 @@ function referenceOf(entityName, ent) {
|
||||
<span>{{ activeEntity }}</span>
|
||||
<span
|
||||
v-if="r.entities[activeEntity].action_label"
|
||||
class="text-[10px] px-1 py-0.5 rounded bg-gray-100"
|
||||
:class="[
|
||||
'text-[10px] px-1 py-0.5 rounded',
|
||||
r.entities[activeEntity].action === 'create' && 'bg-emerald-100 text-emerald-700',
|
||||
r.entities[activeEntity].action === 'update' && 'bg-blue-100 text-blue-700',
|
||||
r.entities[activeEntity].action === 'reactivate' && 'bg-purple-100 text-purple-700 font-semibold',
|
||||
r.entities[activeEntity].action === 'skip' && 'bg-gray-100 text-gray-600',
|
||||
r.entities[activeEntity].action === 'implicit' && 'bg-teal-100 text-teal-700'
|
||||
].filter(Boolean)"
|
||||
>{{ r.entities[activeEntity].action_label }}</span
|
||||
>
|
||||
<span
|
||||
@@ -502,10 +509,25 @@ function referenceOf(entityName, ent) {
|
||||
</div>
|
||||
<div>
|
||||
Akcija:
|
||||
<span class="font-medium">{{
|
||||
r.entities[activeEntity].action_label ||
|
||||
r.entities[activeEntity].action
|
||||
}}</span>
|
||||
<span
|
||||
:class="[
|
||||
'font-medium inline-flex items-center gap-1',
|
||||
r.entities[activeEntity].action === 'reactivate' && 'text-purple-700'
|
||||
].filter(Boolean)"
|
||||
>{{
|
||||
r.entities[activeEntity].action_label ||
|
||||
r.entities[activeEntity].action
|
||||
}}
|
||||
<span
|
||||
v-if="r.entities[activeEntity].reactivation"
|
||||
class="text-[9px] px-1 py-0.5 rounded bg-purple-100 text-purple-700"
|
||||
title="Pogodba bo reaktivirana"
|
||||
>react</span
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div v-if="r.entities[activeEntity].original_action === 'update' && r.entities[activeEntity].action === 'reactivate'" class="text-[10px] text-purple-600 mt-0.5">
|
||||
(iz neaktivnega → aktivno)
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -18,6 +18,7 @@ const form = useForm({
|
||||
source_type: "csv",
|
||||
default_record_type: "",
|
||||
is_active: true,
|
||||
reactivate: false,
|
||||
client_uuid: null,
|
||||
entities: [],
|
||||
meta: {
|
||||
@@ -285,6 +286,10 @@ watch(
|
||||
<label for="is_active" class="text-sm font-medium text-gray-700"
|
||||
>Active</label
|
||||
>
|
||||
<div class="flex items-center gap-2 ml-6">
|
||||
<input id="reactivate" v-model="form.reactivate" type="checkbox" class="rounded" />
|
||||
<label for="reactivate" class="text-sm font-medium text-gray-700">Reactivation import</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
|
||||
@@ -20,6 +20,7 @@ const form = useForm({
|
||||
source_type: props.template.source_type,
|
||||
default_record_type: props.template.default_record_type || "",
|
||||
is_active: props.template.is_active,
|
||||
reactivate: props.template.reactivate ?? false,
|
||||
client_uuid: props.template.client_uuid || null,
|
||||
sample_headers: props.template.sample_headers || [],
|
||||
// Add meta with default delimiter support
|
||||
@@ -434,9 +435,11 @@ watch(
|
||||
type="checkbox"
|
||||
class="rounded"
|
||||
/>
|
||||
<label for="is_active" class="text-sm font-medium text-gray-700"
|
||||
>Aktivna</label
|
||||
>
|
||||
<label for="is_active" class="text-sm font-medium text-gray-700">Aktivna</label>
|
||||
<div class="flex items-center gap-2 ml-6">
|
||||
<input id="reactivate" v-model="form.reactivate" type="checkbox" class="rounded" />
|
||||
<label for="reactivate" class="text-sm font-medium text-gray-700">Reaktivacija</label>
|
||||
</div>
|
||||
<button
|
||||
@click.prevent="save"
|
||||
class="ml-auto px-3 py-2 bg-indigo-600 text-white rounded"
|
||||
|
||||
@@ -73,6 +73,27 @@ function formatAmount(val) {
|
||||
});
|
||||
}
|
||||
|
||||
function formatDateShort(val) {
|
||||
if (!val) return "";
|
||||
try {
|
||||
const d = new Date(val);
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
return d.toLocaleDateString("sl-SI", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function activityActionLine(a) {
|
||||
const base = a?.action?.name || "";
|
||||
const decision = a?.decision?.name ? ` → ${a.decision.name}` : "";
|
||||
return base + decision;
|
||||
}
|
||||
|
||||
// Activity drawer state
|
||||
const drawerAddActivity = ref(false);
|
||||
const activityContractUuid = ref(null);
|
||||
@@ -139,6 +160,35 @@ const submitComplete = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Contracts objects (Predmeti) modal state
|
||||
const objectsModal = reactive({ open: false, items: [], contract: null });
|
||||
function getContractObjects(c) {
|
||||
if (!c) return [];
|
||||
// Try a few common property names; fallback empty
|
||||
return c.objects || c.contract_objects || c.items || [];
|
||||
}
|
||||
function openObjectsModal(c) {
|
||||
objectsModal.contract = c;
|
||||
objectsModal.items = getContractObjects(c) || [];
|
||||
objectsModal.open = true;
|
||||
}
|
||||
function closeObjectsModal() {
|
||||
objectsModal.open = false;
|
||||
objectsModal.items = [];
|
||||
objectsModal.contract = null;
|
||||
}
|
||||
|
||||
// Client details (Stranka) summary
|
||||
const clientSummary = computed(() => {
|
||||
const p = props.client?.person || {};
|
||||
return {
|
||||
name: p.full_name || p.name || "—",
|
||||
tax: p.tax_number || p.davcna || p.tax || null,
|
||||
emso: p.emso || p.ems || null,
|
||||
trr: p.trr || p.bank_account || null,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -172,10 +222,13 @@ const submitComplete = () => {
|
||||
<!-- Client details (account holder) -->
|
||||
<div class="bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Stranka</template>
|
||||
</SectionTitle>
|
||||
<div class="mt-2">
|
||||
<h3
|
||||
class="text-base font-semibold text-gray-900 leading-tight flex items-center gap-2"
|
||||
>
|
||||
<span class="truncate">{{ clientSummary.name }}</span>
|
||||
<span class="chip-base chip-indigo">Naročnik</span>
|
||||
</h3>
|
||||
<div class="mt-4 pt-4 border-t border-dashed">
|
||||
<PersonDetailPhone
|
||||
:types="types"
|
||||
:person="client.person"
|
||||
@@ -188,14 +241,17 @@ const submitComplete = () => {
|
||||
<!-- Person (case person) -->
|
||||
<div class="bg-white rounded-lg shadow border overflow-hidden">
|
||||
<div class="p-3 sm:p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Primer - oseba</template>
|
||||
</SectionTitle>
|
||||
<div class="mt-2">
|
||||
<h3
|
||||
class="text-base font-semibold text-gray-900 leading-tight flex items-center gap-2"
|
||||
>
|
||||
<span class="truncate">{{ client_case.person.full_name }}</span>
|
||||
<span class="chip-base chip-indigo">Primer</span>
|
||||
</h3>
|
||||
<div class="mt-4 pt-4 border-t border-dashed">
|
||||
<PersonDetailPhone
|
||||
:types="types"
|
||||
:person="client_case.person"
|
||||
default-tab="phones"
|
||||
default-tab="addresses"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,48 +267,82 @@ const submitComplete = () => {
|
||||
<div
|
||||
v-for="c in contracts"
|
||||
:key="c.uuid || c.id"
|
||||
class="rounded border p-3 sm:p-4"
|
||||
class="rounded border p-3 sm:p-4 bg-white shadow-sm"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900">{{ c.reference || c.uuid }}</p>
|
||||
<p class="text-sm text-gray-600">Tip: {{ c.type?.name || "—" }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="space-y-2">
|
||||
<p v-if="c.account" class="text-sm text-gray-700">
|
||||
Odprto: {{ formatAmount(c.account.balance_amount) }} €
|
||||
<!-- Header Row -->
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<p
|
||||
class="font-semibold text-gray-900 text-sm leading-tight truncate"
|
||||
>
|
||||
{{ c.reference || c.uuid }}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700 w-full sm:w-auto"
|
||||
@click="openDrawerAddActivity(c)"
|
||||
<span
|
||||
v-if="c.type?.name"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full bg-indigo-100 text-indigo-700 text-[11px] font-medium"
|
||||
>
|
||||
+ Aktivnost
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700 w-full sm:w-auto"
|
||||
@click="openDocDialog(c)"
|
||||
{{ c.type.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="c.account" class="mt-2 flex items-baseline gap-2">
|
||||
<span class="uppercase tracking-wide text-[11px] text-gray-400"
|
||||
>Odprto</span
|
||||
>
|
||||
<span
|
||||
class="text-lg font-semibold text-gray-900 leading-none tracking-tight"
|
||||
>{{ formatAmount(c.account.balance_amount) }} €</span
|
||||
>
|
||||
+ Dokument
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5 w-32 text-right shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 active:scale-[.97] transition shadow"
|
||||
@click="openDrawerAddActivity(c)"
|
||||
>
|
||||
+ Aktivnost
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm px-3 py-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 active:scale-[.97] transition shadow"
|
||||
@click="openDocDialog(c)"
|
||||
>
|
||||
+ Dokument
|
||||
</button>
|
||||
<!--button
|
||||
type="button"
|
||||
:disabled="!getContractObjects(c).length"
|
||||
@click="openObjectsModal(c)"
|
||||
class="relative text-sm px-3 py-2 rounded-md flex items-center justify-center transition disabled:cursor-not-allowed disabled:opacity-50 bg-slate-600 text-white hover:bg-slate-700 active:scale-[.97] shadow"
|
||||
>
|
||||
Predmeti
|
||||
<span
|
||||
class="ml-1 inline-flex items-center justify-center min-w-[1.1rem] h-5 text-[11px] px-1.5 rounded-full bg-white/90 text-slate-700 font-medium"
|
||||
>{{ getContractObjects(c).length }}</span
|
||||
>
|
||||
</button-->
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="c.last_object" class="mt-2 text-sm text-gray-700">
|
||||
<p class="font-medium">Predmet:</p>
|
||||
<p>
|
||||
<span class="text-gray-900">{{
|
||||
c.last_object.name || c.last_object.reference
|
||||
}}</span>
|
||||
<span v-if="c.last_object.type" class="ml-2 text-gray-500"
|
||||
<!-- Subject / Last Object -->
|
||||
<div v-if="c.last_object" class="mt-3 border-t pt-3">
|
||||
<p class="text-[11px] uppercase tracking-wide text-gray-400 mb-1">
|
||||
Zadnji predmet
|
||||
</p>
|
||||
<div class="text-sm font-medium text-gray-800">
|
||||
{{ c.last_object.name || c.last_object.reference }}
|
||||
<span
|
||||
v-if="c.last_object.type"
|
||||
class="ml-2 text-xs font-normal text-gray-500"
|
||||
>({{ c.last_object.type }})</span
|
||||
>
|
||||
</p>
|
||||
<p v-if="c.last_object.description" class="text-gray-600 mt-1">
|
||||
</div>
|
||||
<div
|
||||
v-if="c.last_object.description"
|
||||
class="mt-1 text-sm text-gray-600 leading-snug"
|
||||
>
|
||||
{{ c.last_object.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="!contracts?.length" class="text-sm text-gray-600">
|
||||
@@ -270,40 +360,66 @@ const submitComplete = () => {
|
||||
<template #title>Aktivnosti</template>
|
||||
</SectionTitle>
|
||||
<button
|
||||
class="text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
|
||||
class="text-xs font-medium px-3 py-2 rounded-md bg-indigo-600 text-white shadow-sm active:scale-[.98] hover:bg-indigo-700"
|
||||
@click="openDrawerAddActivity()"
|
||||
>
|
||||
Nova
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 divide-y">
|
||||
<div v-for="a in activities" :key="a.id" class="py-2 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-gray-800">
|
||||
{{ a.action?.name
|
||||
}}<span v-if="a.decision"> → {{ a.decision?.name }}</span>
|
||||
<div class="mt-3 space-y-3">
|
||||
<div
|
||||
v-for="a in activities"
|
||||
:key="a.id"
|
||||
class="rounded-md border border-gray-200 bg-gray-50/70 px-3 py-3 shadow-sm text-[13px]"
|
||||
>
|
||||
<!-- Top line: action + date/user -->
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="font-medium text-gray-800 leading-snug truncate">
|
||||
{{ activityActionLine(a) || "Aktivnost" }}
|
||||
</div>
|
||||
<div class="text-right text-gray-500">
|
||||
<div v-if="a.contract">Pogodba: {{ a.contract.reference }}</div>
|
||||
<div class="text-xs" v-if="a.created_at || a.user || a.user_name">
|
||||
<span v-if="a.created_at">{{
|
||||
new Date(a.created_at).toLocaleDateString("sl-SI")
|
||||
}}</span>
|
||||
<span v-if="(a.user && a.user.name) || a.user_name" class="ml-1"
|
||||
>· {{ a.user?.name || a.user_name }}</span
|
||||
>
|
||||
<div
|
||||
class="shrink-0 text-right text-[11px] text-gray-500 leading-tight"
|
||||
>
|
||||
<div v-if="a.created_at">{{ formatDateShort(a.created_at) }}</div>
|
||||
<div v-if="(a.user && a.user.name) || a.user_name" class="truncate">
|
||||
{{ a.user?.name || a.user_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="a.note" class="text-gray-600">{{ a.note }}</div>
|
||||
<div class="text-gray-500">
|
||||
<span v-if="a.due_date">Zapadlost: {{ a.due_date }}</span>
|
||||
<span v-if="a.amount != null" class="ml-2"
|
||||
|
||||
<!-- Badges row -->
|
||||
<div class="mt-2 flex flex-wrap gap-1.5">
|
||||
<span
|
||||
v-if="a.contract"
|
||||
class="inline-flex items-center rounded-full bg-indigo-100 text-indigo-700 px-2 py-0.5 text-[10px] font-medium"
|
||||
>Pogodba: {{ a.contract.reference }}</span
|
||||
>
|
||||
<span
|
||||
v-if="a.due_date"
|
||||
class="inline-flex items-center rounded-full bg-amber-100 text-amber-700 px-2 py-0.5 text-[10px] font-medium"
|
||||
>Zapadlost: {{ formatDateShort(a.due_date) || a.due_date }}</span
|
||||
>
|
||||
<span
|
||||
v-if="a.amount != null"
|
||||
class="inline-flex items-center rounded-full bg-emerald-100 text-emerald-700 px-2 py-0.5 text-[10px] font-medium"
|
||||
>Znesek: {{ formatAmount(a.amount) }} €</span
|
||||
>
|
||||
<span
|
||||
v-if="a.status"
|
||||
class="inline-flex items-center rounded-full bg-gray-200 text-gray-700 px-2 py-0.5 text-[10px] font-medium"
|
||||
>{{ a.status }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Note -->
|
||||
<div v-if="a.note" class="mt-2 text-gray-700 leading-snug">
|
||||
{{ a.note }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!activities?.length" class="text-gray-600 py-2">
|
||||
<div
|
||||
v-if="!activities?.length"
|
||||
class="text-gray-600 text-sm py-2 text-center"
|
||||
>
|
||||
Ni aktivnosti.
|
||||
</div>
|
||||
</div>
|
||||
@@ -423,6 +539,65 @@ const submitComplete = () => {
|
||||
</template>
|
||||
</ConfirmationModal>
|
||||
|
||||
<!-- Contract Objects (Predmeti) Modal -->
|
||||
<DialogModal :show="objectsModal.open" @close="closeObjectsModal">
|
||||
<template #title>
|
||||
Predmeti
|
||||
<span
|
||||
v-if="objectsModal.contract"
|
||||
class="block text-xs font-normal text-gray-500 mt-0.5"
|
||||
>
|
||||
{{ objectsModal.contract.reference || objectsModal.contract.uuid }}
|
||||
</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
v-if="objectsModal.items.length"
|
||||
class="space-y-3 max-h-[60vh] overflow-y-auto pr-1"
|
||||
>
|
||||
<div
|
||||
v-for="(o, idx) in objectsModal.items"
|
||||
:key="o.id || o.uuid || idx"
|
||||
class="rounded border border-gray-200 bg-gray-50 px-3 py-2 text-sm"
|
||||
>
|
||||
<div class="font-medium text-gray-800 truncate">
|
||||
{{ o.name || o.reference || "#" + (o.id || o.uuid || idx + 1) }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-xs text-gray-500 flex flex-wrap gap-x-2 gap-y-0.5">
|
||||
<span
|
||||
v-if="o.type"
|
||||
class="inline-flex items-center bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded-full"
|
||||
>{{ o.type }}</span
|
||||
>
|
||||
<span
|
||||
v-if="o.status"
|
||||
class="inline-flex items-center bg-gray-200 text-gray-700 px-1.5 py-0.5 rounded-full"
|
||||
>{{ o.status }}</span
|
||||
>
|
||||
<span
|
||||
v-if="o.amount != null"
|
||||
class="inline-flex items-center bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded-full"
|
||||
>{{ formatAmount(o.amount) }} €</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="o.description" class="mt-1 text-gray-600 leading-snug">
|
||||
{{ o.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-gray-600 text-sm">Ni predmetov.</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200"
|
||||
@click="closeObjectsModal"
|
||||
>
|
||||
Zapri
|
||||
</button>
|
||||
</template>
|
||||
</DialogModal>
|
||||
|
||||
<!-- Upload Document Modal -->
|
||||
<DialogModal :show="docDialogOpen" @close="closeDocDialog">
|
||||
<template #title>Dodaj dokument</template>
|
||||
@@ -493,4 +668,27 @@ const submitComplete = () => {
|
||||
</AppPhoneLayout>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
/* Using basic CSS since @apply is not processed in this scoped block by default */
|
||||
.chip-base {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.5rem; /* py-0.5 px-2 */
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.chip-indigo {
|
||||
background: #eef2ff;
|
||||
color: #3730a3;
|
||||
} /* approx indigo-50 / indigo-700 */
|
||||
.chip-default {
|
||||
background: #f1f5f9;
|
||||
color: #334155;
|
||||
} /* slate-100 / slate-700 */
|
||||
.chip-emerald {
|
||||
background: #ecfdf5;
|
||||
color: #047857;
|
||||
} /* emerald-50 / emerald-700 */
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,591 @@
|
||||
<script setup>
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import { useForm, router } from "@inertiajs/vue3";
|
||||
import { ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
settings: Object,
|
||||
archiveEntities: Array,
|
||||
actions: Array,
|
||||
segments: Array,
|
||||
chainPatterns: Array,
|
||||
});
|
||||
|
||||
const newForm = useForm({
|
||||
name: "",
|
||||
description: "",
|
||||
enabled: true,
|
||||
strategy: "immediate",
|
||||
soft: true,
|
||||
reactivate: false,
|
||||
focus: "",
|
||||
related: [],
|
||||
entities: [],
|
||||
action_id: null,
|
||||
decision_id: null,
|
||||
segment_id: null,
|
||||
options: { batch_size: 200 },
|
||||
});
|
||||
|
||||
// Editing state & form
|
||||
const editingSetting = ref(null);
|
||||
// Conditions temporarily inactive in backend; keep placeholder for future restore
|
||||
const originalEntityMeta = ref({ columns: ["id"] });
|
||||
const editForm = useForm({
|
||||
name: "",
|
||||
description: "",
|
||||
enabled: true,
|
||||
strategy: "immediate",
|
||||
soft: true,
|
||||
reactivate: false,
|
||||
focus: "",
|
||||
related: [],
|
||||
entities: [],
|
||||
action_id: null,
|
||||
decision_id: null,
|
||||
segment_id: null,
|
||||
options: { batch_size: 200 },
|
||||
});
|
||||
|
||||
const selectedEntity = ref(null);
|
||||
|
||||
function onFocusChange() {
|
||||
const found = props.archiveEntities.find((e) => e.focus === newForm.focus);
|
||||
selectedEntity.value = found || null;
|
||||
newForm.related = [];
|
||||
}
|
||||
|
||||
function submitCreate() {
|
||||
if (!newForm.focus) {
|
||||
alert("Select a focus entity.");
|
||||
return;
|
||||
}
|
||||
if (newForm.decision_id && !newForm.action_id) {
|
||||
alert("Select an action before choosing a decision.");
|
||||
return;
|
||||
}
|
||||
newForm.entities = [
|
||||
{
|
||||
table: newForm.focus,
|
||||
related: newForm.related,
|
||||
// conditions omitted while inactive
|
||||
columns: ["id"],
|
||||
},
|
||||
];
|
||||
newForm.post(route("settings.archive.store"), {
|
||||
onSuccess: () => {
|
||||
newForm.focus = "";
|
||||
newForm.related = [];
|
||||
newForm.entities = [];
|
||||
newForm.action_id = null;
|
||||
newForm.decision_id = null;
|
||||
newForm.segment_id = null;
|
||||
selectedEntity.value = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function toggleEnabled(setting) {
|
||||
router.put(route("settings.archive.update", setting.id), {
|
||||
...setting,
|
||||
enabled: !setting.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
function startEdit(setting) {
|
||||
editingSetting.value = setting;
|
||||
// Populate editForm
|
||||
editForm.name = setting.name || "";
|
||||
editForm.description = setting.description || "";
|
||||
editForm.enabled = setting.enabled;
|
||||
editForm.strategy = setting.strategy || "immediate";
|
||||
editForm.soft = setting.soft;
|
||||
editForm.reactivate = setting.reactivate ?? false;
|
||||
editForm.action_id = setting.action_id ?? null;
|
||||
editForm.decision_id = setting.decision_id ?? null;
|
||||
editForm.segment_id = setting.segment_id ?? null;
|
||||
// Entities (first only)
|
||||
const first = Array.isArray(setting.entities) ? setting.entities[0] : null;
|
||||
if (first) {
|
||||
editForm.focus = first.table || "";
|
||||
editForm.related = first.related || [];
|
||||
originalEntityMeta.value = {
|
||||
columns: first.columns || ["id"],
|
||||
};
|
||||
const found = props.archiveEntities.find((e) => e.focus === editForm.focus);
|
||||
selectedEntity.value = found || null;
|
||||
} else {
|
||||
editForm.focus = "";
|
||||
editForm.related = [];
|
||||
originalEntityMeta.value = { columns: ["id"] };
|
||||
// If reactivate is checked it implies soft semantics; keep soft true (UI might show both)
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editingSetting.value = null;
|
||||
editForm.reset();
|
||||
selectedEntity.value = null;
|
||||
}
|
||||
|
||||
function submitUpdate() {
|
||||
if (!editingSetting.value) return;
|
||||
if (!editForm.focus) {
|
||||
alert("Select a focus entity.");
|
||||
return;
|
||||
}
|
||||
if (editForm.decision_id && !editForm.action_id) {
|
||||
alert("Select an action before choosing a decision.");
|
||||
return;
|
||||
}
|
||||
editForm.entities = [
|
||||
{
|
||||
table: editForm.focus,
|
||||
related: editForm.related,
|
||||
// conditions omitted while inactive
|
||||
columns: originalEntityMeta.value.columns || ["id"],
|
||||
},
|
||||
];
|
||||
editForm.put(route("settings.archive.update", editingSetting.value.id), {
|
||||
onSuccess: () => {
|
||||
cancelEdit();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function remove(setting) {
|
||||
if (!confirm("Delete archive rule?")) return;
|
||||
router.delete(route("settings.archive.destroy", setting.id));
|
||||
}
|
||||
|
||||
// Run Now removed (feature temporarily disabled)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Archive Settings">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Archive Settings</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-6 max-w-6xl mx-auto px-4">
|
||||
<div class="mb-6 border-l-4 border-amber-500 bg-amber-50 text-amber-800 px-4 py-3 rounded">
|
||||
<p class="text-sm font-medium">Archive rule conditions are temporarily inactive.</p>
|
||||
<p class="text-xs mt-1">All enabled rules apply to the focus entity and its selected related tables without date/other filters. Stored condition JSON is preserved for future reactivation.</p>
|
||||
<p class="text-xs mt-1 font-medium">The "Run Now" action is currently disabled.</p>
|
||||
<div class="mt-3 text-xs bg-white/60 rounded p-3 border border-amber-200">
|
||||
<p class="font-semibold mb-1 text-amber-900">Chain Path Help</p>
|
||||
<p class="mb-1">Supported chained related tables (dot notation):</p>
|
||||
<ul class="list-disc ml-4 space-y-0.5">
|
||||
<li v-for="cp in chainPatterns" :key="cp">
|
||||
<code class="px-1 bg-amber-100 rounded">{{ cp }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-1 italic">Only these chains are processed; others are ignored.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<div class="md:col-span-2 space-y-4">
|
||||
<div
|
||||
v-for="s in settings.data"
|
||||
:key="s.id"
|
||||
class="border rounded-lg p-4 bg-white shadow-sm"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-medium text-gray-900 flex items-center gap-2">
|
||||
<span class="truncate">{{ s.name || "Untitled Rule #" + s.id }}</span>
|
||||
<span
|
||||
v-if="!s.enabled"
|
||||
class="inline-flex text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-800"
|
||||
>Disabled</span
|
||||
>
|
||||
</h3>
|
||||
<p v-if="s.description" class="text-sm text-gray-600 mt-1">
|
||||
{{ s.description }}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Strategy: {{ s.strategy }} • Soft: {{ s.soft ? "Yes" : "No" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2 shrink-0">
|
||||
<button
|
||||
@click="startEdit(s)"
|
||||
class="text-xs px-3 py-1.5 rounded bg-gray-200 text-gray-800 hover:bg-gray-300"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<!-- Run Now removed -->
|
||||
<button
|
||||
@click="toggleEnabled(s)"
|
||||
class="text-xs px-3 py-1.5 rounded bg-indigo-600 text-white hover:bg-indigo-700"
|
||||
>
|
||||
{{ s.enabled ? "Disable" : "Enable" }}
|
||||
</button>
|
||||
<button
|
||||
@click="remove(s)"
|
||||
class="text-xs px-3 py-1.5 rounded bg-red-600 text-white hover:bg-red-700"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs bg-gray-50 border rounded p-2 overflow-x-auto">
|
||||
<pre class="whitespace-pre-wrap">{{
|
||||
JSON.stringify(s.entities, null, 2)
|
||||
}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!settings.data.length" class="text-sm text-gray-600">
|
||||
No archive rules.
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div v-if="!editingSetting" class="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<h3 class="font-semibold text-gray-900 mb-2 text-sm">New Rule</h3>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Segment (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="newForm.segment_id"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option v-for="seg in segments" :key="seg.id" :value="seg.id">
|
||||
{{ seg.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Action (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="newForm.action_id"
|
||||
@change="
|
||||
() => {
|
||||
newForm.decision_id = null;
|
||||
}
|
||||
"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option v-for="a in actions" :key="a.id" :value="a.id">
|
||||
{{ a.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Decision (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="newForm.decision_id"
|
||||
:disabled="!newForm.action_id"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option
|
||||
v-for="d in actions.find((a) => a.id === newForm.action_id)
|
||||
?.decisions || []"
|
||||
:key="d.id"
|
||||
:value="d.id"
|
||||
>
|
||||
{{ d.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Name</label>
|
||||
<input
|
||||
v-model="newForm.name"
|
||||
type="text"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
/>
|
||||
<div v-if="newForm.errors.name" class="text-red-600 text-xs mt-1">
|
||||
{{ newForm.errors.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Focus Entity</label
|
||||
>
|
||||
<select
|
||||
v-model="newForm.focus"
|
||||
@change="onFocusChange"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option value="" disabled>-- choose --</option>
|
||||
<option v-for="ae in archiveEntities" :key="ae.id" :value="ae.focus">
|
||||
{{ ae.name || ae.focus }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="selectedEntity" class="space-y-1">
|
||||
<div class="text-xs font-medium text-gray-600">Related Tables</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="r in selectedEntity.related"
|
||||
:key="r"
|
||||
class="inline-flex items-center gap-1 text-xs bg-gray-100 px-2 py-1 rounded border"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="r"
|
||||
v-model="newForm.related"
|
||||
class="rounded"
|
||||
/>
|
||||
<span>{{ r }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Description</label>
|
||||
<textarea
|
||||
v-model="newForm.description"
|
||||
rows="2"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
></textarea>
|
||||
<div v-if="newForm.errors.description" class="text-red-600 text-xs mt-1">
|
||||
{{ newForm.errors.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="enabled" type="checkbox" v-model="newForm.enabled" />
|
||||
<label for="enabled" class="text-xs font-medium text-gray-700"
|
||||
>Enabled</label
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="soft" type="checkbox" v-model="newForm.soft" />
|
||||
<label for="soft" class="text-xs font-medium text-gray-700"
|
||||
>Soft Archive</label
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="reactivate" type="checkbox" v-model="newForm.reactivate" />
|
||||
<label for="reactivate" class="text-xs font-medium text-gray-700"
|
||||
>Reactivate (undo archive)</label
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Strategy</label>
|
||||
<select
|
||||
v-model="newForm.strategy"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option value="immediate">Immediate</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
<option value="queued">Queued</option>
|
||||
<option value="manual">Manual (never auto-run)</option>
|
||||
</select>
|
||||
<div v-if="newForm.errors.strategy" class="text-red-600 text-xs mt-1">
|
||||
{{ newForm.errors.strategy }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="submitCreate"
|
||||
type="button"
|
||||
:disabled="newForm.processing"
|
||||
class="w-full text-sm px-3 py-2 rounded bg-green-600 text-white hover:bg-green-700 disabled:opacity-50"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<div v-if="Object.keys(newForm.errors).length" class="text-xs text-red-600">
|
||||
Please fix validation errors.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<h3 class="font-semibold text-gray-900 mb-2 text-sm">
|
||||
Edit Rule #{{ editingSetting.id }}
|
||||
</h3>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div
|
||||
class="text-xs text-gray-500"
|
||||
v-if="editingSetting.strategy === 'manual'"
|
||||
>
|
||||
Manual strategy: this rule will only run when triggered manually.
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Segment (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="editForm.segment_id"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option v-for="seg in segments" :key="seg.id" :value="seg.id">
|
||||
{{ seg.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Action (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="editForm.action_id"
|
||||
@change="
|
||||
() => {
|
||||
editForm.decision_id = null;
|
||||
}
|
||||
"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option v-for="a in actions" :key="a.id" :value="a.id">
|
||||
{{ a.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Decision (optional)</label
|
||||
>
|
||||
<select
|
||||
v-model="editForm.decision_id"
|
||||
:disabled="!editForm.action_id"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">-- none --</option>
|
||||
<option
|
||||
v-for="d in actions.find((a) => a.id === editForm.action_id)
|
||||
?.decisions || []"
|
||||
:key="d.id"
|
||||
:value="d.id"
|
||||
>
|
||||
{{ d.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Name</label>
|
||||
<input
|
||||
v-model="editForm.name"
|
||||
type="text"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
/>
|
||||
<div v-if="editForm.errors.name" class="text-red-600 text-xs mt-1">
|
||||
{{ editForm.errors.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600"
|
||||
>Focus Entity</label
|
||||
>
|
||||
<select
|
||||
v-model="editForm.focus"
|
||||
@change="onFocusChange() /* reuse selectedEntity for preview */"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option value="" disabled>-- choose --</option>
|
||||
<option v-for="ae in archiveEntities" :key="ae.id" :value="ae.focus">
|
||||
{{ ae.name || ae.focus }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedEntity && editForm.focus === selectedEntity.focus"
|
||||
class="space-y-1"
|
||||
>
|
||||
<div class="text-xs font-medium text-gray-600">Related Tables</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="r in selectedEntity.related"
|
||||
:key="r"
|
||||
class="inline-flex items-center gap-1 text-xs bg-gray-100 px-2 py-1 rounded border"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="r"
|
||||
v-model="editForm.related"
|
||||
class="rounded"
|
||||
/>
|
||||
<span>{{ r }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Description</label>
|
||||
<textarea
|
||||
v-model="editForm.description"
|
||||
rows="2"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
></textarea>
|
||||
<div v-if="editForm.errors.description" class="text-red-600 text-xs mt-1">
|
||||
{{ editForm.errors.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="edit_enabled" type="checkbox" v-model="editForm.enabled" />
|
||||
<label for="edit_enabled" class="text-xs font-medium text-gray-700"
|
||||
>Enabled</label
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="edit_soft" type="checkbox" v-model="editForm.soft" />
|
||||
<label for="edit_soft" class="text-xs font-medium text-gray-700"
|
||||
>Soft Archive</label
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="edit_reactivate" type="checkbox" v-model="editForm.reactivate" />
|
||||
<label for="edit_reactivate" class="text-xs font-medium text-gray-700"
|
||||
>Reactivate (undo archive)</label
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Strategy</label>
|
||||
<select
|
||||
v-model="editForm.strategy"
|
||||
class="mt-1 w-full border rounded px-2 py-1.5 text-sm"
|
||||
>
|
||||
<option value="immediate">Immediate</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
<option value="queued">Queued</option>
|
||||
<option value="manual">Manual (never auto-run)</option>
|
||||
</select>
|
||||
<div v-if="editForm.errors.strategy" class="text-red-600 text-xs mt-1">
|
||||
{{ editForm.errors.strategy }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="submitUpdate"
|
||||
type="button"
|
||||
:disabled="editForm.processing"
|
||||
class="flex-1 text-sm px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
@click="cancelEdit"
|
||||
type="button"
|
||||
class="px-3 py-2 rounded text-sm bg-gray-200 hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="Object.keys(editForm.errors).length"
|
||||
class="text-xs text-red-600"
|
||||
>
|
||||
Please fix validation errors.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +1,86 @@
|
||||
<script setup>
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Settings">
|
||||
<template #header></template>
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Segments</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Manage segments used across the app.</p>
|
||||
<Link :href="route('settings.segments')" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Open Segments</Link>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Payments</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Defaults for payments and auto-activity.</p>
|
||||
<Link :href="route('settings.payment.edit')" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Open Payment Settings</Link>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Workflow</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Configure actions and decisions relationships.</p>
|
||||
<Link :href="route('settings.workflow')" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Open Workflow</Link>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Field Job Settings</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Configure segment-based field job rules.</p>
|
||||
<Link :href="route('settings.fieldjob.index')" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Open Field Job</Link>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Contract Configs</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Auto-assign initial segments for contracts by type.</p>
|
||||
<Link :href="route('settings.contractConfigs.index')" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Open Contract Configs</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AppLayout title="Settings">
|
||||
<template #header></template>
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Segments</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">Manage segments used across the app.</p>
|
||||
<Link
|
||||
:href="route('settings.segments')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Segments</Link
|
||||
>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Payments</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Defaults for payments and auto-activity.
|
||||
</p>
|
||||
<Link
|
||||
:href="route('settings.payment.edit')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Payment Settings</Link
|
||||
>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Workflow</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Configure actions and decisions relationships.
|
||||
</p>
|
||||
<Link
|
||||
:href="route('settings.workflow')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Workflow</Link
|
||||
>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Field Job Settings</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Configure segment-based field job rules.
|
||||
</p>
|
||||
<Link
|
||||
:href="route('settings.fieldjob.index')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Field Job</Link
|
||||
>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Contract Configs</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Auto-assign initial segments for contracts by type.
|
||||
</p>
|
||||
<Link
|
||||
:href="route('settings.contractConfigs.index')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Contract Configs</Link
|
||||
>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Archive Settings</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Define rules for archiving or soft-deleting aged data.
|
||||
</p>
|
||||
<Link
|
||||
:href="route('settings.archive.index')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
|
||||
>
|
||||
Open Archive Settings</Link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user