@extends('layouts.app', ['class' => 'g-sidenav-show bg-gray-100']) @section('content') @include('layouts.navbars.auth.topnav', ['title' => '']) @php $user = $user_login ?? auth()->user(); $fullName = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')); $candidateName = $fullName !== '' ? $fullName : ($user->username ?? 'User'); $userName = is_numeric($candidateName) ? ($user->username ?? 'User') : $candidateName; $userId = $user->id ?? null; $sessionUsername = strtolower(trim((string) (session('username') ?? ''))); $modelUsername = strtolower(trim((string) ($user->username ?? ''))); $usernameFromId = $userId ? strtolower((string) (\Illuminate\Support\Facades\DB::table('users')->where('id', $userId)->value('username') ?? '')) : ''; $userLogin = $sessionUsername !== '' && !is_numeric($sessionUsername) ? $sessionUsername : ($modelUsername !== '' && !is_numeric($modelUsername) ? $modelUsername : $usernameFromId); $roleTokens = array_filter(array_map('trim', explode(',', strtolower($user->idroles ?? '')))); $isPrivilegedRole = count(array_intersect($roleTokens, ['admins', 'admin', 'hrdxxx', 'hrd', 'headxx'])) > 0; $isAdminDashboard = count(array_intersect($roleTokens, ['admins', 'admin'])) > 0; $filterMonth = request()->input('bulan', now()->format('m')); $filterYear = request()->input('tahun', now()->format('Y')); $monthNames = [ '01' => 'Januari', '02' => 'Februari', '03' => 'Maret', '04' => 'April', '05' => 'Mei', '06' => 'Juni', '07' => 'Juli', '08' => 'Agustus', '09' => 'September', '10' => 'Oktober', '11' => 'November', '12' => 'Desember', ]; $selectedMonthLabel = $monthNames[$filterMonth] ?? now()->translatedFormat('F'); $selectedPeriodLabel = $selectedMonthLabel . ' ' . $filterYear; $hrdUserIds = \Illuminate\Support\Facades\DB::table('users') ->where(function ($q) { $q->whereRaw("FIND_IN_SET('hrdxxx', REPLACE(LOWER(idroles), ' ', ''))") ->orWhereRaw("FIND_IN_SET('hrd', REPLACE(LOWER(idroles), ' ', ''))"); }) ->pluck('id') ->map(fn ($id) => (int) $id) ->all(); $hrdUsernames = \Illuminate\Support\Facades\DB::table('users') ->where(function ($q) { $q->whereRaw("FIND_IN_SET('hrdxxx', REPLACE(LOWER(idroles), ' ', ''))") ->orWhereRaw("FIND_IN_SET('hrd', REPLACE(LOWER(idroles), ' ', ''))"); }) ->pluck('username') ->map(fn ($username) => strtolower((string) $username)) ->all(); $isVisibleOnDashboard = function ($item) use ($isPrivilegedRole, $userId, $userLogin, $hrdUserIds, $hrdUsernames) { $uid = isset($item->user_id) ? (string) $item->user_id : ''; $uidInt = is_numeric($uid) ? (int) $uid : null; $createdBy = strtolower((string) ($item->user_create ?? '')); $isHrd = ($uidInt !== null && in_array($uidInt, $hrdUserIds, true)) || ($uid !== '' && !is_numeric($uid) && in_array(strtolower($uid), $hrdUsernames, true)) || ($createdBy !== '' && in_array($createdBy, $hrdUsernames, true)); $matchedUserId = $userId && $uidInt !== null && $uidInt === (int) $userId; $matchedUserCreate = $userLogin !== '' && $createdBy === $userLogin; if ($isPrivilegedRole) return true; if ($isHrd) return false; if ($matchedUserId) return true; if ($matchedUserCreate) return true; return false; }; // Get real data from tr_non_periodic table $allRequests = \Illuminate\Support\Facades\DB::table('tr_non_periodic') ->when(!$isPrivilegedRole && ($userId || $userLogin), function ($query) use ($userId, $userLogin) { $query->where(function ($inner) use ($userId, $userLogin) { if ($userId) $inner->where('user_id', $userId); if ($userLogin) $inner->orWhere('user_create', $userLogin); }); }) ->whereMonth('created_at', $filterMonth) ->whereYear('created_at', $filterYear) ->where(function ($query) { $query->where('is_active', 1)->orWhereNull('is_active'); }) ->orderBy('created_at', 'desc') ->get() ->filter(fn ($item) => $isVisibleOnDashboard($item)) ->values(); // Summary statistics $totalRequests = $allRequests->count(); $verifikasiRequests = $allRequests->filter(function ($item) { $status = strtolower(trim($item->request_status ?? '')); return in_array($status, ['verifikasi', 'review', 'pending', 'approved']) && empty($item->hrd_approval_date); })->count(); $pendingRequests = $allRequests->filter(function ($item) { $status = strtolower(trim($item->request_status ?? '')); return in_array($status, ['review', 'pending', 'approved']) && empty($item->hrd_approval_date); })->count(); $inProgressRequests = $allRequests->filter(function ($item) { $status = strtolower(trim($item->request_status ?? '')); $reviewApproved = in_array($status, ['review', 'approved']) && !empty($item->hrd_approval_date); $inProgress = in_array($status, ['pengadaan', 'pengerjaan', 'revisi']); return $reviewApproved || $inProgress; })->count(); $completedRequests = $allRequests->filter(function ($item) { $status = strtolower(trim($item->request_status ?? '')); return $status === 'completed'; })->count(); $stats = $isAdminDashboard ? [ ['label' => 'Total Permintaan', 'value' => $totalRequests, 'icon' => 'ni-collection', 'class' => 'primary', 'desc' => 'Semua pengajuan PU', 'url' => url('user')], ['label' => 'Menunggu Approval', 'value' => $verifikasiRequests, 'icon' => 'ni-time-alarm', 'class' => 'primary', 'desc' => 'Belum ditinjau HRD', 'url' => url('user') . '#list_user_verifikasi'], ['label' => 'Dalam Proses', 'value' => $inProgressRequests, 'icon' => 'ni-settings-gear-65','class' => 'primary', 'desc' => 'Pengadaan / pengerjaan aktif', 'url' => url('user') . '#list_user_tracking'], ['label' => 'Selesai', 'value' => $completedRequests, 'icon' => 'ni-check-bold', 'class' => 'primary', 'desc' => 'Pengajuan selesai', 'url' => url('user') . '?status=completed#list_user_history'], ] : [ ['label' => 'Total Permintaan', 'value' => $totalRequests, 'icon' => 'ni-collection', 'class' => 'primary', 'desc' => 'Semua permintaan', 'url' => url('user')], ['label' => 'Menunggu Approval', 'value' => $verifikasiRequests, 'icon' => 'ni-time-alarm', 'class' => 'primary', 'desc' => 'Belum ditinjau HRD', 'url' => url('user') . '#list_user_verifikasi'], ['label' => 'Ditugaskan', 'value' => $inProgressRequests, 'icon' => 'ni-settings-gear-65','class' => 'primary', 'desc' => 'Sedang dalam pengerjaan', 'url' => url('user') . '#list_user_tracking'], ['label' => 'Selesai', 'value' => $completedRequests, 'icon' => 'ni-check-bold', 'class' => 'primary', 'desc' => 'Pekerjaan selesai', 'url' => url('user') . '?status=completed#list_user_history'], ]; // Most frequent area $mostFrequentArea = $allRequests->groupBy('area_id') ->map(fn ($group) => $group->count()) ->sortDesc() ->keys() ->first(); $areaName = '-'; if ($mostFrequentArea) { $areaFromTable = \Illuminate\Support\Facades\DB::table('ms_area') ->where('id', $mostFrequentArea) ->value('nama_area'); $areaName = $areaFromTable ?? $mostFrequentArea; } // Recent activities $activities = []; foreach ($allRequests->take(5) as $request) { $status = strtolower(trim($request->request_status ?? '')); if ($status === 'verifikasi') { $statusText = 'Menunggu Approval'; $statusClass = 'bg-warning'; } elseif (in_array($status, ['review', 'pending', 'approved'])) { $statusText = 'Menunggu Approval'; $statusClass = 'bg-primary'; } elseif (in_array($status, ['pengadaan', 'pengerjaan'])) { $statusText = 'Ditugaskan'; $statusClass = 'bg-info'; } elseif ($status === 'completed') { $statusText = 'Selesai'; $statusClass = 'bg-success'; } elseif (in_array($status, ['reject', 'rejected'])) { $statusText = 'Ditolak'; $statusClass = 'bg-danger'; } elseif (in_array($status, ['cancel', 'cancelled', 'canceled'])) { $statusText = 'Dibatalkan'; $statusClass = 'bg-secondary'; } else { $statusText = 'Pending'; $statusClass = 'bg-primary'; } $createdDate = \Carbon\Carbon::parse($request->created_at); if ($createdDate->isToday()) $dateText = 'Hari ini'; elseif ($createdDate->isYesterday()) $dateText = 'Kemarin'; elseif ($createdDate->diffInDays() < 7) $dateText = floor($createdDate->diffInDays()) . ' hari lalu'; elseif ($createdDate->diffInWeeks() < 4) $dateText = floor($createdDate->diffInWeeks()) . ' minggu lalu'; else $dateText = $createdDate->format('d M Y'); $activities[] = [ 'title' => $request->job_description ?? 'Permintaan PU', 'status' => $statusText, 'status_class' => $statusClass, 'date' => $dateText, ]; } // ── Trend bulan terpilih ─────────────────────────────────────── $startDate = \Carbon\Carbon::createFromDate((int) $filterYear, (int) $filterMonth, 1)->startOfMonth(); $endDate = (clone $startDate)->endOfMonth(); $rangeDates = []; $cursor = $startDate->copy(); while ($cursor->lte($endDate)) { $rangeDates[] = $cursor->toDateString(); $cursor->addDay(); } $groupedByDate = $allRequests->groupBy(fn ($it) => \Carbon\Carbon::parse($it->created_at)->toDateString()); $trendLabels = []; $trendData = []; foreach ($rangeDates as $d) { $trendLabels[] = \Carbon\Carbon::parse($d)->format('d/m'); $trendData[] = isset($groupedByDate[$d]) ? $groupedByDate[$d]->count() : 0; } $periodicYearLabels = []; $periodicYearTotalData = []; $periodicYearDoneData = []; $periodicYearPendingData = []; $periodicYearRateData = []; $periodicYearSummary = []; for ($month = 1; $month <= 12; $month++) { $monthKey = str_pad((string) $month, 2, '0', STR_PAD_LEFT); $periodicYearLabels[] = $monthNames[$monthKey] ?? $monthKey; $periodicYearSummary[$monthKey] = ['total' => 0, 'done' => 0, 'pending' => 0]; } $periodicYearRows = \Illuminate\Support\Facades\DB::table('tr_periodic_items') ->where(fn ($query) => $query->where('is_active', 1)->orWhereNull('is_active')) ->whereNotNull('planned_date') ->whereYear('planned_date', $filterYear) ->select( \Illuminate\Support\Facades\DB::raw('MONTH(planned_date) as month_number'), \Illuminate\Support\Facades\DB::raw('COUNT(*) as total_count'), \Illuminate\Support\Facades\DB::raw('SUM(CASE WHEN realization_date IS NOT NULL THEN 1 ELSE 0 END) as done_count') ) ->groupBy(\Illuminate\Support\Facades\DB::raw('MONTH(planned_date)')) ->get(); foreach ($periodicYearRows as $row) { $monthKey = str_pad((string) $row->month_number, 2, '0', STR_PAD_LEFT); $totalCount = (int) $row->total_count; $doneCount = (int) $row->done_count; $periodicYearSummary[$monthKey] = [ 'total' => $totalCount, 'done' => $doneCount, 'pending' => max(0, $totalCount - $doneCount), ]; } foreach ($periodicYearSummary as $summary) { $periodicYearTotalData[] = $summary['total']; $periodicYearDoneData[] = $summary['done']; $periodicYearPendingData[] = $summary['pending']; $periodicYearRateData[] = $summary['total'] > 0 ? round(($summary['done'] / $summary['total']) * 100, 1) : 0; } $periodicYearTotal = array_sum($periodicYearTotalData); $periodicYearDone = array_sum($periodicYearDoneData); $periodicYearRate = $periodicYearTotal > 0 ? round(($periodicYearDone / $periodicYearTotal) * 100, 1) : 0; $periodicDetails = \Illuminate\Support\Facades\DB::table('tr_periodic_detail')->count(); $generatedItems = \Illuminate\Support\Facades\DB::table('tr_periodic_items')->count(); $unrealizedItems = \Illuminate\Support\Facades\DB::table('tr_periodic_items') ->whereNull('realization_date') ->count(); $totalDailyTasks = \Illuminate\Support\Facades\DB::table('ms_daily')->where('is_active', 1)->count(); $todayLoggedTasks = \Illuminate\Support\Facades\DB::table('rp_daily_log') ->whereDate('work_date', \Illuminate\Support\Carbon::today()->toDateString()) ->distinct() ->count('daily_task_id'); $todayCompleted = \Illuminate\Support\Facades\DB::table('rp_daily_log') ->whereDate('work_date', \Illuminate\Support\Carbon::today()->toDateString()) ->whereRaw('LOWER(COALESCE(job_status, \'\')) = ?', ['completed']) ->distinct() ->count('daily_task_id'); $dailyTarget = $totalDailyTasks; $completionRate = $dailyTarget > 0 ? round(($todayCompleted / $dailyTarget) * 100) : 0; $dailyRateLabels = []; $dailyRateData = []; $dailyCompletedData = []; $dailyTargetData = []; $dailyCompletedRows = \Illuminate\Support\Facades\DB::table('rp_daily_log') ->whereBetween('work_date', [$startDate->toDateString(), $endDate->toDateString()]) ->whereRaw('LOWER(COALESCE(job_status, \'\')) = ?', ['completed']) ->select('work_date', \Illuminate\Support\Facades\DB::raw('COUNT(DISTINCT daily_task_id) as completed_count')) ->groupBy('work_date') ->get() ->keyBy('work_date'); $dailyCursorDate = $startDate->copy(); while ($dailyCursorDate->lte($endDate)) { $dailyDate = $dailyCursorDate->toDateString(); $dailyCompletedCount = isset($dailyCompletedRows[$dailyDate]) ? (int) $dailyCompletedRows[$dailyDate]->completed_count : 0; $dailyRateLabels[] = $dailyCursorDate->format('d M'); $dailyCompletedData[] = $dailyCompletedCount; $dailyTargetData[] = $dailyTarget; $dailyRateData[] = $dailyTarget > 0 ? round(($dailyCompletedCount / $dailyTarget) * 100, 1) : 0; $dailyCursorDate->addDay(); } $dailyMonthlyAverage = count($dailyRateData) > 0 ? round(array_sum($dailyRateData) / count($dailyRateData), 1) : 0; // ── Highlight personal bulan terpilih ───────────────────────── $currentMonthStart = (clone $startDate); $currentMonthEnd = (clone $endDate); $currentMonthRequests = $allRequests->filter( fn ($item) => \Carbon\Carbon::parse($item->created_at)->between($currentMonthStart, $currentMonthEnd) ); $userIdsInRequests = $currentMonthRequests->pluck('user_id')->filter()->unique()->values()->all(); $departmentMap = []; if (!empty($userIdsInRequests)) { $departmentMap = \Illuminate\Support\Facades\DB::table('ms_employee') ->whereIn('user_id', $userIdsInRequests) ->join('ms_department', 'ms_employee.departemen_id', '=', 'ms_department.id') ->pluck('ms_department.nama_departemen', 'ms_employee.user_id') ->toArray(); } $allDepartments = \Illuminate\Support\Facades\DB::table('ms_department') ->where(fn ($query) => $query->where('is_active', '1')->orWhereNull('is_active')) ->orderBy('nama_departemen') ->pluck('nama_departemen') ->toArray(); $deptCounts = array_fill_keys($allDepartments, 0); $deptCounts['Tidak Diketahui'] = 0; foreach ($currentMonthRequests as $requestRow) { if (isset($requestRow->user_id) && isset($departmentMap[$requestRow->user_id])) { $deptName = $departmentMap[$requestRow->user_id]; if (!array_key_exists($deptName, $deptCounts)) $deptCounts[$deptName] = 0; $deptCounts[$deptName]++; } else { $deptCounts['Tidak Diketahui']++; } } $deptLabels = array_keys($deptCounts); $deptData = array_values($deptCounts); $chartColors = [ 'rgba(15,98,254,0.75)', 'rgba(66,190,101,0.75)', 'rgba(255,131,43,0.75)', 'rgba(250,77,86,0.75)', 'rgba(138,63,252,0.75)', 'rgba(51,177,255,0.75)', 'rgba(255,193,7,0.75)', 'rgba(233,30,99,0.75)', 'rgba(0,150,136,0.75)', 'rgba(121,85,72,0.75)', ]; $deptBgColors = []; foreach ($deptLabels as $idx => $label) { $deptBgColors[] = $label === 'Tidak Diketahui' ? 'rgba(158,158,158,0.45)' : $chartColors[$idx % count($chartColors)]; } $internalCount = $allRequests->where('work_type', 'internal')->count(); $vendorCount = $allRequests->where('work_type', 'vendor')->count(); $workTypeValue = $internalCount > $vendorCount ? 'Internal' : ($vendorCount > $internalCount ? 'Vendor' : 'Seimbang'); $vendorStatus = $internalCount > $vendorCount ? 'Internal (' . $internalCount . ')' : ($vendorCount > $internalCount ? 'Vendor (' . $vendorCount . ')' : 'Seimbang'); $highlights = [ ['label' => 'Area paling sering', 'value' => $areaName], ['label' => 'Metode pelaksana', 'value' => $vendorStatus], ['label' => 'Permintaan ' . $selectedMonthLabel, 'value' => $currentMonthRequests->count() . ' permintaan'], ]; $overdueEstimationAlerts = collect(); if ($isAdminDashboard) { $today = now()->toDateString(); $overdueEstimationAlerts = \Illuminate\Support\Facades\DB::table('tr_non_periodic') ->leftJoin('ms_area', 'tr_non_periodic.area_id', '=', 'ms_area.id') ->select( 'tr_non_periodic.id', 'tr_non_periodic.request_status', 'tr_non_periodic.job_description', 'tr_non_periodic.pengadaan_estimated_ended_at', 'tr_non_periodic.pengerjaan_estimated_ended_at', 'ms_area.nama_area as area_name', \Illuminate\Support\Facades\DB::raw(" CASE WHEN LOWER(COALESCE(tr_non_periodic.request_status, '')) = 'pengadaan' AND tr_non_periodic.pengadaan_estimated_ended_at IS NOT NULL AND tr_non_periodic.pengadaan_estimated_ended_at < '{$today}' THEN 'Pengadaan' WHEN LOWER(COALESCE(tr_non_periodic.request_status, '')) IN ('pengerjaan', 'revisi') AND tr_non_periodic.pengerjaan_estimated_ended_at IS NOT NULL AND tr_non_periodic.pengerjaan_estimated_ended_at < '{$today}' THEN 'Pengerjaan' ELSE NULL END as overdue_stage "), \Illuminate\Support\Facades\DB::raw(" CASE WHEN LOWER(COALESCE(tr_non_periodic.request_status, '')) = 'pengadaan' THEN tr_non_periodic.pengadaan_estimated_ended_at WHEN LOWER(COALESCE(tr_non_periodic.request_status, '')) IN ('pengerjaan', 'revisi') THEN tr_non_periodic.pengerjaan_estimated_ended_at ELSE NULL END as overdue_date ") ) ->where(function ($query) { $query->where('tr_non_periodic.is_active', '1') ->orWhereNull('tr_non_periodic.is_active'); }) ->where(function ($query) use ($today) { $query->where(function ($inner) use ($today) { $inner->whereRaw("LOWER(COALESCE(tr_non_periodic.request_status, '')) = ?", ['pengadaan']) ->whereNotNull('tr_non_periodic.pengadaan_estimated_ended_at') ->whereDate('tr_non_periodic.pengadaan_estimated_ended_at', '<', $today); })->orWhere(function ($inner) use ($today) { $inner->whereRaw("LOWER(COALESCE(tr_non_periodic.request_status, '')) IN (?, ?)", ['pengerjaan', 'revisi']) ->whereNotNull('tr_non_periodic.pengerjaan_estimated_ended_at') ->whereDate('tr_non_periodic.pengerjaan_estimated_ended_at', '<', $today); }); }) ->orderBy('overdue_date') ->limit(10) ->get(); } @endphp @if (request()->ajax()) @php header('Content-Type: application/json'); echo json_encode([ 'stats' => $stats, 'activities' => $activities, 'highlights' => $highlights, 'trendLabels' => $trendLabels, 'trendData' => $trendData, 'deptLabels' => $deptLabels, 'deptData' => $deptData, 'deptBgColors' => $deptBgColors, 'today' => now()->format('d M Y'), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; @endphp @endif

