@hed-hog/lms 0.0.338 → 0.0.349

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/class-group/class-group.controller.d.ts +8 -8
  2. package/dist/class-group/class-group.service.d.ts +8 -8
  3. package/dist/course/course.controller.d.ts +6 -1
  4. package/dist/course/course.controller.d.ts.map +1 -1
  5. package/dist/course/course.controller.js +19 -2
  6. package/dist/course/course.controller.js.map +1 -1
  7. package/dist/course/course.service.d.ts +6 -0
  8. package/dist/course/course.service.d.ts.map +1 -1
  9. package/dist/course/course.service.js +51 -8
  10. package/dist/course/course.service.js.map +1 -1
  11. package/dist/course/dto/create-course.dto.d.ts +1 -0
  12. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  13. package/dist/course/dto/create-course.dto.js +5 -0
  14. package/dist/course/dto/create-course.dto.js.map +1 -1
  15. package/dist/enterprise/enterprise.controller.d.ts +12 -12
  16. package/dist/enterprise/enterprise.service.d.ts +12 -12
  17. package/dist/enterprise/training/training-admin.controller.d.ts +5 -5
  18. package/dist/enterprise/training/training-admin.service.d.ts +5 -5
  19. package/dist/evaluation/evaluation.controller.d.ts +2 -2
  20. package/dist/evaluation/evaluation.service.d.ts +2 -2
  21. package/dist/instructor/instructor.controller.d.ts +1 -0
  22. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  23. package/dist/instructor/instructor.service.d.ts +2 -0
  24. package/dist/instructor/instructor.service.d.ts.map +1 -1
  25. package/dist/instructor/instructor.service.js +9 -7
  26. package/dist/instructor/instructor.service.js.map +1 -1
  27. package/dist/training/dto/create-training.dto.d.ts +1 -0
  28. package/dist/training/dto/create-training.dto.d.ts.map +1 -1
  29. package/dist/training/dto/create-training.dto.js +5 -0
  30. package/dist/training/dto/create-training.dto.js.map +1 -1
  31. package/dist/training/training.controller.d.ts +4 -0
  32. package/dist/training/training.controller.d.ts.map +1 -1
  33. package/dist/training/training.service.d.ts +8 -0
  34. package/dist/training/training.service.d.ts.map +1 -1
  35. package/dist/training/training.service.js +71 -6
  36. package/dist/training/training.service.js.map +1 -1
  37. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +38 -9
  38. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +33 -6
  39. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +1 -3
  40. package/hedhog/frontend/app/classes/page.tsx.ejs +28 -6
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1 -1
  42. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +25 -18
  43. package/hedhog/frontend/app/paths/page.tsx.ejs +68 -5
  44. package/hedhog/frontend/app/training/page.tsx.ejs +70 -6
  45. package/hedhog/frontend/messages/pt.json +14 -1
  46. package/hedhog/table/learning_path.yaml +4 -0
  47. package/package.json +8 -8
  48. package/src/course/course.controller.ts +18 -0
  49. package/src/course/course.service.ts +73 -2
  50. package/src/course/dto/create-course.dto.ts +4 -0
  51. package/src/instructor/instructor.service.ts +2 -0
  52. package/src/training/dto/create-training.dto.ts +4 -0
  53. package/src/training/training.service.ts +104 -5
@@ -589,6 +589,7 @@ export default function QuestoesPage() {
589
589
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
590
590
  const [questaoToDelete, setQuestaoToDelete] = useState<Questao | null>(null);
591
591
  const [hasChanges, setHasChanges] = useState(false);
592
+ const [saving, setSaving] = useState(false);
592
593
  const [buscaInput, setBuscaInput] = useState('');
593
594
  const [debouncedBuscaInput, setDebouncedBuscaInput] = useState('');
594
595
  const [filtroTipoInput, setFiltroTipoInput] = useState<
@@ -993,24 +994,29 @@ export default function QuestoesPage() {
993
994
  }),
