@hed-hog/lms 0.0.329 → 0.0.330

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 (37) 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 +7 -5
  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/classes/[id]/page.tsx.ejs +10 -10
  10. package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
  11. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +5 -3
  12. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
  13. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
  14. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
  15. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
  16. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
  17. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
  18. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +7 -5
  19. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
  20. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
  21. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
  22. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
  23. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
  24. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
  25. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
  26. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
  27. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
  28. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
  29. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
  30. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
  31. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
  32. package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
  33. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
  34. package/hedhog/frontend/app/instructors/page.tsx.ejs +4 -2
  35. package/hedhog/frontend/messages/en.json +619 -13
  36. package/hedhog/frontend/messages/pt.json +619 -13
  37. package/package.json +7 -7
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import { useMutation, useQueryClient } from '@tanstack/react-query';
24
+ import { useTranslations } from 'next-intl';
24
25
  import { toast } from 'sonner';
25
26
 
26
27
  import { useApp } from '@hed-hog/next-app-provider';
@@ -73,6 +74,7 @@ interface UpdateCourseVars {
73
74
  * cache entry so the header immediately reflects the new title/slug.
74
75
  */
75
76
  export function useUpdateCourseMutation() {
77
+ const t = useTranslations('lms.courseStructure');
76
78
  const { request } = useApp();
77
79
  const queryClient = useQueryClient();
78
80
  const courseId = useStructureStore((s) => s.courseId);
@@ -106,13 +108,13 @@ export function useUpdateCourseMutation() {
106
108
  },
107
109
  };
108
110
  });
109
- toast.success('Curso salvo');
111
+ toast.success(t('mutations.course.saveSuccess'));
110
112
  },
111
113
  onError: () => {
112
114
  void queryClient.invalidateQueries({
113
115
  queryKey: courseStructureQueryKey(courseId),
114
116
  });
115
- toast.error('Erro ao salvar curso');
117
+ toast.error(t('mutations.course.saveError'));
116
118
  },
117
119
  });
118
120
  }
@@ -128,6 +130,7 @@ export function useUpdateCourseMutation() {
128
130
  * The newly created session becomes the active/selected item.
129
131
  */
130
132
  export function useCreateSessionMutation() {
133
+ const t = useTranslations('lms.courseStructure');
131
134
  const { request } = useApp();
132
135
  const queryClient = useQueryClient();
133
136
  const courseId = useStructureStore((s) => s.courseId);
@@ -153,13 +156,13 @@ export function useCreateSessionMutation() {
153
156
  void queryClient.invalidateQueries({
154
157
  queryKey: courseStructureQueryKey(courseId),
155
158
  });
156
- toast.success('Sessão criada');
159
+ toast.success(t('mutations.session.createSuccess'));
157
160
  },
158
161
  onError: () => {
159
162
  void queryClient.invalidateQueries({
160
163
  queryKey: courseStructureQueryKey(courseId),
161
164
  });
162
- toast.error('Erro ao criar sessão');
165
+ toast.error(t('mutations.session.createError'));
163
166
  },
164
167
  });
165
168
  }
@@ -179,6 +182,7 @@ interface UpdateSessionVars {
179
182
  * Persists the form values and updates the store on success.
180
183
  */
181
184
  export function useUpdateSessionMutation() {
185
+ const t = useTranslations('lms.courseStructure');
182
186
  const { request } = useApp();
183
187
  const queryClient = useQueryClient();
184
188
  const courseId = useStructureStore((s) => s.courseId);
@@ -214,14 +218,14 @@ export function useUpdateSessionMutation() {
214
218
  };
215
219
  }
216
220
  );
217
- toast.success('Sessão salva');
221
+ toast.success(t('mutations.session.saveSuccess'));
218
222
  },
219
223
  onError: () => {
220
224
  // Fall back to server truth
221
225
  void queryClient.invalidateQueries({
222
226
  queryKey: courseStructureQueryKey(courseId),
223
227
  });
224
- toast.error('Erro ao salvar sessão');
228
+ toast.error(t('mutations.session.saveError'));
225
229
  },
