feat(cohorts): rename Programs to Cohorts with data migration (#694)
* feat(cohorts): rename Programs to Cohorts with data migration - Add cohort database tables and Drizzle migration alongside legacy program tables - Introduce migrate-programs-to-cohorts script to copy existing program data safely - Rename API routes (/cohort), services, queries, validation, and error codes - Rename dashboard feature module, routes (/cohorts), navigation, and translations - Update docs, emails, and internal compliance cron endpoints for cohort terminology - Keep legacy program tables in place for rollback until explicitly removed Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): restore migrate:answer-data script alongside cohort migration Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): cast program enums when migrating data to cohort tables Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): truncate cohort tables before program-to-cohort copy Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): report accurate row counts in programs-to-cohorts migration Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
fe9f8c441f
commit
ec61bfe7d9
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { cohortApi } from '$features/cohort/api';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { CohortsPage } from '$features/cohort/pages';
|
||||
|
||||
let searchValue = $state('');
|
||||
|
||||
$effect(() => {
|
||||
if ($profile.id) {
|
||||
cohortApi.listEnrolledCohorts();
|
||||
}
|
||||
});
|
||||
|
||||
const cohorts = $derived(cohortApi.cohorts);
|
||||
const filteredCohorts = $derived.by(() => {
|
||||
const normalizedSearch = searchValue.trim().toLowerCase();
|
||||
|
||||
return cohorts.filter((cohort) => {
|
||||
if (!normalizedSearch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [cohort.name, cohort.description].some((value) => value?.toLowerCase().includes(normalizedSearch));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t('lms_navigation.cohorts') || 'Cohorts'} - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Root class="w-full">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('lms_navigation.cohorts') || 'Cohorts'}</Page.Title>
|
||||
</Page.HeaderContent>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<CohortsPage
|
||||
cohorts={filteredCohorts}
|
||||
isLMS={true}
|
||||
isLoading={cohortApi.isLoading && cohorts.length === 0}
|
||||
bind:searchValue
|
||||
emptyTitle={$t('cohorts.lms.empty_title') || 'No cohorts yet'}
|
||||
emptyDescription={$t('cohorts.lms.empty_description')}
|
||||
/>
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
</Page.Root>
|
||||
@@ -1,52 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Page from '@cio/ui/base/page';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { programApi } from '$features/program/api';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { ProgramsPage } from '$features/program/pages';
|
||||
|
||||
let searchValue = $state('');
|
||||
|
||||
$effect(() => {
|
||||
if ($profile.id) {
|
||||
programApi.listEnrolledPrograms();
|
||||
}
|
||||
});
|
||||
|
||||
const programs = $derived(programApi.programs);
|
||||
const filteredPrograms = $derived.by(() => {
|
||||
const normalizedSearch = searchValue.trim().toLowerCase();
|
||||
|
||||
return programs.filter((program) => {
|
||||
if (!normalizedSearch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [program.name, program.description].some((value) => value?.toLowerCase().includes(normalizedSearch));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t('lms_navigation.programs') || 'Programs'} - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<Page.Root class="w-full">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>{$t('lms_navigation.programs') || 'Programs'}</Page.Title>
|
||||
</Page.HeaderContent>
|
||||
</Page.Header>
|
||||
<Page.Body>
|
||||
{#snippet child()}
|
||||
<ProgramsPage
|
||||
programs={filteredPrograms}
|
||||
isLMS={true}
|
||||
isLoading={programApi.isLoading && programs.length === 0}
|
||||
bind:searchValue
|
||||
emptyTitle={$t('programs.lms.empty_title') || 'No programs yet'}
|
||||
emptyDescription={$t('programs.lms.empty_description')}
|
||||
/>
|
||||
{/snippet}
|
||||
</Page.Body>
|
||||
</Page.Root>
|
||||
Reference in New Issue
Block a user