feat: org and course student analytics page (#509)

* UI initialization

* feat: structure API

* feat: audience & people analytics

* feat: quick changes

* feat: add comments

* redesign course student page

* chore: make requests to db more efficient

* clean up analytics page for course

* feat: finish up audience analytics page

* chore: minor UI tweaks

* translate all

* replace inserts of log in events to update

* catch error if last_login isn't inserted

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2024-12-30 02:59:10 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 381776f388
commit f69bf920a9
46 changed files with 1916 additions and 437 deletions
@@ -1,24 +1,24 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import ComingSoon from '$lib/components/ComingSoon/index.svelte';
import { validateForm } from '$lib/components/Courses/functions';
import { courses, createCourseModal } from '$lib/components/Courses/store';
import TextArea from '$lib/components/Form/TextArea.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import Modal from '$lib/components/Modal/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { ROLE } from '$lib/utils/constants/roles';
import { supabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
import { addDefaultNewsFeed, addGroupMember } from '$lib/utils/services/courses';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { currentOrg } from '$lib/utils/store/org';
import { profile } from '$lib/utils/store/user';
import { COURSE_TYPE, COURSE_VERSION } from '$lib/utils/types';
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { courses, createCourseModal } from '$lib/components/Courses/store';
import { validateForm } from '$lib/components/Courses/functions';
import { ROLE } from '$lib/utils/constants/roles';
import { addGroupMember, addDefaultNewsFeed } from '$lib/utils/services/courses';
import { supabase } from '$lib/utils/functions/supabase';
import { profile } from '$lib/utils/store/user';
import { currentOrg } from '$lib/utils/store/org';
import { goto } from '$app/navigation';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { t } from '$lib/utils/functions/translations';
import { COURSE_TYPE, COURSE_VERSION } from '$lib/utils/types';
import ComingSoon from '$lib/components/ComingSoon/index.svelte';
let isLoading = false;
let errors = {
@@ -143,18 +143,19 @@
onClose={() => onClose($page.url.pathname)}
bind:open
width="w-4/5 md:w-2/5 md:min-w-[600px]"
containerClass="max-w-2xl mx-auto"
modalHeading={$t('courses.new_course_modal.heading')}
>
{#if step === 0}
<div>
<h2 class="text-xl font-medium my-5">
<h2 class="my-5 text-xl font-medium">
{$t('courses.new_course_modal.type_selector_title')}
</h2>
<div class="flex flex-col md:flex-row gap-2 justify-between items-center my-8">
<div class="my-8 flex flex-col items-center justify-evenly gap-4 md:flex-row">
{#each options as option}
<button
class="w-11/12 md:w-[261px] md:h-[240px] p-5 rounded-md dark:bg-neutral-700 border-2 {option.type ===
class="w-11/12 rounded-md border-2 p-5 dark:bg-neutral-700 md:h-[240px] md:w-[261px] {option.type ===
type
? 'border-primary-400'
: `border-gray-200 dark:border-neutral-600 ${
@@ -164,7 +165,7 @@
type="button"
on:click={!option.isDisabled ? () => (type = option.type) : undefined}
>
<div class="w-full flex flex-row-reverse h-[70%]">
<div class="flex h-[70%] w-full flex-row-reverse">
{#if option.type === type}
<CheckmarkFilledIcon
size={16}
@@ -176,19 +177,19 @@
</div>
<div>
<p class="font-bold text-start flex items-center">
<p class="flex items-center text-start font-bold">
<span class="mr-2 text-sm">{option.title}</span>
{#if option.isDisabled}
<ComingSoon />
{/if}
</p>
<p class="text-xs font-light text-start">{option.subtitle}</p>
<p class="text-start text-xs font-light">{option.subtitle}</p>
</div>
</button>
{/each}
</div>
<div class="mt-8 flex items-center flex-row-reverse">
<div class="mt-8 flex flex-row-reverse items-center">
<PrimaryButton
className="px-6 py-3"
label={$t('courses.new_course_modal.next')}
@@ -199,7 +200,7 @@
</div>
{:else}
<form on:submit|preventDefault={createCourse}>
<div class="flex items-end space-x-2 mb-4">
<div class="mb-4 flex items-end space-x-2">
<TextField
label={$t('courses.new_course_modal.course_name')}
bind:value={$createCourseModal.title}