New report system and views
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/Components/ui/dropdown-menu";
|
||||
import { MoreHorizontal, Pencil, Trash, Power, PowerOff } from "lucide-vue-next";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
|
||||
defineProps({
|
||||
rule: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["edit", "toggle-enabled", "delete"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h3 class="font-medium text-gray-900 truncate">
|
||||
{{ rule.name || "Untitled Rule #" + rule.id }}
|
||||
</h3>
|
||||
<Badge v-if="!rule.enabled" variant="secondary" class="text-xs">
|
||||
Disabled
|
||||
</Badge>
|
||||
</div>
|
||||
<p v-if="rule.description" class="text-sm text-muted-foreground mt-1">
|
||||
{{ rule.description }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3 mt-2 text-xs text-muted-foreground">
|
||||
<span>Strategy: <span class="font-medium">{{ rule.strategy }}</span></span>
|
||||
<span>•</span>
|
||||
<span>Soft: <span class="font-medium">{{ rule.soft ? "Yes" : "No" }}</span></span>
|
||||
<span v-if="rule.reactivate" class="text-amber-600 font-medium">
|
||||
• Reactivate Mode
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreHorizontal class="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click="emit('edit', rule)">
|
||||
<Pencil class="w-4 h-4 mr-2" />
|
||||
Uredi
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="emit('toggle-enabled', rule)">
|
||||
<component :is="rule.enabled ? PowerOff : Power" class="w-4 h-4 mr-2" />
|
||||
{{ rule.enabled ? "Disable" : "Enable" }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@click="emit('delete', rule)"
|
||||
class="text-red-600 focus:text-red-600"
|
||||
>
|
||||
<Trash class="w-4 h-4 mr-2" />
|
||||
Izbriši
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div class="mt-3 text-xs bg-muted rounded p-3 overflow-x-auto">
|
||||
<pre class="whitespace-pre-wrap">{{ JSON.stringify(rule.entities, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,184 @@
|
||||
<script setup>
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/Components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
import { Checkbox } from "@/Components/ui/checkbox";
|
||||
import AppCheckboxArray from "@/Components/app/ui/AppCheckboxArray.vue";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Textarea } from "@/Components/ui/textarea";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
form: Object,
|
||||
archiveEntities: Array,
|
||||
actions: Array,
|
||||
segments: Array,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["submit"]);
|
||||
|
||||
const selectedEntity = ref(null);
|
||||
|
||||
function onFocusChange() {
|
||||
const found = props.archiveEntities.find((e) => e.focus === props.form.focus);
|
||||
selectedEntity.value = found || null;
|
||||
props.form.related = [];
|
||||
}
|
||||
|
||||
const availableDecisions = computed(() => {
|
||||
if (!props.form.action_id) return [];
|
||||
const action = props.actions.find((a) => a.id === props.form.action_id);
|
||||
return action?.decisions || [];
|
||||
});
|
||||
|
||||
function handleActionChange() {
|
||||
props.form.decision_id = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-base">New Archive Rule</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div>
|
||||
<InputLabel for="new_segment">Segment (optional)</InputLabel>
|
||||
<Select v-model="form.segment_id">
|
||||
<SelectTrigger id="new_segment" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="seg in segments" :key="seg.id" :value="seg.id">
|
||||
{{ seg.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_action">Action (optional)</InputLabel>
|
||||
<Select v-model="form.action_id" @update:model-value="handleActionChange">
|
||||
<SelectTrigger id="new_action" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="a in actions" :key="a.id" :value="a.id">
|
||||
{{ a.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_decision">Decision (optional)</InputLabel>
|
||||
<Select v-model="form.decision_id" :disabled="!form.action_id">
|
||||
<SelectTrigger id="new_decision" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="d in availableDecisions" :key="d.id" :value="d.id">
|
||||
{{ d.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_name">Name</InputLabel>
|
||||
<Input id="new_name" v-model="form.name" type="text" />
|
||||
<InputError :message="form.errors.name" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_focus">Focus Entity</InputLabel>
|
||||
<Select v-model="form.focus" @update:model-value="onFocusChange">
|
||||
<SelectTrigger id="new_focus" class="w-full">
|
||||
<SelectValue placeholder="-- choose --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="ae in archiveEntities" :key="ae.id" :value="ae.focus">
|
||||
{{ ae.name || ae.focus }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedEntity" class="space-y-2">
|
||||
<InputLabel>Related Tables</InputLabel>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="r in selectedEntity.related"
|
||||
:key="r"
|
||||
class="inline-flex items-center gap-2 text-sm bg-muted px-3 py-1.5 rounded-md border cursor-pointer hover:bg-muted/80"
|
||||
>
|
||||
<AppCheckboxArray :value="r" v-model="form.related" />
|
||||
<span>{{ r }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_description">Description</InputLabel>
|
||||
<Textarea id="new_description" v-model="form.description" rows="2" />
|
||||
<InputError :message="form.errors.description" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="new_enabled" v-model="form.enabled" />
|
||||
<InputLabel for="new_enabled" class="text-sm font-normal cursor-pointer">
|
||||
Enabled
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="new_soft" v-model="form.soft" />
|
||||
<InputLabel for="new_soft" class="text-sm font-normal cursor-pointer">
|
||||
Soft Archive
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="new_reactivate" v-model="form.reactivate" />
|
||||
<InputLabel for="new_reactivate" class="text-sm font-normal cursor-pointer">
|
||||
Reactivate (undo archive)
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="new_strategy">Strategy</InputLabel>
|
||||
<Select v-model="form.strategy">
|
||||
<SelectTrigger id="new_strategy" class="w-full">
|
||||
<SelectValue placeholder="Select strategy" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="immediate">Immediate</SelectItem>
|
||||
<SelectItem value="scheduled">Scheduled</SelectItem>
|
||||
<SelectItem value="queued">Queued</SelectItem>
|
||||
<SelectItem value="manual">Manual (never auto-run)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError :message="form.errors.strategy" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<Button @click="emit('submit')" :disabled="form.processing" class="w-full">
|
||||
Create Rule
|
||||
</Button>
|
||||
|
||||
<div v-if="Object.keys(form.errors).length" class="text-xs text-red-600">
|
||||
Please fix validation errors.
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -0,0 +1,199 @@
|
||||
<script setup>
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/Components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
import { Checkbox } from "@/Components/ui/checkbox";
|
||||
import AppCheckboxArray from "@/Components/app/ui/AppCheckboxArray.vue";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Textarea } from "@/Components/ui/textarea";
|
||||
import { Alert, AlertDescription } from "@/Components/ui/alert";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import { ref, computed, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
form: Object,
|
||||
setting: Object,
|
||||
archiveEntities: Array,
|
||||
actions: Array,
|
||||
segments: Array,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["submit", "cancel"]);
|
||||
|
||||
const selectedEntity = ref(null);
|
||||
|
||||
// Initialize selectedEntity based on form.focus
|
||||
watch(
|
||||
() => props.form.focus,
|
||||
(newFocus) => {
|
||||
const found = props.archiveEntities.find((e) => e.focus === newFocus);
|
||||
selectedEntity.value = found || null;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const availableDecisions = computed(() => {
|
||||
if (!props.form.action_id) return [];
|
||||
const action = props.actions.find((a) => a.id === props.form.action_id);
|
||||
return action?.decisions || [];
|
||||
});
|
||||
|
||||
function handleActionChange() {
|
||||
props.form.decision_id = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-base">Edit Rule #{{ setting.id }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<Alert v-if="setting.strategy === 'manual'" variant="default">
|
||||
<AlertDescription class="text-xs">
|
||||
Manual strategy: this rule will only run when triggered manually.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_segment">Segment (optional)</InputLabel>
|
||||
<Select v-model="form.segment_id">
|
||||
<SelectTrigger id="edit_segment" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="seg in segments" :key="seg.id" :value="seg.id">
|
||||
{{ seg.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_action">Action (optional)</InputLabel>
|
||||
<Select v-model="form.action_id" @update:model-value="handleActionChange">
|
||||
<SelectTrigger id="edit_action" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="a in actions" :key="a.id" :value="a.id">
|
||||
{{ a.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_decision">Decision (optional)</InputLabel>
|
||||
<Select v-model="form.decision_id" :disabled="!form.action_id">
|
||||
<SelectTrigger id="edit_decision" class="w-full">
|
||||
<SelectValue placeholder="-- none --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">-- none --</SelectItem>
|
||||
<SelectItem v-for="d in availableDecisions" :key="d.id" :value="d.id">
|
||||
{{ d.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_name">Name</InputLabel>
|
||||
<Input id="edit_name" v-model="form.name" type="text" />
|
||||
<InputError :message="form.errors.name" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_focus">Focus Entity</InputLabel>
|
||||
<Select v-model="form.focus">
|
||||
<SelectTrigger id="edit_focus" class="w-full">
|
||||
<SelectValue placeholder="-- choose --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="ae in archiveEntities" :key="ae.id" :value="ae.focus">
|
||||
{{ ae.name || ae.focus }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedEntity && form.focus === selectedEntity.focus" class="space-y-2">
|
||||
<InputLabel>Related Tables</InputLabel>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="r in selectedEntity.related"
|
||||
:key="r"
|
||||
class="inline-flex items-center gap-2 text-sm bg-muted px-3 py-1.5 rounded-md border cursor-pointer hover:bg-muted/80"
|
||||
>
|
||||
<AppCheckboxArray :value="r" v-model="form.related" />
|
||||
<span>{{ r }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_description">Description</InputLabel>
|
||||
<Textarea id="edit_description" v-model="form.description" rows="2" />
|
||||
<InputError :message="form.errors.description" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="edit_enabled" v-model="form.enabled" />
|
||||
<InputLabel for="edit_enabled" class="text-sm font-normal cursor-pointer">
|
||||
Enabled
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="edit_soft" v-model="form.soft" />
|
||||
<InputLabel for="edit_soft" class="text-sm font-normal cursor-pointer">
|
||||
Soft Archive
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="edit_reactivate" v-model="form.reactivate" />
|
||||
<InputLabel for="edit_reactivate" class="text-sm font-normal cursor-pointer">
|
||||
Reactivate (undo archive)
|
||||
</InputLabel>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="edit_strategy">Strategy</InputLabel>
|
||||
<Select v-model="form.strategy">
|
||||
<SelectTrigger id="edit_strategy" class="w-full">
|
||||
<SelectValue placeholder="Select strategy" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="immediate">Immediate</SelectItem>
|
||||
<SelectItem value="scheduled">Scheduled</SelectItem>
|
||||
<SelectItem value="queued">Queued</SelectItem>
|
||||
<SelectItem value="manual">Manual (never auto-run)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError :message="form.errors.strategy" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button @click="emit('submit')" :disabled="form.processing" class="flex-1">
|
||||
Update
|
||||
</Button>
|
||||
<Button @click="emit('cancel')" variant="outline"> Cancel </Button>
|
||||
</div>
|
||||
|
||||
<div v-if="Object.keys(form.errors).length" class="text-xs text-red-600">
|
||||
Please fix validation errors.
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
Reference in New Issue
Block a user