ubahan sertifikat dan admin projek
This commit is contained in:
parent
e0aefa4eb6
commit
8ff23608d5
302
app/Http/Controllers/Admin/ProjectController.php
Normal file
302
app/Http/Controllers/Admin/ProjectController.php
Normal file
@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Lesson;
|
||||
use App\Models\ProjectSubmission;
|
||||
use App\Models\Watch_history;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required',
|
||||
'section' => 'required|numeric',
|
||||
'course_id' => 'required'
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$data['title'] = $request->title;
|
||||
$data['course_id'] = $request->course_id;
|
||||
$data['section_id'] = $request->section;
|
||||
$data['description'] = $request->description;
|
||||
$data['attachment'] = $request->attachment;
|
||||
$data['summary'] = $request->summary;
|
||||
|
||||
|
||||
Lesson::where('id', $id)->update($data);
|
||||
|
||||
Session::flash('success', get_phrase('Project has been updated.'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$maxSort = Lesson::where('section_id', $request->section)
|
||||
->max('sort');
|
||||
|
||||
$data['user_id'] = auth()->user()->id;
|
||||
$data['sort'] = $maxSort + 1;
|
||||
$data['title'] = $request->title;
|
||||
$data['course_id'] = $request->course_id;
|
||||
$data['section_id'] = $request->section;
|
||||
$data['attachment'] = $request->attachment;
|
||||
$data['description'] = $request->description;
|
||||
$data['summary'] = $request->summary;
|
||||
$data['lesson_type'] = 'project';
|
||||
$data['status'] = 1;
|
||||
|
||||
Lesson::insert($data);
|
||||
|
||||
Session::flash('success', get_phrase('Project has been created.'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function getSubmissions($id)
|
||||
{
|
||||
$participants = ProjectSubmission::join('users', 'project_submissions.user_id', '=', 'users.id')
|
||||
->where('project_submissions.lesson_id', $id)
|
||||
->where('project_submissions.status', 0)
|
||||
->select('users.name', 'users.id')
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
return view('admin.project.grading.index', ['participants' => $participants, 'lesson_id' => $id]);
|
||||
}
|
||||
|
||||
public function getPreview(Request $request)
|
||||
{
|
||||
// Validasi input
|
||||
$request->validate([
|
||||
'lesson_id' => 'required|integer',
|
||||
'user_id' => 'required|integer'
|
||||
]);
|
||||
|
||||
// Cari submission terbaru
|
||||
$submission = ProjectSubmission::where('lesson_id', $request->lesson_id)
|
||||
->where('user_id', $request->user_id)
|
||||
->latest()
|
||||
->first();
|
||||
// dd($submission); // <-- INI HARUS DIHAPUS ATAU DI-KOMEN
|
||||
|
||||
// Tentukan label komentar
|
||||
$commentLabel = (!empty($submission) && !empty($submission->comment))
|
||||
? "Revisi Sebelumnya"
|
||||
: "Masukkan Revisi Anda";
|
||||
|
||||
// Return view preview
|
||||
return view('admin.project.grading.preview', compact('submission', 'commentLabel'));
|
||||
}
|
||||
|
||||
public function getParticipantSubmission(Request $request)
|
||||
{
|
||||
$submissions = ProjectSubmission::where('lesson_id', $request->quizId)
|
||||
->where('user_id', $request->participant)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
$html = '<option value="">' . get_phrase('Select a submission') . '</option>';
|
||||
|
||||
if($submissions->count() > 0){
|
||||
foreach ($submissions as $submission) {
|
||||
$date = date('d M Y, H:i', strtotime($submission->created_at));
|
||||
|
||||
$statusLabel = '';
|
||||
if($submission->status == 1) $statusLabel = ' (Lulus)';
|
||||
elseif($submission->status == 2) $statusLabel = ' (Revisi)';
|
||||
else $statusLabel = ' (Pending)';
|
||||
|
||||
$html .= '<option value="' . $submission->id . '">' . $date . $statusLabel . '</option>';
|
||||
}
|
||||
} else {
|
||||
$html .= '<option value="" disabled>' . get_phrase('No submission found') . '</option>';
|
||||
}
|
||||
|
||||
return response()->json($html);
|
||||
}
|
||||
|
||||
public function updateSubmission(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'status' => 'required|in:1,2',
|
||||
'comment' => 'required_if:status,2', // Wajib isi jika status 2 (Revisi)
|
||||
]);
|
||||
|
||||
// Menggunakan Model findOrFail
|
||||
$submission = ProjectSubmission::findOrFail($id);
|
||||
|
||||
$submission->status = $request->status;
|
||||
$submission->reviewed_by = auth()->id();
|
||||
$submission->reviewed_at = now();
|
||||
|
||||
if ($request->status == 1) {
|
||||
// Jika Diterima (1), Comment jadi NULL (sesuai request)
|
||||
$submission->comment = null;
|
||||
} else {
|
||||
// Jika Perlu Perbaikan (2), Simpan Comment
|
||||
$submission->comment = $request->comment;
|
||||
}
|
||||
|
||||
$submission->save();
|
||||
|
||||
// **LOGIKA WATCH HISTORY - DIPERBAIKI berdasarkan referensi**
|
||||
|
||||
$lesson = Lesson::findOrFail($submission->lesson_id);
|
||||
|
||||
$student_id = $submission->user_id;
|
||||
$course_id = $lesson->course_id;
|
||||
$lesson_id = $submission->lesson_id;
|
||||
|
||||
// Ambil semua lesson ID untuk course ini
|
||||
$total_lesson = Lesson::where('course_id', $course_id)
|
||||
->pluck('id')
|
||||
->map(function($id) {
|
||||
return (int)$id;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
// Cari watch_history untuk student ini
|
||||
$watch_history = Watch_history::where('course_id', $course_id)
|
||||
->where('student_id', $student_id)
|
||||
->first();
|
||||
|
||||
// **LOGIKA UTAMA berdasarkan referensi set_watch_history**
|
||||
if ($watch_history) {
|
||||
// Parse data lama dengan handling yang robust
|
||||
$completed_lessons = [];
|
||||
|
||||
if (!empty($watch_history->completed_lesson)) {
|
||||
$decoded = json_decode($watch_history->completed_lesson, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||
// Konversi semua nilai ke integer
|
||||
$completed_lessons = array_map('intval', $decoded);
|
||||
} else {
|
||||
// Fallback jika format tidak valid
|
||||
$completed_lesson_data = trim($watch_history->completed_lesson, '[]"\' ');
|
||||
if (!empty($completed_lesson_data)) {
|
||||
$completed_lessons = array_map('intval', explode(',', $completed_lesson_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hapus duplikat dan sortir
|
||||
$completed_lessons = array_unique($completed_lessons);
|
||||
sort($completed_lessons);
|
||||
|
||||
// **HANYA tambahkan lesson jika project DITERIMA (status 1)**
|
||||
if ($request->status == 1 && !in_array($lesson_id, $completed_lessons)) {
|
||||
$completed_lessons[] = $lesson_id;
|
||||
sort($completed_lessons);
|
||||
}
|
||||
|
||||
// Hitung completion dengan benar (seperti referensi)
|
||||
$completed_in_course = array_intersect($completed_lessons, $total_lesson);
|
||||
$is_completed = count($total_lesson) > 0 &&
|
||||
count($completed_in_course) >= count($total_lesson);
|
||||
|
||||
$data = [
|
||||
'course_id' => $course_id,
|
||||
'student_id' => $student_id,
|
||||
'watching_lesson_id' => $lesson_id,
|
||||
'completed_lesson' => json_encode($completed_lessons, JSON_NUMERIC_CHECK), // Gunakan JSON_NUMERIC_CHECK
|
||||
'completed_date' => $is_completed ? date('Y-m-d H:i:s') : null, // Format MySQL datetime
|
||||
];
|
||||
|
||||
// **FIX: Update dengan student_id yang benar (bukan auth user)**
|
||||
Watch_history::where('course_id', $course_id)
|
||||
->where('student_id', $student_id)
|
||||
->update($data);
|
||||
|
||||
// Log untuk debugging
|
||||
\Log::info('Project submission watch history updated:', [
|
||||
'project_submission_id' => $id,
|
||||
'student_id' => $student_id,
|
||||
'course_id' => $course_id,
|
||||
'lesson_id' => $lesson_id,
|
||||
'completed_lessons' => $completed_lessons,
|
||||
'is_completed' => $is_completed,
|
||||
'total_lessons_in_course' => count($total_lesson)
|
||||
]);
|
||||
|
||||
} else {
|
||||
// **Jika tidak ada watch_history, buat baru**
|
||||
// Hanya buat jika project DITERIMA (status 1)
|
||||
if ($request->status == 1) {
|
||||
$completed_lessons = [$lesson_id];
|
||||
|
||||
// Hitung completion untuk data baru
|
||||
$is_completed = count($total_lesson) == 1 && in_array($lesson_id, $total_lesson);
|
||||
|
||||
$data = [
|
||||
'course_id' => $course_id,
|
||||
'student_id' => $student_id,
|
||||
'watching_lesson_id' => $lesson_id,
|
||||
'completed_lesson' => json_encode($completed_lessons, JSON_NUMERIC_CHECK),
|
||||
'completed_date' => $is_completed ? date('Y-m-d H:i:s') : null,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
Watch_history::create($data);
|
||||
|
||||
\Log::info('New watch history created from project submission:', [
|
||||
'project_submission_id' => $id,
|
||||
'student_id' => $student_id,
|
||||
'course_id' => $course_id,
|
||||
'lesson_id' => $lesson_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// **LOGIKA SERTIFIKAT - dengan progress yang benar**
|
||||
$completed_lessons = $completed_lessons ?? [];
|
||||
$completed_in_course = array_intersect($completed_lessons, $total_lesson);
|
||||
$completed_count = count($completed_in_course);
|
||||
$total_count = count($total_lesson);
|
||||
|
||||
$progress_percentage = 0;
|
||||
if ($total_count > 0) {
|
||||
$progress_percentage = ($completed_count / $total_count) * 100;
|
||||
}
|
||||
|
||||
// Jika progress 100% dan status project diterima, buat sertifikat
|
||||
if ($progress_percentage >= 100 && $request->status == 1) {
|
||||
$certificateExists = Certificate::where('user_id', $student_id)
|
||||
->where('course_id', $course_id)
|
||||
->exists();
|
||||
|
||||
if (!$certificateExists) {
|
||||
// Cek jika method generateIdentifier ada
|
||||
if (method_exists($this, 'generateIdentifier')) {
|
||||
$identifier = $this->generateIdentifier(12);
|
||||
} else {
|
||||
// Fallback ke random string
|
||||
$identifier = \Illuminate\Support\Str::random(12);
|
||||
}
|
||||
|
||||
Certificate::create([
|
||||
'user_id' => $student_id,
|
||||
'course_id' => $course_id,
|
||||
'identifier' => $identifier,
|
||||
]);
|
||||
|
||||
\Log::info('Certificate created after project submission:', [
|
||||
'student_id' => $student_id,
|
||||
'course_id' => $course_id,
|
||||
'project_submission_id' => $id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', get_phrase('Project graded successfully'));
|
||||
}
|
||||
}
|
||||
@ -56,6 +56,10 @@ class QuizController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$maxSort = Lesson::where('section_id', $request->section)
|
||||
->max('sort');
|
||||
$data['user_id'] = auth()->user()->id;
|
||||
$data['sort'] = $maxSort + 1;
|
||||
$data['title'] = $request->title;
|
||||
$data['course_id'] = $request->course_id;
|
||||
$data['section_id'] = $request->section;
|
||||
|
||||
@ -657,11 +657,41 @@ class SettingController extends Controller
|
||||
return redirect(route('admin.certificate.settings'))->with('success', get_phrase('Certificate template has been updated'));
|
||||
}
|
||||
|
||||
function certificate_update_template_details(Request $request)
|
||||
{
|
||||
$request->validate(['certificate_template_details' => 'required|image']);
|
||||
|
||||
$row = Setting::where('type', 'certificate_template_details');
|
||||
|
||||
if ($row->count() > 0) {
|
||||
remove_file(get_settings('certificate_template_details'));
|
||||
$path = FileUploader::upload($request->certificate_template_details, 'uploads/certificate-template-details', 1000);
|
||||
Setting::where('type', 'certificate_template_details')->update(['description' => $path]);
|
||||
} else {
|
||||
$path = FileUploader::upload($request->certificate_template_details, 'uploads/certificate-template-details', 1000);
|
||||
Setting::insert(['type' => 'certificate_template_details', 'description' => $path]);
|
||||
}
|
||||
|
||||
$certificate_builder_content = get_settings('certificate_builder_content');
|
||||
if ($certificate_builder_content) {
|
||||
// Use regular expression to replace the image source
|
||||
$modifiedHtml = preg_replace('/(<img[^>]+src=")([^"]+)(")/', '$1' . get_image($path) . '$3', $certificate_builder_content);
|
||||
Setting::where('type', 'certificate_builder_content')->update(['description' => $modifiedHtml]);
|
||||
}
|
||||
|
||||
return redirect(route('admin.certificate.settings'))->with('success', get_phrase('Certificate template has been updated'));
|
||||
}
|
||||
|
||||
function certificate_builder()
|
||||
{
|
||||
return view('admin.certificate.builder');
|
||||
}
|
||||
|
||||
function certificate_builder_details()
|
||||
{
|
||||
return view('admin.certificate.builder_details');
|
||||
}
|
||||
|
||||
function certificate_builder_update(Request $request)
|
||||
{
|
||||
$request->validate(['certificate_builder_content' => 'required']);
|
||||
@ -677,6 +707,20 @@ class SettingController extends Controller
|
||||
return route('admin.certificate.settings');
|
||||
}
|
||||
|
||||
function certificate_builder_details_update(Request $request)
|
||||
{
|
||||
$request->validate(['certificate_builder_content_details' => 'required']);
|
||||
|
||||
$row = Setting::where('type', 'certificate_builder_content_details');
|
||||
if ($row->count() > 0) {
|
||||
Setting::where('type', 'certificate_builder_content_details')->update(['description' => $request->certificate_builder_content_details]);
|
||||
} else {
|
||||
Setting::insert(['type' => 'certificate_builder_content_details', 'description' => $request->certificate_builder_content_details]);
|
||||
}
|
||||
Session::flash('success', get_phrase('Certificate builder template has been updated'));
|
||||
return route('admin.certificate.settings');
|
||||
}
|
||||
|
||||
//User Review Add
|
||||
public function user_review_add()
|
||||
{
|
||||
|
||||
@ -14,39 +14,32 @@ class ProjectController extends Controller
|
||||
{
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'status' => 'required|in:1,2',
|
||||
'comment' => 'required_if:status,2',
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required',
|
||||
'section' => 'required|numeric',
|
||||
'course_id' => 'required'
|
||||
]);
|
||||
|
||||
$submission = ProjectSubmission::findOrFail($id);
|
||||
|
||||
$submission->status = $request->status;
|
||||
$submission->reviewed_by = auth()->id();
|
||||
$submission->reviewed_at = now();
|
||||
|
||||
if ($request->status == 1) {
|
||||
$submission->comment = null;
|
||||
} else {
|
||||
$submission->comment = $request->comment;
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$submission->save();
|
||||
$data['title'] = $request->title;
|
||||
$data['course_id'] = $request->course_id;
|
||||
$data['section_id'] = $request->section;
|
||||
$data['description'] = $request->description;
|
||||
$data['attachment'] = $request->attachment;
|
||||
$data['summary'] = $request->summary;
|
||||
|
||||
return redirect()->back()->with('success', get_phrase('Project graded successfully'));
|
||||
|
||||
Lesson::where('id', $id)->update($data);
|
||||
|
||||
Session::flash('success', get_phrase('Project has been updated.'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$title = Lesson::join('sections', 'lessons.section_id', 'sections.id')
|
||||
->join('courses', 'sections.course_id', 'courses.id')
|
||||
->where('courses.user_id', auth()->user()->id)
|
||||
->where('lessons.title', $request->title)
|
||||
->first();
|
||||
if ($title) {
|
||||
Session::flash('error', get_phrase('Title has been taken.'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$maxSort = Lesson::where('section_id', $request->section)
|
||||
->max('sort');
|
||||
@ -56,8 +49,8 @@ class ProjectController extends Controller
|
||||
$data['title'] = $request->title;
|
||||
$data['course_id'] = $request->course_id;
|
||||
$data['section_id'] = $request->section;
|
||||
$data['description'] = $request->description;
|
||||
$data['attachment'] = $request->attachment;
|
||||
$data['description'] = $request->description;
|
||||
$data['summary'] = $request->summary;
|
||||
$data['lesson_type'] = 'project';
|
||||
$data['status'] = 1;
|
||||
@ -68,10 +61,11 @@ class ProjectController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function getIndex($id)
|
||||
public function getSubmissions($id)
|
||||
{
|
||||
$participants = ProjectSubmission::join('users', 'project_submissions.user_id', '=', 'users.id')
|
||||
->where('project_submissions.lesson_id', $id)
|
||||
->where('project_submissions.status', 0)
|
||||
->select('users.name', 'users.id')
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
849
resources/views/admin/certificate/builder_details.blade.php
Normal file
849
resources/views/admin/certificate/builder_details.blade.php
Normal file
@ -0,0 +1,849 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{ get_phrase('Certificate') }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/backend/vendors/bootstrap/bootstrap.min.css') }}" />
|
||||
<link rel="stylesheet" href="{{ asset('assets/global/jquery-ui-themes-1.13.2/themes/base/jquery-ui.css') }}">
|
||||
|
||||
{{-- FlatIcons --}}
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-solid-rounded/css/uicons-solid-rounded.css') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-bold-rounded/css/uicons-bold-rounded.css') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-bold-straight/css/uicons-bold-straight.css') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-regular-rounded/css/uicons-regular-rounded.css') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-thin-rounded/css/uicons-thin-rounded.css') }}" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('assets/backend/css/style.css') }}">
|
||||
|
||||
{{-- Font Awesome untuk ikon tambahan --}}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<script type="text/javascript" src="{{ asset('assets/backend/js/jquery-3.7.1.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ asset('assets/global/jquery-ui-1.13.2/jquery-ui.min.js') }}"></script>
|
||||
<script src="{{ asset('assets/backend/vendors/bootstrap/bootstrap.bundle.min.js') }}"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: 'Inter', 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
border: 2px dashed rgba(255, 255, 255, 0.8);
|
||||
cursor: move;
|
||||
background-color: #15b57e33;
|
||||
top: 0;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.draggable:hover {
|
||||
border-color: #15b57e;
|
||||
box-shadow: 0 6px 12px rgba(21, 181, 126, 0.2);
|
||||
}
|
||||
|
||||
.hidden-position:not(.certificate-layout-module) {
|
||||
background-color: #ffd3d3 !important;
|
||||
}
|
||||
|
||||
.resizeable-canvas {
|
||||
width: 400px;
|
||||
padding: 10px;
|
||||
box-shadow: 1px 3px 11px -4px #565656;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.certificate-layout-module.resizeable-canvas {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.certificate-layout-module {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -350px;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
background-color: #ffffff;
|
||||
width: 350px;
|
||||
height: 100%;
|
||||
transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
box-shadow: -5px 0 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: rgba(0, 17, 81, 1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 150;
|
||||
background: rgba(0, 17, 81, 1);
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.remove-item {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.remove-item:hover {
|
||||
background-color: #c82333;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
i:not(.fas, .fa, .fab) {
|
||||
line-height: 1.5em !important;
|
||||
vertical-align: -0.14em !important;
|
||||
display: inline-flex !important;
|
||||
}
|
||||
|
||||
.dotted-background {
|
||||
background-image: radial-gradient(circle, #afafaf 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background-color: #f0f2f5;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.font-family-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.font-family-option:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.font-family-option.active {
|
||||
background-color: #e8eeff;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.font-family-preview {
|
||||
font-size: 14px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin: 2px;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.badge:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 10px 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-ol-btn-light-primary {
|
||||
background-color: #e8eeff;
|
||||
color: #667eea;
|
||||
border: 1px solid #c2d0ff;
|
||||
}
|
||||
|
||||
.btn-ol-btn-light-primary:hover {
|
||||
background-color: #d5deff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.btn-ol-btn-primary {
|
||||
background: rgba(0, 17, 81, 1);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-ol-btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.font-style-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.font-style-btn {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.font-style-btn:hover {
|
||||
border-color: #667eea;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.font-style-btn.active {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.color-picker-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.font-weight-slider {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.text-decoration-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.text-decoration-btn {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-decoration-btn:hover {
|
||||
border-color: #667eea;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.text-decoration-btn.active {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.font-option-group {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.font-option-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a onclick="$('.sidebar').addClass('open')" href="#" class="sidebar-toggle">
|
||||
<i class="fas fa-sliders-h" style="color: white; font-size: 20px;"></i>
|
||||
</a>
|
||||
|
||||
<div class="sidebar open">
|
||||
<div class="sidebar-header border-bottom d-flex align-items-center">
|
||||
<a class="btn text-white" href="#" onclick="$('.sidebar').removeClass('open')">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
<span class="ms-2 fw-bold">{{ get_phrase('Certificate Designer') }}</span>
|
||||
<a class="ms-auto btn btn-sm btn-outline-light" href="{{ route('admin.certificate.settings') }}">
|
||||
<i class="fas fa-arrow-left me-1"></i>{{ get_phrase('Back') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-body">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title d-flex align-items-center">
|
||||
<i class="fas fa-code me-2"></i>{{ get_phrase('Available Variables') }}
|
||||
</h6>
|
||||
<div class="d-flex flex-wrap">
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{course_duration}')">{course_duration}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{certificate_id}')">{certificate_id}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{instructor_name}')">{instructor_name}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{student_name}')">{student_name}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{course_title}')">{course_title}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{number_of_lesson}')">{number_of_lesson}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{qr_code}')">{qr_code}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{course_completion_date}')">{course_completion_date}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{certificate_download_date}')">{certificate_download_date}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{course_level}')">{course_level}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{course_language}')">{course_language}</span>
|
||||
<span class="badge bg-primary cursor-pointer" onclick="addVariableToText('{section_list}')">{section_list}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm" id="custom_elem_form">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title d-flex align-items-center">
|
||||
<i class="fas fa-plus-circle me-2"></i>{{ get_phrase('Add New Element') }}
|
||||
</h6>
|
||||
|
||||
<form action="#">
|
||||
<div class="font-option-group">
|
||||
<div class="font-option-title">
|
||||
<i class="fas fa-font"></i>{{ get_phrase('Text Content') }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="certificate_element_content" class="form-label">{{ get_phrase('Enter Text with variables') }}</label>
|
||||
<textarea name="certificate_element_content"
|
||||
placeholder="{{ get_phrase('Example: This certifies that {student_name} has completed {course_title}') }}"
|
||||
id="certificate_element_content"
|
||||
rows="3"
|
||||
class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="font-option-group">
|
||||
<div class="font-option-title">
|
||||
<i class="fas fa-palette"></i>{{ get_phrase('Font Styling') }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="font_family" class="form-label">{{ get_phrase('Font Family') }}</label>
|
||||
<div id="font-family-options">
|
||||
<!-- Font options will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ get_phrase('Font Weight') }}</label>
|
||||
<input type="range"
|
||||
class="form-range font-weight-slider"
|
||||
id="font_weight"
|
||||
min="100"
|
||||
max="900"
|
||||
step="100"
|
||||
value="400">
|
||||
<div class="d-flex justify-content-between">
|
||||
<small>Thin</small>
|
||||
<small>Normal</small>
|
||||
<small>Bold</small>
|
||||
<small>Black</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ get_phrase('Font Style') }}</label>
|
||||
<div class="font-style-options">
|
||||
<button type="button" class="font-style-btn" data-style="normal">
|
||||
<i class="fas fa-font"></i> Normal
|
||||
</button>
|
||||
<button type="button" class="font-style-btn" data-style="italic">
|
||||
<i class="fas fa-italic"></i> Italic
|
||||
</button>
|
||||
<button type="button" class="font-style-btn" data-style="oblique">
|
||||
<i class="fas fa-slash"></i> Oblique
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ get_phrase('Text Decoration') }}</label>
|
||||
<div class="text-decoration-options">
|
||||
<button type="button" class="text-decoration-btn" data-decoration="none">
|
||||
None
|
||||
</button>
|
||||
<button type="button" class="text-decoration-btn" data-decoration="underline">
|
||||
<u>Underline</u>
|
||||
</button>
|
||||
<button type="button" class="text-decoration-btn" data-decoration="overline">
|
||||
Overline
|
||||
</button>
|
||||
<button type="button" class="text-decoration-btn" data-decoration="line-through">
|
||||
<s>Strike</s>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ get_phrase('Text Color') }}</label>
|
||||
<div class="color-picker-container">
|
||||
<input type="color"
|
||||
id="text_color"
|
||||
value="#000000"
|
||||
style="position: absolute; opacity: 0; width: 30px; height: 30px; cursor: pointer;">
|
||||
<div class="color-preview" id="color_preview" style="background-color: #000000;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="font_size" class="form-label">{{ get_phrase('Font Size') }}: <span id="font_size_value">16</span>px</label>
|
||||
<input type="range"
|
||||
class="form-range"
|
||||
id="font_size"
|
||||
min="8"
|
||||
max="72"
|
||||
step="1"
|
||||
value="16">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="font-option-group">
|
||||
<div class="font-option-title">
|
||||
<i class="fas fa-text-height"></i>{{ get_phrase('Text Alignment') }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary flex-fill" onclick="setTextAlignment('left')">
|
||||
<i class="fas fa-align-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary flex-fill" onclick="setTextAlignment('center')">
|
||||
<i class="fas fa-align-center"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary flex-fill" onclick="setTextAlignment('right')">
|
||||
<i class="fas fa-align-right"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary flex-fill" onclick="setTextAlignment('justify')">
|
||||
<i class="fas fa-align-justify"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<button type="button" class="btn ol-btn-light-primary w-100" onclick="addElemToCertificate()">
|
||||
<i class="fas fa-plus me-2"></i>{{ get_phrase('Add Element') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<button type="button" class="btn ol-btn-primary w-100" onclick="saveTemplate()">
|
||||
<i class="fas fa-save me-2"></i>{{ get_phrase('Save Template') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="certificate_builder_content_details" class="builder dotted-background">
|
||||
{{-- Common style for page builder start --}}
|
||||
<style>
|
||||
/* Import Google Fonts for certificate styling */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Pinyon+Script&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Italianno&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Miss+Fajardose&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@100;300;400;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700;800&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+Pro:wght@200;300;400;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Tangerine:wght@400;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Alex+Brush&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Sacramento&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Parisienne&display=swap');
|
||||
</style>
|
||||
{{-- Common style for page builder END --}}
|
||||
|
||||
@if (get_settings('certificate_builder_content_details'))
|
||||
@php
|
||||
$htmlContent = get_settings('certificate_builder_content_details');
|
||||
$newSrc = get_image(get_settings('certificate_template_details'));
|
||||
$certificate_builder_content_details = preg_replace('/(<img[^>]*class=["\']certificate-template["\'][^>]*src=["\'])([^"\']*)(["\'])/i', '${1}' . $newSrc . '${3}', $htmlContent);
|
||||
@endphp
|
||||
{!! $certificate_builder_content_details !!}
|
||||
@else
|
||||
<div id="certificate-layout-module" class="certificate-layout-module resizeable-canvas draggable position-relative">
|
||||
<img class="certificate-template w-100 h-100" src="{{ get_image(get_settings('certificate_template_details')) }}">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// Available fonts with categories
|
||||
const fontOptions = [
|
||||
{ name: 'Auto', value: 'auto', category: 'default' },
|
||||
{ name: 'Inter', value: 'Inter, sans-serif', category: 'modern' },
|
||||
{ name: 'Pinyon Script', value: 'Pinyon Script, cursive', category: 'elegant' },
|
||||
{ name: 'Italianno', value: 'Italianno, cursive', category: 'script' },
|
||||
{ name: 'Miss Fajardose', value: 'Miss Fajardose, cursive', category: 'script' },
|
||||
{ name: 'Great Vibes', value: 'Great Vibes, cursive', category: 'script' },
|
||||
{ name: 'Dancing Script', value: 'Dancing Script, cursive', category: 'script' },
|
||||
{ name: 'Playfair Display', value: 'Playfair Display, serif', category: 'serif' },
|
||||
{ name: 'Montserrat', value: 'Montserrat, sans-serif', category: 'modern' },
|
||||
{ name: 'Roboto', value: 'Roboto, sans-serif', category: 'modern' },
|
||||
{ name: 'Lato', value: 'Lato, sans-serif', category: 'modern' },
|
||||
{ name: 'Open Sans', value: 'Open Sans, sans-serif', category: 'modern' },
|
||||
{ name: 'Merriweather', value: 'Merriweather, serif', category: 'serif' },
|
||||
{ name: 'Cinzel', value: 'Cinzel, serif', category: 'decorative' },
|
||||
{ name: 'Raleway', value: 'Raleway, sans-serif', category: 'modern' },
|
||||
{ name: 'Source Serif Pro', value: 'Source Serif Pro, serif', category: 'serif' },
|
||||
{ name: 'Cormorant Garamond', value: 'Cormorant Garamond, serif', category: 'serif' },
|
||||
{ name: 'Tangerine', value: 'Tangerine, cursive', category: 'script' },
|
||||
{ name: 'Alex Brush', value: 'Alex Brush, cursive', category: 'script' },
|
||||
{ name: 'Sacramento', value: 'Sacramento, cursive', category: 'script' },
|
||||
{ name: 'Parisienne', value: 'Parisienne, cursive', category: 'script' }
|
||||
];
|
||||
|
||||
// Current styling options
|
||||
let currentFontStyle = 'normal';
|
||||
let currentTextDecoration = 'none';
|
||||
let currentTextAlign = 'left';
|
||||
let currentTextColor = '#000000';
|
||||
|
||||
$(document).ready(function() {
|
||||
initialize();
|
||||
populateFontOptions();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
function populateFontOptions() {
|
||||
const container = $('#font-family-options');
|
||||
container.empty();
|
||||
|
||||
// Group fonts by category
|
||||
const fontsByCategory = {};
|
||||
fontOptions.forEach(font => {
|
||||
if (!fontsByCategory[font.category]) {
|
||||
fontsByCategory[font.category] = [];
|
||||
}
|
||||
fontsByCategory[font.category].push(font);
|
||||
});
|
||||
|
||||
// Create options for each category
|
||||
Object.keys(fontsByCategory).forEach(category => {
|
||||
const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1);
|
||||
const categoryDiv = $(`<div class="mb-3"><small class="text-muted">${categoryTitle} Fonts</small></div>`);
|
||||
|
||||
fontsByCategory[category].forEach(font => {
|
||||
const isActive = font.value === 'auto' ? 'active' : '';
|
||||
const option = $(`
|
||||
<div class="font-family-option ${isActive}" data-value="${font.value}">
|
||||
<input type="radio" name="font_family" value="${font.value}"
|
||||
id="font_family_${font.value.replace(/\s+/g, '_')}"
|
||||
${font.value === 'auto' ? 'checked' : ''}>
|
||||
<label for="font_family_${font.value.replace(/\s+/g, '_')}" class="ms-2 flex-grow-1">${font.name}</label>
|
||||
<span class="font-family-preview" style="font-family: ${font.value}">Aa</span>
|
||||
</div>
|
||||
`);
|
||||
categoryDiv.append(option);
|
||||
});
|
||||
|
||||
container.append(categoryDiv);
|
||||
});
|
||||
|
||||
// Add click event to font options
|
||||
$('.font-family-option').click(function() {
|
||||
$('.font-family-option').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$(this).find('input[type="radio"]').prop('checked', true);
|
||||
});
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
// Font size display
|
||||
$('#font_size').on('input', function() {
|
||||
$('#font_size_value').text($(this).val());
|
||||
});
|
||||
|
||||
// Font style buttons
|
||||
$('.font-style-btn').click(function() {
|
||||
$('.font-style-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
currentFontStyle = $(this).data('style');
|
||||
});
|
||||
|
||||
// Text decoration buttons
|
||||
$('.text-decoration-btn').click(function() {
|
||||
$('.text-decoration-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
currentTextDecoration = $(this).data('decoration');
|
||||
});
|
||||
|
||||
// Color picker
|
||||
$('#text_color').on('input', function() {
|
||||
$('#color_preview').css('background-color', $(this).val());
|
||||
currentTextColor = $(this).val();
|
||||
});
|
||||
|
||||
// Initialize first button as active
|
||||
$('.font-style-btn[data-style="normal"]').addClass('active');
|
||||
$('.text-decoration-btn[data-decoration="none"]').addClass('active');
|
||||
}
|
||||
|
||||
function addVariableToText(variable) {
|
||||
const textarea = $('#certificate_element_content');
|
||||
const currentValue = textarea.val();
|
||||
const cursorPos = textarea[0].selectionStart;
|
||||
|
||||
const newValue = currentValue.substring(0, cursorPos) +
|
||||
variable +
|
||||
currentValue.substring(cursorPos);
|
||||
|
||||
textarea.val(newValue);
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
function setTextAlignment(alignment) {
|
||||
currentTextAlign = alignment;
|
||||
$('.btn-outline-secondary').removeClass('active');
|
||||
$(`button[onclick="setTextAlignment('${alignment}')"]`).addClass('active');
|
||||
}
|
||||
|
||||
function saveTemplate() {
|
||||
var certificate_builder_content_details = $('#certificate_builder_content_details').html();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "{{ route('admin.certificate.builder.details.update') }}",
|
||||
data: {
|
||||
certificate_builder_content_details: certificate_builder_content_details
|
||||
},
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
success: function(response) {
|
||||
$(location).attr('href', response);
|
||||
console.log(response)
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error('Error saving template:', xhr);
|
||||
alert('Error saving template. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addElemToCertificate() {
|
||||
var font_family = $("input[type='radio'][name='font_family']:checked").val();
|
||||
var font_size = $("#font_size").val();
|
||||
var font_weight = $("#font_weight").val();
|
||||
var certificate_element_content = $('#certificate_element_content').val();
|
||||
|
||||
// Generate unique ID for the element
|
||||
var elementId = 'elem_' + Date.now() + Math.floor(Math.random() * 1000);
|
||||
|
||||
var certificateElem = `
|
||||
<div id="${elementId}"
|
||||
class="draggable resizeable-canvas"
|
||||
style="
|
||||
padding: 10px !important;
|
||||
position: absolute;
|
||||
font-size: ${font_size}px;
|
||||
font-weight: ${font_weight};
|
||||
font-style: ${currentFontStyle};
|
||||
text-decoration: ${currentTextDecoration};
|
||||
text-align: ${currentTextAlign};
|
||||
color: ${currentTextColor};
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
min-width: 100px;
|
||||
min-height: 40px;
|
||||
font-family: ${font_family};
|
||||
border-radius: 8px;
|
||||
z-index: 5;
|
||||
">
|
||||
${certificate_element_content}
|
||||
<div class="remove-item" onclick="$(this).parent().remove(); positionTracking(this.parentNode)">
|
||||
<i class="fas fa-times"></i>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (certificate_element_content.trim() !== '') {
|
||||
$('#certificate-layout-module').append(certificateElem);
|
||||
|
||||
// Reset form
|
||||
$('#certificate_element_content').val('');
|
||||
$("#font_size").val(16);
|
||||
$("#font_size_value").text('16');
|
||||
$("#font_weight").val(400);
|
||||
currentTextColor = '#000000';
|
||||
$('#color_preview').css('background-color', '#000000');
|
||||
$('#text_color').val('#000000');
|
||||
|
||||
// Reset to default font
|
||||
$('.font-family-option').removeClass('active');
|
||||
$('.font-family-option[data-value="auto"]').addClass('active').find('input').prop('checked', true);
|
||||
|
||||
initialize();
|
||||
} else {
|
||||
alert('Please enter some text content.');
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
$(".draggable").draggable({
|
||||
containment: "#certificate-layout-module",
|
||||
cursor: "move",
|
||||
scroll: false
|
||||
});
|
||||
|
||||
$(".resizeable-canvas").resizable({
|
||||
resize: function(event, ui) {
|
||||
$(".draggable").draggable("disable");
|
||||
positionTracking(this);
|
||||
},
|
||||
stop: function(event, ui) {
|
||||
$(".draggable").draggable("enable");
|
||||
positionTracking(this);
|
||||
},
|
||||
handles: "all",
|
||||
minHeight: 30,
|
||||
minWidth: 50
|
||||
});
|
||||
|
||||
$(".draggable").on("dragstop", function(e, pos) {
|
||||
positionTracking(this);
|
||||
});
|
||||
}
|
||||
|
||||
function positionTracking(e) {
|
||||
var layoutCanvasOffset = $('#certificate-layout-module').offset();
|
||||
var layoutCanvasRight = $('#certificate-layout-module').width() + layoutCanvasOffset.left;
|
||||
var layoutCanvasBottom = $('#certificate-layout-module').height() + layoutCanvasOffset.top;
|
||||
|
||||
if ($(e).attr('id') != 'certificate-layout-module') {
|
||||
var itemOffset = $(e).offset();
|
||||
var itemRight = $(e).width() + itemOffset.left;
|
||||
var itemBottom = $(e).height() + itemOffset.top;
|
||||
|
||||
if (
|
||||
layoutCanvasOffset.left < itemOffset.left &&
|
||||
layoutCanvasOffset.top < itemOffset.top &&
|
||||
layoutCanvasRight > itemRight &&
|
||||
layoutCanvasBottom > itemBottom
|
||||
) {
|
||||
$(e).removeClass('hidden-position');
|
||||
$(e).css('border-color', 'rgba(21, 181, 126, 0.5)');
|
||||
} else {
|
||||
$(e).addClass('hidden-position');
|
||||
$(e).css('border-color', '#ff6b6b');
|
||||
}
|
||||
} else {
|
||||
var draggableItems = document.getElementsByClassName("draggable");
|
||||
for (var i = 0; i < draggableItems.length; i++) {
|
||||
var item = $(draggableItems.item(i));
|
||||
if (item.attr('id') == 'certificate-layout-module') continue;
|
||||
|
||||
var itemOffset = item.offset();
|
||||
var itemRight = item.width() + itemOffset.left;
|
||||
var itemBottom = item.height() + itemOffset.top;
|
||||
|
||||
if (
|
||||
layoutCanvasOffset.left < itemOffset.left &&
|
||||
layoutCanvasOffset.top < itemOffset.top &&
|
||||
layoutCanvasRight > itemRight &&
|
||||
layoutCanvasBottom > itemBottom
|
||||
) {
|
||||
item.removeClass('hidden-position');
|
||||
item.css('border-color', 'rgba(21, 181, 126, 0.5)');
|
||||
} else {
|
||||
item.addClass('hidden-position');
|
||||
item.css('border-color', '#ff6b6b');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -32,7 +32,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="ol-card p-4">
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate template') }}</p>
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate Template Main') }}</p>
|
||||
<div class="ol-card-body certificate_builder_view" id="certificate_builder_view">
|
||||
@php
|
||||
$htmlContent = get_settings('certificate_builder_content');
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ol-card p-4">
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate template') }}</p>
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate Template Main') }}</p>
|
||||
<div class="ol-card-body">
|
||||
<form action="{{ route('admin.certificate.update.template') }}" method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@ -55,7 +55,7 @@
|
||||
<img class="my-2" height="200px" src="{{ get_image(get_settings('certificate_template')) }}" alt="">
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label ol-form-label" for="certificate_template">{{ get_phrase('Upload your certificate template') }}</label>
|
||||
<label class="form-label ol-form-label" for="certificate_template">{{ get_phrase('Upload your certificate template main') }}</label>
|
||||
<input type="file" class="form-control" name="certificate_template" id="certificate_template"rows="4">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -66,6 +66,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="ol-card p-4">
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate Template Details') }}</p>
|
||||
<div class="ol-card-body certificate_builder_view" id="certificate_builder_view">
|
||||
@php
|
||||
$htmlContent = get_settings('certificate_builder_content_details');
|
||||
// Use regex to update the src attribute of the <img> tag with the class 'certificate-template'.
|
||||
$newSrc = get_image(get_settings('certificate_template_details'));
|
||||
$certificate_builder_content = preg_replace('/(<img[^>]*class=["\']certificate-template["\'][^>]*src=["\'])([^"\']*)(["\'])/i', '${1}' . $newSrc . '${3}', $htmlContent);
|
||||
@endphp
|
||||
{!! $certificate_builder_content !!}
|
||||
<a class="btn ol-btn-primary mt-3" href="{{ route('admin.certificate.builder_details') }}">{{ get_phrase('Build your certificate') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ol-card p-4">
|
||||
<p class="title text-14px mb-3">{{ get_phrase('Certificate Template Details') }}</p>
|
||||
<div class="ol-card-body">
|
||||
<form action="{{ route('admin.certificate.update.template.details') }}" method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="form-group text-start mb-3">
|
||||
<img class="my-2" height="200px" src="{{ get_image(get_settings('certificate_template_details')) }}" alt="">
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label ol-form-label" for="certificate_template_details">{{ get_phrase('Upload your certificate template details') }}</label>
|
||||
<input type="file" class="form-control" name="certificate_template_details" id="certificate_template_details"rows="4">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn ol-btn-primary" type="submit">{{ get_phrase('Upload') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('js')
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
<a href="#" onclick="ajaxModal('{{ route('modal', ['admin.quiz.create', 'id' => $course_details->id]) }}', '{{ get_phrase('Add new quiz') }}')" class="btn ol-btn-light ol-btn-sm">{{ get_phrase('Add quiz') }}</a>
|
||||
|
||||
<a href="#" onclick="ajaxModal('{{ route('modal', ['admin.course.section_sort', 'id' => $course_details->id]) }}', '{{ get_phrase('Sort sections') }}')" class="btn ol-btn-light ol-btn-sm">{{ get_phrase('Sort Section') }}</a>
|
||||
|
||||
<a href="#" onclick="ajaxModal('{{ route('modal', ['admin.project.create', 'id' => $course_details->id]) }}', '{{ get_phrase('Add new project') }}')" class="btn ol-btn-light ol-btn-sm">{{ get_phrase('Add project') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@ -57,7 +59,7 @@
|
||||
<div class="buttons">
|
||||
@if ($lesson->lesson_type == 'quiz')
|
||||
<a href="#" data-bs-toggle="tooltip" title="{{ get_phrase('Result') }}" onclick="ajaxModal('{{ route('modal', ['admin.quiz_result.index', 'id' => $lesson->id]) }}', '{{ get_phrase('Result') }}', 'modal-xl')" class="edit-delete">
|
||||
<span class="fi fi-rr-clipboard-list-check"></span>
|
||||
<span class="fi fi-rr-paperclip-vertical"></span>
|
||||
</a>
|
||||
|
||||
<a href="#" data-bs-toggle="tooltip" title="{{ get_phrase('Questions') }}" onclick="ajaxModal('{{ route('modal', ['admin.questions.index', 'id' => $lesson->id]) }}', '{{ get_phrase('Questions') }}', 'modal-lg')" class="edit-delete">
|
||||
@ -67,9 +69,20 @@
|
||||
<a href="#" data-bs-toggle="tooltip" title="{{ get_phrase('Edit quiz') }}" onclick="ajaxModal('{{ route('modal', ['admin.quiz.edit', 'id' => $lesson->id]) }}', '{{ get_phrase('Edit quiz') }}')" class="edit-delete">
|
||||
<span class="fi-rr-pencil"></span>
|
||||
</a>
|
||||
@elseif ($lesson->lesson_type == 'project')
|
||||
<a href="javascript:void(0);"
|
||||
data-bs-toggle="tooltip"
|
||||
title="{{ get_phrase('Submissions') }}"
|
||||
onclick="ajaxModal('{{ route('admin.project.grading.index', $lesson->id) }}', '{{ get_phrase('Submissions') }}', 'modal-xl')"
|
||||
class="edit-delete">
|
||||
<span class="fi fi-rr-clipboard-list"></span>
|
||||
</a>
|
||||
<a href="#" data-bs-toggle="tooltip" title="{{ get_phrase('Edit project') }}" onclick="ajaxModal('{{ route('modal', ['admin.project.edit', 'id' => $lesson->id]) }}', '{{ get_phrase('Edit project') }}')" class="edit-delete">
|
||||
<span class="fi-rr-pencil"></span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if ($lesson->lesson_type != 'quiz')
|
||||
@if ($lesson->lesson_type != 'quiz' && $lesson->lesson_type != 'project')
|
||||
<a href="#" data-bs-toggle="tooltip" title="{{ get_phrase('Edit lesson') }}" onclick="ajaxModal('{{ route('modal', ['admin.course.lesson_edit', 'id' => $lesson->id]) }}', '{{ get_phrase('Edit lesson') }}')" class="edit-delete">
|
||||
<span class="fi-rr-pencil"></span>
|
||||
</a>
|
||||
|
||||
50
resources/views/admin/project/create.blade.php
Normal file
50
resources/views/admin/project/create.blade.php
Normal file
@ -0,0 +1,50 @@
|
||||
<form action="{{ route('admin.course.project.store') }}" method="post">@csrf
|
||||
|
||||
<input type="hidden" name="course_id" value="{{ $id }}">
|
||||
<div class="fpb7 mb-3">
|
||||
<label class="form-label ol-form-label" for="title">
|
||||
{{ get_phrase('Title') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<input class="form-control ol-form-control" type="text" id="title" name="title" required>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 fpb-7">
|
||||
<label class="form-label ol-form-label">
|
||||
{{ get_phrase('Section') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section" required>
|
||||
<option value="">{{ get_phrase('Select an option') }}</option>
|
||||
@foreach (App\Models\Section::where('course_id', $id)->get() as $section)
|
||||
<option value="{{ $section->id }}">{{ $section->title }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-3">
|
||||
<label for="description"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Project Assignment') }}</label>
|
||||
<textarea name="description" rows="5" class="form-control ol-form-control text_editor"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-3">
|
||||
<label for="attachment"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Assessment Criteria') }}</label>
|
||||
<textarea name="attachment" rows="5" class="form-control ol-form-control text_editor"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-6">
|
||||
<label for="summary"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Summary') }}</label>
|
||||
<textarea name="summary" rows="5" class="form-control ol-form-control text_editor"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb7">
|
||||
<button type="submit" class="btn ol-btn-primary">{{ get_phrase('Add Project') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@include('admin.init')
|
||||
63
resources/views/admin/project/edit.blade.php
Normal file
63
resources/views/admin/project/edit.blade.php
Normal file
@ -0,0 +1,63 @@
|
||||
@php
|
||||
$project = App\Models\Lesson::join('sections', 'lessons.section_id', 'sections.id')
|
||||
->join('courses', 'sections.course_id', 'courses.id')
|
||||
->select('lessons.*', 'courses.id as course_id')
|
||||
->where('lessons.id', $id)
|
||||
->first();
|
||||
@endphp
|
||||
|
||||
<form action="{{ route('admin.course.project.update', $id) }}" method="post">@csrf
|
||||
|
||||
{{-- FIX: Pass the course_id back to the controller --}}
|
||||
<input type="hidden" name="course_id" value="{{ $project->course_id }}">
|
||||
|
||||
<div class="fpb7 mb-3">
|
||||
<label class="form-label ol-form-label" for="title">
|
||||
{{ get_phrase('Title') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<input class="form-control ol-form-control" type="text" id="title" name="title" value="{{ $project->title }}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 fpb-7">
|
||||
<label class="form-label ol-form-label">
|
||||
{{ get_phrase('Section') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section" required>
|
||||
<option value="">{{ get_phrase('Select an option') }}</option>
|
||||
@foreach (App\Models\Section::where('course_id', $project->course_id)->get() as $section)
|
||||
<option value="{{ $section->id }}" @if ($section->id == $project->section_id) selected @endif>
|
||||
{{ $section->title }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-3">
|
||||
<label for="description"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Project Assignment') }}</label>
|
||||
<textarea name="description" rows="5" class="form-control ol-form-control text_editor">{!! $project->description !!}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-3">
|
||||
<label for="attachment"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Assessment Criteria') }}</label>
|
||||
{{-- Ensure your Controller handles $request->attachment --}}
|
||||
<textarea name="attachment" rows="5" class="form-control ol-form-control text_editor">{!! $project->attachment !!}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-6">
|
||||
<label for="summary"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Summary') }}</label>
|
||||
<textarea name="summary" rows="5" class="form-control ol-form-control text_editor">{!! $project->summary !!}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb7">
|
||||
<button type="submit" class="btn ol-btn-primary">{{ get_phrase('Update Project') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@include('admin.init')
|
||||
121
resources/views/admin/project/grading/index.blade.php
Normal file
121
resources/views/admin/project/grading/index.blade.php
Normal file
@ -0,0 +1,121 @@
|
||||
{{-- Container Utama --}}
|
||||
<div class="row g-0 h-100">
|
||||
|
||||
{{-- KOLOM KIRI: LIST SISWA --}}
|
||||
<div class="col-md-4 border-end" style="min-height: 400px; max-height: 70vh; overflow-y: auto;">
|
||||
<div class="p-3 border-bottom bg-light sticky-top">
|
||||
<h6 class="mb-0 fw-bold"><i class="fas fa-users me-2"></i> {{ get_phrase('Student List') }}</h6>
|
||||
</div>
|
||||
|
||||
<div class="list-group list-group-flush" id="studentList">
|
||||
@if($participants->isEmpty())
|
||||
<div class="text-center p-5 text-muted small">
|
||||
{{ get_phrase('No participants found.') }}
|
||||
</div>
|
||||
@else
|
||||
@foreach ($participants as $participant)
|
||||
<a href="javascript:void(0);"
|
||||
class="list-group-item list-group-item-action py-3 student-item"
|
||||
{{-- PERBAIKAN DISINI: Tambahkan parameter ke-3 yaitu $lesson_id --}}
|
||||
onclick="loadStudentSubmission(this, '{{ $participant->id }}', '{{ $lesson_id }}')">
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-3 d-flex align-items-center justify-content-center rounded-circle bg-primary text-white fw-bold"
|
||||
style="width: 44px; height: 36px; font-size: 14px;">
|
||||
{{ strtoupper(substr($participant->name, 0, 1)) }}
|
||||
</div>
|
||||
|
||||
<div class="w-100">
|
||||
<h6 class="mb-0 text-dark" style="font-size: 14px;">{{ $participant->name }}</h6>
|
||||
<small class="text-muted" style="font-size: 12px;">{{ get_phrase('Click to grade') }}</small>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right text-muted small"></i>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- KOLOM KANAN: AREA PREVIEW & GRADING --}}
|
||||
<div class="col-md-8 bg-white">
|
||||
{{-- ID ini penting: target AJAX --}}
|
||||
<div id="grading-area" class="h-100 position-relative">
|
||||
|
||||
{{-- State Awal (Belum Klik) --}}
|
||||
<div class="d-flex flex-column align-items-center justify-content-center h-100 text-center p-5">
|
||||
<div class="mb-3 p-4 rounded-circle bg-light">
|
||||
<i class="fi fi-rr-box-open fs-1 text-secondary opacity-50"></i>
|
||||
</div>
|
||||
<h6 class="text-muted">{{ get_phrase('Select a student to start grading') }}</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function loadStudentSubmission(element, userId, lessonId) {
|
||||
// Cegah event default
|
||||
event.preventDefault();
|
||||
|
||||
// Validasi parameter
|
||||
if (!userId || !lessonId) {
|
||||
console.error("Missing parameters. User ID:", userId, "Lesson ID:", lessonId);
|
||||
alert('Missing parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Visual Active State
|
||||
$('.student-item').removeClass('active bg-light');
|
||||
$(element).addClass('active bg-light');
|
||||
|
||||
// 2. Loading State
|
||||
$('#grading-area').html(`
|
||||
<div class="d-flex flex-column align-items-center justify-content-center h-100">
|
||||
<div class="spinner-border text-primary mb-2" role="status"></div>
|
||||
<small class="text-muted">Loading data...</small>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Debugging
|
||||
console.log("AJAX Call Parameters:");
|
||||
console.log("- Lesson ID:", lessonId);
|
||||
console.log("- User ID:", userId);
|
||||
console.log("- URL:", "{{ route('admin.project.grading.preview') }}");
|
||||
console.log("- CSRF Token:", $('meta[name="csrf-token"]').attr('content'));
|
||||
|
||||
// 3. AJAX Call
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "{{ route('admin.project.grading.preview') }}",
|
||||
data: {
|
||||
lesson_id: lessonId,
|
||||
user_id: userId
|
||||
},
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
success: function(response) {
|
||||
console.log("Success Response:", response.substring(0, 200)); // Log 200 karakter pertama
|
||||
$('#grading-area').html(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("AJAX Error:", error);
|
||||
console.error("Status:", status);
|
||||
console.error("Response:", xhr.responseText);
|
||||
|
||||
$('#grading-area').html(`
|
||||
<div class="alert alert-danger m-3">
|
||||
<h6>Failed to load data</h6>
|
||||
<small>Error: ${error}</small>
|
||||
<br>
|
||||
<small>Check console for details</small>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
182
resources/views/admin/project/grading/preview.blade.php
Normal file
182
resources/views/admin/project/grading/preview.blade.php
Normal file
@ -0,0 +1,182 @@
|
||||
{{-- HAPUS <div class="row"> jika ada, gunakan div biasa --}}
|
||||
<div class="h-100">
|
||||
|
||||
{{-- KONDISI 1: Belum Submit --}}
|
||||
@if(!$submission)
|
||||
<div class="d-flex flex-column align-items-center justify-content-center h-100 text-center p-5">
|
||||
<div class="mb-3 p-3 bg-light rounded-circle">
|
||||
<i class="fas fa-times text-danger fa-2x"></i>
|
||||
</div>
|
||||
<h5>{{ get_phrase('Not Submitted') }}</h5>
|
||||
<p class="text-muted">{{ get_phrase('This student has not submitted the project yet.') }}</p>
|
||||
</div>
|
||||
|
||||
{{-- KONDISI 2: Sudah Submit --}}
|
||||
@else
|
||||
<div class="p-4">
|
||||
{{-- Header Status --}}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 pb-3 border-bottom">
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1">{{ get_phrase('Project Submission') }}</h5>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-clock me-1"></i>
|
||||
{{ date('d M Y, H:i', strtotime($submission->created_at)) }}
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
@if($submission->status == 1)
|
||||
<span class="badge bg-success px-3 py-2">{{ get_phrase('Accepted') }}</span>
|
||||
@elseif($submission->status == 2)
|
||||
<span class="badge bg-danger px-3 py-2">{{ get_phrase('Revision Needed') }}</span>
|
||||
@else
|
||||
<span class="badge bg-warning text-dark px-3 py-2">{{ get_phrase('Pending Review') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Link Project --}}
|
||||
<div class="mb-4">
|
||||
<label class="fw-bold mb-2">{{ get_phrase('Student Project Link') }}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control bg-light" value="{{ $submission->drive_link }}" readonly>
|
||||
<a href="{{ $submission->drive_link }}" target="_blank" class="btn btn-primary">
|
||||
<i class="fas fa-external-link-alt"></i> {{ get_phrase('Open') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Form Grading --}}
|
||||
<div class="card border bg-light shadow-none">
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.project.grading.update', $submission->id) }}" method="post" id="gradingForm">
|
||||
@csrf
|
||||
<h6 class="fw-bold mb-3"><i class="fas fa-check-double me-2"></i>{{ get_phrase('Grading') }}</h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="btn-group w-100" role="group">
|
||||
<input type="radio" class="btn-check" name="status" id="accept_{{ $submission->id }}" value="1"
|
||||
{{ $submission->status == 1 ? 'checked' : '' }} onchange="toggleComment(this)">
|
||||
<label class="btn btn-outline-success" for="accept_{{ $submission->id }}">{{ get_phrase('Passed') }}</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="status" id="revise_{{ $submission->id }}" value="2"
|
||||
{{ $submission->status == 2 ? 'checked' : '' }} onchange="toggleComment(this)">
|
||||
<label class="btn btn-outline-danger" for="revise_{{ $submission->id }}">{{ get_phrase('Revision Needed') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Area Komentar dengan WYSIWYG Editor --}}
|
||||
<div id="comment_box_area" style="display: {{ $submission->status == 2 ? 'block' : 'none' }};">
|
||||
<div class="mb-3">
|
||||
<label class="fw-bold text-danger mb-1">
|
||||
{{ empty($submission->comment) ? get_phrase('Add Revision Notes') : get_phrase('Previous Revision Notes') }} *
|
||||
</label>
|
||||
|
||||
{{-- Textarea untuk Summernote --}}
|
||||
<textarea name="comment" id="comment_editor" class="form-control">{{ $submission->comment }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">{{ get_phrase('Save Grading') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Tambahkan CSS Summernote --}}
|
||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.css" rel="stylesheet">
|
||||
|
||||
{{-- Tambahkan Script Summernote --}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script>
|
||||
|
||||
<script>
|
||||
let commentEditor = null;
|
||||
|
||||
function toggleComment(radio) {
|
||||
let box = document.getElementById('comment_box_area');
|
||||
|
||||
if (radio.value == '2') {
|
||||
$(box).slideDown();
|
||||
// Inisialisasi editor jika belum diinisialisasi
|
||||
initSummernote();
|
||||
// Fokus ke editor
|
||||
setTimeout(() => {
|
||||
if (commentEditor && commentEditor.summernote) {
|
||||
commentEditor.summernote('focus');
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
$(box).slideUp();
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk menginisialisasi Summernote
|
||||
function initSummernote() {
|
||||
if (!commentEditor) {
|
||||
$('#comment_editor').summernote({
|
||||
height: 200,
|
||||
toolbar: [
|
||||
['style', ['style']],
|
||||
['font', ['bold', 'italic', 'underline', 'clear']],
|
||||
['fontname', ['fontname']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['table', ['table']],
|
||||
['insert', ['link', 'picture']],
|
||||
['view', ['fullscreen', 'codeview', 'help']]
|
||||
],
|
||||
placeholder: '{{ get_phrase("Explain what needs to be fixed...") }}'
|
||||
});
|
||||
commentEditor = $('#comment_editor');
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk validasi form sebelum submit
|
||||
$('#gradingForm').on('submit', function(e) {
|
||||
const status = $('input[name="status"]:checked').val();
|
||||
|
||||
if (status == '2') {
|
||||
// Jika status "Revision Needed", pastikan ada konten di editor
|
||||
let commentContent = '';
|
||||
|
||||
if (commentEditor && commentEditor.summernote) {
|
||||
commentContent = commentEditor.summernote('code').trim();
|
||||
} else {
|
||||
commentContent = $('#comment_editor').val().trim();
|
||||
}
|
||||
|
||||
// Hapus tag HTML untuk cek konten sebenarnya
|
||||
const plainText = commentContent.replace(/<[^>]*>/g, '').trim();
|
||||
|
||||
if (plainText === '') {
|
||||
e.preventDefault();
|
||||
alert('{{ get_phrase("Please add revision notes before submitting.") }}');
|
||||
if (commentEditor && commentEditor.summernote) {
|
||||
commentEditor.summernote('focus');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inisialisasi editor otomatis jika sudah ada komentar sebelumnya dan status adalah "Revision Needed"
|
||||
@if($submission && $submission->status == 2 && !empty($submission->comment))
|
||||
$(document).ready(function() {
|
||||
// Tunggu sebentar agar DOM siap
|
||||
setTimeout(() => {
|
||||
initSummernote();
|
||||
}, 100);
|
||||
});
|
||||
@endif
|
||||
|
||||
// Inisialisasi editor jika comment_box_area langsung terlihat
|
||||
$(document).ready(function() {
|
||||
@if($submission && $submission->status == 2)
|
||||
// Tunggu sedikit agar DOM selesai render
|
||||
setTimeout(() => {
|
||||
initSummernote();
|
||||
}, 200);
|
||||
@endif
|
||||
});
|
||||
</script>
|
||||
@ -8,21 +8,10 @@
|
||||
@endphp
|
||||
|
||||
<style>
|
||||
/* Mengatur agar list dan blockquote tidak memakan banyak ruang */
|
||||
.description blockquote{
|
||||
margin: 5px 0 !important;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
.description ul, ul {
|
||||
list-style-type: disc !important;
|
||||
list-style-position: inside !important;
|
||||
margin-left: 10px !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.description li {
|
||||
display: list-item !important;
|
||||
list-style-type: disc;
|
||||
list-style-position: inside ;
|
||||
margin-left: 10px ;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<p class="d-none">{{ $lesson->lesson_type }}</p>
|
||||
@if (in_array($type, ['google_drive', 'video-url', 'quiz']))
|
||||
@if (in_array($type, ['google_drive', 'video-url', 'quiz', 'project']))
|
||||
<a href="{{ route('course.player', ['slug' => $course_details->slug, 'id' => $lesson->id]) }}"
|
||||
class="video-title">
|
||||
{{ $lesson->title }}
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.certificate-layout-module {
|
||||
.certificate-layout-module {
|
||||
left: unset !important;
|
||||
top: unset !important;
|
||||
margin-left: auto !important;
|
||||
@ -38,6 +38,11 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.certificate-page {
|
||||
width: 1123px;
|
||||
height: 794px;
|
||||
}
|
||||
|
||||
.download-wrapper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
@ -123,201 +128,220 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
@php
|
||||
$bulan_indonesia = [
|
||||
1 => 'Januari', 2 => 'Februari', 3 => 'Maret', 4 => 'April',
|
||||
5 => 'Mei', 6 => 'Juni', 7 => 'Juli', 8 => 'Agustus',
|
||||
9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Desember'
|
||||
];
|
||||
|
||||
{{-- Downloadable canvas --}}
|
||||
$completion_date = $certificate->created_at;
|
||||
$timestamp = strtotime($completion_date);
|
||||
$hari = date('j', $timestamp);
|
||||
$bulan_angka = (int)date('n', $timestamp);
|
||||
$tahun = date('Y', $timestamp);
|
||||
|
||||
$course_completion_date = $hari . ' ' . $bulan_indonesia[$bulan_angka] . ' ' . $tahun;
|
||||
$course_duration = $certificate->course->total_duration();
|
||||
$student_name = $certificate->user->name;
|
||||
$course_title = $certificate->course->title;
|
||||
$number_of_lesson = $certificate->course->lessons->count();
|
||||
$qr_code = $qrcode;
|
||||
$certificate_id = $certificate->identifier;
|
||||
$certificate_download_date = date('d M Y');
|
||||
$course_level = ucfirst($certificate->course->level);
|
||||
$course_language = ucfirst($certificate->course->language);
|
||||
|
||||
$instructor_name = '';
|
||||
foreach ($certificate->course->instructors() as $instructor) {
|
||||
$instructor_name .= '<p>' . $instructor->name . '</p>';
|
||||
}
|
||||
|
||||
// --- PERBAIKAN DI SINI (Hapus tanda = yang nyasar) ---
|
||||
|
||||
$sections = $certificate->course->sections()->orderBy('sort', 'asc')->get();
|
||||
|
||||
// --- PERBAIKAN NAMA VARIABEL ---
|
||||
// Ubah $section_list menjadi $section_list_html agar sesuai dengan pemanggilan di bawah
|
||||
$section_list_html = '<ol style="list-style-type: decimal; padding-left: 20px;">';
|
||||
foreach ($sections as $section) {
|
||||
$section_list_html .= '<li style="margin-bottom: 5px;">' . $section->title . '</li>';
|
||||
}
|
||||
$section_list_html .= '</ol>';
|
||||
|
||||
|
||||
$certificate_builder_content = get_settings('certificate_builder_content');
|
||||
|
||||
// Replacements Array (Agar lebih rapi dan bisa dipakai ulang)
|
||||
$replacements = [
|
||||
'{course_duration}' => $course_duration,
|
||||
'{instructor_name}' => $instructor_name,
|
||||
'{student_name}' => $student_name,
|
||||
'{course_title}' => $course_title,
|
||||
'{number_of_lesson}' => $number_of_lesson,
|
||||
'{qr_code}' => $qr_code,
|
||||
'{course_completion_date}' => $course_completion_date,
|
||||
'{certificate_id}' => $certificate_id,
|
||||
'{certificate_download_date}' => $certificate_download_date,
|
||||
'{course_level}' => $course_level,
|
||||
'{course_language}' => $course_language,
|
||||
];
|
||||
|
||||
// Proses Halaman 1
|
||||
foreach ($replacements as $key => $value) {
|
||||
$certificate_builder_content = str_replace($key, $value, $certificate_builder_content);
|
||||
}
|
||||
|
||||
// Update Image Src Halaman 1
|
||||
$newSrc = get_image(get_settings('certificate_template'));
|
||||
$certificate_builder_content = preg_replace('/(<img[^>]*class=["\']certificate-template["\'][^>]*src=["\'])([^"\']*)(["\'])/i', '${1}' . $newSrc . '${3}', $certificate_builder_content);
|
||||
|
||||
|
||||
// --- PEMROSESAN HALAMAN TAMBAHAN (DETAILS) ---
|
||||
$certificate_builder_content_details = get_settings('certificate_builder_content_details');
|
||||
|
||||
// Proses Halaman 2
|
||||
foreach ($replacements as $key => $value) {
|
||||
$certificate_builder_content_details = str_replace($key, $value, $certificate_builder_content_details);
|
||||
}
|
||||
|
||||
// Masukkan Section List (Pastikan variabelnya $section_list_html)
|
||||
$certificate_builder_content_details = str_replace('{section_list}', $section_list_html, $certificate_builder_content_details);
|
||||
|
||||
// Update Image Src Halaman 2
|
||||
$newSrcDetails = get_image(get_settings('certificate_template_details'));
|
||||
$certificate_builder_content_details = preg_replace('/(<img[^>]*class=["\']certificate-template-details["\'][^>]*src=["\'])([^"\']*)(["\'])/i', '${1}' . $newSrcDetails . '${3}', $certificate_builder_content_details);
|
||||
@endphp
|
||||
|
||||
|
||||
{{-- Capture Certificate Wrapper --}}
|
||||
<div class="captureCertificate" id="captureCertificate">
|
||||
@php
|
||||
$bulan_indonesia = [
|
||||
1 => 'Januari',
|
||||
2 => 'Februari',
|
||||
3 => 'Maret',
|
||||
4 => 'April',
|
||||
5 => 'Mei',
|
||||
6 => 'Juni',
|
||||
7 => 'Juli',
|
||||
8 => 'Agustus',
|
||||
9 => 'September',
|
||||
10 => 'Oktober',
|
||||
11 => 'November',
|
||||
12 => 'Desember'
|
||||
];
|
||||
|
||||
// Jika date_formatter() mengembalikan tanggal string
|
||||
$completion_date = $certificate->created_at; // Ambil timestamp asli
|
||||
|
||||
// Konversi ke format Indonesia
|
||||
$timestamp = strtotime($completion_date); // atau $completion_date jika sudah timestamp
|
||||
$hari = date('j', $timestamp); // tanggal tanpa nol
|
||||
$bulan_angka = (int)date('n', $timestamp); // bulan angka
|
||||
$tahun = date('Y', $timestamp);
|
||||
|
||||
$course_completion_date = $hari . ' ' . $bulan_indonesia[$bulan_angka] . ' ' . $tahun;
|
||||
$course_duration = $certificate->course->total_duration();
|
||||
$student_name = $certificate->user->name;
|
||||
$course_title = $certificate->course->title;
|
||||
$number_of_lesson = $certificate->course->lessons->count();
|
||||
$qr_code = $qrcode;
|
||||
$certificate_id = $certificate->identifier;
|
||||
$certificate_download_date = date('d M Y');
|
||||
$course_level = ucfirst($certificate->course->level);
|
||||
$course_language = ucfirst($certificate->course->language);
|
||||
$instructor_name = '';
|
||||
|
||||
foreach ($certificate->course->instructors() as $instructor) {
|
||||
$instructor_name .= '<p>' . $instructor->name . '</p>';
|
||||
}
|
||||
|
||||
$certificate_builder_content = get_settings('certificate_builder_content');
|
||||
$certificate_builder_content = str_replace('{course_duration}', $course_duration, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{instructor_name}', $instructor_name, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{student_name}', $student_name, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{course_title}', $course_title, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{number_of_lesson}', $number_of_lesson, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{qr_code}', $qr_code, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{course_completion_date}', $course_completion_date, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{certificate_id}', $certificate_id, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{certificate_download_date}', $certificate_download_date, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{course_level}', $course_level, $certificate_builder_content);
|
||||
$certificate_builder_content = str_replace('{course_language}', $course_language, $certificate_builder_content);
|
||||
|
||||
// Use regex to update the src attribute of the <img> tag with the class 'certificate-template'.
|
||||
$newSrc = get_image(get_settings('certificate_template'));
|
||||
$certificate_builder_content = preg_replace('/(<img[^>]*class=["\']certificate-template["\'][^>]*src=["\'])([^"\']*)(["\'])/i', '${1}' . $newSrc . '${3}', $certificate_builder_content);
|
||||
@endphp
|
||||
|
||||
{!! $certificate_builder_content !!}
|
||||
</div>
|
||||
{{-- Downloadable canvas end--}}
|
||||
|
||||
|
||||
{{-- Preview certificate --}}
|
||||
<div class="absolute-view">
|
||||
<div class="certificate_builder_view" id="certificate_builder_view">
|
||||
{{-- Halaman 1 --}}
|
||||
<div class="certificate-page page-1">
|
||||
{!! $certificate_builder_content !!}
|
||||
</div>
|
||||
</div>
|
||||
{{-- Preview certificate end--}}
|
||||
|
||||
{{-- Halaman 2 (Baru) --}}
|
||||
{{-- Style margin-top hanya visual di web, tidak mempengaruhi PDF split --}}
|
||||
<div class="certificate-page page-2" style="margin-top: 50px;">
|
||||
{!! $certificate_builder_content_details !!}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{-- Capture Certificate End --}}
|
||||
|
||||
|
||||
{{-- Preview Certificate --}}
|
||||
<div class="absolute-view">
|
||||
<div class="certificate_builder_view" id="certificate_builder_view">
|
||||
{{-- Tampilkan kedua halaman di preview juga --}}
|
||||
<div class="certificate-page">
|
||||
{!! $certificate_builder_content !!}
|
||||
</div>
|
||||
<div class="certificate-page" style="margin-top: 20px;">
|
||||
{!! $certificate_builder_content_details !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="download-wrapper">
|
||||
<a class="download-btn" href="#"
|
||||
<!-- <a class="download-btn secondary" href="#"
|
||||
onclick="setTimeout(() => renderCanvasToImage(), 500); return false;">
|
||||
{{ get_phrase('Download Image') }}
|
||||
</a>
|
||||
</a> -->
|
||||
|
||||
<a class="download-btn secondary" href="#"
|
||||
<a class="download-btn" href="#"
|
||||
onclick="renderCanvasToPDF(); return false;">
|
||||
{{ get_phrase('Download PDF') }}
|
||||
{{ get_phrase('Download Sertificate') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
var certificate_builder_view_width = $('.certificate_builder_view').width();
|
||||
var certificate_layout_module = $('.certificate_builder_view .certificate-layout-module').width();
|
||||
var zoomScaleValue = ((certificate_builder_view_width/certificate_layout_module)*100) - 8;
|
||||
$('.certificate_builder_view .certificate-layout-module').css('zoom', zoomScaleValue+'%');
|
||||
});
|
||||
$(function() {
|
||||
// Skrip zoom diperbaiki agar berlaku untuk semua halaman sertifikat
|
||||
var certificate_builder_view_width = $('.certificate_builder_view').width();
|
||||
|
||||
function renderCanvasToImage() {
|
||||
// Ambil modul layout pertama untuk perhitungan rasio
|
||||
var certificate_layout_module = $('.certificate_builder_view .certificate-layout-module').first().width();
|
||||
|
||||
var certificate_width = $('#captureCertificate > div').width();
|
||||
html2canvas(document.querySelector("#captureCertificate > div"), {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
width: certificate_width,
|
||||
scale: 2
|
||||
}).then(canvas => {
|
||||
document.querySelector("#captureCertificate").appendChild(canvas);
|
||||
$("canvas").hide();
|
||||
|
||||
setTimeout(function() {
|
||||
var canvas = document.querySelector("canvas");
|
||||
downloadCanvas(canvas, "certificate.png");
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function downloadCanvas(canvas, filename) {
|
||||
// Convert canvas to data URL
|
||||
var dataUrl = canvas.toDataURL("image/png");
|
||||
|
||||
// Create a temporary link element
|
||||
var link = document.createElement("a");
|
||||
link.href = dataUrl;
|
||||
link.download = filename;
|
||||
|
||||
// Append the link to the body
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Create a new MouseEvent and dispatch it
|
||||
var event = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
link.dispatchEvent(event);
|
||||
|
||||
// Remove the link from the body
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
var certificate_builder_view_width = $('.certificate_builder_view').width();
|
||||
var certificate_layout_module = $('.certificate_builder_view .certificate-layout-module').width();
|
||||
var zoomScaleValue = ((certificate_builder_view_width / certificate_layout_module) * 100) - 8;
|
||||
$('.certificate_builder_view .certificate-layout-module').css('zoom', zoomScaleValue + '%');
|
||||
});
|
||||
|
||||
function renderCanvasToPDF() {
|
||||
const target = document.querySelector("#captureCertificate > div");
|
||||
|
||||
html2canvas(target, {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
scale: 2
|
||||
}).then(canvas => {
|
||||
|
||||
const imgData = canvas.toDataURL("image/png");
|
||||
|
||||
// A4 Landscape
|
||||
const pdf = new window.jspdf.jsPDF({
|
||||
orientation: 'landscape',
|
||||
unit: 'mm',
|
||||
format: 'a4'
|
||||
if(certificate_layout_module > 0) {
|
||||
var zoomScaleValue = ((certificate_builder_view_width / certificate_layout_module) * 100) - 8;
|
||||
// Terapkan zoom ke SEMUA layout module
|
||||
$('.certificate_builder_view .certificate-layout-module').css('zoom', zoomScaleValue + '%');
|
||||
}
|
||||
});
|
||||
|
||||
const pageWidth = pdf.internal.pageSize.getWidth();
|
||||
const pageHeight = pdf.internal.pageSize.getHeight();
|
||||
// Fungsi Gambar (Download PNG) - Hanya mengambil halaman pertama atau gabungan (opsional)
|
||||
// Disarankan default mengambil halaman utama saja untuk PNG, atau biarkan custom
|
||||
function renderCanvasToImage() {
|
||||
// Mengambil elemen halaman pertama saja untuk PNG thumbnail
|
||||
var elementToCapture = document.querySelector("#captureCertificate .page-1");
|
||||
var certificate_width = $(elementToCapture).width();
|
||||
|
||||
// Hitung rasio agar tidak terpotong
|
||||
const imgWidth = pageWidth;
|
||||
const imgHeight = canvas.height * imgWidth / canvas.width;
|
||||
html2canvas(elementToCapture, {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
width: certificate_width,
|
||||
scale: 3
|
||||
}).then(canvas => {
|
||||
// Logic download existing
|
||||
var link = document.createElement('a');
|
||||
link.download = 'certificate.png';
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
|
||||
const positionY = (pageHeight - imgHeight) / 2;
|
||||
async function renderCanvasToPDF() {
|
||||
const pages = document.querySelectorAll("#captureCertificate .certificate-page");
|
||||
if (pages.length === 0) return;
|
||||
|
||||
pdf.addImage(
|
||||
imgData,
|
||||
'PNG',
|
||||
0,
|
||||
positionY,
|
||||
imgWidth,
|
||||
imgHeight,
|
||||
undefined,
|
||||
'FAST'
|
||||
);
|
||||
const pdf = new window.jspdf.jsPDF({
|
||||
orientation: 'landscape',
|
||||
unit: 'mm',
|
||||
format: 'a4'
|
||||
});
|
||||
|
||||
pdf.save("certificate.pdf");
|
||||
});
|
||||
}
|
||||
const pageWidth = pdf.internal.pageSize.getWidth(); // 297 mm
|
||||
const pageHeight = pdf.internal.pageSize.getHeight(); // 210 mm
|
||||
|
||||
</script>
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const pageElement = pages[i];
|
||||
|
||||
const canvas = await html2canvas(pageElement, {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
scale: 2, // cukup tajam, jangan kebesaran
|
||||
backgroundColor: '#ffffff'
|
||||
});
|
||||
|
||||
const imgData = canvas.toDataURL("image/png");
|
||||
|
||||
if (i > 0) pdf.addPage();
|
||||
|
||||
// 🔥 FULL PAGE – TANPA WHITESPACE
|
||||
pdf.addImage(
|
||||
imgData,
|
||||
'PNG',
|
||||
0,
|
||||
0,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
undefined,
|
||||
'FAST'
|
||||
);
|
||||
}
|
||||
|
||||
const courseTitle = {!! json_encode($course_title) !!};
|
||||
const fileName = `Grownesesa Certificate - ${courseTitle}.pdf`;
|
||||
pdf.save(fileName);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
{{ get_phrase('Section') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section">
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section" required>
|
||||
<option value="">{{ get_phrase('Select an option') }}</option>
|
||||
@foreach (App\Models\Section::where('course_id', $id)->get() as $section)
|
||||
<option value="{{ $section->id }}">{{ $section->title }}</option>
|
||||
@ -37,9 +37,9 @@
|
||||
</div>
|
||||
|
||||
<div class="fpb-7 mb-6">
|
||||
<label for="attachment"
|
||||
<label for="summary"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Summary') }}</label>
|
||||
<textarea name="attachment" rows="5" class="form-control ol-form-control text_editor"></textarea>
|
||||
<textarea name="summary" rows="5" class="form-control ol-form-control text_editor"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="fpb7">
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
@endphp
|
||||
|
||||
<form action="{{ route('instructor.course.project.update', $id) }}" method="post">@csrf
|
||||
|
||||
{{-- FIX: Pass the course_id back to the controller --}}
|
||||
<input type="hidden" name="course_id" value="{{ $project->course_id }}">
|
||||
|
||||
<div class="fpb7 mb-3">
|
||||
<label class="form-label ol-form-label" for="title">
|
||||
{{ get_phrase('Title') }}
|
||||
@ -22,7 +26,7 @@
|
||||
{{ get_phrase('Section') }}
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section">
|
||||
<select class="form-control ol-form-control ol-select2" data-toggle="select2" name="section" required>
|
||||
<option value="">{{ get_phrase('Select an option') }}</option>
|
||||
@foreach (App\Models\Section::where('course_id', $project->course_id)->get() as $section)
|
||||
<option value="{{ $section->id }}" @if ($section->id == $project->section_id) selected @endif>
|
||||
@ -41,6 +45,7 @@
|
||||
<div class="fpb-7 mb-3">
|
||||
<label for="attachment"
|
||||
class="form-label ol-form-label col-form-label">{{ get_phrase('Assessment Criteria') }}</label>
|
||||
{{-- Ensure your Controller handles $request->attachment --}}
|
||||
<textarea name="attachment" rows="5" class="form-control ol-form-control text_editor">{!! $project->attachment !!}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\MessageController;
|
||||
use App\Http\Controllers\Admin\OfflinePaymentController;
|
||||
use App\Http\Controllers\Admin\OpenAiController;
|
||||
use App\Http\Controllers\Admin\PageBuilderController;
|
||||
use App\Http\Controllers\Admin\ProjectController;
|
||||
use App\Http\Controllers\Admin\QuestionController;
|
||||
use App\Http\Controllers\Admin\QuizController;
|
||||
use App\Http\Controllers\Admin\TeamTrainingController;
|
||||
@ -256,8 +257,11 @@ Route::name('admin.')->prefix('admin')->middleware('admin')->group(function () {
|
||||
// Certificate settings
|
||||
Route::get('certificate_settings', 'certificate')->name('certificate.settings');
|
||||
Route::post('certificate/update/template', 'certificate_update_template')->name('certificate.update.template');
|
||||
Route::post('certificate/update/template/details', 'certificate_update_template_details')->name('certificate.update.template.details');
|
||||
Route::get('certificate/builder', 'certificate_builder')->name('certificate.builder');
|
||||
Route::post('certificate/builder/update', 'certificate_builder_update')->name('certificate.certificate.builder.update');
|
||||
Route::get('certificate/builder_details', 'certificate_builder_details')->name('certificate.builder_details');
|
||||
Route::post('certificate/builder/update/details', 'certificate_builder_details_update')->name('certificate.builder.details.update');
|
||||
|
||||
// Admin User Review Add
|
||||
Route::get('user/review', 'user_review_add')->name('review.create');
|
||||
@ -356,6 +360,16 @@ Route::name('admin.')->prefix('admin')->middleware('admin')->group(function () {
|
||||
Route::get('quiz/result/preview', 'result_preview')->name('quiz.result.preview');
|
||||
});
|
||||
|
||||
// course project
|
||||
Route::controller(ProjectController::class)->group(function () {
|
||||
Route::post('course/project/store', 'store')->name('course.project.store');
|
||||
Route::post('course/project/update/{id}', 'update')->name('course.project.update');
|
||||
Route::get('/project/grading/preview', 'getPreview')->name('project.grading.preview');
|
||||
Route::get('/project/grading/{id}', 'getSubmissions')->name('project.grading.index');
|
||||
Route::get('/project/participant/submission', 'getParticipantSubmission')->name('project.participant.submission');
|
||||
Route::post('/project/grading/update/{id}', 'updateSubmission')->name('project.grading.update');
|
||||
});
|
||||
|
||||
// question route
|
||||
Route::controller(QuestionController::class)->group(function () {
|
||||
Route::post('course/question/store', 'store')->name('course.question.store');
|
||||
|
||||
@ -82,7 +82,7 @@ Route::name('instructor.')->prefix('instructor')->middleware(['instructor'])->gr
|
||||
Route::post('course/project/store', 'store')->name('course.project.store');
|
||||
Route::post('course/project/update/{id}', 'update')->name('course.project.update');
|
||||
Route::get('/project/grading/preview', 'getPreview')->name('project.grading.preview');
|
||||
Route::get('/project/grading/{id}', 'getIndex')->name('project.grading.index');
|
||||
Route::get('/project/grading/{id}', 'getSubmissions')->name('project.grading.index');
|
||||
Route::get('/project/participant/submission', 'getParticipantSubmission')->name('project.participant.submission');
|
||||
Route::post('/project/grading/update/{id}', 'updateSubmission')->name('project.grading.update');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user