@hed-hog/lms 0.0.329 → 0.0.331

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 (60) hide show
  1. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
  2. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -8
  3. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
  4. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
  5. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
  6. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
  7. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
  8. package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
  9. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
  10. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +27 -27
  11. package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
  12. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
  13. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +8 -6
  14. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
  15. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
  16. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
  17. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
  18. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
  19. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
  20. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
  21. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
  22. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +78 -36
  23. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
  24. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
  25. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
  26. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
  27. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
  28. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
  29. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
  30. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
  31. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
  32. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
  33. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
  34. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
  35. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
  36. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
  37. package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
  38. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
  39. package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
  40. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
  41. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
  42. package/hedhog/frontend/app/instructors/page.tsx.ejs +75 -54
  43. package/hedhog/frontend/app/paths/page.tsx.ejs +11 -7
  44. package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
  45. package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
  46. package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
  47. package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
  48. package/hedhog/frontend/app/training/page.tsx.ejs +5 -5
  49. package/hedhog/frontend/messages/en.json +899 -45
  50. package/hedhog/frontend/messages/pt.json +894 -38
  51. package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
  52. package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
  53. package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
  54. package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
  55. package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
  56. package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
  57. package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
  58. package/hedhog/table/instructor_qualification.yaml +1 -1
  59. package/hedhog/table/instructor_skill.yaml +1 -1
  60. package/package.json +7 -7
@@ -10,9 +10,11 @@ import {
10
10
  AlertDialogHeader,
11
11
  AlertDialogTitle,
12
12
  } from '@/components/ui/alert-dialog';
13
+ import { useTranslations } from 'next-intl';
13
14
  import { useStructureStore } from './store';
14
15
 
15
16
  export function ConfirmDialog() {
17
+ const t = useTranslations('lms.CoursesPage.StructurePage.confirmDialog');
16
18
  const confirmDialog = useStructureStore((s) => s.confirmDialog);
17
19
  const closeConfirm = useStructureStore((s) => s.closeConfirm);
18
20
 
@@ -31,15 +33,15 @@ export function ConfirmDialog() {
31
33
  )}
32
34
  </AlertDialogHeader>
33
35
  <AlertDialogFooter>
34
- <AlertDialogCancel onClick={closeConfirm}>Cancelar</AlertDialogCancel>
36
+ <AlertDialogCancel onClick={closeConfirm}>{t('cancel')}</AlertDialogCancel>
35
37
  <AlertDialogAction
36
38
  onClick={handleConfirm}
37
39
  className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
38
40
  >
39
- Excluir
41
+ {t('confirm')}
40
42
  </AlertDialogAction>
41
43
  </AlertDialogFooter>
42
44
  </AlertDialogContent>
43
45
  </AlertDialog>
44
46
  );
45
- }
47
+ }
@@ -295,7 +295,7 @@ export function CourseTreeDnd() {
295
295
  <p className="text-sm font-medium text-foreground/70">
296
296
  Nenhuma sessão
297
297
  </p>
298
- <p className="text-xs text-muted-foreground leading-relaxed max-w-[180px]">
298
+ <p className="text-xs text-muted-foreground leading-relaxed max-w-45">
299
299
  Adicione a primeira sessão para começar a organizar o conteúdo
300
300
  do curso.
301
301
  </p>
@@ -3,6 +3,7 @@
3
3
  import { Button } from '@/components/ui/button';
4
4
  import { cn } from '@/lib/utils';
5
5
  import { ChevronsDownUp, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
6
+ import { useTranslations } from 'next-intl';
6
7
  import { forwardRef, useMemo } from 'react';
7
8
  import { useCreateSessionMutation } from '../_data/use-course-structure-mutations';
8
9
  import { CourseTreeDnd } from './course-tree-dnd';
@@ -12,6 +13,7 @@ import { useStructureStore } from './store';
12
13
  import { buildVisibleItems } from './tree-helpers';
13
14
 
14
15
  export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
16
+ const t = useTranslations('lms.CoursesPage.StructurePage');
15
17
  const course = useStructureStore((s) => s.course);
16
18
  const sessions = useStructureStore((s) => s.sessions);
17
19
  const lessons = useStructureStore((s) => s.lessons);
@@ -57,10 +59,10 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
57
59
  )}
