CLASSROOMIO-T-146:Made the course page responsive

CLASSROOMIO-T-146:Made updates to the code following the reviews

CLASSROOMIO-T-146:Made updates to the Z-Index

Merge branch 'main' into CLASSROOMIO-T-146

Merge branch 'main' into CLASSROOMIO-T-146

CLASSROOMIO-T-146:Made the course page responsive

CLASSROOMIO-T-146:working on the course page responsiveness

Merge branch 'main' into CLASSROOMIO-T-145

CLASSROOMIO-T-146:Making the course page responsive

CLASSROOMIO-T-145: All done and ready

CLASSROOMIO-T-145: All done and ready

CLASSROOMIO-T-145: Fixed the Z-index issue

Merge branch 'main' into CLASSROOMIO-T-145

CLASSROOMIO-T-145: Finished implementing the responsive design for the Main dashboard and LMS dashboard

Merge branch 'main' into CLASSROOMIO-T-145

CLASSROOMIO-T-144:Updating the dashboard responsiveness ooth for LMS and the Main dashboard

CLASSROOMIO-T-144:Updated the dashboard responsiveness ooth for LMS and the Main dashboard

Merge branch 'main' into CLASSROOMIO-T-145

CLASSROOMIO-T-145:Made the LMS dashboard responsive


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


