@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

Identitas Dokumen

Disusun mengikuti blok awal template kampus: identitas mata kuliah, kode dokumen, bobot SKS, semester, dan otorisasi penyusunan.

Kode Dokumen
Otomatis: RPS/Kode Prodi/Kode Fakultas/Bulan Romawi/Tahun/Nomor Urut.
Tanggal Penyusunan
Otomatis: Gasal/Ganjil = 31 Agustus tahun berjalan, Genap = 28 Februari tahun berjalan.
Status
Universitas Fakultas
Program Studi
Mata Kuliah (MK) Kode MK
Rumpun MK
BOBOT (SKS)
Semester
SKS Teori (T) SKS Praktik (P) Dosen Pengampu
Otomatis mengikuti dosen pada Pembagian Mata Kuliah untuk periode aktif.
Mata Kuliah Syarat
Otomatis mengikuti Mata Kuliah Syarat pada Informasi Kurikulum Prodi.

Otorisasi

Meniru blok otorisasi template RPS kampus: Pengembang RPS, Koordinator RMK, dan Ketua PRODI.

@foreach ($approvals as $index => $item) @endforeach
Peran Nama Penandatangan NIDN / Identitas Tanggal No Aksi
{{ $index + 1 }}

Blok Tanda Tangan PDF

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)
Tanda tangan kiri
Jika nama dipilih dari daftar, tanda tangan tersimpan akan otomatis dipakai. Upload manual tetap bisa digunakan bila ingin mengganti.
Upload Tanda Tangan Kanan (PNG)
Tanda tangan kanan
Jika Dosen Pengampu memiliki TTD PNG pada SDM / Daftar Dosen, gambar tanda tangan kanan otomatis dipakai. Upload manual tetap bisa digunakan bila ingin mengganti.

Import Excel Sub-CPMK, Pustaka, dan Rencana Pembelajaran Mingguan

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.

Download Template Excel

Capaian Pembelajaran

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)

{{ $group['title'] }}

@foreach ($group['rows'] as $index => $item) @endforeach
No Kode Uraian Aksi
{{ $index + 1 }} @if ($groupName === 'sub_cpmk_items') @else @endif
@endforeach

Deskripsi Singkat MK dan Bahan Kajian

Deskripsi Singkat MK
Bahan Kajian / Materi Pembelajaran

Pustaka

@foreach ([ 'main_references' => ['title' => 'Pustaka Utama', 'rows' => $mainReferences], 'supporting_references' => ['title' => 'Pustaka Pendukung', 'rows' => $supportingReferences], ] as $groupName => $group)

{{ $group['title'] }}

@foreach ($group['rows'] as $index => $item) @endforeach
No Referensi Aksi
{{ $index + 1 }}
@endforeach

Rencana Pembelajaran Mingguan

Tab dipertahankan agar pengisian lebih leluasa, tetapi isi setiap tab sekarang disusun dalam bentuk tabel kerja yang lebih dekat ke struktur template kampus.

@foreach ($meetings as $index => $meeting) @php $sessionType = $meeting['session_type'] ?? 'regular'; $buttonLabel = $sessionType === 'uts' ? 'Pekan 8 / UTS' : ($sessionType === 'uas' ? 'Pekan 16 / UAS' : 'Pekan '.$meeting['week_number']); @endphp @endforeach
@foreach ($meetings as $index => $meeting) @php $sessionType = $meeting['session_type'] ?? 'regular'; $kicker = $sessionType === 'uts' ? 'Ujian Tengah Semester' : ($sessionType === 'uas' ? 'Ujian Akhir Semester' : 'Pertemuan Reguler'); @endphp
{{ $kicker }}
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]
@endforeach
Batal