994
995
  };
995
996
 
996
- if (editingQuestao) {
997
- await request({
998
- url: `/lms/exams/${examId}/questions/${editingQuestao.id}`,
999
- method: 'PATCH',
1000
- data: payload,
1001
- });
1002
- toast.success(t('toasts.questionUpdated'));
1003
- } else {
1004
- await request({
1005
- url: `/lms/exams/${examId}/questions`,
1006
- method: 'POST',
1007
- data: payload,
1008
- });
1009
- toast.success(t('toasts.questionCreated'));
1010
- }
997
+ setSaving(true);
998
+ try {
999
+ if (editingQuestao) {
1000
+ await request({
1001
+ url: `/lms/exams/${examId}/questions/${editingQuestao.id}`,
1002
+ method: 'PATCH',
1003
+ data: payload,
1004
+ });
1005
+ toast.success(t('toasts.questionUpdated'));
1006
+ } else {
1007
+ await request({
1008
+ url: `/lms/exams/${examId}/questions`,
1009
+ method: 'POST',
1010
+ data: payload,
1011
+ });
1012
+ toast.success(t('toasts.questionCreated'));
1013
+ }
1011
1014
 
1012
- await refetchQuestions();
1013
- setSheetOpen(false);
1015
+ await refetchQuestions();
1016
+ setSheetOpen(false);
1017
+ } finally {
1018
+ setSaving(false);
1019
+ }
1014
1020
  }
1015
1021
 
1016
1022
  async function confirmDelete() {
@@ -1801,7 +1807,8 @@ export default function QuestoesPage() {
1801
1807
  )}
1802
1808
 
1803
1809
  <SheetFooter className="mt-auto gap-2 p-0 pt-4">
1804
- <Button type="submit" className="w-full">
1810
+ <Button type="submit" className="w-full" disabled={saving}>
1811
+ {saving && <Loader2 className="mr-2 size-4 animate-spin" />}
1805
1812
  {editingQuestao
1806
1813
  ? t('sheet.actions.update')
1807
1814
  : t('sheet.actions.create')}