Merge-request: CLASSROOMIO-MR-73
Merged-by: Best Admin <[email protected]>
This commit is contained in:
emmanuel
2023-09-14 20:59:51 +00:00
committed by Space Cloud
parent be619ae6bd
commit f3f8a00809
24 changed files with 520 additions and 520 deletions
+5
View File
@@ -14,6 +14,11 @@ html {
#calendar .grid {
--sc-theme-calendar-colors-background-highlight: var(--main-primary-color);
}
html .dark {
--scrollbarBG: #1f2937;
}
* {
scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
+40 -3
View File
@@ -6,12 +6,15 @@
import SendAlt from 'carbon-icons-svelte/lib/SendAlt.svelte';
import ChartPie from 'carbon-icons-svelte/lib/ChartPie.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
import { apps } from './store';
import QandA from './components/QandA/index.svelte';
import LiveChat from './components/LiveChat/index.svelte';
import Notes from './components/Notes/index.svelte';
import Poll from './components/Poll/index.svelte';
import APPS_CONSTANTS from './constants';
import { OverflowMenu, OverflowMenuItem } from 'carbon-components-svelte';
let selectedApp;
@@ -48,8 +51,21 @@
});
</script>
<div class="root">
<div class="apps">
<div class={`root ${$apps.open ? 'open dark:bg-slate-800' : 'close dark:bg-slate-800'}`}>
<div class={`apps`}>
<div class="lg:hidden">
<IconButton
buttonClassName="lg:hidden"
toolTipProps={{ title: 'Settings', hotkeys: ['A', '0'] }}
onClick={() => {
handleAppClick;
$apps.dropdown = !$apps.dropdown;
}}
>
<Settings size={24} class="carbon-icon dark:text-white lg:hidden" />
</IconButton>
</div>
<IconButton
toolTipProps={{ title: 'Live Chat', hotkeys: ['A', '1'] }}
value={APPS_CONSTANTS.APPS.LIVE_CHAT}
@@ -104,7 +120,7 @@
<style lang="scss">
.root {
height: 93vh;
height: calc(100vh - 48px);
position: sticky;
top: 0;
border-left: 1px solid var(--border-color);
@@ -129,4 +145,25 @@
overflow: auto;
}
}
@media screen and (max-width: 1024px) {
.root {
position: absolute;
height: calc(100vh - 109px);
top: 109px;
right: 0;
z-index: 999;
}
.open {
position: absolute;
transform: translateX(0);
transition: all 0.2s ease-in-out;
}
.close {
position: absolute;
transform: translateX(100%);
display: none;
transition: all 0.2s ease-in-out;
}
}
</style>
+6
View File
@@ -0,0 +1,6 @@
import { writable } from 'svelte/store';
export const apps = writable({
open: false,
dropdown: false
});
@@ -45,8 +45,8 @@
</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">
<div class="flex-1 flex flex-col lg:flex-row justify-between gap-3 w-full mb-3 h-4/5">
<section class="w-full lg:w-2/5 h-full">
<strong class="my-2 text-base font-semibold text-black dark:text-gray-100"
>Certificate settings</strong
>
@@ -99,7 +99,7 @@
</div>
</section>
<section
class="bg-gray-100 dark:bg-gray-700 flex justify-center items-center rounded-md w-full md:w-3/5"
class="bg-gray-100 dark:bg-gray-700 flex justify-center items-center rounded-md w-full lg:w-3/5"
>
<div class="certificate-container flex justify-center items-center h-5/6">
{#if $course.certificate_theme === 'professional'}
@@ -55,7 +55,7 @@
<Modal
onClose={() => (openModal = false)}
bind:open={openModal}
width="w-[60%]"
width="w-[80%] md:w-[60%]"
containerClass="overflow-hidden"
modalHeading="Add New Lesson"
>
@@ -165,8 +165,8 @@
<p class="dark:text-white mt-4 text-sm">Created Jul 3, 2021</p>
</a>
{:else}
<Box className="mt-3">
<div class="flex justify-between flex-col items-center w-96">
<Box className="mt-3 text-center">
<div class="flex justify-between flex-col items-center w-[80%] md:w-96">
<img src="/images/empty-exercise-icon.svg" alt="Exercise" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No exercises added for this lesson</h2>
<p class="text-sm text-center text-slate-500">
@@ -11,13 +11,15 @@
const onChange = (tab) => () => (currentTab = tab);
</script>
<section class="w-full h-full flex flex-row items-start gap-3">
<div class="flex-1 flex-col">
<p class="text-sm font-normal text-start text-[#4F4B4B] dark:text-[#b0a9a9] mb-3">Add By</p>
<section class="w-full h-full flex flex-col md:flex-row items-start gap-3">
<div class="flex flex-row md:flex-col gap-2 items-center">
<p class="text-sm font-normal text-start text-[#4F4B4B] dark:text-[#b0a9a9] mb-3 w-full">
Add By
</p>
{#each tabs as item (item.value)}
<button
on:click={onChange(item.value)}
class={`w-full px-4 py-3 my-5 border ${
class={`w-full px-4 py-3 my-1 border ${
currentTab === item.value
? 'border border-[#0233BD] bg-[#F5F8FE] dark:text-black'
: 'border border-gray-200'
@@ -197,13 +197,13 @@
<Modal
{onClose}
bind:open={$uploadCourseVideoStore.isModalOpen}
width="w-4/5 h-[566px]"
width="w-4/5 w-[90%] h-[80%] md:h-[566px]"
modalHeading="Add a Video"
>
<VideoUploader {lessonId} />
</Modal>
<h1 class="text-4xl mt-0">
<h1 class="text-4xl mt-0 capitalize">
{lessonTitle}
</h1>
@@ -386,7 +386,7 @@
</article>
{/if}
{:else}
<Box>
<Box className="text-center">
<img src="/no-video.svg" alt="Video not found" />
<h3 class="text-xl font-normal dark:text-white py-2">
No note, video or slide added for this lesson yet
@@ -24,6 +24,7 @@
import { updateCourse } from '$lib/utils/services/courses';
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { isMobile } from '$lib/utils/store/useMobile';
import { menu } from '$lib/components/Org/store';
// export let lessonId;
export let path;
@@ -32,12 +33,14 @@
let show = null;
let isLessonActive = false;
const toggleSidebar = () => {
$menu.hidden = !$menu.hidden;
};
function handleMainGroupClick(href) {
return () => {
if ($isMobile) {
setTimeout(() => (show = false), 500);
}
goto(href);
toggleSidebar();
};
}
@@ -146,145 +149,66 @@
<Settings />
<div class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}">
{#if show}
<div class="relative h-full dark:bg-gray-800">
<!-- <PageNav title="Back to courses" paddingClass="pl-2" titleSize="text-md">
<slot:fragment slot="image">
<Avatar src={$course.logo} />
<IconButton
value="toggle"
onClick={() => goto(`${$currentOrgPath}/courses`)}
size="small"
contained={true}
<aside
class={`${
$menu.hidden
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-[40]'
: 'translate-x-0 absolute md:relative z-[40]'
} transition w-[250px] min-w-[250px] bg-gray-100 dark:bg-gray-800 h-[calc(100vh-48px)] border border-l-0 border-t-0 border-b-0 border-r-1`}
>
<div class="h-full flex flex-col">
<ul class="my-5">
{#each navItems as navItem}
{#if !navItem.show || (typeof navItem.show === 'function' && navItem.show())}
<NavExpandable
label={navItem.label}
handleClick={handleMainGroupClick(navItem.to)}
isGroupActive={(path || $page.url.pathname) === navItem.to}
isExpanded={isLessonActive}
total={navItem.isLecture ? ($lessons || []).length : 0}
>
<ArrowLeftIcon size={20} class="carbon-icon dark:text-white" />
</IconButton>
</slot:fragment>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<IconButton onClick={() => ($settingsDialog.open = !$settingsDialog.open)}>
<SettingsIcon size={20} class="carbon-icon dark:text-white" />
</IconButton>
</RoleBasedSecurity>
</slot:fragment>
</PageNav> -->
<div class="h-full py-3 overflow-y-auto">
{#each navItems as navItem}
{#if !navItem.show || (typeof navItem.show === 'function' && navItem.show())}
<NavExpandable
label={navItem.label}
handleClick={handleMainGroupClick(navItem.to)}
isGroupActive={(path || $page.url.pathname) === navItem.to}
isExpanded={isLessonActive}
total={navItem.isLecture ? ($lessons || []).length : 0}
>
{#if navItem.isLecture}
{#each $lessons as item, index}
<a
class="pl-7 w-[95%] text-[0.80rem] mb-1 text-black dark:text-white {isStudent &&
!item.is_unlocked
? 'cursor-not-allowed'
: ''}"
href={isStudent && !item.is_unlocked
? $page.url.pathname
: getLessonsRoute($course.id, item.id)}
on:click={() => {
if ($isMobile) {
setTimeout(() => (show = false), 500);
}
}}
aria-disabled={!item.is_unlocked}
{#if navItem.isLecture}
{#each $lessons as item, index}
<a
class="pl-7 w-[95%] text-[0.80rem] mb-1 text-black dark:text-white {isStudent &&
!item.is_unlocked
? 'cursor-not-allowed'
: ''}"
href={isStudent && !item.is_unlocked
? $page.url.pathname
: getLessonsRoute($course.id, item.id)}
on:click={toggleSidebar}
aria-disabled={!item.is_unlocked}
>
<div
class="flex items-center py-3 px-4 {NavClasses.item} {(
path || $page.url.pathname
).includes(item.id) && NavClasses.active}"
>
<div
class="flex items-center py-3 px-4 {NavClasses.item} {(
path || $page.url.pathname
).includes(item.id) && NavClasses.active}"
>
<TextChip
value={getLectureNo(index + 1)}
className="bg-primary-200 text-primary-600 text-xs mr-2"
size="sm"
shape="rounded-full"
/>
<span>{item.title}</span>
<span class="grow" />
{#if !item.is_unlocked}
<span class="text-md ml-2" title="This lesson is locked.">
<LockedIcon class="carbon-icon dark:text-white" />
</span>
{:else if item.is_complete}
<span class="ml-2" title="You have completed this lesson">
<CheckmarkFilled class="carbon-icon dark:text-white" />
</span>
{/if}
</div>
</a>
{/each}
{/if}
</NavExpandable>
{/if}
{/each}
</div>
</div>
{/if}
<div class="toggler rounded-full shadow-lg absolute bottom-0">
<IconButton
value="toggle"
onClick={() => (show = !show)}
size={$isMobile ? 'large' : 'small'}
color="text-black"
toolTipProps={$isMobile
? {}
: {
title: 'Toggle sidebar',
hotkeys: ['B'],
direction: 'right'
}}
>
{#if show}
<ChevronLeftIcon size={16} />
{:else}
<ChevronRightIcon size={16} />
{/if}
</IconButton>
<TextChip
value={getLectureNo(index + 1)}
className="bg-primary-200 text-primary-600 text-xs mr-2"
size="sm"
shape="rounded-full"
/>
<span>{item.title}</span>
<span class="grow" />
{#if !item.is_unlocked}
<span class="text-md ml-2" title="This lesson is locked.">
<LockedIcon class="carbon-icon dark:text-white" />
</span>
{:else if item.is_complete}
<span class="ml-2" title="You have completed this lesson">
<CheckmarkFilled class="carbon-icon dark:text-white" />
</span>
{/if}
</div>
</a>
{/each}
{/if}
</NavExpandable>
{/if}
{/each}
</ul>
</div>
</div>
<style lang="scss">
.root {
height: calc(100vh - 48px);
display: flex;
flex-direction: column;
min-width: 360px;
max-width: 360px;
top: 0;
border-right: 1px solid var(--border-color);
background-color: rgb(250, 251, 252);
&.hide {
min-width: 15px;
max-width: 15px;
}
}
.footer {
border-top: 1px solid var(--border-color);
}
.toggler {
right: -15px;
z-index: 10;
border: 1px solid var(--border-color);
top: 50px;
height: fit-content;
background: var(--border-color);
}
@media (max-width: 760px) {
.root {
width: 98%;
min-width: unset;
max-width: unset;
}
}
</style>
</aside>
@@ -145,7 +145,7 @@
<Modal
onClose={resetForm}
bind:open={$invitationModal.open}
width="w-2/5"
width="w-4/5 md:w-2/5"
maxWidth="max-w-lg"
modalHeading="Invite people"
>
@@ -16,9 +16,7 @@
export let isFetching = false;
$: {
const user = $group.people.find(
(person) => person.profile_id === $profile.id
);
const user = $group.people.find((person) => person.profile_id === $profile.id);
if (user) {
isStudent = user.role_id === 3;
@@ -58,6 +56,6 @@
}
.rightBar.isMobile {
margin-left: 10px;
/* margin-left: 10px; */
}
</style>
@@ -7,36 +7,12 @@
export let show = true;
</script>
<section
class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}"
>
<section class="root z-10">
{#if show}
<div class="relative h-full bg-white dark:bg-gray-800">
<slot />
</div>
{/if}
<div class="toggler rounded-full shadow-lg absolute bottom-0">
<IconButton
value="toggle"
onClick={() => (show = !show)}
size={$isMobile ? 'large' : 'small'}
color="text-black"
toolTipProps={$isMobile
? {}
: {
title: 'Toggle editor',
hotkeys: ['B'],
direction: 'right',
}}
>
{#if show}
<ChevronLeftIcon size={16} />
{:else}
<ChevronRightIcon size={16} />
{/if}
</IconButton>
</div>
</section>
<style lang="scss">
@@ -50,11 +26,6 @@
left: 0;
border-right: 1px solid var(--border-color);
background-color: rgb(250, 251, 252);
&.hide {
min-width: 15px;
max-width: 15px;
}
}
.toggler {
@@ -67,10 +38,5 @@
}
@media (max-width: 760px) {
.root {
width: 98%;
min-width: unset;
max-width: unset;
}
}
</style>
@@ -18,13 +18,16 @@
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { updateCourse } from '$lib/utils/services/courses';
import generateSlug from '$lib/utils/functions/generateSlug';
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
import { isMobile } from '$lib/utils/store/useMobile';
export let course;
export let courseId;
let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300';
let loading = false;
let show = false;
const sections = [
{
key: 1,
@@ -115,69 +118,112 @@
}
</script>
<Container show={true}>
<!-- Publish Header Section -->
{#if !selectedSection}
<div class="flex justify-between items-center px-2 w-full">
<CloseButton onClick={handleClose} />
<div class="flex items-center">
<PrimaryButton
label="Save"
type="button"
className="mr-1"
variant={VARIANTS.OUTLINED}
onClick={handleSave}
isLoading={loading}
/>
<PrimaryButton label="Publish" type="button" onClick={handlePublish} isDisabled={loading} />
<IconButton onClick={handlePreview} disabled={loading || !course.slug}>
<ArrowUpRightIcon size={24} class="carbon-icon dark:text-white" title="Preview" />
</IconButton>
</div>
</div>
<div class="flex justify-between items-center px-2 w-full mb-2">
<h3 class="dark:text-white">Page Builder</h3>
</div>
{#each sections as section}
<button
class="w-full flex items-center justify-between px-2 py-3 border border-b-0 border-gray-300"
on:click={handleSectionSelect(section.key)}
>
<p class="dark:text-white mr-2">{section.title} section</p>
<ChevronRightIcon size={24} class="carbon-class" />
</button>
{/each}
{:else}
<!-- Title -->
<div class="flex items-center {borderBottomGrey} w-full">
<IconButton onClick={handleClose}>
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" title="Go back" />
</IconButton>
<h3 class="dark:text-white">{selectedSection.title}</h3>
</div>
<div class="title-content p-2 overflow-y-auto">
{#if selectedSection.key === 1}
<HeaderForm bind:course />
{:else if selectedSection.key === 2}
<RequirementForm bind:course />
{:else if selectedSection.key === 3}
<DescriptionForm bind:course />
{:else if selectedSection.key === 4}
<GoalsForm bind:course />
{:else if selectedSection.key === 5}
<ReviewsForm bind:course />
{:else if selectedSection.key === 6}
<InstructorForm bind:course />
{:else if selectedSection.key === 7}
<PricingForm bind:course />
<aside
class={`${
show ? '-translate-x-[100%] fixed z-[9999]' : 'translate-x-0 fixed z-[9999]'
} left-0 transition w-[280px] min-w-[280px] bg-gray-100 dark:bg-gray-800 h-full border border-l-0 border-t-0 border-b-0 border-r-1`}
>
<div class="toggler rounded-full shadow-lg absolute">
<IconButton
value="toggle"
onClick={() => (show = !show)}
size={$isMobile ? 'large' : 'small'}
color="text-black"
toolTipProps={$isMobile
? {}
: {
title: 'Toggle editor',
direction: 'right'
}}
>
{#if show}
<ChevronRightIcon size={16} />
{:else}
<ChevronLeftIcon size={16} />
{/if}
</div>
{/if}
</Container>
</IconButton>
</div>
<div class="h-full flex flex-col">
{#if !selectedSection}
<div class="flex justify-between items-center px-2 w-full">
<CloseButton onClick={handleClose} />
<div class="flex items-center">
<PrimaryButton
label="Save"
type="button"
className="mr-1"
variant={VARIANTS.OUTLINED}
onClick={handleSave}
isLoading={loading}
/>
<PrimaryButton
label="Publish"
type="button"
onClick={handlePublish}
isDisabled={loading}
/>
<IconButton onClick={handlePreview} disabled={loading || !course.slug}>
<ArrowUpRightIcon size={24} class="carbon-icon dark:text-white" title="Preview" />
</IconButton>
</div>
</div>
<div class="flex justify-between items-center px-2 w-full mb-2">
<h3 class="dark:text-white">Page Builder</h3>
</div>
{#each sections as section}
<button
class="w-full flex items-center justify-between px-2 py-3 border border-b-0 border-gray-300"
on:click={handleSectionSelect(section.key)}
>
<p class="dark:text-white mr-2">{section.title} section</p>
<ChevronRightIcon size={24} class="carbon-class" />
</button>
{/each}
{:else}
<!-- Title -->
<div class="flex items-center {borderBottomGrey} w-full">
<IconButton onClick={handleClose}>
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" title="Go back" />
</IconButton>
<h3 class="dark:text-white">{selectedSection.title}</h3>
</div>
<div class="title-content p-2 overflow-y-auto">
{#if selectedSection.key === 1}
<HeaderForm bind:course />
{:else if selectedSection.key === 2}
<RequirementForm bind:course />
{:else if selectedSection.key === 3}
<DescriptionForm bind:course />
{:else if selectedSection.key === 4}
<GoalsForm bind:course />
{:else if selectedSection.key === 5}
<ReviewsForm bind:course />
{:else if selectedSection.key === 6}
<InstructorForm bind:course />
{:else if selectedSection.key === 7}
<PricingForm bind:course />
{/if}
</div>
{/if}
</div>
</aside>
<style>
.title-content {
height: 90%;
}
.toggler {
right: -15px;
z-index: 10;
border: 1px solid var(--border-color);
top: 50px;
height: fit-content;
background: var(--border-color);
}
@media screen and (max-width: 767px) {
.toggler {
right: -25px;
}
}
</style>
@@ -38,7 +38,7 @@
</script>
<!-- Pricing Details -->
<aside class="{className} price-container md:sticky md:top-0 md:shadow-2xl md:rounded-lg m-h-fit">
<aside class="{className} price-container lg:sticky lg:top-0 lg:shadow-2xl lg:rounded-lg m-h-fit">
<div class="p-10">
<!-- Pricing -->
<div class="mb-6">
@@ -120,9 +120,9 @@
height: fit-content;
}
@media (max-width: 768px) {
@media screen and (min-width: 768px) and (max-width: 1024px) {
.price-container {
width: 100%;
width: 405px;
min-width: unset;
}
}
@@ -164,12 +164,14 @@
{/if}
</div>
</header>
<PricingSection className="md:hidden" {courseData} />
<!-- <PricingSection className="md:hidden" {courseData} /> -->
<!-- Body -->
<div class="bg-white dark:bg-gray-800 w-full">
<div class="py-8 lg:w-10/12 w-full m-2 lg:m-auto flex justify-between">
<div
class="py-8 lg:w-11/12 w-full m-2 lg:m-auto flex flex-col-reverse lg:flex-row items-center lg:items-start justify-between"
>
<!-- Course Details -->
<div class="course-content w-full p-3 md:w-9/12 md:mr-14">
<div class="course-content w-full p-3 lg:w-10/12 lg:mr-14">
<!-- Navigation -->
<nav class="flex items-center border-b border-gray-300 pb-3">
{#each NAV_ITEMS as navItem}
@@ -416,7 +418,7 @@
</div>
<!-- Pricing Details -->
<PricingSection className="hidden md:block" {courseData} />
<PricingSection {courseData} />
</div>
</div>
</div>
@@ -464,7 +466,7 @@
}
.course-content {
max-width: 608px;
max-width: 800px;
}
nav {
+2 -2
View File
@@ -6,7 +6,7 @@
</script>
<div class="w-full flex flex-col">
<div class="flex items-center border-b-2 w-full">
<div class="flex items-center border-b-2 w-full overflow-x-auto">
{#each tabs as tab}
{#if !tab.icon && !tab.badgeValue}
<button
@@ -32,7 +32,7 @@
: 'dark:bg-gray-500 dark:text-white'} dark:bg-transparent font-semibold focus:outline-none w-24 text-left py-3 mr-10"
on:click={onChange(tab.value)}
>
<div class="grid grid-cols-4 gap-3 items-center">
<div class="grid grid-cols-5 md:grid-cols-4 gap-5 md:gap-3 items-center">
{#if tab.icon}
<svelte:component this={tab.icon} />
{/if}
+2 -2
View File
@@ -315,14 +315,14 @@
{#if !isQuizPage($page.url.pathname)}
<OrgSideBar />
{/if}
<div class="org-slot w-full bg-white dark:bg-gray-800">
<div class="org-slot bg-white dark:bg-gray-800 w-full">
<slot />
</div>
</div>
{:else if isLMSPage($page.url.pathname)}
<div class="org-root w-full flex items-center justify-between">
<LMSSideBar />
<div class="org-slot w-full bg-white dark:bg-gray-800">
<div class="org-slot bg-white dark:bg-gray-800 w-full">
<slot />
</div>
</div>
+70 -67
View File
@@ -138,77 +138,80 @@
<CourseContainer bind:isStudent>
<PageNav title="Attendance" />
<section class="mx-9 my-5">
<div class="flex flex-row items-center justify-between">
<div class="flex">
<p class="flex items-center mr-5">
<Checkbox checked disabled /> Present
</p>
<p class="flex items-center">
<Checkbox disabled /> Absent
</p>
<PageBody width="w-full max-w-6xl md:w-11/12">
<section class="flex items-center mx-2 lg:mx-9 my-5">
<div
class="flex flex-col lg:flex-row items-start gap-2 lg:items-center justify-between w-full"
>
<div class="flex">
<p class="flex items-center mr-5">
<Checkbox checked disabled /> Present
</p>
<p class="flex items-center">
<Checkbox disabled /> Absent
</p>
</div>
<div>
<Search
class="dark:text-slate-950"
placeholder="Search students"
bind:value={searchValue}
/>
</div>
</div>
<div>
<Search
class="dark:text-slate-950"
placeholder="Search students"
bind:value={searchValue}
/>
</div>
</div>
</section>
</section>
<section class="my-5 mx-9">
<StructuredList class="m-0">
<!-- Moved the lesson headers outside the students loop -->
<StructuredListHead class="bg-primary-100">
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-600 py-3">Student</StructuredListCell>
{#each $lessons as lesson, index}
<StructuredListCell head class="text-primary-600 py-3"
>Lesson 0{getLectureNo(index + 1)}</StructuredListCell
>
{/each}
</StructuredListRow>
</StructuredListHead>
{#each searchStudents(searchValue) as student}
<StructuredListBody>
<StructuredListRow>
<StructuredListCell>
<div class="w-1/4 flex items-center">
<p class="dark:text-white font-semibold">
{student.profile.fullname}
</p>
</div>
</StructuredListCell>
{#each $lessons as lesson}
<StructuredListCell class="">
<Checkbox
class={isStudent && 'cursor-not-allowed'}
disabled={isStudent}
checked={$attendance[student.id]
? $attendance[student.id][lesson.id]
? $attendance[student.id][lesson.id].is_present
: false
: false}
on:change={(e) => handleAttendanceChange(e, student, lesson)}
/>
</StructuredListCell>
<section class="my-5 mx-2 lg:mx-9">
<StructuredList class="m-0 relative">
<!-- Moved the lesson headers outside the students loop -->
<StructuredListHead class="bg-primary-100">
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-600 py-3">Student</StructuredListCell>
{#each $lessons as lesson, index}
<StructuredListCell head class="text-primary-600 py-3"
>Lesson 0{getLectureNo(index + 1)}</StructuredListCell
>
{/each}
</StructuredListRow>
</StructuredListBody>
{/each}
</StructuredList>
{#if students.length === 0}
<div>
<Box>
</StructuredListHead>
{#each searchStudents(searchValue) as student}
<StructuredListBody>
<StructuredListRow>
<StructuredListCell>
<div class="w-1/4 flex items-center">
<p class="dark:text-white font-semibold">
{student.profile.fullname}
</p>
</div>
</StructuredListCell>
{#each $lessons as lesson}
<StructuredListCell class="">
<Checkbox
class={isStudent && 'cursor-not-allowed'}
disabled={isStudent}
checked={$attendance[student.id]
? $attendance[student.id][lesson.id]
? $attendance[student.id][lesson.id].is_present
: false
: false}
on:change={(e) => handleAttendanceChange(e, student, lesson)}
/>
</StructuredListCell>
{/each}
</StructuredListRow>
</StructuredListBody>
{/each}
</StructuredList>
{#if students.length === 0}
<Box className="h-[300px] w-full">
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white">No Student Added</h3>
<h3 class="text-3xl text-gray-500 dark:text-white text-center">No Student Added</h3>
</Box>
</div>
{/if}
<Pagination totalItems={10} pageSizes={[10, 15, 20]} />
</section>
{/if}
{#if students.length !== 0}
<Pagination totalItems={10} pageSizes={[10, 15, 20]} />
{/if}
</section>
</PageBody>
</CourseContainer>
@@ -30,12 +30,12 @@
</script>
<div
class="root absolute inset-0 flex justify-center items-center z-50 bg-white"
class="absolute inset-0 z-50 bg-white"
in:fly={{ y: 500, duration: 500 }}
out:fly={{ y: 500, duration: 500 }}
>
<Editor {courseId} bind:course={courseData} />
<div class="rightBar" class:isMobile={$isMobile}>
<div class="rightBar">
<CourseLandingPage bind:courseData />
</div>
</div>
@@ -43,13 +43,8 @@
<style>
.rightBar {
flex-grow: 1;
width: calc(100% - 360px);
overflow: auto;
width: 100%;
height: 100%;
overflow: auto;
}
.rightBar.isMobile {
margin-left: 10px;
overflow-y: auto;
}
</style>
+2 -2
View File
@@ -321,12 +321,12 @@
</div>
{:else}
<Box>
<div class="flex justify-between flex-col items-center w-96">
<div class="flex justify-between flex-col items-center w-[90%] md:w-96">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No lessons yet</h2>
<p class="text-sm text-center text-slate-500">
Share your knowledge with the world by creating engaging lessons. Start by clicking on
the New Lesson button.
the Add button.
</p>
</div>
</Box>
@@ -4,17 +4,19 @@
import { fetchCourse, fetchLesson } from '$lib/utils/services/courses';
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants.js';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { Loading } from 'carbon-components-svelte';
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
import PageNav from '$lib/components/PageNav/index.svelte';
import PageBody from '$lib/components/PageBody/index.svelte';
import Materials from '$lib/components/Course/components/Lesson/Materials/index.svelte';
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
import MODES from '$lib/utils/constants/mode.js';
import MODES from '$lib/utils/constants/mode';
import { setCourse, course } from '$lib/components/Course/store';
import Download from 'carbon-icons-svelte/lib/Download.svelte';
import OverflowMenuVertical from 'carbon-icons-svelte/lib/OverflowMenuVertical.svelte';
import { apps } from '$lib/components/Apps/store';
import IconButton from '$lib/components/IconButton/index.svelte';
import {
lesson,
lessons,
@@ -144,6 +146,10 @@
}));
}
const toggleApps = () => {
$apps.open = !$apps.open;
};
function toggleMode() {
prevMode = mode;
mode = mode === MODES.edit ? MODES.view : MODES.edit;
@@ -176,9 +182,20 @@
<svelte:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
{#if data.isMaterialsTabActive}
<div class="flex items-center">
<div class="lg:hidden">
<IconButton onClick={toggleApps} buttonClassName="">
<OverflowMenuVertical size={24} />
</IconButton>
</div>
<div
class={`flex-row ${
$apps.dropdown && $apps.open
? 'absolute lg:relative top-[85px] lg:top-0 right-14 lg:right-0 z-40 dark:bg-gray-800 p-3 lg:p-0'
: 'hidden'
} lg:flex items-center`}
>
<PrimaryButton
className="mr-2"
className="mb-2 lg:mb-0 mr-2"
variant={VARIANTS.OUTLINED}
onClick={downloadLesson}
{isLoading}
@@ -207,7 +224,7 @@
<PageBody>
<Materials lessonId={data.lessonId} {mode} {prevMode} {toggleMode} bind:isSaving />
<div class="w-full flex flex-row-reverse">
<div class="w-full flex flex-row-reverse mt-10">
<PrimaryButton
onClick={markLessonComplete}
isLoading={isMarkingComplete}
+15 -33
View File
@@ -8,10 +8,7 @@
import { ROLE } from '$lib/utils/constants/roles';
import { setCourse, course, group } from '$lib/components/Course/store';
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import {
fetchCourse,
fetchExercisesByMarks,
} from '$lib/utils/services/courses';
import { fetchCourse, fetchExercisesByMarks } from '$lib/utils/services/courses';
import { getLectureNo } from '$lib/components/Course/function';
import { fetchMarks } from '$lib/utils/services/marks';
import { profile } from '$lib/utils/store/user';
@@ -19,10 +16,8 @@
export let data;
const { courseId } = data;
let borderBottomGrey =
'border-r-0 border-t-0 border-b border-l-0 border-gray-300';
let borderleftGrey =
'border-r-0 border-t-0 border-b-0 border-l border-gray-300';
let borderBottomGrey = 'border-r-0 border-t-0 border-b border-l-0 border-gray-300';
let borderleftGrey = 'border-r-0 border-t-0 border-b-0 border-l border-gray-300';
let students = [];
let isStudent = false;
@@ -52,11 +47,11 @@
if (studentMarksByExerciseId[groupmember_id]) {
studentMarksByExerciseId[groupmember_id] = {
...studentMarksByExerciseId[groupmember_id],
[exercise_id]: total_points_gotten,
[exercise_id]: total_points_gotten
};
} else {
studentMarksByExerciseId[groupmember_id] = {
[exercise_id]: total_points_gotten,
[exercise_id]: total_points_gotten
};
}
});
@@ -70,11 +65,11 @@
if (lessonMapping[lesson_id]) {
lessonMapping[lesson_id] = {
...lessonMapping[lesson_id],
[exercise_id]: { title: exercise_title, points },
[exercise_id]: { title: exercise_title, points }
};
} else {
lessonMapping[lesson_id] = {
[exercise_id]: { title: exercise_title, points },
[exercise_id]: { title: exercise_title, points }
};
}
});
@@ -82,20 +77,16 @@
$: {
students = isStudent
? $group.people.filter(
(person) => !!person.profile && person.profile.id === $profile.id
)
: $group.people.filter(
(person) => !!person.profile && person.role_id === ROLE.STUDENT
);
? $group.people.filter((person) => !!person.profile && person.profile.id === $profile.id)
: $group.people.filter((person) => !!person.profile && person.role_id === ROLE.STUDENT);
}
</script>
<CourseContainer bind:isStudent>
<PageNav title="Marks" />
<PageBody width="w-11/12">
<div class="table rounded-md border border-gray-300 w-auto">
<PageBody width="w-full max-w-6xl md:w-11/12">
<div class="table rounded-md border border-gray-300 w-full">
<div class="flex items-center {borderBottomGrey}">
<div class="box flex items-center p-3">
<p class="dark:text-white w-40">Student</p>
@@ -111,8 +102,7 @@
>
{#each Object.keys(lessonMapping[lesson.id]) as exerciseId, index}
<p
class="col text-sm dark:text-white {index &&
borderleftGrey}"
class="col text-sm dark:text-white {index && borderleftGrey}"
title={lessonMapping[lesson.id][exerciseId].title}
>
{lessonMapping[lesson.id][exerciseId].title}
@@ -129,9 +119,7 @@
</div>
{#each students as student}
<div
class="flex relative items-center p-3 cursor-pointer {borderBottomGrey}"
>
<div class="flex relative items-center p-3 cursor-pointer {borderBottomGrey}">
<div class="w-40 flex items-center">
<img
alt="Student avatar"
@@ -143,11 +131,7 @@
{student.profile.fullname}
</p>
<p class="dark:text-white">
{`${
student.assigned_student_id
? '#' + student.assigned_student_id
: '-'
}`}
{`${student.assigned_student_id ? '#' + student.assigned_student_id : '-'}`}
</p>
</div>
</div>
@@ -176,9 +160,7 @@
{:else}
<Box>
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white">
No Student Added
</h3>
<h3 class="text-3xl text-gray-500 dark:text-white">No Student Added</h3>
</Box>
{/each}
</div>
+141 -129
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import TextChip from '$lib/components/Chip/Text.svelte';
import ComingSoon from '$lib/components/ComingSoon/index.svelte';
import Filter from 'carbon-icons-svelte/lib/Filter.svelte';
import { onMount } from 'svelte';
import copy from 'copy-to-clipboard';
import {
@@ -17,6 +18,7 @@
import { fetchCourse, deleteGroupMember, updatedGroupMember } from '$lib/utils/services/courses';
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
import PageNav from '$lib/components/PageNav/index.svelte';
import PageBody from '$lib/components/PageBody/index.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import Select from '$lib/components/Form/Select.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
@@ -135,138 +137,148 @@
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
<section class="my-5 mx-9">
<div class="flex flex-row flex-end justify-end items-center mb-7">
<div class="mr-5 w-80">
<Search
class="dark:text-slate-950 border-0 bg-zinc-100 w-full"
placeholder="Search people"
bind:value={searchValue}
/>
</div>
<div class="mb-3">
<Select bind:value={filterBy} options={ROLES} className="dark:text-black mt-3 w-20" />
<!-- <select bind:value={filterBy} class="mt-3">
{#each ROLES as option}
<option value={option.value}>{option.label}</option>
{/each}
</select> -->
</div>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<p class="dark:text-white hidden lg:block text-lg w-20" />
</RoleBasedSecurity>
</div>
<StructuredList class="m-0">
<StructuredListHead
class="bg-slate-100 dark:bg-neutral-800 dark:border-2 dark:border-neutral-800"
<PageBody width="w-full max-w-6xl md:w-11/12">
<section class="my-5 mx-2 md:mx-9">
<div
class="flex flex-col md:flex-row flex-end gap-2 justify-end items-start md:items-center mb-7"
>
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Name</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Role</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Action</StructuredListCell
>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<p class="dark:text-white hidden lg:block text-lg w-20" />
</RoleBasedSecurity>
</StructuredListRow>
</StructuredListHead>
<div class="max-w-[320px]">
<Search
class="dark:text-slate-950 border-0 bg-zinc-100 w-full"
placeholder="Search people"
bind:value={searchValue}
/>
</div>
<div class="mb-3">
<Select
bind:value={filterBy}
options={ROLES}
className="dark:text-black mt-3 max-w-[80px]"
/>
<!-- <select bind:value={filterBy} class="mt-3">
{#each ROLES as option}
<option value={option.value}>{option.label}</option>
{/each}
</select> -->
</div>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<p class="dark:text-white hidden lg:block text-lg w-20" />
</RoleBasedSecurity>
</div>
{#each filterPeople(searchValue, people) as person}
<StructuredListBody>
<StructuredListRow class="relative">
<!-- first column -->
<StructuredListCell class="w-3/6">
{#if person.profile}
<div class="flex items-center absolute bottom-3">
<Avatar
src={person.profile.avatar_url}
name={person.profile.fullname}
width="w-8"
height="h-8"
className="mr-3"
/>
<div class="flex items-center">
<div class="mr-2">
<p class="dark:text-white text-base font-normal">
{person.profile.fullname}
</p>
<p class="text-xs text-primary-600">
{getEmail(person)}
</p>
</div>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<div class="hidden lg:flex lg:justify-between lg:items-center mr-2">
<CopyButton text={getEmail(person)} feedback="Copied Email to clipboard" />
<StructuredList class="m-0">
<StructuredListHead
class="bg-slate-100 dark:bg-neutral-800 dark:border-2 dark:border-neutral-800"
>
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Name</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Role</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Action</StructuredListCell
>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<p class="dark:text-white hidden lg:block text-lg w-20" />
</RoleBasedSecurity>
</StructuredListRow>
</StructuredListHead>
{#each filterPeople(searchValue, people) as person}
<StructuredListBody>
<StructuredListRow class="relative">
<!-- first column -->
<StructuredListCell class="w-4/6 md:w-3/6">
{#if person.profile}
<div class="flex items-start lg:items-center">
<Avatar
src={person.profile.avatar_url}
name={person.profile.fullname}
width="w-8"
height="h-8"
className="mr-3"
/>
<div class="flex flex-col lg:flex-row items-start lg:items-center">
<div class="mr-2">
<p class="dark:text-white text-base font-normal">
{person.profile.fullname}
</p>
<p class="text-xs text-primary-600 line-clamp-1">
{getEmail(person)}
</p>
</div>
</RoleBasedSecurity>
{#if person.profile_id == $profile.id}
<ComingSoon label="You" />
<div class="flex items-center">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<CopyButton
text={getEmail(person)}
feedback="Copied Email to clipboard"
/>
</RoleBasedSecurity>
{#if person.profile_id == $profile.id}
<ComingSoon label="You" />
{/if}
</div>
</div>
</div>
{:else}
<div class="flex items-start lg:items-center w-2/4">
<TextChip
value={person.email.substring(0, 2).toUpperCase()}
className="bg-primary-200 text-black font-semibold text-xs mr-3"
shape="rounded-full"
/>
<a
href="mailto:{person.email}"
class="text-md text-primary-600 mr-2 dark:text-white"
>
{person.email}
</a>
<div class="flex items-center justify-between">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<CopyButton text={getEmail(person)} feedback="Copied Email to clipboard" />
</RoleBasedSecurity>
<TextChip
value="Pending"
className="text-xs bg-yellow-200 text-yellow-700 h-fit"
size="sm"
/>
</div>
</div>
{/if}
</StructuredListCell>
<!-- second column -->
<StructuredListCell class="w-1/4 px-3">
<p class=" dark:text-white font-normal text-center text-base w-1/4">
{ROLE_LABEL[person.role_id]}
</p>
</StructuredListCell>
<!-- third column -->
<StructuredListCell class="p-0 w-1/4">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<div class="hidden lg:flex lg:justify-between lg:items-center w-20">
{#if person.profile_id !== $profile.id}
<IconButton
onClick={() => {
member = person;
$deleteMemberModal.open = true;
}}
>
<TrashCanIcon size={16} class="carbon-icon dark:text-white" />
</IconButton>
{/if}
</div>
</div>
{:else}
<div class="flex items-center w-2/4 absolute bottom-3">
<TextChip
value={person.email.substring(0, 2).toUpperCase()}
className="bg-primary-200 text-black font-semibold text-xs mr-3"
shape="rounded-full"
/>
<a
href="mailto:{person.email}"
class="text-md text-primary-600 mr-2 dark:text-white"
>
{person.email}
</a>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<div class="hidden lg:flex lg:justify-between lg:items-center w-20">
<CopyButton text={getEmail(person)} feedback="Copied Email to clipboard" />
</div>
</RoleBasedSecurity>
<TextChip
value="Pending"
className="text-xs bg-yellow-200 text-yellow-700 h-fit"
size="sm"
/>
</div>
{/if}
</StructuredListCell>
<!-- second column -->
<StructuredListCell class="w-1/4 py-4 px-3">
<p class="dark:text-white font-normal text-base w-1/4 my-auto">
{ROLE_LABEL[person.role_id]}
</p>
</StructuredListCell>
<!-- third column -->
<StructuredListCell class="p-0 w-1/4">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<div class="hidden lg:flex lg:justify-between lg:items-center w-20">
{#if person.profile_id !== $profile.id}
<IconButton
onClick={() => {
member = person;
$deleteMemberModal.open = true;
}}
>
<TrashCanIcon size={16} class="carbon-icon dark:text-white" />
</IconButton>
{/if}
</div>
</RoleBasedSecurity>
</StructuredListCell>
</StructuredListRow>
</StructuredListBody>
{/each}
</StructuredList>
<Pagination totalItems={10} pageSizes={[10, 15, 20]} />
</section>
</RoleBasedSecurity>
</StructuredListCell>
</StructuredListRow>
</StructuredListBody>
{/each}
</StructuredList>
<!-- <Pagination totalItems={10} pageSizes={[10, 15, 20]} /> -->
</section>
</PageBody>
</CourseContainer>
@@ -244,8 +244,8 @@
<CourseContainer>
<PageNav title="Submitted Exercises" />
<PageBody width="w-11/12 overflow-x-auto overflow-y-hidden">
<div class="flex items-center w-full">
<PageBody width="w-full max-w-6xl md:w-11/12 overflow-x-auto">
<div class="flex items-center w-full overflow-x-scroll">
{#each sections as { id, title, items }, idx (id)}
<div
class="section rounded-md bg-gray-100 dark:bg-gray-800 border border-gray-50 p-3 h-80 mr-3 overflow-hidden"
@@ -301,7 +301,7 @@
{`${item.tutor ? 'created by' + item.tutor.name : ''}`}
</p>
</a>
<p class="dark:text-white text-gray-500 dark:text-white text-xs">
<p class="dark:text-white text-gray-500 text-xs">
{item.submittedAt}
</p>
<!-- <div class="badge rounded-md px-2 bg-green-500 text-white">
@@ -324,10 +324,15 @@
.section {
max-width: 355px;
min-width: 355px;
height: 80vh;
height: 75vh;
}
.content {
height: 95%;
}
@media screen and (max-width: 768px) {
.section {
min-width: 250px;
}
}
</style>