@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
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
} from 'lucide-react';
|
|
57
57
|
import { useTranslations } from 'next-intl';
|
|
58
58
|
import { useEffect, useState } from 'react';
|
|
59
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
59
60
|
import { toast } from 'sonner';
|
|
60
61
|
import { InstructorFormSheet } from './_components/instructor-form-sheet';
|
|
61
62
|
import type {
|
|
@@ -102,7 +103,11 @@ export default function InstructorsPage() {
|
|
|
102
103
|
const t = useTranslations('lms.InstructorsPage');
|
|
103
104
|
|
|
104
105
|
const [page, setPage] = useState(1);
|
|
105
|
-
const [pageSize, setPageSize] =
|
|
106
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
107
|
+
storageKey: 'pagination:global:pageSize',
|
|
108
|
+
defaultValue: 12,
|
|
109
|
+
allowedValues: [6, 12, 24, 48],
|
|
110
|
+
});
|
|
106
111
|
const [searchInput, setSearchInput] = useState('');
|
|
107
112
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
108
113
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
TableRow,
|
|
65
65
|
} from '@/components/ui/table';
|
|
66
66
|
import { Textarea } from '@/components/ui/textarea';
|
|
67
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
67
68
|
import { usePersistedViewMode } from '@/hooks/use-persisted-view-mode';
|
|
68
69
|
import {
|
|
69
70
|
DndContext,
|
|
@@ -113,6 +114,7 @@ interface Formacao {
|
|
|
113
114
|
id: number;
|
|
114
115
|
nome: string;
|
|
115
116
|
descricao: string;
|
|
117
|
+
progressionMode: 'sequencial' | 'livre';
|
|
116
118
|
area: string;
|
|
117
119
|
nivel: string;
|
|
118
120
|
prerequisitos: string;
|
|
@@ -134,6 +136,9 @@ type TrainingColorPayload = {
|
|
|
134
136
|
secondaryColor?: string | null;
|
|
135
137
|
primary_color?: string | null;
|
|
136
138
|
secondary_color?: string | null;
|
|
139
|
+
progressionMode?: string | null;
|
|
140
|
+
progressMode?: string | null;
|
|
141
|
+
progress_mode?: string | null;
|
|
137
142
|
};
|
|
138
143
|
|
|
139
144
|
interface CursoOption {
|
|
@@ -339,6 +344,14 @@ function normalizeLevelValue(value: string) {
|
|
|
339
344
|
return 'Iniciante';
|
|
340
345
|
}
|
|
341
346
|
|
|
347
|
+
function normalizeProgressModeValue(value?: string) {
|
|
348
|
+
const normalized = normalizeText(value ?? '');
|
|
349
|
+
|
|
350
|
+
if (['livre', 'free'].includes(normalized)) return 'livre';
|
|
351
|
+
|
|
352
|
+
return 'sequencial';
|
|
353
|
+
}
|
|
354
|
+
|
|
342
355
|
function normalizeStatusValue(value: string) {
|
|
343
356
|
const normalized = normalizeText(value);
|
|
344
357
|
|
|
@@ -390,6 +403,12 @@ function normalizeTrainingColorPayload<T extends TrainingColorPayload>(
|
|
|
390
403
|
|
|
391
404
|
return {
|
|
392
405
|
...payload,
|
|
406
|
+
progressionMode: normalizeProgressModeValue(
|
|
407
|
+
payload.progressionMode ??
|
|
408
|
+
payload.progressMode ??
|
|
409
|
+
payload.progress_mode ??
|
|
410
|
+
undefined
|
|
411
|
+
),
|
|
393
412
|
primaryColor: primaryColor ?? undefined,
|
|
394
413
|
secondaryColor: secondaryColor ?? undefined,
|
|
395
414
|
};
|
|
@@ -421,7 +440,8 @@ function buildCourseCodeFromTitle(title: string) {
|
|
|
421
440
|
|
|
422
441
|
const formacaoSchema = z.object({
|
|
423
442
|
nome: z.string().min(3, 'Minimo 3 caracteres'),
|
|
424
|
-
descricao: z.string().
|
|
443
|
+
descricao: z.string().optional(),
|
|
444
|
+
progressionMode: z.enum(['sequencial', 'livre']),
|
|
425
445
|
area: z.enum(['Tecnologia', 'Design', 'Gestao', 'Marketing', 'Financas']),
|
|
426
446
|
nivel: z.enum(['Iniciante', 'Intermediario', 'Avancado']),
|
|
427
447
|
prerequisitos: z.string().optional(),
|
|
@@ -565,7 +585,11 @@ export default function TrainingPage() {
|
|
|
565
585
|
|
|
566
586
|
// Pagination
|
|
567
587
|
const [currentPage, setCurrentPage] = useState(1);
|
|
568
|
-
const [pageSize, setPageSize] =
|
|
588
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
589
|
+
storageKey: 'pagination:global:pageSize',
|
|
590
|
+
defaultValue: 12,
|
|
591
|
+
allowedValues: PAGE_SIZES,
|
|
592
|
+
});
|
|
569
593
|
|
|
570
594
|
// Double-click tracking
|
|
571
595
|
const clickTimers = useRef<Map<number, ReturnType<typeof setTimeout>>>(
|
|
@@ -723,6 +747,7 @@ export default function TrainingPage() {
|
|
|
723
747
|
defaultValues: {
|
|
724
748
|
nome: '',
|
|
725
749
|
descricao: '',
|
|
750
|
+
progressionMode: 'sequencial' as const,
|
|
726
751
|
area: 'Tecnologia',
|
|
727
752
|
nivel: 'Iniciante',
|
|
728
753
|
prerequisitos: '',
|
|
@@ -962,6 +987,7 @@ export default function TrainingPage() {
|
|
|
962
987
|
form.reset({
|
|
963
988
|
nome: '',
|
|
964
989
|
descricao: '',
|
|
990
|
+
progressionMode: 'sequencial',
|
|
965
991
|
area: 'Tecnologia',
|
|
966
992
|
nivel: 'Iniciante',
|
|
967
993
|
prerequisitos: '',
|
|
@@ -1017,6 +1043,9 @@ export default function TrainingPage() {
|
|
|
1017
1043
|
form.reset({
|
|
1018
1044
|
nome: fullFormacao.nome,
|
|
1019
1045
|
descricao: fullFormacao.descricao,
|
|
1046
|
+
progressionMode: normalizeProgressModeValue(
|
|
1047
|
+
fullFormacao.progressionMode
|
|
1048
|
+
),
|
|
1020
1049
|
area: normalizeAreaValue(fullFormacao.area),
|
|
1021
1050
|
nivel: normalizeLevelValue(fullFormacao.nivel),
|
|
1022
1051
|
prerequisitos: fullFormacao.prerequisitos,
|
|
@@ -1166,6 +1195,9 @@ export default function TrainingPage() {
|
|
|
1166
1195
|
}
|
|
1167
1196
|
|
|
1168
1197
|
async function onSubmit(data: FormacaoForm) {
|
|
1198
|
+
const toApiProgressMode = (value: FormacaoForm['progressionMode']) =>
|
|
1199
|
+
value === 'livre' ? 'free' : 'sequential';
|
|
1200
|
+
|
|
1169
1201
|
try {
|
|
1170
1202
|
const orderedItems = normalizeTrailOrder(
|
|
1171
1203
|
[...learningPathItems].sort((a, b) => a.order - b.order)
|
|
@@ -1191,6 +1223,7 @@ export default function TrainingPage() {
|
|
|
1191
1223
|
shortDescription?: string;
|
|
1192
1224
|
level?: 'beginner' | 'intermediate' | 'advanced';
|
|
1193
1225
|
status?: 'draft' | 'active' | 'archived';
|
|
1226
|
+
progressMode?: 'sequential' | 'free';
|
|
1194
1227
|
primaryColor?: string;
|
|
1195
1228
|
secondaryColor?: string;
|
|
1196
1229
|
items?: Array<{
|
|
@@ -1210,6 +1243,7 @@ export default function TrainingPage() {
|
|
|
1210
1243
|
if (dirty.status) {
|
|
1211
1244
|
payload.status = ptStatusToApi(data.status as Formacao['status']);
|
|
1212
1245
|
}
|
|
1246
|
+
payload.progressMode = toApiProgressMode(data.progressionMode);
|
|
1213
1247
|
if (dirty.primaryColor) payload.primaryColor = data.primaryColor;
|
|
1214
1248
|
if (dirty.secondaryColor) payload.secondaryColor = data.secondaryColor;
|
|
1215
1249
|
if (itemsChanged) {
|
|
@@ -1239,6 +1273,7 @@ export default function TrainingPage() {
|
|
|
1239
1273
|
shortDescription: data.prerequisitos?.trim() || undefined,
|
|
1240
1274
|
level: ptLevelToApi(data.nivel),
|
|
1241
1275
|
status: ptStatusToApi(data.status as Formacao['status']),
|
|
1276
|
+
progressMode: toApiProgressMode(data.progressionMode),
|
|
1242
1277
|
primaryColor: data.primaryColor,
|
|
1243
1278
|
secondaryColor: data.secondaryColor,
|
|
1244
1279
|
items: orderedItems.map((item, index) => ({
|
|
@@ -1893,18 +1928,51 @@ export default function TrainingPage() {
|
|
|
1893
1928
|
<FieldError>{form.formState.errors.nome?.message}</FieldError>
|
|
1894
1929
|
</Field>
|
|
1895
1930
|
<Field>
|
|
1896
|
-
<FieldLabel htmlFor="descricao">
|
|
1897
|
-
{t('form.fields.description.label')}
|
|
1898
|
-
<span className="text-destructive">*</span>
|
|
1931
|
+
<FieldLabel htmlFor="descricao">
|
|
1932
|
+
{t('form.fields.description.label')}
|
|
1899
1933
|
</FieldLabel>
|
|
1900
1934
|
<Textarea
|
|
1901
|
-
id="descricao"
|
|
1935
|
+
id="descricao"
|
|
1902
1936
|
rows={3}
|
|
1903
1937
|
placeholder={t('form.fields.description.placeholder')}
|
|
1904
|
-
{...form.register('descricao')}
|
|
1938
|
+
{...form.register('descricao')}
|
|
1939
|
+
/>
|
|
1940
|
+
</Field>
|
|
1941
|
+
<Field>
|
|
1942
|
+
<FieldLabel>
|
|
1943
|
+
{t('form.fields.progressionMode.label')}{' '}
|
|
1944
|
+
<span className="text-destructive">*</span>
|
|
1945
|
+
</FieldLabel>
|
|
1946
|
+
<Controller
|
|
1947
|
+
name="progressionMode"
|
|
1948
|
+
control={form.control}
|
|
1949
|
+
render={({ field }) => (
|
|
1950
|
+
<Select onValueChange={field.onChange} value={field.value}>
|
|
1951
|
+
<SelectTrigger>
|
|
1952
|
+
<SelectValue
|
|
1953
|
+
placeholder={t(
|
|
1954
|
+
'form.fields.progressionMode.placeholder'
|
|
1955
|
+
)}
|
|
1956
|
+
/>
|
|
1957
|
+
</SelectTrigger>
|
|
1958
|
+
<SelectContent>
|
|
1959
|
+
<SelectItem value="sequencial">
|
|
1960
|
+
{t('form.fields.progressionMode.options.sequential')}
|
|
1961
|
+
</SelectItem>
|
|
1962
|
+
<SelectItem value="livre">
|
|
1963
|
+
{t('form.fields.progressionMode.options.free')}
|
|
1964
|
+
</SelectItem>
|
|
1965
|
+
</SelectContent>
|
|
1966
|
+
</Select>
|
|
1967
|
+
)}
|
|
1905
1968
|
/>
|
|
1969
|
+
<p className="text-xs text-muted-foreground">
|
|
1970
|
+
{watchedFormValues.progressionMode === 'livre'
|
|
1971
|
+
? t('form.fields.progressionMode.help.free')
|
|
1972
|
+
: t('form.fields.progressionMode.help.sequential')}
|
|
1973
|
+
</p>
|
|
1906
1974
|
<FieldError>
|
|
1907
|
-
{form.formState.errors.
|
|
1975
|
+
{form.formState.errors.progressionMode?.message}
|
|
1908
1976
|
</FieldError>
|
|
1909
1977
|
</Field>
|
|
1910
1978
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
import { useTranslations } from 'next-intl';
|
|
62
62
|
import { useRouter } from 'next/navigation';
|
|
63
63
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
|
|
64
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
64
65
|
import type { DateRange } from 'react-day-picker';
|
|
65
66
|
|
|
66
67
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
@@ -360,7 +361,11 @@ export default function EvaluationsReportPage() {
|
|
|
360
361
|
|
|
361
362
|
// Pagination
|
|
362
363
|
const [currentPage, setCurrentPage] = useState(1);
|
|
363
|
-
const [pageSize, setPageSize] =
|
|
364
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
365
|
+
storageKey: 'pagination:global:pageSize',
|
|
366
|
+
defaultValue: 12,
|
|
367
|
+
allowedValues: PAGE_SIZES,
|
|
368
|
+
});
|
|
364
369
|
|
|
365
370
|
// Reset page when filters change
|
|
366
371
|
useEffect(() => {
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
TableRow,
|
|
64
64
|
} from '@/components/ui/table';
|
|
65
65
|
import { Textarea } from '@/components/ui/textarea';
|
|
66
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
66
67
|
import { usePersistedViewMode } from '@/hooks/use-persisted-view-mode';
|
|
67
68
|
import {
|
|
68
69
|
DndContext,
|
|
@@ -112,6 +113,7 @@ interface Formacao {
|
|
|
112
113
|
id: number;
|
|
113
114
|
nome: string;
|
|
114
115
|
descricao: string;
|
|
116
|
+
progressionMode: 'sequencial' | 'livre';
|
|
115
117
|
area: string;
|
|
116
118
|
nivel: string;
|
|
117
119
|
prerequisitos: string;
|
|
@@ -343,6 +345,14 @@ function normalizeStatusValue(value: string) {
|
|
|
343
345
|
return 'rascunho';
|
|
344
346
|
}
|
|
345
347
|
|
|
348
|
+
function normalizeProgressModeValue(value?: string) {
|
|
349
|
+
const normalized = normalizeText(value ?? '');
|
|
350
|
+
|
|
351
|
+
if (['livre', 'free'].includes(normalized)) return 'livre';
|
|
352
|
+
|
|
353
|
+
return 'sequencial';
|
|
354
|
+
}
|
|
355
|
+
|
|
346
356
|
function slugifyText(value: string) {
|
|
347
357
|
return normalizeText(value)
|
|
348
358
|
.replace(/[^a-z0-9\s-]/g, '')
|
|
@@ -416,7 +426,8 @@ function buildCourseCodeFromTitle(title: string) {
|
|
|
416
426
|
|
|
417
427
|
const formacaoSchema = z.object({
|
|
418
428
|
nome: z.string().min(3, 'Minimo 3 caracteres'),
|
|
419
|
-
descricao: z.string().
|
|
429
|
+
descricao: z.string().optional(),
|
|
430
|
+
progressionMode: z.enum(['sequencial', 'livre']),
|
|
420
431
|
area: z.enum(['Tecnologia', 'Design', 'Gestao', 'Marketing', 'Financas']),
|
|
421
432
|
nivel: z.enum(['Iniciante', 'Intermediario', 'Avancado']),
|
|
422
433
|
prerequisitos: z.string().optional(),
|
|
@@ -560,7 +571,11 @@ export default function TrainingPage() {
|
|
|
560
571
|
|
|
561
572
|
// Pagination
|
|
562
573
|
const [currentPage, setCurrentPage] = useState(1);
|
|
563
|
-
const [pageSize, setPageSize] =
|
|
574
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
575
|
+
storageKey: 'pagination:global:pageSize',
|
|
576
|
+
defaultValue: 12,
|
|
577
|
+
allowedValues: PAGE_SIZES,
|
|
578
|
+
});
|
|
564
579
|
|
|
565
580
|
// Double-click tracking
|
|
566
581
|
const clickTimers = useRef<Map<number, ReturnType<typeof setTimeout>>>(
|
|
@@ -716,6 +731,7 @@ export default function TrainingPage() {
|
|
|
716
731
|
defaultValues: {
|
|
717
732
|
nome: '',
|
|
718
733
|
descricao: '',
|
|
734
|
+
progressionMode: 'sequencial',
|
|
719
735
|
area: 'Tecnologia',
|
|
720
736
|
nivel: 'Iniciante',
|
|
721
737
|
prerequisitos: '',
|
|
@@ -867,6 +883,8 @@ export default function TrainingPage() {
|
|
|
867
883
|
...formacao,
|
|
868
884
|
nome: watchedFormValues.nome ?? formacao.nome,
|
|
869
885
|
descricao: watchedFormValues.descricao ?? formacao.descricao,
|
|
886
|
+
progressionMode:
|
|
887
|
+
watchedFormValues.progressionMode ?? formacao.progressionMode,
|
|
870
888
|
nivel: watchedFormValues.nivel ?? formacao.nivel,
|
|
871
889
|
status: watchedFormValues.status ?? formacao.status,
|
|
872
890
|
prerequisitos:
|
|
@@ -955,6 +973,7 @@ export default function TrainingPage() {
|
|
|
955
973
|
form.reset({
|
|
956
974
|
nome: '',
|
|
957
975
|
descricao: '',
|
|
976
|
+
progressionMode: 'sequencial',
|
|
958
977
|
area: 'Tecnologia',
|
|
959
978
|
nivel: 'Iniciante',
|
|
960
979
|
prerequisitos: '',
|
|
@@ -1010,6 +1029,9 @@ export default function TrainingPage() {
|
|
|
1010
1029
|
form.reset({
|
|
1011
1030
|
nome: fullFormacao.nome,
|
|
1012
1031
|
descricao: fullFormacao.descricao,
|
|
1032
|
+
progressionMode: normalizeProgressModeValue(
|
|
1033
|
+
fullFormacao.progressionMode
|
|
1034
|
+
),
|
|
1013
1035
|
area: normalizeAreaValue(fullFormacao.area),
|
|
1014
1036
|
nivel: normalizeLevelValue(fullFormacao.nivel),
|
|
1015
1037
|
prerequisitos: fullFormacao.prerequisitos,
|
|
@@ -1159,6 +1181,9 @@ export default function TrainingPage() {
|
|
|
1159
1181
|
}
|
|
1160
1182
|
|
|
1161
1183
|
async function onSubmit(data: FormacaoForm) {
|
|
1184
|
+
const toApiProgressMode = (value: FormacaoForm['progressionMode']) =>
|
|
1185
|
+
value === 'livre' ? 'free' : 'sequential';
|
|
1186
|
+
|
|
1162
1187
|
try {
|
|
1163
1188
|
const orderedItems = normalizeTrailOrder(
|
|
1164
1189
|
[...learningPathItems].sort((a, b) => a.order - b.order)
|
|
@@ -1184,6 +1209,7 @@ export default function TrainingPage() {
|
|
|
1184
1209
|
shortDescription?: string;
|
|
1185
1210
|
level?: 'beginner' | 'intermediate' | 'advanced';
|
|
1186
1211
|
status?: 'draft' | 'active' | 'archived';
|
|
1212
|
+
progressMode?: 'sequential' | 'free';
|
|
1187
1213
|
primaryColor?: string;
|
|
1188
1214
|
secondaryColor?: string;
|
|
1189
1215
|
items?: Array<{
|
|
@@ -1195,7 +1221,9 @@ export default function TrainingPage() {
|
|
|
1195
1221
|
} = {};
|
|
1196
1222
|
|
|
1197
1223
|
if (dirty.nome) payload.title = data.nome;
|
|
1198
|
-
if (dirty.descricao)
|
|
1224
|
+
if (dirty.descricao) {
|
|
1225
|
+
payload.description = data.descricao?.trim() || undefined;
|
|
1226
|
+
}
|
|
1199
1227
|
if (dirty.prerequisitos) {
|
|
1200
1228
|
payload.shortDescription = data.prerequisitos?.trim() || undefined;
|
|
1201
1229
|
}
|
|
@@ -1203,6 +1231,9 @@ export default function TrainingPage() {
|
|
|
1203
1231
|
if (dirty.status) {
|
|
1204
1232
|
payload.status = ptStatusToApi(data.status as Formacao['status']);
|
|
1205
1233
|
}
|
|
1234
|
+
if (dirty.progressionMode) {
|
|
1235
|
+
payload.progressMode = toApiProgressMode(data.progressionMode);
|
|
1236
|
+
}
|
|
1206
1237
|
if (dirty.primaryColor) payload.primaryColor = data.primaryColor;
|
|
1207
1238
|
if (dirty.secondaryColor) payload.secondaryColor = data.secondaryColor;
|
|
1208
1239
|
if (itemsChanged) {
|
|
@@ -1228,10 +1259,11 @@ export default function TrainingPage() {
|
|
|
1228
1259
|
} else {
|
|
1229
1260
|
const payload = {
|
|
1230
1261
|
title: data.nome,
|
|
1231
|
-
description: data.descricao,
|
|
1262
|
+
description: data.descricao?.trim() || undefined,
|
|
1232
1263
|
shortDescription: data.prerequisitos?.trim() || undefined,
|
|
1233
1264
|
level: ptLevelToApi(data.nivel),
|
|
1234
1265
|
status: ptStatusToApi(data.status as Formacao['status']),
|
|
1266
|
+
progressMode: toApiProgressMode(data.progressionMode),
|
|
1235
1267
|
primaryColor: data.primaryColor,
|
|
1236
1268
|
secondaryColor: data.secondaryColor,
|
|
1237
1269
|
items: orderedItems.map((item, index) => ({
|
|
@@ -1886,20 +1918,57 @@ export default function TrainingPage() {
|
|
|
1886
1918
|
<FieldError>{form.formState.errors.nome?.message}</FieldError>
|
|
1887
1919
|
</Field>
|
|
1888
1920
|
<Field>
|
|
1889
|
-
<FieldLabel htmlFor="descricao">
|
|
1890
|
-
{t('form.fields.description.label')}
|
|
1891
|
-
<span className="text-destructive">*</span>
|
|
1921
|
+
<FieldLabel htmlFor="descricao">
|
|
1922
|
+
{t('form.fields.description.label')}
|
|
1892
1923
|
</FieldLabel>
|
|
1893
1924
|
<Textarea
|
|
1894
|
-
id="descricao"
|
|
1925
|
+
id="descricao"
|
|
1895
1926
|
rows={3}
|
|
1927
|
+
required={false}
|
|
1896
1928
|
placeholder={t('form.fields.description.placeholder')}
|
|
1897
|
-
{...form.register('descricao')}
|
|
1929
|
+
{...form.register('descricao')}
|
|
1898
1930
|
/>
|
|
1899
1931
|
<FieldError>
|
|
1900
1932
|
{form.formState.errors.descricao?.message}
|
|
1901
1933
|
</FieldError>
|
|
1902
1934
|
</Field>
|
|
1935
|
+
<Field>
|
|
1936
|
+
<FieldLabel>
|
|
1937
|
+
{t('form.fields.progressionMode.label')}{' '}
|
|
1938
|
+
<span className="text-destructive">*</span>
|
|
1939
|
+
</FieldLabel>
|
|
1940
|
+
<Controller
|
|
1941
|
+
name="progressionMode"
|
|
1942
|
+
control={form.control}
|
|
1943
|
+
render={({ field }) => (
|
|
1944
|
+
<Select onValueChange={field.onChange} value={field.value}>
|
|
1945
|
+
<SelectTrigger>
|
|
1946
|
+
<SelectValue
|
|
1947
|
+
placeholder={t(
|
|
1948
|
+
'form.fields.progressionMode.placeholder'
|
|
1949
|
+
)}
|
|
1950
|
+
/>
|
|
1951
|
+
</SelectTrigger>
|
|
1952
|
+
<SelectContent>
|
|
1953
|
+
<SelectItem value="sequencial">
|
|
1954
|
+
{t('form.fields.progressionMode.options.sequential')}
|
|
1955
|
+
</SelectItem>
|
|
1956
|
+
<SelectItem value="livre">
|
|
1957
|
+
{t('form.fields.progressionMode.options.free')}
|
|
1958
|
+
</SelectItem>
|
|
1959
|
+
</SelectContent>
|
|
1960
|
+
</Select>
|
|
1961
|
+
)}
|
|
1962
|
+
/>
|
|
1963
|
+
<p className="text-xs text-muted-foreground">
|
|
1964
|
+
{watchedFormValues.progressionMode === 'livre'
|
|
1965
|
+
? t('form.fields.progressionMode.help.free')
|
|
1966
|
+
: t('form.fields.progressionMode.help.sequential')}
|
|
1967
|
+
</p>
|
|
1968
|
+
<FieldError>
|
|
1969
|
+
{form.formState.errors.progressionMode?.message}
|
|
1970
|
+
</FieldError>
|
|
1971
|
+
</Field>
|
|
1903
1972
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
1904
1973
|
<Field>
|
|
1905
1974
|
<FieldLabel>
|
|
@@ -2907,6 +2907,11 @@
|
|
|
2907
2907
|
"subFree": "{count} spots available",
|
|
2908
2908
|
"subFull": "Full"
|
|
2909
2909
|
},
|
|
2910
|
+
"nextClass": "Next Class",
|
|
2911
|
+
"noClass": "No class",
|
|
2912
|
+
"createNext": "Create next class",
|
|
2913
|
+
"calendar": "Calendar",
|
|
2914
|
+
"completed": "{count} completed",
|
|
2910
2915
|
"avgAttendance": {
|
|
2911
2916
|
"label": "Average Attendance",
|
|
2912
2917
|
"sub": "overall attendance"
|
|
@@ -3538,10 +3543,92 @@
|
|
|
3538
3543
|
"studentRemoveError": "Failed to remove student."
|
|
3539
3544
|
},
|
|
3540
3545
|
"form": {
|
|
3546
|
+
"title": {
|
|
3547
|
+
"create": "Create New Course",
|
|
3548
|
+
"edit": "Edit Course"
|
|
3549
|
+
},
|
|
3550
|
+
"description": {
|
|
3551
|
+
"create": "Fill in the details to create a new course. After creating, you will be redirected to the course page.",
|
|
3552
|
+
"edit": "Update the course information below."
|
|
3553
|
+
},
|
|
3554
|
+
"fields": {
|
|
3555
|
+
"internalName": {
|
|
3556
|
+
"label": "Internal Name",
|
|
3557
|
+
"placeholder": "Ex: Advanced React",
|
|
3558
|
+
"description": "Internal course name for administrative use"
|
|
3559
|
+
},
|
|
3560
|
+
"slug": {
|
|
3561
|
+
"label": "Slug",
|
|
3562
|
+
"placeholder": "Ex: advanced-react",
|
|
3563
|
+
"description": "Unique URL identifier (lowercase letters, numbers, and hyphens)"
|
|
3564
|
+
},
|
|
3565
|
+
"commercialTitle": {
|
|
3566
|
+
"label": "Commercial Title",
|
|
3567
|
+
"placeholder": "Ex: Advanced React"
|
|
3568
|
+
},
|
|
3569
|
+
"description": {
|
|
3570
|
+
"label": "Description",
|
|
3571
|
+
"placeholder": "Describe the course content and objectives..."
|
|
3572
|
+
},
|
|
3573
|
+
"level": {
|
|
3574
|
+
"label": "Level",
|
|
3575
|
+
"placeholder": "Select"
|
|
3576
|
+
},
|
|
3577
|
+
"status": {
|
|
3578
|
+
"label": "Status",
|
|
3579
|
+
"placeholder": "Select"
|
|
3580
|
+
},
|
|
3581
|
+
"categories": {
|
|
3582
|
+
"label": "Categories",
|
|
3583
|
+
"description": "Select one or more categories",
|
|
3584
|
+
"createAction": "Create category",
|
|
3585
|
+
"selectPlaceholder": "Select a category",
|
|
3586
|
+
"removeAction": "Remove category",
|
|
3587
|
+
"searchPlaceholder": "Type to search category...",
|
|
3588
|
+
"noResults": "No categories found",
|
|
3589
|
+
"selectedCount": "selected category(ies)"
|
|
3590
|
+
},
|
|
3591
|
+
"primaryColor": {
|
|
3592
|
+
"label": "Primary Color",
|
|
3593
|
+
"placeholder": "#1D4ED8"
|
|
3594
|
+
},
|
|
3595
|
+
"secondaryColor": {
|
|
3596
|
+
"label": "Secondary Color",
|
|
3597
|
+
"placeholder": "#111827"
|
|
3598
|
+
},
|
|
3599
|
+
"logo": {
|
|
3600
|
+
"label": "Course Logo",
|
|
3601
|
+
"alt": "Course logo",
|
|
3602
|
+
"upload": "Upload logo",
|
|
3603
|
+
"replace": "Replace logo",
|
|
3604
|
+
"remove": "Remove",
|
|
3605
|
+
"description": "Optional image to identify the course"
|
|
3606
|
+
}
|
|
3607
|
+
},
|
|
3608
|
+
"validation": {
|
|
3609
|
+
"internalNameMinLength": "Internal name must have at least 3 characters",
|
|
3610
|
+
"slugPattern": "Slug must contain only lowercase letters, numbers and hyphens"
|
|
3611
|
+
},
|
|
3612
|
+
"actions": {
|
|
3613
|
+
"cancel": "Cancel",
|
|
3614
|
+
"save": "Save Changes",
|
|
3615
|
+
"create": "Create Course"
|
|
3616
|
+
},
|
|
3541
3617
|
"toasts": {
|
|
3542
3618
|
"courseCreated": "Course created successfully! Redirecting...",
|
|
3543
3619
|
"courseCreateError": "Failed to create the course."
|
|
3544
3620
|
}
|
|
3621
|
+
},
|
|
3622
|
+
"levels": {
|
|
3623
|
+
"beginner": "Beginner",
|
|
3624
|
+
"intermediate": "Intermediate",
|
|
3625
|
+
"advanced": "Advanced"
|
|
3626
|
+
},
|
|
3627
|
+
"status": {
|
|
3628
|
+
"active": "Active",
|
|
3629
|
+
"inactive": "Inactive",
|
|
3630
|
+
"draft": "Draft",
|
|
3631
|
+
"archived": "Archived"
|
|
3545
3632
|
}
|
|
3546
3633
|
},
|
|
3547
3634
|
"InstructorsPage": {
|
|
@@ -3652,9 +3739,17 @@
|
|
|
3652
3739
|
"classes": {
|
|
3653
3740
|
"emptyTitle": "No classes found",
|
|
3654
3741
|
"emptyDescription": "This instructor is not linked to any classes yet.",
|
|
3742
|
+
"searchPlaceholder": "Search class by name, code or course...",
|
|
3743
|
+
"statusFilterPlaceholder": "Filter by status",
|
|
3744
|
+
"allStatuses": "All statuses",
|
|
3655
3745
|
"start": "Start:",
|
|
3656
3746
|
"end": "End:",
|
|
3657
|
-
"students": "{count} student(s)"
|
|
3747
|
+
"students": "{count} student(s)",
|
|
3748
|
+
"studentsOfCapacity": "{count} / {total} student(s)",
|
|
3749
|
+
"actions": {
|
|
3750
|
+
"editAriaLabel": "Edit class",
|
|
3751
|
+
"openAriaLabel": "Open class page"
|
|
3752
|
+
}
|
|
3658
3753
|
},
|
|
3659
3754
|
"evaluations": {
|
|
3660
3755
|
"comingSoonTitle": "Evaluations — feature coming soon",
|
|
@@ -3704,23 +3799,17 @@
|
|
|
3704
3799
|
"current": "Instructor Skills"
|
|
3705
3800
|
},
|
|
3706
3801
|
"filters": {
|
|
3707
|
-
"searchPlaceholder": "Search by slug
|
|
3802
|
+
"searchPlaceholder": "Search by slug..."
|
|
3708
3803
|
},
|
|
3709
3804
|
"table": {
|
|
3710
3805
|
"slug": "Slug",
|
|
3711
|
-
"namePt": "Name (PT)",
|
|
3712
|
-
"nameEn": "Name (EN)",
|
|
3713
3806
|
"status": "Status"
|
|
3714
3807
|
},
|
|
3715
3808
|
"form": {
|
|
3716
3809
|
"slug": "Slug",
|
|
3717
3810
|
"slugPlaceholder": "e.g. javascript, react-js",
|
|
3718
|
-
"namePt": "Name (PT)",
|
|
3719
|
-
"namePtPlaceholder": "Portuguese name",
|
|
3720
|
-
"nameEn": "Name (EN)",
|
|
3721
|
-
"nameEnPlaceholder": "Name in English (optional)",
|
|
3722
3811
|
"status": "Status",
|
|
3723
|
-
"statusPlaceholder": "Select status",
|
|
3812
|
+
"statusPlaceholder": "Select a status",
|
|
3724
3813
|
"validation": {
|
|
3725
3814
|
"required": "Required",
|
|
3726
3815
|
"max100": "Maximum 100 characters",
|
|
@@ -3933,7 +4022,8 @@
|
|
|
3933
4022
|
},
|
|
3934
4023
|
"actions": {
|
|
3935
4024
|
"create": "Create new class",
|
|
3936
|
-
"add": "Add"
|
|
4025
|
+
"add": "Add",
|
|
4026
|
+
"edit": "Edit"
|
|
3937
4027
|
},
|
|
3938
4028
|
"empty": {
|
|
3939
4029
|
"title": "No classes found.",
|
|
@@ -3941,6 +4031,7 @@
|
|
|
3941
4031
|
},
|
|
3942
4032
|
"table": {
|
|
3943
4033
|
"class": "Class",
|
|
4034
|
+
"instructor": "Instructor",
|
|
3944
4035
|
"status": "Status",
|
|
3945
4036
|
"period": "Period",
|
|
3946
4037
|
"capacity": "Capacity"
|