From 635b8f0461d64f74aced6b0249be33df58e9de98 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 10 Feb 2026 08:54:12 +0100 Subject: [PATCH] feat(activity-log) : add global activity log page with filters and pagination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New /activity-log page showing all audit entries across pieces, products and composants. Includes entity type and action filters, expandable diffs, clickable entity links and pagination. Navbar link added under Ressources liées. Co-Authored-By: Claude Opus 4.6 --- app/components/layout/AppNavbar.vue | 3 +- app/composables/useActivityLog.ts | 70 +++++++ app/pages/activity-log.vue | 274 ++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 app/composables/useActivityLog.ts create mode 100644 app/pages/activity-log.vue diff --git a/app/components/layout/AppNavbar.vue b/app/components/layout/AppNavbar.vue index 50eacb5..ee6985b 100644 --- a/app/components/layout/AppNavbar.vue +++ b/app/components/layout/AppNavbar.vue @@ -275,11 +275,12 @@ const navGroups: NavGroup[] = [ { id: 'resources', label: 'Ressources liées', - activePaths: ['/sites', '/documents', '/constructeurs'], + activePaths: ['/sites', '/documents', '/constructeurs', '/activity-log'], children: [ { to: '/sites', label: 'Sites' }, { to: '/documents', label: 'Documents' }, { to: '/constructeurs', label: 'Fournisseurs' }, + { to: '/activity-log', label: 'Journal d\'activité' }, ], }, ] diff --git a/app/composables/useActivityLog.ts b/app/composables/useActivityLog.ts new file mode 100644 index 0000000..03d039b --- /dev/null +++ b/app/composables/useActivityLog.ts @@ -0,0 +1,70 @@ +import { ref } from 'vue' +import { useApi } from '~/composables/useApi' + +export type ActivityLogActor = { + id: string + label: string +} + +export type ActivityLogEntry = { + id: string + entityType: string + entityId: string + entityName: string | null + entityRef: string | null + action: 'create' | 'update' | 'delete' | string + createdAt: string + actor: ActivityLogActor | null + diff: Record | null + snapshot: Record | null +} + +interface LoadActivityLogOptions { + page?: number + itemsPerPage?: number + entityType?: string + action?: string +} + +export function useActivityLog() { + const { get } = useApi() + + const entries = ref([]) + const total = ref(0) + const loading = ref(false) + const error = ref(null) + + const loadActivityLog = async (options: LoadActivityLogOptions = {}) => { + loading.value = true + error.value = null + try { + const params = new URLSearchParams() + params.set('page', String(options.page ?? 1)) + params.set('itemsPerPage', String(options.itemsPerPage ?? 30)) + if (options.entityType) params.set('entityType', options.entityType) + if (options.action) params.set('action', options.action) + + const result = await get(`/activity-logs?${params.toString()}`) + if (!result.success) { + error.value = result.error ?? 'Impossible de charger le journal d\'activité.' + entries.value = [] + return result + } + + const data = result.data as any + entries.value = Array.isArray(data?.items) ? data.items : [] + total.value = typeof data?.total === 'number' ? data.total : entries.value.length + + return { success: true, data: entries.value } + } catch (err: any) { + const message = err?.message ?? 'Erreur inconnue' + error.value = message + entries.value = [] + return { success: false, error: message } + } finally { + loading.value = false + } + } + + return { entries, total, loading, error, loadActivityLog } +} diff --git a/app/pages/activity-log.vue b/app/pages/activity-log.vue new file mode 100644 index 0000000..2a0c220 --- /dev/null +++ b/app/pages/activity-log.vue @@ -0,0 +1,274 @@ + + +