226
230
  });
227
231
  }
@@ -241,6 +245,7 @@ interface CreateLessonVars {
241
245
  * The newly created lesson becomes the active/selected item.
242
246
  */
243
247
  export function useCreateLessonMutation() {
248
+ const t = useTranslations('lms.courseStructure');
244
249
  const { request } = useApp();
245
250
  const queryClient = useQueryClient();
246
251
  const courseId = useStructureStore((s) => s.courseId);
@@ -271,13 +276,13 @@ export function useCreateLessonMutation() {
271
276
  void queryClient.invalidateQueries({
272
277
  queryKey: courseStructureQueryKey(courseId),
273
278
  });
274
- toast.success('Aula criada');
279
+ toast.success(t('mutations.lesson.createSuccess'));
275
280
  },
276
281
  onError: () => {
277
282
  void queryClient.invalidateQueries({
278
283
  queryKey: courseStructureQueryKey(courseId),
279
284
  });
280
- toast.error('Erro ao criar aula');
285
+ toast.error(t('mutations.lesson.createError'));
281
286
  },
282
287
  });
283
288
  }
@@ -304,6 +309,7 @@ interface UpdateLessonVars {
304
309
  * Persists the form values and updates the store on success.
305
310
  */
306
311
  export function useUpdateLessonMutation() {
312
+ const t = useTranslations('lms.courseStructure');
307
313
  const { request } = useApp();
308
314
  const queryClient = useQueryClient();
309
315
  const courseId = useStructureStore((s) => s.courseId);
@@ -340,13 +346,13 @@ export function useUpdateLessonMutation() {
340
346
  };
341
347
  }
342
348
  );
343
- toast.success('Aula salva');
349
+ toast.success(t('mutations.lesson.saveSuccess'));
344
350
  },
345
351
  onError: () => {
346
352
  void queryClient.invalidateQueries({
347
353
  queryKey: courseStructureQueryKey(courseId),
348
354
  });
349
- toast.error('Erro ao salvar aula');
355
+ toast.error(t('mutations.lesson.saveError'));
350
356
  },
351
357
  });
352
358
  }
@@ -366,6 +372,7 @@ interface DeleteSessionVars {
366
372
  * from the Zustand store. Selection is reset to the course root.
367
373
  */
368
374
  export function useDeleteSessionMutation() {
375
+ const t = useTranslations('lms.courseStructure');
369
376
  const { request } = useApp();
370
377
  const queryClient = useQueryClient();
371
378
  const courseId = useStructureStore((s) => s.courseId);
@@ -389,13 +396,13 @@ export function useDeleteSessionMutation() {
389
396
  };
390
397
  }
391
398
  );
392
- toast.success('Sessão excluída');
399
+ toast.success(t('mutations.session.deleteSuccess'));
393
400
  },
394
401
  onError: () => {
395
402
  void queryClient.invalidateQueries({
396
403
  queryKey: courseStructureQueryKey(courseId),
397
404
  });
398
- toast.error('Erro ao excluir sessão');
405
+ toast.error(t('mutations.session.deleteError'));
399
406
  },
400
407
  });
401
408
  }
@@ -415,6 +422,7 @@ interface DeleteLessonVars {
415
422
  * Deletes a lesson and removes it from the Zustand store.
416
423
  */
417
424
  export function useDeleteLessonMutation() {
425
+ const t = useTranslations('lms.courseStructure');
418
426
  const { request } = useApp();
419
427
  const queryClient = useQueryClient();
420
428
  const courseId = useStructureStore((s) => s.courseId);
@@ -433,13 +441,13 @@ export function useDeleteLessonMutation() {
433
441
  ? { ...old, lessons: old.lessons.filter((l) => l.id !== lid) }
434
442
  : old
435
443
  );
436
- toast.success('Aula excluída');
444
+ toast.success(t('mutations.lesson.deleteSuccess'));
437
445
  },
438
446
  onError: () => {
439
447
  void queryClient.invalidateQueries({
440
448
  queryKey: courseStructureQueryKey(courseId),
441
449
  });
442
- toast.error('Erro ao excluir aula');
450
+ toast.error(t('mutations.lesson.deleteError'));
443
451
  },
444
452
  });
