CLASSROOMIO-T-11: Developed the certificate issuing and editor page

deleted the certicate png files and some other unused png files

Merge branch 'CLASSROOMIO-T-11' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-11

deleted the certicate png files

CLASSROOMIO-T-11: More refactoring

CLASSROOMIO-T-11: More spacing and font

Merge branch 'CLASSROOMIO-T-11' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-11

CLASSROOMIO-T-11:Renamed the mockdata import,also deleted some comments and unused images

CLASSROOMIO-T-11:Renamed the mockdata import,also deleted some comments and unused images

CLASSROOMIO-T-11: Remove unnecessary comments

CLASSROOMIO-T-11:Fixed the merge conflicts after careful review

CLASROOMIO-T-11:Removed the upload component,made the certificate logo and name to be gotten from the organisation settings,and also used svg for the images

CLASSROOMIO-T-11: Add settings to make certificate downloadable

CLASSROOMIO-T-11: Temp disable design tab

CLASSROOMIO-T-11: Add auto send key

CLASSROOMIO-T-11: Refactoring

CLASSROOMIO-T-11:Renamed the images of the certificate theme files

CLASSROOMIO-T-11:Renamed the certificate theme files

CLASSROOMIO-T-11:fixed a bug in the certificate issuing page that prevented the application from running

CLASSROOMIO-T-11:fixed a bug in the certificate issuing page that prevented the application from running

CLASSROOMIO-T-11:Updated the file name in the certificate issuin page

CLASSROOMIO-T-11:Finished the certificate issuing page

CLASSROOMIO-T-11:Finished the certificate issuing page

CLASSROOMIO-T-47:Fixed the dark mode and then made it responsive

CLASSROOMIO-T-47:Fixed the dark mode and then made it responsive

CLASSROOMIO-T-47:fixed the dark mode and also made the it vertically aligned on mobile

CLASSROOMIO-T-47: Update nav for lms only

CLASSROOMIO-T-47: Reorder file

CLASSROOMIO-T-47:fixed the prettier config and resaved

CLASSROOMIO-T-47:Created the not found page


Co-authored-by: Ifeanyi Nwosu <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
Co-authored-by: emmanuel <[email protected]>


