@hed-hog/lms 0.0.314 → 0.0.316
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/enterprise/dto/enterprise-profile.dto.d.ts +13 -0
- package/dist/enterprise/dto/enterprise-profile.dto.d.ts.map +1 -0
- package/dist/enterprise/dto/enterprise-profile.dto.js +3 -0
- package/dist/enterprise/dto/enterprise-profile.dto.js.map +1 -0
- package/dist/enterprise/enterprise.controller.d.ts +3 -0
- package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
- package/dist/enterprise/enterprise.controller.js +14 -0
- package/dist/enterprise/enterprise.controller.js.map +1 -1
- package/dist/enterprise/enterprise.service.d.ts +3 -0
- package/dist/enterprise/enterprise.service.d.ts.map +1 -1
- package/dist/enterprise/enterprise.service.js +128 -1
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/instructor/instructor.controller.d.ts +23 -0
- package/dist/instructor/instructor.controller.d.ts.map +1 -1
- package/dist/instructor/instructor.controller.js +41 -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 +126 -8
- package/dist/instructor/instructor.service.js.map +1 -1
- package/hedhog/data/menu.yaml +23 -7
- package/hedhog/data/role.yaml +17 -1
- package/hedhog/data/route.yaml +48 -0
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +44 -44
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -362
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -111
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -134
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -113
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -314
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -62
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +173 -173
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -58
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +51 -51
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -276
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -1216
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1824 -1824
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -443
- package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +40 -40
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -185
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -264
- package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +95 -95
- package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +73 -73
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -136
- package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -80
- package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +949 -949
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -525
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +181 -181
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +51 -51
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -271
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -167
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -108
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -318
- package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -10
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +2 -1
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +438 -0
- package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +40 -0
- package/hedhog/frontend/app/instructors/page.tsx.ejs +696 -0
- package/hedhog/frontend/app/training/page.tsx.ejs +2339 -0
- package/hedhog/query/add_route_role.sql +15 -0
- package/hedhog/table/enterprise_user.yaml +1 -1
- package/package.json +6 -6
- package/src/enterprise/dto/enterprise-profile.dto.ts +17 -0
- package/src/enterprise/enterprise.controller.ts +9 -1
- package/src/enterprise/enterprise.service.ts +147 -4
- package/src/instructor/instructor.controller.ts +36 -9
- package/src/instructor/instructor.service.ts +140 -10
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
5
|
+
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
|
6
|
+
import {
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from '@/components/ui/select';
|
|
13
|
+
import {
|
|
14
|
+
Sheet,
|
|
15
|
+
SheetContent,
|
|
16
|
+
SheetDescription,
|
|
17
|
+
SheetFooter,
|
|
18
|
+
SheetHeader,
|
|
19
|
+
SheetTitle,
|
|
20
|
+
} from '@/components/ui/sheet';
|
|
21
|
+
import { Switch } from '@/components/ui/switch';
|
|
22
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
23
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
24
|
+
import { Loader2, Plus } from 'lucide-react';
|
|
25
|
+
import { useEffect, useState } from 'react';
|
|
26
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
27
|
+
import { toast } from 'sonner';
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import { CreateLmsPersonSheet } from '../../_components/create-lms-person-sheet';
|
|
30
|
+
import type { InstructorRow, PersonOption } from './instructor-types';
|
|
31
|
+
|
|
32
|
+
const QUALIFICATION_OPTIONS = [
|
|
33
|
+
{
|
|
34
|
+
slug: 'course-lessons',
|
|
35
|
+
label: 'Aulas de curso',
|
|
36
|
+
description: 'Pode atuar em aulas gravadas e estrutura de curso.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
slug: 'class-sessions',
|
|
40
|
+
label: 'Sessões de turma',
|
|
41
|
+
description: 'Pode atuar em contextos ao vivo e turmas.',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const instructorFormSchema = z.object({
|
|
46
|
+
personId: z
|
|
47
|
+
.number({ invalid_type_error: 'Selecione uma pessoa' })
|
|
48
|
+
.int()
|
|
49
|
+
.positive('Selecione uma pessoa'),
|
|
50
|
+
qualificationSlugs: z
|
|
51
|
+
.array(z.string())
|
|
52
|
+
.min(1, 'Selecione pelo menos uma qualificação'),
|
|
53
|
+
status: z.enum(['active', 'inactive']),
|
|
54
|
+
can_teach_courses: z.boolean(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
type InstructorFormValues = z.infer<typeof instructorFormSchema>;
|
|
58
|
+
|
|
59
|
+
type InstructorFormSheetProps = {
|
|
60
|
+
open: boolean;
|
|
61
|
+
onOpenChange: (open: boolean) => void;
|
|
62
|
+
instructorId?: number | null;
|
|
63
|
+
onSaved?: (instructor: InstructorRow) => void | Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function InstructorFormSheet({
|
|
67
|
+
open,
|
|
68
|
+
onOpenChange,
|
|
69
|
+
instructorId,
|
|
70
|
+
onSaved,
|
|
71
|
+
}: InstructorFormSheetProps) {
|
|
72
|
+
const { request } = useApp();
|
|
73
|
+
const [saving, setSaving] = useState(false);
|
|
74
|
+
const [createPersonOpen, setCreatePersonOpen] = useState(false);
|
|
75
|
+
const [trainingAccess, setTrainingAccess] = useState<boolean | null>(null);
|
|
76
|
+
const [togglingAccess, setTogglingAccess] = useState(false);
|
|
77
|
+
const isEditing = typeof instructorId === 'number' && instructorId > 0;
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
control,
|
|
81
|
+
handleSubmit,
|
|
82
|
+
reset,
|
|
83
|
+
setValue,
|
|
84
|
+
formState: { errors },
|
|
85
|
+
} = useForm<InstructorFormValues>({
|
|
86
|
+
resolver: zodResolver(instructorFormSchema),
|
|
87
|
+
defaultValues: {
|
|
88
|
+
personId: undefined,
|
|
89
|
+
qualificationSlugs: ['course-lessons'],
|
|
90
|
+
status: 'active',
|
|
91
|
+
can_teach_courses: true,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const { data: existingInstructor } = useQuery<InstructorRow | null>({
|
|
96
|
+
queryKey: ['lms-instructor-detail', instructorId],
|
|
97
|
+
queryFn: async () => {
|
|
98
|
+
if (!instructorId) return null;
|
|
99
|
+
const response = await request<InstructorRow>({
|
|
100
|
+
url: `/lms/instructors/${instructorId}`,
|
|
101
|
+
method: 'GET',
|
|
102
|
+
});
|
|
103
|
+
return response.data;
|
|
104
|
+
},
|
|
105
|
+
enabled: isEditing && open,
|
|
106
|
+
placeholderData: null,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (isEditing && existingInstructor) {
|
|
111
|
+
reset({
|
|
112
|
+
personId: existingInstructor.personId,
|
|
113
|
+
qualificationSlugs:
|
|
114
|
+
existingInstructor.qualificationSlugs.length > 0
|
|
115
|
+
? existingInstructor.qualificationSlugs
|
|
116
|
+
: ['course-lessons'],
|
|
117
|
+
status: existingInstructor.status,
|
|
118
|
+
can_teach_courses: true,
|
|
119
|
+
});
|
|
120
|
+
setTrainingAccess(existingInstructor.hasTrainingAccess ?? false);
|
|
121
|
+
} else if (!isEditing && open) {
|
|
122
|
+
reset({
|
|
123
|
+
personId: undefined,
|
|
124
|
+
qualificationSlugs: ['course-lessons'],
|
|
125
|
+
status: 'active',
|
|
126
|
+
can_teach_courses: true,
|
|
127
|
+
});
|
|
128
|
+
setTrainingAccess(null);
|
|
129
|
+
}
|
|
130
|
+
}, [isEditing, existingInstructor, open, reset]);
|
|
131
|
+
|
|
132
|
+
const handleSheetClose = (nextOpen: boolean) => {
|
|
133
|
+
if (!nextOpen) {
|
|
134
|
+
reset();
|
|
135
|
+
}
|
|
136
|
+
onOpenChange(nextOpen);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const onSubmit = async (values: InstructorFormValues) => {
|
|
140
|
+
try {
|
|
141
|
+
setSaving(true);
|
|
142
|
+
|
|
143
|
+
let result: InstructorRow;
|
|
144
|
+
|
|
145
|
+
if (isEditing) {
|
|
146
|
+
const response = await request<InstructorRow>({
|
|
147
|
+
url: `/lms/instructors/${instructorId}`,
|
|
148
|
+
method: 'PATCH',
|
|
149
|
+
data: {
|
|
150
|
+
qualificationSlugs: values.qualificationSlugs,
|
|
151
|
+
status: values.status,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
result = response.data;
|
|
155
|
+
toast.success('Instrutor atualizado com sucesso.');
|
|
156
|
+
} else {
|
|
157
|
+
const response = await request<InstructorRow>({
|
|
158
|
+
url: '/lms/instructors',
|
|
159
|
+
method: 'POST',
|
|
160
|
+
data: {
|
|
161
|
+
personId: values.personId,
|
|
162
|
+
name: '',
|
|
163
|
+
qualificationSlugs: values.qualificationSlugs,
|
|
164
|
+
status: values.status,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
result = response.data;
|
|
168
|
+
toast.success('Instrutor criado com sucesso.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await onSaved?.(result);
|
|
172
|
+
handleSheetClose(false);
|
|
173
|
+
} catch {
|
|
174
|
+
toast.error(
|
|
175
|
+
isEditing
|
|
176
|
+
? 'Erro ao atualizar instrutor. Tente novamente.'
|
|
177
|
+
: 'Erro ao criar instrutor. Tente novamente.'
|
|
178
|
+
);
|
|
179
|
+
} finally {
|
|
180
|
+
setSaving(false);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<>
|
|
186
|
+
<Sheet open={open} onOpenChange={handleSheetClose}>
|
|
187
|
+
<SheetContent
|
|
188
|
+
side="right"
|
|
189
|
+
className="flex w-full max-w-md flex-col overflow-y-auto sm:max-w-lg"
|
|
190
|
+
>
|
|
191
|
+
<SheetHeader>
|
|
192
|
+
<SheetTitle>
|
|
193
|
+
{isEditing ? 'Editar Instrutor' : 'Novo Instrutor'}
|
|
194
|
+
</SheetTitle>
|
|
195
|
+
<SheetDescription>
|
|
196
|
+
{isEditing
|
|
197
|
+
? 'Atualize os dados do perfil do instrutor.'
|
|
198
|
+
: 'Vincule uma pessoa existente ou crie uma nova para cadastrá-la como instrutora.'}
|
|
199
|
+
</SheetDescription>
|
|
200
|
+
</SheetHeader>
|
|
201
|
+
|
|
202
|
+
<form
|
|
203
|
+
onSubmit={handleSubmit(onSubmit)}
|
|
204
|
+
className="mt-6 flex flex-1 flex-col gap-5 px-4"
|
|
205
|
+
>
|
|
206
|
+
{/* Person picker */}
|
|
207
|
+
{!isEditing && (
|
|
208
|
+
<Field>
|
|
209
|
+
<FieldLabel>
|
|
210
|
+
Pessoa <span className="text-destructive">*</span>
|
|
211
|
+
</FieldLabel>
|
|
212
|
+
<div className="flex items-center gap-2">
|
|
213
|
+
<div className="min-w-0 flex-1">
|
|
214
|
+
<Controller
|
|
215
|
+
control={control}
|
|
216
|
+
name="personId"
|
|
217
|
+
render={({ field }) => (
|
|
218
|
+
<EntityPicker
|
|
219
|
+
value={field.value ?? null}
|
|
220
|
+
onChange={(val) =>
|
|
221
|
+
field.onChange(
|
|
222
|
+
val !== null ? Number(val) : undefined
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
valueType="number"
|
|
226
|
+
loadOptions={async ({ page, pageSize, search }) => {
|
|
227
|
+
const response = await request<{
|
|
228
|
+
data: PersonOption[];
|
|
229
|
+
lastPage?: number;
|
|
230
|
+
}>({
|
|
231
|
+
url: '/person',
|
|
232
|
+
method: 'GET',
|
|
233
|
+
params: {
|
|
234
|
+
page,
|
|
235
|
+
pageSize,
|
|
236
|
+
type: 'individual',
|
|
237
|
+
...(search.trim()
|
|
238
|
+
? { search: search.trim() }
|
|
239
|
+
: {}),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
items: (response.data?.data ?? []).map((p) => ({
|
|
244
|
+
id: p.id,
|
|
245
|
+
name: p.name,
|
|
246
|
+
})),
|
|
247
|
+
hasMore: page < (response.data?.lastPage ?? 1),
|
|
248
|
+
};
|
|
249
|
+
}}
|
|
250
|
+
getOptionValue={(opt) => opt.id as number}
|
|
251
|
+
getOptionLabel={(opt) => opt.name ?? ''}
|
|
252
|
+
placeholder="Buscar pessoa por nome..."
|
|
253
|
+
searchable
|
|
254
|
+
clearable
|
|
255
|
+
showCreateButton={false}
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
<Button
|
|
261
|
+
type="button"
|
|
262
|
+
variant="outline"
|
|
263
|
+
size="icon"
|
|
264
|
+
className="shrink-0"
|
|
265
|
+
onClick={() => setCreatePersonOpen(true)}
|
|
266
|
+
aria-label="Criar nova pessoa"
|
|
267
|
+
title="Criar nova pessoa"
|
|
268
|
+
>
|
|
269
|
+
<Plus className="h-4 w-4" />
|
|
270
|
+
</Button>
|
|
271
|
+
</div>
|
|
272
|
+
{errors.personId && (
|
|
273
|
+
<FieldError>{errors.personId.message}</FieldError>
|
|
274
|
+
)}
|
|
275
|
+
</Field>
|
|
276
|
+
)}
|
|
277
|
+
|
|
278
|
+
{/* Status */}
|
|
279
|
+
<Field>
|
|
280
|
+
<FieldLabel>Status</FieldLabel>
|
|
281
|
+
<Controller
|
|
282
|
+
control={control}
|
|
283
|
+
name="status"
|
|
284
|
+
render={({ field }) => (
|
|
285
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
286
|
+
<SelectTrigger>
|
|
287
|
+
<SelectValue placeholder="Selecione o status" />
|
|
288
|
+
</SelectTrigger>
|
|
289
|
+
<SelectContent>
|
|
290
|
+
<SelectItem value="active">Ativo</SelectItem>
|
|
291
|
+
<SelectItem value="inactive">Inativo</SelectItem>
|
|
292
|
+
</SelectContent>
|
|
293
|
+
</Select>
|
|
294
|
+
)}
|
|
295
|
+
/>
|
|
296
|
+
</Field>
|
|
297
|
+
|
|
298
|
+
{/* can_teach_courses */}
|
|
299
|
+
<div className="flex items-center justify-between rounded-lg border p-4">
|
|
300
|
+
<div className="space-y-0.5">
|
|
301
|
+
<p className="text-sm font-medium">Pode ensinar cursos</p>
|
|
302
|
+
<p className="text-xs text-muted-foreground">
|
|
303
|
+
Permite que o instrutor seja vinculado a aulas de curso.
|
|
304
|
+
</p>
|
|
305
|
+
</div>
|
|
306
|
+
<Controller
|
|
307
|
+
control={control}
|
|
308
|
+
name="can_teach_courses"
|
|
309
|
+
render={({ field }) => (
|
|
310
|
+
<Switch
|
|
311
|
+
checked={field.value}
|
|
312
|
+
onCheckedChange={field.onChange}
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
{/* Acesso ao Training */}
|
|
319
|
+
{isEditing && (
|
|
320
|
+
<div className="flex items-center justify-between rounded-lg border p-4">
|
|
321
|
+
<div className="space-y-0.5">
|
|
322
|
+
<p className="text-sm font-medium">
|
|
323
|
+
Acesso ao Hcode Training
|
|
324
|
+
</p>
|
|
325
|
+
<p className="text-xs text-muted-foreground">
|
|
326
|
+
{existingInstructor?.userId
|
|
327
|
+
? 'Permite que este instrutor acesse a plataforma com o perfil de instrutor.'
|
|
328
|
+
: 'Nenhum usuário vinculado a este cadastro. Vincule um usuário para habilitar.'}
|
|
329
|
+
</p>
|
|
330
|
+
</div>
|
|
331
|
+
<Switch
|
|
332
|
+
checked={trainingAccess ?? false}
|
|
333
|
+
disabled={!existingInstructor?.userId || togglingAccess}
|
|
334
|
+
onCheckedChange={async (next) => {
|
|
335
|
+
setTogglingAccess(true);
|
|
336
|
+
try {
|
|
337
|
+
await request({
|
|
338
|
+
url: `/lms/instructors/${instructorId}/training-access`,
|
|
339
|
+
method: 'PATCH',
|
|
340
|
+
data: { enabled: next },
|
|
341
|
+
});
|
|
342
|
+
setTrainingAccess(next);
|
|
343
|
+
toast.success(
|
|
344
|
+
next
|
|
345
|
+
? 'Acesso ao Training habilitado.'
|
|
346
|
+
: 'Acesso ao Training desabilitado.'
|
|
347
|
+
);
|
|
348
|
+
} catch {
|
|
349
|
+
toast.error('Erro ao alterar acesso ao Training.');
|
|
350
|
+
} finally {
|
|
351
|
+
setTogglingAccess(false);
|
|
352
|
+
}
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
</div>
|
|
356
|
+
)}
|
|
357
|
+
|
|
358
|
+
{/* Qualificações */}
|
|
359
|
+
<Field>
|
|
360
|
+
<FieldLabel>
|
|
361
|
+
Qualificações <span className="text-destructive">*</span>
|
|
362
|
+
</FieldLabel>
|
|
363
|
+
<div className="space-y-3">
|
|
364
|
+
{QUALIFICATION_OPTIONS.map((option) => (
|
|
365
|
+
<Controller
|
|
366
|
+
key={option.slug}
|
|
367
|
+
control={control}
|
|
368
|
+
name="qualificationSlugs"
|
|
369
|
+
render={({ field }) => {
|
|
370
|
+
const checked = field.value.includes(option.slug);
|
|
371
|
+
return (
|
|
372
|
+
<div className="flex items-start justify-between rounded-lg border p-3">
|
|
373
|
+
<div className="space-y-0.5">
|
|
374
|
+
<p className="text-sm font-medium">
|
|
375
|
+
{option.label}
|
|
376
|
+
</p>
|
|
377
|
+
<p className="text-xs text-muted-foreground">
|
|
378
|
+
{option.description}
|
|
379
|
+
</p>
|
|
380
|
+
</div>
|
|
381
|
+
<Switch
|
|
382
|
+
checked={checked}
|
|
383
|
+
onCheckedChange={(next) => {
|
|
384
|
+
if (next) {
|
|
385
|
+
field.onChange([...field.value, option.slug]);
|
|
386
|
+
} else {
|
|
387
|
+
field.onChange(
|
|
388
|
+
field.value.filter((s) => s !== option.slug)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}}
|
|
392
|
+
/>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}}
|
|
396
|
+
/>
|
|
397
|
+
))}
|
|
398
|
+
</div>
|
|
399
|
+
{errors.qualificationSlugs && (
|
|
400
|
+
<FieldError>{errors.qualificationSlugs.message}</FieldError>
|
|
401
|
+
)}
|
|
402
|
+
</Field>
|
|
403
|
+
|
|
404
|
+
<SheetFooter className="mt-auto pb-4">
|
|
405
|
+
<Button
|
|
406
|
+
type="button"
|
|
407
|
+
variant="outline"
|
|
408
|
+
onClick={() => handleSheetClose(false)}
|
|
409
|
+
disabled={saving}
|
|
410
|
+
>
|
|
411
|
+
Cancelar
|
|
412
|
+
</Button>
|
|
413
|
+
<Button type="submit" disabled={saving}>
|
|
414
|
+
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
415
|
+
{isEditing ? 'Salvar alterações' : 'Criar instrutor'}
|
|
416
|
+
</Button>
|
|
417
|
+
</SheetFooter>
|
|
418
|
+
</form>
|
|
419
|
+
</SheetContent>
|
|
420
|
+
</Sheet>
|
|
421
|
+
|
|
422
|
+
<CreateLmsPersonSheet
|
|
423
|
+
open={createPersonOpen}
|
|
424
|
+
onOpenChange={setCreatePersonOpen}
|
|
425
|
+
onCreated={(instructor) => {
|
|
426
|
+
setValue('personId', instructor.personId);
|
|
427
|
+
setCreatePersonOpen(false);
|
|
428
|
+
}}
|
|
429
|
+
title="Nova Pessoa"
|
|
430
|
+
description="Preencha os dados para criar uma nova pessoa e vinculá-la como instrutora."
|
|
431
|
+
submitLabel="Criar pessoa"
|
|
432
|
+
successMessage="Pessoa criada com sucesso."
|
|
433
|
+
errorMessage="Erro ao criar pessoa."
|
|
434
|
+
defaultQualificationSlugs={['course-lessons']}
|
|
435
|
+
/>
|
|
436
|
+
</>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type InstructorRow = {
|
|
2
|
+
id: number;
|
|
3
|
+
personId: number;
|
|
4
|
+
name: string;
|
|
5
|
+
avatarId: number | null;
|
|
6
|
+
email?: string | null;
|
|
7
|
+
phone?: string | null;
|
|
8
|
+
status: 'active' | 'inactive';
|
|
9
|
+
qualificationSlugs: string[];
|
|
10
|
+
userId?: number | null;
|
|
11
|
+
hasTrainingAccess?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type InstructorStats = {
|
|
15
|
+
total: number;
|
|
16
|
+
active: number;
|
|
17
|
+
inactive: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type InstructorPaginatedResult = {
|
|
21
|
+
data: InstructorRow[];
|
|
22
|
+
total: number;
|
|
23
|
+
page: number;
|
|
24
|
+
pageSize: number;
|
|
25
|
+
lastPage?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type PersonOption = {
|
|
29
|
+
id: number;
|
|
30
|
+
name: string;
|
|
31
|
+
avatarId?: number | null;
|
|
32
|
+
email?: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type InstructorFormValues = {
|
|
36
|
+
personId: number | null;
|
|
37
|
+
qualificationSlugs: string[];
|
|
38
|
+
status: 'active' | 'inactive';
|
|
39
|
+
can_teach_courses: boolean;
|
|
40
|
+
};
|