331 lines
11 KiB
TypeScript
331 lines
11 KiB
TypeScript
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
|
|
import type { Program, ProgramDetail, ProgramMember, ProgramCourse, ProgramNewsfeed } from '../utils/types';
|
|
import type {
|
|
TCreateProgram,
|
|
TUpdateProgram,
|
|
TAddProgramMembers,
|
|
TUpdateProgramMember,
|
|
TAddCourseToProgram
|
|
} from '@cio/utils/validation/program';
|
|
import { currentOrg } from '$lib/utils/store/org';
|
|
import { get } from 'svelte/store';
|
|
import { isStudentExperience } from '$lib/utils/store/app';
|
|
import { snackbar } from '$features/ui/snackbar/store';
|
|
import { t } from '$lib/utils/functions/translations';
|
|
|
|
class ProgramApi extends BaseApiWithErrors {
|
|
programs = $state<Program[]>([]);
|
|
program = $state<ProgramDetail | null>(null);
|
|
members = $state<ProgramMember[]>([]);
|
|
courses = $state<ProgramCourse[]>([]);
|
|
newsfeed = $state<ProgramNewsfeed | null>(null);
|
|
isProgramShellLoading = $state(false);
|
|
currentProgramId = $state<string | null>(null);
|
|
|
|
loadedProgramId = $state<string | null>(null);
|
|
loadedMembersProgramId = $state<string | null>(null);
|
|
loadedCoursesProgramId = $state<string | null>(null);
|
|
loadedCoursesStudentExperience = $state<boolean | null>(null);
|
|
loadedNewsfeedProgramId = $state<string | null>(null);
|
|
|
|
syncProgramCourseCount(programId: string, courseCount: number) {
|
|
this.programs = this.programs.map((program) => (program.id === programId ? { ...program, courseCount } : program));
|
|
}
|
|
|
|
async listPrograms() {
|
|
const org = get(currentOrg);
|
|
if (!org.id) return;
|
|
|
|
await this.execute<typeof classroomio.program.$get>({
|
|
requestFn: () => classroomio.program.$get({ query: { organizationId: org.id } }),
|
|
onSuccess: (data) => {
|
|
this.programs = data.data;
|
|
},
|
|
logContext: 'listPrograms'
|
|
});
|
|
}
|
|
|
|
async getProgram(programId: string, force = false) {
|
|
if (!force && this.loadedProgramId === programId) return;
|
|
|
|
await this.execute<(typeof classroomio.program)[':programId']['$get']>({
|
|
requestFn: () => classroomio.program[':programId'].$get({ param: { programId } }),
|
|
onSuccess: (data) => {
|
|
this.program = data.data;
|
|
this.loadedProgramId = programId;
|
|
},
|
|
logContext: 'getProgram'
|
|
});
|
|
}
|
|
|
|
resetProgramShell(programId?: string) {
|
|
this.currentProgramId = programId ?? null;
|
|
this.program = null;
|
|
this.members = [];
|
|
this.courses = [];
|
|
this.newsfeed = null;
|
|
this.loadedProgramId = null;
|
|
this.loadedMembersProgramId = null;
|
|
this.loadedCoursesProgramId = null;
|
|
this.loadedCoursesStudentExperience = null;
|
|
this.loadedNewsfeedProgramId = null;
|
|
}
|
|
|
|
async ensureProgramShell(programId: string, force = false) {
|
|
const studentExperience = get(isStudentExperience);
|
|
const isShellReady =
|
|
this.loadedProgramId === programId &&
|
|
this.loadedMembersProgramId === programId &&
|
|
this.loadedCoursesProgramId === programId &&
|
|
this.loadedCoursesStudentExperience === studentExperience;
|
|
|
|
if (!force && isShellReady) {
|
|
return;
|
|
}
|
|
|
|
const isProgramChanged = this.currentProgramId !== programId;
|
|
if (force || isProgramChanged) {
|
|
this.resetProgramShell(programId);
|
|
}
|
|
|
|
this.currentProgramId = programId;
|
|
this.isProgramShellLoading = true;
|
|
|
|
try {
|
|
await Promise.all([
|
|
this.getProgram(programId, force),
|
|
this.listMembers(programId, force),
|
|
this.listCourses(programId, force)
|
|
]);
|
|
} finally {
|
|
this.isProgramShellLoading = false;
|
|
}
|
|
}
|
|
|
|
async createProgram(data: TCreateProgram) {
|
|
const org = get(currentOrg);
|
|
if (!org.id) return;
|
|
|
|
await this.execute<typeof classroomio.program.$post>({
|
|
requestFn: () =>
|
|
classroomio.program.$post({
|
|
json: { ...data, organizationId: org.id }
|
|
}),
|
|
onSuccess: (res) => {
|
|
this.programs = [res.data, ...this.programs];
|
|
snackbar.success(t.get('programs.create_success') || 'Program created');
|
|
},
|
|
logContext: 'createProgram'
|
|
});
|
|
}
|
|
|
|
async updateProgram(programId: string, data: TUpdateProgram) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['$put']>({
|
|
requestFn: () => classroomio.program[':programId'].$put({ param: { programId }, json: data }),
|
|
onSuccess: (res) => {
|
|
this.program = res.data;
|
|
this.programs = this.programs.map((p) => (p.id === programId ? { ...p, ...res.data } : p));
|
|
snackbar.success(t.get('programs.update_success') || 'Program updated');
|
|
},
|
|
onError: (result) => {
|
|
const message = typeof result === 'string' ? result : result.error;
|
|
snackbar.error(message || 'Failed to update program');
|
|
},
|
|
logContext: 'updateProgram'
|
|
});
|
|
}
|
|
|
|
async deleteProgram(programId: string) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['$delete']>({
|
|
requestFn: () => classroomio.program[':programId'].$delete({ param: { programId } }),
|
|
onSuccess: () => {
|
|
this.programs = this.programs.filter((p) => p.id !== programId);
|
|
snackbar.success(t.get('programs.delete_success') || 'Program deleted');
|
|
},
|
|
logContext: 'deleteProgram'
|
|
});
|
|
}
|
|
|
|
// Members
|
|
|
|
async listMembers(programId: string, force = false) {
|
|
if (!force && this.loadedMembersProgramId === programId) return;
|
|
|
|
await this.execute<(typeof classroomio.program)[':programId']['members']['$get']>({
|
|
requestFn: () => classroomio.program[':programId'].members.$get({ param: { programId } }),
|
|
onSuccess: (data) => {
|
|
this.members = data.data;
|
|
this.loadedMembersProgramId = programId;
|
|
},
|
|
logContext: 'listProgramMembers'
|
|
});
|
|
}
|
|
|
|
async addMembers(programId: string, data: TAddProgramMembers) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['members']['$post']>({
|
|
requestFn: () => classroomio.program[':programId'].members.$post({ param: { programId }, json: data }),
|
|
onSuccess: async () => {
|
|
await this.listMembers(programId, true);
|
|
snackbar.success(t.get('programs.members_added') || 'Members added');
|
|
},
|
|
logContext: 'addProgramMembers'
|
|
});
|
|
}
|
|
|
|
async updateMember(programId: string, memberId: string, data: TUpdateProgramMember) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['members'][':memberId']['$put']>({
|
|
requestFn: () =>
|
|
classroomio.program[':programId'].members[':memberId'].$put({
|
|
param: { programId, memberId },
|
|
json: data
|
|
}),
|
|
onSuccess: (res) => {
|
|
this.members = this.members.map((m) => (m.id === memberId ? { ...m, ...res.data } : m));
|
|
},
|
|
logContext: 'updateProgramMember'
|
|
});
|
|
}
|
|
|
|
async removeMember(programId: string, memberId: string) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['members'][':memberId']['$delete']>({
|
|
requestFn: () =>
|
|
classroomio.program[':programId'].members[':memberId'].$delete({
|
|
param: { programId, memberId }
|
|
}),
|
|
onSuccess: () => {
|
|
this.members = this.members.filter((m) => m.id !== memberId);
|
|
snackbar.success(t.get('programs.member_removed') || 'Member removed');
|
|
},
|
|
logContext: 'removeProgramMember'
|
|
});
|
|
}
|
|
|
|
// Courses
|
|
|
|
async listCourses(programId: string, force = false) {
|
|
const studentExperience = get(isStudentExperience);
|
|
|
|
if (
|
|
!force &&
|
|
this.loadedCoursesProgramId === programId &&
|
|
this.loadedCoursesStudentExperience === studentExperience
|
|
) {
|
|
return;
|
|
}
|
|
|
|
await this.execute<(typeof classroomio.program)[':programId']['courses']['$get']>({
|
|
requestFn: () => classroomio.program[':programId'].courses.$get({ param: { programId } }),
|
|
onSuccess: (data) => {
|
|
this.courses = data.data;
|
|
this.syncProgramCourseCount(programId, data.data.length);
|
|
this.loadedCoursesProgramId = programId;
|
|
this.loadedCoursesStudentExperience = studentExperience;
|
|
},
|
|
logContext: 'listProgramCourses'
|
|
});
|
|
}
|
|
|
|
async addCourse(programId: string, data: TAddCourseToProgram) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['courses']['$post']>({
|
|
requestFn: () => classroomio.program[':programId'].courses.$post({ param: { programId }, json: data }),
|
|
onSuccess: async () => {
|
|
await this.listCourses(programId, true);
|
|
this.syncProgramCourseCount(programId, this.courses.length);
|
|
snackbar.success(t.get('programs.course_added') || 'Course added');
|
|
},
|
|
logContext: 'addCourseToProgram'
|
|
});
|
|
}
|
|
|
|
async addCourses(programId: string, courseIds: string[]) {
|
|
if (courseIds.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
this.error = null;
|
|
this.success = false;
|
|
|
|
try {
|
|
for (const courseId of courseIds) {
|
|
const response = await classroomio.program[':programId'].courses.$post({
|
|
param: { programId },
|
|
json: { courseId }
|
|
});
|
|
const result = (await response.json()) as { success?: boolean; error?: string };
|
|
|
|
if (!result.success) {
|
|
this.error = result.error ?? 'Failed to add course to program';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
await this.listCourses(programId, true);
|
|
this.syncProgramCourseCount(programId, this.courses.length);
|
|
|
|
const successMessage =
|
|
courseIds.length === 1
|
|
? t.get('programs.course_added') || 'Course added'
|
|
: t.get('programs.multiple_added') || 'Courses added';
|
|
|
|
snackbar.success(successMessage);
|
|
this.success = true;
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error in addCoursesToProgram:', error);
|
|
this.error = error instanceof Error ? error.message : 'Failed to add courses to program';
|
|
return false;
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
}
|
|
|
|
async removeCourse(programId: string, courseId: string) {
|
|
await this.execute<(typeof classroomio.program)[':programId']['courses'][':courseId']['$delete']>({
|
|
requestFn: () =>
|
|
classroomio.program[':programId'].courses[':courseId'].$delete({
|
|
param: { programId, courseId }
|
|
}),
|
|
onSuccess: () => {
|
|
this.courses = this.courses.filter((c) => c.courseId !== courseId);
|
|
this.syncProgramCourseCount(programId, this.courses.length);
|
|
snackbar.success(t.get('programs.course_removed') || 'Course removed');
|
|
},
|
|
logContext: 'removeCourseFromProgram'
|
|
});
|
|
}
|
|
|
|
// Newsfeed
|
|
|
|
async listNewsfeed(programId: string, force = false, cursor?: string) {
|
|
if (!force && this.loadedNewsfeedProgramId === programId) return;
|
|
|
|
await this.execute<(typeof classroomio.program)[':programId']['newsfeed']['$get']>({
|
|
requestFn: () =>
|
|
classroomio.program[':programId'].newsfeed.$get({
|
|
param: { programId },
|
|
query: { limit: '10', ...(cursor ? { cursor } : {}) }
|
|
}),
|
|
onSuccess: (data) => {
|
|
this.newsfeed = data.data;
|
|
this.loadedNewsfeedProgramId = programId;
|
|
},
|
|
logContext: 'listProgramNewsfeed'
|
|
});
|
|
}
|
|
|
|
// LMS — enrolled programs
|
|
|
|
async listEnrolledPrograms() {
|
|
await this.execute<typeof classroomio.program.enrolled.$get>({
|
|
requestFn: () => classroomio.program.enrolled.$get({}),
|
|
onSuccess: (data) => {
|
|
this.programs = data.data;
|
|
},
|
|
logContext: 'listEnrolledPrograms'
|
|
});
|
|
}
|
|
}
|
|
|
|
export const programApi = new ProgramApi();
|