@hed-hog/lms 0.0.331 → 0.0.338

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 (110) hide show
  1. package/dist/class-group/class-group.controller.d.ts +3 -3
  2. package/dist/class-group/class-group.service.d.ts +3 -3
  3. package/dist/course/course.service.d.ts.map +1 -1
  4. package/dist/course/course.service.js +12 -20
  5. package/dist/course/course.service.js.map +1 -1
  6. package/dist/enterprise/enterprise.controller.d.ts +72 -0
  7. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  8. package/dist/enterprise/enterprise.controller.js +10 -0
  9. package/dist/enterprise/enterprise.controller.js.map +1 -1
  10. package/dist/enterprise/enterprise.service.d.ts +78 -0
  11. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  12. package/dist/enterprise/enterprise.service.js +413 -40
  13. package/dist/enterprise/enterprise.service.js.map +1 -1
  14. package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
  15. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  16. package/dist/enterprise/training/training-admin.controller.js +10 -6
  17. package/dist/enterprise/training/training-admin.controller.js.map +1 -1
  18. package/dist/enterprise/training/training-admin.service.d.ts +8 -2
  19. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  20. package/dist/enterprise/training/training-admin.service.js +108 -52
  21. package/dist/enterprise/training/training-admin.service.js.map +1 -1
  22. package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
  23. package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
  24. package/dist/evaluation/evaluation.controller.d.ts +4 -4
  25. package/dist/evaluation/evaluation.service.d.ts +4 -4
  26. package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
  27. package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
  28. package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
  29. package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
  30. package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
  31. package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
  32. package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
  33. package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
  34. package/dist/instructor/instructor-skill.controller.d.ts +4 -4
  35. package/dist/instructor/instructor-skill.service.d.ts +4 -7
  36. package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
  37. package/dist/instructor/instructor-skill.service.js +2 -89
  38. package/dist/instructor/instructor-skill.service.js.map +1 -1
  39. package/dist/instructor/instructor.controller.d.ts +20 -0
  40. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  41. package/dist/instructor/instructor.controller.js +19 -0
  42. package/dist/instructor/instructor.controller.js.map +1 -1
  43. package/dist/instructor/instructor.service.d.ts +25 -0
  44. package/dist/instructor/instructor.service.d.ts.map +1 -1
  45. package/dist/instructor/instructor.service.js +70 -18
  46. package/dist/instructor/instructor.service.js.map +1 -1
  47. package/dist/lms.module.d.ts.map +1 -1
  48. package/dist/lms.module.js.map +1 -1
  49. package/hedhog/data/route.yaml +23 -1
  50. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
  51. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
  52. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
  53. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
  54. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +6 -1
  55. package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
  56. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
  57. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
  58. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
  59. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +40 -13
  60. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
  61. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
  62. package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
  63. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
  64. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
  65. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
  66. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
  67. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
  68. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
  69. package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
  70. package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
  71. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
  72. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
  73. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
  74. package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
  75. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
  76. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
  77. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
  78. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
  79. package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
  80. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
  81. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
  82. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
  83. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
  84. package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
  85. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
  86. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
  87. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
  88. package/hedhog/frontend/app/exams/page.tsx.ejs +6 -1
  89. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
  90. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +625 -366
  91. package/hedhog/frontend/app/instructors/page.tsx.ejs +6 -1
  92. package/hedhog/frontend/app/paths/page.tsx.ejs +9 -4
  93. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
  94. package/hedhog/frontend/app/training/page.tsx.ejs +9 -4
  95. package/hedhog/frontend/messages/en.json +101 -10
  96. package/hedhog/frontend/messages/pt.json +101 -10
  97. package/hedhog/table/enterprise_student_license_event.yaml +30 -0
  98. package/hedhog/table/instructor_skill.yaml +0 -11
  99. package/package.json +7 -7
  100. package/src/course/course.service.ts +12 -24
  101. package/src/enterprise/enterprise.controller.ts +5 -0
  102. package/src/enterprise/enterprise.service.ts +507 -29
  103. package/src/enterprise/training/training-admin.controller.ts +4 -0
  104. package/src/enterprise/training/training-admin.service.ts +115 -51
  105. package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
  106. package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
  107. package/src/instructor/instructor-skill.service.ts +2 -97
  108. package/src/instructor/instructor.controller.ts +16 -0
  109. package/src/instructor/instructor.service.ts +85 -10
  110. package/src/lms.module.ts +1 -0
