@hed-hog/lms 0.0.331 → 0.0.347
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 +8 -8
- package/dist/class-group/class-group.service.d.ts +8 -8
- package/dist/course/course.controller.d.ts +6 -1
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.controller.js +19 -2
- package/dist/course/course.controller.js.map +1 -1
- package/dist/course/course.service.d.ts +6 -0
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +63 -28
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course.dto.d.ts +1 -0
- package/dist/course/dto/create-course.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course.dto.js +5 -0
- package/dist/course/dto/create-course.dto.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +84 -12
- 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 +90 -12
- 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 +9 -6
- 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 +11 -5
- 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 +2 -2
- package/dist/evaluation/evaluation.service.d.ts +2 -2
- 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 +21 -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 +27 -0
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +79 -25
- 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/dist/training/dto/create-training.dto.d.ts +1 -0
- package/dist/training/dto/create-training.dto.d.ts.map +1 -1
- package/dist/training/dto/create-training.dto.js +5 -0
- package/dist/training/dto/create-training.dto.js.map +1 -1
- package/dist/training/training.controller.d.ts +4 -0
- package/dist/training/training.controller.d.ts.map +1 -1
- package/dist/training/training.service.d.ts +8 -0
- package/dist/training/training.service.d.ts.map +1 -1
- package/dist/training/training.service.js +71 -6
- package/dist/training/training.service.js.map +1 -1
- package/hedhog/data/route.yaml +23 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +80 -33
- 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 +39 -7
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +1 -3
- package/hedhog/frontend/app/classes/page.tsx.ejs +34 -7
- 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 +243 -34
- 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 +31 -19
- 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 +76 -8
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +78 -9
- package/hedhog/frontend/messages/en.json +101 -10
- package/hedhog/frontend/messages/pt.json +115 -11
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/hedhog/table/learning_path.yaml +4 -0
- package/package.json +6 -6
- package/src/course/course.controller.ts +18 -0
- package/src/course/course.service.ts +85 -26
- package/src/course/dto/create-course.dto.ts +4 -0
- 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 +87 -10
- package/src/lms.module.ts +1 -0
- package/src/training/dto/create-training.dto.ts +4 -0
- package/src/training/training.service.ts +104 -5
|
@@ -23,7 +23,6 @@ import { useForm } from 'react-hook-form';
|
|
|
23
23
|
import { toast } from 'sonner';
|
|
24
24
|
import { z } from 'zod';
|
|
25
25
|
|
|
26
|
-
import { CreateLmsPersonSheet } from '@/app/(app)/(libraries)/lms/_components/create-lms-person-sheet';
|
|
27
26
|
import { createDefaultTemplate } from '@/app/(app)/(libraries)/lms/_lib/editor/types';
|
|
28
27
|
import { RichTextEditor } from '@/components/rich-text-editor';
|
|
29
28
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -48,10 +47,19 @@ import {
|
|
|
48
47
|
import { Input } from '@/components/ui/input';
|
|
49
48
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
50
49
|
import { Separator } from '@/components/ui/separator';
|
|
50
|
+
import {
|
|
51
|
+
Sheet,
|
|
52
|
+
SheetContent,
|
|
53
|
+
SheetDescription,
|
|
54
|
+
SheetFooter,
|
|
55
|
+
SheetHeader,
|
|
56
|
+
SheetTitle,
|
|
57
|
+
} from '@/components/ui/sheet';
|
|
51
58
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
52
59
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
53
60
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
54
61
|
|
|
62
|
+
import { InstructorFormSheet } from '../../../../instructors/_components/instructor-form-sheet';
|
|
55
63
|
import type {
|
|
56
64
|
CourseEditFormValues,
|
|
57
65
|
PickerOption,
|
|
@@ -114,6 +122,16 @@ type ApiCourseDetail = {
|
|
|
114
122
|
};
|
|
115
123
|
|
|
116
124
|
type ApiCategory = { id: number; slug: string; name: string };
|
|
125
|
+
type ApiCategoryDetail = {
|
|
126
|
+
id: number;
|
|
127
|
+
slug: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
category_id?: number | null;
|
|
130
|
+
color?: string | null;
|
|
131
|
+
icon?: string | null;
|
|
132
|
+
status?: 'active' | 'inactive';
|
|
133
|
+
category_locale?: Array<{ locale_id: number; name: string }>;
|
|
134
|
+
};
|
|
117
135
|
type ApiCategoryList = {
|
|
118
136
|
data: ApiCategory[];
|
|
119
137
|
total: number;
|
|
@@ -284,7 +302,27 @@ export function EditorCourse() {
|
|
|
284
302
|
|
|
285
303
|
// ── UI state ────────────────────────────────────────────────────────────────
|
|
286
304
|
const [activeTab, setActiveTab] = useState('estrutura');
|
|
287
|
-
const [
|
|
305
|
+
const [instructorEditSheetOpen, setInstructorEditSheetOpen] = useState(false);
|
|
306
|
+
const [editingInstructorId, setEditingInstructorId] = useState<number | null>(
|
|
307
|
+
null
|
|
308
|
+
);
|
|
309
|
+
const [categoryEditSheetOpen, setCategoryEditSheetOpen] = useState(false);
|
|
310
|
+
const [editingCategoryId, setEditingCategoryId] = useState<number | null>(
|
|
311
|
+
null
|
|
312
|
+
);
|
|
313
|
+
const [editingCategoryOriginalSlug, setEditingCategoryOriginalSlug] =
|
|
314
|
+
useState('');
|
|
315
|
+
const [editingCategoryName, setEditingCategoryName] = useState('');
|
|
316
|
+
const [editingCategorySlug, setEditingCategorySlug] = useState('');
|
|
317
|
+
const [editingCategoryColor, setEditingCategoryColor] = useState('#000000');
|
|
318
|
+
const [editingCategoryIcon, setEditingCategoryIcon] = useState('');
|
|
319
|
+
const [editingCategoryParentId, setEditingCategoryParentId] = useState<
|
|
320
|
+
number | null
|
|
321
|
+
>(null);
|
|
322
|
+
const [editingCategoryStatus, setEditingCategoryStatus] = useState<
|
|
323
|
+
'active' | 'inactive'
|
|
324
|
+
>('active');
|
|
325
|
+
const [savingCategoryEdit, setSavingCategoryEdit] = useState(false);
|
|
288
326
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
289
327
|
const [deleting, setDeleting] = useState(false);
|
|
290
328
|
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
|
@@ -415,7 +453,7 @@ export function EditorCourse() {
|
|
|
415
453
|
hasCertificate: data.certificado,
|
|
416
454
|
isFeatured: data.destaque,
|
|
417
455
|
isListed: data.listado,
|
|
418
|
-
certificateModel: data.modeloCertificado ||
|
|
456
|
+
certificateModel: data.modeloCertificado.trim() || null,
|
|
419
457
|
instructorIds: (data.instrutores ?? []).map(Number),
|
|
420
458
|
},
|
|
421
459
|
});
|
|
@@ -691,7 +729,7 @@ export function EditorCourse() {
|
|
|
691
729
|
|
|
692
730
|
async function handleCreateCategory(values: Record<string, string>) {
|
|
693
731
|
const name = String(values.name ?? '').trim();
|
|
694
|
-
const slug = slugify(values.
|
|
732
|
+
const slug = slugify(values.name || values.slug || '');
|
|
695
733
|
if (!name || !slug) {
|
|
696
734
|
toast.error('Informe nome e slug para criar a categoria.');
|
|
697
735
|
return null;
|
|
@@ -764,26 +802,112 @@ export function EditorCourse() {
|
|
|
764
802
|
}
|
|
765
803
|
}
|
|
766
804
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
805
|
+
function handleCreateInstructor() {
|
|
806
|
+
setEditingInstructorId(null);
|
|
807
|
+
setInstructorEditSheetOpen(true);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function handleEditInstructor(instructorId: string) {
|
|
811
|
+
const parsed = Number(instructorId);
|
|
812
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
813
|
+
toast.error('Instrutor inválido para edição.');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
setEditingInstructorId(parsed);
|
|
817
|
+
setInstructorEditSheetOpen(true);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function handleEditCategory(categorySlug: string) {
|
|
821
|
+
const category = (categoryListData?.data ?? []).find(
|
|
822
|
+
(item) => item.slug === categorySlug
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
if (!category?.id) {
|
|
826
|
+
toast.error('Categoria não encontrada para edição.');
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
try {
|
|
831
|
+
const response = await request<ApiCategoryDetail>({
|
|
832
|
+
url: `/category/${category.id}`,
|
|
833
|
+
method: 'GET',
|
|
783
834
|
});
|
|
835
|
+
const detail = response.data;
|
|
836
|
+
const localeName = detail.category_locale?.[0]?.name ?? detail.name ?? '';
|
|
837
|
+
|
|
838
|
+
setEditingCategoryId(detail.id);
|
|
839
|
+
setEditingCategoryOriginalSlug(detail.slug || categorySlug);
|
|
840
|
+
setEditingCategorySlug(detail.slug || categorySlug);
|
|
841
|
+
setEditingCategoryName(localeName);
|
|
842
|
+
setEditingCategoryColor(detail.color || '#000000');
|
|
843
|
+
setEditingCategoryIcon(detail.icon || '');
|
|
844
|
+
setEditingCategoryParentId(detail.category_id ?? null);
|
|
845
|
+
setEditingCategoryStatus(detail.status || 'active');
|
|
846
|
+
setCategoryEditSheetOpen(true);
|
|
847
|
+
} catch {
|
|
848
|
+
toast.error('Não foi possível carregar a categoria para edição.');
|
|
784
849
|
}
|
|
785
|
-
|
|
786
|
-
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function handleSaveCategoryEdit() {
|
|
853
|
+
if (!editingCategoryId) return;
|
|
854
|
+
|
|
855
|
+
const name = editingCategoryName.trim();
|
|
856
|
+
const slug = slugify(editingCategorySlug);
|
|
857
|
+
|
|
858
|
+
if (!name || !slug) {
|
|
859
|
+
toast.error('Informe nome e slug válidos para a categoria.');
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const localeCode =
|
|
864
|
+
currentLocaleCode ||
|
|
865
|
+
(locales?.[0] as Locale | undefined)?.code ||
|
|
866
|
+
'pt-BR';
|
|
867
|
+
|
|
868
|
+
setSavingCategoryEdit(true);
|
|
869
|
+
try {
|
|
870
|
+
await request({
|
|
871
|
+
url: `/category/${editingCategoryId}`,
|
|
872
|
+
method: 'PATCH',
|
|
873
|
+
data: {
|
|
874
|
+
locale: {
|
|
875
|
+
[localeCode]: {
|
|
876
|
+
name,
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
slug,
|
|
880
|
+
category_id: editingCategoryParentId,
|
|
881
|
+
color: editingCategoryColor,
|
|
882
|
+
icon: editingCategoryIcon,
|
|
883
|
+
status: editingCategoryStatus,
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
const selectedCategories = form.getValues('categorias') ?? [];
|
|
888
|
+
if (editingCategoryOriginalSlug && editingCategoryOriginalSlug !== slug) {
|
|
889
|
+
form.setValue(
|
|
890
|
+
'categorias',
|
|
891
|
+
selectedCategories.map((item) =>
|
|
892
|
+
item === editingCategoryOriginalSlug ? slug : item
|
|
893
|
+
),
|
|
894
|
+
{
|
|
895
|
+
shouldDirty: true,
|
|
896
|
+
shouldTouch: true,
|
|
897
|
+
shouldValidate: true,
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
await refetchCategoryOptions();
|
|
903
|
+
setCategoryEditSheetOpen(false);
|
|
904
|
+
toast.success('Categoria atualizada com sucesso.');
|
|
905
|
+
} catch {
|
|
906
|
+
toast.error('Não foi possível atualizar a categoria.');
|
|
907
|
+
} finally {
|
|
908
|
+
setSavingCategoryEdit(false);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
787
911
|
|
|
788
912
|
async function handleDelete() {
|
|
789
913
|
setDeleting(true);
|
|
@@ -1008,7 +1132,9 @@ export function EditorCourse() {
|
|
|
1008
1132
|
categoryOptions={categoryOptions}
|
|
1009
1133
|
instructorOptions={instructorOptions}
|
|
1010
1134
|
onCreateCategory={handleCreateCategory}
|
|
1011
|
-
onCreateInstructor={
|
|
1135
|
+
onCreateInstructor={handleCreateInstructor}
|
|
1136
|
+
onEditCategory={handleEditCategory}
|
|
1137
|
+
onEditInstructor={handleEditInstructor}
|
|
1012
1138
|
/>
|
|
1013
1139
|
<CourseContentCard form={form} />
|
|
1014
1140
|
</TabsContent>
|
|
@@ -1131,19 +1257,102 @@ export function EditorCourse() {
|
|
|
1131
1257
|
</div>
|
|
1132
1258
|
</form>
|
|
1133
1259
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1260
|
+
<InstructorFormSheet
|
|
1261
|
+
open={instructorEditSheetOpen}
|
|
1262
|
+
onOpenChange={(open) => {
|
|
1263
|
+
setInstructorEditSheetOpen(open);
|
|
1264
|
+
if (!open) {
|
|
1265
|
+
setEditingInstructorId(null);
|
|
1266
|
+
}
|
|
1267
|
+
}}
|
|
1268
|
+
instructorId={editingInstructorId}
|
|
1269
|
+
onSaved={async (instructor) => {
|
|
1270
|
+
if (editingInstructorId === null && instructor?.id) {
|
|
1271
|
+
const createdId = String(instructor.id);
|
|
1272
|
+
const current = form.getValues('instrutores') ?? [];
|
|
1273
|
+
if (!current.includes(createdId)) {
|
|
1274
|
+
form.setValue('instrutores', [...current, createdId], {
|
|
1275
|
+
shouldDirty: true,
|
|
1276
|
+
shouldTouch: true,
|
|
1277
|
+
shouldValidate: true,
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
await refetchInstructorOptions();
|
|
1283
|
+
await refetchCourse();
|
|
1284
|
+
}}
|
|
1145
1285
|
/>
|
|
1146
1286
|
|
|
1287
|
+
<Sheet
|
|
1288
|
+
open={categoryEditSheetOpen}
|
|
1289
|
+
onOpenChange={(open) => {
|
|
1290
|
+
setCategoryEditSheetOpen(open);
|
|
1291
|
+
if (!open) {
|
|
1292
|
+
setEditingCategoryId(null);
|
|
1293
|
+
setEditingCategoryOriginalSlug('');
|
|
1294
|
+
setEditingCategoryName('');
|
|
1295
|
+
setEditingCategorySlug('');
|
|
1296
|
+
setEditingCategoryColor('#000000');
|
|
1297
|
+
setEditingCategoryIcon('');
|
|
1298
|
+
setEditingCategoryParentId(null);
|
|
1299
|
+
setEditingCategoryStatus('active');
|
|
1300
|
+
}
|
|
1301
|
+
}}
|
|
1302
|
+
>
|
|
1303
|
+
<SheetContent className="sm:max-w-lg">
|
|
1304
|
+
<SheetHeader>
|
|
1305
|
+
<SheetTitle>Editar categoria</SheetTitle>
|
|
1306
|
+
<SheetDescription>
|
|
1307
|
+
Atualize rapidamente os dados da categoria vinculada ao curso.
|
|
1308
|
+
</SheetDescription>
|
|
1309
|
+
</SheetHeader>
|
|
1310
|
+
|
|
1311
|
+
<div className="space-y-3 px-4 pb-4">
|
|
1312
|
+
<div className="space-y-1.5">
|
|
1313
|
+
<FormLabel>Nome</FormLabel>
|
|
1314
|
+
<Input
|
|
1315
|
+
value={editingCategoryName}
|
|
1316
|
+
onChange={(event) => {
|
|
1317
|
+
const nextName = event.target.value;
|
|
1318
|
+
setEditingCategoryName(nextName);
|
|
1319
|
+
setEditingCategorySlug(slugify(nextName));
|
|
1320
|
+
}}
|
|
1321
|
+
placeholder="Nome da categoria"
|
|
1322
|
+
/>
|
|
1323
|
+
</div>
|
|
1324
|
+
|
|
1325
|
+
<div className="space-y-1.5">
|
|
1326
|
+
<FormLabel>Slug</FormLabel>
|
|
1327
|
+
<Input
|
|
1328
|
+
value={editingCategorySlug}
|
|
1329
|
+
readOnly
|
|
1330
|
+
placeholder="slug-da-categoria"
|
|
1331
|
+
/>
|
|
1332
|
+
</div>
|
|
1333
|
+
</div>
|
|
1334
|
+
|
|
1335
|
+
<SheetFooter>
|
|
1336
|
+
<Button
|
|
1337
|
+
variant="outline"
|
|
1338
|
+
onClick={() => setCategoryEditSheetOpen(false)}
|
|
1339
|
+
disabled={savingCategoryEdit}
|
|
1340
|
+
>
|
|
1341
|
+
Cancelar
|
|
1342
|
+
</Button>
|
|
1343
|
+
<Button
|
|
1344
|
+
onClick={handleSaveCategoryEdit}
|
|
1345
|
+
disabled={savingCategoryEdit}
|
|
1346
|
+
>
|
|
1347
|
+
{savingCategoryEdit ? (
|
|
1348
|
+
<Loader2 className="size-4 animate-spin" />
|
|
1349
|
+
) : null}
|
|
1350
|
+
Salvar
|
|
1351
|
+
</Button>
|
|
1352
|
+
</SheetFooter>
|
|
1353
|
+
</SheetContent>
|
|
1354
|
+
</Sheet>
|
|
1355
|
+
|
|
1147
1356
|
{/* ── Delete dialog ────────────────────────────────────────────────── */}
|
|
1148
1357
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
1149
1358
|
<DialogContent>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Data — LMS Course Structure
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ TEMPORARY — replace when integrating with the real API.
|
|
5
|
+
*
|
|
6
|
+
* TODO[API]: Remove this file entirely once `useCourseStructure` fetches data
|
|
7
|
+
* from GET /lms/courses/:id/structure. The Zustand store should then
|
|
8
|
+
* be seeded with the server response instead of these constants.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
Course,
|
|
13
|
+
Lesson,
|
|
14
|
+
LessonStatus,
|
|
15
|
+
LessonType,
|
|
16
|
+
Session,
|
|
17
|
+
VideoProvider,
|
|
18
|
+
Visibility,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Mock Data — LMS Course Structure
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export const MOCK_COURSE: Course = {
|
|
26
|
+
id: 'course-1',
|
|
27
|
+
code: 'REACT-ADV',
|
|
28
|
+
name: 'React Avancado',
|
|
29
|
+
title: 'React Avancado',
|
|
30
|
+
description:
|
|
31
|
+
'Domine os conceitos avancados do React: hooks, patterns, performance e gerenciamento de estado moderno.',
|
|
32
|
+
slug: 'react-avancado',
|
|
33
|
+
published: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Session config: title + how many lessons each gets
|
|
37
|
+
const SESSION_CONFIG: { title: string; lessonCount: number }[] = [
|
|
38
|
+
{ title: 'Boas-vindas ao curso', lessonCount: 5 },
|
|
39
|
+
{ title: 'Hooks Avancados useReducer e useContext', lessonCount: 32 },
|
|
40
|
+
{ title: 'Patterns de Composicao', lessonCount: 31 },
|
|
41
|
+
{ title: 'Gerenciamento de Estado com Zustand', lessonCount: 8 },
|
|
42
|
+
{ title: 'Performance e Otimizacao', lessonCount: 30 },
|
|
43
|
+
{ title: 'React Server Components', lessonCount: 10 },
|
|
44
|
+
{ title: 'Data Fetching Moderno', lessonCount: 9 },
|
|
45
|
+
{ title: 'Roteamento Avancado com Next.js', lessonCount: 7 },
|
|
46
|
+
{ title: 'Formularios e Validacao com RHF', lessonCount: 11 },
|
|
47
|
+
{ title: 'Autenticacao e Seguranca', lessonCount: 9 },
|
|
48
|
+
{ title: 'Testing com React Testing Library', lessonCount: 12 },
|
|
49
|
+
{ title: 'Acessibilidade a11y', lessonCount: 6 },
|
|
50
|
+
{ title: 'Internacionalizacao i18n', lessonCount: 7 },
|
|
51
|
+
{ title: 'Animacoes com Framer Motion', lessonCount: 8 },
|
|
52
|
+
{ title: 'Arquitetura de Projetos Escalaveis', lessonCount: 10 },
|
|
53
|
+
{ title: 'Design Systems e Storybook', lessonCount: 8 },
|
|
54
|
+
{ title: 'Deploy CI/CD e DevOps', lessonCount: 7 },
|
|
55
|
+
{ title: 'Monorepos e Micro-frontends', lessonCount: 6 },
|
|
56
|
+
{ title: 'Debugging Avancado', lessonCount: 6 },
|
|
57
|
+
{ title: 'Projeto Final App Completo', lessonCount: 5 },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const MOCK_SESSIONS: Session[] = SESSION_CONFIG.map((s, i) => ({
|
|
61
|
+
id: `s${i + 1}`,
|
|
62
|
+
code: `S${String(i + 1).padStart(2, '0')}`,
|
|
63
|
+
title: s.title,
|
|
64
|
+
duration: s.lessonCount * 12,
|
|
65
|
+
order: i,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
// Lesson title patterns
|
|
69
|
+
const TITLE_PATTERNS = [
|
|
70
|
+
'Introducao e objetivos',
|
|
71
|
+
'Conceitos fundamentais',
|
|
72
|
+
'Configuracao do ambiente',
|
|
73
|
+
'Pratica guiada passo a passo',
|
|
74
|
+
'Exercicio: implementacao',
|
|
75
|
+
'Quiz de revisao',
|
|
76
|
+
'Caso de uso real',
|
|
77
|
+
'Implementacao completa',
|
|
78
|
+
'Debugging e troubleshooting',
|
|
79
|
+
'Otimizacoes e boas praticas',
|
|
80
|
+
'Desafio pratico',
|
|
81
|
+
'Revisao do modulo',
|
|
82
|
+
'Q e A e duvidas frequentes',
|
|
83
|
+
'Proximos passos',
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const LESSON_STATUSES: LessonStatus[] = [
|
|
87
|
+
'preparada',
|
|
88
|
+
'gravada',
|
|
89
|
+
'editada',
|
|
90
|
+
'finalizada',
|
|
91
|
+
'publicada',
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const VISIBILITIES: Visibility[] = [
|
|
95
|
+
'publico',
|
|
96
|
+
'publico',
|
|
97
|
+
'privado',
|
|
98
|
+
'restrito',
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const LESSON_TYPES: LessonType[] = [
|
|
102
|
+
'video',
|
|
103
|
+
'video',
|
|
104
|
+
'video',
|
|
105
|
+
'post',
|
|
106
|
+
'video',
|
|
107
|
+
'video',
|
|
108
|
+
'questao',
|
|
109
|
+
'exercicio',
|
|
110
|
+
];
|
|
111
|
+
const PROVIDERS: VideoProvider[] = [
|
|
112
|
+
'youtube',
|
|
113
|
+
'vimeo',
|
|
114
|
+
'bunny',
|
|
115
|
+
'youtube',
|
|
116
|
+
'youtube',
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
let _lid = 0;
|
|
120
|
+
|
|
121
|
+
export const MOCK_LESSONS: Lesson[] = SESSION_CONFIG.flatMap((s, si) =>
|
|
122
|
+
Array.from({ length: s.lessonCount }, (_, li) => {
|
|
123
|
+
_lid += 1;
|
|
124
|
+
const type = LESSON_TYPES[_lid % LESSON_TYPES.length] as LessonType;
|
|
125
|
+
const isVideo = type === 'video';
|
|
126
|
+
const provider = PROVIDERS[_lid % PROVIDERS.length] as VideoProvider;
|
|
127
|
+
const baseTitle = TITLE_PATTERNS[li % TITLE_PATTERNS.length] as string;
|
|
128
|
+
const title =
|
|
129
|
+
li < TITLE_PATTERNS.length
|
|
130
|
+
? baseTitle
|
|
131
|
+
: baseTitle +
|
|
132
|
+
' parte ' +
|
|
133
|
+
String(Math.floor(li / TITLE_PATTERNS.length) + 1);
|
|
134
|
+
|
|
135
|
+
const hasResource = li % 4 === 0;
|
|
136
|
+
|
|
137
|
+
const status = LESSON_STATUSES[
|
|
138
|
+
_lid % LESSON_STATUSES.length
|
|
139
|
+
] as LessonStatus;
|
|
140
|
+
const visibility = VISIBILITIES[_lid % VISIBILITIES.length] as Visibility;
|
|
141
|
+
|
|
142
|
+
const lesson: Lesson = {
|
|
143
|
+
id: `l${_lid}`,
|
|
144
|
+
code: `A${String(_lid).padStart(3, '0')}`,
|
|
145
|
+
title,
|
|
146
|
+
type,
|
|
147
|
+
status,
|
|
148
|
+
visibility,
|
|
149
|
+
duration: 8 + (_lid % 32),
|
|
150
|
+
publicDescription: `Aprenda ${s.title.toLowerCase()} de forma pratica nesta aula.`,
|
|
151
|
+
privateDescription: li % 7 === 0 ? 'Revisar antes de publicar.' : '',
|
|
152
|
+
sessionId: `s${si + 1}`,
|
|
153
|
+
order: li,
|
|
154
|
+
resources: hasResource
|
|
155
|
+
? [
|
|
156
|
+
{
|
|
157
|
+
id: `r${_lid}`,
|
|
158
|
+
name: `material-${String(_lid).padStart(3, '0')}.pdf`,
|
|
159
|
+
size: `${1 + (_lid % 5)}.${_lid % 9} MB`,
|
|
160
|
+
type: 'application/pdf',
|
|
161
|
+
public: _lid % 2 === 0,
|
|
162
|
+
url: `https://www.w3.org/WAI/WCAG21/Techniques/pdf/PDF1.pdf`,
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
: [],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (isVideo) {
|
|
169
|
+
lesson.videoProvider = provider;
|
|
170
|
+
lesson.videoUrl = `https://example.com/video/${_lid}`;
|
|
171
|
+
lesson.autoDuration = _lid % 3 !== 0;
|
|
172
|
+
if (li === 0) {
|
|
173
|
+
lesson.transcription = `Transcricao completa da primeira aula de "${s.title}". Lorem ipsum dolor sit amet, consectetur adipiscing elit.`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (type === 'questao') {
|
|
177
|
+
lesson.linkedExam = `exam-${_lid}`;
|
|
178
|
+
}
|
|
179
|
+
if (type === 'post') {
|
|
180
|
+
lesson.postContent = `Conteudo detalhado sobre ${s.title.toLowerCase()}...`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return lesson;
|
|
184
|
+
})
|
|
185
|
+
);
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
import { useTranslations } from 'next-intl';
|
|
61
61
|
import { useRouter } from 'next/navigation';
|
|
62
62
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
63
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
63
64
|
import { useForm, useWatch } from 'react-hook-form';
|
|
64
65
|
import { toast } from 'sonner';
|
|
65
66
|
import { CourseAvatar } from '../_components/course-avatar';
|
|
@@ -307,7 +308,11 @@ export default function CursosPage() {
|
|
|
307
308
|
|
|
308
309
|
// Pagination
|
|
309
310
|
const [currentPage, setCurrentPage] = useState(1);
|
|
310
|
-
const [pageSize, setPageSize] =
|
|
311
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
312
|
+
storageKey: 'pagination:global:pageSize',
|
|
313
|
+
defaultValue: 12,
|
|
314
|
+
allowedValues: [6, 12, 24],
|
|
315
|
+
});
|
|
311
316
|
|
|
312
317
|
// Double-click tracking
|
|
313
318
|
const clickTimers = useRef<Map<number, ReturnType<typeof setTimeout>>>(
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
4
|
+
import { formatDate } from '@/lib/format-date';
|
|
5
|
+
import { useApp } from '@hed-hog/next-app-provider';
|
|
6
|
+
import {
|
|
7
|
+
BookOpen,
|
|
8
|
+
CalendarDays,
|
|
9
|
+
CircleDot,
|
|
10
|
+
ShieldCheck,
|
|
11
|
+
UserPlus,
|
|
12
|
+
} from 'lucide-react';
|
|
13
|
+
import type { EnterpriseOverview } from './enterprise-types';
|
|
14
|
+
|
|
15
|
+
const activityIcon = {
|
|
16
|
+
assigned: UserPlus,
|
|
17
|
+
revoked: UserPlus,
|
|
18
|
+
status_changed: UserPlus,
|
|
19
|
+
course: BookOpen,
|
|
20
|
+
class: CalendarDays,
|
|
21
|
+
student: UserPlus,
|
|
22
|
+
admin: ShieldCheck,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function EnterpriseActivityTimeline({
|
|
26
|
+
activities,
|
|
27
|
+
}: {
|
|
28
|
+
activities: EnterpriseOverview['activities'];
|
|
29
|
+
}) {
|
|
30
|
+
const { getSettingValue, currentLocaleCode } = useApp();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Card className="border-border/60">
|
|
34
|
+
<CardHeader className="pb-2">
|
|
35
|
+
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
|
36
|
+
<CircleDot className="h-4 w-4 text-muted-foreground" />
|
|
37
|
+
Atividades recentes
|
|
38
|
+
</CardTitle>
|
|
39
|
+
</CardHeader>
|
|
40
|
+
<CardContent>
|
|
41
|
+
{activities.length === 0 ? (
|
|
42
|
+
<div className="flex h-28 items-center justify-center text-xs text-muted-foreground">
|
|
43
|
+
Nenhuma atividade recente.
|
|
44
|
+
</div>
|
|
45
|
+
) : (
|
|
46
|
+
<div className="space-y-1">
|
|
47
|
+
{activities.map((activity, index) => {
|
|
48
|
+
const Icon =
|
|
49
|
+
activityIcon[activity.type as keyof typeof activityIcon] ??
|
|
50
|
+
CircleDot;
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
key={activity.id}
|
|
54
|
+
className="grid grid-cols-[auto_1fr] gap-3 py-2"
|
|
55
|
+
>
|
|
56
|
+
<div className="flex flex-col items-center">
|
|
57
|
+
<span className="flex h-7 w-7 items-center justify-center rounded-full border bg-background text-muted-foreground">
|
|
58
|
+
<Icon className="h-3.5 w-3.5" />
|
|
59
|
+
</span>
|
|
60
|
+
{index < activities.length - 1 && (
|
|
61
|
+
<span className="mt-1 h-full min-h-4 w-px bg-border" />
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
<div className="min-w-0 pb-2">
|
|
65
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
66
|
+
<p className="text-sm font-medium">{activity.title}</p>
|
|
67
|
+
<span className="text-[11px] text-muted-foreground">
|
|
68
|
+
{formatDate(
|
|
69
|
+
activity.createdAt,
|
|
70
|
+
getSettingValue,
|
|
71
|
+
currentLocaleCode
|
|
72
|
+
)}
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
76
|
+
{activity.description}
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</CardContent>
|
|
85
|
+
</Card>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
Sheet,
|
|
24
24
|
SheetContent,
|
|
25
|
+
SheetDescription,
|
|
25
26
|
SheetFooter,
|
|
26
27
|
SheetHeader,
|
|
27
28
|
SheetTitle,
|
|
@@ -167,6 +168,9 @@ export function EnterpriseAdminCreateSheet({
|
|
|
167
168
|
<SheetContent side="right" className="flex flex-col gap-0 sm:max-w-md">
|
|
168
169
|
<SheetHeader className="border-b px-6 py-4">
|
|
169
170
|
<SheetTitle>New administrator</SheetTitle>
|
|
171
|
+
<SheetDescription>
|
|
172
|
+
Create an administrator account for this enterprise.
|
|
173
|
+
</SheetDescription>
|
|
170
174
|
</SheetHeader>
|
|
171
175
|
|
|
172
176
|
<Form {...form}>
|