@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.
- package/dist/class-group/class-group.controller.d.ts +3 -3
- package/dist/class-group/class-group.service.d.ts +3 -3
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +12 -20
- package/dist/course/course.service.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +72 -0
- package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
- package/dist/enterprise/enterprise.controller.js +10 -0
- package/dist/enterprise/enterprise.controller.js.map +1 -1
- package/dist/enterprise/enterprise.service.d.ts +78 -0
- package/dist/enterprise/enterprise.service.d.ts.map +1 -1
- package/dist/enterprise/enterprise.service.js +413 -40
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.controller.js +10 -6
- package/dist/enterprise/training/training-admin.controller.js.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +8 -2
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +108 -52
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
- package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
- package/dist/evaluation/evaluation.controller.d.ts +4 -4
- package/dist/evaluation/evaluation.service.d.ts +4 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
- package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
- package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/instructor-skill.controller.d.ts +4 -4
- package/dist/instructor/instructor-skill.service.d.ts +4 -7
- package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
- package/dist/instructor/instructor-skill.service.js +2 -89
- package/dist/instructor/instructor-skill.service.js.map +1 -1
- package/dist/instructor/instructor.controller.d.ts +20 -0
- package/dist/instructor/instructor.controller.d.ts.map +1 -1
- package/dist/instructor/instructor.controller.js +19 -0
- package/dist/instructor/instructor.controller.js.map +1 -1
- package/dist/instructor/instructor.service.d.ts +25 -0
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +70 -18
- package/dist/instructor/instructor.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js.map +1 -1
- package/hedhog/data/route.yaml +23 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
- package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +40 -13
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
- package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
- package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +625 -366
- package/hedhog/frontend/app/instructors/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/paths/page.tsx.ejs +9 -4
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +9 -4
- package/hedhog/frontend/messages/en.json +101 -10
- package/hedhog/frontend/messages/pt.json +101 -10
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/package.json +7 -7
- package/src/course/course.service.ts +12 -24
- package/src/enterprise/enterprise.controller.ts +5 -0
- package/src/enterprise/enterprise.service.ts +507 -29
- package/src/enterprise/training/training-admin.controller.ts +4 -0
- package/src/enterprise/training/training-admin.service.ts +115 -51
- package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
- package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
- package/src/instructor/instructor-skill.service.ts +2 -97
- package/src/instructor/instructor.controller.ts +16 -0
- package/src/instructor/instructor.service.ts +85 -10
- 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 {
|
|
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
|
|
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
|
|
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
|
-
<
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
<
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'form.fields.categories.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
<
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
}
|