@hed-hog/lms 0.0.357 → 0.0.358

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/course/course-operations-integration.service.d.ts +31 -0
  2. package/dist/course/course-operations-integration.service.d.ts.map +1 -1
  3. package/dist/course/course-operations-integration.service.js +286 -22
  4. package/dist/course/course-operations-integration.service.js.map +1 -1
  5. package/dist/course/course-operations.controller.d.ts +10 -0
  6. package/dist/course/course-operations.controller.d.ts.map +1 -0
  7. package/dist/course/course-operations.controller.js +67 -0
  8. package/dist/course/course-operations.controller.js.map +1 -0
  9. package/dist/course/course-structure.controller.d.ts +3 -1
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.service.d.ts +3 -1
  12. package/dist/course/course-structure.service.d.ts.map +1 -1
  13. package/dist/course/course-structure.service.js +13 -6
  14. package/dist/course/course-structure.service.js.map +1 -1
  15. package/dist/course/course.module.d.ts.map +1 -1
  16. package/dist/course/course.module.js +4 -1
  17. package/dist/course/course.module.js.map +1 -1
  18. package/dist/course/dto/update-course-operations-config.dto.d.ts +6 -0
  19. package/dist/course/dto/update-course-operations-config.dto.d.ts.map +1 -0
  20. package/dist/course/dto/update-course-operations-config.dto.js +33 -0
  21. package/dist/course/dto/update-course-operations-config.dto.js.map +1 -0
  22. package/dist/course/lms-operations-task.subscriber.d.ts +13 -0
  23. package/dist/course/lms-operations-task.subscriber.d.ts.map +1 -0
  24. package/dist/course/lms-operations-task.subscriber.js +57 -0
  25. package/dist/course/lms-operations-task.subscriber.js.map +1 -0
  26. package/dist/enterprise/enterprise.service.js +1 -1
  27. package/dist/enterprise/enterprise.service.js.map +1 -1
  28. package/dist/instructor/instructor.service.d.ts.map +1 -1
  29. package/dist/instructor/instructor.service.js +12 -3
  30. package/dist/instructor/instructor.service.js.map +1 -1
  31. package/hedhog/data/route.yaml +27 -0
  32. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +26 -4
  33. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +6 -0
  34. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +447 -31
  35. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +59 -47
  36. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +201 -35
  37. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +36 -5
  38. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +382 -0
  39. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +31 -1
  40. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +91 -20
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -0
  42. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +11 -3
  43. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -2
  44. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +2 -2
  45. package/hedhog/frontend/app/courses/page.tsx.ejs +21 -88
  46. package/hedhog/frontend/app/enterprise/page.tsx.ejs +18 -6
  47. package/hedhog/frontend/app/exams/[id]/page.tsx.ejs +5 -4
  48. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +16 -10
  49. package/package.json +8 -8
  50. package/src/course/course-operations-integration.service.ts +460 -22
  51. package/src/course/course-operations.controller.ts +45 -0
  52. package/src/course/course-structure.service.ts +5 -1
  53. package/src/course/course.module.ts +4 -1
  54. package/src/course/dto/update-course-operations-config.dto.ts +16 -0
  55. package/src/course/lms-operations-task.subscriber.ts +44 -0
  56. package/src/enterprise/enterprise.service.ts +1 -1
  57. package/src/instructor/instructor.service.ts +12 -3