445
453
  }
@@ -466,6 +474,7 @@ interface BulkDeleteVars {
466
474
  * 5. Show partial-error toast when some items fail.
467
475
  */
468
476
  export function useBulkDeleteMutation() {
477
+ const t = useTranslations('lms.courseStructure');
469
478
  const { request } = useApp();
470
479
  const queryClient = useQueryClient();
471
480
  const courseId = useStructureStore((s) => s.courseId);
@@ -538,7 +547,7 @@ export function useBulkDeleteMutation() {
538
547
  void queryClient.invalidateQueries({
539
548
  queryKey: courseStructureQueryKey(courseId),
540
549
  });
541
- toast.error('Erro ao excluir itens');
550
+ toast.error(t('mutations.item.deleteError'));
542
551
  },
543
552
  });
544
553
  }
@@ -561,6 +570,7 @@ interface ReorderSessionsVars {
561
570
  * On error: rolls back via setStructureFromApi with the previous snapshot.
562
571
  */
563
572
  export function useReorderSessionsMutation() {
573
+ const t = useTranslations('lms.courseStructure');
564
574
  const { request } = useApp();
565
575
  const queryClient = useQueryClient();
566
576
  const courseId = useStructureStore((s) => s.courseId);
@@ -600,7 +610,7 @@ export function useReorderSessionsMutation() {
600
610
  if (context?.previousCache) {
601
611
  queryClient.setQueryData(qKey, context.previousCache);
602
612
  }
603
- toast.error('Erro ao salvar ordem das sessões — revertido');
613
+ toast.error(t('mutations.session.reorderError'));
604
614
  },
605
615
  });
606
616
  }
@@ -624,6 +634,7 @@ interface ReorderLessonsVars {
624
634
  * On error: rolls back via setStructureFromApi with the previous snapshot.
625
635
  */
626
636
  export function useReorderLessonsMutation() {
637
+ const t = useTranslations('lms.courseStructure');
627
638
  const { request } = useApp();
628
639
  const queryClient = useQueryClient();
629
640
  const courseId = useStructureStore((s) => s.courseId);
@@ -662,7 +673,7 @@ export function useReorderLessonsMutation() {
662
673
  if (context?.previousCache) {
663
674
  queryClient.setQueryData(qKey, context.previousCache);
664
675
  }
665
- toast.error('Erro ao salvar ordem das aulas — revertido');
676
+ toast.error(t('mutations.lesson.reorderError'));
666
677
  },
667
678
  });
668
679
  }
@@ -687,6 +698,7 @@ interface MoveLessonVars {
687
698
  * On error: rolls back via setStructureFromApi with the previous snapshot.
688
699
  */
689
700
  export function useMoveLessonMutation() {
701
+ const t = useTranslations('lms.courseStructure');
690
702
  const { request } = useApp();
691
703
  const queryClient = useQueryClient();
692
704
  const courseId = useStructureStore((s) => s.courseId);
@@ -729,7 +741,7 @@ export function useMoveLessonMutation() {
729
741
  if (context?.previousCache) {
730
742
  queryClient.setQueryData(qKey, context.previousCache);
731
743
  }
732
- toast.error('Erro ao mover aula — revertido');
744
+ toast.error(t('mutations.lesson.moveError'));
733
745
  },
734
746
  });
735
747
  }
