ux(audit): auto-filter, drop diff column and actor filter
- Removed the "Фильтр" button; entity changes apply immediately, the entity_id input is debounced (250ms) and runs the same fetch. - Removed the actor filter input — kept entity + entity_id only. - Removed the DIFF column and the per-row expand/collapse mechanism. Diff JSON is no longer surfaced in the UI (backend still returns it; reachable via curl when needed).
This commit is contained in:
parent
0a403381c5
commit
169eef90f6
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import Skeleton from '@/components/Skeleton.vue';
|
import Skeleton from '@/components/Skeleton.vue';
|
||||||
import { listAudit, type AuditEntry } from '@/api/audit';
|
import { listAudit, type AuditEntry } from '@/api/audit';
|
||||||
@ -8,10 +8,9 @@ import { useToast } from '@/composables/useToast';
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const filters = reactive<{ entity: string; entity_id: string; actor: string }>({
|
const filters = reactive<{ entity: string; entity_id: string }>({
|
||||||
entity: '',
|
entity: '',
|
||||||
entity_id: '',
|
entity_id: '',
|
||||||
actor: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const entries = ref<AuditEntry[]>([]);
|
const entries = ref<AuditEntry[]>([]);
|
||||||
@ -32,21 +31,12 @@ const ACTION_LABELS: Record<string, string> = {
|
|||||||
recalc: 'пересчёт score',
|
recalc: 'пересчёт score',
|
||||||
};
|
};
|
||||||
|
|
||||||
const expanded = ref<Set<string>>(new Set());
|
|
||||||
|
|
||||||
function toggleExpand(id: string) {
|
|
||||||
if (expanded.value.has(id)) expanded.value.delete(id);
|
|
||||||
else expanded.value.add(id);
|
|
||||||
expanded.value = new Set(expanded.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const r = await listAudit({
|
const r = await listAudit({
|
||||||
entity: filters.entity || undefined,
|
entity: filters.entity || undefined,
|
||||||
entity_id: filters.entity_id || undefined,
|
entity_id: filters.entity_id || undefined,
|
||||||
actor: filters.actor || undefined,
|
|
||||||
limit: 200,
|
limit: 200,
|
||||||
});
|
});
|
||||||
entries.value = r.data;
|
entries.value = r.data;
|
||||||
@ -60,28 +50,26 @@ async function load() {
|
|||||||
function reset() {
|
function reset() {
|
||||||
filters.entity = '';
|
filters.entity = '';
|
||||||
filters.entity_id = '';
|
filters.entity_id = '';
|
||||||
filters.actor = '';
|
|
||||||
load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-filter: react to entity change immediately, debounce entity_id input.
|
||||||
|
let idTimer: number | null = null;
|
||||||
|
watch(
|
||||||
|
() => filters.entity,
|
||||||
|
() => load(),
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => filters.entity_id,
|
||||||
|
() => {
|
||||||
|
if (idTimer !== null) window.clearTimeout(idTimer);
|
||||||
|
idTimer = window.setTimeout(load, 250);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function fmtTime(s: string): string {
|
function fmtTime(s: string): string {
|
||||||
return new Date(s).toLocaleString('ru-RU');
|
return new Date(s).toLocaleString('ru-RU');
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasDiff(e: AuditEntry): boolean {
|
|
||||||
if (e.diff === null || e.diff === undefined) return false;
|
|
||||||
if (typeof e.diff === 'object' && Object.keys(e.diff as object).length === 0) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function diffString(e: AuditEntry): string {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(e.diff, null, 2);
|
|
||||||
} catch {
|
|
||||||
return String(e.diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function gotoEntity(e: AuditEntry) {
|
function gotoEntity(e: AuditEntry) {
|
||||||
if (e.entity === 'lead') {
|
if (e.entity === 'lead') {
|
||||||
router.push({ name: 'lead-card', params: { id: e.entity_id } });
|
router.push({ name: 'lead-card', params: { id: e.entity_id } });
|
||||||
@ -102,10 +90,7 @@ onMounted(load);
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<div class="card-pad grid grid-cols-1 md:grid-cols-[1fr_2fr_auto] gap-3 items-end">
|
||||||
class="card-pad grid grid-cols-1 md:grid-cols-4 gap-3"
|
|
||||||
@submit.prevent="load"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-gray-600 mb-1">Сущность</label>
|
<label class="block text-xs font-medium text-gray-600 mb-1">Сущность</label>
|
||||||
<select v-model="filters.entity" class="input-base text-sm">
|
<select v-model="filters.entity" class="input-base text-sm">
|
||||||
@ -124,22 +109,10 @@ onMounted(load);
|
|||||||
class="input-base text-sm"
|
class="input-base text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<button type="button" class="btn-ghost text-sm" @click="reset">
|
||||||
<label class="block text-xs font-medium text-gray-600 mb-1">Кто (actor)</label>
|
Сброс
|
||||||
<input
|
</button>
|
||||||
v-model="filters.actor"
|
</div>
|
||||||
type="text"
|
|
||||||
placeholder="username"
|
|
||||||
class="input-base text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-end gap-2">
|
|
||||||
<button type="submit" class="btn-primary text-sm">Фильтр</button>
|
|
||||||
<button type="button" class="btn-ghost text-sm" @click="reset">
|
|
||||||
Сброс
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div v-if="loading" class="card p-4">
|
<div v-if="loading" class="card p-4">
|
||||||
<Skeleton :rows="6" height="36px" />
|
<Skeleton :rows="6" height="36px" />
|
||||||
@ -156,62 +129,41 @@ onMounted(load);
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="w-44">Когда</th>
|
<th class="w-44">Когда</th>
|
||||||
<th class="w-28">Кто</th>
|
<th class="w-28">Кто</th>
|
||||||
<th class="w-24">Сущность</th>
|
<th class="w-28">Сущность</th>
|
||||||
<th>Действие · ID</th>
|
<th>Действие · ID</th>
|
||||||
<th class="w-24 text-right">Diff</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="e in entries" :key="e.id">
|
<tr v-for="e in entries" :key="e.id" class="hover:bg-gray-50/60">
|
||||||
<tr class="hover:bg-gray-50/60">
|
<td class="px-4 py-2 text-xs text-gray-600 tabular-nums">
|
||||||
<td class="px-4 py-2 text-xs text-gray-600 tabular-nums">
|
{{ fmtTime(e.created_at) }}
|
||||||
{{ fmtTime(e.created_at) }}
|
</td>
|
||||||
</td>
|
<td class="px-4 py-2 text-sm font-medium">{{ e.actor }}</td>
|
||||||
<td class="px-4 py-2 text-sm font-medium">{{ e.actor }}</td>
|
<td class="px-4 py-2 text-xs">
|
||||||
<td class="px-4 py-2 text-xs">
|
<span class="badge bg-gray-100 text-gray-700">
|
||||||
<span class="badge bg-gray-100 text-gray-700">
|
{{ ENTITY_LABELS[e.entity] ?? e.entity }}
|
||||||
{{ ENTITY_LABELS[e.entity] ?? e.entity }}
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
<td class="px-4 py-2 text-sm">
|
||||||
<td class="px-4 py-2 text-sm">
|
<span class="text-gray-900">
|
||||||
<span class="text-gray-900">
|
{{ ACTION_LABELS[e.action] ?? e.action }}
|
||||||
{{ ACTION_LABELS[e.action] ?? e.action }}
|
</span>
|
||||||
</span>
|
<button
|
||||||
<button
|
v-if="e.entity === 'lead'"
|
||||||
v-if="e.entity === 'lead'"
|
type="button"
|
||||||
type="button"
|
class="ml-2 text-xs text-sky-600 hover:underline font-mono"
|
||||||
class="ml-2 text-xs text-sky-600 hover:underline font-mono"
|
@click="gotoEntity(e)"
|
||||||
@click="gotoEntity(e)"
|
>
|
||||||
>
|
{{ e.entity_id.slice(0, 8) }}…
|
||||||
{{ e.entity_id.slice(0, 8) }}…
|
</button>
|
||||||
</button>
|
<span
|
||||||
<span
|
v-else
|
||||||
v-else
|
class="ml-2 text-xs text-gray-500 font-mono"
|
||||||
class="ml-2 text-xs text-gray-500 font-mono"
|
>
|
||||||
>
|
{{ e.entity_id }}
|
||||||
{{ e.entity_id }}
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td class="px-4 py-2 text-right">
|
|
||||||
<button
|
|
||||||
v-if="hasDiff(e)"
|
|
||||||
type="button"
|
|
||||||
class="text-xs text-gray-500 hover:text-gray-900"
|
|
||||||
@click="toggleExpand(e.id)"
|
|
||||||
>
|
|
||||||
{{ expanded.has(e.id) ? 'Скрыть' : 'Показать' }}
|
|
||||||
</button>
|
|
||||||
<span v-else class="text-xs text-gray-300">—</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="expanded.has(e.id) && hasDiff(e)">
|
|
||||||
<td colspan="5" class="px-4 py-2 bg-gray-50 border-b border-gray-100">
|
|
||||||
<pre
|
|
||||||
class="text-[11px] text-gray-700 whitespace-pre-wrap font-mono"
|
|
||||||
>{{ diffString(e) }}</pre>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user