@@ -73,7 +73,7 @@ import {
73
73
  import { useTranslations } from 'next-intl';
74
74
  import { useRouter } from 'next/navigation';
75
75
  import { useEffect, useMemo, useState } from 'react';
76
- import { useForm, useWatch } from 'react-hook-form';
76
+ import { useForm } from 'react-hook-form';
77
77
  import { toast } from 'sonner';
78
78
  import { CourseAvatar } from '../_components/course-avatar';
79
79
  import {
@@ -348,7 +348,6 @@ export default function CursosPage() {
348
348
  const [newCategorySlugTouched, setNewCategorySlugTouched] = useState(false);
349
349
 
350
350
  // Data
351
- const [editingCurso, setEditingCurso] = useState<Curso | null>(null);
352
351
  const [cursoToDelete, setCursoToDelete] = useState<Curso | null>(null);
353
352
  const [cachedCourseList, setCachedCourseList] =
354
353
  useState<ApiCourseList | null>(null);
@@ -380,7 +379,6 @@ export default function CursosPage() {
380
379
  resolver: zodResolver(getCourseSheetSchema(t)),
381
380
  defaultValues: DEFAULT_COURSE_FORM_VALUES,
382
381
  });
383
- const watchedFormValues = useWatch({ control: form.control });
384
382
 
385
383
  const {
386
384
  data: courseList,
@@ -529,29 +527,6 @@ export default function CursosPage() {
529
527
  () => (effectiveCourseList?.data ?? []).map((item) => mapApiCourse(item)),
530
528
  [effectiveCourseList]
531
529
  );
532
- const previewCurso = useMemo(() => {
533
- if (!sheetOpen || !editingCurso) return null;
534
-
535
- return {
536
- ...editingCurso,
537
- nomeInterno: watchedFormValues.nomeInterno ?? editingCurso.nomeInterno,
538
- tituloComercial:
539
- watchedFormValues.tituloComercial ?? editingCurso.tituloComercial,
540
- descricao: watchedFormValues.descricao ?? editingCurso.descricao,
541
- primaryColor: watchedFormValues.primaryColor ?? editingCurso.primaryColor,
542
- secondaryColor:
543
- watchedFormValues.secondaryColor ?? editingCurso.secondaryColor,
544
- nivel:
545
- (watchedFormValues.nivel as Curso['nivel'] | undefined) ??
546
- editingCurso.nivel,
547
- status:
548
- (watchedFormValues.status as Curso['status'] | undefined) ??
549
- editingCurso.status,
550
- tipoCurso: watchedFormValues.offeringType ?? editingCurso.tipoCurso,
551
- categorias: watchedFormValues.categorias ?? editingCurso.categorias,
552
- } satisfies Curso;
553
- }, [editingCurso, sheetOpen, watchedFormValues]);
554
-
555
530
  const categoryOptions = useMemo<CourseCategoryOption[]>(
556
531
  () =>
557
532
  (categoryListData?.data ?? [])
@@ -573,13 +548,7 @@ export default function CursosPage() {
573
548
  [categoryOptions]
574
549
  );
575
550
 
576
- const paginatedCursos = useMemo(() => {
577
- if (!previewCurso) return cursos;
578
-
579
- return cursos.map((curso) =>
580
- curso.id === previewCurso.id ? previewCurso : curso
581
- );
582
- }, [cursos, previewCurso]);
551
+ const paginatedCursos = cursos;
583
552
  const selectedCourses = useMemo(
584
553
  () => cursos.filter((curso) => selectedIds.has(curso.id)),
585
554
  [cursos, selectedIds]
@@ -652,32 +621,13 @@ export default function CursosPage() {
652
621
  // ── CRUD ──────────────────────────────────────────────────────────────────
653
622
 
654
623
  function openCreateSheet() {
655
- setEditingCurso(null);
656
624
  form.reset(DEFAULT_COURSE_FORM_VALUES);
657
625
  setSheetOpen(true);
658
626
  }
659
627
 
660
- function openEditSheet(curso: Curso, e?: React.MouseEvent) {
628
+ function goToCourseDetails(curso: Curso, e?: React.MouseEvent) {
661
629
  e?.stopPropagation();
662
- setEditingCurso(curso);
663
- form.reset({
664
- nomeInterno: curso.nomeInterno,
665
- slug: curso.slug,
666
- code: curso.code || '',
667
- tituloComercial: curso.tituloComercial,
668
- descricao: curso.descricao,
669
- primaryColor: curso.primaryColor || '#1D4ED8',
670
- secondaryColor: curso.secondaryColor || '#111827',
671
- nivel: curso.nivel,
672
- status: curso.status,
673
- offeringType: curso.tipoCurso,
674
- categorias: curso.categorias,
675
- operationsProjectId: curso.operationsProjectId
676
- ? String(curso.operationsProjectId)
677
- : '',
678
- logoFileId: curso.logoFileId ?? null,
679
- });
680
- setSheetOpen(true);
630
+ router.push(`/lms/courses/${curso.id}`);
681
631
  }
682
632
 
683
633
  async function onSubmit(data: CourseSheetFormValues) {
@@ -703,27 +653,15 @@ export default function CursosPage() {
703
653
  logoFileId: data.logoFileId ?? null,
704
654
  };
705
655
 
706
- if (editingCurso) {
707
- await request({
708
- url: `/lms/courses/${editingCurso.id}`,
709
- method: 'PATCH',
710
- data: payload,
711
- });
712
- toast.success(t('toasts.courseUpdated'));
713
- } else {
714
- const response = await request<ApiCourse>({
715
- url: '/lms/courses',
716
- method: 'POST',
717
- data: payload,
718
- });
656
+ const response = await request<ApiCourse>({
657
+ url: '/lms/courses',
658
+ method: 'POST',
659
+ data: payload,
660
+ });
719
661
 
720
- toast.success(t('toasts.courseCreated'));
721
- if (response.data?.id) {
722
- setTimeout(
723
- () => router.push(`/lms/courses/${response.data.id}`),
724
- 400
725
- );
726
- }
662
+ toast.success(t('toasts.courseCreated'));
663
+ if (response.data?.id) {
664
+ setTimeout(() => router.push(`/lms/courses/${response.data.id}`), 400);
727
665
  }
728
666
 
729
667
  await refetchCourses();
@@ -1276,14 +1214,14 @@ export default function CursosPage() {
1276
1214
  <DropdownMenuItem
1277
1215
  onClick={(e) => {
1278
1216
  e.stopPropagation();
1279
- router.push(`/lms/courses/${curso.id}`);
1217
+ goToCourseDetails(curso, e);
1280
1218
  }}
1281
1219
  >
1282
1220
  <Eye className="mr-2 size-4" />{' '}
1283
1221
  {t('table.actions.viewDetails')}
1284
1222
  </DropdownMenuItem>
1285
1223
  <DropdownMenuItem
1286
- onClick={(e) => openEditSheet(curso, e)}
1224
+ onClick={(e) => goToCourseDetails(curso, e)}
1287
1225
  >
1288
1226
  <Pencil className="mr-2 size-4" />{' '}
1289
1227
  {t('table.actions.edit')}
@@ -1476,15 +1414,13 @@ export default function CursosPage() {
1476
1414
  </DropdownMenuTrigger>
1477
1415
  <DropdownMenuContent align="end" className="w-48">
1478
1416
  <DropdownMenuItem
1479
- onClick={() =>
1480
- router.push(`/lms/courses/${curso.id}`)
1481
- }
1417
+ onClick={(e) => goToCourseDetails(curso, e)}
1482
1418
  >
1483
1419
  <Eye className="mr-2 size-4" />{' '}
1484
1420
  {t('table.actions.viewDetails')}
1485
1421
  </DropdownMenuItem>
1486
1422
  <DropdownMenuItem
1487
- onClick={() => openEditSheet(curso)}
1423
+ onClick={(e) => goToCourseDetails(curso, e)}
1488
1424
  >
1489
1425
  <Pencil className="mr-2 size-4" />{' '}
1490
1426
  {t('table.actions.edit')}
@@ -1529,21 +1465,18 @@ export default function CursosPage() {
1529
1465
  </div>
1530
1466
  )}
1531
1467
 
1532
- {/* ── Create / Edit Sheet ────────────────────────────────────────────── */}
1468
+ {/* ── Create Sheet ───────────────────────────────────────────────────── */}
1533
1469
  <CourseFormSheet
1534
- key={editingCurso ? `edit-${editingCurso.id}` : 'new'}
1470
+ key="new"
1535
1471
  open={sheetOpen}
1536
- onOpenChange={(open) => {
1537
- setSheetOpen(open);
1538
- if (!open) setEditingCurso(null);
1539
- }}
1540
- editing={!!editingCurso}
1472
+ onOpenChange={setSheetOpen}
1473
+ editing={false}
1541
1474
  saving={saving}
1542
1475
  form={form}
1543
1476
  onSubmit={onSubmit}
1544
1477
  categories={categoryOptions}
1545
1478
  onCreateCategory={openCreateCategorySheet}
1546
- initialLogoFileId={editingCurso?.logoFileId ?? null}
1479
+ initialLogoFileId={null}
1547
1480
  t={t}
1548
1481
  />
1549
1482
 
@@ -588,12 +588,24 @@ export default function EnterprisePage() {
588
588
 
589
589
  {/* CRM */}
590
590
  {account.crmAccountName && (
591
- <p className="truncate text-xs text-muted-foreground">
592
- <span className="font-medium">
593
- {t.has('labels.crm') ? t('labels.crm') : 'CRM'}:
594
- </span>{' '}
595
- {account.crmAccountName}
596
- </p>
591
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
592
+ <Avatar className="h-4 w-4 shrink-0 rounded-full">
593
+ <AvatarImage
594
+ src={getPersonAvatarUrl(account.crmAccount?.avatarId ?? null)}
595
+ alt={account.crmAccountName}
596
+ className="object-cover"
597
+ />
598
+ <AvatarFallback className="bg-slate-100 text-[8px] font-semibold uppercase text-slate-700">
599
+ {getInitials(account.crmAccountName)}
600
+ </AvatarFallback>
601
+ </Avatar>
602
+ <p className="truncate">
603
+ <span className="font-medium">
604
+ {t.has('labels.crm') ? t('labels.crm') : 'CRM'}:
605
+ </span>{' '}
606
+ {account.crmAccountName}
607
+ </p>
608
+ </div>
597
609
  )}
598
610
 
599
611
  {/* Stats grid */}
@@ -1,11 +1,12 @@
1
1
  import { redirect } from 'next/navigation';
2
2
 
3
3
  type ExamPageProps = {
4
- params: {
4
+ params: Promise<{
5
5
  id: string;
6
- };
6
+ }>;
7
7
  };
8
8
 
9
- export default function ExamPage({ params }: ExamPageProps) {
10
- redirect(`/lms/exams/${params.id}/questions`);
9
+ export default async function ExamPage({ params }: ExamPageProps) {
10
+ const { id } = await params;
11
+ redirect(`/lms/exams/${id}/questions`);
11
12
  }
@@ -325,7 +325,7 @@ export function InstructorFormSheet({
325
325
  }
326
326
 
327
327
  const params = new URLSearchParams({
328
- page: String(turmasPage),
328
+ page: String(Math.max(turmasPage, 1)),
329
329
  pageSize: String(TURMAS_PAGE_SIZE),
330
330
  });
331
331
 
@@ -350,12 +350,9 @@ export function InstructorFormSheet({
350
350
  placeholderData: (prev) => prev,
351
351
  });
352
352
  const turmasData = turmasResult?.data ?? [];
353
- const turmasLastPage = turmasResult?.lastPage ?? 1;
353
+ const turmasLastPage = Math.max(turmasResult?.lastPage ?? 1, 1);
354
354
 
355
- const {
356
- data: avaliacoesResult,
357
- isLoading: loadingAvaliacoes,
358
- } = useQuery<{
355
+ const { data: avaliacoesResult, isLoading: loadingAvaliacoes } = useQuery<{
359
356
  data: EvaluationItem[];
360
357
  total: number;
361
358
  lastPage: number;
@@ -399,6 +396,11 @@ export function InstructorFormSheet({
399
396
  useEffect(() => {
400
397
  if (turmasPage > turmasLastPage) {
401
398
  setTurmasPage(turmasLastPage);
399
+ return;
400
+ }
401
+
402
+ if (turmasPage < 1) {
403
+ setTurmasPage(1);
402
404
  }
403
405
  }, [turmasLastPage, turmasPage]);
404
406
 
@@ -1170,7 +1172,7 @@ export function InstructorFormSheet({
1170
1172
  variant="outline"
1171
1173
  size="icon"
1172
1174
  disabled={turmasPage <= 1}
1173
- onClick={() => setTurmasPage((p) => p - 1)}
1175
+ onClick={() => setTurmasPage((p) => Math.max(p - 1, 1))}
1174
1176
  >
1175
1177
  <ChevronLeft className="h-4 w-4" />
1176
1178
  </Button>
@@ -1182,7 +1184,9 @@ export function InstructorFormSheet({
1182
1184
  variant="outline"
1183
1185
  size="icon"
1184
1186
  disabled={turmasPage >= turmasLastPage}
1185
- onClick={() => setTurmasPage((p) => p + 1)}
1187
+ onClick={() =>
1188
+ setTurmasPage((p) => Math.min(p + 1, turmasLastPage))
1189
+ }
1186
1190
  >
1187
1191
  <ChevronRight className="h-4 w-4" />
1188
1192
  </Button>
@@ -1249,7 +1253,8 @@ export function InstructorFormSheet({
1249
1253
  />
1250
1254
  ))}
1251
1255
  <span className="ml-1 text-xs font-medium tabular-nums text-muted-foreground">
1252
- {ev.score.toFixed(1)}{t('form.evaluations.of')}10
1256
+ {ev.score.toFixed(1)}
1257
+ {t('form.evaluations.of')}10
1253
1258
  </span>
1254
1259
  </div>
1255
1260
  </div>
@@ -1272,7 +1277,8 @@ export function InstructorFormSheet({
1272
1277
  {/* Evaluator + date */}
1273
1278
  <div className="flex items-center gap-2 text-xs text-muted-foreground">
1274
1279
  <span>
1275
- {ev.evaluatorName ?? t('form.evaluations.anonymous')}
1280
+ {ev.evaluatorName ??
1281
+ t('form.evaluations.anonymous')}
1276
1282
  </span>
1277
1283
  <span>·</span>
1278
1284
  <span>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.357",
3
+ "version": "0.0.358",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,16 +9,16 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.6",
13
- "@hed-hog/api-types": "0.0.1",
14
12
  "@hed-hog/api": "0.0.8",
15
- "@hed-hog/category": "0.0.356",
13
+ "@hed-hog/api-prisma": "0.0.6",
16
14
  "@hed-hog/api-locale": "0.0.14",
15
+ "@hed-hog/api-types": "0.0.1",
17
16
  "@hed-hog/api-pagination": "0.0.7",
18
- "@hed-hog/crm": "0.0.356",
19
- "@hed-hog/finance": "0.0.356",
20
- "@hed-hog/core": "0.0.356",
21
- "@hed-hog/queue": "0.0.356"
17
+ "@hed-hog/crm": "0.0.358",
18
+ "@hed-hog/category": "0.0.358",
19
+ "@hed-hog/queue": "0.0.358",
20
+ "@hed-hog/finance": "0.0.358",
21
+ "@hed-hog/core": "0.0.358"
22
22
  },
23
23
  "exports": {
24
24
  ".": {