Notifikasi PU

Selamat datang, {{ $userName }}

Menampilkan data periode {{ $selectedPeriodLabel }}

{{ now()->format('d M Y') }}
@if ($isAdminDashboard) @if ($overdueEstimationAlerts->isNotEmpty())
Pengajuan Melewati Estimasi

{{ $overdueEstimationAlerts->count() }} pengajuan masih aktif tetapi tanggal estimasinya sudah lewat.

Lihat Daftar PU
@foreach ($overdueEstimationAlerts as $alert) @endforeach
Pekerjaan Area Tahap Estimasi Selesai Aksi
{{ $alert->job_description ?? '-' }} {{ $alert->area_name ?? '-' }} {{ $alert->overdue_stage ?? '-' }} {{ $alert->overdue_date ? \Illuminate\Support\Carbon::parse($alert->overdue_date)->format('d-m-Y') : '-' }} Detail
@endif
Jadwal Periodic

Template Aktif

{{ $periodicDetails }}

Item Generated

{{ $generatedItems }}
{{ $unrealizedItems }} item belum direalisasikan
Daily Checklist - {{ now()->format('d M Y') }}

Progress Completion

{{ $completionRate }}%

{{ $todayCompleted }} dari {{ $dailyTarget }} task hari ini

@if ($todayLoggedTasks < $dailyTarget)

