feat: customize the LMS from dashboard (#304)

* Done with the customization of the LMS task

* generated custom_lms migration file

* updated the migration file

* copy improvements

* update wording

* made updates based on reviews,i hope i didnt miss anything

* updated the translations and migration files

* refactoring

* disabled improvements for apps

* more improvements

* more custom improvements

* pr refactoring

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Em_Dev
2024-05-17 01:28:59 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 78db68fa51
commit 73eb0ad4b1
30 changed files with 1013 additions and 200 deletions
@@ -17,6 +17,8 @@
import { browser } from '$app/environment';
import { course } from '$lib/components/Course/store';
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
let appBarRef: HTMLDivElement;
let appContentRef: HTMLDivElement;
@@ -80,10 +82,18 @@
<div class="mb-2 relative {getAppClass(APPS_CONSTANTS.APPS.COMMENTS, $apps.selectedApp)}">
<IconButton
toolTipProps={{ title: 'Comments', hotkeys: ['Ctrl/Command', '1'] }}
toolTipProps={{
title: `${
$globalStore.isOrgSite && !$currentOrg.customization.apps.comments
? $t('course.navItem.lessons.disabled')
: $t('course.navItem.lessons.comments.title')
}`,
hotkeys: ['Ctrl/Command', '1']
}}
value={APPS_CONSTANTS.APPS.COMMENTS}
onClick={handleAppClick}
buttonClassName="relative"
disabled={$globalStore.isOrgSite && !$currentOrg.customization.apps.comments}
>
<Chip
value={$lesson.totalComments}
@@ -92,11 +102,20 @@
<SendAlt size={24} class="carbon-icon dark:text-white" />
</IconButton>
</div>
<div class="mb-2 {getAppClass(APPS_CONSTANTS.APPS.POLL, $apps.selectedApp)}">
<IconButton
toolTipProps={{ title: 'Poll', hotkeys: ['Ctrl/Command', '2'] }}
toolTipProps={{
title: `${
$globalStore.isOrgSite && !$currentOrg.customization.apps.poll
? $t('course.navItem.lessons.disabled')
: $t('course.navItem.lessons.polls.title')
} `,
hotkeys: ['Ctrl/Command', '2']
}}
value={APPS_CONSTANTS.APPS.POLL}
onClick={handleAppClick}
disabled={$globalStore.isOrgSite && !$currentOrg.customization.apps.poll}
>
<Chip
value={$course.polls.length}
@@ -105,6 +124,7 @@
<ChartPie size={24} class="carbon-icon dark:text-white" />
</IconButton>
</div>
<!-- <div class="mb-2">
<IconButton
toolTipProps={{ title: 'QandA', hotkeys: ['A', '3'] }}
@@ -15,7 +15,7 @@
import { sideBar } from '$lib/components/Org/store';
import { profile } from '$lib/utils/store/user';
import { getIsLessonComplete } from '../Lesson/functions';
import { isFreePlan } from '$lib/utils/store/org';
import { currentOrg, isFreePlan } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
export let path: string;
@@ -141,7 +141,10 @@
label: $t('course.navItems.nav_news_feed'),
to: getNavItemRoute($course.id),
hideSortIcon: true,
isPaidFeature: false
isPaidFeature: false,
show() {
return isStudent ? $currentOrg.customization.course.newsfeed : true;
}
},
{
label: $t('course.navItems.nav_lessons'),
@@ -170,7 +173,10 @@
label: $t('course.navItems.nav_marks'),
to: getNavItemRoute($course.id, 'marks'),
isPaidFeature: false,
hideSortIcon: true
hideSortIcon: true,
show() {
return isStudent ? $currentOrg.customization.course.grading : true;
}
},
{
label: $t('course.navItems.nav_people'),
@@ -14,7 +14,6 @@
import Share from 'carbon-icons-svelte/lib/Share.svelte';
import UserFollow from 'carbon-icons-svelte/lib/UserFollow.svelte';
import { t } from '$lib/utils/functions/translations';
import { onDestroy } from 'svelte';
export let bannerImage: string | undefined;
export let id = '';
@@ -53,6 +52,12 @@
// TODO: Delete course functionality
alert('WIP: Delete course');
}
function getCourseUrl() {
return isOnLandingPage
? `/course/${slug}`
: `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`;
}
</script>
{#if !isLMS && !isOnLandingPage}
@@ -87,7 +92,7 @@
<a
rel="prefetch"
bind:this={target}
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`}
href={getCourseUrl()}
class="text-black border border-gray dark:border-neutral-600 rounded w-full max-w-[320px] relative hover:scale-95 transition-all ease-in-out"
>
<div class="p-4">
@@ -31,11 +31,11 @@
<ToolTip
title={toolTipProps.title}
hotkeys={toolTipProps.hotkeys}
hotkeys={disabled ? [] : toolTipProps.hotkeys}
direction={toolTipProps.direction}
>
<button
class="root {color} {selected && 'active'} {size} {contained &&
class="root {color} {selected && 'active'} {disabled && 'disabled'} {size} {contained &&
'contained dark:bg-neutral-700'} {buttonClassName}"
{disabled}
{type}
@@ -65,6 +65,11 @@
-webkit-tap-highlight-color: transparent;
width: fit-content;
&.disabled {
cursor: not-allowed;
color: rgba(0, 0, 0, 0.07);
}
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
@@ -11,11 +11,13 @@
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { sideBar } from '$lib/components/Org/store';
import { t } from '$lib/utils/functions/translations';
import { currentOrg } from '$lib/utils/store/org';
interface sideLinks {
name: string;
icon: any;
link: string;
show?: () => boolean;
}
function isActive(pagePath: string, itemPath: string) {
@@ -45,14 +47,21 @@
{
name: $t('lms_navigation.exercise'),
icon: LicenseDraft,
link: '/lms/exercises'
link: '/lms/exercises',
show() {
return $currentOrg?.customization?.dashboard?.exercise;
}
},
{
name: $t('lms_navigation.community'),
icon: CommunityIcon,
link: '/lms/community'
link: '/lms/community',
show() {
return $currentOrg?.customization?.dashboard?.community;
}
}
];
].filter((link) => (link.show ? link.show() : true));
const toggleSidebar = () => {
$sideBar.hidden = !$sideBar.hidden;
};
@@ -0,0 +1,184 @@
<script lang="ts">
import { Grid, Row, Column } from 'carbon-components-svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import Save from 'carbon-icons-svelte/lib/Save.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import SectionTitle from '../SectionTitle.svelte';
import { Toggle } from 'carbon-components-svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { getSupabase } from '$lib/utils/functions/supabase';
import { snackbar } from '$lib/components/Snackbar/store';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
import { t } from '$lib/utils/functions/translations';
const supabase = getSupabase();
let widgetKey = '';
let isSaving = false;
function widgetControl(key: string) {
widgetKey = key;
$handleOpenWidget.open = true;
}
async function handleSave() {
isSaving = true;
const { error } = await supabase
.from('organization')
.update({ customization: $currentOrg.customization })
.match({ id: $currentOrg.id });
if (error) {
const message = error?.message || 'snackbar.lms.error.try_again';
console.error('Error updating customizations', message);
snackbar.error('snackbar.lms.error.update');
} else {
snackbar.success('snackbar.success_update');
}
isSaving = false;
}
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}>
<SectionTitle>{$t('components.settings.customize_lms.dashboard.title')}</SectionTitle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.dashboard.community')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.dashboard.community}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.dashboard.exercises')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.dashboard.exercise}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div class="mb-3">
<p>{$t('components.settings.customize_lms.dashboard.banner_image')}</p>
<PrimaryButton
variant={VARIANTS.OUTLINED}
label={$t('components.settings.customize_lms.dashboard.banner_image_btn')}
className="mt-3"
onClick={() => widgetControl('banner-image')}
/>
{#if $currentOrg.customization.dashboard.bannerImage}
<img
alt="Banner"
src={$currentOrg.customization.dashboard.bannerImage}
class="mt-2 rounded-md w-full"
/>
{/if}
{#if $handleOpenWidget.open && widgetKey === 'banner-image'}
<UploadWidget bind:imageURL={$currentOrg.customization.dashboard.bannerImage} />
{/if}
</div>
<div>
<p>{$t('components.settings.customize_lms.dashboard.banner_text')}</p>
<TextField
label={$t('components.settings.customize_lms.dashboard.banner_text_label')}
placeholder={$t('components.settings.customize_lms.dashboard.banner_text_placeholder')}
className="mb-5"
bind:value={$currentOrg.customization.dashboard.bannerText}
/>
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('components.settings.customize_lms.course.title')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.course.newsfeed')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.course.newsfeed}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.course.grading')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.course.grading}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('components.settings.customize_lms.apps.title')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.apps.poll')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.apps.poll}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.apps.live_comment')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.apps.comments}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
</Column>
</Row>
<div class="hidden md:block sticky float-right bottom-12 mr-2 z-[120]">
<PrimaryButton
label={$t('components.settings.customize_lms.save')}
onClick={handleSave}
isLoading={isSaving}
isDisabled={isSaving}
/>
</div>
</Grid>
<div class="absolute block md:hidden right-6 bottom-8 z-[120]">
<PrimaryButton onClick={handleSave} isLoading={isSaving} isDisabled={isSaving}>
<Save size={24} class="" />
</PrimaryButton>
</div>
@@ -96,7 +96,7 @@
snackbar.error(`snackbar.lms.error.update ${message}`);
} else {
$currentOrg.landingpage = $landingPageSettings;
snackbar.success();
snackbar.success('snackbar.success_update');
}
isSaving = false;
@@ -115,7 +115,7 @@
}
}
$: setDefault($currentOrg?.landingpage as OrgLandingPageJson);
$: setDefault($currentOrg?.landingpage as unknown as OrgLandingPageJson);
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
@@ -177,6 +177,28 @@
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle
>{$t('settings.organization.organization_profile.customize_lms.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">
{$t('settings.organization.organization_profile.customize_lms.sub_heading')}
</h4>
<p class="text-sm text-gray-500 dark:text-white">
{$t('settings.organization.organization_profile.customize_lms.body')}
</p>
<PrimaryButton
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
variant={VARIANTS.OUTLINED}
onClick={() => gotoSetting('/customize-lms')}
>
{$t('settings.organization.organization_profile.customize_lms.button')}
</PrimaryButton>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle
@@ -63,6 +63,7 @@ export interface OrgLandingPageJson {
show: boolean;
};
}
export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
header: {
title: 'Web Development Courses for',
+11
View File
@@ -0,0 +1,11 @@
import { goto } from '$app/navigation';
import { globalStore } from '$lib/utils/store/app';
import { get } from 'svelte/store';
export function redirectToLesson(settings, courseId) {
const globalStoreVal = get(globalStore);
if (!settings && globalStoreVal.isOrgSite) {
goto(`${courseId}/lessons?next=true`);
}
}
@@ -78,6 +78,7 @@ export async function getOrganizations(userId: string, isOrgSite?: boolean, orgS
siteName,
avatar_url,
landingpage,
customization,
theme,
created_at,
organization_plan(
@@ -114,6 +115,7 @@ export async function getOrganizations(userId: string, isOrgSite?: boolean, orgS
memberId: orgMember?.id,
role_id: orgMember?.role_id,
landingpage: orgMember?.organization?.landingpage,
customization: orgMember?.organization?.customization,
organization_plan: orgMember?.organization?.organization_plan
});
});
@@ -226,6 +228,7 @@ export async function getCurrentOrg(siteName: string, justGet = false) {
siteName,
avatar_url,
landingpage,
customization,
theme,
organization_plan(
plan_name,
@@ -16,6 +16,11 @@ export const defaultCurrentOrgState: CurrentOrg = {
memberId: '',
role_id: '',
landingpage: {},
customization: {
apps: { poll: true, comments: true },
course: { grading: true, newsfeed: true },
dashboard: { exercise: true, community: true, bannerText: '', bannerImage: '' }
},
theme: '',
organization_plan: []
};
@@ -153,7 +153,8 @@
"updating_poll": "Beim Aktualisieren des Umfragestatus ist ein Fehler aufgetreten",
"submitting_poll": "Beim Absenden der Umfrage ist ein Fehler aufgetreten"
}
}
},
"success_update": "Erfolgreich geupdated"
},
"exercises": {
"heading": "Übungen",
@@ -710,7 +711,8 @@
"save": "Speichern",
"edit": "Bearbeiten",
"delete": "Löschen"
}
},
"disabled": "Funktion ist deaktiviert"
},
"news_feed": {
"heading": "Neuigkeiten",
@@ -846,7 +848,60 @@
"url": "URL",
"update": "Domäne aktualisieren",
"custom": "Benutzerdefinierten Domain",
"domain": "Fügen Sie Ihren eigenen Domainnamen hinzu"
"domain": "Fügen Sie Ihren eigenen Domainnamen hinzu",
"title": "Benutzerdefinierten Domain"
},
"customize-lms": {
"title": "LMS anpassen",
"disabled": "deaktiviert",
"enabled": "Ermöglicht",
"save": "Änderungen speichern",
"apps": {
"title": "Apps",
"poll": "Umfrage",
"live_comment": "Live-Kommentare"
},
"course": {
"title": "Kurs",
"newsfeed": "Neuigkeiten",
"grading": "Benotung"
},
"dashboard": {
"title": "Armaturenbrett",
"community": "Gemeinschaft",
"exercises": "Übungen",
"banner_image": "Bannerbild",
"banner_image_btn": "Bannerbild aktualisieren",
"banner_text": "Bannertext",
"banner_text_label": "Bannertext aktualisieren",
"banner_text_placeholder": "Schreiben Sie hier den Text"
}
},
"customize_lms": {
"title": "LMS anpassen",
"enabled": "Ermöglicht",
"save": "Änderungen speichern",
"apps": {
"title": "Apps",
"poll": "Umfrage",
"live_comment": "Live-Kommentare"
},
"course": {
"title": "Kurs",
"newsfeed": "Neuigkeiten",
"grading": "Benotung"
},
"dashboard": {
"title": "Armaturenbrett",
"community": "Gemeinschaft",
"exercises": "Übungen",
"banner_image": "Bannerbild",
"banner_image_btn": "Bannerbild aktualisieren",
"banner_text": "Bannertext",
"banner_text_label": "Bannertext aktualisieren",
"banner_text_placeholder": "Schreiben Sie hier den Text"
},
"disabled": "Deaktiviert"
}
},
"quiz": {
@@ -1083,6 +1138,12 @@
"theme": {
"heading": "Thema",
"sub_heading": "Legen Sie Ihre Markenfarbe fest"
},
"customize_lms": {
"heading": "LMS anpassen",
"sub_heading": "Personalisieren Sie Ihr Organisations-LMS",
"button": "LMS anpassen",
"body": "Kontrollieren Sie die Lernerfahrung Ihrer Schüler. Sie können Registerkarten und Seiten aktivieren oder deaktivieren, LMS-Banner und In-App-Kommentare entsprechend den Anforderungen Ihres Unternehmens aktualisieren"
}
}
},
@@ -1152,4 +1213,4 @@
"seconds": "Sekunden",
"snackbar_error": "Fehler beim Senden des Codes"
}
}
}
@@ -80,6 +80,12 @@
"heading": "Theme",
"sub_heading": "Set your brand color"
},
"customize_lms": {
"heading": "Customize LMS",
"sub_heading": "Personalize Your Organization LMS",
"body": "Control your students learning experience. You can enable or disable tabs and pages, update LMS banner and in-app comments to your organization requirements",
"button": "Customize LMS"
},
"custom_domain": {
"heading": "Custom Domain",
"sub_heading": "Customise your Domain",
@@ -315,12 +321,39 @@
},
"settings": {
"domains": {
"title": "Custom domain",
"add": "Add",
"work_together": "Add your team mates or collaborators to your organization. Start working together",
"url": "URL",
"update": "Update Domain",
"custom": "Custom domain",
"domain": "Add your own domain name"
},
"customize_lms": {
"title": "Customize LMS",
"disabled": "Disabled",
"enabled": "Enabled",
"save": "Save Changes",
"dashboard": {
"title": "Dashboard",
"community": "Community",
"exercises": "Exercises",
"banner_image": "Banner Image",
"banner_image_btn": "Update banner image",
"banner_text": "Banner Text",
"banner_text_label": "Update banner text",
"banner_text_placeholder": "Write the text here"
},
"course": {
"title": "Course",
"newsfeed": "Newsfeed",
"grading": "Grading"
},
"apps": {
"title": "Apps",
"poll": "Poll",
"live_comment": "Live comments"
}
}
}
},
@@ -662,7 +695,7 @@
}
}
},
"disabled": "Feature is disabled",
"comments": {
"title": "Comments",
"you": "You",
@@ -1042,6 +1075,7 @@
"snackbar": {
"success": "Success",
"success_update": "Updated successfully",
"something": "Something went wrong - Please try later",
"update": "Update",
@@ -153,7 +153,8 @@
"updating_poll": "Se produjo un error al actualizar el estado de la encuesta.",
"submitting_poll": "Se produjo un error al enviar la encuesta."
}
}
},
"success_update": "Actualizado exitosamente"
},
"exercises": {
"heading": "Ejercicios",
@@ -710,7 +711,8 @@
"save": "Ahorrar",
"edit": "Editar",
"delete": "Borrar"
}
},
"disabled": "La característica está deshabilitada"
},
"news_feed": {
"heading": "Noticias",
@@ -846,7 +848,60 @@
"url": "URL",
"update": "Actualizar dominio",
"custom": "Dominio personalizado",
"domain": "Añade tu propio nombre de dominio"
"domain": "Añade tu propio nombre de dominio",
"title": "Dominio personalizado"
},
"customize-lms": {
"title": "Personalizar LMS",
"disabled": "desactivado",
"enabled": "Activado",
"save": "Guardar cambios",
"apps": {
"title": "Aplicaciones",
"poll": "Encuesta",
"live_comment": "Comentarios en vivo"
},
"course": {
"title": "Curso",
"newsfeed": "Noticias",
"grading": "Calificación"
},
"dashboard": {
"title": "Panel",
"community": "Comunidad",
"exercises": "Ejercicios",
"banner_image": "Imagen de banner",
"banner_image_btn": "Actualizar imagen de banner",
"banner_text": "Texto del cartel",
"banner_text_label": "Actualizar texto del banner",
"banner_text_placeholder": "Escribe el texto aquí"
}
},
"customize_lms": {
"title": "Personalizar LMS",
"enabled": "Activado",
"save": "Guardar cambios",
"apps": {
"title": "Aplicaciones",
"poll": "Encuesta",
"live_comment": "Comentarios en vivo"
},
"course": {
"title": "Curso",
"newsfeed": "Noticias",
"grading": "Calificación"
},
"dashboard": {
"title": "Panel",
"community": "Comunidad",
"exercises": "Ejercicios",
"banner_image": "Imagen de banner",
"banner_image_btn": "Actualizar imagen de banner",
"banner_text": "Texto del cartel",
"banner_text_label": "Actualizar texto del banner",
"banner_text_placeholder": "Escribe el texto aquí"
},
"disabled": "Desactivado"
}
},
"quiz": {
@@ -1083,6 +1138,12 @@
"theme": {
"heading": "Tema",
"sub_heading": "Establece el color de tu marca"
},
"customize_lms": {
"heading": "Personalizar LMS",
"sub_heading": "Personalice el LMS de su organización",
"button": "Personalizar LMS",
"body": "Controle la experiencia de aprendizaje de sus estudiantes. Puede habilitar o deshabilitar pestañas y páginas, actualizar el banner de LMS y los comentarios en la aplicación según los requisitos de su organización."
}
}
},
@@ -1152,4 +1213,4 @@
"seconds": "segundos",
"snackbar_error": "error al enviar código"
}
}
}
@@ -153,7 +153,8 @@
"updating_poll": "Une erreur s'est produite lors de la mise à jour de l'état du sondage",
"submitting_poll": "Une erreur s'est produite lors de la soumission du sondage"
}
}
},
"success_update": "Mis à jour avec succés"
},
"exercises": {
"heading": "Des exercices",
@@ -459,7 +460,9 @@
"grade_with_ai": "Noter avec l'IA",
"submit_grades": "Soumettre les notes",
"accept_grade": "Accepter",
"reject_grade": "Rejeter"
"reject_grade": "Rejeter",
"add_comment_placeholder": "laissez un commentaire",
"questions_tried": "Attribué ce score en raison du nombre total d'essais :"
}
},
"attendance": {
@@ -708,7 +711,8 @@
"save": "Sauvegarder",
"edit": "Modifier",
"delete": "Supprimer"
}
},
"disabled": "La fonctionnalité est désactivée"
},
"news_feed": {
"heading": "Fil d'actualité",
@@ -844,7 +848,60 @@
"url": "URL",
"update": "Mettre à jour le domaine",
"custom": "Domaine personnalisé",
"domain": "Ajoutez votre propre nom de domaine"
"domain": "Ajoutez votre propre nom de domaine",
"title": "Domaine personnalisé"
},
"customize-lms": {
"title": "Personnaliser le LMS",
"disabled": "désactivé",
"enabled": "Activé",
"save": "Sauvegarder les modifications",
"apps": {
"title": "applications",
"poll": "Sondage",
"live_comment": "Commentaires en direct"
},
"course": {
"title": "Cours",
"newsfeed": "Fil d'actualité",
"grading": "Classement"
},
"dashboard": {
"title": "Tableau de bord",
"community": "Communauté",
"exercises": "Des exercices",
"banner_image": "Image de la bannière",
"banner_image_btn": "Mettre à jour l'image de la bannière",
"banner_text": "Texte de la bannière",
"banner_text_label": "Mettre à jour le texte de la bannière",
"banner_text_placeholder": "Écrivez le texte ici"
}
},
"customize_lms": {
"title": "Personnaliser le LMS",
"enabled": "Activé",
"save": "Sauvegarder les modifications",
"apps": {
"title": "applications",
"poll": "Sondage",
"live_comment": "Commentaires en direct"
},
"course": {
"title": "Cours",
"newsfeed": "Fil d'actualité",
"grading": "Classement"
},
"dashboard": {
"title": "Tableau de bord",
"community": "Communauté",
"exercises": "Des exercices",
"banner_image": "Image de la bannière",
"banner_image_btn": "Mettre à jour l'image de la bannière",
"banner_text": "Texte de la bannière",
"banner_text_label": "Mettre à jour le texte de la bannière",
"banner_text_placeholder": "Écrivez le texte ici"
},
"disabled": "Désactivé"
}
},
"quiz": {
@@ -1081,6 +1138,12 @@
"theme": {
"heading": "Thème",
"sub_heading": "Définissez la couleur de votre marque"
},
"customize_lms": {
"heading": "Personnaliser le LMS",
"sub_heading": "Personnalisez le LMS de votre organisation",
"button": "Personnaliser le LMS",
"body": "Contrôlez l’expérience d’apprentissage de vos étudiants. Vous pouvez activer ou désactiver les onglets et les pages, mettre à jour la bannière LMS et les commentaires dans l'application en fonction des besoins de votre organisation."
}
}
},
@@ -1150,4 +1213,4 @@
"seconds": "secondes",
"snackbar_error": "erreur lors de l'envoi du code"
}
}
}
@@ -153,7 +153,8 @@
"updating_poll": "मतदान स्थिति अद्यतन करते समय एक त्रुटि उत्पन्न हुई",
"submitting_poll": "मतदान सबमिट करते समय एक त्रुटि उत्पन्न हुई"
}
}
},
"success_update": "सफलतापूर्वक अपडेट किया गया"
},
"exercises": {
"heading": "अभ्यास",
@@ -710,7 +711,8 @@
"save": "बचाना",
"edit": "संपादन करना",
"delete": "मिटाना"
}
},
"disabled": "सुविधा अक्षम है"
},
"news_feed": {
"heading": "समाचार फ़ीड",
@@ -846,7 +848,60 @@
"url": "यूआरएल",
"update": "डोमेन अद्यतन करें",
"custom": "कस्टम डोमेन",
"domain": "अपना स्वयं का डोमेन नाम जोड़ें"
"domain": "अपना स्वयं का डोमेन नाम जोड़ें",
"title": "कस्टम डोमेन"
},
"customize-lms": {
"title": "एलएमएस को अनुकूलित करें",
"disabled": "अक्षम",
"enabled": "सक्रिय",
"save": "परिवर्तनों को सुरक्षित करें",
"apps": {
"title": "ऐप्स",
"poll": "मतदान",
"live_comment": "लाइव टिप्पणियाँ"
},
"course": {
"title": "अवधि",
"newsfeed": "समाचार फ़ीड",
"grading": "ग्रेडिंग"
},
"dashboard": {
"title": "डैशबोर्ड",
"community": "समुदाय",
"exercises": "अभ्यास",
"banner_image": "बैनर छवि",
"banner_image_btn": "बैनर छवि अपडेट करें",
"banner_text": "बैनर पाठ",
"banner_text_label": "बैनर टेक्स्ट अपडेट करें",
"banner_text_placeholder": "यहां पाठ लिखें"
}
},
"customize_lms": {
"title": "एलएमएस को अनुकूलित करें",
"enabled": "सक्रिय",
"save": "परिवर्तनों को सुरक्षित करें",
"apps": {
"title": "ऐप्स",
"poll": "मतदान",
"live_comment": "लाइव टिप्पणियाँ"
},
"course": {
"title": "अवधि",
"newsfeed": "समाचार फ़ीड",
"grading": "ग्रेडिंग"
},
"dashboard": {
"title": "डैशबोर्ड",
"community": "समुदाय",
"exercises": "अभ्यास",
"banner_image": "बैनर छवि",
"banner_image_btn": "बैनर छवि अपडेट करें",
"banner_text": "बैनर पाठ",
"banner_text_label": "बैनर टेक्स्ट अपडेट करें",
"banner_text_placeholder": "यहां पाठ लिखें"
},
"disabled": "अक्षम"
}
},
"quiz": {
@@ -1083,6 +1138,12 @@
"theme": {
"heading": "विषय",
"sub_heading": "अपना ब्रांड रंग सेट करें"
},
"customize_lms": {
"heading": "एलएमएस को अनुकूलित करें",
"sub_heading": "अपने संगठन एलएमएस को वैयक्तिकृत करें",
"button": "एलएमएस को अनुकूलित करें",
"body": "अपने विद्यार्थियों के सीखने के अनुभव को नियंत्रित करें। आप टैब और पेजों को सक्षम या अक्षम कर सकते हैं, एलएमएस बैनर और इन-ऐप टिप्पणियों को अपने संगठन की आवश्यकताओं के अनुसार अपडेट कर सकते हैं"
}
}
},
@@ -1152,4 +1213,4 @@
"seconds": "सेकंड",
"snackbar_error": "कोड भेजते समय त्रुटि"
}
}
}
@@ -153,7 +153,8 @@
"updating_poll": "Ocorreu um erro ao atualizar o status da enquete",
"submitting_poll": "Ocorreu um erro ao enviar a enquete"
}
}
},
"success_update": "Atualizado com sucesso"
},
"exercises": {
"heading": "Exercícios",
@@ -710,7 +711,8 @@
"save": "Salvar",
"edit": "Editar",
"delete": "Excluir"
}
},
"disabled": "O recurso está desativado"
},
"news_feed": {
"heading": "Notícias",
@@ -846,7 +848,60 @@
"url": "URL",
"update": "Atualizar domínio",
"custom": "Domínio personalizado",
"domain": "Adicione seu próprio nome de domínio"
"domain": "Adicione seu próprio nome de domínio",
"title": "Domínio personalizado"
},
"customize-lms": {
"title": "Personalizar LMS",
"disabled": "desabilitado",
"enabled": "Habilitado",
"save": "Salvar alterações",
"apps": {
"title": "Aplicativos",
"poll": "Enquete",
"live_comment": "Comentários ao vivo"
},
"course": {
"title": "Curso",
"newsfeed": "Notícias",
"grading": "Classificação"
},
"dashboard": {
"title": "Painel",
"community": "Comunidade",
"exercises": "Exercícios",
"banner_image": "Imagem do banner",
"banner_image_btn": "Atualizar imagem do banner",
"banner_text": "Texto do banner",
"banner_text_label": "Atualizar texto do banner",
"banner_text_placeholder": "Escreva o texto aqui"
}
},
"customize_lms": {
"title": "Personalizar LMS",
"enabled": "Habilitado",
"save": "Salvar alterações",
"apps": {
"title": "Aplicativos",
"poll": "Enquete",
"live_comment": "Comentários ao vivo"
},
"course": {
"title": "Curso",
"newsfeed": "Notícias",
"grading": "Classificação"
},
"dashboard": {
"title": "Painel",
"community": "Comunidade",
"exercises": "Exercícios",
"banner_image": "Imagem do banner",
"banner_image_btn": "Atualizar imagem do banner",
"banner_text": "Texto do banner",
"banner_text_label": "Atualizar texto do banner",
"banner_text_placeholder": "Escreva o texto aqui"
},
"disabled": "Desabilitado"
}
},
"quiz": {
@@ -1083,6 +1138,12 @@
"theme": {
"heading": "Tema",
"sub_heading": "Defina a cor da sua marca"
},
"customize_lms": {
"heading": "Personalizar LMS",
"sub_heading": "Personalize o LMS da sua organização",
"button": "Personalizar LMS",
"body": "Controle a experiência de aprendizagem de seus alunos. Você pode ativar ou desativar guias e páginas, atualizar o banner do LMS e os comentários no aplicativo de acordo com os requisitos da sua organização"
}
}
},
@@ -1152,4 +1213,4 @@
"seconds": "segundos",
"snackbar_error": "erro ao enviar código"
}
}
}
@@ -153,7 +153,8 @@
"updating_poll": "Произошла ошибка при обновлении статуса опроса.",
"submitting_poll": "Произошла ошибка при отправке опроса"
}
}
},
"success_update": "Успешно Обновлено"
},
"exercises": {
"heading": "Упражнения",
@@ -710,7 +711,8 @@
"save": "Сохранять",
"edit": "Редактировать",
"delete": "Удалить"
}
},
"disabled": "Функция отключена"
},
"news_feed": {
"heading": "Новостная лента",
@@ -846,7 +848,60 @@
"url": "URL-адрес",
"update": "Обновить домен",
"custom": "Пользовательский домен",
"domain": "Добавьте свое собственное доменное имя"
"domain": "Добавьте свое собственное доменное имя",
"title": "Пользовательский домен"
},
"customize-lms": {
"title": "Настроить систему управления обучением",
"disabled": "неполноценный",
"enabled": "Включено",
"save": "Сохранить изменения",
"apps": {
"title": "Программы",
"poll": "Голосование",
"live_comment": "Живые комментарии"
},
"course": {
"title": "Курс",
"newsfeed": "Новостная лента",
"grading": "Оценка"
},
"dashboard": {
"title": "Панель приборов",
"community": "Сообщество",
"exercises": "Упражнения",
"banner_image": "Изображение баннера",
"banner_image_btn": "Обновить изображение баннера",
"banner_text": "Текст баннера",
"banner_text_label": "Обновить текст баннера",
"banner_text_placeholder": "Напишите текст здесь"
}
},
"customize_lms": {
"title": "Настроить систему управления обучением",
"enabled": "Включено",
"save": "Сохранить изменения",
"apps": {
"title": "Программы",
"poll": "Голосование",
"live_comment": "Живые комментарии"
},
"course": {
"title": "Курс",
"newsfeed": "Новостная лента",
"grading": "Оценка"
},
"dashboard": {
"title": "Панель приборов",
"community": "Сообщество",
"exercises": "Упражнения",
"banner_image": "Изображение баннера",
"banner_image_btn": "Обновить изображение баннера",
"banner_text": "Текст баннера",
"banner_text_label": "Обновить текст баннера",
"banner_text_placeholder": "Напишите текст здесь"
},
"disabled": "Неполноценный"
}
},
"quiz": {
@@ -1083,6 +1138,12 @@
"theme": {
"heading": "Тема",
"sub_heading": "Установите цвет вашего бренда"
},
"customize_lms": {
"heading": "Настроить систему управления обучением",
"sub_heading": "Персонализируйте свою организацию LMS",
"button": "Настроить систему управления обучением",
"body": "Контролируйте процесс обучения своих учеников. Вы можете включать или отключать вкладки и страницы, обновлять баннер LMS и комментарии в приложении в соответствии с требованиями вашей организации."
}
}
},
@@ -1152,4 +1213,4 @@
"seconds": "секунды",
"snackbar_error": "ошибка при отправке кода"
}
}
}
@@ -153,7 +153,8 @@
"updating_poll": "Đã xảy ra lỗi khi cập nhật trạng thái cuộc thăm dò ý kiến",
"submitting_poll": "Đã xảy ra lỗi khi gửi cuộc thăm dò ý kiến"
}
}
},
"success_update": "Cập nhật thành công"
},
"exercises": {
"heading": "Bài tập",
@@ -459,7 +460,9 @@
"grade_with_ai": "Chấm điểm bằng AI",
"submit_grades": "Gửi điểm",
"accept_grade": "Chấp nhận",
"reject_grade": "Từ chối"
"reject_grade": "Từ chối",
"add_comment_placeholder": "Để lại một bình luận",
"questions_tried": "Phân bổ số điểm này dựa trên tổng số lần thử:"
}
},
"attendance": {
@@ -708,7 +711,8 @@
"save": "Cứu",
"edit": "Biên tập",
"delete": "Xóa bỏ"
}
},
"disabled": "Tính năng bị vô hiệu hóa"
},
"news_feed": {
"heading": "Nguồn cấp tin tức",
@@ -844,7 +848,60 @@
"url": "URL",
"update": "Cập nhật tên miền",
"custom": "Tên miền tùy chọn",
"domain": "Thêm tên miền của riêng bạn"
"domain": "Thêm tên miền của riêng bạn",
"title": "Tên miền tùy chọn"
},
"customize-lms": {
"title": "Tùy chỉnh LMS",
"disabled": "tàn tật",
"enabled": "Đã bật",
"save": "Lưu thay đổi",
"apps": {
"title": "Ứng dụng",
"poll": "Thăm dò ý kiến",
"live_comment": "Bình luận trực tiếp"
},
"course": {
"title": "Khóa học",
"newsfeed": "Nguồn cấp tin tức",
"grading": "Chấm điểm"
},
"dashboard": {
"title": "bảng điều khiển",
"community": "Cộng đồng",
"exercises": "Bài tập",
"banner_image": "Hình ảnh biểu ngữ",
"banner_image_btn": "Cập nhật hình ảnh biểu ngữ",
"banner_text": "Văn bản biểu ngữ",
"banner_text_label": "Cập nhật văn bản biểu ngữ",
"banner_text_placeholder": "Viết văn bản ở đây"
}
},
"customize_lms": {
"title": "Tùy chỉnh LMS",
"enabled": "Đã bật",
"save": "Lưu thay đổi",
"apps": {
"title": "Ứng dụng",
"poll": "Thăm dò ý kiến",
"live_comment": "Bình luận trực tiếp"
},
"course": {
"title": "Khóa học",
"newsfeed": "Nguồn cấp tin tức",
"grading": "Chấm điểm"
},
"dashboard": {
"title": "bảng điều khiển",
"community": "Cộng đồng",
"exercises": "Bài tập",
"banner_image": "Hình ảnh biểu ngữ",
"banner_image_btn": "Cập nhật hình ảnh biểu ngữ",
"banner_text": "Văn bản biểu ngữ",
"banner_text_label": "Cập nhật văn bản biểu ngữ",
"banner_text_placeholder": "Viết văn bản ở đây"
},
"disabled": "Tàn tật"
}
},
"quiz": {
@@ -1081,6 +1138,12 @@
"theme": {
"heading": "chủ đề",
"sub_heading": "Đặt màu thương hiệu của bạn"
},
"customize_lms": {
"heading": "Tùy chỉnh LMS",
"sub_heading": "Cá nhân hóa tổ chức của bạn LMS",
"button": "Tùy chỉnh LMS",
"body": "Kiểm soát trải nghiệm học tập của học sinh. Bạn có thể bật hoặc tắt các tab và trang, cập nhật biểu ngữ LMS và nhận xét trong ứng dụng theo yêu cầu của tổ chức"
}
}
},
@@ -1150,4 +1213,4 @@
"seconds": "giây",
"snackbar_error": "lỗi khi gửi mã"
}
}
}
+18
View File
@@ -1,3 +1,20 @@
export interface OrgCustomization {
dashboard: {
community: boolean;
exercise: boolean;
bannerImage: string;
bannerText: string;
};
course: {
newsfeed: boolean;
grading: boolean;
};
apps: {
poll: boolean;
comments: boolean;
};
}
export interface CurrentOrg {
id: string;
name: string;
@@ -9,6 +26,7 @@ export interface CurrentOrg {
landingpage: {
[key: string]: unknown;
};
customization: OrgCustomization;
theme: string;
organization_plan: {
subscriptionId: string;
+14 -3
View File
@@ -28,7 +28,7 @@
import { getOrganizations } from '$lib/utils/services/org';
import { toggleBodyByMode } from '$lib/utils/functions/app';
import { globalStore } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org';
import { currentOrg, currentOrgDomain } from '$lib/utils/store/org';
import { setTheme } from '$lib/utils/functions/theme';
import hideNavByRoute from '$lib/utils/functions/routes/hideNavByRoute';
import shouldRedirectOnAuth from '$lib/utils/functions/routes/shouldRedirectOnAuth';
@@ -38,6 +38,7 @@
import { handleLocaleChange } from '$lib/utils/functions/translations';
import '../app.postcss';
import { ROLE } from '$lib/utils/constants/roles';
export let data;
@@ -175,6 +176,8 @@
const orgRes = await getOrganizations(profileData.id, data.isOrgSite, data.orgSiteName);
const isStudentAccount = parseInt(orgRes.currentOrg.role_id) === ROLE.STUDENT;
// student redirect
if (data.isOrgSite) {
if (params.has('redirect')) {
@@ -183,8 +186,16 @@
goto('/lms');
}
} else {
// Not on invite page or no org, go to onboarding
if (isEmpty(orgRes.orgs) && !path.includes('invite')) {
if (isStudentAccount) {
// Check if the student logged into the dashboard.
console.log('Student logged into dashboard');
if (dev) {
goto('/lms');
} else {
window.location.replace(`${$currentOrgDomain}/lms`);
}
} else if (isEmpty(orgRes.orgs) && !path.includes('invite')) {
// Not on invite page or no org, go to onboarding
goto(ROUTE.ONBOARDING);
} else if (params.has('redirect')) {
goto(params.get('redirect') || '');
@@ -31,6 +31,8 @@
} from '$lib/utils/services/notification/notification';
import NewsFeedLoader from '$lib/components/Course/components/NewsFeed/NewsFeedLoader.svelte';
import { t } from '$lib/utils/functions/translations.js';
import { currentOrg } from '$lib/utils/store/org.js';
import { redirectToLesson } from '$lib/utils/redirect.js';
export let data;
@@ -221,6 +223,7 @@
};
}
$: redirectToLesson($currentOrg?.customization?.course?.newsfeed, data.courseId);
$: initNewsFeed(data.courseId);
$: setAuthor($group, $profile.id);
@@ -228,70 +231,72 @@
$: $newsFeed = $newsFeed.sort((a, b) => Number(b.isPinned) - Number(a.isPinned));
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title={$t('course.navItem.news_feed.heading')} disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2, $currentOrg?.customization?.course?.newsfeed ? 3 : 0]}>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title={$t('course.navItem.news_feed.heading')} disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<PrimaryButton
className="mr-2"
label={$t('course.navItem.news_feed.heading_button.title')}
onClick={() => ($isNewFeedModal.open = true)}
/>
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
<PageBody width="max-w-4xl px-3">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<PrimaryButton
className="mr-2"
label={$t('course.navItem.news_feed.heading_button.title')}
onClick={() => ($isNewFeedModal.open = true)}
/>
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
<PageBody width="max-w-4xl px-3">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<NewFeedModal
courseId={data.courseId}
{author}
bind:edit
bind:editFeed
onSave={(newFeed) => {
$newsFeed = [newFeed, ...$newsFeed];
}}
{onEdit}
/>
</RoleBasedSecurity>
{#if isLoading}
<div>
<NewsFeedLoader />
<NewsFeedLoader />
<NewsFeedLoader />
</div>
{:else if !$newsFeed.length}
<Box>
<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">{$t('course.navItem.news_feed.body_header')}</h2>
<p class="text-sm text-center text-slate-500">
{$t('course.navItem.news_feed.body_content')}
</p>
</div>
</Box>
{:else}
{#each $newsFeed as feed}
{#if feed.isPinned}
<div class="flex items-center gap-2 mb-3">
<PinFilled size={16} />
<p class="text-sm">{$t('course.navItem.news_feed.pinned')}</p>
</div>
{/if}
<NewsFeedCard
{feed}
{deleteFeed}
{addNewComment}
{deleteComment}
{addNewReaction}
{onPin}
<NewFeedModal
courseId={data.courseId}
{author}
bind:edit
bind:editFeed
isActive={feedId === feed.id}
onSave={(newFeed) => {
$newsFeed = [newFeed, ...$newsFeed];
}}
{onEdit}
/>
{/each}
{/if}
</PageBody>
</CourseContainer>
</RoleBasedSecurity>
{#if isLoading}
<div>
<NewsFeedLoader />
<NewsFeedLoader />
<NewsFeedLoader />
</div>
{:else if !$newsFeed.length}
<Box>
<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">{$t('course.navItem.news_feed.body_header')}</h2>
<p class="text-sm text-center text-slate-500">
{$t('course.navItem.news_feed.body_content')}
</p>
</div>
</Box>
{:else}
{#each $newsFeed as feed}
{#if feed.isPinned}
<div class="flex items-center gap-2 mb-3">
<PinFilled size={16} />
<p class="text-sm">{$t('course.navItem.news_feed.pinned')}</p>
</div>
{/if}
<NewsFeedCard
{feed}
{deleteFeed}
{addNewComment}
{deleteComment}
{addNewReaction}
{onPin}
{author}
bind:edit
bind:editFeed
isActive={feedId === feed.id}
/>
{/each}
{/if}
</PageBody>
</CourseContainer>
</RoleBasedSecurity>
@@ -165,7 +165,7 @@
>
{#each $lessons as lesson (lesson.id)}
<div
class="relative m-auto mb-4 flex max-w-xl items-center rounded-md border-2 border-gray-200 dark:border-neutral-600 p-5 dark:border-neutral-700 dark:bg-neutral-800"
class="relative m-auto mb-4 flex max-w-xl items-center rounded-md border-2 border-gray-200 dark:border-neutral-600 p-5 dark:bg-neutral-800"
>
<!-- Number Chip -->
<div class="mr-5">
@@ -231,6 +231,12 @@
}
function handleAppClick(appName: string) {
if ($apps.selectedApp === appName) {
$apps.selectedApp = undefined;
$apps.open = false;
return;
}
$apps.selectedApp = appName;
$apps.open = true;
}
@@ -345,7 +351,7 @@
{isStudent}
/>
{#if isStudent}
<!-- {#if isStudent}
<div class="w-full hidden lg:flex flex-row-reverse mt-10">
<PrimaryButton
onClick={() => markLessonComplete(data.lessonId)}
@@ -365,11 +371,11 @@
: $t('course.navItem.lessons.complete')}
</PrimaryButton>
</div>
{/if}
{/if} -->
</PageBody>
{/if}
<!-- Mobile Navigation -->
<div class="sticky bottom-5 flex items-center justify-center lg:hidden">
<div class="sticky bottom-5 flex items-center justify-center">
<div
class="flex items-center gap-2 w-fit rounded-full shadow-xl bg-gray-100 dark:bg-neutral-700 px-5 py-1"
>
@@ -390,8 +396,9 @@
</button>
{/if}
<button
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center"
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center disabled:opacity-10 disabled:cursor-not-allowed"
on:click={() => handleAppClick(APPS_CONSTANTS.APPS.COMMENTS)}
disabled={isStudent && !$currentOrg.customization.apps.comments}
>
<SendAlt size={24} class="carbon-icon" />
<span class="ml-1">{$lesson.totalComments}</span>
@@ -3,6 +3,7 @@
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
import PageNav from '$lib/components/PageNav/index.svelte';
import PageBody from '$lib/components/PageBody/index.svelte';
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
import Box from '$lib/components/Box/index.svelte';
import { ROLE } from '$lib/utils/constants/roles';
import { group } from '$lib/components/Course/store';
@@ -16,6 +17,8 @@
import { snackbar } from '$lib/components/Snackbar/store';
import { globalStore } from '$lib/utils/store/app';
import { t } from '$lib/utils/functions/translations.js';
import { currentOrg } from '$lib/utils/store/org';
import { redirectToLesson } from '$lib/utils/redirect.js';
export let data;
@@ -83,95 +86,98 @@
? $group.people.filter((person) => !!person.profile && person.profile.id === $profile.id)
: $group.people.filter((person) => !!person.profile && person.role_id === ROLE.STUDENT);
$: redirectToLesson($currentOrg.customization.course.grading, data.courseId);
$: browser && $course.id && firstRender($course.id);
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title={$t('course.navItem.marks.title')} />
<RoleBasedSecurity allowedRoles={[1, 2, $currentOrg.customization.course.grading ? 3 : 0]}>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title={$t('course.navItem.marks.title')} />
<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">{$t('course.navItem.marks.student')}</p>
</div>
{#each $lessons as lesson, index}
{#if lessonMapping[lesson.id]}
<div class="box flex flex-col items-center {borderleftGrey}">
<p class="dark:text-white col lesson-number" title={lesson.title}>
{getLectureNo(index + 1)}
</p>
<div
class="flex h-full items-center border-r-0 border-t border-b-0 border-l-0 border-gray-300"
>
{#each Object.keys(lessonMapping[lesson.id]) as exerciseId, index}
<p
class="col text-sm dark:text-white {index && borderleftGrey}"
title={lessonMapping[lesson.id][exerciseId].title}
>
{lessonMapping[lesson.id][exerciseId].title}
<span>({lessonMapping[lesson.id][exerciseId].points})</span>
</p>
{/each}
</div>
</div>
{/if}
{/each}
<div class="box flex items-center {borderleftGrey}">
<p class="dark:text-white w-20 text-center">{$t('course.navItem.marks.total')}</p>
</div>
</div>
{#each students as student}
<div class="flex relative items-center p-3 cursor-pointer {borderBottomGrey}">
<div class="w-40 flex items-center">
<img
alt="Student avatar"
src={student.profile.avatar_url}
class="w-8 h-8 rounded-full mr-2"
/>
<div class="text-sm">
<p class="dark:text-white font-semibold">
{student.profile.fullname}
</p>
<p class="dark:text-white">
{`${student.assigned_student_id ? '#' + student.assigned_student_id : '-'}`}
</p>
</div>
<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">{$t('course.navItem.marks.student')}</p>
</div>
{#each $lessons as lesson}
{#each $lessons as lesson, index}
{#if lessonMapping[lesson.id]}
<div class="flex items-center">
{#each Object.keys(lessonMapping[lesson.id]) as exerciseId}
<p class="dark:text-white col">
{studentMarksByExerciseId[student.id]
? studentMarksByExerciseId[student.id][exerciseId] || '-'
: '-'}
</p>
{/each}
<div class="box flex flex-col items-center {borderleftGrey}">
<p class="dark:text-white col lesson-number" title={lesson.title}>
{getLectureNo(index + 1)}
</p>
<div
class="flex h-full items-center border-r-0 border-t border-b-0 border-l-0 border-gray-300"
>
{#each Object.keys(lessonMapping[lesson.id]) as exerciseId, index}
<p
class="col text-sm dark:text-white {index && borderleftGrey}"
title={lessonMapping[lesson.id][exerciseId].title}
>
{lessonMapping[lesson.id][exerciseId].title}
<span>({lessonMapping[lesson.id][exerciseId].points})</span>
</p>
{/each}
</div>
</div>
{/if}
{/each}
<div class="w-20 flex items-center">
<div class="text-sm">
<p class="dark:text-white col">
{calculateStudentTotal(studentMarksByExerciseId[student.id])}
</p>
</div>
<div class="box flex items-center {borderleftGrey}">
<p class="dark:text-white w-20 text-center">{$t('course.navItem.marks.total')}</p>
</div>
</div>
{:else}
<Box>
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white">
{$t('course.navItem.marks.no_student')}
</h3>
</Box>
{/each}
</div>
</PageBody>
</CourseContainer>
{#each students as student}
<div class="flex relative items-center p-3 cursor-pointer {borderBottomGrey}">
<div class="w-40 flex items-center">
<img
alt="Student avatar"
src={student.profile.avatar_url}
class="w-8 h-8 rounded-full mr-2"
/>
<div class="text-sm">
<p class="dark:text-white font-semibold">
{student.profile.fullname}
</p>
<p class="dark:text-white">
{`${student.assigned_student_id ? '#' + student.assigned_student_id : '-'}`}
</p>
</div>
</div>
{#each $lessons as lesson}
{#if lessonMapping[lesson.id]}
<div class="flex items-center">
{#each Object.keys(lessonMapping[lesson.id]) as exerciseId}
<p class="dark:text-white col">
{studentMarksByExerciseId[student.id]
? studentMarksByExerciseId[student.id][exerciseId] || '-'
: '-'}
</p>
{/each}
</div>
{/if}
{/each}
<div class="w-20 flex items-center">
<div class="text-sm">
<p class="dark:text-white col">
{calculateStudentTotal(studentMarksByExerciseId[student.id])}
</p>
</div>
</div>
</div>
{:else}
<Box>
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white">
{$t('course.navItem.marks.no_student')}
</h3>
</Box>
{/each}
</div>
</PageBody>
</CourseContainer>
</RoleBasedSecurity>
<style>
.col {
+9 -5
View File
@@ -65,18 +65,22 @@
<div
class="w-full h-fit lg:h-[265px] flex md:items-center justify-between flex-col-reverse md:flex-row p-5 lg:p-10 rounded-md bg-primary-900 my-2"
>
<span>
<p class="w-full md:w-[75%] lg:w-[80%] text-white text-xs lg:text-xl font-normal mb-5">
{$t('dashboard.lms_dashboard_hero')}
<div class="w-full md:w-[75%] lg:w-[80%]">
<p class=" text-white text-xs lg:text-xl font-normal mb-5">
{$currentOrg.customization.dashboard.bannerText
? $currentOrg.customization.dashboard.bannerText
: $t('dashboard.lms_dashboard_hero')}
</p>
<PrimaryButton
label={$t('dashboard.dont')}
variant={VARIANTS.CONTAINED_WHITE}
onClick={() => goto('/lms/mylearning')}
/>
</span>
</div>
<img
src="/images/student-learning.svg"
src={$currentOrg.customization.dashboard.bannerImage
? $currentOrg.customization.dashboard.bannerImage
: '/images/student-learning.svg'}
alt="student Learning Pictogram"
class="w-28 md:block md:w-1/3 lg:w-[275px] lg:h-[205px] mb-3 md:mb-0"
/>
@@ -0,0 +1,24 @@
<script>
import CustomizeLMS from '$lib/components/Org/Settings/CustomizeLMS.svelte';
import { t } from '$lib/utils/functions/translations';
import { currentOrgPath } from '$lib/utils/store/org';
</script>
<svelte:head>
<title>Customize LMS - ClassroomIO</title>
</svelte:head>
<section class="w-full md:max-w-4xl mx-auto">
<div class="py-10 px-3 md:px-5">
<a class="text-gray-500 dark:text-white text-md" href={`${$currentOrgPath}/settings?tab=org`}
>{$t('upgrade.back')}</a
>
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">
{$t('components.settings.customize_lms.title')}
</h1>
</div>
<CustomizeLMS />
</div>
</section>
@@ -1,5 +1,6 @@
<script>
import Domains from '$lib/components/Org/Settings/Domains.svelte';
import { t } from '$lib/utils/functions/translations';
import { currentOrgPath } from '$lib/utils/store/org';
</script>
@@ -10,10 +11,10 @@
<section class="w-full md:max-w-4xl mx-auto">
<div class="py-10 px-3 md:px-5">
<a class="text-gray-500 dark:text-white text-md" href={`${$currentOrgPath}/settings?tab=org`}
>Back to Settings</a
>{$t('upgrade.back')}</a
>
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">Custom domain</h1>
<h1 class="dark:text-white text-3xl font-bold">{$t('components.settings.domains.title')}</h1>
</div>
<Domains />
@@ -0,0 +1 @@
alter table "public"."organization" add column "customization" json not null default '{"apps":{"poll":true,"comments":true},"course":{"grading":true,"newsfeed":true},"dashboard":{"exercise":true,"community":true,"bannerText":"","bannerImage":""}}'::json;