Initial commit for combined ProjectKP

This commit is contained in:
raditarahman5-cloud
2026-06-28 15:14:51 +07:00
commit ac6c3473d4
203 changed files with 41848 additions and 0 deletions
@@ -0,0 +1,206 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class ActivityController extends Controller
{
private function apiUrl($endpoint = '')
{
return rtrim(env('BACKEND_API_URL'), '/') . '/' . ltrim($endpoint, '/');
}
/**
* FUNGSI DASHBOARD ADMIN
*/
public function dashboard()
{
$response = Http::withToken(session('api_token'))
->acceptJson()
->get($this->apiUrl('/dashboard'));
$stats = ['total' => 0, 'selesai' => 0, 'proses' => 0, 'users' => 0];
$activities = [];
if ($response->successful()) {
$data = $response->json();
if (isset($data['data']['stats'])) {
$st = $data['data']['stats'];
$stats['total'] = $st['total_tugas'] ?? 0;
$stats['selesai'] = $st['status_selesai'] ?? 0;
$stats['proses'] = $st['status_proses'] ?? 0;
}
if (isset($data['data']['recent_activities'])) {
$activities = collect($data['data']['recent_activities'])->map(function($item) {
$petugasNames = 'System';
if (!empty($item['petugas']) && is_array($item['petugas'])) {
$names = [];
foreach ($item['petugas'] as $p) {
if (isset($p['username'])) {
$names[] = $p['username'];
} elseif (isset($p['name'])) {
$names[] = $p['name'];
}
}
if (!empty($names)) {
$petugasNames = implode(', ', $names);
}
}
return (object) [
'id' => $item['id'],
'nama_kegiatan' => $item['kegiatan'],
'tanggal' => $item['tanggal_mulai'],
'status' => $item['status'] == 'proses' ? 'on_progres' : $item['status'],
'user' => (object) ['name' => $petugasNames],
'keterangan' => $item['keterangan'] ?? $item['kegiatan'] ?? '-',
];
});
}
}
$topUsers = [];
$weeklyData = [0, 0, 0, 0, 0, 0, 0];
$divisionProgress = [
['name' => 'Operasional', 'pct' => 75, 'clr' => 'linear-gradient(90deg,#3b82f6,#6366f1)'],
['name' => 'IT', 'pct' => 45, 'clr' => 'linear-gradient(90deg,#a855f7,#ec4899)'],
['name' => 'SDM', 'pct' => 90, 'clr' => 'linear-gradient(90deg,#f97316,#f59e0b)'],
['name' => 'Keuangan', 'pct' => 64, 'clr' => 'linear-gradient(90deg,#22c55e,#10b981)'],
];
return view('admin.dashboard', compact(
'stats',
'activities',
'topUsers',
'weeklyData',
'divisionProgress'
));
}
/**
* HALAMAN INDEX (DAFTAR KEGIATAN DENGAN FILTER)
*/
public function index(Request $request)
{
$response = Http::withToken(session('api_token'))
->acceptJson()
->get($this->apiUrl('/penugasan'));
$items = collect([]);
if ($response->successful()) {
$data = $response->json();
$items = collect($data['data'])->map(function($item) {
$petugasNames = 'System';
if (!empty($item['petugas']) && is_array($item['petugas'])) {
$names = [];
foreach ($item['petugas'] as $p) {
if (isset($p['username'])) {
$names[] = $p['username'];
} elseif (isset($p['name'])) {
$names[] = $p['name'];
}
}
if (!empty($names)) {
$petugasNames = implode(', ', $names);
}
}
return (object) [
'id' => $item['id'],
'nama_kegiatan' => $item['kegiatan'],
'tanggal' => $item['tanggal_mulai'],
'status' => $item['status'] == 'proses' ? 'on_progres' : $item['status'],
'user' => (object) ['name' => $petugasNames],
];
});
// Sembunyikan kegiatan yang sudah dibatalkan
$items = $items->filter(function($item) {
return $item->status !== 'dibatalkan';
})->values();
}
// Simpan semua item sebelum filter untuk statistik
$allItems = $items;
// FILTER SEARCH
if ($request->search) {
$search = strtolower($request->search);
$items = $items->filter(function($item) use ($search) {
return str_contains(strtolower($item->nama_kegiatan), $search) ||
str_contains(strtolower($item->user->name), $search);
});
}
// FILTER STATUS
if ($request->status && $request->status != 'semua') {
$items = $items->filter(function($item) use ($request) {
return $item->status === $request->status;
});
}
// Manual Pagination
$perPage = 8;
$currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
$currentItems = $items->slice(($currentPage - 1) * $perPage, $perPage)->all();
$activities = new \Illuminate\Pagination\LengthAwarePaginator($currentItems, count($items), $perPage, $currentPage, [
'path' => \Illuminate\Pagination\Paginator::resolveCurrentPath(),
'query' => $request->query(),
]);
$divisis = collect(['-']);
return view('admin.kegiatan.index', compact('activities', 'divisis', 'allItems'));
}
// DETAIL
public function show($id)
{
$response = Http::withToken(session('api_token'))
->acceptJson()
->get($this->apiUrl('/penugasan/' . $id));
if (!$response->successful()) {
return redirect()->route('admin.kegiatan.index')->with('error', 'Data tidak ditemukan');
}
$data = $response->json()['data'];
$petugas = collect($data['petugas'] ?? []);
$petugasNames = 'System';
if ($petugas->isNotEmpty()) {
$names = [];
foreach ($petugas as $p) {
if (isset($p['username'])) {
$names[] = $p['username'];
} elseif (isset($p['name'])) {
$names[] = $p['name'];
}
}
if (!empty($names)) {
$petugasNames = implode(', ', $names);
}
}
$activity = (object) [
'id' => $data['id'],
'nomor_surat' => $data['nomor_surat'] ?? '-',
'nama_kegiatan' => $data['kegiatan'],
'tanggal' => $data['tanggal_mulai'],
'tanggal_selesai' => $data['tanggal_selesai'] ?? null,
'lokasi' => '-',
'deskripsi' => '-',
'updated_at' => $data['updated_at'] ?? now(),
'status' => $data['status'] == 'proses' ? 'on_progres' : $data['status'],
'user' => (object) ['name' => $petugasNames],
'petugas' => $petugas,
];
return view('admin.kegiatan.detail', compact('activity'));
}
}
@@ -0,0 +1,555 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
class SuperAdminController extends Controller
{
private function apiUrl($endpoint = '')
{
return rtrim(env('BACKEND_API_URL'), '/') . '/' . ltrim($endpoint, '/');
}
public function index()
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/penugasan'), ['is_archived' => 'false']);
$dashResponse = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/dashboard'));
$items = collect([]);
if ($response->successful()) {
$items = collect($response->json()['data'])->map(function($item) {
return (object) [
'id' => $item['id'],
'nama_kegiatan' => $item['kegiatan'],
'tanggal' => $item['tanggal_mulai'],
'tanggal_selesai' => $item['tanggal_selesai'] ?? null,
'created_at' => \Carbon\Carbon::parse($item['created_at']),
'status' => $item['status'] == 'proses' ? 'on_progres' : $item['status'],
'user' => (object) ['name' => $item['pembuat']['name'] ?? 'System'],
'petugas' => $item['petugas'] ?? [],
'pembuat' => $item['pembuat'] ?? []
];
});
}
$allItems = clone $items;
$items = $items->filter(function($item) {
return $item->status !== 'dibatalkan';
})->values();
// Manual Pagination
$perPage = 10;
$currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
$currentItems = $items->slice(($currentPage - 1) * $perPage, $perPage)->all();
$activities = new \Illuminate\Pagination\LengthAwarePaginator($currentItems, count($items), $perPage, $currentPage, [
'path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()
]);
$stats = [
'total' => $items->count(),
'users' => 0, // diisi nanti
'proses' => $items->where('status', 'on_progres')->count(),
'deleted' => $allItems->where('status', 'dibatalkan')->count(),
];
// Ambil User
$usersResponse = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/users'));
$users = collect([]);
if ($usersResponse->successful()) {
$users = collect($usersResponse->json()['data']);
$stats['users'] = $users->count();
}
// ── Top 5 Petugas (Dihitung dari penugasan)
$topUsers = $users->map(function($u) use ($items) {
$count = $items->filter(function($item) use ($u) {
if (isset($item->petugas) && is_array($item->petugas)) {
foreach ($item->petugas as $p) {
if (isset($p['id']) && $p['id'] == $u['id']) return true;
}
}
return isset($item->pembuat['id']) && $item->pembuat['id'] == $u['id'];
})->count();
return (object) [
'name' => $u['name'] ?? 'System',
'activities_count' => $count
];
})->sortByDesc('activities_count')->take(5)->values();
// ── Data Grafik
$chartData = $this->buildChartData($items);
$system = [
'server' => rand(25, 55),
'database' => rand(45, 75),
'storage' => rand(60, 85),
];
return view('superadmin.dashboard', compact(
'activities',
'stats',
'topUsers',
'chartData',
'system'
));
}
private function buildChartData($items)
{
$now = Carbon::now();
// ── MINGGU INI ─────────────────────────────────
$startOfWeek = $now->copy()->startOfWeek(Carbon::MONDAY);
$endOfWeek = $now->copy()->endOfWeek(Carbon::SUNDAY);
$weekLabels = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];
$weekValues = array_fill(0, 7, 0);
foreach ($items as $item) {
$date = $item->created_at;
if ($date->between($startOfWeek, $endOfWeek)) {
$idx = $date->dayOfWeekIso - 1;
$weekValues[$idx]++;
}
}
// ── BULAN INI ──────────────────────────────
$startOfMonth = $now->copy()->startOfMonth();
$endOfMonth = $now->copy()->endOfMonth();
$daysInMonth = $now->daysInMonth;
$monthLabels = [];
$monthValues = array_fill(0, $daysInMonth, 0);
for ($i = 1; $i <= $daysInMonth; $i++) {
$monthLabels[] = ($i % 5 === 1 || $i === 1) ? (string) $i : '';
}
foreach ($items as $item) {
$date = $item->created_at;
if ($date->between($startOfMonth, $endOfMonth)) {
$monthValues[$date->day - 1]++;
}
}
// ── TAHUN INI ────────────────────────────────
$monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des'];
$yearValues = array_fill(0, 12, 0);
foreach ($items as $item) {
$date = $item->created_at;
if ($date->year === $now->year) {
$yearValues[$date->month - 1]++;
}
}
return [
'week' => [
'labels' => $weekLabels,
'values' => $weekValues,
'todayIndex' => $now->dayOfWeekIso - 1,
],
'month' => [
'labels' => $monthLabels,
'values' => $monthValues,
'todayIndex' => $now->day - 1,
],
'year' => [
'labels' => $monthNames,
'values' => $yearValues,
'todayIndex' => $now->month - 1,
],
];
}
public function show($id)
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/penugasan/' . $id));
if (!$response->successful()) {
return redirect()->back()->with('error', 'Kegiatan tidak ditemukan.');
}
$data = $response->json()['data'];
$activity = (object) [
'id' => $data['id'],
'nama_kegiatan' => $data['kegiatan'],
'tanggal' => $data['tanggal_mulai'],
'tanggal_selesai' => $data['tanggal_selesai'] ?? null,
'status' => $data['status'] == 'proses' ? 'on_progres' : $data['status'],
'deskripsi' => $data['keterangan'] ?? '-',
'lokasi' => $data['lokasi'] ?? '-',
'status_note' => $data['status_note'] ?? null,
'updated_at' => \Carbon\Carbon::parse($data['updated_at'] ?? now()),
'user' => (object) ['name' => $data['pembuat']['name'] ?? 'System'],
'petugas' => $data['petugas'] ?? [],
'nomor_surat' => $data['nomor_surat'] ?? '-',
'progress' => $data['progress'] ?? []
];
return view('superadmin.kegiatan.show', compact('activity'));
}
public function approve(Request $request, $id)
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->post($this->apiUrl("/penugasan/{$id}/update-status"), [
'status' => 'telah_ditinjau'
]);
if ($response->successful()) {
return redirect()->back()->with('success', 'Kegiatan berhasil disetujui (Telah Ditinjau).');
}
return redirect()->back()->with('error', 'Terjadi kesalahan saat menyetujui kegiatan.');
}
public function revision(Request $request, $id)
{
$request->validate([
'status_note' => 'required|string|min:5|max:255'
]);
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->post($this->apiUrl("/penugasan/{$id}/update-status"), [
'status' => 'proses',
'status_note' => 'REVISI dari ' . session('user_name') . ': ' . $request->status_note
]);
if ($response->successful()) {
return redirect()->back()->with('success', 'Kegiatan dikembalikan ke pegawai untuk direvisi.');
}
return redirect()->back()->with('error', 'Terjadi kesalahan saat meminta revisi.');
}
public function destroy(Request $request, $id)
{
$request->validate([
'alasan_hapus' => 'required|string|min:10|max:500',
]);
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->post($this->apiUrl("/penugasan/{$id}/delete"), [
'alasan' => $request->alasan_hapus
]);
if ($response->successful()) {
return redirect()->back()->with('success', 'Kegiatan berhasil dibatalkan dan dicatat dalam log.');
}
return redirect()->back()->with('error', 'Gagal membatalkan kegiatan.');
}
public function arsip()
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/penugasan'), ['is_archived' => 'true']);
$items = collect([]);
if ($response->successful()) {
$items = collect($response->json()['data'])->map(function($item) {
return (object) [
'id' => $item['id'],
'nama_kegiatan' => $item['kegiatan'],
'tanggal' => $item['tanggal_mulai'],
'tanggal_selesai' => $item['tanggal_selesai'] ?? null,
'created_at' => \Carbon\Carbon::parse($item['created_at']),
'status' => $item['status'] == 'proses' ? 'on_progres' : $item['status'],
'user' => (object) ['name' => $item['pembuat']['name'] ?? 'System'],
'petugas' => $item['petugas'] ?? [],
'pembuat' => $item['pembuat'] ?? []
];
});
}
// Manual Pagination
$perPage = 10;
$currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
$currentItems = $items->slice(($currentPage - 1) * $perPage, $perPage)->all();
$activities = new \Illuminate\Pagination\LengthAwarePaginator($currentItems, count($items), $perPage, $currentPage, [
'path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()
]);
return view('superadmin.arsip.index', compact('activities'));
}
public function archive($id)
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->post($this->apiUrl("/penugasan/{$id}/archive"));
if ($response->successful()) {
return redirect()->back()->with('success', 'Status arsip laporan berhasil diperbarui.');
}
return redirect()->back()->with('error', 'Gagal memperbarui status arsip laporan.');
}
public function userIndex()
{
// Panggil API Penugasan untuk menghitung activities dinamis
$penugasanResponse = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/penugasan'));
$items = collect([]);
if ($penugasanResponse->successful()) {
$items = collect($penugasanResponse->json()['data']);
}
// Panggil API Users
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/users'));
$users = collect([]);
if ($response->successful()) {
$users = collect($response->json()['data'])->map(function($u) use ($items) {
$activitiesCount = $items->filter(function($item) use ($u) {
// Cek apakah user ada di array petugas
if (isset($item['petugas']) && is_array($item['petugas'])) {
foreach ($item['petugas'] as $p) {
if ($p['id'] == $u['id']) return true;
}
}
// Fallback cek pembuat
return ($item['pembuat']['id'] ?? null) == $u['id'];
})->count();
return (object) [
'id' => $u['id'],
'name' => $u['name'],
'username' => $u['username'],
'role' => $u['role'],
'created_at' => isset($u['created_at']) ? \Carbon\Carbon::parse($u['created_at']) : now(),
'activities_count' => $activitiesCount
];
});
}
// Manual Pagination
$perPage = 15;
$currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
$currentItems = $users->slice(($currentPage - 1) * $perPage, $perPage)->all();
$paginatedUsers = new \Illuminate\Pagination\LengthAwarePaginator($currentItems, $users->count(), $perPage, $currentPage, [
'path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()
]);
$stats = [
'total' => $users->count(),
'active' => $users->where('activities_count', '>', 0)->count(),
'activities' => $users->sum('activities_count'),
'max_activities' => max($users->max('activities_count') ?? 1, 1),
];
return view('superadmin.user.index', ['users' => $paginatedUsers, 'stats' => $stats]);
}
public function createUser()
{
return view('superadmin.user.create-user');
}
public function storeUser(Request $request)
{
$request->validate([
'name' => 'required|string',
'username' => 'required|string',
'password' => 'required|string|min:6',
'role' => 'required|in:user,admin,superadmin',
]);
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->acceptJson()
->post($this->apiUrl('/register'), [
'name' => $request->name,
'username' => $request->username,
'password' => $request->password,
'role' => $request->role,
]);
if ($response->successful()) {
return redirect()->route('admin.superadmin.user.index')->with('success', 'Pengguna berhasil ditambahkan');
}
if ($response->status() === 422) {
return back()->withErrors($response->json('errors'))->withInput();
}
return back()->with('error', $response->json('message') ?? 'Gagal menambah pengguna')->withInput();
}
public function editUser($id)
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl("/users/{$id}"));
if ($response->successful()) {
$userData = $response->json()['data'];
$user = (object) [
'id' => $userData['id'],
'name' => $userData['name'],
'username' => $userData['username'],
'role' => $userData['role'],
'email' => $userData['email'] ?? '',
'created_at' => isset($userData['created_at'])
? \Carbon\Carbon::parse($userData['created_at'])
: now(),
];
return view('superadmin.user.edit-user', compact('user'));
}
return redirect()->back()->with('error', 'User tidak ditemukan atau gagal mengambil data.');
}
public function updateUser(Request $request, $id)
{
$request->validate([
'name' => 'required',
'username' => 'required',
'role' => 'required',
'password' => 'nullable|min:6|confirmed',
]);
$payload = $request->only('name', 'username', 'role');
if ($request->filled('password')) {
$payload['password'] = $request->password;
}
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->acceptJson()
->put($this->apiUrl("/users/{$id}"), $payload);
if ($response->successful()) {
return redirect()->route('admin.superadmin.user.index')->with('success', 'User berhasil diupdate');
}
if ($response->status() === 422) {
return back()->withErrors($response->json('errors'))->withInput();
}
return redirect()->back()->with('error', 'Gagal update user.')->withInput();
}
// 🔥 DELETE USER
public function destroyUser($id)
{
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->delete($this->apiUrl('/users/' . $id));
if ($response->successful()) {
return redirect()->route('admin.superadmin.user.index')->with('success', 'Pengguna berhasil dihapus');
}
return back()->with('error', 'Gagal menghapus pengguna');
}
// 🔥 CREATE KEGIATAN
public function createKegiatan()
{
// Ambil data users untuk dropdown pilihan pegawai (opsional)
$usersResponse = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/users'));
$users = [];
if ($usersResponse->successful()) {
$allUsers = $usersResponse->json()['data'] ?? [];
// Hanya tampilkan user dengan role "user" (Pegawai/Petugas)
$users = array_filter($allUsers, function($u) {
return isset($u['role']) && strtolower($u['role']) === 'user';
});
}
return view('superadmin.kegiatan.create', compact('users'));
}
// 🔥 STORE KEGIATAN
public function storeKegiatan(Request $request)
{
$request->validate([
'nomor_surat' => 'required|string',
'kegiatan' => 'required|string',
'tanggal_mulai' => 'required|date',
'tanggal_selesai' => 'nullable|date',
'pegawai_ids' => 'required|array',
]);
$payload = [
'nomor_surat' => $request->nomor_surat,
'kegiatan' => $request->kegiatan,
'tanggal_mulai' => $request->tanggal_mulai,
'tanggal_selesai' => $request->tanggal_selesai,
'pegawai_ids' => $request->pegawai_ids,
];
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->acceptJson()
->post($this->apiUrl('/penugasan'), $payload);
if ($response->successful()) {
return redirect()->route('admin.superadmin.dashboard')->with('success', 'Tugas baru berhasil dibuat');
}
if ($response->status() === 422) {
return back()->withErrors($response->json('errors'))->withInput();
}
return back()->with('error', 'Gagal membuat tugas baru: ' . $response->json('message', 'Error API'))->withInput();
}
public function export(Request $request)
{
$columns = $request->input('columns', []);
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/export-penugasan'), [
'columns' => $columns
]);
if ($response->successful()) {
$fileName = 'Laporan_Penugasan.xlsx';
$contentDisposition = $response->header('Content-Disposition');
if ($contentDisposition && preg_match('/filename="?([^"]+)"?/', $contentDisposition, $matches)) {
$fileName = $matches[1];
}
return response($response->body())
->header('Content-Type', $response->header('Content-Type') ?? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
->header('Content-Disposition', 'attachment; filename="' . $fileName . '"');
}
return back()->with('error', 'Gagal mengunduh laporan: Endpoint backend mungkin bermasalah atau tidak tersedia.');
}
public function chartData(Request $request)
{
$mode = $request->get('mode', 'week');
$response = \Illuminate\Support\Facades\Http::withToken(session('api_token'))
->get($this->apiUrl('/penugasan'));
$items = collect([]);
if ($response->successful()) {
$items = collect($response->json()['data'])->filter(function($item) {
return $item['status'] !== 'dibatalkan';
})->map(function($item) {
return (object) ['created_at' => \Carbon\Carbon::parse($item['created_at'])];
});
}
$data = $this->buildChartData($items);
return response()->json([
'success' => true,
'data' => $data[$mode] ?? $data['week'],
]);
}
}