2023-01-03 09:26:08 +01:00
|
|
|
<script lang="ts">
|
2023-08-02 19:35:27 +01:00
|
|
|
import { goto } from '$app/navigation';
|
2024-12-31 17:04:07 +01:00
|
|
|
import { ActivityCard } from '$lib/components/Analytics';
|
|
|
|
|
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
|
|
|
|
|
import Progress from '$lib/components/Progress/index.svelte';
|
|
|
|
|
import UserProfile from 'carbon-icons-svelte/lib/UserProfile.svelte';
|
|
|
|
|
|
|
|
|
|
import { snackbar } from '$lib/components/Snackbar/store';
|
|
|
|
|
import { calDateDiff } from '$lib/utils/functions/date';
|
|
|
|
|
import { getAccessToken } from '$lib/utils/functions/supabase';
|
2024-12-31 17:18:42 +01:00
|
|
|
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
2024-12-30 02:59:10 +01:00
|
|
|
import { profile } from '$lib/utils/store/user';
|
2024-12-31 17:04:07 +01:00
|
|
|
import type { OrganisationAnalytics } from '$lib/utils/types/analytics';
|
|
|
|
|
import Add from 'carbon-icons-svelte/lib/Add.svelte';
|
|
|
|
|
import Book from 'carbon-icons-svelte/lib/Book.svelte';
|
|
|
|
|
import CurrencyDollar from 'carbon-icons-svelte/lib/CurrencyDollar.svelte';
|
|
|
|
|
import UserMultiple from 'carbon-icons-svelte/lib/UserMultiple.svelte';
|
|
|
|
|
|
|
|
|
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
2023-09-16 22:22:24 +01:00
|
|
|
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
2024-12-30 02:59:10 +01:00
|
|
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
|
|
|
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
|
|
|
|
import WelcomeModal from '$lib/components/WelcomeModal/WelcomeModal.svelte';
|
2023-10-22 09:47:13 +01:00
|
|
|
import { getGreeting } from '$lib/utils/functions/date';
|
2024-05-22 05:49:10 +01:00
|
|
|
import { t } from '$lib/utils/functions/translations';
|
2024-12-31 17:04:07 +01:00
|
|
|
import { isOrgAdmin } from '$lib/utils/store/org';
|
2024-12-30 02:59:10 +01:00
|
|
|
import { isMobile } from '$lib/utils/store/useMobile';
|
2024-12-31 17:04:07 +01:00
|
|
|
import { Grid, Link, SkeletonPlaceholder } from 'carbon-components-svelte';
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:18:42 +01:00
|
|
|
// export let data;
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
let dashAnalytics: OrganisationAnalytics;
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2023-09-24 13:20:04 +00:00
|
|
|
function createCourse() {
|
2024-12-31 17:18:42 +01:00
|
|
|
goto(`${$currentOrgPath}/courses?create=true`);
|
2024-12-31 17:04:07 +01:00
|
|
|
}
|
2023-09-24 13:20:04 +00:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
async function fetchDashAnalytics(orgId: string) {
|
|
|
|
|
if (!orgId) return;
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
const accessToken = await getAccessToken();
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/analytics/dash', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
Authorization: accessToken
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ orgId })
|
|
|
|
|
});
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
if (!response.ok) {
|
|
|
|
|
console.error(response);
|
|
|
|
|
throw new Error('Failed to fetch analytics data');
|
2023-01-03 09:26:08 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
dashAnalytics = (await response.json()) as OrganisationAnalytics;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
snackbar.error('Failed to fetch analytics data');
|
2023-01-03 09:26:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
$: fetchDashAnalytics($currentOrg.id);
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
$: cards = [
|
|
|
|
|
{
|
|
|
|
|
icon: CurrencyDollar,
|
|
|
|
|
title: `${$t('dashboard.revenue')} ($)`,
|
|
|
|
|
percentage: dashAnalytics?.revenue ?? 0,
|
|
|
|
|
description: $t('dashboard.revenue_description'),
|
|
|
|
|
hidePercentage: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: Book,
|
|
|
|
|
title: $t('dashboard.no_of_courses'),
|
|
|
|
|
percentage: dashAnalytics?.numberOfCourses ?? 0,
|
|
|
|
|
description: $t('dashboard.no_courses_description'),
|
|
|
|
|
hidePercentage: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: UserMultiple,
|
|
|
|
|
title: $t('dashboard.total_students'),
|
|
|
|
|
percentage: dashAnalytics?.totalStudents ?? 0,
|
|
|
|
|
description: $t('dashboard.total_students_description'),
|
|
|
|
|
hidePercentage: true
|
2023-01-03 09:26:08 +01:00
|
|
|
}
|
2024-12-31 17:04:07 +01:00
|
|
|
];
|
2023-01-03 09:26:08 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>Dashboard - ClassroomIO</title>
|
|
|
|
|
</svelte:head>
|
|
|
|
|
|
2023-07-18 13:50:47 +00:00
|
|
|
<WelcomeModal />
|
|
|
|
|
|
2024-12-30 02:59:10 +01:00
|
|
|
<div class="w-full max-w-5xl px-5 py-10 md:mx-auto">
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="mb-5 flex items-center justify-between">
|
2024-12-30 02:59:10 +01:00
|
|
|
<h1 class="mb-3 text-2xl font-bold dark:text-white md:text-3xl">
|
2024-04-19 05:45:48 +01:00
|
|
|
{$t(getGreeting())}
|
2023-10-22 09:47:13 +01:00
|
|
|
{$profile.fullname}!
|
|
|
|
|
</h1>
|
2023-09-16 22:22:24 +01:00
|
|
|
<div class="flex items-center">
|
2023-09-17 00:14:49 +01:00
|
|
|
<PrimaryButton
|
|
|
|
|
variant={VARIANTS.OUTLINED}
|
|
|
|
|
onClick={createCourse}
|
|
|
|
|
isDisabled={!$isOrgAdmin}
|
|
|
|
|
className="min-h-[36px]"
|
|
|
|
|
>
|
2023-09-16 22:27:21 +01:00
|
|
|
{#if $isMobile}
|
2023-09-16 22:22:24 +01:00
|
|
|
<Add size={24} />
|
2023-09-16 22:27:21 +01:00
|
|
|
{:else}
|
2024-04-19 05:45:48 +01:00
|
|
|
{$t('dashboard.create_course')}
|
2023-09-16 22:27:21 +01:00
|
|
|
{/if}
|
2023-09-08 09:17:05 +00:00
|
|
|
</PrimaryButton>
|
2023-09-16 22:22:24 +01:00
|
|
|
|
|
|
|
|
<VisitOrgSiteButton />
|
|
|
|
|
</div>
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
|
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="mb-10 flex flex-wrap items-start">
|
|
|
|
|
<Grid class="px-0" fullWidth>
|
|
|
|
|
<div class="mt-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
|
|
|
{#each cards as card}
|
|
|
|
|
{#if !dashAnalytics}
|
|
|
|
|
<SkeletonPlaceholder
|
|
|
|
|
style="width: 100%; min-width: 300px; height: 10rem;"
|
|
|
|
|
class="rounded-md"
|
|
|
|
|
/>
|
|
|
|
|
{:else}
|
|
|
|
|
<ActivityCard activity={card} />
|
|
|
|
|
{/if}
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</Grid>
|
2023-10-22 09:47:13 +01:00
|
|
|
</div>
|
|
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
|
|
|
<div
|
|
|
|
|
class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 dark:border-neutral-600 md:p-5"
|
|
|
|
|
>
|
|
|
|
|
<h3 class="mt-0 text-2xl font-bold">
|
|
|
|
|
{$t('dashboard.top_courses')}
|
|
|
|
|
</h3>
|
2023-01-03 09:26:08 +01:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="h-full space-y-6">
|
|
|
|
|
{#if !dashAnalytics}
|
|
|
|
|
{#each Array(5) as _}
|
|
|
|
|
<SkeletonPlaceholder style="width: 100%; height: 40px;" class="rounded-md" />
|
|
|
|
|
{/each}
|
2023-10-22 09:47:13 +01:00
|
|
|
{:else}
|
2024-12-31 17:04:07 +01:00
|
|
|
{#each dashAnalytics.topCourses as course}
|
2024-12-31 17:47:07 +01:00
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<div class="w-4/6 space-y-1">
|
2024-12-31 17:04:07 +01:00
|
|
|
<Link href={`/courses/${course.id}`}>
|
2024-12-31 17:47:07 +01:00
|
|
|
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
|
|
|
|
{course.title}
|
|
|
|
|
</p>
|
2024-12-31 17:04:07 +01:00
|
|
|
</Link>
|
|
|
|
|
<p class="text-muted-foreground text-sm">
|
|
|
|
|
{course.enrollments}
|
2024-12-31 17:18:42 +01:00
|
|
|
{$t(course.enrollments === 1 ? 'dashboard.student' : 'dashboard.students')}
|
2023-10-22 09:47:13 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="ml-auto w-2/6">
|
|
|
|
|
<Progress value={course.completion} />
|
|
|
|
|
<div class="text-sm font-medium">
|
|
|
|
|
{course.completion}%
|
|
|
|
|
{$t('dashboard.completion')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
2023-10-22 09:47:13 +01:00
|
|
|
{:else}
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="flex flex-col h-full items-center justify-center p-3">
|
|
|
|
|
<div class="bg-primary-200 w-fit rounded-full p-4 text-black">
|
|
|
|
|
<CourseIcon width="30" height="30" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="my-4 text-center">
|
|
|
|
|
<p class=" text-xl font-semibold">
|
|
|
|
|
{$t('dashboard.create_first_course')}
|
|
|
|
|
</p>
|
|
|
|
|
<p class="text-sm text-gray-500 dark:text-gray-300">
|
|
|
|
|
{$t('dashboard.create_first_course_description')}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<PrimaryButton
|
|
|
|
|
variant={VARIANTS.OUTLINED}
|
|
|
|
|
onClick={createCourse}
|
|
|
|
|
label={$t('dashboard.create_course')}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-10-22 09:47:13 +01:00
|
|
|
{/each}
|
2024-12-31 17:04:07 +01:00
|
|
|
{/if}
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
<div
|
|
|
|
|
class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 dark:border-neutral-600 md:p-5"
|
|
|
|
|
>
|
|
|
|
|
<h3 class="mt-0 text-2xl font-bold">
|
|
|
|
|
{$t('dashboard.recent_enrollments')}
|
|
|
|
|
</h3>
|
2023-09-12 11:31:20 +00:00
|
|
|
|
2024-12-31 17:04:07 +01:00
|
|
|
<div class="h-full space-y-6">
|
|
|
|
|
{#if !dashAnalytics}
|
|
|
|
|
{#each Array(5) as _}
|
|
|
|
|
<SkeletonPlaceholder style="width: 100%; height: 40px;" class="rounded-md" />
|
|
|
|
|
{/each}
|
|
|
|
|
{:else}
|
|
|
|
|
{#each dashAnalytics.enrollments as enrollment}
|
|
|
|
|
<div class="flex items-center justify-between gap-2">
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<Avatar
|
|
|
|
|
src={enrollment.avatarUrl}
|
|
|
|
|
name={enrollment.name}
|
|
|
|
|
width="w-6"
|
|
|
|
|
height="h-6"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div class="min-h-[45px] space-y-1">
|
2024-12-31 17:47:07 +01:00
|
|
|
<p class="text-sm font-medium capitalize leading-none">{enrollment.name}</p>
|
2024-12-31 17:04:07 +01:00
|
|
|
<p class="text-muted-foreground text-sm">
|
|
|
|
|
<span class="italic">
|
|
|
|
|
{calDateDiff(enrollment.date)}
|
|
|
|
|
</span>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
|
|
|
|
|
2024-12-31 17:18:42 +01:00
|
|
|
<div class="w-2/4">
|
2024-12-31 17:04:07 +01:00
|
|
|
<Link href={`/courses/${enrollment.courseId}`}>
|
2024-12-31 17:18:42 +01:00
|
|
|
<p class="line-clamp-2 pb-[0.1rem] text-sm font-medium leading-none">
|
2024-12-31 17:04:07 +01:00
|
|
|
{enrollment.course}
|
|
|
|
|
</p>
|
|
|
|
|
</Link>
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2024-12-31 17:04:07 +01:00
|
|
|
{:else}
|
|
|
|
|
<div class="flex flex-col h-full items-center justify-center p-3">
|
|
|
|
|
<div class="bg-primary-200 w-fit rounded-full p-4 text-black">
|
|
|
|
|
<UserProfile size={24} />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="my-4 text-center">
|
|
|
|
|
<p class=" text-xl font-semibold">
|
|
|
|
|
{$t('dashboard.publish_first_course')}
|
|
|
|
|
</p>
|
|
|
|
|
<p class="text-sm text-gray-500 dark:text-gray-300">
|
|
|
|
|
{$t('dashboard.publish_first_course_description')}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<PrimaryButton
|
|
|
|
|
variant={VARIANTS.OUTLINED}
|
2024-12-31 17:18:42 +01:00
|
|
|
onClick={() => goto(`${$currentOrgPath}/courses`)}
|
2024-12-31 17:04:07 +01:00
|
|
|
label={$t('dashboard.publish_course')}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
{/if}
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
2024-12-31 17:04:07 +01:00
|
|
|
</div>
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|