feat: implement activity logging for user actions and create activities table
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 44s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 44s
This commit is contained in:
parent
6c75efbc28
commit
7e2f320b4f
12 changed files with 420 additions and 160 deletions
|
|
@ -209,4 +209,13 @@ export interface Identifier {
|
|||
// STATE_DISABLED = 0,
|
||||
// STATE_VALIDATED = 1,
|
||||
// STATE_2FA_AUTHENTICATED = 1,
|
||||
// }
|
||||
// }
|
||||
|
||||
// resources/js/Dataset.ts (oder wo User definiert ist)
|
||||
export interface Activity {
|
||||
id: number | string;
|
||||
type: string;
|
||||
description: string;
|
||||
user: string | null;
|
||||
created_at: string; // ISO-String, relativeTime() erwartet das
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.
|
|||
import CardBoxDataset from '@/Components/CardBoxDataset.vue';
|
||||
import type { User } from '@/Dataset';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import type { Activity } from '@/Dataset';
|
||||
|
||||
const mainService = MainService();
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ onMounted(async () => {
|
|||
mainService.fetchApi('clients'),
|
||||
mainService.fetchApi('authors'),
|
||||
mainService.fetchApi('datasets'),
|
||||
mainService.fetchApi('activities'),
|
||||
loadChart(),
|
||||
]);
|
||||
} catch (e) {
|
||||
|
|
@ -176,16 +178,16 @@ const quickActions = [
|
|||
},
|
||||
];
|
||||
|
||||
type Activity = {
|
||||
id: number | string;
|
||||
description: string;
|
||||
user?: string;
|
||||
created_at: string;
|
||||
};
|
||||
// type Activity = {
|
||||
// id: number | string;
|
||||
// description: string;
|
||||
// user?: string;
|
||||
// created_at: string;
|
||||
// };
|
||||
|
||||
// Reads from the store if your backend provides it; otherwise renders an empty
|
||||
// state. Populate via e.g. mainService.fetchApi('activities') in onMounted.
|
||||
const recentActivity = computed<Activity[]>(() => (mainService as any).activities ?? []);
|
||||
const recentActivity = computed<Activity[]>(() => mainService.activities);
|
||||
|
||||
const relativeTime = (iso: string): string => {
|
||||
const then = new Date(iso).getTime();
|
||||
|
|
@ -235,67 +237,6 @@ const relativeTime = (iso: string): string => {
|
|||
{{ loadError }}
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="reveal reveal-1 grid grid-cols-1 gap-6 lg:grid-cols-3 mb-8">
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-emerald-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-emerald-500" :icon="mdiAccountMultiple" :number="authors.length" label="Authors" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-blue-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-blue-500" :icon="mdiDatabaseOutline" :number="datasets.length" label="Publications" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-purple-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-purple-500" :icon="mdiChartTimelineVariant" :number="submitters.length" label="Submitters" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Datasets Section -->
|
||||
<div v-if="recentDatasets.length > 0" class="reveal reveal-2 mb-8">
|
||||
<SectionTitleLineWithButton :icon="mdiTrendingUp" title="Recent Publications">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400"> Latest {{ recentDatasets.length }} publications </span>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<CardBoxDataset
|
||||
v-for="dataset in recentDatasets"
|
||||
:key="dataset.id"
|
||||
:dataset="dataset"
|
||||
class="hover:shadow-md transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends Overview" class="reveal reveal-3 mt-8">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Publications per month</span>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<CardBox
|
||||
title="Performance"
|
||||
:icon="mdiFinance"
|
||||
:header-icon="mdiReload"
|
||||
class="reveal reveal-3 mb-6 shadow-lg"
|
||||
@header-icon-click="loadChart"
|
||||
>
|
||||
<div v-if="isLoadingChart" role="status" aria-live="polite" class="flex items-center justify-center h-96">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading chart data...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="chartData" class="relative">
|
||||
<line-chart :data="chartData" class="h-96" />
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center h-96 text-gray-500 dark:text-gray-400">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<!-- ============================== Admin ============================== -->
|
||||
<template v-if="isAdmin">
|
||||
<SectionTitleLineWithButton :icon="mdiShieldCrownOutline" title="Admin Overview" class="mt-10">
|
||||
|
|
@ -330,7 +271,12 @@ const relativeTime = (iso: string): string => {
|
|||
<!-- Quick actions + Recent activity -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
<!-- Quick actions -->
|
||||
<CardBox :icon="mdiLightningBoltOutline" :show-header-icon="false" title="Quick Actions" class="lg:col-span-1 shadow-lg">
|
||||
<CardBox
|
||||
:icon="mdiLightningBoltOutline"
|
||||
:show-header-icon="false"
|
||||
title="Quick Actions"
|
||||
class="lg:col-span-1 shadow-lg"
|
||||
>
|
||||
<div class="grid gap-3">
|
||||
<Link
|
||||
v-for="action in quickActions"
|
||||
|
|
@ -398,6 +344,67 @@ const relativeTime = (iso: string): string => {
|
|||
<TableSampleClients />
|
||||
</CardBox>
|
||||
</template>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<!-- <div class="reveal reveal-1 grid grid-cols-1 gap-6 lg:grid-cols-3 mb-8">
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-emerald-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-emerald-500" :icon="mdiAccountMultiple" :number="authors.length" label="Authors" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-blue-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-blue-500" :icon="mdiDatabaseOutline" :number="datasets.length" label="Publications" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-xl border-l-4 border-purple-500 bg-white dark:bg-slate-900/40 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<CardBoxWidget color="text-purple-500" :icon="mdiChartTimelineVariant" :number="submitters.length" label="Submitters" />
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Recent Datasets Section -->
|
||||
<div v-if="recentDatasets.length > 0" class="reveal reveal-2 mb-8">
|
||||
<SectionTitleLineWithButton :icon="mdiTrendingUp" title="Recent Publications">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400"> Latest {{ recentDatasets.length }} publications </span>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<CardBoxDataset
|
||||
v-for="dataset in recentDatasets"
|
||||
:key="dataset.id"
|
||||
:dataset="dataset"
|
||||
class="hover:shadow-md transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends Overview" class="reveal reveal-3 mt-8">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Publications per month</span>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<CardBox
|
||||
title="Performance"
|
||||
:icon="mdiFinance"
|
||||
:header-icon="mdiReload"
|
||||
class="reveal reveal-3 mb-6 shadow-lg"
|
||||
@header-icon-click="loadChart"
|
||||
>
|
||||
<div v-if="isLoadingChart" role="status" aria-live="polite" class="flex items-center justify-center h-96">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading chart data...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="chartData" class="relative">
|
||||
<line-chart :data="chartData" class="h-96" />
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center h-96 text-gray-500 dark:text-gray-400">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
import { Dataset } from '@/Dataset';
|
||||
import { Activity, Dataset } from '@/Dataset';
|
||||
import menu from '@/menu';
|
||||
// import type Person from '#models/person';
|
||||
|
||||
|
|
@ -133,6 +133,8 @@ export const MainService = defineStore('main', {
|
|||
used: 0,
|
||||
codes: [],
|
||||
|
||||
activities: [] as Array<Activity>, // <-- neu
|
||||
|
||||
graphData: {},
|
||||
}),
|
||||
actions: {
|
||||
|
|
@ -203,6 +205,17 @@ export const MainService = defineStore('main', {
|
|||
});
|
||||
},
|
||||
|
||||
// async fetchApi(resource: string) {
|
||||
// try {
|
||||
// const { data } = await axios.get(`/api/${resource}`);
|
||||
// // @ts-ignore – dynamischer Key
|
||||
// this[resource] = data;
|
||||
// } catch (error) {
|
||||
// console.error(`Failed to fetch ${resource}`, error);
|
||||
// }
|
||||
// },
|
||||
|
||||
|
||||
setState(state: any) {
|
||||
this.totpState = state;
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue