feat: setup dashboard analytics api (#604)

* feat: setup dashboard analytics api

* chore: requested pr fixes

* fix: dash analytics server side type

* refactor: update Avatar component usage across the dashboard

- Replaced custom Avatar component with the new @cio/ui/base/avatar implementation for consistency.
- Enhanced avatar rendering with fallback options and improved accessibility.
- Cleaned up unused Avatar component files and related functions.
- Updated various components to utilize the new Avatar structure, ensuring a uniform appearance and behavior across the application.

* chore: remove console.log statements from analytics routes and API calls

- Eliminated console.log statements for orgId and siteName in the dash analytics router and DashStatApi class to clean up the code and improve performance.

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2025-12-02 15:27:41 +01:00
committed by GitHub
co-authored by Rotimi Best
parent c7854efae3
commit 9a5aee1ee0
40 changed files with 573 additions and 276 deletions
@@ -1,5 +1,5 @@
<script lang="ts">
import Avatar from '$lib/components/Avatar/index.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import { calDateDiff } from '$lib/utils/functions/date';
import { t } from '$lib/utils/functions/translations';
import type { UserAnalytics } from '$lib/utils/types/analytics';
@@ -15,7 +15,13 @@
<div class="rounded-md border p-5 dark:border-neutral-600">
<div class="flex w-full flex-col items-center justify-start gap-4 text-start md:flex-row">
<Avatar src={user.avatarUrl} name={user.fullName} width="w-16" height="h-16" />
<Avatar.Root class="h-16 w-16">
<Avatar.Image
src={user.avatarUrl ? user.avatarUrl : '/logo-192.png'}
alt={user.fullName ? user.fullName : 'User'}
/>
<Avatar.Fallback>{user.fullName ? user.fullName : 'User'}</Avatar.Fallback>
</Avatar.Root>
<div class="flex flex-col space-y-2">
<p class="text-center text-2xl md:text-left dark:text-white">
{user.fullName}
@@ -1,21 +0,0 @@
<script lang="ts">
interface Props {
name?: string;
src?: string;
className?: string;
shape?: string;
width?: string;
height?: string;
}
let {
name = '',
src = '',
className = '',
shape = 'rounded-full',
width = 'w-10',
height = 'h-10'
}: Props = $props();
</script>
<img alt={name} {src} class="{className} {width} {height} {shape}" />
@@ -8,8 +8,9 @@
import { t } from '$lib/utils/functions/translations';
import EmptyState from './EmptyState.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import type { StudentOverview } from '$lib/utils/types/analytics';
import { shortenName } from '$lib/utils/functions/string';
interface Props {
students?: StudentOverview[];
@@ -97,7 +98,13 @@
class="sticky left-0 z-10 min-w-[200px] whitespace-nowrap bg-white px-4 py-3 transition-colors group-hover:bg-gray-50 dark:bg-neutral-800 dark:group-hover:bg-gray-700"
>
<div class="flex items-center gap-3">
<Avatar src={student.profile.avatar_url} name={student.profile.fullname} width="w-8" height="h-8" />
<Avatar.Root class="size-8">
<Avatar.Image
src={student.profile.avatar_url ? student.profile.avatar_url : '/logo-192.png'}
alt={student.profile.fullname ? student.profile.fullname : 'Student'}
/>
<Avatar.Fallback>{shortenName(student.profile.fullname) || 'S'}</Avatar.Fallback>
</Avatar.Root>
<div class="min-w-0 flex-1">
<p class="truncate font-medium text-gray-900 dark:text-white">
{student.profile.fullname}
@@ -5,8 +5,9 @@
import type { PostgrestSingleResponse, RealtimePostgresChangesPayload } from '@supabase/supabase-js';
import { group } from '$lib/components/Course/store';
import Avatar from '$lib/components/Avatar/index.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { shortenName } from '$lib/utils/functions/string';
import DeleteModal from '$lib/components/Modal/DeleteModal.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { lesson, lessonCommentsChannel } from '$lib/components/Course/components/Lesson/store/lessons';
@@ -62,7 +63,7 @@
id: 0,
comment: comment,
name: $t('course.navItem.lessons.comments.you'),
avatar: $profile.avatar_url,
avatar: $profile.avatarUrl,
commentAt: new Date(),
groupmember_id: groupmember.id
},
@@ -270,7 +271,13 @@
</div>
<div>
<div class="flex h-full items-start gap-3">
<Avatar src={$profile.avatar_url} name={$profile.fullname} width="w-8" height="h-8" className="mt-2" />
<Avatar.Root class="mt-2 h-8 w-8">
<Avatar.Image
src={$profile.avatarUrl ? $profile.avatarUrl : '/logo-192.png'}
alt={$profile.fullname ? $profile.fullname : 'User'}
/>
<Avatar.Fallback>{shortenName($profile.fullname) || 'U'}</Avatar.Fallback>
</Avatar.Root>
<div class="h-full w-full">
<TextArea
label={$t('course.navItem.lessons.comments.text_area_title')}
@@ -293,7 +300,13 @@
<div class="my-10">
{#each comments as commentItem}
<div class="mt-2 flex items-start gap-3 pb-2">
<Avatar src={commentItem.avatar} name={commentItem.name} width="w-8" height="h-8" />
<Avatar.Root class="h-8 w-8">
<Avatar.Image
src={commentItem.avatar ? commentItem.avatar : '/logo-192.png'}
alt={commentItem.name ? commentItem.name : 'User'}
/>
<Avatar.Fallback>{shortenName(commentItem.name) || 'U'}</Avatar.Fallback>
</Avatar.Root>
<div class="w-full rounded-md border px-4 pb-4 pt-2 dark:border-neutral-700">
<div class="flex items-center justify-between gap-2">
@@ -349,7 +362,7 @@
/>
</div>
{:else}
<article class="prose max-w-[300px] sm:prose-sm dark:text-white">
<article class="prose sm:prose-sm max-w-[300px] dark:text-white">
{commentItem.comment}
</article>
{/if}
@@ -7,8 +7,9 @@
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import ReviewFormEditor from './ReviewFormEditor.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import { processErrors } from '$lib/utils/functions/validator';
import { shortenName } from '$lib/utils/functions/string';
import { t } from '$lib/utils/functions/translations';
import type { Course } from '$lib/utils/types';
@@ -98,7 +99,13 @@
{#if review.id !== reviewToExpand}
<!-- the headers -->
<div class="flex w-full items-center justify-between">
<Avatar src={review.avatar_url} name={review.name} className="mt-1" />
<Avatar.Root class="mt-1 h-10 w-10">
<Avatar.Image
src={review.avatar_url ? review.avatar_url : '/logo-192.png'}
alt={review.name ? review.name : 'Reviewer'}
/>
<Avatar.Fallback>{shortenName(review.name) || 'R'}</Avatar.Fallback>
</Avatar.Root>
<p class="text-sm">{review.name}</p>
<IconButton value="expand" onClick={() => onExpand(review.id)} size={$isMobile ? 'large' : 'small'}>
@@ -21,9 +21,10 @@
import Chip from '../Chip/index.svelte';
import Modal from '../Modal/index.svelte';
import ImageRenderer from '../Org/ImageRenderer.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import PricingSection from './components/PricingSection.svelte';
import { PoweredBy } from '$lib/features/ui';
import { shortenName } from '$lib/utils/functions/string';
import SectionsDisplay from './components/SectionsDisplay.svelte';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
@@ -332,7 +333,13 @@
<div class="item-start my-2 flex w-2/4 flex-row">
<!-- image container -->
{#if review.avatar_url}
<Avatar src={review.avatar_url} name="Avatar" className="mt-1" />
<Avatar.Root class="mt-1 size-10">
<Avatar.Image
src={review.avatar_url ? review.avatar_url : '/logo-192.png'}
alt={review.name ? review.name : 'Avatar'}
/>
<Avatar.Fallback>{shortenName(review.name) || 'AV'}</Avatar.Fallback>
</Avatar.Root>
{/if}
<!-- profile content -->
@@ -401,7 +408,13 @@
<div class="item-start my-2 flex w-full flex-row">
<!-- image container -->
{#if review.avatar_url}
<Avatar src={review.avatar_url} name={review.name} className="mt-1" />
<Avatar.Root class="mt-1 h-10 w-10">
<Avatar.Image
src={review.avatar_url ? review.avatar_url : '/logo-192.png'}
alt={review.name ? review.name : 'Avatar'}
/>
<Avatar.Fallback>{shortenName(review.name) || 'AV'}</Avatar.Fallback>
</Avatar.Root>
{/if}
<!-- profile content -->
@@ -1,39 +1,30 @@
<script lang="ts">
import { page } from '$app/state';
import BellIcon from '@lucide/svelte/icons/bell';
import Avatar from '$lib/components/Avatar/index.svelte';
import TextChip from '$lib/components/Chip/Text.svelte';
import * as Avatar from '@cio/ui/base/avatar';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
import { shortenName } from '$lib/utils/functions/string';
interface Props {
navClass?: string;
}
let { navClass = '' }: Props = $props();
function shortenName(name: string) {
return name?.substring(0, 2)?.toUpperCase() || '';
}
</script>
<nav class="{navClass} bg-primary-700 flex h-[48px] w-full p-2 md:px-6">
<ul class="flex w-full items-center">
<div class="">
<a href={page.url.pathname} title={$t('navigation.goto_home')} id="logo" class="flex items-center text-lg">
{#if $currentOrg.avatar_url}
<Avatar
src={$currentOrg.avatar_url}
name={$currentOrg.name}
shape="rounded-md"
width="w-7"
height="h-7"
className="mr-2"
<Avatar.Root class="mr-2 h-7 w-7 rounded-md">
<Avatar.Image
src={$currentOrg.avatarUrl ? $currentOrg.avatarUrl : '/logo-192.png'}
alt={$currentOrg.name ? $currentOrg.name : 'Organization'}
/>
{:else}
<TextChip value={shortenName($currentOrg.name)} className="bg-primary-200 mr-2 dark:text-black" />
{/if}
<Avatar.Fallback>{shortenName($currentOrg.name)}</Avatar.Fallback>
</Avatar.Root>
<span class="line-clamp-1">
{$currentOrg.name}
</span>