@@ -745,6 +757,7 @@ export function useMoveLessonMutation() {
745
757
  * Non-optimistic: waits for API then adds real items to store.
746
758
  */
747
759
  export function useDuplicateSessionMutation() {
760
+ const t = useTranslations('lms.courseStructure');
748
761
  const { request } = useApp();
749
762
  const queryClient = useQueryClient();
750
763
  const courseId = useStructureStore((s) => s.courseId);
@@ -763,13 +776,13 @@ export function useDuplicateSessionMutation() {
763
776
  void queryClient.invalidateQueries({
764
777
  queryKey: courseStructureQueryKey(courseId),
765
778
  });
766
- toast.success(`Sessão duplicada`);
779
+ toast.success(t('mutations.session.duplicateSuccess'));
767
780
  },
768
781
  onError: () => {
769
782
  void queryClient.invalidateQueries({
770
783
  queryKey: courseStructureQueryKey(courseId),
771
784
  });
772
- toast.error('Erro ao duplicar sessão');
785
+ toast.error(t('mutations.session.duplicateError'));
773
786
  },
774
787
  });
775
788
  }
@@ -785,6 +798,7 @@ export function useDuplicateSessionMutation() {
785
798
  * Non-optimistic: waits for API then adds the real item to store.
786
799
  */
787
800
  export function useDuplicateLessonMutation() {
801
+ const t = useTranslations('lms.courseStructure');
788
802
  const { request } = useApp();
789
803
  const queryClient = useQueryClient();
790
804
  const courseId = useStructureStore((s) => s.courseId);
@@ -805,13 +819,13 @@ export function useDuplicateLessonMutation() {
805
819
  void queryClient.invalidateQueries({
806
820
  queryKey: courseStructureQueryKey(courseId),
807
821
  });
808
- toast.success('Aula duplicada');
822
+ toast.success(t('mutations.lesson.duplicateSuccess'));
809
823
  },
810
824
  onError: () => {
811
825
  void queryClient.invalidateQueries({
812
826
  queryKey: courseStructureQueryKey(courseId),
813
827
  });
814
- toast.error('Erro ao duplicar aula');
828
+ toast.error(t('mutations.lesson.duplicateError'));
815
829
  },
816
830
  });
817
831
  }
@@ -827,6 +841,7 @@ export function useDuplicateLessonMutation() {
827
841
  * Non-optimistic: waits for API then adds the new items to store.
828
842
  */
829
843
  export function usePasteLessonsMutation() {
844
+ const t = useTranslations('lms.courseStructure');
830
845
  const { request } = useApp();
831
846
  const queryClient = useQueryClient();
832
847
  const courseId = useStructureStore((s) => s.courseId);
@@ -864,7 +879,7 @@ export function usePasteLessonsMutation() {
864
879
  void queryClient.invalidateQueries({
865
880
  queryKey: courseStructureQueryKey(courseId),
866
881
  });
867
- toast.error('Erro ao colar aulas');
882
+ toast.error(t('mutations.lesson.pasteError'));
868
883
  },
869
884
  });
870
885
  }
@@ -880,6 +895,7 @@ export function usePasteLessonsMutation() {
880
895
  * Non-optimistic: fires duplicate requests serially, then adds all results.
881
896
  */
882
897
  export function usePasteSessionsMutation() {
898
+ const t = useTranslations('lms.courseStructure');
883
899
  const { request } = useApp();
884
900
  const queryClient = useQueryClient();
885
901
  const courseId = useStructureStore((s) => s.courseId);
@@ -914,7 +930,7 @@ export function usePasteSessionsMutation() {
914
930
  void queryClient.invalidateQueries({
915
931
  queryKey: courseStructureQueryKey(courseId),
916
932
  });
917
- toast.error('Erro ao colar sessões');
933
+ toast.error(t('mutations.session.pasteError'));
918
934
  },
919
935
  });
920
936
  }
@@ -929,6 +945,7 @@ export function usePasteSessionsMutation() {
929
945
  * On error: rolls back store to snapshot.
930
946
  */
931
947
  export function useMoveLessonsMutation() {
948
+ const t = useTranslations('lms.courseStructure');
932
949
  const { request } = useApp();
933
950
  const queryClient = useQueryClient();
934
951
  const courseId = useStructureStore((s) => s.courseId);
@@ -981,7 +998,7 @@ export function useMoveLessonsMutation() {
981
998
  if (context?.previousCache) {
982
999
  queryClient.setQueryData(qKey, context.previousCache);
983
1000
  }
984
- toast.error('Erro ao mover aulas — revertido');
1001
+ toast.error(t('mutations.lesson.moveBatchError'));
985
1002
  },
986
1003
  });
