feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -489,8 +489,8 @@ class OrgApi extends BaseApiWithErrors {
snackbar.error(result);
return;
}
if ('error' in result && 'field' in result) {
this.errors[result.field as string] = result.error;
if ('error' in result) {
snackbar.error(result.error);
}
}
});
@@ -0,0 +1,52 @@
<script lang="ts">
import * as Chart from '@cio/ui/base/chart';
import { t } from '$lib/utils/functions/translations';
import type { LoginActivityData } from '$features/org/utils/types';
interface Props {
data: LoginActivityData;
}
let { data }: Props = $props();
const chartConfig = $derived({
count: {
label: $t('dashboard.login_activity_logins'),
color: 'var(--chart-1)'
}
} satisfies Chart.ChartConfig);
const series = $derived([
{
key: 'count',
value: 'count',
label: chartConfig.count.label,
color: 'var(--color-count)'
}
]);
const hasData = $derived(data !== null && data.some((d) => d.count > 0));
</script>
<div class="bg-card flex min-h-[45vh] w-full flex-col rounded-xl border p-3 md:p-5 dark:text-white">
<div class="mb-4">
<h3 class="text-lg font-semibold tracking-tight">
{$t('dashboard.login_activity_title')}
</h3>
<p class="ui:text-muted-foreground mt-1 text-sm">
{$t('dashboard.login_activity_description')}
</p>
</div>
<div class="flex h-full flex-col justify-center">
{#if hasData}
<Chart.ChartContainer class="h-[260px] w-full" config={chartConfig}>
<Chart.BarChart {data} x="day" axis="x" {series} />
</Chart.ChartContainer>
{:else}
<div class="ui:text-muted-foreground flex h-[260px] items-center justify-center text-sm">
{$t('dashboard.login_activity_no_data')}
</div>
{/if}
</div>
</div>
@@ -319,8 +319,7 @@ export function mapPublicCoursesToLandingPageCourses(courses: OrgPublicCourses):
const tags = Array.isArray(courseRecord.tags)
? (courseRecord.tags as OrgLandingPageProps['courses'][number]['tags'])
: undefined;
const courseSlug =
typeof courseRecord.slug === 'string' && courseRecord.slug.length > 0 ? courseRecord.slug : course.id;
const courseSlug = typeof courseRecord.slug === 'string' && courseRecord.slug.length > 0 ? courseRecord.slug : '';
const courseCost = typeof courseRecord.cost === 'number' ? courseRecord.cost : undefined;
const courseCurrency = typeof courseRecord.currency === 'string' ? courseRecord.currency : undefined;
const lessonCount = typeof courseRecord.lessonCount === 'number' ? courseRecord.lessonCount : undefined;
@@ -356,7 +355,7 @@ export function mapPublicCoursesToLandingPageCourses(courses: OrgPublicCourses):
: undefined,
tags,
image,
link: `/course/${courseSlug}`,
link: courseSlug ? `/course/${courseSlug}` : undefined,
price,
duration,
level
@@ -57,6 +57,10 @@ export type GetDashStatsRequest = typeof classroomio.dash.stats.$get;
export type DashStatsResponse = InferResponseType<GetDashStatsRequest> | null;
export type DashStatsSuccess = Extract<InferResponseType<GetDashStatsRequest>, { success: true }>;
export type GetLoginActivityRequest = (typeof classroomio.dash)['login-activity']['$get'];
export type LoginActivitySuccess = Extract<InferResponseType<GetLoginActivityRequest>, { success: true }>;
export type LoginActivityData = LoginActivitySuccess['data'];
// invite team types
export type InviteTeamRequest = (typeof classroomio.organization)['team']['invite']['$post'];
export type InviteTeamSuccess = Extract<InferResponseType<InviteTeamRequest>, { success: true }>;