feat: certificates, landing pages, ai tutor, website update, compliance
This commit is contained in:
@@ -94,7 +94,9 @@
|
||||
moveUp: t.get('course.navItem.lessons.exercises.all_exercises.section.move_up'),
|
||||
moveDown: t.get('course.navItem.lessons.exercises.all_exercises.section.move_down'),
|
||||
color: t.get('course.navItem.lessons.exercises.all_exercises.section.color'),
|
||||
descriptionPlaceholder: t.get('course.navItem.lessons.exercises.all_exercises.section.description_placeholder')
|
||||
descriptionPlaceholder: t.get('course.navItem.lessons.exercises.all_exercises.section.description_placeholder'),
|
||||
collapseSection: t.get('course.navItem.lessons.exercises.all_exercises.section.collapse_section'),
|
||||
expandSection: t.get('course.navItem.lessons.exercises.all_exercises.section.expand_section')
|
||||
});
|
||||
const sectionAfterBehaviorLabels = $derived({
|
||||
afterSectionPrefix: t.get('course.navItem.lessons.exercises.all_exercises.section.after_section'),
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
|
||||
import ArrowDownIcon from '@lucide/svelte/icons/arrow-down';
|
||||
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
|
||||
const COLOR_THEMES: ExerciseSectionColorTheme[] = ['blue', 'green', 'amber', 'rose', 'violet', 'slate'];
|
||||
|
||||
@@ -82,6 +84,8 @@
|
||||
let localTitle = $derived(title);
|
||||
let localDescription = $derived(description ?? '');
|
||||
|
||||
let sectionCollapsed = $state(false);
|
||||
|
||||
function handleTitleInput() {
|
||||
onTitleChange?.(localTitle);
|
||||
}
|
||||
@@ -94,12 +98,49 @@
|
||||
<section
|
||||
id={`exercise-section-${exerciseSectionId}`}
|
||||
class={`overflow-hidden rounded-lg border bg-white shadow-xs dark:bg-zinc-950 ${BORDER_CLASSES[colorTheme]}`}
|
||||
aria-expanded={!sectionCollapsed}
|
||||
>
|
||||
<div class="flex items-center gap-3 border-b bg-zinc-50 px-4 py-3 dark:bg-zinc-900">
|
||||
<div class="group/sectionhead flex items-center gap-3 border-b bg-zinc-50 px-4 py-3 dark:bg-zinc-900">
|
||||
{#if showReorderHandles}
|
||||
<GripVerticalIcon class="ui:text-muted-foreground h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
{/if}
|
||||
<span class={`h-2.5 w-2.5 shrink-0 rounded-full ${COLOR_CLASSES[colorTheme]}`}></span>
|
||||
<div class="relative h-7 w-7 shrink-0">
|
||||
{#if !sectionCollapsed}
|
||||
<span
|
||||
class={`pointer-events-none absolute top-1/2 left-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full opacity-100 transition-opacity ${COLOR_CLASSES[colorTheme]} group-hover/sectionhead:opacity-0`}
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-hover/sectionhead:opacity-100"
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onclick={() => (sectionCollapsed = true)}
|
||||
tooltip={labels.collapseSection ?? 'Collapse section'}
|
||||
tooltipSide="bottom"
|
||||
class="shrink-0"
|
||||
>
|
||||
<ChevronUpIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<IconButton
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onclick={() => (sectionCollapsed = false)}
|
||||
tooltip={labels.expandSection ?? 'Expand section'}
|
||||
tooltipSide="bottom"
|
||||
class="shrink-0"
|
||||
>
|
||||
<ChevronDownIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Input
|
||||
bind:value={localTitle}
|
||||
oninput={handleTitleInput}
|
||||
@@ -137,48 +178,50 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="border-b bg-white px-4 py-3 dark:bg-zinc-950">
|
||||
<TextareaField
|
||||
bind:value={localDescription}
|
||||
rows={2}
|
||||
oninput={handleDescriptionInput}
|
||||
placeholder={labels.descriptionPlaceholder ?? 'Describe this section'}
|
||||
errorMessage={descriptionError}
|
||||
className="m-0"
|
||||
bgColor="resize-none border-dashed bg-transparent text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 bg-white px-4 py-4 dark:bg-zinc-950">
|
||||
{@render children?.()}
|
||||
|
||||
<div class="flex justify-center">
|
||||
<Button variant="outline" onclick={onAddQuestion}>+ {labels.addQuestion ?? 'Add question'}</Button>
|
||||
{#if !sectionCollapsed}
|
||||
<div class="border-b bg-white px-4 py-3 dark:bg-zinc-950">
|
||||
<TextareaField
|
||||
bind:value={localDescription}
|
||||
rows={2}
|
||||
oninput={handleDescriptionInput}
|
||||
placeholder={labels.descriptionPlaceholder ?? 'Describe this section'}
|
||||
errorMessage={descriptionError}
|
||||
className="m-0"
|
||||
bgColor="resize-none border-dashed bg-transparent text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between border-t bg-zinc-50 px-4 py-3 dark:bg-zinc-900">
|
||||
<p class="ui:text-muted-foreground text-sm">
|
||||
{requiredQuestionCount}
|
||||
{labels.required ?? 'required'}
|
||||
</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<IconButton
|
||||
onclick={onMoveUp}
|
||||
disabled={!canMoveUp}
|
||||
tooltip={labels.moveUp ?? 'Move section up'}
|
||||
tooltipSide="bottom"
|
||||
>
|
||||
<ArrowUpIcon size={16} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onclick={onMoveDown}
|
||||
disabled={!canMoveDown}
|
||||
tooltip={labels.moveDown ?? 'Move section down'}
|
||||
tooltipSide="bottom"
|
||||
>
|
||||
<ArrowDownIcon size={16} />
|
||||
</IconButton>
|
||||
<div class="space-y-4 bg-white px-4 py-4 dark:bg-zinc-950">
|
||||
{@render children?.()}
|
||||
|
||||
<div class="flex justify-center">
|
||||
<Button variant="outline" onclick={onAddQuestion}>+ {labels.addQuestion ?? 'Add question'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between border-t bg-zinc-50 px-4 py-3 dark:bg-zinc-900">
|
||||
<p class="ui:text-muted-foreground text-sm">
|
||||
{requiredQuestionCount}
|
||||
{labels.required ?? 'required'}
|
||||
</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<IconButton
|
||||
onclick={onMoveUp}
|
||||
disabled={!canMoveUp}
|
||||
tooltip={labels.moveUp ?? 'Move section up'}
|
||||
tooltipSide="bottom"
|
||||
>
|
||||
<ArrowUpIcon size={16} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onclick={onMoveDown}
|
||||
disabled={!canMoveDown}
|
||||
tooltip={labels.moveDown ?? 'Move section down'}
|
||||
tooltipSide="bottom"
|
||||
>
|
||||
<ArrowDownIcon size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { questionnaire } from './store';
|
||||
import { hasSections, questionnaire } from './store';
|
||||
import { SafeHtmlContent } from '@cio/ui/custom/safe-html-content';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
const activeExerciseSectionsCount = $derived(
|
||||
[...($questionnaire.sections ?? [])].filter((section) => !section.deletedAt).length
|
||||
);
|
||||
const showSectionsInPreviewSummary = $derived(hasSections($questionnaire.sections ?? []));
|
||||
</script>
|
||||
|
||||
<div class="mb-5 {!preview ? 'px-6' : 'px-2'}">
|
||||
@@ -31,6 +36,13 @@
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.questions')}
|
||||
</p>
|
||||
|
|
||||
{#if showSectionsInPreviewSummary}
|
||||
<p class="mx-2 dark:text-white">
|
||||
<strong>{activeExerciseSectionsCount}</strong>
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.sections')}
|
||||
</p>
|
||||
|
|
||||
{/if}
|
||||
<p class="mx-2 dark:text-white">
|
||||
<strong>{getTotalPossibleGrade($questionnaire.questions)}</strong>
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.points')}.
|
||||
|
||||
@@ -761,6 +761,13 @@
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.questions')}
|
||||
</p>
|
||||
|
|
||||
{#if hasSectionedExercise}
|
||||
<p class="mx-2 dark:text-white">
|
||||
<strong>{activeSections.length}</strong>
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.sections')}
|
||||
</p>
|
||||
|
|
||||
{/if}
|
||||
<p class="mx-2 dark:text-white">
|
||||
<strong>{getTotalPossibleGrade($questionnaire.questions)}</strong>
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.points')}.
|
||||
|
||||
Reference in New Issue
Block a user