58
60
  title={
59
61
  allExpanded
60
- ? 'Recolher tudo (Ctrl+Shift+E)'
61
- : 'Expandir tudo (Ctrl+Shift+E)'
62
+ ? t('tree.collapseAllShortcut')
63
+ : t('tree.expandAllShortcut')
62
64
  }
63
- aria-label={allExpanded ? 'Recolher tudo' : 'Expandir tudo'}
65
+ aria-label={allExpanded ? t('tree.collapseAll') : t('tree.expandAll')}
64
66
  onClick={allExpanded ? collapseAll : expandAll}
65
67
  disabled={isFiltering}
66
68
  >
@@ -75,8 +77,8 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
75
77
  variant="ghost"
76
78
  size="icon"
77
79
  className="size-8 shrink-0"
78
- title="Nova sessão"
79
- aria-label="Nova sessão"
80
+ title={t('tree.addSession')}
81
+ aria-label={t('tree.addSession')}
80
82
  disabled={createSession.isPending}
81
83
  onClick={() => createSession.mutate()}
82
84
  >
@@ -92,8 +94,8 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
92
94
  {isFiltering && resultCount !== undefined && (
93
95
  <div className="px-3 py-1 text-[0.65rem] text-muted-foreground bg-muted/30 border-b shrink-0">
94
96
  {resultCount === 0
95
- ? 'Nenhum resultado encontrado'
96
- : `${resultCount} resultado${resultCount === 1 ? '' : 's'} encontrado${resultCount === 1 ? '' : 's'}`}
97
+ ? t('search.noResults')
98
+ : t('search.results', { count: resultCount })}
97
99
  </div>
98
100
  )}
99
101
 
@@ -1,4 +1,5 @@
1
1
  import { Skeleton } from '@/components/ui/skeleton';
2
+ import { useTranslations } from 'next-intl';
2
3
 
3
4
  /**
4
5
  * CourseTreeSkeleton
@@ -7,11 +8,12 @@ import { Skeleton } from '@/components/ui/skeleton';
7
8
  * the API. Mimics the visual shape of session headers and lesson rows.
8
9
  */
