first
This commit is contained in:
150
resources/views/admin/audit-logs/index.blade.php
Normal file
150
resources/views/admin/audit-logs/index.blade.php
Normal file
@@ -0,0 +1,150 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Log Audit')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-journal-text me-2 text-primary"></i>Log Audit</h4>
|
||||
<a href="{{ route('admin.audit-logs.export', request()->query()) }}"
|
||||
class="btn btn-outline-success btn-sm">
|
||||
<i class="bi bi-download me-1"></i>Eksport CSV
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body py-2">
|
||||
<form method="GET" class="row g-2 align-items-center">
|
||||
<div class="col-md-2">
|
||||
<select class="form-select form-select-sm" name="action">
|
||||
<option value="">Semua Tindakan</option>
|
||||
@foreach($actions as $action)
|
||||
<option value="{{ $action }}" {{ request('action') === $action ? 'selected' : '' }}>
|
||||
{{ $action }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control form-control-sm" name="search"
|
||||
value="{{ request('search') }}" placeholder="Cari tindakan / justifikasi...">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="date" class="form-control form-control-sm" name="date_from"
|
||||
value="{{ request('date_from') }}" placeholder="Dari tarikh">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="date" class="form-control form-control-sm" name="date_to"
|
||||
value="{{ request('date_to') }}" placeholder="Hingga tarikh">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit">
|
||||
<i class="bi bi-search me-1"></i>Cari
|
||||
</button>
|
||||
<a href="{{ route('admin.audit-logs.index') }}" class="btn btn-sm btn-outline-secondary ms-1">
|
||||
<i class="bi bi-x me-1"></i>Padam
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Masa</th>
|
||||
<th>Oleh</th>
|
||||
<th>Tindakan</th>
|
||||
<th>Sasaran</th>
|
||||
<th>IP</th>
|
||||
<th>Butiran</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($logs as $log)
|
||||
<tr>
|
||||
<td class="text-nowrap small text-muted">
|
||||
{{ $log->created_at?->format('d/m/Y H:i:s') }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="fw-semibold small">{{ $log->actor?->name ?? 'Sistem' }}</span>
|
||||
@if($log->actor_role)
|
||||
<span class="badge bg-light text-dark ms-1 small">{{ $log->actor_role }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<code class="small">{{ $log->action }}</code>
|
||||
</td>
|
||||
<td class="small">
|
||||
{{ $log->targetUser?->name ?? ($log->subject_type ? class_basename($log->subject_type).'#'.$log->subject_id : '—') }}
|
||||
</td>
|
||||
<td class="small text-muted">{{ $log->ip_address ?? '—' }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary py-0"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#logModal{{ $log->id }}">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{-- Detail Modal --}}
|
||||
<div class="modal fade" id="logModal{{ $log->id }}" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h6 class="modal-title"><code>{{ $log->action }}</code></h6>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body small">
|
||||
<table class="table table-sm">
|
||||
<tr><td width="30%">Masa</td><td>{{ $log->created_at?->format('d/m/Y H:i:s') }}</td></tr>
|
||||
<tr><td>Oleh</td><td>{{ $log->actor?->name ?? 'Sistem' }} ({{ $log->actor_role }})</td></tr>
|
||||
<tr><td>IP</td><td>{{ $log->ip_address ?? '—' }}</td></tr>
|
||||
<tr>
|
||||
<td>Justifikasi</td>
|
||||
<td>{{ $log->justification ?? '—' }}</td>
|
||||
</tr>
|
||||
@if($log->old_values)
|
||||
<tr>
|
||||
<td>Nilai Lama</td>
|
||||
<td><pre class="mb-0 small bg-light p-2 rounded">{{ json_encode($log->old_values, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) }}</pre></td>
|
||||
</tr>
|
||||
@endif
|
||||
@if($log->new_values)
|
||||
<tr>
|
||||
<td>Nilai Baru</td>
|
||||
<td><pre class="mb-0 small bg-light p-2 rounded">{{ json_encode($log->new_values, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) }}</pre></td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">
|
||||
<i class="bi bi-inbox fs-4 d-block mb-2"></i>Tiada rekod log.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($logs->hasPages())
|
||||
<div class="card-footer bg-white">
|
||||
{{ $logs->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
254
resources/views/admin/dashboard.blade.php
Normal file
254
resources/views/admin/dashboard.blade.php
Normal file
@@ -0,0 +1,254 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Dashboard')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-speedometer2 me-2 text-primary"></i>Dashboard Pentadbir</h4>
|
||||
<span class="text-muted small">{{ now()->format('d M Y, H:i') }}</span>
|
||||
</div>
|
||||
|
||||
{{-- Baris 1: Pengguna & Projek --}}
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card bg-success text-white h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="fs-1 fw-bold">{{ $stats['users_active'] }}</div>
|
||||
<div class="opacity-75 small">Pengguna Aktif</div>
|
||||
<div class="mt-1 opacity-60" style="font-size:0.78rem">
|
||||
{{ $stats['users_total'] }} jumlah •
|
||||
{{ $stats['users_inactive'] }} dinyahaktif
|
||||
</div>
|
||||
</div>
|
||||
<i class="bi bi-people-fill fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card bg-primary text-white h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="fs-1 fw-bold">{{ $stats['projects_total'] }}</div>
|
||||
<div class="opacity-75 small">Jumlah Projek</div>
|
||||
<div class="mt-1 opacity-60" style="font-size:0.78rem">
|
||||
+{{ $stats['projects_last_7'] }} (7 hari) •
|
||||
+{{ $stats['projects_last_30'] }} (30 hari)
|
||||
</div>
|
||||
</div>
|
||||
<i class="bi bi-folder-fill fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card bg-info text-white h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="fs-1 fw-bold">{{ $stats['projects_completed'] }}</div>
|
||||
<div class="opacity-75 small">Transkripsi Selesai</div>
|
||||
@php
|
||||
$pct = $stats['projects_total'] > 0
|
||||
? round($stats['projects_completed'] / $stats['projects_total'] * 100)
|
||||
: 0;
|
||||
@endphp
|
||||
<div class="mt-1 opacity-60" style="font-size:0.78rem">
|
||||
{{ $pct }}% kadar kejayaan
|
||||
</div>
|
||||
</div>
|
||||
<i class="bi bi-check-circle-fill fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card bg-warning text-dark h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
@php
|
||||
$totalBytes = $stats['storage_bytes'];
|
||||
if ($totalBytes >= 1073741824) {
|
||||
$storageLabel = number_format($totalBytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($totalBytes >= 1048576) {
|
||||
$storageLabel = number_format($totalBytes / 1048576, 1) . ' MB';
|
||||
} else {
|
||||
$storageLabel = number_format($totalBytes / 1024, 0) . ' KB';
|
||||
}
|
||||
$durationH = intdiv($stats['duration_total_seconds'], 3600);
|
||||
$durationM = intdiv($stats['duration_total_seconds'] % 3600, 60);
|
||||
@endphp
|
||||
<div class="fs-3 fw-bold">{{ $storageLabel }}</div>
|
||||
<div class="small" style="opacity:0.8">Storan Digunakan</div>
|
||||
<div class="mt-1" style="font-size:0.78rem; opacity:0.7">
|
||||
{{ $durationH }}j {{ $durationM }}m jumlah audio
|
||||
</div>
|
||||
</div>
|
||||
<i class="bi bi-hdd-fill fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Baris 2: Status + Trend --}}
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-white fw-semibold">
|
||||
<i class="bi bi-pie-chart me-2 text-primary"></i>Status Transkripsi
|
||||
</div>
|
||||
<div class="card-body pb-2">
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
@php
|
||||
$statusRows = [
|
||||
['bg-secondary', 'Menunggu', $stats['projects_pending']],
|
||||
['bg-warning text-dark', 'Sedang Diproses', $stats['projects_processing']],
|
||||
['bg-success', 'Selesai', $stats['projects_completed']],
|
||||
['bg-danger', 'Gagal', $stats['projects_failed']],
|
||||
['bg-light text-muted border', 'Dipadam (soft)', $stats['projects_deleted']],
|
||||
];
|
||||
@endphp
|
||||
@foreach($statusRows as [$cls, $label, $count])
|
||||
<tr>
|
||||
<td><span class="badge {{ $cls }}">{{ $label }}</span></td>
|
||||
<td class="fw-bold">{{ $count }}</td>
|
||||
<td class="text-muted small">
|
||||
@if($stats['projects_total'] > 0)
|
||||
{{ round($count / ($stats['projects_total'] ?: 1) * 100) }}%
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-white fw-semibold">
|
||||
<i class="bi bi-bar-chart me-2 text-success"></i>Trend 30 Hari Lepas
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($trendRaw->isEmpty())
|
||||
<div class="text-muted small text-center py-4">Tiada data projek dalam 30 hari lepas.</div>
|
||||
@else
|
||||
<div class="table-responsive" style="max-height:220px; overflow-y:auto">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light sticky-top">
|
||||
<tr>
|
||||
<th>Tarikh</th>
|
||||
<th class="text-center">Baru</th>
|
||||
<th class="text-center text-success">Selesai</th>
|
||||
<th class="text-center text-danger">Gagal</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($trendRaw->sortByDesc('date') as $row)
|
||||
<tr>
|
||||
<td class="small">{{ \Carbon\Carbon::parse($row->date)->format('d/m') }}</td>
|
||||
<td class="text-center">{{ $row->total }}</td>
|
||||
<td class="text-center text-success">{{ $row->completed }}</td>
|
||||
<td class="text-center text-danger">{{ $row->failed }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-white fw-semibold">
|
||||
<i class="bi bi-trophy me-2 text-warning"></i>Pengguna Paling Aktif
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($topUsers->isEmpty())
|
||||
<div class="text-muted small text-center py-4">Tiada data pengguna.</div>
|
||||
@else
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
@foreach($topUsers as $i => $u)
|
||||
<tr>
|
||||
<td style="width:28px" class="text-center text-muted small">{{ $i + 1 }}</td>
|
||||
<td>
|
||||
<div class="small fw-semibold">{{ $u->name }}</div>
|
||||
<div class="text-muted" style="font-size:0.72rem">{{ $u->email }}</div>
|
||||
</td>
|
||||
<td class="text-end fw-bold small">{{ $u->owned_projects_count }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Baris 3: Jabatan + Tindakan Pantas --}}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-white fw-semibold">
|
||||
<i class="bi bi-building me-2 text-secondary"></i>Projek Mengikut Jabatan
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($deptStats->isEmpty())
|
||||
<div class="text-muted small text-center py-4">Tiada data jabatan.</div>
|
||||
@else
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
@foreach($deptStats as $dept)
|
||||
<tr>
|
||||
<td class="small">{{ $dept->name }}</td>
|
||||
<td class="text-end">
|
||||
<span class="badge bg-primary">{{ $dept->project_count }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-white fw-semibold">
|
||||
<i class="bi bi-lightning me-2 text-warning"></i>Tindakan Pantas
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column gap-2">
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-people me-2"></i>Urus Pengguna
|
||||
<span class="badge bg-primary ms-1">{{ $stats['users_total'] }}</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.users.create') }}" class="btn btn-outline-success">
|
||||
<i class="bi bi-person-plus me-2"></i>Daftar Pengguna Baru
|
||||
</a>
|
||||
<a href="{{ route('admin.projects.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-folder2 me-2"></i>Senarai Projek (Metadata)
|
||||
<span class="badge bg-secondary ms-1">{{ $stats['projects_total'] }}</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.audit-logs.index') }}" class="btn btn-outline-dark">
|
||||
<i class="bi bi-journal-text me-2"></i>Log Audit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
215
resources/views/admin/projects/index.blade.php
Normal file
215
resources/views/admin/projects/index.blade.php
Normal file
@@ -0,0 +1,215 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Senarai Projek')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-folder2 me-2 text-primary"></i>Senarai Projek</h4>
|
||||
<p class="text-muted small mb-0 mt-1">Metadata sahaja — kandungan audio dan teks tidak dipaparkan.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-speedometer2 me-2"></i>Kembali ke Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body py-2">
|
||||
<form method="GET" class="row g-2 align-items-center">
|
||||
<div class="col-md-3">
|
||||
<input type="text" class="form-control form-control-sm" name="search"
|
||||
value="{{ request('search') }}" placeholder="Cari tajuk projek...">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select class="form-select form-select-sm" name="status">
|
||||
<option value="">Semua Status</option>
|
||||
<option value="pending" {{ request('status') === 'pending' ? 'selected' : '' }}>Menunggu</option>
|
||||
<option value="processing" {{ request('status') === 'processing' ? 'selected' : '' }}>Sedang Diproses</option>
|
||||
<option value="completed" {{ request('status') === 'completed' ? 'selected' : '' }}>Selesai</option>
|
||||
<option value="failed" {{ request('status') === 'failed' ? 'selected' : '' }}>Gagal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select form-select-sm" name="owner_id">
|
||||
<option value="">Semua Pemilik</option>
|
||||
@foreach($owners as $owner)
|
||||
<option value="{{ $owner->id }}" {{ request('owner_id') == $owner->id ? 'selected' : '' }}>
|
||||
{{ $owner->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit">
|
||||
<i class="bi bi-search me-1"></i>Cari
|
||||
</button>
|
||||
<a href="{{ route('admin.projects.index') }}" class="btn btn-sm btn-outline-secondary ms-1">Padam</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Tajuk</th>
|
||||
<th>Pemilik</th>
|
||||
<th>Status</th>
|
||||
<th>Saiz Fail</th>
|
||||
<th>Tempoh</th>
|
||||
<th>Kolaborator</th>
|
||||
<th>Tarikh Cipta</th>
|
||||
<th style="width:80px">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$statusMap = [
|
||||
'pending' => ['bg-secondary', 'Menunggu'],
|
||||
'processing' => ['bg-warning text-dark', 'Sedang Diproses'],
|
||||
'completed' => ['bg-success', 'Selesai'],
|
||||
'failed' => ['bg-danger', 'Gagal'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@forelse($projects as $project)
|
||||
<tr class="{{ $project->deleted_at ? 'table-secondary text-muted' : '' }}">
|
||||
<td>
|
||||
<span class="fw-semibold">{{ $project->title }}</span>
|
||||
@if($project->deleted_at)
|
||||
<span class="badge bg-secondary ms-1 small">Dipadam</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="small">{{ $project->owner->name ?? '—' }}</span>
|
||||
<div class="text-muted" style="font-size:0.75rem">{{ $project->owner->email ?? '' }}</div>
|
||||
</td>
|
||||
<td>
|
||||
@php [$cls, $label] = $statusMap[$project->transcription_status] ?? ['bg-secondary', $project->transcription_status]; @endphp
|
||||
<span class="badge {{ $cls }}">{{ $label }}</span>
|
||||
</td>
|
||||
<td class="small">
|
||||
@php
|
||||
$bytes = $project->file_size;
|
||||
echo $bytes >= 1048576
|
||||
? number_format($bytes/1048576, 1) . ' MB'
|
||||
: number_format($bytes/1024, 0) . ' KB';
|
||||
@endphp
|
||||
</td>
|
||||
<td class="small">
|
||||
@if($project->duration_seconds)
|
||||
{{ intdiv($project->duration_seconds, 60) }}:{{ str_pad($project->duration_seconds % 60, 2, '0', STR_PAD_LEFT) }}
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-light text-dark border">
|
||||
{{ $project->projectCollaborators->count() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="small text-muted text-nowrap">
|
||||
{{ $project->created_at->format('d/m/Y H:i') }}
|
||||
</td>
|
||||
<td>
|
||||
@if(! $project->deleted_at)
|
||||
<button class="btn btn-sm btn-outline-warning"
|
||||
title="Pindah Pemilikan"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modalTransfer{{ $project->id }}"
|
||||
data-project-title="{{ $project->title }}"
|
||||
data-project-owner="{{ $project->owner->name ?? '—' }}">
|
||||
<i class="bi bi-arrow-left-right"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted py-4">
|
||||
<i class="bi bi-inbox fs-4 d-block mb-2"></i>Tiada projek ditemui.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($projects->hasPages())
|
||||
<div class="card-footer bg-white">
|
||||
{{ $projects->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-muted small">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Kandungan audio, teks transkripsi, dan ulasan projek tidak dipaparkan kepada pentadbir.
|
||||
</div>
|
||||
|
||||
{{-- Transfer Ownership Modals --}}
|
||||
@foreach($projects as $project)
|
||||
@if(! $project->deleted_at)
|
||||
<div class="modal fade" id="modalTransfer{{ $project->id }}" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h6 class="modal-title">
|
||||
<i class="bi bi-arrow-left-right me-2 text-warning"></i>Pindah Pemilikan Projek
|
||||
</h6>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('admin.projects.transfer-owner', $project) }}">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning py-2 small">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
Tindakan ini akan memindahkan semua hak pemilik projek
|
||||
<strong>"{{ $project->title }}"</strong> kepada pengguna lain.
|
||||
Pemilik asal: <strong>{{ $project->owner->name ?? '—' }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Pemilik Baru <span class="text-danger">*</span></label>
|
||||
<select class="form-select" name="new_owner_id" required>
|
||||
<option value="">— Pilih pengguna —</option>
|
||||
@foreach($owners as $owner)
|
||||
@if($owner->id !== $project->owner_user_id)
|
||||
<option value="{{ $owner->id }}">{{ $owner->name }}</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">
|
||||
Justifikasi <span class="text-danger">*</span>
|
||||
<span class="text-muted fw-normal small">(min. 10 aksara)</span>
|
||||
</label>
|
||||
<textarea class="form-control" name="justification" rows="3"
|
||||
placeholder="Nyatakan sebab pemindahan projek ini..."
|
||||
minlength="10" maxlength="1000" required></textarea>
|
||||
<div class="form-text">Justifikasi ini akan direkodkan dalam log audit.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-warning"
|
||||
onclick="return confirm('Sahkan pemindahan pemilikan projek ini?')">
|
||||
<i class="bi bi-arrow-left-right me-1"></i>Pindah Pemilikan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@endsection
|
||||
93
resources/views/admin/users/create.blade.php
Normal file
93
resources/views/admin/users/create.blade.php
Normal file
@@ -0,0 +1,93 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Daftar Pengguna Baru')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-person-plus me-2 text-primary"></i>Daftar Pengguna Baru</h4>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.users.store') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="name" class="form-label fw-semibold">Nama Penuh <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('name') is-invalid @enderror"
|
||||
id="name" name="name" value="{{ old('name') }}" required>
|
||||
@error('name')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="form-label fw-semibold">E-mel <span class="text-danger">*</span></label>
|
||||
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
||||
id="email" name="email" value="{{ old('email') }}" required>
|
||||
@error('email')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="password" class="form-label fw-semibold">Kata Laluan <span class="text-danger">*</span></label>
|
||||
<input type="password" class="form-control @error('password') is-invalid @enderror"
|
||||
id="password" name="password" required>
|
||||
<div class="form-text">Minimum 8 aksara, huruf besar, huruf kecil, dan nombor.</div>
|
||||
@error('password')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="password_confirmation" class="form-label fw-semibold">Sahkan Kata Laluan <span class="text-danger">*</span></label>
|
||||
<input type="password" class="form-control"
|
||||
id="password_confirmation" name="password_confirmation" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="role" class="form-label fw-semibold">Peranan <span class="text-danger">*</span></label>
|
||||
<select class="form-select @error('role') is-invalid @enderror" id="role" name="role" required>
|
||||
<option value="user" {{ old('role', 'user') === 'user' ? 'selected' : '' }}>Pengguna Jabatan</option>
|
||||
<option value="admin" {{ old('role') === 'admin' ? 'selected' : '' }}>Pentadbir</option>
|
||||
</select>
|
||||
@error('role')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="department_id" class="form-label fw-semibold">Jabatan</label>
|
||||
<select class="form-select @error('department_id') is-invalid @enderror"
|
||||
id="department_id" name="department_id">
|
||||
<option value="">— Pilih Jabatan —</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}"
|
||||
{{ old('department_id') == $dept->id ? 'selected' : '' }}>
|
||||
{{ $dept->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('department_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-person-check me-1"></i>Daftarkan Pengguna
|
||||
</button>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-secondary">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
81
resources/views/admin/users/edit.blade.php
Normal file
81
resources/views/admin/users/edit.blade.php
Normal file
@@ -0,0 +1,81 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Kemaskini E-mel Pengguna')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-pencil me-2 text-primary"></i>Kemaskini E-mel Pengguna</h4>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{-- User Info Card --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light fw-semibold">Maklumat Pengguna</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm mb-0">
|
||||
<tr>
|
||||
<td class="text-muted" width="35%">Nama</td>
|
||||
<td class="fw-semibold">{{ $user->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">E-mel Semasa</td>
|
||||
<td>{{ $user->email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Jabatan</td>
|
||||
<td>{{ $user->department?->name ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Status</td>
|
||||
<td>
|
||||
@if($user->is_active)
|
||||
<span class="badge bg-success">Aktif</span>
|
||||
@else
|
||||
<span class="badge bg-secondary">Dinyahaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Email Update Form --}}
|
||||
<div class="card">
|
||||
<div class="card-header bg-light fw-semibold">Kemaskini E-mel</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.users.update-email', $user) }}">
|
||||
@csrf @method('PATCH')
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label fw-semibold">E-mel Baru <span class="text-danger">*</span></label>
|
||||
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
||||
id="email" name="email" value="{{ old('email', $user->email) }}" required>
|
||||
@error('email')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="justification" class="form-label fw-semibold">Justifikasi</label>
|
||||
<textarea class="form-control" id="justification" name="justification"
|
||||
rows="3" maxlength="500"
|
||||
placeholder="Nyatakan sebab perubahan e-mel...">{{ old('justification') }}</textarea>
|
||||
<div class="form-text">Perubahan ini akan direkod dalam log audit.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-envelope-check me-1"></i>Kemaskini E-mel
|
||||
</button>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-secondary">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
125
resources/views/admin/users/index.blade.php
Normal file
125
resources/views/admin/users/index.blade.php
Normal file
@@ -0,0 +1,125 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Senarai Pengguna')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0"><i class="bi bi-people me-2 text-primary"></i>Senarai Pengguna</h4>
|
||||
<a href="{{ route('admin.users.create') }}" class="btn btn-primary">
|
||||
<i class="bi bi-person-plus me-1"></i>Daftar Pengguna Baru
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body py-2">
|
||||
<form method="GET" class="row g-2 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<input type="text" class="form-control form-control-sm" name="search"
|
||||
value="{{ request('search') }}" placeholder="Cari nama atau e-mel...">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select form-select-sm" name="status">
|
||||
<option value="">Semua Status</option>
|
||||
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>Aktif</option>
|
||||
<option value="inactive" {{ request('status') === 'inactive' ? 'selected' : '' }}>Dinyahaktif</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit">
|
||||
<i class="bi bi-search me-1"></i>Cari
|
||||
</button>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-sm btn-outline-secondary ms-1">Padam Carian</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Nama</th>
|
||||
<th>E-mel</th>
|
||||
<th>Jabatan</th>
|
||||
<th>Status</th>
|
||||
<th>Log Masuk Terakhir</th>
|
||||
<th>Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($users as $user)
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-semibold">{{ $user->name }}</span>
|
||||
</td>
|
||||
<td>{{ $user->email }}</td>
|
||||
<td>{{ $user->department?->name ?? '—' }}</td>
|
||||
<td>
|
||||
@if($user->is_active)
|
||||
<span class="badge bg-success">Aktif</span>
|
||||
@else
|
||||
<span class="badge bg-secondary">Dinyahaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
{{ $user->last_login_at?->format('d/m/Y H:i') ?? 'Belum pernah' }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<a href="{{ route('admin.users.edit', $user) }}"
|
||||
class="btn btn-sm btn-outline-primary" title="Edit E-mel">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
|
||||
@if($user->is_active)
|
||||
<form method="POST" action="{{ route('admin.users.deactivate', $user) }}"
|
||||
onsubmit="return confirm('Dinyahaktifkan akaun {{ $user->name }}?')">
|
||||
@csrf @method('PATCH')
|
||||
<button class="btn btn-sm btn-outline-warning" title="Nyahaktif">
|
||||
<i class="bi bi-person-x"></i>
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<form method="POST" action="{{ route('admin.users.activate', $user) }}">
|
||||
@csrf @method('PATCH')
|
||||
<button class="btn btn-sm btn-outline-success" title="Aktifkan">
|
||||
<i class="bi bi-person-check"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if(! $user->hasUsage())
|
||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||
onsubmit="return confirm('PADAM akaun {{ $user->name }} secara kekal?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn btn-sm btn-outline-danger" title="Padam">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">
|
||||
<i class="bi bi-inbox fs-4 d-block mb-2"></i>Tiada pengguna ditemui.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($users->hasPages())
|
||||
<div class="card-footer bg-white">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user