diff --git a/app/Http/Controllers/Admin/System/ActivityLogController.php b/app/Http/Controllers/Admin/System/ActivityLogController.php new file mode 100644 index 0000000..f5338bb --- /dev/null +++ b/app/Http/Controllers/Admin/System/ActivityLogController.php @@ -0,0 +1,33 @@ +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, + ]); + } +} diff --git a/composer.json b/composer.json index 32fd117..2a6b1e3 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "laravel/framework": "^13.0", "laravel/tinker": "^3.0", "laravel/wayfinder": "^0.1.14", + "spatie/laravel-activitylog": "^4.12", "spatie/laravel-medialibrary": "^11.21", "spatie/laravel-sluggable": "^3.8" }, diff --git a/composer.lock b/composer.lock index 9efebba..7d0d4d2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b928ca95e5630c3434a47320633e6e52", + "content-hash": "449b4cbc173dadfe79027dc7d0baeb62", "packages": [ { "name": "bacon/bacon-qr-code", @@ -4054,6 +4054,97 @@ }, "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", "version": "11.21.0", @@ -10356,5 +10447,5 @@ "php": "^8.3" }, "platform-dev": {}, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2026_04_20_231657_create_activity_log_table.php b/database/migrations/2026_04_20_231657_create_activity_log_table.php new file mode 100644 index 0000000..b788f65 --- /dev/null +++ b/database/migrations/2026_04_20_231657_create_activity_log_table.php @@ -0,0 +1,27 @@ +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')); + } +} diff --git a/database/migrations/2026_04_20_231658_add_event_column_to_activity_log_table.php b/database/migrations/2026_04_20_231658_add_event_column_to_activity_log_table.php new file mode 100644 index 0000000..78d9a0e --- /dev/null +++ b/database/migrations/2026_04_20_231658_add_event_column_to_activity_log_table.php @@ -0,0 +1,22 @@ +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'); + }); + } +} diff --git a/database/migrations/2026_04_20_231659_add_batch_uuid_column_to_activity_log_table.php b/database/migrations/2026_04_20_231659_add_batch_uuid_column_to_activity_log_table.php new file mode 100644 index 0000000..320ef5c --- /dev/null +++ b/database/migrations/2026_04_20_231659_add_batch_uuid_column_to_activity_log_table.php @@ -0,0 +1,22 @@ +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'); + }); + } +} diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 2c98ddc..99bff9f 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,5 +1,5 @@ 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 { NavMain } from '@/components/nav-main'; import { @@ -74,6 +74,11 @@ const systemNavItems: NavItem[] = [ href: system.logs.index().url, icon: ScrollText, }, + { + title: 'Riwayat', + href: system.activityLogs.index().url, + icon: History, + }, ]; export function AppSidebar() { diff --git a/resources/js/pages/admin/system/activity-log/index.tsx b/resources/js/pages/admin/system/activity-log/index.tsx new file mode 100644 index 0000000..c0b1926 --- /dev/null +++ b/resources/js/pages/admin/system/activity-log/index.tsx @@ -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 ( +
+ + +
+
+

Riwayat Aktivitas

+
+
+ + + + + + +
+ ); +} + +ActivityLogIndex.layout = { + breadcrumbs: [ + { title: 'Sistem' }, + ], +}; diff --git a/resources/js/pages/admin/system/activity-log/partials/columns.tsx b/resources/js/pages/admin/system/activity-log/partials/columns.tsx new file mode 100644 index 0000000..af67e4c --- /dev/null +++ b/resources/js/pages/admin/system/activity-log/partials/columns.tsx @@ -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[] { + return [ + { + accessorKey: 'date', + header: 'Waktu', + cell: ({ row }) => ( +
+ {row.original.date} + {row.original.created_at} +
+ ) + }, + { + accessorKey: 'description', + header: 'Aksi', + cell: ({ row }) => { + const label = row.original.description; + + if (label === 'TAMBAH') { + return ( + + {label} + + ); + } + + if (label === 'UBAH') { + return ( + + {label} + + ); + } + + if (label === 'HAPUS') { + return ( + + {label} + + ); + } + + return ( + + {label} + + ); + } + }, + { + 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 -; + + return ( +
+ {Object.entries(attributes).map(([key, value]) => { + return ( +
+ {key}: + {old && old[key] !== undefined && String(old[key]) !== String(value) && ( + + {String(old[key])} + + )} + {old && old[key] !== undefined && String(old[key]) !== String(value) && } + {String(value)} +
+ ); + })} +
+ ); + } + }, + ]; +} diff --git a/routes/system.php b/routes/system.php index 13ba14e..de4ef35 100644 --- a/routes/system.php +++ b/routes/system.php @@ -1,10 +1,12 @@ group(function () { Route::prefix('admin/system')->group(function () { Route::get('logs', [LogController::class, 'index'])->name('system.logs.index'); + Route::get('activity-logs', [ActivityLogController::class, 'index'])->name('system.activity-logs.index'); }); });