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 Illuminate\Support\Facades\Route;
use Modules\Certificate\Http\Controllers\CertificateController; use Modules\Certificate\Http\Controllers\CertificateController;
use Modules\Certificate\Http\Controllers\CertificationSettingsController;
use Modules\Certificate\Http\Controllers\CertificateTemplateController; use Modules\Certificate\Http\Controllers\CertificateTemplateController;
use Modules\Certificate\Http\Controllers\MarksheetTemplateController; use Modules\Certificate\Http\Controllers\MarksheetTemplateController;
Route::middleware(['auth', 'verified'])->group(function () { Route::middleware(['auth', 'verified'])->group(function () {
// Admin routes for managing certificate templates // Admin routes for managing certificate templates
Route::middleware(['role:admin'])->prefix('dashboard/certification')->group(function () { 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::resource('certificate', CertificateTemplateController::class)->except(['show', 'create', 'update'])->names('certificate.templates');
Route::get('certificate/create-certificate', [CertificateTemplateController::class, 'create'])->name('certificate.templates.create'); Route::get('certificate/create-certificate', [CertificateTemplateController::class, 'create'])->name('certificate.templates.create');
Route::post('certificate/{id}', [CertificateTemplateController::class, 'update'])->name('certificate.templates.update'); 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) 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(); $user = Auth::user();
$course = $this->studentService->getEnrolledCourse($id, $user); $course = $this->studentService->getEnrolledCourse($id, $user);
$props = $this->studentService->getEnrolledCourseOverview($id, $tab, $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 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 [ return [
'modules' => $tab === 'modules' ? $this->getCourseModules($course_id) : null, 'modules' => $tab === 'modules' ? $this->getCourseModules($course_id) : null,
'live_classes' => $tab === 'live_classes' ? $this->getCourseLiveClasses($course_id) : null, 'live_classes' => $tab === 'live_classes' ? $this->getCourseLiveClasses($course_id) : null,
'assignments' => $tab === 'assignments' ? $this->getCourseAssignments($course_id, $user) : null, 'assignments' => $tab === 'assignments' ? $this->getCourseAssignments($course_id, $user) : null,
'quizzes' => $tab === 'quizzes' ? $this->getCourseSectionQuizzes($course_id, $user) : null, 'quizzes' => $tab === 'quizzes' ? $this->getCourseSectionQuizzes($course_id, $user) : null,
'resources' => $tab === 'resources' ? $this->getCourseLessonResources($course_id) : null, 'resources' => $tab === 'resources' ? $this->getCourseLessonResources($course_id) : null,
'certificateTemplate' => $tab === 'certificate' ? $this->certificate->getActiveCertificateTemplate('course') : null, 'certificateTemplate' => $tab === 'certificate' && $showCourseCertificate ? $this->certificate->getActiveCertificateTemplate('course') : null,
'marksheetTemplate' => $tab === 'certificate' ? $this->certificate->getActiveMarksheetTemplate('course') : null, 'marksheetTemplate' => $tab === 'certificate' && $showCourseMarksheet ? $this->certificate->getActiveMarksheetTemplate('course') : null,
'studentMarks' => $tab === 'certificate' ? $this->calculateStudentMarks($course_id, $user->id) : 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 ContentList = ({ completedContents, courseCompletion }: ContentListProps) => {
const { props } = usePage<CoursePlayerProps>(); 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 { button, common, frontend } = translate;
const showCourseCertificate = system?.fields?.show_course_certificate ?? true;
// Get live classes from course data // Get live classes from course data
const liveClasses = course.live_classes || []; const liveClasses = course.live_classes || [];
@ -115,7 +116,7 @@ const ContentList = ({ completedContents, courseCompletion }: ContentListProps)
<Link <Link
href={route('student.course.show', { href={route('student.course.show', {
id: course.id, id: course.id,
tab: 'certificate', tab: showCourseCertificate ? 'certificate' : 'modules',
})} })}
> >
<Button className="w-full" variant="secondary" size="lg" disabled={courseCompletion.percentage !== '100.00'}> <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 { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; 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 DashboardLayout from '@/layouts/dashboard/layout';
import { SharedData } from '@/types/global'; 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 { Award, Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import CertificateCard from './partials/certificate-card'; import CertificateCard from './partials/certificate-card';
interface CertificatePageProps extends SharedData { interface CertificatePageProps extends SharedData {
templates: CertificateTemplate[]; templates: CertificateTemplate[];
} }
const CertificateIndex = ({ templates }: CertificatePageProps) => { const CertificateIndex = ({ templates, system }: CertificatePageProps) => {
const examTemplates = templates.filter((template) => template.type === 'exam'); const examTemplates = templates.filter((template) => template.type === 'exam');
const courseTemplates = templates.filter((template) => template.type === 'course'); 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 ( return (
<> <>
<Head title="Certificate Templates" /> <Head title="Certificate Templates" />
<div className="space-y-6"> <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 className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-semibold">Certificate Templates</h2> <h2 className="text-xl font-semibold">Certificate Templates</h2>

View File

@ -1,24 +1,57 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; 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 DashboardLayout from '@/layouts/dashboard/layout';
import { SharedData } from '@/types/global'; 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 { ClipboardList, Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import MarkSheetCard from './partials/marksheet-card'; import MarkSheetCard from './partials/marksheet-card';
interface MarksheetPageProps extends SharedData { interface MarksheetPageProps extends SharedData {
templates: MarksheetTemplate[]; templates: MarksheetTemplate[];
} }
const MarksheetIndex = ({ templates }: MarksheetPageProps) => { const MarksheetIndex = ({ templates, system }: MarksheetPageProps) => {
const examTemplates = templates.filter((template) => template.type === 'exam'); const examTemplates = templates.filter((template) => template.type === 'exam');
const courseTemplates = templates.filter((template) => template.type === 'course'); 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 ( return (
<> <>
<Head title="Certificate & Marksheet Templates" /> <Head title="Certificate & Marksheet Templates" />
<div className="space-y-6"> <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 className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-semibold">Marksheet Templates</h2> <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'; import CourseResources from './tabs-content/course-resources';
const Course = (props: StudentCourseProps) => { 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 = [ const tabs = [
{ {
@ -38,10 +42,14 @@ const Course = (props: StudentCourseProps) => {
value: 'resources', value: 'resources',
label: 'Ressourcen', label: 'Ressourcen',
}, },
...(showCertificateTab
? [
{ {
value: 'certificate', value: 'certificate',
label: 'Zertifikat', label: 'Zertifikat',
}, },
]
: []),
]; ];
const renderContent = () => { const renderContent = () => {

View File

@ -10,18 +10,34 @@ import { Award, ClipboardList, Lock } from 'lucide-react';
const CourseCertificate = () => { const CourseCertificate = () => {
const { props } = usePage<StudentCourseProps>(); 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 // Check if course is completed
const isCompleted = watchHistory?.completion_date || completion?.completion === 100; const isCompleted = watchHistory?.completion_date || completion?.completion === 100;
if (!isCompleted) { 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 ( return (
<Alert> <Alert>
<Lock className="h-4 w-4" /> <Lock className="h-4 w-4" />
<AlertTitle>Zertifikat & Notenblatt gesperrt</AlertTitle> <AlertTitle>{title}</AlertTitle>
<AlertDescription> <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> </AlertDescription>
</Alert> </Alert>
); );
@ -32,14 +48,20 @@ const CourseCertificate = () => {
: format(new Date(), 'MMMM d, yyyy'); : format(new Date(), 'MMMM d, yyyy');
// Check if both are unavailable // 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 ( return (
<div className="p-6"> <div className="p-6">
<Card> <Card>
<CardContent className="flex flex-col items-center justify-center p-12 text-center"> <CardContent className="flex flex-col items-center justify-center p-12 text-center">
<Award className="text-muted-foreground mb-4 h-16 w-16" /> <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> <h3 className="mb-2 text-xl font-semibold">{title}</h3>
<p className="text-muted-foreground">Der Dozent hat für diesen Kurs noch keine Zertifikate oder Notenblätter eingerichtet.</p> <p className="text-muted-foreground">{description}</p>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
@ -48,18 +70,23 @@ const CourseCertificate = () => {
return ( return (
<div> <div>
<Tabs defaultValue="certificate" className="w-full"> <Tabs defaultValue={showCourseCertificate ? 'certificate' : 'marksheet'} className="w-full">
<TabsList className="mb-6 grid h-11 w-full grid-cols-2"> <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"> <TabsTrigger value="certificate" className="flex h-9 cursor-pointer items-center gap-2">
<Award className="h-4 w-4" /> <Award className="h-4 w-4" />
Zertifikat Zertifikat
</TabsTrigger> </TabsTrigger>
)}
{showCourseMarksheet && (
<TabsTrigger value="marksheet" className="flex h-9 cursor-pointer items-center gap-2"> <TabsTrigger value="marksheet" className="flex h-9 cursor-pointer items-center gap-2">
<ClipboardList className="h-4 w-4" /> <ClipboardList className="h-4 w-4" />
Notenblatt Notenblatt
</TabsTrigger> </TabsTrigger>
)}
</TabsList> </TabsList>
{showCourseCertificate && (
<TabsContent value="certificate"> <TabsContent value="certificate">
{!certificateTemplate ? ( {!certificateTemplate ? (
<Card> <Card>
@ -78,7 +105,9 @@ const CourseCertificate = () => {
/> />
)} )}
</TabsContent> </TabsContent>
)}
{showCourseMarksheet && (
<TabsContent value="marksheet"> <TabsContent value="marksheet">
{!marksheetTemplate || !studentMarks ? ( {!marksheetTemplate || !studentMarks ? (
<Card> <Card>
@ -102,6 +131,7 @@ const CourseCertificate = () => {
/> />
)} )}
</TabsContent> </TabsContent>
)}
</Tabs> </Tabs>
</div> </div>
); );

View File

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