@hed-hog/lms 0.0.350 → 0.0.351

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 (160) hide show
  1. package/dist/certificate/certificate.controller.d.ts +2 -2
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +8 -6
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +5 -2
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +70 -6
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/course/course-structure.controller.d.ts +24 -10
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.controller.js +23 -2
  12. package/dist/course/course-structure.controller.js.map +1 -1
  13. package/dist/course/course-structure.service.d.ts +16 -8
  14. package/dist/course/course-structure.service.d.ts.map +1 -1
  15. package/dist/course/course-structure.service.js +61 -30
  16. package/dist/course/course-structure.service.js.map +1 -1
  17. package/dist/course/course-video-conversion.service.d.ts +37 -0
  18. package/dist/course/course-video-conversion.service.d.ts.map +1 -0
  19. package/dist/course/course-video-conversion.service.js +308 -0
  20. package/dist/course/course-video-conversion.service.js.map +1 -0
  21. package/dist/course/course.controller.d.ts +17 -0
  22. package/dist/course/course.controller.d.ts.map +1 -1
  23. package/dist/course/course.controller.js +23 -0
  24. package/dist/course/course.controller.js.map +1 -1
  25. package/dist/course/course.module.d.ts.map +1 -1
  26. package/dist/course/course.module.js +15 -2
  27. package/dist/course/course.module.js.map +1 -1
  28. package/dist/course/course.service.d.ts +15 -0
  29. package/dist/course/course.service.d.ts.map +1 -1
  30. package/dist/course/course.service.js +103 -49
  31. package/dist/course/course.service.js.map +1 -1
  32. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +5 -1
  33. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  34. package/dist/course/dto/create-course-structure-lesson.dto.js +16 -2
  35. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  36. package/dist/course/dto/create-course.dto.d.ts +1 -0
  37. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  38. package/dist/course/dto/create-course.dto.js +9 -0
  39. package/dist/course/dto/create-course.dto.js.map +1 -1
  40. package/dist/enterprise/enterprise.controller.d.ts +3 -3
  41. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  42. package/dist/enterprise/enterprise.controller.js +0 -1
  43. package/dist/enterprise/enterprise.controller.js.map +1 -1
  44. package/dist/enterprise/enterprise.service.d.ts +3 -3
  45. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  46. package/dist/evaluation/evaluation.service.js +9 -2
  47. package/dist/evaluation/evaluation.service.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/lms.module.d.ts.map +1 -1
  53. package/dist/lms.module.js +3 -0
  54. package/dist/lms.module.js.map +1 -1
  55. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts +6 -0
  56. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts.map +1 -0
  57. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js +33 -0
  58. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js.map +1 -0
  59. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts +6 -0
  60. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts.map +1 -0
  61. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js +33 -0
  62. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js.map +1 -0
  63. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts +38 -0
  64. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts.map +1 -0
  65. package/dist/video-resolution-profile/video-resolution-profile.controller.js +89 -0
  66. package/dist/video-resolution-profile/video-resolution-profile.controller.js.map +1 -0
  67. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts +26 -0
  68. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts.map +1 -0
  69. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js +160 -0
  70. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js.map +1 -0
  71. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts +3 -0
  72. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts.map +1 -0
  73. package/dist/video-resolution-profile/video-resolution-profile.module.js +26 -0
  74. package/dist/video-resolution-profile/video-resolution-profile.module.js.map +1 -0
  75. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts +45 -0
  76. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts.map +1 -0
  77. package/dist/video-resolution-profile/video-resolution-profile.service.js +117 -0
  78. package/dist/video-resolution-profile/video-resolution-profile.service.js.map +1 -0
  79. package/hedhog/data/menu.yaml +17 -0
  80. package/hedhog/data/route.yaml +133 -0
  81. package/hedhog/data/video_resolution_profile.yaml +7 -0
  82. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +269 -324
  83. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +124 -70
  84. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +7 -4
  85. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +2 -2
  86. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +2 -2
  87. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +34 -4
  88. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +28 -3
  89. package/hedhog/frontend/app/achievements/page.tsx.ejs +9 -3
  90. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +9 -3
  91. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +7 -3
  92. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +29 -8
  93. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +14 -0
  94. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +194 -9
  95. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +15 -5
  96. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +9 -5
  97. package/hedhog/frontend/app/classes/page.tsx.ejs +73 -47
  98. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +19 -9
  99. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +24 -1
  100. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +1 -1
  101. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +1 -1
  102. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +28 -16
  103. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +11 -6
  104. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +7 -4
  105. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -0
  106. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +24 -87
  107. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +892 -411
  108. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1004 -293
  109. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +11 -11
  110. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +62 -52
  111. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +2 -0
  112. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -6
  113. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +86 -1
  114. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +3 -0
  115. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +1 -0
  116. package/hedhog/frontend/app/courses/page.tsx.ejs +112 -89
  117. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +1 -1
  118. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +10 -3
  119. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +8 -4
  120. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +2 -2
  121. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +10 -4
  122. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +10 -3
  123. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +10 -3
  124. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +10 -3
  125. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +23 -9
  126. package/hedhog/frontend/app/exams/page.tsx.ejs +14 -6
  127. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +9 -3
  128. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +190 -17
  129. package/hedhog/frontend/app/layout.tsx.ejs +5 -1
  130. package/hedhog/frontend/app/paths/page.tsx.ejs +13 -5
  131. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +10 -10
  132. package/hedhog/frontend/app/training/page.tsx.ejs +13 -5
  133. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +607 -0
  134. package/hedhog/frontend/messages/en.json +250 -9
  135. package/hedhog/frontend/messages/pt.json +250 -9
  136. package/hedhog/table/course.yaml +4 -0
  137. package/hedhog/table/course_lesson_file.yaml +8 -0
  138. package/hedhog/table/course_video_resolution_profile.yaml +22 -0
  139. package/hedhog/table/video_resolution_profile.yaml +18 -0
  140. package/package.json +7 -6
  141. package/src/certificate/certificate.controller.ts +19 -14
  142. package/src/certificate/certificate.service.ts +106 -11
  143. package/src/course/course-structure.controller.ts +24 -2
  144. package/src/course/course-structure.service.ts +21 -4
  145. package/src/course/course-video-conversion.service.ts +415 -0
  146. package/src/course/course.controller.ts +18 -0
  147. package/src/course/course.module.ts +15 -2
  148. package/src/course/course.service.ts +72 -2
  149. package/src/course/dto/create-course-structure-lesson.dto.ts +13 -2
  150. package/src/course/dto/create-course.dto.ts +8 -0
  151. package/src/enterprise/enterprise.controller.ts +0 -1
  152. package/src/evaluation/evaluation.service.ts +9 -2
  153. package/src/index.ts +1 -0
  154. package/src/lms.module.ts +3 -0
  155. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +16 -0
  156. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +16 -0
  157. package/src/video-resolution-profile/video-resolution-profile.controller.ts +62 -0
  158. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +128 -0
  159. package/src/video-resolution-profile/video-resolution-profile.module.ts +13 -0
  160. package/src/video-resolution-profile/video-resolution-profile.service.ts +117 -0
