Sync SOP ke e-doc Plant & Add Subject dan penyempurnaan bagian matrix training karyawan

This commit is contained in:
Iwit
2026-06-04 19:01:17 +07:00
parent 91c0a8147f
commit 1089f60a3d
26 changed files with 1241 additions and 155 deletions
@@ -8,6 +8,7 @@ use App\Models\User;
use App\Models\Sop;
use App\Models\ExamResult;
use Illuminate\View\View;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
@@ -44,13 +45,193 @@ class DashboardController extends Controller
return view('pages.admin.master-data.index');
}
public function openSopFile($id)
{
// 1. Cari tahu versi terbaru dari dokumen ini di database
$sop = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblDocumentContent')
->where('document', $id)
->orderBy('version', 'desc')
->first();
// Jika file tidak ada di database, kembalikan ke halaman sebelumnya dengan pesan error
if (!$sop) {
return back()->with('error', 'File PDF untuk dokumen ini belum diunggah atau tidak ditemukan.');
}
$versi = $sop->version;
// 2. Susun URL persis seperti fitur "View Online" bawaan SeedDMS
$seeddmsUrl = "http://192.168.10.11/plantlib/plantlibrary/op/op.ViewOnline.php?documentid=" . $id . "&version=" . $versi;
// 3. Lontarkan pengguna ke URL tersebut
return redirect()->away($seeddmsUrl);
}
/**
* 4. Fungsi untuk menu Dokumen SOP
*/
public function sopIndex(): View
public function sopIndex(\Illuminate\Http\Request $request)
{
return view('pages.admin.sop.index');
$search = $request->input('search');
$department = $request->input('department');
$category = $request->input('category');
$version = $request->input('version');
// 1. Departemen
$departments = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblFolders')
->where('parent', 59)
->orderBy('name', 'asc')
->get();
// 2. Kategori
$categories = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblCategory')
->orderBy('name', 'asc')
->get();
// 3. Versi: Diambil dengan Logika Fallback (Cek Nomor -> Cek Judul)
$allDocs = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblDocuments')
->select('name', 'comment')
->get();
$versions = $allDocs->map(function ($doc) {
// Prioritas 1: 2 Karakter Terakhir dari Nomor SOP
$vName = substr(trim($doc->name), -2);
if (is_numeric($vName)) return $vName;
// Prioritas 2: 2 Karakter Terakhir dari Judul SOP
$vComment = substr(trim($doc->comment), -2);
if (is_numeric($vComment)) return $vComment;
return null; // Abaikan jika keduanya tidak memiliki akhiran angka
})->filter()->unique()->sort()->values();
// 4. Query Utama
$query = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblDocuments as d')
->leftJoin('tblFolders as f_doc', 'd.folder', '=', 'f_doc.id')
->leftJoin('tblFolders as f_parent', 'f_doc.parent', '=', 'f_parent.id')
->leftJoin('tblDocumentCategory as dc', 'd.id', '=', 'dc.documentID')
->leftJoin('tblCategory as cat', 'dc.categoryID', '=', 'cat.id')
->leftJoin('tblDocumentContent as c', 'd.id', '=', 'c.document')
->select(
'd.id',
'd.name as nomor_sop',
'd.comment as judul_sop',
'c.orgFileName as file_pdf',
'c.dir as direktori',
'd.date as tanggal_dibuat',
'cat.name as nama_kategori',
\Illuminate\Support\Facades\DB::raw("
CASE
WHEN f_doc.parent = 59 THEN f_doc.name
WHEN f_parent.parent = 59 THEN f_parent.name
ELSE f_doc.name
END as nama_departemen
")
);
// 5. Logika Filter
if ($search) {
$query->where(function($q) use ($search) {
$q->where('d.name', 'LIKE', "%{$search}%")
->orWhere('d.comment', 'LIKE', "%{$search}%");
});
}
if ($department) {
$query->where(function($q) use ($department) {
$q->where('f_doc.id', $department)
->orWhere('f_doc.parent', $department)
->orWhere('f_doc.folderList', 'LIKE', "%:{$department}:%");
});
}
if ($category) {
$query->where('dc.categoryID', $category);
}
// Filter Versi Cerdas SQL (Jika 2 digit nama bukan angka, cari di judul)
if ($version !== null && $version !== '') {
$query->where(function($q) use ($version) {
$q->whereRaw("RIGHT(TRIM(d.name), 2) = ?", [$version])
->orWhere(function($sub) use ($version) {
$sub->whereRaw("RIGHT(TRIM(d.name), 2) NOT REGEXP '^[0-9]+$'")
->whereRaw("RIGHT(TRIM(d.comment), 2) = ?", [$version]);
});
});
}
// 6. Logika Ekspor dengan Metadata
if ($request->has('export')) {
$dataExport = $query->orderBy('d.date', 'desc')->get();
$totalRecords = $dataExport->count();
$downloader = auth()->user()->first_name . ' ' . auth()->user()->last_name;
$downloadTime = now()->format('d M Y H:i:s');
if ($request->export == 'excel') {
return $this->exportCsv($dataExport, $totalRecords, $downloader, $downloadTime);
} elseif ($request->export == 'pdf') {
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pages.admin.sops.pdf', compact('dataExport', 'totalRecords', 'downloader', 'downloadTime'))
->setPaper('a4', 'landscape');
return $pdf->download('Data_Dokumen_Tunggal_Pharma.pdf');
}
}
$sops = $query->orderBy('d.date', 'desc')->paginate(20);
return view('pages.admin.sops.index', compact('sops', 'departments', 'categories', 'versions'));
}
private function exportCsv($data, $totalRecords, $downloader, $downloadTime)
{
$fileName = 'Daftar_Dokumen_' . date('Ymd_His') . '.csv';
$headers = [
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=$fileName",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
];
$callback = function() use($data, $totalRecords, $downloader, $downloadTime) {
$file = fopen('php://output', 'w');
fputcsv($file, ['DAFTAR MASTER DOKUMEN - TUNGGAL PHARMA']);
fputcsv($file, ['Diunduh Oleh:', $downloader]);
fputcsv($file, ['Waktu Unduh:', $downloadTime . ' WIB']);
fputcsv($file, ['Total Record:', $totalRecords . ' Dokumen']);
fputcsv($file, []);
fputcsv($file, ['No', 'Departemen/Folder', 'Kategori', 'Nomor Dokumen', 'Judul Dokumen', 'Versi', 'Tanggal Upload']);
$i = 1;
foreach ($data as $row) {
// Logika Ekstraksi Versi untuk Excel
$versi = substr(trim($row->nomor_sop), -2);
if (!is_numeric($versi)) {
$versi = substr(trim($row->judul_sop), -2);
if (!is_numeric($versi)) $versi = '-';
}
$tanggal = \Carbon\Carbon::createFromTimestamp($row->tanggal_dibuat)->format('d-m-Y');
fputcsv($file, [
$i++,
$row->nama_departemen ?? '-',
$row->nama_kategori ?? '-',
$row->nomor_sop,
$row->judul_sop,
"v." . $versi,
$tanggal
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}
@@ -87,9 +87,12 @@ class EmployeeController extends Controller
->pluck('initial')
->map(fn($i) => strtolower($i))
->toArray();
// [TAMBAHAN] Menarik daftar Materi/SOP untuk ditampilkan di form
$subjects = \App\Models\Subject::orderBy('name', 'asc')->get();
return view('pages.admin.employee.create', compact(
'departments', 'positions', 'trainingMatrices', 'deptPosMapping', 'existingInitials'
'departments', 'positions', 'trainingMatrices', 'deptPosMapping', 'existingInitials', 'subjects'
));
}
@@ -112,6 +115,7 @@ class EmployeeController extends Controller
'position_id' => 'required|exists:positions,id',
'training_matrix_id' => 'nullable|integer',
'documents.*' => 'nullable|file|max:5120',
'trainings' => 'nullable|array', // Validasi input matrix
]);
DB::beginTransaction();
@@ -121,10 +125,11 @@ class EmployeeController extends Controller
$validated['role'] = $request->role ?? 'karyawan';
$validated['must_change_password'] = true;
$userData = Arr::except($validated, ['documents', 'is_trainer']);
$userData = Arr::except($validated, ['documents', 'is_trainer', 'trainings']);
$user = User::create($userData);
// Pengaturan Hak Akses (Role)
$rolesToAssign = [];
if ($validated['role'] == 'admin') {
$rolesToAssign[] = 'admin';
@@ -142,6 +147,18 @@ class EmployeeController extends Controller
$user->assignRole(array_unique($rolesToAssign));
// [TAMBAHAN] Menyimpan Training Matrix ke tabel Pivot (employee_subject)
if ($request->has('trainings')) {
$matrixData = [];
foreach ($request->trainings as $subjectId => $training) {
if (isset($training['selected']) && $training['selected'] == 1) {
$matrixData[$subjectId] = ['status' => $training['status']];
}
}
$user->trainings()->sync($matrixData);
}
// Menyimpan Dokumen
if ($request->hasFile('documents')) {
foreach ($request->file('documents') as $file) {
$fileName = time() . '_' . str_replace(' ', '_', $file->getClientOriginalName());
@@ -167,7 +184,6 @@ class EmployeeController extends Controller
DB::commit();
// FIX: Menggunakan rute jamak (employees)
return redirect()->route('admin.employees.index')
->with('success', "Data Karyawan {$user->first_name} berhasil ditambahkan! Password default: Karyawan123!");
@@ -179,12 +195,16 @@ class EmployeeController extends Controller
public function show(User $employee)
{
$employee->load(['department', 'position', 'roles']);
// [TAMBAHAN] Memuat relasi 'trainings' agar matrix terlihat di halaman Detail
$employee->load(['department', 'position', 'roles', 'trainings']);
return view('pages.admin.employee.show', compact('employee'));
}
public function edit(User $employee)
{
// [TAMBAHAN] Memuat relasi trainings yang sudah dimiliki karyawan
$employee->load('trainings');
$departments = Department::with('positions')->orderBy('name', 'asc')->get();
$positions = Position::all();
@@ -197,7 +217,10 @@ class EmployeeController extends Controller
$marketingDeptIds = Department::where('name', 'LIKE', '%MKT%')->orWhere('name', 'LIKE', '%MARKETING%')->pluck('id');
$trainingMatrices = TrainingMatrix::whereIn('department_id', $marketingDeptIds)->get();
return view('pages.admin.employee.edit', compact('employee', 'departments', 'positions', 'trainingMatrices', 'deptPosMapping'));
// [TAMBAHAN] Menarik daftar semua Materi/SOP
$subjects = \App\Models\Subject::orderBy('name', 'asc')->get();
return view('pages.admin.employee.edit', compact('employee', 'departments', 'positions', 'trainingMatrices', 'deptPosMapping', 'subjects'));
}
public function update(Request $request, User $employee)
@@ -219,6 +242,7 @@ class EmployeeController extends Controller
'position_id' => 'required|exists:positions,id',
'training_matrix_id' => 'nullable|integer',
'is_active' => 'nullable|boolean',
'trainings' => 'nullable|array', // Validasi input matrix
]);
DB::beginTransaction();
@@ -228,10 +252,11 @@ class EmployeeController extends Controller
$validated['password'] = Hash::make($request->password);
}
$userData = Arr::except($validated, ['is_trainer', 'is_active']);
$userData = Arr::except($validated, ['is_trainer', 'is_active', 'trainings']);
$userData['is_active'] = $request->has('is_active');
$employee->update($userData);
// Perbarui Hak Akses (Role)
$rolesToAssign = [];
$inputRole = $validated['role'] ?? 'karyawan';
@@ -246,6 +271,22 @@ class EmployeeController extends Controller
$employee->syncRoles(array_unique($rolesToAssign));
// [TAMBAHAN] Memperbarui Training Matrix
if ($request->has('trainings')) {
$matrixData = [];
foreach ($request->trainings as $subjectId => $training) {
// Cek jika dicentang
if (isset($training['selected']) && $training['selected'] == 1) {
$matrixData[$subjectId] = ['status' => $training['status']];
}
}
// sync() akan secara otomatis menghapus yang tidak dicentang dan menambah/memperbarui yang dicentang
$employee->trainings()->sync($matrixData);
} else {
// Jika tidak ada materi yang dicentang sama sekali, kosongkan riwayat trainingnya
$employee->trainings()->detach();
}
DB::table('audit_trails')->insert([
'user_id' => auth()->id(),
'action' => 'Update Employee',
@@ -256,7 +297,6 @@ class EmployeeController extends Controller
DB::commit();
// FIX: Menggunakan rute jamak (employees)
return redirect()->route('admin.employees.index')->with('success', "Data Karyawan {$employee->first_name} berhasil diperbarui.");
} catch (\Exception $e) {
@@ -264,7 +304,7 @@ class EmployeeController extends Controller
return back()->with('error', 'Gagal memperbarui data! ' . $e->getMessage())->withInput();
}
}
public function destroy(User $employee)
{
if ($employee->hasRole('admin')) {
@@ -6,54 +6,60 @@ use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Department;
use App\Models\Position;
use App\Models\Subject; // PASTIKAN SUBJECT DI-IMPORT
use App\Models\Subject;
use App\Models\User;
use App\Models\QuestionBank;
class ExamManagementController extends Controller
{
public function questionBank(Request $request)
public function questionBank(Request $request)
{
$departments = Department::orderBy('name', 'asc')->get();
$positions = Position::orderBy('name', 'asc')->get();
$subjects = Subject::orderBy('name', 'asc')->get(); // Mengambil data Subject
$creators = User::whereHas('roles', function($q){
$q->whereIn('name', ['admin', 'trainer']);
})->orderBy('first_name', 'asc')->get();
// 1. Ambil ID unik siapa saja yang PERNAH membuat soal di tabel question_banks
$creatorIds = \App\Models\QuestionBank::whereNotNull('created_by')
->distinct()
->pluck('created_by');
// Load relasi subject, department, position, dan creator
$query = QuestionBank::with(['subject', 'department', 'position', 'creator']);
// 2. Tarik data User HANYA berdasarkan ID pembuat soal tersebut
$creators = \App\Models\User::whereIn('id', $creatorIds)
->orderBy('first_name', 'asc')
->get();
// Logika Filter
if ($request->filled('search')) {
$query->where('question_text', 'like', '%' . $request->search . '%');
// 3. Tarik data pendukung lainnya untuk filter & dropdown
$subjects = \App\Models\Subject::orderBy('name', 'asc')->get();
$departments = \App\Models\Department::orderBy('name', 'asc')->get();
$positions = \App\Models\Position::orderBy('name', 'asc')->get();
// 4. Hitung Total Soal Keseluruhan (TANPA FILTER) untuk Badge di View
$totalQuestions = \App\Models\QuestionBank::count(); // <--- TAMBAHKAN BARIS INI
// 5. Query utama Bank Soal (dengan fitur pencarian/filter)
$query = \App\Models\QuestionBank::with(['subject', 'department', 'creator', 'position']);
// Filter berdasarkan pembuat soal
if ($request->filled('creator_id')) {
$query->where('created_by', $request->creator_id);
}
// Filter berdasarkan subject/materi
if ($request->filled('subject_id')) {
$query->where('subject_id', $request->subject_id);
}
// Filter berdasarkan departemen
if ($request->filled('department_id')) {
$query->where('department_id', $request->department_id);
}
// Filter berdasarkan posisi/jabatan
if ($request->filled('position_id')) {
$query->where('position_id', $request->position_id);
}
if ($request->filled('subject_id')) { // Filter berdasarkan Subject
$query->where('subject_id', $request->subject_id);
}
if ($request->filled('question_type')) {
$query->where('question_type', $request->question_type);
}
if ($request->filled('question_level')) {
$query->where('question_level', $request->question_level);
}
if ($request->filled('created_by')) {
$query->where('created_by', $request->created_by);
}
$questions = $query->latest()->paginate(15)->withQueryString();
$totalQuestions = $questions->total();
// Pagination
$questions = $query->latest()->paginate(20);
return view('pages.admin.exams.question-bank', compact(
'questions', 'totalQuestions', 'departments', 'positions', 'subjects', 'creators'
));
// Pastikan $totalQuestions ikut dikirim ke compact
return view('pages.admin.question-banks.question-bank', compact('questions', 'creators', 'subjects', 'departments', 'positions', 'totalQuestions'));
}
// Fungsi untuk menampilkan halaman Tambah Soal
@@ -63,7 +69,7 @@ class ExamManagementController extends Controller
$positions = Position::orderBy('name', 'asc')->get();
$subjects = Subject::orderBy('name', 'asc')->get(); // Kirim Subject ke form
return view('pages.admin.exams.create', compact('departments', 'positions', 'subjects'));
return view('pages.admin.question-banks.create', compact('departments', 'positions', 'subjects'));
}
// Fungsi untuk menampilkan halaman Edit Soal
@@ -74,14 +80,14 @@ class ExamManagementController extends Controller
$positions = Position::orderBy('name', 'asc')->get();
$subjects = Subject::orderBy('name', 'asc')->get(); // Kirim Subject ke form
return view('pages.admin.exams.edit', compact('question', 'departments', 'positions', 'subjects'));
return view('pages.admin.question-banks.edit', compact('question', 'departments', 'positions', 'subjects'));
}
// Fungsi Show untuk Detail Soal
public function show($id)
{
$question = QuestionBank::with(['subject', 'department', 'position', 'creator'])->findOrFail($id);
return view('pages.admin.exams.show', compact('question'));
return view('pages.admin.question-banks.show', compact('question'));
}
/**
@@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Subject;
use Illuminate\Http\Request;
class SubjectController extends Controller
{
public function index()
{
// Tarik data materi LMS
$subjects = Subject::withCount('questions')->orderBy('name', 'asc')->paginate(20);
// Tarik data SOP dari database server 11 untuk opsi Dropdown
$sops = \Illuminate\Support\Facades\DB::connection('plantdoc')
->table('tblDocuments')
->select('name as nomor', 'comment as judul')
->orderBy('name', 'asc')
->get();
// Pastikan $sops dikirim ke view
return view('pages.admin.subjects.index', compact('subjects', 'sops'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255|unique:subjects,name',
]);
Subject::create(['name' => $request->name]);
return redirect()->route('admin.subjects.index')->with('success', 'Materi baru berhasil ditambahkan.');
}
public function update(Request $request, $id)
{
$request->validate([
'name' => 'required|string|max:255|unique:subjects,name,' . $id,
]);
$subject = Subject::findOrFail($id);
$subject->update(['name' => $request->name]);
return redirect()->route('admin.subjects.index')->with('success', 'Nama materi berhasil diperbarui.');
}
public function show($id)
{
// Tarik data materi beserta soal-soalnya secara menurun (terbaru di atas)
$subject = \App\Models\Subject::with(['questions' => function($query) {
$query->latest();
}])->withCount('questions')->findOrFail($id);
return view('pages.admin.subjects.show', compact('subject'));
}
public function edit($id)
{
$subject = Subject::findOrFail($id);
return view('pages.admin.subjects.edit', compact('subject'));
}
public function destroy($id)
{
$subject = Subject::findOrFail($id);
// Cek apakah materi ini sudah dipakai di bank soal
if ($subject->questions()->count() > 0) {
return redirect()->route('admin.subjects.index')->with('error', 'Gagal menghapus! Materi ini sedang digunakan pada Bank Soal.');
}
$subject->delete();
return redirect()->route('admin.subjects.index')->with('success', 'Materi berhasil dihapus.');
}
}
+7 -5
View File
@@ -73,11 +73,13 @@ class User extends Authenticatable
return $this->belongsTo(Position::class, 'position_id');
}
// Jika model TrainingMatrix sudah Anda buat, buka komentar di bawah ini:
/*
public function trainingMatrix()
public function trainings()
{
return $this->belongsTo(TrainingMatrix::class);
// Ubah 'user_id' menjadi 'employee_id' agar sesuai dengan database Anda saat ini
return $this->belongsToMany(Subject::class, 'employee_subject', 'employee_id', 'subject_id')
->withPivot('status')
->withTimestamps();
}
*/
}
+16
View File
@@ -64,6 +64,22 @@ return [
]) : [],
],
'plantdoc' => [
'driver' => env('DB_PLANTDOC_CONNECTION', 'mysql'),
'host' => env('DB_PLANTDOC_HOST', '192.168.10.11'),
'port' => env('DB_PLANTDOC_PORT', '3306'),
'database' => env('DB_PLANTDOC_DATABASE', 'plantdoc'),
'username' => env('DB_PLANTDOC_USERNAME', 'root'),
'password' => env('DB_PLANTDOC_PASSWORD', 'r4h4s1an1h'),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
],
// database lama dari CI3
'mysql_old' => [
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create('employee_subject', function (Blueprint $table) {
$table->id();
// Menggunakan user_id karena data karyawan ada di tabel users
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('subject_id');
$table->enum('status', ['belum_training', 'passed'])->default('belum_training');
$table->timestamps();
// Relasikan ke tabel users
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('subject_id')->references('id')->on('subjects')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('employee_subject');
}
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"resources/css/app.css": {
"file": "assets/app-CyRyejKf.css",
"file": "assets/app-PaZlYqLy.css",
"name": "app",
"names": [
"app.css"
@@ -115,13 +115,37 @@
</div>
<div class="border-t border-slate-100 pt-6 mt-6">
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
<select name="training_matrix_id" class="select2 w-full text-sm">
<option value="">-- Kosongkan jika bukan Plant Dept --</option>
@foreach($trainingMatrices as $matrix)
<option value="{{ $matrix->id }}" {{ old('training_matrix_id') == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
@endforeach
</select>
<h3 class="text-sm font-bold text-slate-700 mb-1">Materi SOP / Training Matrix</h3>
<p class="text-xs text-slate-500 mb-4">Pilih materi wajib untuk karyawan ini dan tentukan status pelatihannya.</p>
<div class="bg-slate-50 border border-slate-200 rounded-xl p-4 max-h-[400px] overflow-y-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="border-b border-slate-200 text-slate-500 uppercase text-xs sticky top-0 bg-slate-50">
<tr>
<th class="py-2 w-12 text-center">Pilih</th>
<th class="py-2">Nama Materi / Dokumen SOP</th>
<th class="py-2 w-48 text-center">Status Training</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
@foreach($subjects as $subject)
<tr class="hover:bg-white transition-colors">
<td class="py-3 text-center">
<input type="checkbox" name="trainings[{{ $subject->id }}][selected]" value="1"
class="w-5 h-5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer">
</td>
<td class="py-3 font-bold text-slate-700">{{ $subject->name }}</td>
<td class="py-3">
<select name="trainings[{{ $subject->id }}][status]" class="w-full px-3 py-1.5 bg-white border border-slate-300 rounded-lg text-xs font-bold focus:ring-blue-500">
<option value="belum_training" class="text-amber-600">Belum Training</option>
<option value="passed" class="text-emerald-600">Lulus (Passed)</option>
</select>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="border-t border-slate-100 pt-6 mt-6" x-data="{ documents: [{ id: 1 }], addDocument() { this.documents.push({ id: Date.now() }); }, removeDocument(id) { this.documents = this.documents.filter(doc => doc.id !== id); } }">
@@ -20,6 +20,16 @@
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Data Karyawan</h2>
</div>
@if ($errors->any())
<div class="mb-6 p-4 bg-red-50 text-red-700 rounded-xl border border-red-100 text-sm font-bold">
<ul class="list-disc pl-5">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<form action="{{ route('admin.employees.update', $employee->id) }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
@csrf
@@ -85,7 +95,7 @@
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan <span class="text-red-500">*</span></label>
<select name="position_id" id="posSelect" class="select2 w-full text-sm" required>
<option value="">-- Pilih Jabatan --</option>
</select>
</select>
</div>
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
@@ -114,13 +124,82 @@
</div>
<div class="border-t border-slate-100 pt-6 mt-6">
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
<select name="training_matrix_id" class="select2 w-full text-sm">
<option value="">-- Kosongkan jika bukan marketing --</option>
@foreach($trainingMatrices as $matrix)
<option value="{{ $matrix->id }}" {{ old('training_matrix_id', $employee->training_matrix_id) == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
@endforeach
</select>
<h3 class="text-sm font-bold text-slate-700 mb-1">Materi SOP / Training Matrix</h3>
<p class="text-xs text-slate-500 mb-4">Perbarui materi dan status pelatihan untuk karyawan ini.</p>
<div class="bg-slate-50 border border-slate-200 rounded-xl p-4 max-h-[400px] overflow-y-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="border-b border-slate-200 text-slate-500 uppercase text-xs sticky top-0 bg-slate-50">
<tr>
<th class="py-2 w-12 text-center">Pilih</th>
<th class="py-2">Nama Materi / Dokumen SOP</th>
<th class="py-2 w-48 text-center">Status Training</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
@foreach($subjects as $subject)
@php
$hasTraining = $employee->trainings->contains('id', $subject->id);
$currentStatus = $hasTraining ? $employee->trainings->find($subject->id)->pivot->status : 'belum_training';
@endphp
<tr class="hover:bg-white transition-colors">
<td class="py-3 text-center">
<input type="checkbox" name="trainings[{{ $subject->id }}][selected]" value="1"
{{ $hasTraining ? 'checked' : '' }}
class="w-5 h-5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer">
</td>
<td class="py-3 font-bold text-slate-700">{{ $subject->name }}</td>
<td class="py-3">
<select name="trainings[{{ $subject->id }}][status]" class="w-full px-3 py-1.5 bg-white border border-slate-300 rounded-lg text-xs font-bold focus:ring-blue-500">
<option value="belum_training" class="text-amber-600" {{ $currentStatus == 'belum_training' ? 'selected' : '' }}>Belum Training</option>
<option value="passed" class="text-emerald-600" {{ $currentStatus == 'passed' ? 'selected' : '' }}>Lulus (Passed)</option>
</select>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="mt-8 pt-6 border-t border-slate-200">
<h3 class="text-lg font-bold text-slate-800 mb-2">Training Matrix & Kompetensi</h3>
<p class="text-sm text-slate-500 mb-4">Pilih materi/SOP wajib untuk karyawan ini dan perbarui status pelatihannya.</p>
<div class="bg-slate-50 border border-slate-200 rounded-xl p-4 max-h-[400px] overflow-y-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="border-b border-slate-200 text-slate-500 uppercase text-xs sticky top-0 bg-slate-50">
<tr>
<th class="py-2 w-12 text-center">Pilih</th>
<th class="py-2">Nama Materi / Dokumen SOP</th>
<th class="py-2 w-48 text-center">Status Training</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
@foreach($subjects as $subject)
@php
// Mengecek apakah relasi dengan pivot sudah ada di database
$hasTraining = $employee->trainings->contains('id', $subject->id);
$currentStatus = $hasTraining ? $employee->trainings->find($subject->id)->pivot->status : 'belum_training';
@endphp
<tr class="hover:bg-white transition-colors">
<td class="py-3 text-center">
<input type="checkbox" name="trainings[{{ $subject->id }}][selected]" value="1"
{{ $hasTraining ? 'checked' : '' }}
class="w-5 h-5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer">
</td>
<td class="py-3 font-bold text-slate-700">{{ $subject->name }}</td>
<td class="py-3">
<select name="trainings[{{ $subject->id }}][status]" class="w-full px-3 py-1.5 bg-white border border-slate-300 rounded-lg text-xs font-bold focus:ring-blue-500">
<option value="belum_training" class="text-amber-600" {{ $currentStatus == 'belum_training' ? 'selected' : '' }}>Belum Training</option>
<option value="passed" class="text-emerald-600" {{ $currentStatus == 'passed' ? 'selected' : '' }}>Lulus (Passed)</option>
</select>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="border-t border-slate-100 pt-6 mt-6">
@@ -140,7 +219,6 @@
$(document).ready(function() {
$('.select2').select2({ width: '100%' });
// Logika Dropdown Bertingkat untuk Edit (Sama dengan Create)
const mapping = @json($deptPosMapping ?? []);
const posSelect = $('#posSelect');
const deptSelect = $('#deptSelect');
@@ -88,6 +88,52 @@
</dl>
</div>
<div class="mt-8 bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-100 bg-slate-50 flex items-center justify-between">
<h3 class="text-lg font-bold text-slate-800">Riwayat Training & SOP</h3>
<span class="px-3 py-1 bg-blue-100 text-blue-700 text-xs font-bold rounded-full">
{{ $employee->trainings->count() }} Materi Diikuti
</span>
</div>
<div class="p-0 overflow-x-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="bg-white border-b border-slate-100 text-slate-500 uppercase text-xs">
<tr>
<th class="px-6 py-3 w-16 text-center">No</th>
<th class="px-6 py-3">Nama Materi / Dokumen</th>
<th class="px-6 py-3 w-48 text-center">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
@forelse($employee->trainings as $index => $training)
<tr class="hover:bg-slate-50 transition-colors">
<td class="px-6 py-4 text-center text-slate-400">{{ $index + 1 }}</td>
<td class="px-6 py-4 font-bold text-slate-700">{{ $training->name }}</td>
<td class="px-6 py-4 text-center">
@if($training->pivot->status == 'passed')
<span class="inline-flex items-center px-2.5 py-1 bg-emerald-50 text-emerald-700 rounded-md text-xs font-bold border border-emerald-200">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
Lulus
</span>
@else
<span class="inline-flex items-center px-2.5 py-1 bg-amber-50 text-amber-700 rounded-md text-xs font-bold border border-amber-200">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
Belum Training
</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="3" class="px-6 py-8 text-center text-slate-500">Karyawan ini belum ditugaskan untuk materi/SOP apapun.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<!-- Kolom Kanan: Info Personal -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 pb-2 border-b border-slate-100">Informasi Personal & Kontak</h3>
@@ -115,14 +115,15 @@
</div>
<div>
<label class="block text-xs font-semibold text-slate-500 mb-1">Pembuat Soal</label>
<select name="created_by" class="select2 w-full text-sm">
<option value="">Semua</option>
@foreach($creators as $creator)
<option value="{{ $creator->id }}" {{ request('created_by') == $creator->id ? 'selected' : '' }}>{{ $creator->first_name }} {{ $creator->last_name }}</option>
@endforeach
</select>
</div>
<label class="block text-xs font-bold text-slate-500 uppercase mb-1">Pembuat Soal</label>
<select name="creator_id" class="w-full px-3 py-2 bg-white border border-slate-200 rounded-lg text-sm focus:ring-blue-500" onchange="this.form.submit()">
<option value="">-- Semua Trainer --</option>
@foreach($creators as $creator)
<option value="{{ $creator->id }}" {{ request('creator_id') == $creator->id ? 'selected' : '' }}>
{{ $creator->first_name }} {{ $creator->last_name }}
</option>
@endforeach
</select>
</div>
<div class="flex flex-col md:flex-row gap-4 items-center justify-between border-t border-slate-100 pt-4">
@@ -1,79 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
<div>
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen SOP & Training Matrix</h2>
<p class="text-sm text-slate-500 mt-1">Kelola dokumen Standar Operasional Prosedur dan modul pelatihan.</p>
</div>
<div class="flex flex-wrap items-center gap-3">
<a href="#" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
+ Tambah SOP Baru
</a>
</div>
</div>
<div class="bg-white p-5 rounded-t-2xl border border-slate-200 border-b-0">
<form action="{{ route('admin.sops.index') }}" method="GET" class="flex flex-col md:flex-row gap-4">
<div class="w-full md:w-1/3">
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari judul SOP atau Modul..."
class="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-blue-500">
</div>
<button type="submit" class="px-5 py-2 bg-slate-800 text-white rounded-xl text-sm font-semibold hover:bg-slate-700">Cari</button>
@if(request('search'))
<a href="{{ route('admin.sops.index') }}" class="px-5 py-2 bg-red-50 text-red-600 border border-red-100 rounded-xl text-sm font-semibold hover:bg-red-100">Reset</a>
@endif
</form>
</div>
<div class="bg-white border border-slate-200 rounded-b-2xl overflow-hidden shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left text-sm whitespace-nowrap">
<thead class="bg-slate-50/80 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
<tr>
<th class="px-6 py-4 font-semibold">No</th>
<th class="px-6 py-4 font-semibold">Judul SOP / Modul</th>
<th class="px-6 py-4 font-semibold">Departemen Terkait</th>
<th class="px-6 py-4 font-semibold">Status</th>
<th class="px-6 py-4 font-semibold text-right">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 text-slate-700">
{{-- Ganti $sops dengan variabel yang Anda kirim dari DashboardController@sopIndex --}}
@forelse($sops ?? [] as $index => $sop)
<tr class="hover:bg-slate-50">
<td class="px-6 py-4 text-slate-500">{{ $index + 1 }}</td>
<td class="px-6 py-4 font-bold text-slate-900">{{ $sop->title ?? $sop->name ?? 'Judul SOP' }}</td>
<td class="px-6 py-4">{{ $sop->department->name ?? 'Semua Departemen' }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 bg-emerald-50 text-emerald-600 rounded text-[10px] font-bold uppercase tracking-wider border border-emerald-100">Aktif</span>
</td>
<td class="px-6 py-4 text-right space-x-3">
<a href="#" class="text-indigo-600 hover:text-indigo-800 font-medium text-sm">Lihat Dokumen</a>
<a href="#" class="text-slate-400 hover:text-blue-600 font-medium text-sm">Edit</a>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-6 py-12 text-center">
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
<p class="text-slate-500 font-medium">Belum ada data SOP atau Training Matrix.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Jika ada pagination --}}
@if(isset($sops) && method_exists($sops, 'hasPages') && $sops->hasPages())
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
{{ $sops->links() }}
</div>
@endif
</div>
</div>
@endsection
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Membuka Dokumen...</title>
<style>
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f8fafc; margin: 0; }
.loader { text-align: center; color: #64748b; }
.spinner { border: 4px solid #e2e8f0; border-top: 4px solid #3b82f6; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 15px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body onload="document.getElementById('seeddms-login').submit();">
<div class="loader">
<div class="spinner"></div>
<h3>Sedang menghubungkan ke server dokumen...</h3>
<p>Mohon tunggu sebentar.</p>
</div>
<form id="seeddms-login" action="{{ $loginUrl }}" method="POST" style="display: none;">
<input type="hidden" name="guestlogin" value="1">
<input type="hidden" name="referuri" value="{{ $targetUri }}">
</form>
</body>
</html>
@@ -0,0 +1,134 @@
@extends('layouts.app')
@section('content')
<div class="max-w-7xl mx-auto px-4 py-8">
<div class="mb-6">
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Master Dokumen SOP</h2>
<p class="text-sm text-slate-500 mt-1">Data ditarik secara Real-Time (Read-Only) dari Database Plantdoc Server 192.168.10.11</p>
</div>
<div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-200 mb-6">
<form action="{{ route('admin.sops.index') }}" method="GET" class="flex flex-wrap items-end gap-4">
<div class="flex-1 min-w-[200px]">
<label class="block text-xs font-bold text-slate-500 uppercase mb-2">Pencarian</label>
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari No / Judul..."
class="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm focus:ring-blue-500 bg-slate-50 focus:bg-white">
</div>
<div class="w-full md:w-48">
<label class="block text-xs font-bold text-slate-500 uppercase mb-2">Folder / Dept</label>
<select name="department" class="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm bg-slate-50 focus:bg-white">
<option value="">Semua Folder</option>
@foreach($departments as $dept)
<option value="{{ $dept->id }}" {{ request('department') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
@endforeach
</select>
</div>
<div class="w-full md:w-48">
<label class="block text-xs font-bold text-slate-500 uppercase mb-2">Kategori</label>
<select name="category" class="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm bg-slate-50 focus:bg-white">
<option value="">Semua Kategori</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}" {{ request('category') == $cat->id ? 'selected' : '' }}>{{ $cat->name }}</option>
@endforeach
</select>
</div>
<div class="w-full md:w-28">
<label class="block text-xs font-bold text-slate-500 uppercase mb-2">Versi</label>
<select name="version" class="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm bg-slate-50 focus:bg-white">
<option value="">Semua</option>
@foreach($versions as $ver)
<option value="{{ $ver }}" {{ request('version') == $ver ? 'selected' : '' }}>v.{{ $ver }}</option>
@endforeach
</select>
</div>
<div class="flex gap-2 w-full md:w-auto">
<button type="submit" class="px-6 py-2 bg-blue-600 text-white text-sm font-bold rounded-xl hover:bg-blue-700 shadow-sm transition-colors">Cari</button>
<a href="{{ route('admin.sops.index') }}" class="px-4 py-2 bg-slate-100 text-slate-600 text-sm font-bold rounded-xl hover:bg-slate-200 transition-colors">Reset</a>
</div>
<div class="flex gap-2 ml-auto w-full md:w-auto mt-4 md:mt-0 pt-4 md:pt-0 border-t md:border-t-0 border-slate-100">
<button type="submit" name="export" value="excel" class="flex items-center px-4 py-2 bg-emerald-500 text-white text-sm font-bold rounded-xl hover:bg-emerald-600 shadow-sm transition-colors">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
Excel
</button>
<button type="submit" name="export" value="pdf" class="flex items-center px-4 py-2 bg-red-500 text-white text-sm font-bold rounded-xl hover:bg-red-600 shadow-sm transition-colors">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
PDF
</button>
</div>
</form>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="bg-slate-50 border-b border-slate-200 text-slate-500 uppercase text-xs tracking-wider">
<tr>
<th class="px-6 py-4 font-bold">Dept</th>
<th class="px-6 py-4 font-bold">Kategori</th>
<th class="px-6 py-4 font-bold">Nomor Dokumen</th>
<th class="px-6 py-4 font-bold">Judul SOP</th>
<th class="px-6 py-4 font-bold text-center">Versi</th>
<th class="px-6 py-4 font-bold">Tanggal Upload</th>
<th class="px-6 py-4 font-bold text-center">Aksi / File</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@forelse($sops as $sop)
@php
// Prioritas 1: Baca versi dari Nomor SOP
$versi = substr(trim($sop->nomor_sop), -2);
if (!is_numeric($versi)) {
// Prioritas 2: Baca versi dari Judul SOP jika Nomor SOP tidak ada angka versinya
$versi = substr(trim($sop->judul_sop), -2);
if (!is_numeric($versi)) $versi = '-';
}
@endphp
<tr class="hover:bg-slate-50 transition-colors">
<td class="px-6 py-4 font-bold text-indigo-700">{{ $sop->nama_departemen ?? '-' }}</td>
<td class="px-6 py-4 text-slate-600 text-xs uppercase font-bold">{{ $sop->nama_kategori ?? 'Umum' }}</td>
<td class="px-6 py-4 font-bold text-slate-800 whitespace-nowrap">{{ $sop->nomor_sop }}</td>
<td class="px-6 py-4 font-medium text-slate-700">{{ $sop->judul_sop ?? '-' }}</td>
<td class="px-6 py-4 text-center">
<span class="px-2 py-1 bg-slate-100 text-slate-600 rounded-md text-xs font-bold border border-slate-200">
v.{{ $versi }}
</span>
</td>
<td class="px-6 py-4 text-slate-500 whitespace-nowrap">
{{ \Carbon\Carbon::createFromTimestamp($sop->tanggal_dibuat)->format('d M Y') }}
</td>
<td class="px-6 py-4 text-center">
@if(!empty($sop->file_pdf))
<a href="{{ route('admin.sops.open', $sop->id) }}" target="_blank" class="inline-flex items-center text-xs font-bold text-white bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded-xl transition-colors shadow-sm">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
Buka File
</a>
@else
<span class="text-xs font-medium text-slate-400">Tidak tersedia</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-12 text-center">
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
<p class="text-slate-500 font-medium">SOP tidak ditemukan dengan filter tersebut.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="p-4 border-t border-slate-100 bg-white">
{{ $sops->appends(request()->query())->links() }}
</div>
</div>
</div>
@endsection
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>Master Dokumen - Tunggal Pharma</title>
<style>
body { font-family: 'Helvetica', 'Arial', sans-serif; font-size: 11px; color: #333; }
.header-title { text-align: center; font-size: 18px; font-weight: bold; margin-bottom: 5px; text-transform: uppercase; color: #1e3a8a; }
.divider { border-bottom: 2px solid #1e3a8a; margin-bottom: 15px; }
.info-table { width: 100%; margin-bottom: 15px; border: none; font-size: 11px; }
.info-table td { border: none; padding: 4px 0; }
table.data-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
table.data-table th, table.data-table td { border: 1px solid #94a3b8; padding: 6px 8px; text-align: left; vertical-align: top; }
table.data-table th { background-color: #f1f5f9; font-weight: bold; text-transform: uppercase; font-size: 10px; color: #475569; }
table.data-table tr:nth-child(even) { background-color: #f8fafc; }
.text-center { text-align: center; }
.badge { background: #e2e8f0; padding: 2px 5px; border-radius: 3px; font-size: 10px; font-weight: bold; }
</style>
</head>
<body>
<div class="header-title">Data Master Dokumen - Tunggal Pharma</div>
<div class="divider"></div>
<table class="info-table">
<tr>
<td width="15%"><b>Diunduh Oleh</b></td>
<td width="35%">: {{ $downloader }}</td>
<td width="15%"><b>Total Record</b></td>
<td width="35%">: <span style="background: #1e3a8a; color: white; padding: 2px 6px; border-radius: 3px;">{{ $totalRecords }} Dokumen</span></td>
</tr>
<tr>
<td><b>Waktu Unduh</b></td>
<td>: {{ $downloadTime }} WIB</td>
<td></td>
<td></td>
</tr>
</table>
<table class="data-table">
<thead>
<tr>
<th class="text-center" width="5%">No</th>
<th width="15%">Dept / Folder</th>
<th width="15%">Kategori</th>
<th width="20%">Nomor Dokumen</th>
<th width="30%">Judul Dokumen</th>
<th class="text-center" width="6%">Versi</th>
<th width="9%">Tgl Upload</th>
</tr>
</thead>
<tbody>
@foreach($dataExport as $index => $sop)
@php
$versi = substr(trim($sop->nomor_sop), -2);
if (!is_numeric($versi)) {
$versi = substr(trim($sop->judul_sop), -2);
if (!is_numeric($versi)) $versi = '-';
}
@endphp
<tr>
<td class="text-center">{{ $index + 1 }}</td>
<td><b>{{ $sop->nama_departemen ?? '-' }}</b></td>
<td>{{ $sop->nama_kategori ?? '-' }}</td>
<td>{{ $sop->nomor_sop }}</td>
<td>{{ $sop->judul_sop }}</td>
<td class="text-center"><span class="badge">v.{{ $versi }}</span></td>
<td>{{ \Carbon\Carbon::createFromTimestamp($sop->tanggal_dibuat)->format('d/m/Y') }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,127 @@
@extends('layouts.app')
@section('content')
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style>
/* Menyesuaikan tampilan Select2 agar menyatu dengan desain Tailwind */
.select2-container .select2-selection--single { height: 46px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 46px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 44px; right: 10px; }
.select2-container--default .select2-search--dropdown .select2-search__field { border-radius: 0.5rem; border-color: #cbd5e1; }
</style>
<div class="max-w-2xl mx-auto px-4 py-8">
<div class="flex items-center space-x-3 mb-6">
<a href="{{ route('admin.subjects.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
</a>
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Materi</h2>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<form action="{{ route('admin.subjects.update', $subject->id) }}" method="POST" class="p-6 sm:p-8 space-y-6">
@csrf
@method('PUT')
@php
// 1. Ambil nama materi saat ini dari database
$currentName = old('name', $subject->name) ?? '';
$isSopMatch = false;
$matchedSopValue = '';
$originalManualValue = $currentName;
// 2. Logika Smart Auto-Sync: Cocokkan dengan data Master SOP
if ($currentName) {
// Pastikan variabel $sops sudah dikirim dari SubjectController@edit
if(isset($sops)) {
foreach($sops as $sop) {
$fullname = $sop->nomor . ' - ' . $sop->judul;
// Jika persis sama ATAU nama lama mengandung unsur nomor SOP
if ($currentName === $fullname || strpos($currentName, $sop->nomor) !== false) {
$isSopMatch = true;
$matchedSopValue = $fullname; // Arahkan ke nama resmi
$originalManualValue = ''; // Kosongkan form manual
break;
}
}
}
}
// 3. Tentukan mode radio button saat halaman dimuat
$defaultType = ($currentName && !$isSopMatch) ? 'manual' : 'sop';
@endphp
<div x-data="{ inputType: '{{ $defaultType }}' }">
<label class="block text-sm font-bold text-slate-700 mb-2">Sumber Materi</label>
<div class="flex space-x-6 mb-4 p-3 bg-slate-50 rounded-xl border border-slate-200">
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="inputType" value="sop" class="w-4 h-4 text-blue-600 focus:ring-blue-500 border-slate-300">
<span class="ml-2 text-sm font-bold text-slate-700">Dari Master SOP</span>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="inputType" value="manual" class="w-4 h-4 text-blue-600 focus:ring-blue-500 border-slate-300">
<span class="ml-2 text-sm font-bold text-slate-700">Input Manual (Non-Plant)</span>
</label>
</div>
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Materi <span class="text-red-500">*</span></label>
<div x-show="inputType === 'sop'">
<select name="name" x-bind:disabled="inputType !== 'sop'" class="select2-sop w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-blue-500" required>
<option value="">-- Cari dan Pilih Dokumen SOP --</option>
@if(isset($sops))
@foreach($sops as $sop)
@php $fullname = $sop->nomor . ' - ' . $sop->judul; @endphp
<option value="{{ $fullname }}" {{ $matchedSopValue === $fullname ? 'selected' : '' }}>
{{ $fullname }}
</option>
@endforeach
@endif
</select>
</div>
<div x-show="inputType === 'manual'" x-cloak>
<input type="text" name="name" x-bind:disabled="inputType !== 'manual'" value="{{ $originalManualValue }}"
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all"
placeholder="Contoh: Modul Marketing Advance" required>
</div>
@error('name')
<p class="text-red-500 text-xs mt-2 font-semibold">{{ $message }}</p>
@enderror
</div>
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
<a href="{{ route('admin.subjects.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
<button type="submit" class="px-6 py-2.5 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-sm transition-colors">Update Materi</button>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function() {
// Inisialisasi Select2
$('.select2-sop').select2({
placeholder: "-- Cari dan Pilih Dokumen SOP --",
width: '100%'
});
// Memaksa Select2 merender ulang ukurannya ketika user berpindah dari mode Manual ke SOP
$('input[type=radio][value=sop]').change(function() {
setTimeout(function() {
$('.select2-sop').select2('destroy').select2({
placeholder: "-- Cari dan Pilih Dokumen SOP --",
width: '100%'
});
}, 50);
});
});
</script>
@endsection
@@ -0,0 +1,204 @@
@extends('layouts.app')
@section('content')
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style>
/* Styling Select2 untuk Tailwind */
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
.select2-container--default .select2-search--dropdown .select2-search__field { border-radius: 0.5rem; border-color: #cbd5e1; }
</style>
<div class="max-w-5xl mx-auto px-4 py-8" x-data="{
showModal: false,
editMode: false,
formAction: '',
subjectName: '',
inputType: 'sop',
// Data Master SOP untuk logika Sinkronisasi Cerdas
sopsData: [
@foreach($sops as $sop)
{ nomor: '{{ $sop->nomor }}', full: '{{ $sop->nomor }} - {{ addslashes($sop->judul) }}' },
@endforeach
],
// Fungsi saat menekan tombol Tambah
openCreate() {
this.editMode = false;
this.formAction = '{{ route('admin.subjects.store') }}';
this.subjectName = '';
this.inputType = 'sop';
this.showModal = true;
this.syncSelect2('');
},
// Fungsi saat menekan tombol Edit
openEdit(id, name, url) {
this.editMode = true;
this.formAction = url;
let isSop = false;
let targetSop = '';
// Pengecekan Smart Auto-Sync: Cocokkan nama dengan Master SOP
for (let sop of this.sopsData) {
if (name === sop.full || name.includes(sop.nomor)) {
isSop = true;
targetSop = sop.full;
break;
}
}
if (isSop) {
this.inputType = 'sop';
this.subjectName = targetSop;
this.syncSelect2(targetSop); // Arahkan dropdown ke nama SOP yang benar
} else {
this.inputType = 'manual';
this.subjectName = name; // Pertahankan nama manual
this.syncSelect2('');
}
this.showModal = true;
},
// Fungsi untuk memperbarui tampilan Dropdown Select2 secara otomatis
syncSelect2(val) {
setTimeout(() => {
$('#sopSelect').val(val).trigger('change');
}, 50);
}
}">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Kelola Materi (Subjects)</h2>
<button @click="openCreate()" class="px-4 py-2 bg-blue-600 text-white rounded-xl text-sm font-bold hover:bg-blue-700 shadow-sm transition-colors">
+ Tambah Materi
</button>
</div>
@if(session('success'))
<div class="mb-4 p-4 bg-emerald-50 text-emerald-700 rounded-xl border border-emerald-100 text-sm font-bold">{{ session('success') }}</div>
@endif
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 text-red-700 rounded-xl border border-red-100 text-sm font-bold">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<table class="w-full text-left text-sm text-slate-600">
<thead class="bg-slate-50 border-b border-slate-200 text-slate-500 uppercase text-xs tracking-wider">
<tr>
<th class="px-6 py-4 font-bold">ID</th>
<th class="px-6 py-4 font-bold">Nama Materi</th>
<th class="px-6 py-4 font-bold">Total Soal</th>
<th class="px-6 py-4 font-bold text-right">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@forelse($subjects as $subject)
<tr class="hover:bg-slate-50 transition-colors">
<td class="px-6 py-4 font-mono text-slate-400">#{{ $subject->id }}</td>
<td class="px-6 py-4 font-bold text-slate-800">{{ $subject->name }}</td>
<td class="px-6 py-4">
<span class="px-2.5 py-1 bg-indigo-50 text-indigo-700 rounded-md text-xs font-bold border border-indigo-100">
{{ $subject->questions_count ?? 0 }} Soal
</span>
</td>
<td class="px-6 py-4 flex justify-end space-x-2">
<a href="{{ route('admin.subjects.show', $subject->id) }}"
class="p-2 text-blue-500 hover:bg-blue-50 rounded-lg transition-colors" title="Lihat Detail Materi & Soal">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
</a>
<button @click="openEdit({{ $subject->id }}, '{{ addslashes($subject->name) }}', '{{ url('admin/subjects') }}/{{ $subject->id }}')"
class="p-2 text-amber-500 hover:bg-amber-50 rounded-lg transition-colors" title="Edit Nama Materi">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
</button>
<form action="{{ route('admin.subjects.destroy', $subject->id) }}" method="POST" onsubmit="return confirm('Apakah Anda yakin ingin menghapus materi ini?');">
@csrf @method('DELETE')
<button type="submit" class="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors" title="Hapus Materi">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-8 text-center text-slate-500">Belum ada data materi.</td>
</tr>
@endforelse
</tbody>
</table>
<div class="p-4 border-t border-slate-100">
{{ $subjects->links() }}
</div>
</div>
<div x-show="showModal" class="fixed inset-0 z-[100] overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
<div x-show="showModal" @click="showModal = false" class="fixed inset-0 transition-opacity bg-slate-900/50 backdrop-blur-sm"></div>
<div id="modalContent" x-show="showModal" class="relative inline-block w-full max-w-md p-6 overflow-visible text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl">
<h3 class="text-lg font-bold text-slate-800 mb-4" x-text="editMode ? 'Edit Materi' : 'Tambah Materi Baru'"></h3>
<form :action="formAction" method="POST">
@csrf
<template x-if="editMode"><input type="hidden" name="_method" value="PUT"></template>
<div>
<label class="block text-sm font-bold text-slate-700 mb-2">Sumber Materi</label>
<div class="flex space-x-6 mb-3 p-3 bg-slate-50 rounded-xl border border-slate-200">
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="inputType" value="sop" @change="syncSelect2(subjectName)" class="w-4 h-4 text-blue-600 focus:ring-blue-500 border-slate-300">
<span class="ml-2 text-sm font-bold text-slate-700">Dari Master SOP</span>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" x-model="inputType" value="manual" class="w-4 h-4 text-blue-600 focus:ring-blue-500 border-slate-300">
<span class="ml-2 text-sm font-bold text-slate-700">Input Manual</span>
</label>
</div>
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Materi <span class="text-red-500">*</span></label>
<div x-show="inputType === 'sop'">
<select id="sopSelect" name="name" x-bind:disabled="inputType !== 'sop'" required class="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-blue-500" style="width: 100%;">
<option value="">-- Cari dan Pilih Dokumen SOP --</option>
@foreach($sops as $sop)
<option value="{{ $sop->nomor }} - {{ $sop->judul }}">{{ $sop->nomor }} - {{ $sop->judul }}</option>
@endforeach
</select>
</div>
<div x-show="inputType === 'manual'" x-cloak>
<input type="text" name="name" x-model="subjectName" x-bind:disabled="inputType !== 'manual'" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all" placeholder="Contoh: Modul K3 Tingkat Lanjut">
</div>
</div>
<div class="flex justify-end space-x-3 mt-8 pt-4 border-t border-slate-100">
<button type="button" @click="showModal = false" class="px-4 py-2 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</button>
<button type="submit" class="px-5 py-2 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-sm transition-colors" x-text="editMode ? 'Simpan Perubahan' : 'Simpan Materi'"></button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Inisialisasi Select2 di dalam Modal
$('#sopSelect').select2({
placeholder: "-- Cari dan Pilih Dokumen SOP --",
width: '100%',
dropdownParent: $('#modalContent') // WAJIB: Agar kolom search select2 bisa diklik saat di dalam modal
});
});
</script>
@endsection
@@ -0,0 +1,76 @@
@extends('layouts.app')
@section('content')
<div class="max-w-6xl mx-auto px-4 py-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center space-x-3">
<a href="{{ route('admin.subjects.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
</a>
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Detail Materi</h2>
</div>
</div>
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-2xl shadow-md border border-blue-800 p-8 mb-8 flex flex-col md:flex-row items-center justify-between text-white">
<div>
<p class="text-blue-200 text-sm font-bold uppercase tracking-widest mb-2">Nama Subject / Materi</p>
<h3 class="text-3xl font-black">{{ $subject->name }}</h3>
</div>
<div class="text-right mt-4 md:mt-0 bg-white/10 px-6 py-4 rounded-xl border border-white/20 backdrop-blur-sm">
<p class="text-blue-100 text-xs font-bold uppercase tracking-wider mb-1">Total Bank Soal</p>
<span class="text-4xl font-black">{{ $subject->questions_count ?? $subject->questions->count() }} <span class="text-lg font-medium text-blue-200">Soal</span></span>
</div>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-5 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center">
<h4 class="font-bold text-slate-700">Daftar Pertanyaan di Materi Ini</h4>
<a href="{{ route('admin.exams.questions.create') }}" class="text-sm font-bold text-blue-600 hover:text-blue-800">
+ Tambah Soal Baru
</a>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="bg-white border-b border-slate-200 text-slate-500 text-xs uppercase tracking-wider">
<tr>
<th class="px-6 py-4 font-bold">Q.ID</th>
<th class="px-6 py-4 font-bold">Tipe & Level</th>
<th class="px-6 py-4 font-bold">Teks Pertanyaan</th>
<th class="px-6 py-4 font-bold text-right">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@forelse($subject->questions as $q)
<tr class="hover:bg-slate-50 transition-colors">
<td class="px-6 py-4 font-mono text-slate-400">#{{ $q->id }}</td>
<td class="px-6 py-4">
<span class="block text-xs font-bold text-indigo-600 uppercase mb-1">{{ str_replace('_', ' ', $q->question_type) }}</span>
<span class="text-[10px] font-bold px-2 py-0.5 rounded {{ $q->question_level == 'mudah' ? 'bg-emerald-100 text-emerald-700' : ($q->question_level == 'sedang' ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700') }}">
{{ strtoupper($q->question_level) }}
</span>
</td>
<td class="px-6 py-4">
<p class="line-clamp-2 text-slate-700 font-medium">{!! strip_tags($q->question_text) !!}</p>
</td>
<td class="px-6 py-4 text-right whitespace-nowrap">
<a href="{{ route('admin.exams.questions.show', $q->id) }}"
class="inline-block px-3 py-1.5 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 font-bold text-xs transition-colors" target="_blank">
Cek Soal &rarr;
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-12 text-center">
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg>
<p class="text-slate-500 font-medium">Belum ada bank soal yang menggunakan materi ini.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@endsection
@@ -24,6 +24,14 @@
@if(auth()->check() && auth()->user()->hasRole('admin'))
<p class="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 mt-6">Master Data</p>
<a href="{{ route('admin.subjects.index') }}"
class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.subjects.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
</svg>
<span class="text-sm font-medium">Materi (Subjects)</span>
</a>
<a href="{{ route('admin.departments.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.departments.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
<span class="text-sm font-medium">Departemen</span>
+5
View File
@@ -126,6 +126,11 @@ Route::middleware(['auth'])->group(function () {
Route::resource('departments', DepartmentController::class);
Route::resource('positions', PositionController::class);
Route::resource('employees', EmployeeController::class);
//Subject
Route::resource('subjects', App\Http\Controllers\Admin\SubjectController::class);
Route::get('/sops/open/{id}', [DashboardController::class, 'openSopFile'])->name('sops.open');
Route::get('/sops', [DashboardController::class, 'sopIndex'])->name('sops.index');
});