{{ $todayLoggedTasks }} task sudah diinput hari ini

@endif
Aktivitas Terbaru
@forelse ($activities as $activity) @empty @endforelse
Permintaan Status Waktu
{{ $activity['title'] }}

Permintaan pekerjaan PU

{{ $activity['status'] }} {{ $activity['date'] }}
Belum ada aktivitas.
Highlight
@foreach ($highlights as $item)
{{ $item['label'] }} {{ $item['value'] }}
@endforeach
Realisasi Periodic - {{ $filterYear }}
Total: {{ $periodicYearTotal }} | Terlaksana: {{ $periodicYearDone }} | Rate: {{ $periodicYearRate }}%
Completion Rate Daily - {{ $selectedPeriodLabel }}
Rata-rata: {{ $dailyMonthlyAverage }}% | Target harian: {{ $dailyTarget }} task
Permintaan Departemen - {{ $selectedPeriodLabel }}
Distribusi permintaan berdasarkan departemen pengaju
Trend Permintaan Non-Periodic - {{ $selectedPeriodLabel }}
Pergerakan jumlah permintaan berdasarkan tanggal pengajuan
@else
{{-- Aktivitas Terbaru --}}
Aktivitas Terbaru
@forelse ($activities as $activity) @empty @endforelse
Permintaan Status Waktu
{{ $activity['title'] }}

Permintaan pekerjaan PU

{{ $activity['status'] }} {{ $activity['date'] }}
Belum ada aktivitas.
{{-- Panel Kanan --}}

Status Proyek

PU Monitoring
{{-- Chart Trend Bulan Terpilih --}}
Trend Permintaan Non-Periodic - {{ $selectedPeriodLabel }}
{{-- Highlight --}}
Highlight
@foreach ($highlights as $item)
{{ $item['label'] }} {{ $item['value'] }}
@endforeach
@endif
@endsection @push('js') @endpush