@@ -33,12 +33,12 @@ import { Label } from '@/components/ui/label';
33
33
  import { Separator } from '@/components/ui/separator';
34
34
  import {
35
35
  Sheet,
36
- SheetContent,
37
36
  SheetDescription,
38
37
  SheetFooter,
39
38
  SheetHeader,
40
39
  SheetTitle,
41
40
  } from '@/components/ui/sheet';
41
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
42
42
  import { Skeleton } from '@/components/ui/skeleton';
43
43
  import {
44
44
  Table,
@@ -59,8 +59,10 @@ import {
59
59
  Award,
60
60
  BookOpen,
61
61
  Eye,
62
+ Layers,
62
63
  Loader2,
63
64
  MoreHorizontal,
65
+ Paperclip,
64
66
  Pencil,
65
67
  Plus,
66
68
  Star,
@@ -89,6 +91,7 @@ const API_COURSES_CACHE_KEY = 'lms:courses:api-cache';
89
91
  interface Curso {
90
92
  id: number;
91
93
  slug: string;
94
+ code?: string | null;
92
95
  nomeInterno: string;
93
96
  tituloComercial: string;
94
97
  descricao: string;
@@ -96,11 +99,15 @@ interface Curso {
96
99
  secondaryColor?: string | null;
97
100
  nivel: 'iniciante' | 'intermediario' | 'avancado';
98
101
  status: 'ativo' | 'rascunho' | 'arquivado';
102
+ tipoCurso: 'on_demand' | 'agendado' | 'hibrido';
99
103
  categorias: string[];
100
104
  destaque: boolean;
101
105
  certificado: boolean;
102
106
  listado: boolean;
103
107
  alunosInscritos: number;
108
+ quantidadeSessoes: number;
109
+ quantidadeAulas: number;
110
+ quantidadeRecursos: number;
104
111
  criadoEm: string;
105
112
  logoFileId?: number | null;
106
113
  operationsProjectId?: number | null;
@@ -110,6 +117,7 @@ type ApiCourse = {
110
117
  id: number;
111
118
  name: string;
112
119
  slug: string;
120
+ code?: string | null;
113
121
  title: string;
114
122
  description: string;
115
123
  primaryColor?: string | null;
@@ -118,11 +126,15 @@ type ApiCourse = {
118
126
  secondaryContrastColor?: string | null;
119
127
  level: 'beginner' | 'intermediate' | 'advanced';
120
128
  status: 'draft' | 'published' | 'archived';
129
+ offeringType?: 'on_demand' | 'scheduled' | 'blended' | null;
121
130
  categories: string[];
122
131
  isFeatured: boolean;
123
132
  hasCertificate: boolean;
124
133
  isListed: boolean;
125
134
  enrollmentCount: number;
135
+ moduleCount: number;
136
+ lessonCount: number;
137
+ resourceCount: number;
126
138
  createdAt: string;
127
139
  logoFileId?: number | null;
128
140
  operationsProjectId?: number | null;
@@ -192,6 +204,12 @@ function toPtLevel(level?: string | null): CourseSheetFormValues['nivel'] {
192
204
  return 'iniciante';
193
205
  }
194
206
 
207
+ function toPtOfferingType(type?: string | null): Curso['tipoCurso'] {
208
+ if (type === 'scheduled') return 'agendado';
209
+ if (type === 'blended') return 'hibrido';
210
+ return 'on_demand';
211
+ }
212
+
195
213
  function toPtStatus(status?: string | null): CourseSheetFormValues['status'] {
196
214
  const normalizedStatus = normalizeEnumValue(status);
197
215
 
@@ -216,6 +234,7 @@ function mapApiCourse(course: ApiCourse): Curso {
216
234
  return {
217
235
  id: course.id,
218
236
  slug: course.slug,
237
+ code: course.code ?? null,
219
238
  nomeInterno: course.name ?? '',
220
239
  tituloComercial: course.title,
221
240
  descricao: course.description ?? '',
@@ -223,11 +242,15 @@ function mapApiCourse(course: ApiCourse): Curso {
223
242
  secondaryColor: course.secondaryColor ?? null,
224
243
  nivel: toPtLevel(course.level),
225
244
  status: toPtStatus(course.status),
245
+ tipoCurso: toPtOfferingType(course.offeringType),
226
246
  categorias: course.categories ?? [],
227
247
  destaque: course.isFeatured,
228
248
  certificado: course.hasCertificate,
229
249
  listado: course.isListed,
230
250
  alunosInscritos: course.enrollmentCount ?? 0,
251
+ quantidadeSessoes: course.moduleCount ?? 0,
252
+ quantidadeAulas: course.lessonCount ?? 0,
253
+ quantidadeRecursos: course.resourceCount ?? 0,
231
254
  criadoEm: course.createdAt ?? '',
232
255
  logoFileId: course.logoFileId ?? null,
233
256
  operationsProjectId: course.operationsProjectId ?? null,
@@ -246,6 +269,12 @@ function toApiStatus(status: CourseSheetFormValues['status']) {
246
269
  return 'archived';
247
270
  }
248
271
 
272
+ function toApiOfferingType(type: CourseSheetFormValues['offeringType']) {
273
+ if (type === 'agendado') return 'scheduled';
274
+ if (type === 'hibrido') return 'blended';
275
+ return 'on_demand';
276
+ }
277
+
249
278
  function getContrastColor(hex: string) {
250
279
  const cleaned = hex.replace('#', '');
251
280
  if (cleaned.length !== 6) return '#FFFFFF';
@@ -272,6 +301,18 @@ const STATUS_VARIANT: Record<string, 'default' | 'secondary' | 'outline'> = {
272
301
  arquivado: 'outline',
273
302
  };
274
303
 
304
+ const OFFERING_TYPE_LABEL: Record<string, string> = {
305
+ on_demand: 'On Demand',
306
+ agendado: 'Agendado',
307
+ hibrido: 'Híbrido',
308
+ };
309
+
310
+ const OFFERING_TYPE_COLOR: Record<string, string> = {
311
+ on_demand: 'bg-violet-50 text-violet-700 border-violet-200',
312
+ agendado: 'bg-blue-50 text-blue-700 border-blue-200',
313
+ hibrido: 'bg-teal-50 text-teal-700 border-teal-200',
314
+ };
315
+
275
316
  const PAGE_SIZES = [6, 12, 24] as const;
276
317
 
277
318
  // ── Seed Data ─────────────────────────────────────────────────────────────────
@@ -505,6 +546,7 @@ export default function CursosPage() {
505
546
  status:
506
547
  (watchedFormValues.status as Curso['status'] | undefined) ??
507
548
  editingCurso.status,
549
+ tipoCurso: watchedFormValues.offeringType ?? editingCurso.tipoCurso,
508
550
  categorias: watchedFormValues.categorias ?? editingCurso.categorias,
509
551
  } satisfies Curso;
510
552
  }, [editingCurso, sheetOpen, watchedFormValues]);
@@ -601,12 +643,14 @@ export default function CursosPage() {
601
643
  form.reset({
602
644
  nomeInterno: curso.nomeInterno,
603
645
  slug: curso.slug,
646
+ code: curso.code || '',
604
647
  tituloComercial: curso.tituloComercial,
605
648
  descricao: curso.descricao,
606
649
  primaryColor: curso.primaryColor || '#1D4ED8',
607
650
  secondaryColor: curso.secondaryColor || '#111827',
608
651
  nivel: curso.nivel,
609
652
  status: curso.status,
653
+ offeringType: curso.tipoCurso,
610
654
  categorias: curso.categorias,
611
655
  operationsProjectId: curso.operationsProjectId
612
656
  ? String(curso.operationsProjectId)
@@ -622,10 +666,12 @@ export default function CursosPage() {
622
666
  const payload = {
623
667
  name: data.nomeInterno.trim(),
624
668
  slug: data.slug.trim().toLowerCase(),
669
+ code: data.code?.trim() || undefined,
625
670
  title: data.tituloComercial,
626
671
  description: data.descricao,
627
672
  level: toApiLevel(data.nivel),
628
673
  status: toApiStatus(data.status),
674
+ offeringType: toApiOfferingType(data.offeringType),
629
675
  categorySlugs: data.categorias,
630
676
  primaryColor: data.primaryColor,
631
677
  primaryContrastColor: getContrastColor(data.primaryColor),
@@ -1032,17 +1078,21 @@ export default function CursosPage() {
1032
1078
  <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
1033
1079
  {Array.from({ length: 6 }).map((_, i) => (
1034
1080
  <Card key={i} className="overflow-hidden">
1035
- <CardContent className="p-5">
1036
- <div className="mb-3 flex items-start justify-between">
1037
- <Skeleton className="h-5 w-20 rounded-full" />
1038
- <Skeleton className="size-8 rounded-md" />
1081
+ <CardContent className="p-3">
1082
+ <div className="flex items-start gap-2.5">
1083
+ <Skeleton className="size-10 shrink-0 rounded-lg" />
1084
+ <div className="flex-1 space-y-2">
1085
+ <Skeleton className="h-4 w-3/4" />
1086
+ <div className="flex gap-1.5">
1087
+ <Skeleton className="h-4 w-16 rounded-full" />
1088
+ <Skeleton className="h-4 w-14 rounded-full" />
1089
+ </div>
1090
+ </div>
1039
1091
  </div>
1040
- <Skeleton className="mb-1.5 h-5 w-3/4" />
1041
- <Skeleton className="mb-4 h-4 w-full" />
1042
- <Skeleton className="mb-4 h-4 w-2/3" />
1043
- <div className="flex gap-2">
1044
- <Skeleton className="h-6 w-16 rounded-full" />
1045
- <Skeleton className="h-6 w-20 rounded-full" />
1092
+ <div className="mt-3 flex gap-4 border-t border-border/50 pt-2.5">
1093
+ <Skeleton className="h-3.5 w-12" />
1094
+ <Skeleton className="h-3.5 w-14" />
1095
+ <Skeleton className="h-3.5 w-10" />
1046
1096
  </div>
1047
1097
  </CardContent>
1048
1098
  </Card>
@@ -1120,7 +1170,6 @@ export default function CursosPage() {
1120
1170
  animate="show"
1121
1171
  >
1122
1172
  {paginatedCursos.map((curso) => {
1123
- const nivelColor = NIVEL_COLOR[curso.nivel];
1124
1173
  const statusVariant = STATUS_VARIANT[curso.status];
1125
1174
  const isSelected = selectedIds.has(curso.id);
1126
1175
 
@@ -1132,7 +1181,9 @@ export default function CursosPage() {
1132
1181
  >
1133
1182
  <Card
1134
1183
  className={`group relative flex h-full cursor-pointer flex-col overflow-hidden border-border/70 shadow-sm transition-all duration-200 hover:border-border hover:shadow-md ${isSelected ? 'ring-2 ring-primary ring-offset-2' : ''}`}
1135
- onDoubleClick={() => openEditSheet(curso)}
1184
+ onDoubleClick={() =>
1185
+ router.push(`/lms/courses/${curso.id}`)
1186
+ }
1136
1187
  title={t('cards.tooltip')}
1137
1188
  >
1138
1189
  <div
@@ -1157,17 +1208,17 @@ export default function CursosPage() {
1157
1208
  />
1158
1209
  </div>
1159
1210
 
1160
- <CardContent className="flex h-full flex-col p-4">
1161
- <div className="mb-3 flex items-start gap-3">
1211
+ <CardContent className="flex flex-col p-3">
1212
+ <div className="flex items-start gap-2.5">
1162
1213
  <CourseAvatar
1163
1214
  fileId={curso.logoFileId}
1164
1215
  title={curso.nomeInterno}
1165
- className="size-12 rounded-xl"
1166
- iconSize="size-6"
1216
+ className="size-10 shrink-0 rounded-lg"
1217
+ iconSize="size-5"
1167
1218
  />
1168
1219
  <div className="min-w-0 flex-1">
1169
- <div className="mb-1.5 flex items-start justify-between gap-2">
1170
- <h3 className="line-clamp-2 font-semibold leading-snug text-foreground">
1220
+ <div className="flex items-start justify-between gap-1">
1221
+ <h3 className="line-clamp-1 text-sm font-semibold leading-snug text-foreground">
1171
1222
  {curso.nomeInterno}
1172
1223
  </h3>
1173
1224
  <DropdownMenu>
@@ -1175,11 +1226,11 @@ export default function CursosPage() {
1175
1226
  <Button
1176
1227
  variant="ghost"
1177
1228
  size="icon"
1178
- className="size-8 shrink-0 -mr-2 -mt-1"
1229
+ className="-mr-1.5 -mt-0.5 size-7 shrink-0"
1179
1230
  onClick={(e) => e.stopPropagation()}
1180
1231
  aria-label={t('table.actions.label')}
1181
1232
  >
1182
- <MoreHorizontal className="size-4" />
1233
+ <MoreHorizontal className="size-3.5" />
1183
1234
  </Button>
1184
1235
  </DropdownMenuTrigger>
1185
1236
  <DropdownMenuContent
@@ -1212,76 +1263,39 @@ export default function CursosPage() {
1212
1263
  </DropdownMenuContent>
1213
1264
  </DropdownMenu>
1214
1265
  </div>
1215
- <p className="text-xs text-muted-foreground">
1216
- <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px]">
1217
- {curso.slug}
1218
- </code>
1219
- <span className="mx-1.5 text-muted-foreground/50">
1220
- |
1266
+
1267
+ <div className="mt-1.5 flex flex-wrap items-center gap-1">
1268
+ <span
1269
+ className={`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium ${OFFERING_TYPE_COLOR[curso.tipoCurso]}`}
1270
+ >
1271
+ {OFFERING_TYPE_LABEL[curso.tipoCurso]}
1221
1272
  </span>
1222
- <span>{curso.tituloComercial}</span>
1223
- </p>
1273
+ <Badge
1274
+ variant={statusVariant}
1275
+ className="rounded-full px-2 py-0.5 text-[10px]"
1276
+ >
1277
+ {getStatusLabel(curso)}
1278
+ </Badge>
1279
+ </div>
1224
1280
  </div>
1225
1281
  </div>
1226
1282
 
1227
- <div className="mb-3 flex flex-wrap items-center gap-1.5">
1228
- <span
1229
- className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-[11px] font-medium ${nivelColor}`}
1230
- >
1231
- {getLevelLabel(curso)}
1283
+ <div className="mt-3 flex items-center gap-3 border-t border-border/50 pt-2.5 text-xs text-muted-foreground">
1284
+ <span className="flex items-center gap-1">
1285
+ <Layers className="size-3.5 shrink-0" />
1286
+ <span className="font-medium text-foreground">{curso.quantidadeSessoes}</span>
1287
+ {' '}sess.
1288
+ </span>
1289
+ <span className="flex items-center gap-1">
1290
+ <BookOpen className="size-3.5 shrink-0" />
1291
+ <span className="font-medium text-foreground">{curso.quantidadeAulas}</span>
1292
+ {' '}aulas
1293
+ </span>
1294
+ <span className="flex items-center gap-1">
1295
+ <Paperclip className="size-3.5 shrink-0" />
1296
+ <span className="font-medium text-foreground">{curso.quantidadeRecursos}</span>
1297
+ {' '}rec.
1232
1298
  </span>
1233
- <Badge
1234
- variant={statusVariant}
1235
- className="text-[11px]"
1236
- >
1237
- {getStatusLabel(curso)}
1238
- </Badge>
1239
- {curso.destaque && (
1240
- <span className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-[11px] font-medium text-amber-700">
1241
- <Star className="size-3 fill-amber-400 text-amber-400" />{' '}
1242
- {t('form.flags.featured.label')}
1243
- </span>
1244
- )}
1245
- </div>
1246
-
1247
- <p className="mb-4 line-clamp-2 text-sm leading-relaxed text-muted-foreground">
1248
- {curso.descricao}
1249
- </p>
1250
-
1251
- <div className="mb-4 flex flex-wrap gap-1.5">
1252
- {curso.categorias.slice(0, 3).map((cat) => (
1253
- <span
1254
- key={cat}
1255
- className="rounded-md bg-muted/80 px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
1256
- >
1257
- {categoryLabelBySlug[cat] || cat}
1258
- </span>
1259
- ))}
1260
- {curso.categorias.length > 3 && (
1261
- <span className="rounded-md bg-muted/60 px-2 py-0.5 text-[11px] text-muted-foreground">
1262
- +{curso.categorias.length - 3}
1263
- </span>
1264
- )}
1265
- </div>
1266
-
1267
- <div className="mt-auto flex items-center justify-between rounded-lg bg-muted/40 px-3 py-2.5">
1268
- <div className="flex items-center gap-1.5">
1269
- <Users className="size-4 text-muted-foreground" />
1270
- <span className="text-sm font-medium">
1271
- {curso.alunosInscritos.toLocaleString('pt-BR')}
1272
- </span>
1273
- <span className="text-xs text-muted-foreground">
1274
- {t('cards.studentsLabel')}
1275
- </span>
1276
- </div>
1277
- {curso.certificado && (
1278
- <div className="flex items-center gap-1.5">
1279
- <Award className="size-4 text-foreground" />
1280
- <span className="text-xs font-medium">
1281
- {t('form.flags.certificate.label')}
1282
- </span>
1283
- </div>
1284
- )}
1285
1299
  </div>
1286
1300
  </CardContent>
1287
1301
  </Card>
@@ -1318,7 +1332,9 @@ export default function CursosPage() {
1318
1332
  key={curso.id}
1319
1333
  className="cursor-pointer"
1320
1334
  data-state={isSelected ? 'selected' : undefined}
1321
- onDoubleClick={() => openEditSheet(curso)}
1335
+ onDoubleClick={() =>
1336
+ router.push(`/lms/courses/${curso.id}`)
1337
+ }
1322
1338
  title={t('cards.tooltip')}
1323
1339
  >
1324
1340
  <TableCell onClick={(e) => e.stopPropagation()}>
@@ -1498,7 +1514,14 @@ export default function CursosPage() {
1498
1514
  }
1499
1515
  }}
1500
1516
  >
1501
- <SheetContent side="right" className="w-full sm:max-w-md">
1517
+ <ResizableSheetContent
1518
+ sheetId="lms-courses-create-category-sheet"
1519
+ defaultWidth={520}
1520
+ minWidth={400}
1521
+ maxWidth={860}
1522
+ side="right"
1523
+ className="w-full sm:max-w-md"
1524
+ >
1502
1525
  <SheetHeader>
1503
1526
  <SheetTitle>{t('form.fields.categories.createAction')}</SheetTitle>
1504
1527
  <SheetDescription>
@@ -1555,7 +1578,7 @@ export default function CursosPage() {
1555
1578
  )}
1556
1579
  </Button>
1557
1580
  </SheetFooter>
1558
- </SheetContent>
1581
+ </ResizableSheetContent>
1559
1582
  </Sheet>
1560
1583
 
1561
1584
  {/* ── Delete dialog ────────────────────���────────────────────────────── */}
@@ -101,7 +101,7 @@ function CrmTab({
101
101
  <Button
102
102
  variant="outline"
103
103
  size="sm"
104
- onClick={() => router.push('/contact/accounts')}
104
+ onClick={() => router.push('/crm/accounts')}
105
105
  className="shrink-0"
106
106
  >
107
107
  <ExternalLink className="mr-2 h-4 w-4" />
@@ -21,12 +21,12 @@ import {
21
21
  } from '@/components/ui/select';
22
22
  import {
23
23
  Sheet,
24
- SheetContent,
25
24
  SheetDescription,
26
25
  SheetFooter,
27
26
  SheetHeader,
28
27
  SheetTitle,
29
28
  } from '@/components/ui/sheet';
29
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
30
30
  import { useApp } from '@hed-hog/next-app-provider';
31
31
  import { zodResolver } from '@hookform/resolvers/zod';
32
32
  import { Eye, EyeOff, Loader2, RefreshCw } from 'lucide-react';
@@ -165,7 +165,14 @@ export function EnterpriseAdminCreateSheet({
165
165
 
166
166
  return (
167
167
  <Sheet open={open} onOpenChange={handleOpenChange}>
168
- <SheetContent side="right" className="flex flex-col gap-0 sm:max-w-md">
168
+ <ResizableSheetContent
169
+ sheetId="lms-enterprise-admin-create-sheet"
170
+ defaultWidth={520}
171
+ minWidth={400}
172
+ maxWidth={860}
173
+ side="right"
174
+ className="flex flex-col gap-0 sm:max-w-md"
175
+ >
169
176
  <SheetHeader className="border-b px-6 py-4">
170
177
  <SheetTitle>New administrator</SheetTitle>
171
178
  <SheetDescription>
@@ -318,7 +325,7 @@ export function EnterpriseAdminCreateSheet({
318
325
  </SheetFooter>
319
326
  </form>
320
327
  </Form>
321
- </SheetContent>
328
+ </ResizableSheetContent>
322
329
  </Sheet>
323
330
  );
324
331
  }
@@ -17,11 +17,11 @@ import { Input } from '@/components/ui/input';
17
17
  import { KpiCardsGrid, type KpiCardItem } from '@/components/ui/kpi-cards-grid';
18
18
  import {
19
19
  Sheet,
20
- SheetContent,
21
20
  SheetDescription,
22
21
  SheetHeader,
23
22
  SheetTitle,
24
23
  } from '@/components/ui/sheet';
24
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
25
25
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
26
26
  import { useApp } from '@hed-hog/next-app-provider';
27
27
  import {
@@ -274,9 +274,13 @@ export function EnterpriseDetailSheet({
274
274
  return (
275
275
  <>
276
276
  <Sheet open={open} onOpenChange={onOpenChange}>
277
- <SheetContent
277
+ <ResizableSheetContent
278
+ sheetId="lms-enterprise-detail-sheet"
279
+ defaultWidth={1120}
280
+ minWidth={700}
281
+ maxWidth={1440}
278
282
  side="right"
279
- className="flex w-full flex-col gap-0 overflow-y-auto p-0 sm:max-w-[95vw] lg:max-w-6xl"
283
+ className="flex w-full flex-col gap-0 overflow-y-auto p-0"
280
284
  >
281
285
  {currentAccount && (
282
286
  <>
@@ -612,7 +616,7 @@ export function EnterpriseDetailSheet({
612
616
  </div>
613
617
  </>
614
618
  )}
615
- </SheetContent>
619
+ </ResizableSheetContent>
616
620
  </Sheet>
617
621
 
618
622
  {/* ── Delete Confirmation Dialog ── */}
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
4
  import { useMemo } from 'react';
5
- import { PersonFormSheet } from '../../../contact/person/_components/person-form-sheet';
5
+ import { PersonFormSheet } from '../../../crm/person/_components/person-form-sheet';
6
6
  import type {
7
7
  ContactTypeOption,
8
8
  DocumentTypeOption,
9
9
  Person,
10
- } from '../../../contact/person/_components/person-types';
10
+ } from '../../../crm/person/_components/person-types';
11
11
 
12
12
  export interface EnterprisePersonEditSheetProps {
13
13
  open: boolean;
@@ -20,11 +20,11 @@ import {
20
20
  } from '@/components/ui/select';
21
21
  import {
22
22
  Sheet,
23
- SheetContent,
24
23
  SheetDescription,
25
24
  SheetHeader,
26
25
  SheetTitle,
27
26
  } from '@/components/ui/sheet';
27
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
28
28
  import { Switch } from '@/components/ui/switch';
29
29
  import { Textarea } from '@/components/ui/textarea';
30
30
  import { useApp } from '@hed-hog/next-app-provider';
@@ -34,7 +34,7 @@ import { useEffect, useState } from 'react';
34
34
  import { useForm } from 'react-hook-form';
35
35
  import { toast } from 'sonner';
36
36
  import { z } from 'zod';
37
- import { PersonPicker } from '../../../contact/_components/person-picker';
37
+ import { PersonPicker } from '../../../crm/_components/person-picker';
38
38
  import { EnterprisePersonEditSheet } from './enterprise-person-edit-sheet';
39
39
  import type { EnterpriseAccount } from './enterprise-types';
40
40
 
@@ -187,7 +187,13 @@ export function EnterpriseSheet({
187
187
 
188
188
  return (
189
189
  <Sheet open={open} onOpenChange={onOpenChange}>
190
- <SheetContent className="w-full overflow-y-auto sm:max-w-lg">
190
+ <ResizableSheetContent
191
+ sheetId="lms-enterprise-account-sheet"
192
+ defaultWidth={560}
193
+ minWidth={420}
194
+ maxWidth={920}
195
+ className="w-full overflow-y-auto sm:max-w-lg"
196
+ >
191
197
  <SheetHeader>
192
198
  <SheetTitle>
193
199
  {isEditing ? t('editTitle') : t('createTitle')}
@@ -414,7 +420,7 @@ export function EnterpriseSheet({
414
420
  setSelectedCompanyLabel(person.name ?? '');
415
421
  }}
416
422
  />
417
- </SheetContent>
423
+ </ResizableSheetContent>
418
424
  </Sheet>
419
425
  );
420
426
  }
@@ -13,12 +13,12 @@ import {
13
13
  import { Input } from '@/components/ui/input';
14
14
  import {
15
15
  Sheet,
16
- SheetContent,
17
16
  SheetDescription,
18
17
  SheetFooter,
19
18
  SheetHeader,
20
19
  SheetTitle,
21
20
  } from '@/components/ui/sheet';
21
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
22
22
  import { useApp } from '@hed-hog/next-app-provider';
23
23
  import { zodResolver } from '@hookform/resolvers/zod';
24
24
  import { Loader2 } from 'lucide-react';
@@ -101,7 +101,14 @@ export function EnterpriseStudentCreateSheet({
101
101
 
102
102
  return (
103
103
  <Sheet open={open} onOpenChange={handleOpenChange}>
104
- <SheetContent side="right" className="flex flex-col gap-0 sm:max-w-md">
104
+ <ResizableSheetContent
105
+ sheetId="lms-enterprise-student-create-sheet"
106
+ defaultWidth={520}
107
+ minWidth={400}
108
+ maxWidth={860}
109
+ side="right"
110
+ className="flex flex-col gap-0 sm:max-w-md"
111
+ >
105
112
  <SheetHeader className="border-b px-6 py-4">
106
113
  <SheetTitle>New student (person)</SheetTitle>
107
114
  <SheetDescription>
@@ -167,7 +174,7 @@ export function EnterpriseStudentCreateSheet({
167
174
  </SheetFooter>
168
175
  </form>
169
176
  </Form>
170
- </SheetContent>
177
+ </ResizableSheetContent>
171
178
  </Sheet>
172
179
  );
173
180
  }
@@ -20,12 +20,12 @@ import {
20
20
  } from '@/components/ui/select';
21
21
  import {
22
22
  Sheet,
23
- SheetContent,
24
23
  SheetDescription,
25
24
  SheetFooter,
26
25
  SheetHeader,
27
26
  SheetTitle,
28
27
  } from '@/components/ui/sheet';
28
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
29
29
  import { zodResolver } from '@hookform/resolvers/zod';
30
30
  import { Loader2 } from 'lucide-react';
31
31
  import { useEffect } from 'react';
@@ -104,7 +104,14 @@ export function EnterpriseUserCreateSheet({
104
104
 
105
105
  return (
106
106
  <Sheet open={open} onOpenChange={handleOpenChange}>
107
- <SheetContent side="right" className="flex flex-col gap-0 sm:max-w-md">
107
+ <ResizableSheetContent
108
+ sheetId="lms-enterprise-user-create-sheet"
109
+ defaultWidth={520}
110
+ minWidth={400}
111
+ maxWidth={860}
112
+ side="right"
113
+ className="flex flex-col gap-0 sm:max-w-md"
114
+ >
108
115
  <SheetHeader className="border-b px-6 py-4">
109
116
  <SheetTitle>New user</SheetTitle>
110
117
  <SheetDescription>
@@ -205,7 +212,7 @@ export function EnterpriseUserCreateSheet({
205
212
  </SheetFooter>
206
213
  </form>
207
214
  </Form>
208
- </SheetContent>
215
+ </ResizableSheetContent>
209
216
  </Sheet>
210
217
  );
211
218
  }
@@ -12,11 +12,11 @@ import {
12
12
  } from '@/components/ui/select';
13
13
  import {
14
14
  Sheet,
15
- SheetContent,
16
15
  SheetFooter,
17
16
  SheetHeader,
18
17
  SheetTitle,
19
18
  } from '@/components/ui/sheet';
19
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
20
20
  import { Switch } from '@/components/ui/switch';
21
21
  import { Textarea } from '@/components/ui/textarea';
22
22
  import { useApp } from '@hed-hog/next-app-provider';
@@ -106,7 +106,14 @@ export function EvaluationTopicFormSheet({
106
106
 
107
107
  return (
108
108
  <Sheet open={open} onOpenChange={onOpenChange}>
109
- <SheetContent side="right" className="w-full overflow-y-auto sm:max-w-md">
109
+ <ResizableSheetContent
110
+ sheetId="lms-evaluation-topic-form-sheet"
111
+ defaultWidth={520}
112
+ minWidth={400}
113
+ maxWidth={860}
114
+ side="right"
115
+ className="w-full overflow-y-auto sm:max-w-md"
116
+ >
110
117
  <SheetHeader>
111
118
  <SheetTitle>
112
119
  {editingItem ? t('form.editTitle') : t('form.createTitle')}
@@ -195,7 +202,7 @@ export function EvaluationTopicFormSheet({
195
202
  </Button>
196
203
  </SheetFooter>
197
204
  </form>
198
- </SheetContent>
205
+ </ResizableSheetContent>
199
206
  </Sheet>
200
207
  );
201
208
  }