Merge-request: CLASSROOMIO-MR-21
Merged-by: Best Admin <[email protected]>
This commit is contained in:
emmanuel
2023-07-27 16:08:04 +00:00
committed by Space Cloud
co-authored by Ifeanyi Nwosu Rotimi Best Rotimi Best
parent a0fc775502
commit 9590b3d6d1
14 changed files with 670 additions and 1 deletions
@@ -0,0 +1,131 @@
<script>
import {
RadioButtonGroup,
RadioButton,
Toggle,
} from 'carbon-components-svelte';
import { goto } from '@sapper/app';
import PrimaryButton from '../../../PrimaryButton/index.svelte';
import TextArea from '../../../Form/TextArea.svelte';
import Professional from './certificates/Professional.svelte';
import Plain from './certificates/Plain.svelte';
import { course } from '../../store';
import { currentOrg } from '../../../../utils/store/org';
const studentNamePlaceholder = 'Name of student';
const themes = ['professional', 'plain'];
let isSaving = false;
const saveCertificate = () => {
// set isSaving to true
// Validation - make sure description isn't empty
/**
* Update course description, is_certificate_downloadable and certificate_theme (like in src/components/Course/components/Settings/index.svelte)
*
* await updateCourse($course.id, undefined, {
description: $course.description,
is_certificate_downloadable: $course.is_certificate_downloadable,
certificate_theme: $course.certificate_theme
});
* */
// set isSaving to false
};
</script>
<svelte:head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Qwitcher+Grypen&family=Roboto:wght@300;400;700&display=swap"
rel="stylesheet"
/>
</svelte:head>
<main class="md:-ml-4 md:-mr-4 px-2">
<div
class="flex-1 flex flex-col md:flex-row justify-between gap-3 w-full mb-3 h-4/5"
>
<section class="w-full md:w-2/5 h-full">
<strong class="my-2 text-base font-semibold text-black"
>Certificate settings</strong
>
<p class="text-xs font-normal my-4">Choose a theme</p>
<RadioButtonGroup bind:selected={$course.certificate_theme} class="mb-10">
{#each themes as theme}
<div class="flex mr-3">
<RadioButton value={theme} size="sm" />
<img src={`./${theme}.svg`} alt="themes" />
</div>
{/each}
</RadioButtonGroup>
<div>
<p class="text-xs font-normal text-black my-2">Brand Logo</p>
<div>
<p class="text-base mt-1">
To update your brand image, go to <strong class="font-semibold"
>Settings &gt; Organisation settings</strong
> and upload you brand logo
</p>
<PrimaryButton
label="Go to Settings"
className="rounded-md mt-3"
onClick={() => goto(`org/${$currentOrg.siteName}/settings`)}
/>
</div>
<span class="my-4">
<p class="text-xs font-normal mt-4 mb-2">Course Description</p>
<TextArea
maxRows={6}
rows={6}
placeholder="a little description about the course"
bgColor="bg-gray-100"
bind:value={$course.description}
/>
</span>
<Toggle
labelText="Allow students download certificate"
bind:toggled={$course.is_certificate_downloadable}
class="my-4"
size="sm"
>
<span slot="labelA" style="color: #161616">Locked</span>
<span slot="labelB" style="color: green">Unlocked</span>
</Toggle>
</div>
</section>
<section
class="certificate-bg flex justify-center items-center rounded-md w-full md:w-3/5"
>
<div class="certificate-container flex justify-center items-center h-5/6">
{#if $course.certificate_theme === 'professional'}
<Professional studentName={studentNamePlaceholder} />
{:else}
<Plain studentName={studentNamePlaceholder} />
{/if}
</div>
</section>
</div>
<div class="h-1/5">
<PrimaryButton
label="Save Changes"
className="rounded-md"
onClick={saveCertificate}
isLoading={isSaving}
/>
</div>
</main>
<style>
p {
color: #262626;
}
.certificate-container {
width: 95%;
max-width: 95%;
}
.certificate-bg {
background-color: #f5f8fe;
}
</style>
@@ -0,0 +1,83 @@
<script>
import { Toggle } from 'carbon-components-svelte';
import Modal from '../../../Modal/index.svelte';
import PrimaryButton from '../../../PrimaryButton/index.svelte';
import TextArea from '../../../Form/TextArea.svelte';
import TextField from '../../../Form/TextField.svelte';
import { issueCertificateModal, resetForm } from './store';
const onToggle = (e) => {
console.log(e);
};
const issueCertificate = () => {
resetForm();
};
</script>
<Modal
onClose={resetForm}
bind:open={$issueCertificateModal.open}
width="w-3/5"
maxWidth=""
modalHeading="Send Certificate"
>
<main>
<div>
<Toggle
labelText="Automatically send certificates on completion"
on:toggle={(e) => onToggle(e)}
size="sm"
class="mb-4"
>
<span slot="labelA" style="color: #161616">Automatic</span>
<span slot="labelB" style="color: green">Automatic</span>
</Toggle>
<p class="text-sm font-medium my-4">
If you set this as automatic, certificates will be issued after the
learner completes the course.
</p>
</div>
<p class="text-xs font-normal text-gray-500 my-4">
or send a personalised/ custom certificate below:
</p>
<form on:submit|preventDefault={issueCertificate}>
<div class="flex flex-col md:flex-row gap-2 w-full">
<TextField
label="Email address of the student"
className="w-full my-4"
labelClassName="text-xs font-normal"
inputClassName="text-sm placeholder:text-sm placeholder:font-medium "
placeholder="email,comma seperated"
bind:value={$issueCertificateModal.email}
/>
<TextField
label="Schedule date"
className="w-full my-4"
labelClassName="text-xs font-normal"
inputClassName="text-sm placeholder:text-sm placeholder:font-medium "
placeholder="12/06/2023"
bind:value={$issueCertificateModal.date}
/>
</div>
<TextArea
label="Add a personalized message"
labelClassName="text-xs font-normal"
bind:value={$issueCertificateModal.message}
rows="2"
maxRows="3"
placeholder="your message here"
className="mb-4"
/>
<div class="mt-5 flex w-full items-end justify-end">
<PrimaryButton
className="px-6 py-3 rounded-md text-sm font-medium"
label="Issue certificate"
type="submit"
/>
</div>
</form>
</main>
</Modal>
@@ -0,0 +1,99 @@
<script>
import { CertificateReportData, students } from './mockData';
import {
Search,
Checkbox,
StructuredList,
StructuredListHead,
StructuredListRow,
StructuredListCell,
StructuredListBody,
Pagination,
} from 'carbon-components-svelte';
let searchValue = '';
let checked = false;
</script>
<section class="flex flex-wrap items-start gap-2 mb-10 -ml-4 -mr-4">
{#each CertificateReportData as data (data.id)}
<div
class="box flex flex-col rounded border border-gray-200 justify-center px-2 mb-5"
>
<p class="text-xs font-normal">{data.title}</p>
<bold class="text-2xl font-medium">{data.value}</bold>
</div>
{/each}
</section>
<section>
<div class="flex flex-row items-center justify-between">
<p class="text-base font-semibold w-full">Certificate issued</p>
<Search
placeholder="search students"
bind:value={searchValue}
searchClass="border-b border-transparent"
/>
</div>
</section>
<section class="mt-14 border-t border-gray-300">
<StructuredList>
<StructuredListHead class="bg-blue-50 py-0">
<StructuredListRow head class="py-0">
<StructuredListCell head class="py-0">
<div
class="flex-3 flex flex-row items-center justify-start w-full text-blue-600 text-sm font-medium"
>
<Checkbox bind:checked />
<p class="text-blue-900 text-start w-full text-sm font-medium">
Name of student
</p>
</div>
</StructuredListCell>
<StructuredListCell head class="py-0">
<div
class="flex-2 items-center w-full text-blue-900 text-sm font-medium"
>
<p class="align-middle text-sm font-medium pt-1">Date issued</p>
<p class="opacity-0">personalized</p>
</div>
</StructuredListCell>
</StructuredListRow>
</StructuredListHead>
<StructuredListBody>
{#each students as item (item.id)}
<StructuredListRow label for="row-{item}">
<StructuredListCell>
<div
class="flex-3 flex flex-row items-center justify-start w-full text-sm font-medium pt-4"
>
<Checkbox />
<p class="text-start w-full text-sm font-normal">{item.name}</p>
</div>
</StructuredListCell>
<StructuredListCell>
<div class="flex-2 flex flex-row items-center gap-10 w-full">
<p class="text-sm font-normal align-middle">{item.issued}</p>
{#if item.peronalized == true}
<span class="text-center bg-blue-100 px-3 py-2 rounded-lg">
<p class="opacity-100 text-xs text-blue-600">personalized</p>
</span>
{:else}
<p class="opacity-0">personalized</p>
{/if}
</div>
</StructuredListCell>
</StructuredListRow>
{/each}
</StructuredListBody>
</StructuredList>
<Pagination totalItems={10} pageSizes={[10, 15, 20]} />
</section>
<style>
.box {
width: 246px;
height: 165px;
}
</style>
@@ -0,0 +1,58 @@
<script>
import { course } from '../../../store';
import { currentOrg } from '../../../../../utils/store/org';
export let studentName;
let borderwidth = 'border-4';
let bordercolor = 'border-pink-700';
let logo = './logo-512.png';
</script>
<div
class="flex flex-col items-center justify-center {borderwidth} {bordercolor} py-3"
>
<div class="flex items-center gap-1 my-2">
<img
src={$currentOrg.avatar_url ? $currentOrg.avatar_url : logo}
alt="logo"
class=" w-10 h-10 rounded-md"
/>
<p class="font-semibold capitalize">{$currentOrg.name}</p>
</div>
<div class="w-full px-5 py-3">
<p
class="text-sm text-center font-normal my-2 uppercase tracking-widest text-gray-500 w-full"
>
Certificate of completion
</p>
<div class="border-b border-gray-500 mb-4">
<p class="text-xs text-black font-medium">This is to certify that</p>
<p class="student-name text-center text-5xl">{studentName}</p>
</div>
<div class="mb-2">
<p class="text-xs text-black font-medium">
has successfully completed a courses in
</p>
<div>
<p
class="clamp bg-transparent text-base font-semibold text-pink-700 text-center uppercase"
>
{$course.title}
</p>
</div>
</div>
<p class="desc text-center">{$course.description}</p>
</div>
</div>
<style>
.student-name {
font-family: 'Qwitcher Grypen', cursive;
}
.desc {
font-size: 10px;
}
.clamp {
font-size: clamp(14px, 3vw, 16px);
}
</style>
@@ -0,0 +1,58 @@
<script>
import { course } from '../../../store';
import { currentOrg } from '../../../../../utils/store/org';
export let studentName;
let borderwidth = 'border-l-8';
let bordercolor = 'border-blue-800';
let logo = './logo-512.png';
</script>
<div
class="flex items-center justify-center border border-dashed border-blue-400"
>
<div class="{borderwidth} {bordercolor}">
<div class="w-full px-5 py-3">
<p class="text-sm font-normal my-2 uppercase text-gray-500">
Certificate<br /> of completion
</p>
<div class="border-b border-gray-500 mb-4">
<p class="text-xs text-black font-medium">This is to certify that</p>
<p class="student-name text-center text-5xl">{studentName}</p>
</div>
<div class="mb-2">
<p class="text-xs text-black font-medium">
has successfully completed introductory courses in
</p>
<div>
<p
class="clamp bg-transparent text-base font-semibold text-blue-800 text-center uppercase my-2"
>
{$course.title}
</p>
</div>
</div>
<p class="desc mb-4 text-justify">{$course.description}</p>
<div class="flex items-center gap-1 my-2">
<img
src={$currentOrg.avatar_url ? $currentOrg.avatar_url : logo}
alt="logo"
class="w-10 h-10 rounded-md"
/>
<p class="font-semibold capitalize">{$currentOrg.name}</p>
</div>
</div>
</div>
</div>
<style>
.student-name {
font-family: 'Qwitcher Grypen', cursive;
}
.desc {
font-size: 10px;
}
.clamp {
font-size: clamp(14px, 3vw, 16px);
}
</style>
@@ -0,0 +1,85 @@
export const CertificateReportData = [
{
id: 1,
title: 'Total students taking course',
value: 100,
},
{
id: 2,
title: 'Number of certificates issued ',
value: 45,
},
{
id: 3,
title: 'Certificates left to be issued',
value: 55,
},
{
id: 4,
title: 'Personalised cetificates',
value: 15,
},
];
export const students = [
{
id: 1,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 2,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 3,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: true,
},
{
id: 4,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 5,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 6,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 7,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 8,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 9,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: false,
},
{
id: 10,
name: 'Anyanwu joy victory',
issued: '10 Dec 2020',
peronalized: true,
},
];
@@ -0,0 +1,18 @@
import { writable } from 'svelte/store';
export const issueCertificateModal = writable({
open: false,
automatic: false,
email: '',
date: '',
message: '',
});
export const resetForm = () =>
issueCertificateModal.update(() => ({
open: false,
automatic: false,
email: '',
date: '',
message: '',
}));
@@ -125,6 +125,11 @@
to: getNavItemRoute($course.id, 'people'),
hideSortIcon: true,
},
{
label: 'Certificates',
to: getNavItemRoute($course.id, 'certificates'),
hideSortIcon: true,
},
{
label: 'Landing Page',
to: getNavItemRoute($course.id, 'landingpage'),
+6
View File
@@ -10,6 +10,8 @@ export const course: Writable<Course> = writable({
description: '',
cost: 0,
currency: '',
is_certificate_downloadable: false,
certificate_theme: 'professional',
metadata: {
requirements: '',
description: '',
@@ -128,5 +130,9 @@ export async function setCourse(data: Course, setLesson = true) {
},
};
}
if (!data.certificate_theme) {
data.certificate_theme = 'professional';
}
course.set(data);
}
+1 -1
View File
@@ -1,7 +1,7 @@
<script>
// import
// border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25
export let width = 'max-w-3xl w-full lg:w-11/12';
export let width = 'max-w-4xl w-full lg:w-11/12';
export let padding = 'pb-5 px-4';
export let className = '';
</script>
@@ -0,0 +1,67 @@
<script context="module">
import { fetchCourse } from '../../../utils/services/courses';
export async function preload({ params = { id: '' } }) {
return { courseId: params.id };
}
</script>
<script lang="ts">
import { onMount } from 'svelte';
import CourseContainer from '../../../components/CourseContainer/index.svelte';
import PageNav from '../../../components/PageNav/index.svelte';
import PageBody from '../../../components/PageBody/index.svelte';
import RoleBasedSecurity from '../../../components/RoleBasedSecurity/index.svelte';
import { setCourse, course } from '../../../components/Course/store';
import IssueCertificateModal from '../../../components/Course/components/Ceritficate/IssueCertificateModal.svelte';
// import DeleteConfirmation from '../../../components/Course/components/People/DeleteConfirmation.svelte';
import Design from '../../../components/Course/components/Ceritficate/Design.svelte';
// import Reports from '../../../components/Course/components/Ceritficate/Reports.svelte';
// import { issueCertificateModal } from '../../../components/Course/components/Ceritficate/store';
// import { Tabs, Tab } from 'carbon-components-svelte';
// import TabContent from 'carbon-components-svelte/src/Tabs/TabContent.svelte';
export let courseId: string = '';
let isStudent = false;
onMount(async () => {
if ($course.id) return;
const { data } = await fetchCourse(courseId);
setCourse(data);
});
</script>
<IssueCertificateModal />
<CourseContainer bind:isStudent>
<PageNav title="Certificates" disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<!-- Hide Functionality For V1 -->
<!-- <PrimaryButton
className="mr-2 rounded-md text-sm font-medium"
label="Issue Cert"
onClick={() => ($issueCertificateModal.open = true)}
/> -->
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
<PageBody className="w-full mx-0" padding="px-3">
<Design />
<!-- <Tabs autoWidth class="border-b border-gray-200">
<Tab label="Design" />
<Tab label="Reports" />
<svelte:fragment slot="content">
<TabContent>
<Design />
</TabContent>
Temp disable Reports tab for V2
<TabContent>
<Reports />
</TabContent>
</svelte:fragment>
</Tabs> -->
</PageBody>
</CourseContainer>
+2
View File
@@ -96,6 +96,8 @@ export interface Course {
currency?: string;
group?: Group;
organization?: Organization;
is_certificate_downloadable?: boolean;
certificate_theme?: string;
}
export interface Groupmember {
+28
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.3 MiB