@php $cplItems = old('cpl_items', $formData['cplItems']); $cpmkItems = old('cpmk_items', $formData['cpmkItems']); $subCpmkItems = old('sub_cpmk_items', $formData['subCpmkItems']); $mainReferences = old('main_references', $formData['mainReferences']); $supportingReferences = old('supporting_references', $formData['supportingReferences']); $approvals = old('approvals', $formData['approvals']); $meetings = old('meetings', $formData['meetings']); $curriculumCourseOptions = $formData['curriculumCourseOptions'] ?? []; $curriculumCourseProfiles = $formData['curriculumCourseProfiles'] ?? []; $activeSemesterCourseAssignments = $formData['activeSemesterCourseAssignments'] ?? []; $coursePrerequisiteMap = $formData['coursePrerequisiteMap'] ?? []; $assessmentLearningOptions = $formData['assessmentLearningOptions'] ?? []; $signatureOptions = $formData['signatureOptions'] ?? []; $defaultLeftSignatureOption = $formData['defaultLeftSignatureOption'] ?? []; $authorizationAutoFill = $formData['authorizationAutoFill'] ?? []; $documentCodeSequence = str_pad((string) ((int) preg_replace('/\D/', '', (string) optional($rps)->document_code) ?: 1), 4, '0', STR_PAD_LEFT); if (filled(optional($rps)->document_code) && preg_match('/\/(\d+)$/', (string) $rps->document_code, $documentCodeMatch)) { $documentCodeSequence = str_pad($documentCodeMatch[1], 4, '0', STR_PAD_LEFT); } $manualRpsCleanOptionText = function (mixed $value): string { if (is_array($value)) { $value = implode(' ', array_filter(array_map(fn ($item) => is_scalar($item) ? (string) $item : '', $value))); } return trim(preg_replace('/\s+/u', ' ', (string) $value) ?? ''); }; $manualRpsCleanPointText = function (mixed $value) use ($manualRpsCleanOptionText): string { if (is_array($value)) { $value = implode(" ", array_filter(array_map(fn ($item) => is_scalar($item) ? (string) $item : '', $value))); } $text = str_replace([" ", " "], " ", (string) $value); $text = preg_replace('/\s*;\s*/u', " ", $text) ?? $text; $text = preg_replace('/\s*[•●▪]\s*/u', " ", $text) ?? $text; $lines = collect(explode(" ", $text)) ->map(fn ($line) => $manualRpsCleanOptionText($line)) ->filter() ->unique() ->values() ->all(); return implode(', ', $lines); }; $manualRpsReadSettingRows = function (string $key): array { try { if (! \Illuminate\Support\Facades\Schema::hasColumn('app_settings', $key)) { return []; } $settings = \App\Models\AppSetting::current(); $value = $settings->getAttribute($key); if (is_array($value)) { return $value; } if (is_string($value) && trim($value) !== '') { $decoded = json_decode($value, true); return is_array($decoded) ? $decoded : []; } } catch (\Throwable $exception) { return []; } return []; }; $manualRpsBuildSubCpmkOptionText = function (mixed $code, mixed $description) use ($manualRpsCleanOptionText): string { $cleanCode = $manualRpsCleanOptionText($code); $cleanDescription = $manualRpsCleanOptionText($description); if ($cleanCode !== '' && $cleanDescription !== '') { return $cleanCode.' - '.$cleanDescription; } return $cleanCode !== '' ? $cleanCode : $cleanDescription; }; $weeklySubCpmkOptions = collect($subCpmkItems) ->map(fn ($item) => $manualRpsBuildSubCpmkOptionText($item['code'] ?? '', $item['description'] ?? '')) ->filter() ->unique() ->values() ->all(); $manualRpsStudentAssignmentRows = $manualRpsReadSettingRows('student_assignment_models'); if ($manualRpsStudentAssignmentRows === [] && class_exists(\App\Services\StudentAssignmentModelDefaults::class)) { $manualRpsStudentAssignmentRows = \App\Services\StudentAssignmentModelDefaults::rows(); } $manualRpsLearningMediaRows = $manualRpsReadSettingRows('learning_media_models'); if ($manualRpsLearningMediaRows === [] && class_exists(\App\Services\LearningMediaDefaults::class)) { $manualRpsLearningMediaRows = \App\Services\LearningMediaDefaults::rows(); } $manualRpsLectureDurationRows = $manualRpsReadSettingRows('lecture_duration_models'); if ($manualRpsLectureDurationRows === [] && class_exists(\App\Services\LectureDurationDefaults::class)) { $manualRpsLectureDurationRows = \App\Services\LectureDurationDefaults::rows(); } $manualRpsExtractOptionPoints = function (mixed $value) use ($manualRpsCleanOptionText): array { if (is_array($value)) { $rawItems = []; array_walk_recursive($value, function ($item) use (&$rawItems) { if (is_scalar($item)) { $rawItems[] = (string) $item; } }); $text = implode("\n", $rawItems); } else { $text = (string) $value; } $text = str_replace(["\r\n", "\r"], "\n", $text); $text = preg_replace('/\s*;\s*/u', "\n", $text) ?? $text; $text = preg_replace('/\s*[•●▪]\s*/u', "\n", $text) ?? $text; return collect(explode("\n", $text)) ->map(fn ($line) => $manualRpsCleanOptionText($line)) ->filter() ->unique() ->values() ->all(); }; $manualRpsBuildAssignmentOptionsForRow = function (array $row) use ($manualRpsCleanOptionText, $manualRpsExtractOptionPoints): array { $name = $manualRpsCleanOptionText($row['name'] ?? ''); $description = $manualRpsCleanOptionText($row['description'] ?? ''); $examples = $manualRpsExtractOptionPoints($row['examples'] ?? ($row['items'] ?? '')); $notes = $manualRpsCleanOptionText($row['notes'] ?? ''); if ($examples === []) { $examples = ['']; } $options = []; foreach ($examples as $example) { $parts = []; if ($name !== '') { $parts[] = $name; } if ($description !== '') { $parts[] = 'Deskripsi: '.$description; } if ($example !== '') { $parts[] = 'Contoh/Bentuk/Platform: '.$example; } if ($notes !== '') { $parts[] = 'Tujuan/Catatan: '.$notes; } $option = trim(implode(' - ', $parts)); if ($option !== '') { $options[] = $option; } } return array_values(array_unique($options)); }; $manualRpsBuildMediaOptionsForRow = function (array $row) use ($manualRpsCleanOptionText, $manualRpsExtractOptionPoints): array { $name = $manualRpsCleanOptionText($row['name'] ?? ''); $description = $manualRpsCleanOptionText($row['description'] ?? ''); $items = $manualRpsExtractOptionPoints($row['items'] ?? ($row['examples'] ?? '')); $notes = $manualRpsCleanOptionText($row['notes'] ?? ''); if ($items === []) { $items = ['']; } $options = []; foreach ($items as $item) { $parts = []; if ($name !== '') { $parts[] = $name; } if ($description !== '') { $parts[] = 'Deskripsi: '.$description; } if ($item !== '') { $parts[] = 'Contoh/Bentuk/Platform: '.$item; } if ($notes !== '') { $parts[] = 'Catatan/Tujuan: '.$notes; } $option = trim(implode(' - ', $parts)); if ($option !== '') { $options[] = $option; } } return array_values(array_unique($options)); }; $manualRpsStudentAssignmentOptions = [ 'luring' => collect($manualRpsStudentAssignmentRows)->filter(fn ($row) => ($row['mode'] ?? 'luring') === 'luring')->flatMap(fn ($row) => $manualRpsBuildAssignmentOptionsForRow(is_array($row) ? $row : []))->filter()->unique()->values()->all(), 'daring' => collect($manualRpsStudentAssignmentRows)->filter(fn ($row) => ($row['mode'] ?? 'luring') === 'daring')->flatMap(fn ($row) => $manualRpsBuildAssignmentOptionsForRow(is_array($row) ? $row : []))->filter()->unique()->values()->all(), ]; $manualRpsLearningMediaOptions = [ 'luring' => collect($manualRpsLearningMediaRows)->filter(fn ($row) => ($row['mode'] ?? 'luring') === 'luring')->flatMap(fn ($row) => $manualRpsBuildMediaOptionsForRow(is_array($row) ? $row : []))->filter()->unique()->values()->all(), 'daring' => collect($manualRpsLearningMediaRows)->filter(fn ($row) => ($row['mode'] ?? 'luring') === 'daring')->flatMap(fn ($row) => $manualRpsBuildMediaOptionsForRow(is_array($row) ? $row : []))->filter()->unique()->values()->all(), ]; $manualRpsBuildLectureDurationOptionsForRow = function (array $row) use ($manualRpsCleanOptionText): array { $context = $manualRpsCleanOptionText($row['context'] ?? ''); $syncDuration = $manualRpsCleanOptionText($row['sync_duration'] ?? ''); $asyncDuration = $manualRpsCleanOptionText($row['async_duration'] ?? ''); $notes = $manualRpsCleanOptionText($row['notes'] ?? ''); $labelParts = []; if ($context !== '') { $labelParts[] = $context; } if ($syncDuration !== '') { $labelParts[] = $syncDuration; } if ($asyncDuration !== '') { $labelParts[] = $asyncDuration; } if ($notes !== '') { $labelParts[] = $notes; } $valueParts = array_filter([$syncDuration, $asyncDuration], fn ($item) => $item !== ''); $value = trim(implode(' - ', $valueParts)); $label = trim(implode(' - ', $labelParts)); if ($value === '') { return []; } return [[ 'label' => $label !== '' ? $label : $value, 'value' => $value, ]]; }; $manualRpsLectureDurationOptions = collect($manualRpsLectureDurationRows) ->flatMap(fn ($row) => $manualRpsBuildLectureDurationOptionsForRow(is_array($row) ? $row : [])) ->filter(fn ($option) => is_array($option) && filled($option['value'] ?? '')) ->unique('value') ->values() ->all(); @endphp
Disusun mengikuti blok awal template kampus: identitas mata kuliah, kode dokumen, bobot SKS, semester, dan otorisasi penyusunan.
| Kode Dokumen | Tanggal Penyusunan | Status | |||
|---|---|---|---|---|---|
| Universitas | Fakultas | ||||
| Program Studi | |||||
| Mata Kuliah (MK) | Kode MK |
|
|||
| Rumpun MK |
|
BOBOT (SKS) |
|
Semester |
|
| SKS Teori (T) | SKS Praktik (P) | Dosen Pengampu | |||
| Mata Kuliah Syarat | |||||
Meniru blok otorisasi template RPS kampus: Pengembang RPS, Koordinator RMK, dan Ketua PRODI.
| Peran | Nama Penandatangan | NIDN / Identitas | Tanggal | No | Aksi |
|---|---|---|---|---|---|
| {{ $index + 1 }} |
Bagian ini dipakai untuk mengisi blok tanda tangan kiri dan kanan pada halaman terakhir PDF. File tanda tangan harus PNG agar hasil export tetap rapi.
| Nama Penandatangan Kiri | @php $selectedLeftSigner = old('acknowledged_by_name', $rps->acknowledged_by_name); if (blank($selectedLeftSigner) && filled($defaultLeftSignatureOption['name'] ?? null)) { $selectedLeftSigner = $defaultLeftSignatureOption['name']; } $selectedLeftSignerOption = collect($signatureOptions)->first(fn ($option) => ($option['name'] ?? '') === $selectedLeftSigner); $leftSignerNidn = old('acknowledged_by_nidn', $rps->acknowledged_by_nidn); if (blank($leftSignerNidn) && filled($selectedLeftSignerOption['identifier_value'] ?? null)) { $leftSignerNidn = $selectedLeftSignerOption['identifier_value']; } $leftSignerSignaturePath = old('acknowledged_by_signature_path', $rps->acknowledged_by_signature_path); if (blank($leftSignerSignaturePath) && filled($selectedLeftSignerOption['signature_path'] ?? null)) { $leftSignerSignaturePath = $selectedLeftSignerOption['signature_path']; } $rightSignerName = old('lecturer_name', $rps->lecturer_name); $rightSignerNidn = old('lecturer_nidn', $rps->lecturer_nidn); $rightSignerSignaturePath = old('lecturer_signature_path', $rps->lecturer_signature_path); if (blank($rightSignerNidn) || blank($rightSignerSignaturePath)) { $facultyIdentityMapForSignature = $authorizationAutoFill['faculty_members'] ?? []; foreach (preg_split('/[;\n]+/', (string) $rightSignerName) ?: [] as $rightSignerItem) { $rightSignerKey = mb_strtolower(trim($rightSignerItem)); if ($rightSignerKey === '' || empty($facultyIdentityMapForSignature[$rightSignerKey])) { continue; } $rightSignerData = $facultyIdentityMapForSignature[$rightSignerKey]; if (blank($rightSignerNidn) && filled($rightSignerData['identifier'] ?? null)) { $rightSignerNidn = $rightSignerData['identifier']; } if (blank($rightSignerSignaturePath) && filled($rightSignerData['signature_path'] ?? null)) { $rightSignerSignaturePath = $rightSignerData['signature_path']; } if (filled($rightSignerNidn) && filled($rightSignerSignaturePath)) { break; } } } @endphp | Nama Penandatangan Kanan |
Nama ini mengikuti isian Dosen Pengampu pada tabel identitas dokumen.
|
|---|---|---|---|
| NIDN Kiri | NIDN Kanan | ||
| Upload Tanda Tangan Kiri (PNG) |
Jika nama dipilih dari daftar, tanda tangan tersimpan akan otomatis dipakai. Upload manual tetap bisa digunakan bila ingin mengganti.
|
Upload Tanda Tangan Kanan (PNG) |
Jika Dosen Pengampu memiliki TTD PNG pada SDM / Daftar Dosen, gambar tanda tangan kanan otomatis dipakai. Upload manual tetap bisa digunakan bila ingin mengganti.
|
Gunakan template Excel untuk mengisi bagian Sub-CPMK dan Rencana Pembelajaran Mingguan. Jika tersedia kolom Pustaka Utama/Pendukung pada file, data pustaka juga akan diisi otomatis. Data akan diterapkan ke form terlebih dahulu dan tetap perlu disimpan.
Urutannya diselaraskan dengan template: `CPL-PRODI yang dibebankan pada MK`, `CPMK`, lalu `Sub-CPMK`.
@foreach ([ 'cpl_items' => ['title' => 'CPL-PRODI yang dibebankan pada MK', 'rows' => $cplItems], 'cpmk_items' => ['title' => 'Capaian Pembelajaran Mata Kuliah (CPMK)', 'rows' => $cpmkItems], 'sub_cpmk_items' => ['title' => 'Sub-CPMK', 'rows' => $subCpmkItems], ] as $groupName => $group)| No | Kode | Uraian | Aksi |
|---|---|---|---|
| {{ $index + 1 }} | @if ($groupName === 'sub_cpmk_items') @else @endif |
| Deskripsi Singkat MK |
|
|---|---|
| Bahan Kajian / Materi Pembelajaran |
|
| No | Referensi | Aksi |
|---|---|---|
| {{ $index + 1 }} |
Tab dipertahankan agar pengisian lebih leluasa, tetapi isi setiap tab sekarang disusun dalam bentuk tabel kerja yang lebih dekat ke struktur template kampus.
| Pekan Ke- | Jenis Sesi | Bobot Penilaian (%) | |||
|---|---|---|---|---|---|
| Sub-CPMK |
Pilihan diambil dari bagian Sub-CPMK di atas dengan format Kode - Uraian. |
||||
| Indikator | |||||
| Kriteria Penilaian |
|
Teknik Penilaian |
|
||
| Pembelajaran Luring (offline) |
|
Pembelajaran Daring (online) |
|
||
| Materi Pembelajaran [Pustaka] | |||||