@@ -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,
@@ -103,7 +104,6 @@ import {
103
104
  import { useTranslations } from 'next-intl';
104
105
  import { useRouter } from 'next/navigation';
105
106
  import { useEffect, useMemo, useRef, useState } from 'react';
106
- import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
107
107
  import { Controller, useForm, useWatch } from 'react-hook-form';
108
108
  import { toast } from 'sonner';
109
109
  import { z } from 'zod';
@@ -114,6 +114,7 @@ interface Formacao {
114
114
  id: number;
115
115
  nome: string;
116
116
  descricao: string;
117
+ progressionMode: 'sequencial' | 'livre';
117
118
  area: string;
118
119
  nivel: string;
119
120
  prerequisitos: string;
@@ -135,6 +136,9 @@ type TrainingColorPayload = {
135
136
  secondaryColor?: string | null;
136
137
  primary_color?: string | null;
137
138
  secondary_color?: string | null;
139
+ progressionMode?: string | null;
140
+ progressMode?: string | null;
141
+ progress_mode?: string | null;
138
142
  };
139
143
 
140
144
  interface CursoOption {
@@ -340,6 +344,14 @@ function normalizeLevelValue(value: string) {
340
344
  return 'Iniciante';
341
345
  }
342
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
+
343
355
  function normalizeStatusValue(value: string) {
344
356
  const normalized = normalizeText(value);
345
357
 
@@ -391,6 +403,12 @@ function normalizeTrainingColorPayload<T extends TrainingColorPayload>(
391
403
 
392
404
  return {
393
405
  ...payload,
406
+ progressionMode: normalizeProgressModeValue(
407
+ payload.progressionMode ??
408
+ payload.progressMode ??
409
+ payload.progress_mode ??
410
+ undefined
411
+ ),
394
412
  primaryColor: primaryColor ?? undefined,
395
413
  secondaryColor: secondaryColor ?? undefined,
396
414
  };
@@ -422,7 +440,8 @@ function buildCourseCodeFromTitle(title: string) {
422
440
 
423
441
  const formacaoSchema = z.object({
424
442
  nome: z.string().min(3, 'Minimo 3 caracteres'),
425
- descricao: z.string().min(10, 'Minimo 10 caracteres'),
443
+ descricao: z.string().optional(),
444
+ progressionMode: z.enum(['sequencial', 'livre']),
426
445
  area: z.enum(['Tecnologia', 'Design', 'Gestao', 'Marketing', 'Financas']),
427
446
  nivel: z.enum(['Iniciante', 'Intermediario', 'Avancado']),
428
447
  prerequisitos: z.string().optional(),
@@ -728,6 +747,7 @@ export default function TrainingPage() {
728
747
  defaultValues: {
729
748
  nome: '',
730
749
  descricao: '',
750
+ progressionMode: 'sequencial' as const,
731
751
  area: 'Tecnologia',
732
752
  nivel: 'Iniciante',
733
753
  prerequisitos: '',
@@ -967,6 +987,7 @@ export default function TrainingPage() {
967
987
  form.reset({
968
988
  nome: '',
969
989
  descricao: '',
990
+ progressionMode: 'sequencial',
970
991
  area: 'Tecnologia',
971
992
  nivel: 'Iniciante',
972
993
  prerequisitos: '',
@@ -1022,6 +1043,9 @@ export default function TrainingPage() {
1022
1043
  form.reset({
1023
1044
  nome: fullFormacao.nome,
1024
1045
  descricao: fullFormacao.descricao,
1046
+ progressionMode: normalizeProgressModeValue(
1047
+ fullFormacao.progressionMode
1048
+ ),
1025
1049
  area: normalizeAreaValue(fullFormacao.area),
1026
1050
  nivel: normalizeLevelValue(fullFormacao.nivel),
1027
1051
  prerequisitos: fullFormacao.prerequisitos,
@@ -1171,6 +1195,9 @@ export default function TrainingPage() {
1171
1195
  }
1172
1196
 
1173
1197
  async function onSubmit(data: FormacaoForm) {
1198
+ const toApiProgressMode = (value: FormacaoForm['progressionMode']) =>
1199
+ value === 'livre' ? 'free' : 'sequential';
1200
+
1174
1201
  try {
1175
1202
  const orderedItems = normalizeTrailOrder(
1176
1203
  [...learningPathItems].sort((a, b) => a.order - b.order)
@@ -1196,6 +1223,7 @@ export default function TrainingPage() {
1196
1223
  shortDescription?: string;
1197
1224
  level?: 'beginner' | 'intermediate' | 'advanced';
1198
1225
  status?: 'draft' | 'active' | 'archived';
1226
+ progressMode?: 'sequential' | 'free';
1199
1227
  primaryColor?: string;
1200
1228
  secondaryColor?: string;
1201
1229
  items?: Array<{
@@ -1215,6 +1243,7 @@ export default function TrainingPage() {
1215
1243
  if (dirty.status) {
1216
1244
  payload.status = ptStatusToApi(data.status as Formacao['status']);
1217
1245
  }
1246
+ payload.progressMode = toApiProgressMode(data.progressionMode);
1218
1247
  if (dirty.primaryColor) payload.primaryColor = data.primaryColor;
1219
1248
  if (dirty.secondaryColor) payload.secondaryColor = data.secondaryColor;
1220
1249
  if (itemsChanged) {
@@ -1244,6 +1273,7 @@ export default function TrainingPage() {
1244
1273
  shortDescription: data.prerequisitos?.trim() || undefined,
1245
1274
  level: ptLevelToApi(data.nivel),
1246
1275
  status: ptStatusToApi(data.status as Formacao['status']),
1276
+ progressMode: toApiProgressMode(data.progressionMode),
1247
1277
  primaryColor: data.primaryColor,
1248
1278
  secondaryColor: data.secondaryColor,
1249
1279
  items: orderedItems.map((item, index) => ({
@@ -1899,8 +1929,7 @@ export default function TrainingPage() {
1899
1929
  </Field>
1900
1930
  <Field>
1901
1931
  <FieldLabel htmlFor="descricao">
1902
- {t('form.fields.description.label')}{' '}
1903
- <span className="text-destructive">*</span>
1932
+ {t('form.fields.description.label')}
1904
1933
  </FieldLabel>
1905
1934
  <Textarea
1906
1935
  id="descricao"
@@ -1908,8 +1937,42 @@ export default function TrainingPage() {
1908
1937
  placeholder={t('form.fields.description.placeholder')}
1909
1938
  {...form.register('descricao')}
1910
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
+ )}
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>
1911
1974
  <FieldError>
1912
- {form.formState.errors.descricao?.message}
1975
+ {form.formState.errors.progressionMode?.message}
1913
1976
  </FieldError>
1914
1977
  </Field>
1915
1978
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
@@ -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,
@@ -102,7 +103,6 @@ import {
102
103
  import { useTranslations } from 'next-intl';
103
104
  import { useRouter } from 'next/navigation';
104
105
  import { useEffect, useMemo, useRef, useState } from 'react';
105
- import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
106
106
  import { Controller, useForm, useWatch } from 'react-hook-form';
107
107
  import { toast } from 'sonner';
108
108
  import { z } from 'zod';
@@ -113,6 +113,7 @@ interface Formacao {
113
113
  id: number;
114
114
  nome: string;
115
115
  descricao: string;
116
+ progressionMode: 'sequencial' | 'livre';
116
117
  area: string;
117
118
  nivel: string;
118
119
  prerequisitos: string;
@@ -344,6 +345,14 @@ function normalizeStatusValue(value: string) {
344
345
  return 'rascunho';
345
346
  }
346
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
+
347
356
  function slugifyText(value: string) {
348
357
  return normalizeText(value)
349
358
  .replace(/[^a-z0-9\s-]/g, '')
@@ -417,7 +426,8 @@ function buildCourseCodeFromTitle(title: string) {
417
426
 
418
427
  const formacaoSchema = z.object({
419
428
  nome: z.string().min(3, 'Minimo 3 caracteres'),
420
- descricao: z.string().min(10, 'Minimo 10 caracteres'),
429
+ descricao: z.string().optional(),
430
+ progressionMode: z.enum(['sequencial', 'livre']),
421
431
  area: z.enum(['Tecnologia', 'Design', 'Gestao', 'Marketing', 'Financas']),
422
432
  nivel: z.enum(['Iniciante', 'Intermediario', 'Avancado']),
423
433
  prerequisitos: z.string().optional(),
@@ -721,6 +731,7 @@ export default function TrainingPage() {
721
731
  defaultValues: {
722
732
  nome: '',
723
733
  descricao: '',
734
+ progressionMode: 'sequencial',
724
735
  area: 'Tecnologia',
725
736
  nivel: 'Iniciante',
726
737
  prerequisitos: '',
@@ -872,6 +883,8 @@ export default function TrainingPage() {
872
883
  ...formacao,
873
884
  nome: watchedFormValues.nome ?? formacao.nome,
874
885
  descricao: watchedFormValues.descricao ?? formacao.descricao,
886
+ progressionMode:
887
+ watchedFormValues.progressionMode ?? formacao.progressionMode,
875
888
  nivel: watchedFormValues.nivel ?? formacao.nivel,
876
889
  status: watchedFormValues.status ?? formacao.status,
877
890
  prerequisitos:
@@ -960,6 +973,7 @@ export default function TrainingPage() {
960
973
  form.reset({
961
974
  nome: '',
962
975
  descricao: '',
976
+ progressionMode: 'sequencial',
963
977
  area: 'Tecnologia',
964
978
  nivel: 'Iniciante',
965
979
  prerequisitos: '',
@@ -1015,6 +1029,9 @@ export default function TrainingPage() {
1015
1029
  form.reset({
1016
1030
  nome: fullFormacao.nome,
1017
1031
  descricao: fullFormacao.descricao,
1032
+ progressionMode: normalizeProgressModeValue(
1033
+ fullFormacao.progressionMode
1034
+ ),
1018
1035
  area: normalizeAreaValue(fullFormacao.area),
1019
1036
  nivel: normalizeLevelValue(fullFormacao.nivel),
1020
1037
  prerequisitos: fullFormacao.prerequisitos,
@@ -1164,6 +1181,9 @@ export default function TrainingPage() {
1164
1181
  }
1165
1182
 
1166
1183
  async function onSubmit(data: FormacaoForm) {
1184
+ const toApiProgressMode = (value: FormacaoForm['progressionMode']) =>
1185
+ value === 'livre' ? 'free' : 'sequential';
1186
+
1167
1187
  try {
1168
1188
  const orderedItems = normalizeTrailOrder(
1169
1189
  [...learningPathItems].sort((a, b) => a.order - b.order)
@@ -1189,6 +1209,7 @@ export default function TrainingPage() {
1189
1209
  shortDescription?: string;
1190
1210
  level?: 'beginner' | 'intermediate' | 'advanced';
1191
1211
  status?: 'draft' | 'active' | 'archived';
1212
+ progressMode?: 'sequential' | 'free';
1192
1213
  primaryColor?: string;
1193
1214
  secondaryColor?: string;
1194
1215
  items?: Array<{
@@ -1200,7 +1221,9 @@ export default function TrainingPage() {
1200
1221
  } = {};
1201
1222
 
1202
1223
  if (dirty.nome) payload.title = data.nome;
1203
- if (dirty.descricao) payload.description = data.descricao;
1224
+ if (dirty.descricao) {
1225
+ payload.description = data.descricao?.trim() || undefined;
1226
+ }
1204
1227
  if (dirty.prerequisitos) {
1205
1228
  payload.shortDescription = data.prerequisitos?.trim() || undefined;
1206
1229
  }
@@ -1208,6 +1231,9 @@ export default function TrainingPage() {
1208
1231
  if (dirty.status) {
1209
1232
  payload.status = ptStatusToApi(data.status as Formacao['status']);
1210
1233
  }
1234
+ if (dirty.progressionMode) {
1235
+ payload.progressMode = toApiProgressMode(data.progressionMode);
1236
+ }
1211
1237
  if (dirty.primaryColor) payload.primaryColor = data.primaryColor;
1212
1238
  if (dirty.secondaryColor) payload.secondaryColor = data.secondaryColor;
1213
1239
  if (itemsChanged) {
@@ -1233,10 +1259,11 @@ export default function TrainingPage() {
1233
1259
  } else {
1234
1260
  const payload = {
1235
1261
  title: data.nome,
1236
- description: data.descricao,
1262
+ description: data.descricao?.trim() || undefined,
1237
1263
  shortDescription: data.prerequisitos?.trim() || undefined,
1238
1264
  level: ptLevelToApi(data.nivel),
1239
1265
  status: ptStatusToApi(data.status as Formacao['status']),
1266
+ progressMode: toApiProgressMode(data.progressionMode),
1240
1267
  primaryColor: data.primaryColor,
1241
1268
  secondaryColor: data.secondaryColor,
1242
1269
  items: orderedItems.map((item, index) => ({
@@ -1892,12 +1919,12 @@ export default function TrainingPage() {
1892
1919
  </Field>
1893
1920
  <Field>
1894
1921
  <FieldLabel htmlFor="descricao">
1895
- {t('form.fields.description.label')}{' '}
1896
- <span className="text-destructive">*</span>
1922
+ {t('form.fields.description.label')}
1897
1923
  </FieldLabel>
1898
1924
  <Textarea
1899
1925
  id="descricao"
1900
1926
  rows={3}
1927
+ required={false}
1901
1928
  placeholder={t('form.fields.description.placeholder')}
1902
1929
  {...form.register('descricao')}
1903
1930
  />
@@ -1905,6 +1932,43 @@ export default function TrainingPage() {
1905
1932
  {form.formState.errors.descricao?.message}
1906
1933
  </FieldError>
1907
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>
1908
1972
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
1909
1973
  <Field>
1910
1974
  <FieldLabel>
@@ -1544,7 +1544,20 @@
1544
1544
  "description": {
1545
1545
  "label": "Descrição",
1546
1546
  "placeholder": "Descreva a formação...",
1547
- "required": true
1547
+ "required": false
1548
+ },
1549
+ "progressionMode": {
1550
+ "label": "Fluxo da Trilha",
1551
+ "placeholder": "Selecione o fluxo",
1552
+ "required": true,
1553
+ "options": {
1554
+ "sequential": "Exigir ordem dos passos",
1555
+ "free": "Liberar navegação entre passos"
1556
+ },
1557
+ "help": {
1558
+ "sequential": "O aluno precisa concluir cada etapa da trilha antes de avançar para a próxima.",
1559
+ "free": "O aluno pode abrir qualquer etapa da trilha a qualquer momento, sem bloqueio de sequência."
1560
+ }
1548
1561
  },
1549
1562
  "area": {
1550
1563
  "label": "Área",
@@ -24,6 +24,10 @@ columns:
24
24
  type: enum
25
25
  values: [draft, active, archived]
26
26
  default: draft
27
+ - name: progress_mode
28
+ type: enum
29
+ values: [sequential, free]
30
+ default: sequential
27
31
  - name: primary_color
28
32
  type: varchar
29
33
  length: 9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.338",
3
+ "version": "0.0.349",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,15 +9,15 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.6",
12
+ "@hed-hog/api-locale": "0.0.14",
13
13
  "@hed-hog/api-types": "0.0.1",
14
- "@hed-hog/api": "0.0.8",
14
+ "@hed-hog/api-prisma": "0.0.6",
15
15
  "@hed-hog/api-pagination": "0.0.7",
16
- "@hed-hog/api-locale": "0.0.14",
17
- "@hed-hog/contact": "0.0.338",
18
- "@hed-hog/category": "0.0.338",
19
- "@hed-hog/core": "0.0.338",
20
- "@hed-hog/finance": "0.0.338"
16
+ "@hed-hog/contact": "0.0.349",
17
+ "@hed-hog/api": "0.0.8",
18
+ "@hed-hog/core": "0.0.349",
19
+ "@hed-hog/category": "0.0.349",
20
+ "@hed-hog/finance": "0.0.349"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -28,6 +28,7 @@ export class CourseController {
28
28
  @Query('status') status?: string,
29
29
  @Query('level') level?: string,
30
30
  @Query('category') category?: string,
31
+ @Query('offeringTypes') offeringTypes?: string | string[],
31
32
  ) {
32
33
  return this.courseService.list({
33
34
  page: page ? Number(page) : 1,
@@ -36,9 +37,26 @@ export class CourseController {
36
37
  status,
37
38
  level,
38
39
  category,
40
+ offeringTypes: this.toArray(offeringTypes),
39
41
  });
40
42
  }
41
43
 
44
+ private toArray(value?: string | string[]) {
45
+ if (Array.isArray(value)) {
46
+ return value
47
+ .flatMap((item) => String(item).split(','))
48
+ .map((item) => item.trim())
49
+ .filter(Boolean);
50
+ }
51
+ if (typeof value === 'string') {
52
+ return value
53
+ .split(',')
54
+ .map((item) => item.trim())
55
+ .filter(Boolean);
56
+ }
57
+ return [];
58
+ }
59
+
42
60
  @Get('stats')
43
61
  stats() {
44
62
  return this.courseService.stats();