add certificate disbale function
All checks were successful
Build & Push Docker Image / docker (push) Successful in 1m49s

This commit is contained in:
Ahmed Darrazi 2025-12-19 16:30:50 +01:00
parent cbf39f2feb
commit 368a49fb0c
10 changed files with 229 additions and 71 deletions

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Certificate\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
class CertificationSettingsController extends Controller
{
public function update(Request $request)
{
$validated = $request->validate([
'show_course_certificate' => ['sometimes', 'boolean'],
'show_course_marksheet' => ['sometimes', 'boolean'],
]);
$system = Setting::where('type', 'system')->firstOrFail();
$fields = $system->fields ?? [];
foreach ($validated as $key => $value) {
$fields[$key] = (bool) $value;
}
$system->update(['fields' => $fields]);
return back()->with('success', 'Certification settings updated successfully.');
}
}

View File

@ -2,12 +2,15 @@
use Illuminate\Support\Facades\Route;
use Modules\Certificate\Http\Controllers\CertificateController;
use Modules\Certificate\Http\Controllers\CertificationSettingsController;
use Modules\Certificate\Http\Controllers\CertificateTemplateController;
use Modules\Certificate\Http\Controllers\MarksheetTemplateController;
Route::middleware(['auth', 'verified'])->group(function () {
// Admin routes for managing certificate templates
Route::middleware(['role:admin'])->prefix('dashboard/certification')->group(function () {
Route::post('settings', [CertificationSettingsController::class, 'update'])->name('certification.settings.update');
Route::resource('certificate', CertificateTemplateController::class)->except(['show', 'create', 'update'])->names('certificate.templates');
Route::get('certificate/create-certificate', [CertificateTemplateController::class, 'create'])->name('certificate.templates.create');
Route::post('certificate/{id}', [CertificateTemplateController::class, 'update'])->name('certificate.templates.update');

View File

@ -60,6 +60,15 @@ class StudentController extends Controller
public function show_course(int $id, string $tab)
{
$system = app('system_settings');
$fields = $system?->fields ?? [];
$showCourseCertificate = $fields['show_course_certificate'] ?? true;
$showCourseMarksheet = $fields['show_course_marksheet'] ?? true;
if ($tab === 'certificate' && !$showCourseCertificate && !$showCourseMarksheet) {
return redirect()->route('student.course.show', ['id' => $id, 'tab' => 'modules']);
}
$user = Auth::user();
$course = $this->studentService->getEnrolledCourse($id, $user);
$props = $this->studentService->getEnrolledCourseOverview($id, $tab, $user);

View File

@ -185,15 +185,20 @@ class StudentService extends MediaService
function getEnrolledCourseOverview(string $course_id, string $tab, User $user): array
{
$system = app('system_settings');
$fields = $system?->fields ?? [];
$showCourseCertificate = $fields['show_course_certificate'] ?? true;
$showCourseMarksheet = $fields['show_course_marksheet'] ?? true;
return [
'modules' => $tab === 'modules' ? $this->getCourseModules($course_id) : null,
'live_classes' => $tab === 'live_classes' ? $this->getCourseLiveClasses($course_id) : null,
'assignments' => $tab === 'assignments' ? $this->getCourseAssignments($course_id, $user) : null,
'quizzes' => $tab === 'quizzes' ? $this->getCourseSectionQuizzes($course_id, $user) : null,
'resources' => $tab === 'resources' ? $this->getCourseLessonResources($course_id) : null,
'certificateTemplate' => $tab === 'certificate' ? $this->certificate->getActiveCertificateTemplate('course') : null,
'marksheetTemplate' => $tab === 'certificate' ? $this->certificate->getActiveMarksheetTemplate('course') : null,
'studentMarks' => $tab === 'certificate' ? $this->calculateStudentMarks($course_id, $user->id) : null,
'certificateTemplate' => $tab === 'certificate' && $showCourseCertificate ? $this->certificate->getActiveCertificateTemplate('course') : null,
'marksheetTemplate' => $tab === 'certificate' && $showCourseMarksheet ? $this->certificate->getActiveMarksheetTemplate('course') : null,
'studentMarks' => $tab === 'certificate' && $showCourseMarksheet ? $this->calculateStudentMarks($course_id, $user->id) : null,
];
}

View File

@ -27,8 +27,9 @@ interface ContentListProps {
const ContentList = ({ completedContents, courseCompletion }: ContentListProps) => {
const { props } = usePage<CoursePlayerProps>();
const { course, zoomConfig, section, watchHistory, translate, direction } = props;
const { course, zoomConfig, section, watchHistory, translate, direction, system } = props;
const { button, common, frontend } = translate;
const showCourseCertificate = system?.fields?.show_course_certificate ?? true;
// Get live classes from course data
const liveClasses = course.live_classes || [];
@ -115,7 +116,7 @@ const ContentList = ({ completedContents, courseCompletion }: ContentListProps)
<Link
href={route('student.course.show', {
id: course.id,
tab: 'certificate',
tab: showCourseCertificate ? 'certificate' : 'modules',
})}
>
<Button className="w-full" variant="secondary" size="lg" disabled={courseCompletion.percentage !== '100.00'}>

View File

@ -1,24 +1,61 @@
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import DashboardLayout from '@/layouts/dashboard/layout';
import { SharedData } from '@/types/global';
import { Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import { Award, Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import CertificateCard from './partials/certificate-card';
interface CertificatePageProps extends SharedData {
templates: CertificateTemplate[];
}
const CertificateIndex = ({ templates }: CertificatePageProps) => {
const CertificateIndex = ({ templates, system }: CertificatePageProps) => {
const examTemplates = templates.filter((template) => template.type === 'exam');
const courseTemplates = templates.filter((template) => template.type === 'course');
const initialCourseCertificateEnabled = system?.fields?.show_course_certificate ?? true;
const [courseCertificateEnabled, setCourseCertificateEnabled] = useState<boolean>(initialCourseCertificateEnabled);
useEffect(() => {
setCourseCertificateEnabled(initialCourseCertificateEnabled);
}, [initialCourseCertificateEnabled]);
const handleCourseCertificateToggle = (checked: boolean) => {
const previous = courseCertificateEnabled;
setCourseCertificateEnabled(checked);
router.post(
route('certification.settings.update'),
{ show_course_certificate: checked },
{ preserveScroll: true, preserveState: true, onError: () => setCourseCertificateEnabled(previous) },
);
};
return (
<>
<Head title="Certificate Templates" />
<div className="space-y-6">
<Card className="p-4">
<div className="flex items-center justify-between gap-4">
<div>
<Label htmlFor="show_course_certificate" className="text-base font-semibold">
Course Certificate (Student)
</Label>
<p className="text-muted-foreground text-sm">Enable/disable course certificates for students.</p>
</div>
<Switch
id="show_course_certificate"
checked={courseCertificateEnabled}
onCheckedChange={handleCourseCertificateToggle}
/>
</div>
</Card>
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold">Certificate Templates</h2>

View File

@ -1,24 +1,57 @@
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import DashboardLayout from '@/layouts/dashboard/layout';
import { SharedData } from '@/types/global';
import { Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import { ClipboardList, Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import MarkSheetCard from './partials/marksheet-card';
interface MarksheetPageProps extends SharedData {
templates: MarksheetTemplate[];
}
const MarksheetIndex = ({ templates }: MarksheetPageProps) => {
const MarksheetIndex = ({ templates, system }: MarksheetPageProps) => {
const examTemplates = templates.filter((template) => template.type === 'exam');
const courseTemplates = templates.filter((template) => template.type === 'course');
const initialCourseMarksheetEnabled = system?.fields?.show_course_marksheet ?? true;
const [courseMarksheetEnabled, setCourseMarksheetEnabled] = useState<boolean>(initialCourseMarksheetEnabled);
useEffect(() => {
setCourseMarksheetEnabled(initialCourseMarksheetEnabled);
}, [initialCourseMarksheetEnabled]);
const handleCourseMarksheetToggle = (checked: boolean) => {
const previous = courseMarksheetEnabled;
setCourseMarksheetEnabled(checked);
router.post(
route('certification.settings.update'),
{ show_course_marksheet: checked },
{ preserveScroll: true, preserveState: true, onError: () => setCourseMarksheetEnabled(previous) },
);
};
return (
<>
<Head title="Certificate & Marksheet Templates" />
<div className="space-y-6">
<Card className="p-4">
<div className="flex items-center justify-between gap-4">
<div>
<Label htmlFor="show_course_marksheet" className="text-base font-semibold">
Course Marksheet (Student)
</Label>
<p className="text-muted-foreground text-sm">Enable/disable course marksheets for students.</p>
</div>
<Switch id="show_course_marksheet" checked={courseMarksheetEnabled} onCheckedChange={handleCourseMarksheetToggle} />
</div>
</Card>
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold">Marksheet Templates</h2>

View File

@ -15,7 +15,11 @@ import CourseQuizzes from './tabs-content/course-quizzes';
import CourseResources from './tabs-content/course-resources';
const Course = (props: StudentCourseProps) => {
const { tab, course, watchHistory, completion } = props;
const { tab, course, watchHistory, completion, system } = props;
const showCourseCertificate = system?.fields?.show_course_certificate ?? true;
const showCourseMarksheet = system?.fields?.show_course_marksheet ?? true;
const showCertificateTab = showCourseCertificate || showCourseMarksheet;
const tabs = [
{
@ -38,10 +42,14 @@ const Course = (props: StudentCourseProps) => {
value: 'resources',
label: 'Ressourcen',
},
{
value: 'certificate',
label: 'Zertifikat',
},
...(showCertificateTab
? [
{
value: 'certificate',
label: 'Zertifikat',
},
]
: []),
];
const renderContent = () => {

View File

@ -10,18 +10,34 @@ import { Award, ClipboardList, Lock } from 'lucide-react';
const CourseCertificate = () => {
const { props } = usePage<StudentCourseProps>();
const { course, watchHistory, completion, certificateTemplate, marksheetTemplate, studentMarks, auth } = props;
const { course, watchHistory, completion, certificateTemplate, marksheetTemplate, studentMarks, auth, system } = props;
const showCourseCertificate = system?.fields?.show_course_certificate ?? true;
const showCourseMarksheet = system?.fields?.show_course_marksheet ?? true;
if (!showCourseCertificate && !showCourseMarksheet) {
return (
<Alert>
<Lock className="h-4 w-4" />
<AlertTitle>Zertifikatbereich deaktiviert</AlertTitle>
<AlertDescription>Für diesen Kurs sind Zertifikat und Notenblatt aktuell deaktiviert.</AlertDescription>
</Alert>
);
}
// Check if course is completed
const isCompleted = watchHistory?.completion_date || completion?.completion === 100;
if (!isCompleted) {
const title = showCourseCertificate && showCourseMarksheet ? 'Zertifikat & Notenblatt gesperrt' : showCourseCertificate ? 'Zertifikat gesperrt' : 'Notenblatt gesperrt';
const unlockText = showCourseCertificate && showCourseMarksheet ? 'Schließe alle Kursmodule ab, um dein Zertifikat und Notenblatt freizuschalten.' : showCourseCertificate ? 'Schließe alle Kursmodule ab, um dein Zertifikat freizuschalten.' : 'Schließe alle Kursmodule ab, um dein Notenblatt freizuschalten.';
return (
<Alert>
<Lock className="h-4 w-4" />
<AlertTitle>Zertifikat & Notenblatt gesperrt</AlertTitle>
<AlertTitle>{title}</AlertTitle>
<AlertDescription>
Schließe alle Kursmodule ab, um dein Zertifikat und Notenblatt freizuschalten. Dein aktueller Fortschritt: {completion?.completion || 0}%
{unlockText} Dein aktueller Fortschritt: {completion?.completion || 0}%
</AlertDescription>
</Alert>
);
@ -32,14 +48,20 @@ const CourseCertificate = () => {
: format(new Date(), 'MMMM d, yyyy');
// Check if both are unavailable
if (!certificateTemplate && !marksheetTemplate) {
const certificateAvailable = showCourseCertificate && !!certificateTemplate;
const marksheetAvailable = showCourseMarksheet && !!marksheetTemplate;
if (!certificateAvailable && !marksheetAvailable) {
const title = showCourseCertificate && showCourseMarksheet ? 'Kein Zertifikat oder Notenblatt verfügbar' : showCourseCertificate ? 'Kein Zertifikat verfügbar' : 'Kein Notenblatt verfügbar';
const description = showCourseCertificate && showCourseMarksheet ? 'Der Dozent hat für diesen Kurs noch keine Zertifikate oder Notenblätter eingerichtet.' : showCourseCertificate ? 'Der Dozent hat für diesen Kurs noch keine Zertifikate eingerichtet.' : 'Der Dozent hat für diesen Kurs noch keine Notenblätter eingerichtet.';
return (
<div className="p-6">
<Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center">
<Award className="text-muted-foreground mb-4 h-16 w-16" />
<h3 className="mb-2 text-xl font-semibold">Kein Zertifikat oder Notenblatt verfügbar</h3>
<p className="text-muted-foreground">Der Dozent hat für diesen Kurs noch keine Zertifikate oder Notenblätter eingerichtet.</p>
<h3 className="mb-2 text-xl font-semibold">{title}</h3>
<p className="text-muted-foreground">{description}</p>
</CardContent>
</Card>
</div>
@ -48,60 +70,68 @@ const CourseCertificate = () => {
return (
<div>
<Tabs defaultValue="certificate" className="w-full">
<TabsList className="mb-6 grid h-11 w-full grid-cols-2">
<TabsTrigger value="certificate" className="flex h-9 cursor-pointer items-center gap-2">
<Award className="h-4 w-4" />
Zertifikat
</TabsTrigger>
<TabsTrigger value="marksheet" className="flex h-9 cursor-pointer items-center gap-2">
<ClipboardList className="h-4 w-4" />
Notenblatt
</TabsTrigger>
<Tabs defaultValue={showCourseCertificate ? 'certificate' : 'marksheet'} className="w-full">
<TabsList className={`mb-6 grid h-11 w-full ${showCourseCertificate && showCourseMarksheet ? 'grid-cols-2' : 'grid-cols-1'}`}>
{showCourseCertificate && (
<TabsTrigger value="certificate" className="flex h-9 cursor-pointer items-center gap-2">
<Award className="h-4 w-4" />
Zertifikat
</TabsTrigger>
)}
{showCourseMarksheet && (
<TabsTrigger value="marksheet" className="flex h-9 cursor-pointer items-center gap-2">
<ClipboardList className="h-4 w-4" />
Notenblatt
</TabsTrigger>
)}
</TabsList>
<TabsContent value="certificate">
{!certificateTemplate ? (
<Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center">
<Award className="text-muted-foreground mb-4 h-16 w-16" />
<h3 className="mb-2 text-xl font-semibold">Kein Zertifikat verfügbar</h3>
<p className="text-muted-foreground">Der Dozent hat für diesen Kurs noch keine Zertifikate eingerichtet.</p>
</CardContent>
</Card>
) : (
<DynamicCertificate
template={certificateTemplate}
courseName={course.title}
studentName={auth.user.name}
completionDate={completionDate}
/>
)}
</TabsContent>
{showCourseCertificate && (
<TabsContent value="certificate">
{!certificateTemplate ? (
<Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center">
<Award className="text-muted-foreground mb-4 h-16 w-16" />
<h3 className="mb-2 text-xl font-semibold">Kein Zertifikat verfügbar</h3>
<p className="text-muted-foreground">Der Dozent hat für diesen Kurs noch keine Zertifikate eingerichtet.</p>
</CardContent>
</Card>
) : (
<DynamicCertificate
template={certificateTemplate}
courseName={course.title}
studentName={auth.user.name}
completionDate={completionDate}
/>
)}
</TabsContent>
)}
<TabsContent value="marksheet">
{!marksheetTemplate || !studentMarks ? (
<Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center">
<ClipboardList className="text-muted-foreground mb-4 h-16 w-16" />
<h3 className="mb-2 text-xl font-semibold">Kein Notenblatt verfügbar</h3>
<p className="text-muted-foreground">
{!marksheetTemplate
? 'Der Dozent hat für diesen Kurs noch keine Notenblätter eingerichtet.'
: 'Keine Notendaten verfügbar. Schließe Aufgaben und Tests ab, um dein Notenblatt zu sehen.'}
</p>
</CardContent>
</Card>
) : (
<DynamicMarksheet
template={marksheetTemplate}
courseName={course.title}
studentName={auth.user.name}
completionDate={completionDate}
studentMarks={studentMarks}
/>
)}
</TabsContent>
{showCourseMarksheet && (
<TabsContent value="marksheet">
{!marksheetTemplate || !studentMarks ? (
<Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center">
<ClipboardList className="text-muted-foreground mb-4 h-16 w-16" />
<h3 className="mb-2 text-xl font-semibold">Kein Notenblatt verfügbar</h3>
<p className="text-muted-foreground">
{!marksheetTemplate
? 'Der Dozent hat für diesen Kurs noch keine Notenblätter eingerichtet.'
: 'Keine Notendaten verfügbar. Schließe Aufgaben und Tests ab, um dein Notenblatt zu sehen.'}
</p>
</CardContent>
</Card>
) : (
<DynamicMarksheet
template={marksheetTemplate}
courseName={course.title}
studentName={auth.user.name}
completionDate={completionDate}
studentMarks={studentMarks}
/>
)}
</TabsContent>
)}
</Tabs>
</div>
);

View File

@ -39,6 +39,8 @@ interface SystemFields {
show_course_cart?: boolean;
show_student_exams?: boolean;
show_student_wishlist?: boolean;
show_course_certificate?: boolean;
show_course_marksheet?: boolean;
}
interface GoogleAuthFields {