9
10
  export function CourseTreeSkeleton() {
11
+ const t = useTranslations('lms.CoursesPage.StructurePage');
10
12
  return (
11
13
  <div
12
14
  className="flex flex-col gap-1 p-3"
13
15
  aria-busy="true"
14
- aria-label="Carregando estrutura do curso"
16
+ aria-label={t('loading')}
15
17
  >
16
18
  {/* Course header row */}
17
19
  <div className="flex items-center gap-2 px-2 py-1.5">
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils';
4
4
  import type { DraggableAttributes } from '@dnd-kit/core';
5
5
  import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
6
6
  import { GripVertical } from 'lucide-react';
7
+ import { useTranslations } from 'next-intl';
7
8
 
8
9
  interface DragHandleProps {
9
10
  listeners?: SyntheticListenerMap;
@@ -23,6 +24,7 @@ export function DragHandle({
23
24
  disabled,
24
25
  className,
25
26
  }: DragHandleProps) {
27
+ const t = useTranslations('lms.CoursesPage.StructurePage.dragHandle');
26
28
  if (disabled) {
27
29
  return (
28
30
  <span
@@ -30,7 +32,7 @@ export function DragHandle({
30
32
  'shrink-0 size-5 flex items-center justify-center opacity-20 cursor-not-allowed',
31
33
  className
32
34
  )}
33
- title="Limpe a busca para reordenar"
35
+ title={t('disabled')}
34
36
  >
35
37
  <GripVertical className="size-3.5" />
36
38
  </span>
@@ -48,7 +50,7 @@ export function DragHandle({
48
50
  'transition-colors touch-none',
49
51
  className
50
52
  )}
51
- title="Arrastar para reordenar"
53
+ title={t('enabled')}
52
54
  // Prevent click events from bubbling to the row selection handler
53
55
  onClick={(e) => e.stopPropagation()}
54
56
  >
@@ -10,6 +10,7 @@ import {
10
10
  Video,
11
11
  X,
12
12
  } from 'lucide-react';
13
+ import { useTranslations } from 'next-intl';
13
14
  import { useState } from 'react';
14
15
  import { toast } from 'sonner';
15
16
 
@@ -43,6 +44,7 @@ const STATUS_LABELS: Record<LessonStatus, string> = {
43
44
  // ── Component ─────────────────────────────────────────────────────────────────
44
45
 
45
46
  export function EditorBulk() {
47
+ const t = useTranslations('lms.CoursesPage.StructurePage.bulkEditor');
46
48
  const selectedIds = useStructureStore((s) => s.selectedIds);
47
49
  const sessions = useStructureStore((s) => s.sessions);
48
50
  const lessons = useStructureStore((s) => s.lessons);
@@ -74,8 +76,8 @@ export function EditorBulk() {
74
76
  );
75
77
  toast.success(
76
78
  lines.length
77
- ? `Edição em massa: ${lines.join(', ')} (mock)`
78
- : 'Nenhuma alteração selecionada'
79
+ ? t('toast.preview', { changes: lines.join(', ') })
80
+ : t('toast.none')
79
81
  );
80
82
  }
81
83
 
@@ -93,11 +95,11 @@ export function EditorBulk() {
93
95
  )}
94
96
  </div>
95
97
  <div className="flex-1 min-w-0">
96
- <p className="text-sm font-semibold">Edição em massa</p>
98
+ <p className="text-sm font-semibold">{t('title')}</p>
97
99
  <p className="text-[0.65rem] text-muted-foreground">
98
100
  {selectedIds.size}{' '}
99
- {allLessons ? 'aulas' : allSessions ? 'sessões' : 'itens'}{' '}
100
- selecionados
101
+ {allLessons ? t('types.lessons') : allSessions ? t('types.sessions') : t('types.items')}{' '}
102
+ {t('types.selected')}
101
103
  </p>
102
104
  </div>
103
105
  <Button
@@ -105,7 +107,7 @@ export function EditorBulk() {
105
107
  size="icon"
106
108
  className="size-7 shrink-0"
107
109
  onClick={clearSelection}
108
- title="Limpar seleção"
110
+ title={t('clearSelection')}
109
111
  >
110
112
  <X className="size-3.5" />
111
113
  </Button>
@@ -148,7 +150,7 @@ export function EditorBulk() {
148
150
  <Card>
149
151
  <CardHeader className="px-3 pt-3 pb-2">
150
152
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
151
- Status de produção
153
+ {t('productionStatus')}
152
154
  </CardTitle>
153
155
  </CardHeader>
154
156
  <CardContent className="px-3 pb-3">
@@ -157,14 +159,14 @@ export function EditorBulk() {
157
159
  onValueChange={(v) => setStatus(v as LessonStatus)}
158
160
  >
159
161
  <SelectTrigger className="h-8 text-xs w-full">
160
- <SelectValue placeholder="Manter atual" />
162
+ <SelectValue placeholder={t('keepCurrent')} />
161
163
  </SelectTrigger>
162
164
  <SelectContent>
163
- <SelectItem value="preparada">Preparada</SelectItem>
164
- <SelectItem value="gravada">Gravada</SelectItem>
165
- <SelectItem value="editada">Editada</SelectItem>
166
- <SelectItem value="finalizada">Finalizada</SelectItem>
167
- <SelectItem value="publicada">Publicada</SelectItem>
165
+ <SelectItem value="preparada">{t('status.preparada')}</SelectItem>
166
+ <SelectItem value="gravada">{t('status.gravada')}</SelectItem>
167
+ <SelectItem value="editada">{t('status.editada')}</SelectItem>
168
+ <SelectItem value="finalizada">{t('status.finalizada')}</SelectItem>
169
+ <SelectItem value="publicada">{t('status.publicada')}</SelectItem>
168
170
  </SelectContent>
169
171
  </Select>
170
172
  </CardContent>
@@ -175,7 +177,7 @@ export function EditorBulk() {
175
177
  <Card>
176
178
  <CardHeader className="px-3 pt-3 pb-2">
177
179
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
178
- Visibilidade
180
+ {t('visibilityTitle')}
179
181
  </CardTitle>
180
182
  </CardHeader>
181
183
  <CardContent className="px-3 pb-3">
@@ -184,22 +186,22 @@ export function EditorBulk() {
184
186
  onValueChange={(v) => setVisibility(v as Visibility)}
185
187
  >
186
188
  <SelectTrigger className="h-8 text-xs w-full">
187
- <SelectValue placeholder="Manter atual" />
189
+ <SelectValue placeholder={t('keepCurrent')} />
188
190
  </SelectTrigger>
189
191
  <SelectContent>
190
192
  <SelectItem value="publico">
191
193
  <span className="flex items-center gap-1.5">
192
- <Eye className="size-3" /> Público
194
+ <Eye className="size-3" /> {t('visibility.publico')}
193
195
  </span>
194
196
  </SelectItem>
195
197
  <SelectItem value="privado">
196
198
  <span className="flex items-center gap-1.5">
197
- <EyeOff className="size-3" /> Privado
199
+ <EyeOff className="size-3" /> {t('visibility.privado')}
198
200
  </span>
199
201
  </SelectItem>
200
202
  <SelectItem value="restrito">
201
203
  <span className="flex items-center gap-1.5">
202
- <Lock className="size-3" /> Restrito
204
+ <Lock className="size-3" /> {t('visibility.restrito')}
203
205
  </span>
204
206
  </SelectItem>
205
207
  </SelectContent>
@@ -213,13 +215,13 @@ export function EditorBulk() {
213
215
  <CardHeader className="px-3 pt-3 pb-2">
214
216
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
215
217
  <FolderInput className="size-3" />
216
- Mover para sessão
218
+ {t('moveToSession')}
217
219
  </CardTitle>
218
220
  </CardHeader>
219
221
  <CardContent className="px-3 pb-3">
220
222
  <Select value={targetSession} onValueChange={setTargetSession}>
221
223
  <SelectTrigger className="h-8 text-xs w-full">
222
- <SelectValue placeholder="Selecionar sessão…" />
224
+ <SelectValue placeholder={t('selectSession')} />
223
225
  </SelectTrigger>
224
226
  <SelectContent>
225
227
  {sessions.map((s) => (
@@ -238,8 +240,7 @@ export function EditorBulk() {
238
240
 
239
241
  <div className="rounded-md border border-amber-200 bg-amber-50/60 dark:border-amber-800 dark:bg-amber-950/30 px-3 py-2">
240
242
  <p className="text-[0.65rem] text-amber-700 dark:text-amber-400">
241
- As alterações em massa ainda não estão integradas com a API.
242
- Clique em salvar para ver a pré-visualização.
243
+ {t('apiNotice')}
243
244
  </p>
244
245
  </div>
245
246
  </div>
@@ -257,7 +258,7 @@ export function EditorBulk() {
257
258
  onClick={clearSelection}
258
259
  >
259
260
  <X className="size-3 mr-1" />
260
- Limpar seleção
261
+ {t('clearSelection')}
261
262
  </Button>
262
263
  <div className="flex-1" />
263
264
  <Button