@@ -13,7 +13,7 @@ import {
13
13
  SelectTrigger,
14
14
  SelectValue,
15
15
  } from '@/components/ui/select';
16
- import { Palette, Settings2 } from 'lucide-react';
16
+ import { Settings2 } from 'lucide-react';
17
17
  import type { UseFormReturn } from 'react-hook-form';
18
18
 
19
19
  import { CourseSectionCard } from './CourseSectionCard';
@@ -33,23 +33,6 @@ type CourseClassificationCardProps = {
33
33
  offeringTypes: readonly SelectOption[];
34
34
  };
35
35
 
36
- function ColorPreview({ color, label }: { color: string; label: string }) {
37
- return (
38
- <div className="flex items-center gap-2 rounded-lg border border-border/60 bg-muted/20 px-2.5 py-1.5">
39
- <span
40
- className="h-4 w-4 shrink-0 rounded-full border border-black/10"
41
- style={{ backgroundColor: color }}
42
- />
43
- <div className="min-w-0">
44
- <p className="text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
45
- {label}
46
- </p>
47
- <p className="truncate text-xs font-medium text-foreground">{color}</p>
48
- </div>
49
- </div>
50
- );
51
- }
52
-
53
36
  export function CourseClassificationCard({
54
37
  form,
55
38
  t,
@@ -57,10 +40,6 @@ export function CourseClassificationCard({
57
40
  statuses,
58
41
  offeringTypes,
59
42
  }: CourseClassificationCardProps) {
60
- const watchedPrimary = form.watch('primaryColor');
61
- const watchedSecondary = form.watch('secondaryColor');
62
- const watchedOfferingType = form.watch('tipoOferta');
63
-
64
43
  return (
65
44
  <CourseSectionCard
66
45
  title="Classificação e configuração"
@@ -156,7 +135,7 @@ export function CourseClassificationCard({
156
135
  <FormItem>
157
136
  <FormLabel>{t('form.fields.primaryColor.label')}</FormLabel>
158
137
  <FormControl>
159
- <div className="flex items-center gap-2 rounded-lg border border-border/60 bg-muted/20 px-2 py-1.5">
138
+ <div className="flex items-center gap-2">
160
139
  <Input
161
140
  type="color"
162
141
  value={field.value}
@@ -183,7 +162,7 @@ export function CourseClassificationCard({
183
162
  <FormItem>
184
163
  <FormLabel>{t('form.fields.secondaryColor.label')}</FormLabel>
185
164
  <FormControl>
186
- <div className="flex items-center gap-2 rounded-lg border border-border/60 bg-muted/20 px-2 py-1.5">
165
+ <div className="flex items-center gap-2">
187
166
  <Input
188
167
  type="color"
189
168
  value={field.value}
@@ -202,15 +181,6 @@ export function CourseClassificationCard({
202
181
  </FormItem>
203
182
  )}
204
183
  />
205
-
206
- <div className="space-y-2 rounded-lg border border-border/60 bg-linear-to-br from-muted/30 to-background p-3">
207
- <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
208
- <Palette className="h-3.5 w-3.5" />
209
- Preview
210
- </div>
211
- <ColorPreview color={watchedPrimary} label="Primária" />
212
- <ColorPreview color={watchedSecondary} label="Secundária" />
213
- </div>
214
184
  </div>
215
185
  </CourseSectionCard>
216
186
  );
@@ -1,3 +1,4 @@
1
+ import { RichTextEditor } from '@/components/rich-text-editor';
1
2
  import {
2
3
  FormControl,
3
4
  FormField,
@@ -5,7 +6,6 @@ import {
5
6
  FormLabel,
6
7
  FormMessage,
7
8
  } from '@/components/ui/form';
8
- import { Textarea } from '@/components/ui/textarea';
9
9
  import { FileText } from 'lucide-react';
10
10
  import type { UseFormReturn } from 'react-hook-form';
11
11
 
@@ -31,10 +31,10 @@ export function CourseContentCard({ form }: CourseContentCardProps) {
31
31
  <FormItem>
32
32
  <FormLabel>Objetivos do curso</FormLabel>
33
33
  <FormControl>
34
- <Textarea
35
- {...field}
36
- rows={3}
37
- placeholder="Explique o que o aluno será capaz de fazer ao concluir o curso."
34
+ <RichTextEditor
35
+ value={field.value || ''}
36
+ onChange={field.onChange}
37
+ className="w-full max-w-full"
38
38
  />
39
39
  </FormControl>
40
40
  <FormMessage />
@@ -49,10 +49,10 @@ export function CourseContentCard({ form }: CourseContentCardProps) {
49
49
  <FormItem>
50
50
  <FormLabel>Público-alvo</FormLabel>
51
51
  <FormControl>
52
- <Textarea
53
- {...field}
54
- rows={3}
55
- placeholder="Descreva para quem este curso foi pensado."
52
+ <RichTextEditor
53
+ value={field.value || ''}
54
+ onChange={field.onChange}
55
+ className="w-full max-w-full"
56
56
  />
57
57
  </FormControl>
58
58
  <FormMessage />
@@ -0,0 +1,109 @@
1
+ import { RichTextEditor } from '@/components/rich-text-editor';
2
+ import {
3
+ FormControl,
4
+ FormField,
5
+ FormItem,
6
+ FormLabel,
7
+ FormMessage,
8
+ } from '@/components/ui/form';
9
+ import { Input } from '@/components/ui/input';
10
+ import { Hash, NotebookPen } from 'lucide-react';
11
+ import type { UseFormReturn } from 'react-hook-form';
12
+
13
+ import { CourseSectionCard } from './CourseSectionCard';
14
+ import type { CourseEditFormValues, TranslationFn } from './course-edit-types';
15
+
16
+ type CourseMainInfoCardProps = {
17
+ form: UseFormReturn<CourseEditFormValues>;
18
+ t: TranslationFn;
19
+ };
20
+
21
+ export function CourseMainInfoCard({ form, t }: CourseMainInfoCardProps) {
22
+ return (
23
+ <CourseSectionCard
24
+ title={t('form.sections.basicInfo')}
25
+ description="Os identificadores e a comunicação principal do curso ficam aqui."
26
+ icon={NotebookPen}
27
+ >
28
+ <div className="grid gap-3 md:grid-cols-2">
29
+ <FormField
30
+ control={form.control}
31
+ name="slug"
32
+ render={({ field }) => (
33
+ <FormItem>
34
+ <FormLabel>{t('form.fields.slug.label')}</FormLabel>
35
+ <FormControl>
36
+ <div className="relative">
37
+ <Hash className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
38
+ <Input
39
+ {...field}
40
+ className="pl-9 font-mono"
41
+ placeholder={t('form.fields.slug.placeholder')}
42
+ onChange={(event) =>
43
+ field.onChange(
44
+ String(event.target.value || '').toLowerCase()
45
+ )
46
+ }
47
+ />
48
+ </div>
49
+ </FormControl>
50
+ <FormMessage />
51
+ </FormItem>
52
+ )}
53
+ />
54
+
55
+ <FormField
56
+ control={form.control}
57
+ name="nomeInterno"
58
+ render={({ field }) => (
59
+ <FormItem>
60
+ <FormLabel>{t('form.fields.internalName.label')}</FormLabel>
61
+ <FormControl>
62
+ <Input
63
+ {...field}
64
+ placeholder={t('form.fields.internalName.placeholder')}
65
+ />
66
+ </FormControl>
67
+ <FormMessage />
68
+ </FormItem>
69
+ )}
70
+ />
71
+
72
+ <FormField
73
+ control={form.control}
74
+ name="tituloComercial"
75
+ render={({ field }) => (
76
+ <FormItem className="md:col-span-2">
77
+ <FormLabel>{t('form.fields.title.label')}</FormLabel>
78
+ <FormControl>
79
+ <Input
80
+ {...field}
81
+ placeholder={t('form.fields.title.placeholder')}
82
+ />
83
+ </FormControl>
84
+ <FormMessage />
85
+ </FormItem>
86
+ )}
87
+ />
88
+
89
+ <FormField
90
+ control={form.control}
91
+ name="descricaoPublica"
92
+ render={({ field }) => (
93
+ <FormItem className="md:col-span-2">
94
+ <FormLabel>{t('form.fields.description.label')}</FormLabel>
95
+ <FormControl>
96
+ <RichTextEditor
97
+ value={field.value}
98
+ onChange={field.onChange}
99
+ className="w-full max-w-full"
100
+ />
101
+ </FormControl>
102
+ <FormMessage />
103
+ </FormItem>
104
+ )}
105
+ />
106
+ </div>
107
+ </CourseSectionCard>
108
+ );
109
+ }
@@ -3,7 +3,7 @@ import { Badge } from '@/components/ui/badge';
3
3
  import { Button } from '@/components/ui/button';
4
4
  import { EntityPicker } from '@/components/ui/entity-picker';
5
5
  import { cn } from '@/lib/utils';
6
- import { Grip, Plus, X } from 'lucide-react';
6
+ import { Grip, Pencil, Plus, X } from 'lucide-react';
7
7
  import { useMemo, useState } from 'react';
8
8
 
9
9
  import type { PickerOption } from './course-edit-types';
@@ -28,6 +28,7 @@ type CourseMultiEntityPickerProps = {
28
28
  }>;
29
29
  mapSearchToCreateValues?: (search: string) => Record<string, string>;
30
30
  onChange: (nextValue: string[]) => void;
31
+ onEditSelected?: (option: PickerOption) => void;
31
32
  emptyHint?: string;
32
33
  renderOptionMeta?: boolean;
33
34
  variant?: 'default' | 'instructor';
@@ -57,6 +58,7 @@ export function CourseMultiEntityPicker({
57
58
  createFields,
58
59
  mapSearchToCreateValues,
59
60
  onChange,
61
+ onEditSelected,
60
62
  emptyHint,
61
63
  renderOptionMeta = false,
62
64
  variant = 'default',
@@ -192,18 +194,33 @@ export function CourseMultiEntityPicker({
192
194
  </div>
193
195
  </div>
194
196
 
195
- <Button
196
- type="button"
197
- variant="ghost"
198
- size="icon"
199
- className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
200
- onClick={() =>
201
- onChange(value.filter((item) => item !== option.value))
202
- }
203
- aria-label={`Remover ${option.label}`}
204
- >
205
- <X className="h-4 w-4" />
206
- </Button>
197
+ <div className="flex items-center gap-1">
198
+ {onEditSelected ? (
199
+ <Button
200
+ type="button"
201
+ variant="ghost"
202
+ size="icon"
203
+ className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
204
+ onClick={() => onEditSelected(option)}
205
+ aria-label={`Editar ${option.label}`}
206
+ >
207
+ <Pencil className="h-4 w-4" />
208
+ </Button>
209
+ ) : null}
210
+
211
+ <Button
212
+ type="button"
213
+ variant="ghost"
214
+ size="icon"
215
+ className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
216
+ onClick={() =>
217
+ onChange(value.filter((item) => item !== option.value))
218
+ }
219
+ aria-label={`Remover ${option.label}`}
220
+ >
221
+ <X className="h-4 w-4" />
222
+ </Button>
223
+ </div>
207
224
  </div>
208
225
  ) : (
209
226
  <Badge
@@ -212,6 +229,16 @@ export function CourseMultiEntityPicker({
212
229
  className="flex items-center gap-1.5 rounded-full border border-border/60 bg-background px-3 py-1.5 text-xs font-medium text-foreground"
213
230
  >
214
231
  <span className="max-w-45 truncate">{option.label}</span>
232
+ {onEditSelected ? (
233
+ <button
234
+ type="button"
235
+ className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
236
+ onClick={() => onEditSelected(option)}
237
+ aria-label={`Editar ${option.label}`}
238
+ >
239
+ <Pencil className="h-3.5 w-3.5" />
240
+ </button>
241
+ ) : null}
215
242
  <button
216
243
  type="button"
217
244
  className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
@@ -1,3 +1,4 @@
1
+ import { RichTextEditor } from '@/components/rich-text-editor';
1
2
  import {
2
3
  FormControl,
3
4
  FormField,
@@ -5,7 +6,6 @@ import {
5
6
  FormLabel,
6
7
  FormMessage,
7
8
  } from '@/components/ui/form';
8
- import { Textarea } from '@/components/ui/textarea';
9
9
  import { Network } from 'lucide-react';
10
10
  import type { UseFormReturn } from 'react-hook-form';
11
11
 
@@ -26,6 +26,8 @@ type CourseRelationsCardProps = {
26
26
  values: Record<string, string>
27
27
  ) => Promise<PickerOption | null>;
28
28
  onCreateInstructor: () => void;
29
+ onEditCategory: (categorySlug: string) => void;
30
+ onEditInstructor: (instructorId: string) => void;
29
31
  };
30
32
 
31
33
  export function CourseRelationsCard({
@@ -35,6 +37,8 @@ export function CourseRelationsCard({
35
37
  instructorOptions,
36
38
  onCreateCategory,
37
39
  onCreateInstructor,
40
+ onEditCategory,
41
+ onEditInstructor,
38
42
  }: CourseRelationsCardProps) {
39
43
  return (
40
44
  <CourseSectionCard
@@ -43,83 +47,74 @@ export function CourseRelationsCard({
43
47
  icon={Network}
44
48
  >
45
49
  <div className="space-y-3">
46
- <FormField
47
- control={form.control}
48
- name="categorias"
49
- render={({ field }) => (
50
- <FormItem>
51
- <FormLabel>{t('form.fields.categories.label')}</FormLabel>
52
- <CourseMultiEntityPicker
53
- value={field.value}
54
- onChange={field.onChange}
55
- options={categoryOptions}
56
- placeholder={t('form.fields.categories.selectPlaceholder')}
57
- searchPlaceholder={t(
58
- 'form.fields.categories.searchPlaceholder'
59
- )}
60
- entityLabel="categoria"
61
- emptyStateDescription={t('form.fields.categories.noResults')}
62
- emptyHint="Nenhuma categoria vinculada ainda."
63
- createActionLabel="Criar categoria"
64
- createTitle="Criar categoria"
65
- createDescription="Cadastre uma categoria rapidamente sem sair da edição do curso."
66
- createFields={[
67
- {
68
- name: 'name',
69
- label: 'Nome da categoria',
70
- placeholder: 'Ex: Desenvolvimento Web',
71
- required: true,
72
- },
73
- {
74
- name: 'slug',
75
- label: 'Slug',
76
- placeholder: 'desenvolvimento-web',
77
- required: true,
78
- },
79
- ]}
80
- mapSearchToCreateValues={(search) => ({
81
- name: search,
82
- slug: search
83
- .trim()
84
- .toLowerCase()
85
- .normalize('NFD')
86
- .replace(/[\u0300-\u036f]/g, '')
87
- .replace(/[^a-z0-9]+/g, '-')
88
- .replace(/^-+|-+$/g, ''),
89
- })}
90
- onCreate={onCreateCategory}
91
- />
92
- <FormMessage />
93
- </FormItem>
94
- )}
95
- />
50
+ <div className="grid items-start gap-3 lg:grid-cols-2">
51
+ <FormField
52
+ control={form.control}
53
+ name="categorias"
54
+ render={({ field }) => (
55
+ <FormItem className="self-start">
56
+ <FormLabel>{t('form.fields.categories.label')}</FormLabel>
57
+ <CourseMultiEntityPicker
58
+ value={field.value}
59
+ onChange={field.onChange}
60
+ onEditSelected={(option) => onEditCategory(option.value)}
61
+ options={categoryOptions}
62
+ placeholder={t('form.fields.categories.selectPlaceholder')}
63
+ searchPlaceholder={t(
64
+ 'form.fields.categories.searchPlaceholder'
65
+ )}
66
+ entityLabel="categoria"
67
+ emptyStateDescription={t('form.fields.categories.noResults')}
68
+ emptyHint="Nenhuma categoria vinculada ainda."
69
+ createActionLabel="Criar categoria"
70
+ createTitle="Criar categoria"
71
+ createDescription="Cadastre uma categoria rapidamente sem sair da edição do curso."
72
+ createFields={[
73
+ {
74
+ name: 'name',
75
+ label: 'Nome da categoria',
76
+ placeholder: 'Ex: Desenvolvimento Web',
77
+ required: true,
78
+ },
79
+ ]}
80
+ mapSearchToCreateValues={(search) => ({
81
+ name: search,
82
+ })}
83
+ onCreate={onCreateCategory}
84
+ />
85
+ <FormMessage />
86
+ </FormItem>
87
+ )}
88
+ />
96
89
 
97
- <FormField
98
- control={form.control}
99
- name="instrutores"
100
- render={({ field }) => (
101
- <FormItem>
102
- <FormLabel>{t('form.fields.instructors.label')}</FormLabel>
103
- <CourseMultiEntityPicker
104
- value={field.value}
105
- onChange={field.onChange}
106
- options={instructorOptions}
107
- placeholder={t('form.fields.instructors.selectPlaceholder')}
108
- searchPlaceholder={t(
109
- 'form.fields.instructors.searchPlaceholder'
110
- )}
111
- entityLabel="instrutor"
112
- emptyStateDescription={t('form.fields.instructors.noResults')}
113
- emptyHint="Nenhum instrutor vinculado ainda."
114
- createActionLabel="Cadastrar instrutor"
115
- onCreateClick={onCreateInstructor}
116
- variant="instructor"
117
- renderOptionMeta
118
- />
119
- <FormMessage />
120
- </FormItem>
121
- )}
122
- />
90
+ <FormField
91
+ control={form.control}
92
+ name="instrutores"
93
+ render={({ field }) => (
94
+ <FormItem className="self-start">
95
+ <FormLabel>{t('form.fields.instructors.label')}</FormLabel>
96
+ <CourseMultiEntityPicker
97
+ value={field.value}
98
+ onChange={field.onChange}
99
+ onEditSelected={(option) => onEditInstructor(option.value)}
100
+ options={instructorOptions}
101
+ placeholder={t('form.fields.instructors.selectPlaceholder')}
102
+ searchPlaceholder={t(
103
+ 'form.fields.instructors.searchPlaceholder'
104
+ )}
105
+ entityLabel="instrutor"
106
+ emptyStateDescription={t('form.fields.instructors.noResults')}
107
+ emptyHint="Nenhum instrutor vinculado ainda."
108
+ createActionLabel="Cadastrar instrutor"
109
+ onCreateClick={onCreateInstructor}
110
+ variant="instructor"
111
+ renderOptionMeta
112
+ />
113
+ <FormMessage />
114
+ </FormItem>
115
+ )}
116
+ />
117
+ </div>
123
118
 
124
119
  <FormField
125
120
  control={form.control}
@@ -128,10 +123,10 @@ export function CourseRelationsCard({
128
123
  <FormItem>
129
124
  <FormLabel>{t('form.fields.prerequisites.label')}</FormLabel>
130
125
  <FormControl>
131
- <Textarea
132
- {...field}
133
- rows={3}
134
- placeholder={t('form.fields.prerequisites.placeholder')}
126
+ <RichTextEditor
127
+ value={field.value || ''}
128
+ onChange={field.onChange}
129
+ className="w-full max-w-full"
135
130
  />
136
131
  </FormControl>
137
132
  <FormMessage />
@@ -0,0 +1,60 @@
1
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
2
+ import { CalendarDays, Percent, Users, Video } from 'lucide-react';
3
+
4
+ interface CourseSummaryCardProps {
5
+ course: {
6
+ totalAlunos?: number;
7
+ conclusaoMedia?: number;
8
+ totalAulas?: number;
9
+ totalSessoes?: number;
10
+ } | null;
11
+ }
12
+
13
+ export function CourseSummaryCard({ course }: CourseSummaryCardProps) {
14
+ return (
15
+ <KpiCardsGrid
16
+ items={[
17
+ {
18
+ key: 'students',
19
+ title: 'Total Alunos',
20
+ value: (course?.totalAlunos ?? 0).toLocaleString('pt-BR'),
21
+ icon: Users,
22
+ layout: 'compact',
23
+ accentClassName: 'from-slate-900/20 via-slate-700/10 to-transparent',
24
+ iconContainerClassName: 'bg-slate-900 text-white',
25
+ },
26
+ {
27
+ key: 'completion',
28
+ title: 'Conclusao Media',
29
+ value: `${course?.conclusaoMedia ?? 0}%`,
30
+ icon: Percent,
31
+ layout: 'compact',
32
+ accentClassName:
33
+ 'from-emerald-500/25 via-green-500/15 to-transparent',
34
+ iconContainerClassName: 'bg-emerald-100 text-emerald-700',
35
+ },
36
+ {
37
+ key: 'lessons',
38
+ title: 'Total de Aulas',
39
+ value: String(course?.totalAulas ?? 0),
40
+ icon: Video,
41
+ layout: 'compact',
42
+ accentClassName: 'from-sky-500/25 via-blue-500/15 to-transparent',
43
+ iconContainerClassName: 'bg-sky-100 text-sky-700',
44
+ },
45
+ {
46
+ key: 'sessions',
47
+ title: 'Sessoes',
48
+ value: String(course?.totalSessoes ?? 0),
49
+ icon: CalendarDays,
50
+ layout: 'compact',
51
+ accentClassName:
52
+ 'from-violet-500/25 via-indigo-500/15 to-transparent',
53
+ iconContainerClassName: 'bg-violet-100 text-violet-700',
54
+ },
55
+ ]}
56
+ columns={4}
57
+ className="mb-3"
58
+ />
59
+ );
60
+ }