987
1004
  }
@@ -29,6 +29,7 @@ import {
29
29
  import { useApp } from '@hed-hog/next-app-provider';
30
30
  import { zodResolver } from '@hookform/resolvers/zod';
31
31
  import { Eye, EyeOff, Loader2, RefreshCw } from 'lucide-react';
32
+ import { useTranslations } from 'next-intl';
32
33
  import { useEffect, useState } from 'react';
33
34
  import { useForm } from 'react-hook-form';
34
35
  import { toast } from 'sonner';
@@ -95,6 +96,7 @@ export function EnterpriseAdminCreateSheet({
95
96
  onOpenChange,
96
97
  onCreated,
97
98
  }: EnterpriseAdminCreateSheetProps) {
99
+ const t = useTranslations('lms.EnterprisePage');
98
100
  const { request } = useApp();
99
101
  const [saving, setSaving] = useState(false);
100
102
  const [showPassword, setShowPassword] = useState(false);
@@ -154,7 +156,7 @@ export function EnterpriseAdminCreateSheet({
154
156
  onCreated?.(newUser);
155
157
  handleOpenChange(false);
156
158
  } catch {
157
- toast.error('Failed to create administrator account.');
159
+ toast.error(t('sheet.adminCreated'));
158
160
  } finally {
159
161
  setSaving(false);
160
162
  }
@@ -21,6 +21,7 @@ import {
21
21
  } from '@/components/ui/table';
22
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
23
  import { Trash2 } from 'lucide-react';
24
+ import { useTranslations } from 'next-intl';
24
25
  import { useCallback, useState } from 'react';
25
26
  import { toast } from 'sonner';
26
27
  import { EnterpriseAdminCreateSheet } from './enterprise-admin-create-sheet';
@@ -52,6 +53,7 @@ export function AdministratorsTab({
52
53
  enterpriseId: number;
53
54
  onMutate?: () => void;
54
55
  }) {
56
+ const t = useTranslations('lms.EnterprisePage');
55
57
  const { request } = useApp();
56
58
  const [search, setSearch] = useState('');
57
59
  const [page, setPage] = useState(1);
@@ -104,9 +106,9 @@ export function AdministratorsTab({
104
106
  data: { role },
105
107
  }).then(() => refetch());
106
108
  toast.promise(promise, {
107
- loading: 'Saving role…',
108
- success: 'Role updated.',
109
- error: 'Failed to update role.',
109
+ loading: t('sheet.adminRoleUpdating'),
110
+ success: t('sheet.adminRoleUpdated'),
111
+ error: t('sheet.adminRoleUpdateError'),
110
112
  });
111
113
  try {
112
114
  await promise;
@@ -127,9 +129,9 @@ export function AdministratorsTab({
127
129
  onMutate?.();
128
130
  });
129
131
  toast.promise(promise, {
130
- loading: 'Removing administrator…',
131
- success: 'Administrator removed.',
132
- error: 'Failed to remove administrator.',
132
+ loading: t('sheet.adminRemoving'),
133
+ success: t('sheet.adminRemoved'),
134
+ error: t('sheet.adminRemoveError'),
133
135
  });
134
136
  try {
135
137
  await promise;
@@ -153,7 +155,7 @@ export function AdministratorsTab({
153
155
  refetch();
154
156
  onMutate?.();
155
157
  } catch {
156
- toast.error('Failed to add administrator.');
158
+ toast.error(t('sheet.adminAddError'));
157
159
  }
158
160
  }
159
161
 
@@ -320,7 +322,7 @@ export function AdministratorsTab({
320
322
  data: { user_id: user.userId, role: user.role },
321
323
  });
322
324
  } catch {
323
- toast.error('Failed to link user to enterprise.');
325
+ toast.error(t('sheet.adminLinkError'));
324
326
  }
325
327
  setCreateSheetOpen(false);
326
328
  refetch();
@@ -22,6 +22,7 @@ import { formatDate } from '@/lib/format-date';
22
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
23
  import { CalendarDays, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
24
24
  import { useRouter } from 'next/navigation';
25
+ import { useTranslations } from 'next-intl';
25
26
  import { useCallback, useState } from 'react';
26
27
  import { toast } from 'sonner';
27
28
  import { EnterpriseClassCreateSheet } from './enterprise-class-create-sheet';
@@ -93,6 +94,7 @@ export function ClassesTab({
93
94
  }) {
94
95
  const router = useRouter();
95
96
  const { request, currentLocaleCode, getSettingValue } = useApp();
97
+ const t = useTranslations('lms.EnterpriseDetailPage.relatedClasses');
96
98
 
97
99
  const [search, setSearch] = useState('');
98
100
  const [statusFilter, setStatusFilter] = useState<
@@ -144,11 +146,11 @@ export function ClassesTab({
144
146
  url: `/lms/enterprise/${enterpriseId}/classes/${classGroupId}`,
145
147
  method: 'DELETE',
146
148
  });
147
- toast.success('Class removed from enterprise.');
149
+ toast.success(t('messages.removeSuccess'));
148
150
  refetch();
149
151
  onMutate?.();
150
152
  } catch {
151
- toast.error('Failed to remove class.');
153
+ toast.error(t('messages.removeError'));
152
154
  }
153
155
  }
154
156
 
@@ -168,7 +170,7 @@ export function ClassesTab({
168
170
  refetch();
169
171
  onMutate?.();
170
172
  } catch {
171
- toast.error('Failed to add class.');
173
+ toast.error(t('messages.addError'));
172
174
  }
173
175
  }
174
176
 
@@ -180,7 +182,7 @@ export function ClassesTab({
180
182
  setSearch(v);
181
183
  setPage(1);
182
184
  }}
183
- searchPlaceholder="Search by code or course..."
185
+ searchPlaceholder={t('searchPlaceholder')}
184
186
  filters={
185
187
  <Select
186
188
  value={statusFilter}
@@ -190,19 +192,19 @@ export function ClassesTab({
190
192
  }}
191
193
  >
192
194
  <SelectTrigger className="w-44">
193
- <SelectValue placeholder="All statuses" />
195
+ <SelectValue placeholder={t('filters.allStatuses')} />
194
196
  </SelectTrigger>
195
197
  <SelectContent>
196
- <SelectItem value="all">All statuses</SelectItem>
197
- <SelectItem value="open">Open</SelectItem>
198
- <SelectItem value="ongoing">Ongoing</SelectItem>
199
- <SelectItem value="completed">Completed</SelectItem>
200
- <SelectItem value="cancelled">Cancelled</SelectItem>
198
+ <SelectItem value="all">{t('filters.allStatuses')}</SelectItem>
199
+ <SelectItem value="open">{t('status.open')}</SelectItem>
200
+ <SelectItem value="ongoing">{t('status.ongoing')}</SelectItem>
201
+ <SelectItem value="completed">{t('status.completed')}</SelectItem>
202
+ <SelectItem value="cancelled">{t('status.cancelled')}</SelectItem>
201
203
  </SelectContent>
202
204
  </Select>
203
205
  }
204
- pickerPlaceholder="Select a class..."
205
- pickerEntityLabel="class"
206
+ pickerPlaceholder={t('pickerPlaceholder')}
207
+ pickerEntityLabel={t('pickerEntityLabel')}
206
208
  pickerValue={pickerValue}
207
209
  onPickerChange={(value, option) => {
208
210
  setPickerValue(value);
@@ -236,9 +238,9 @@ export function ClassesTab({
236
238
  </div>
237
239
  )}
238
240
  onCreateNew={() => setCreateSheetOpen(true)}
239
- createLabel="Create new class"
241
+ createLabel={t('actions.create')}
240
242
  onAdd={handleAdd}
241
- addLabel="Add"
243
+ addLabel={t('actions.add')}
242
244
  addDisabled={!pickerClass}
243
245
  currentPage={page}
244
246
  pageSize={pageSize}
@@ -253,10 +255,10 @@ export function ClassesTab({
253
255
  <div className="flex flex-col items-center justify-center py-16 text-center">
254
256
  <CalendarDays className="mb-4 h-10 w-10 text-muted-foreground/30" />
255
257
  <p className="text-sm font-medium text-muted-foreground">
256
- No classes found.
258
+ {t('empty.title')}
257
259
  </p>
258
260
  <p className="mt-1 text-xs text-muted-foreground/60">
259
- Adjust the search or status filter to see results.
261
+ {t('empty.description')}
260
262
  </p>
261
263
  </div>
262
264
  ) : (
@@ -264,10 +266,10 @@ export function ClassesTab({
264
266
  <Table>
265
267
  <TableHeader>
266
268
  <TableRow>
267
- <TableHead>Class</TableHead>
268
- <TableHead>Status</TableHead>
269
- <TableHead>Period</TableHead>
270
- <TableHead className="text-center">Capacity</TableHead>
269
+ <TableHead>{t('table.class')}</TableHead>
270
+ <TableHead>{t('table.status')}</TableHead>
271
+ <TableHead>{t('table.period')}</TableHead>
272
+ <TableHead className="text-center">{t('table.capacity')}</TableHead>
271
273
  <TableHead className="w-10" />
272
274
  </TableRow>
273
275
  </TableHeader>
@@ -77,7 +77,7 @@ export function EnterpriseCourseCreateSheet({
77
77
  enterpriseId,
78
78
  onCreated,
79
79
  }: EnterpriseCourseCreateSheetProps) {
80
- const t = useTranslations('lms.CoursesPage');
80
+ const t = useTranslations('lms.EnterprisePage');
81
81
  const { request } = useApp();
82
82
  const [saving, setSaving] = useState(false);
83
83
 
@@ -159,11 +159,11 @@ export function EnterpriseCourseCreateSheet({
159
159
  data: { course_id: created.id },
160
160
  });
161
161
 
162
- toast.success(t('toasts.courseCreated'));
162
+ toast.success(t('form.toasts.courseCreated'));
163
163
  onCreated?.();
164
164
  onOpenChange(false);
165
165
  } catch {
166
- toast.error('Não foi possível criar o curso.');
166
+ toast.error(t('form.toasts.courseCreateError'));
167
167
  } finally {
168
168
  setSaving(false);
169
169
  }
@@ -22,6 +22,7 @@ import { formatDate } from '@/lib/format-date';
22
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
23
  import { BookOpen, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
24
24
  import { useRouter } from 'next/navigation';
25
+ import { useTranslations } from 'next-intl';
25
26
  import { useCallback, useState } from 'react';
26
27
  import { toast } from 'sonner';
27
28
  import { EnterpriseCourseCreateSheet } from './enterprise-course-create-sheet';
@@ -89,6 +90,7 @@ export function CoursesTab({
89
90
  }) {
90
91
  const router = useRouter();
91
92
  const { request, getSettingValue, currentLocaleCode } = useApp();
93
+ const t = useTranslations('lms.EnterpriseDetailPage.relatedCourses');
92
94
 
93
95
  const [search, setSearch] = useState('');
94
96
  const [statusFilter, setStatusFilter] = useState<
@@ -142,11 +144,11 @@ export function CoursesTab({
142
144
  url: `/lms/enterprise/${enterpriseId}/courses/${courseId}`,
143
145
  method: 'DELETE',
144
146
  });
145
- toast.success('Course removed from enterprise.');
147
+ toast.success(t('messages.removeSuccess'));
146
148
  refetch();
147
149
  onMutate?.();
148
150
  } catch {
149
- toast.error('Failed to remove course.');
151
+ toast.error(t('messages.removeError'));
150
152
  }
151
153
  }
152
154
 
@@ -163,7 +165,7 @@ export function CoursesTab({
163
165
  refetch();
164
166
  onMutate?.();
165
167
  } catch {
166
- toast.error('Failed to add course.');
168
+ toast.error(t('messages.addError'));
167
169
  }
168
170
  }
169
171
 
@@ -175,7 +177,7 @@ export function CoursesTab({
175
177
  setSearch(v);
176
178
  setPage(1);
177
179
  }}
178
- searchPlaceholder="Search by course name..."
180
+ searchPlaceholder={t('searchPlaceholder')}
179
181
  filters={
180
182
  <Select
181
183
  value={statusFilter}
@@ -185,18 +187,18 @@ export function CoursesTab({
185
187
  }}
186
188
  >
187
189
  <SelectTrigger className="w-40">
188
- <SelectValue placeholder="All statuses" />
190
+ <SelectValue placeholder={t('filters.allStatuses')} />
189
191
  </SelectTrigger>
190
192
  <SelectContent>
191
- <SelectItem value="all">All statuses</SelectItem>
192
- <SelectItem value="published">Published</SelectItem>
193
- <SelectItem value="draft">Draft</SelectItem>
194
- <SelectItem value="archived">Archived</SelectItem>
193
+ <SelectItem value="all">{t('filters.allStatuses')}</SelectItem>
194
+ <SelectItem value="published">{t('status.published')}</SelectItem>
195
+ <SelectItem value="draft">{t('status.draft')}</SelectItem>
196
+ <SelectItem value="archived">{t('status.archived')}</SelectItem>
195
197
  </SelectContent>
196
198
  </Select>
197
199
  }
198
- pickerPlaceholder="Select a course..."
199
- pickerEntityLabel="course"
200
+ pickerPlaceholder={t('pickerPlaceholder')}
201
+ pickerEntityLabel={t('pickerEntityLabel')}
200
202
  pickerValue={pickerValue}
201
203
  onPickerChange={(value, option) => {
202
204
  setPickerValue(value);
@@ -228,9 +230,9 @@ export function CoursesTab({
228
230
  </div>
229
231
  )}
230
232
  onCreateNew={() => setCreateSheetOpen(true)}
231
- createLabel="Create new course"
233
+ createLabel={t('actions.create')}
232
234
  onAdd={handleAdd}
233
- addLabel="Add"
235
+ addLabel={t('actions.add')}
234
236
  addDisabled={!pickerCourse}
235
237
  currentPage={page}
236
238
  pageSize={pageSize}
@@ -245,10 +247,10 @@ export function CoursesTab({
245
247
  <div className="flex flex-col items-center justify-center py-16 text-center">
246
248
  <BookOpen className="mb-4 h-10 w-10 text-muted-foreground/30" />
247
249
  <p className="text-sm font-medium text-muted-foreground">
248
- No courses found.
250
+ {t('empty.title')}
249
251
  </p>
250
252
  <p className="mt-1 text-xs text-muted-foreground/60">
251
- Adjust the search or status filter to see results.
253
+ {t('empty.description')}
252
254
  </p>
253
255
  </div>
254
256
  ) : (
@@ -256,10 +258,10 @@ export function CoursesTab({
256
258
  <Table>
257
259
  <TableHeader>
258
260
  <TableRow>
259
- <TableHead>Course</TableHead>
260
- <TableHead>Modality</TableHead>
261
- <TableHead>Status</TableHead>
262
- <TableHead>Contracted</TableHead>
261
+ <TableHead>{t('table.course')}</TableHead>
262
+ <TableHead>{t('table.modality')}</TableHead>
263
+ <TableHead>{t('table.status')}</TableHead>
264
+ <TableHead>{t('table.contracted')}</TableHead>
263
265
  <TableHead className="w-10" />
264
266
  </TableRow>
265
267
  </TableHeader>