feat: add activity log management module with database migrations, controller, and UI components
This commit is contained in:
parent
c48d2ecd18
commit
702b033a8b
33
app/Http/Controllers/Admin/System/ActivityLogController.php
Normal file
33
app/Http/Controllers/Admin/System/ActivityLogController.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\System;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
class ActivityLogController extends Controller
|
||||||
|
{
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$activities = Activity::with('causer')
|
||||||
|
->latest()
|
||||||
|
->limit(500)
|
||||||
|
->get()
|
||||||
|
->map(fn ($activity) => [
|
||||||
|
'id' => $activity->id,
|
||||||
|
'description' => $activity->description,
|
||||||
|
'module' => $activity->log_name,
|
||||||
|
'subject_id' => $activity->subject_id,
|
||||||
|
'causer' => $activity->causer ? $activity->causer->name : 'Sistem',
|
||||||
|
'properties' => $activity->properties,
|
||||||
|
'created_at' => $activity->created_at->diffForHumans(),
|
||||||
|
'date' => $activity->created_at->format('d F Y H:i'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Inertia::render('admin/system/activity-log/index', [
|
||||||
|
'activities' => $activities,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@
|
|||||||
"laravel/framework": "^13.0",
|
"laravel/framework": "^13.0",
|
||||||
"laravel/tinker": "^3.0",
|
"laravel/tinker": "^3.0",
|
||||||
"laravel/wayfinder": "^0.1.14",
|
"laravel/wayfinder": "^0.1.14",
|
||||||
|
"spatie/laravel-activitylog": "^4.12",
|
||||||
"spatie/laravel-medialibrary": "^11.21",
|
"spatie/laravel-medialibrary": "^11.21",
|
||||||
"spatie/laravel-sluggable": "^3.8"
|
"spatie/laravel-sluggable": "^3.8"
|
||||||
},
|
},
|
||||||
|
|||||||
95
composer.lock
generated
95
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "b928ca95e5630c3434a47320633e6e52",
|
"content-hash": "449b4cbc173dadfe79027dc7d0baeb62",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@ -4054,6 +4054,97 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-11-26T10:57:19+00:00"
|
"time": "2025-11-26T10:57:19+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/laravel-activitylog",
|
||||||
|
"version": "4.12.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/laravel-activitylog.git",
|
||||||
|
"reference": "2a2024fcac05628b0d1bfdbb1b94dda8b0661dc0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/2a2024fcac05628b0d1bfdbb1b94dda8b0661dc0",
|
||||||
|
"reference": "2a2024fcac05628b0d1bfdbb1b94dda8b0661dc0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"spatie/laravel-package-tools": "^1.6.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.6 || ^10.0 || ^11.0",
|
||||||
|
"pestphp/pest": "^1.20 || ^2.0 || ^3.0 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Spatie\\Activitylog\\ActivitylogServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/helpers.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\Activitylog\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sebastian De Deyne",
|
||||||
|
"email": "sebastian@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tom Witkowski",
|
||||||
|
"email": "dev.gummibeer@gmail.com",
|
||||||
|
"homepage": "https://gummibeer.de",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A very simple activity logger to monitor the users of your website or application",
|
||||||
|
"homepage": "https://github.com/spatie/activitylog",
|
||||||
|
"keywords": [
|
||||||
|
"activity",
|
||||||
|
"laravel",
|
||||||
|
"log",
|
||||||
|
"spatie",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/laravel-activitylog/issues",
|
||||||
|
"source": "https://github.com/spatie/laravel-activitylog/tree/4.12.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://spatie.be/open-source/support-us",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-24T12:33:53+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/laravel-medialibrary",
|
"name": "spatie/laravel-medialibrary",
|
||||||
"version": "11.21.0",
|
"version": "11.21.0",
|
||||||
@ -10356,5 +10447,5 @@
|
|||||||
"php": "^8.3"
|
"php": "^8.3"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.9.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('log_name')->nullable();
|
||||||
|
$table->text('description');
|
||||||
|
$table->nullableMorphs('subject', 'subject');
|
||||||
|
$table->nullableMorphs('causer', 'causer');
|
||||||
|
$table->json('properties')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index('log_name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddEventColumnToActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->string('event')->nullable()->after('subject_type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->dropColumn('event');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->uuid('batch_uuid')->nullable()->after('properties');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->dropColumn('batch_uuid');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Link } from '@inertiajs/react';
|
import { Link } from '@inertiajs/react';
|
||||||
import { Boxes, Currency, DollarSign, LayoutGrid, List, ScrollText, ShoppingCart, User, Wallet, WalletCardsIcon } from 'lucide-react';
|
import { Boxes, DollarSign, History, LayoutGrid, List, ScrollText, ShoppingCart, User, Wallet } from 'lucide-react';
|
||||||
import AppLogo from '@/components/app-logo';
|
import AppLogo from '@/components/app-logo';
|
||||||
import { NavMain } from '@/components/nav-main';
|
import { NavMain } from '@/components/nav-main';
|
||||||
import {
|
import {
|
||||||
@ -74,6 +74,11 @@ const systemNavItems: NavItem[] = [
|
|||||||
href: system.logs.index().url,
|
href: system.logs.index().url,
|
||||||
icon: ScrollText,
|
icon: ScrollText,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Riwayat',
|
||||||
|
href: system.activityLogs.index().url,
|
||||||
|
icon: History,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
|
|||||||
51
resources/js/pages/admin/system/activity-log/index.tsx
Normal file
51
resources/js/pages/admin/system/activity-log/index.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Head } from '@inertiajs/react';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { DataTable } from '@/components/data-table';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { getColumns, Activity } from './partials/columns';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activities: Activity[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ActivityLogIndex({ activities }: Props) {
|
||||||
|
const columns = getColumns();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 p-6">
|
||||||
|
<Head title="Riwayat Aktivitas" />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold tracking-tight">Riwayat Aktivitas</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm p-5">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={activities}
|
||||||
|
filters={[
|
||||||
|
{
|
||||||
|
columnId: 'description',
|
||||||
|
title: 'Aksi',
|
||||||
|
options: [
|
||||||
|
{ label: 'Tambah', value: 'TAMBAH' },
|
||||||
|
{ label: 'Ubah', value: 'UBAH' },
|
||||||
|
{ label: 'Hapus', value: 'HAPUS' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityLogIndex.layout = {
|
||||||
|
breadcrumbs: [
|
||||||
|
{ title: 'Sistem' },
|
||||||
|
],
|
||||||
|
};
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
|
export interface Activity {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
module: string;
|
||||||
|
subject_id: number | string | null;
|
||||||
|
causer: string;
|
||||||
|
properties: any;
|
||||||
|
created_at: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColumns(): ColumnDef<Activity>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
accessorKey: 'date',
|
||||||
|
header: 'Waktu',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-medium text-sm">{row.original.date}</span>
|
||||||
|
<span className="text-[10px] text-muted-foreground">{row.original.created_at}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'description',
|
||||||
|
header: 'Aksi',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const label = row.original.description;
|
||||||
|
|
||||||
|
if (label === 'TAMBAH') {
|
||||||
|
return (
|
||||||
|
<Badge className="bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300 uppercase text-[10px]">
|
||||||
|
{label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label === 'UBAH') {
|
||||||
|
return (
|
||||||
|
<Badge className="bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300 uppercase text-[10px]">
|
||||||
|
{label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label === 'HAPUS') {
|
||||||
|
return (
|
||||||
|
<Badge className="bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300 uppercase text-[10px]">
|
||||||
|
{label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge variant="outline" className="uppercase text-[10px]">
|
||||||
|
{label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'module',
|
||||||
|
header: 'Modul',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'causer',
|
||||||
|
header: 'Oleh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'properties',
|
||||||
|
header: 'Detail Perubahan',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const attributes = row.original.properties?.attributes;
|
||||||
|
const old = row.original.properties?.old;
|
||||||
|
|
||||||
|
if (!attributes) return <span className="text-muted-foreground text-xs italic">-</span>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1 py-1 max-w-[400px]">
|
||||||
|
{Object.entries(attributes).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<div key={key} className="text-[11px] leading-tight flex flex-wrap items-center gap-1">
|
||||||
|
<span className="font-semibold text-indigo-600 dark:text-indigo-400">{key}: </span>
|
||||||
|
{old && old[key] !== undefined && String(old[key]) !== String(value) && (
|
||||||
|
<span className="text-red-500 line-through opacity-60 italic">
|
||||||
|
{String(old[key])}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{old && old[key] !== undefined && String(old[key]) !== String(value) && <span>→</span>}
|
||||||
|
<span className="text-green-600 dark:text-green-400 font-medium">{String(value)}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -1,10 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Admin\System\ActivityLogController;
|
||||||
use App\Http\Controllers\Admin\System\LogController;
|
use App\Http\Controllers\Admin\System\LogController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::prefix('admin/system')->group(function () {
|
Route::prefix('admin/system')->group(function () {
|
||||||
Route::get('logs', [LogController::class, 'index'])->name('system.logs.index');
|
Route::get('logs', [LogController::class, 'index'])->name('system.logs.index');
|
||||||
|
Route::get('activity-logs', [ActivityLogController::class, 'index'])->name('system.activity-logs.index');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user