@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.
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -8
- package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
- package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
- package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
- package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +27 -27
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +8 -6
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +78 -36
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
- package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
- package/hedhog/frontend/app/instructors/page.tsx.ejs +75 -54
- package/hedhog/frontend/app/paths/page.tsx.ejs +11 -7
- package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
- package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
- package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
- package/hedhog/frontend/app/training/page.tsx.ejs +5 -5
- package/hedhog/frontend/messages/en.json +899 -45
- package/hedhog/frontend/messages/pt.json +894 -38
- package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
- package/hedhog/table/instructor_qualification.yaml +1 -1
- package/hedhog/table/instructor_skill.yaml +1 -1
- package/package.json +7 -7
package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs
CHANGED
|
@@ -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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
1001
|
+
toast.error(t('mutations.lesson.moveBatchError'));
|
|
985
1002
|
},
|
|
986
1003
|
});
|
|
987
1004
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
UserCheck,
|
|
21
21
|
Users,
|
|
22
22
|
} from 'lucide-react';
|
|
23
|
+
import { useTranslations } from 'next-intl';
|
|
23
24
|
import { notFound, useParams, useRouter } from 'next/navigation';
|
|
24
25
|
import { useState } from 'react';
|
|
25
26
|
import { AdministratorsTab } from '../_components/enterprise-administrators-tab';
|
|
@@ -44,13 +45,6 @@ const STATUS_VARIANT: Record<
|
|
|
44
45
|
suspended: 'destructive',
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
const STATUS_LABEL: Record<EnterpriseStatus, string> = {
|
|
48
|
-
active: 'Active',
|
|
49
|
-
trial: 'Trial',
|
|
50
|
-
inactive: 'Inactive',
|
|
51
|
-
suspended: 'Suspended',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
48
|
// ── CRM Tab ───────────────────────────────────────────────────────────────────
|
|
55
49
|
|
|
56
50
|
function CrmTab({
|
|
@@ -62,6 +56,7 @@ function CrmTab({
|
|
|
62
56
|
crmAccountName: string | null;
|
|
63
57
|
onLinkCrm?: () => void;
|
|
64
58
|
}) {
|
|
59
|
+
const t = useTranslations('lms.EnterpriseDetailPage');
|
|
65
60
|
const router = useRouter();
|
|
66
61
|
if (!crmAccountId || !crmAccountName) {
|
|
67
62
|
return (
|
|
@@ -70,11 +65,10 @@ function CrmTab({
|
|
|
70
65
|
<Link2 className="h-12 w-12" />
|
|
71
66
|
</div>
|
|
72
67
|
<p className="text-sm font-medium text-muted-foreground">
|
|
73
|
-
|
|
68
|
+
{t('crm.noLinked')}
|
|
74
69
|
</p>
|
|
75
70
|
<p className="mt-1 max-w-xs text-xs text-muted-foreground/60">
|
|
76
|
-
|
|
77
|
-
account. Link one to keep both records in sync.
|
|
71
|
+
{t('crm.noLinkedDesc')}
|
|
78
72
|
</p>
|
|
79
73
|
<Button
|
|
80
74
|
variant="outline"
|
|
@@ -83,7 +77,7 @@ function CrmTab({
|
|
|
83
77
|
onClick={onLinkCrm}
|
|
84
78
|
>
|
|
85
79
|
<Link2 className="mr-2 h-4 w-4" />
|
|
86
|
-
|
|
80
|
+
{t('crm.linkButton')}
|
|
87
81
|
</Button>
|
|
88
82
|
</div>
|
|
89
83
|
);
|
|
@@ -100,7 +94,7 @@ function CrmTab({
|
|
|
100
94
|
<div>
|
|
101
95
|
<p className="text-base font-semibold">{crmAccountName}</p>
|
|
102
96
|
<p className="text-xs text-muted-foreground">
|
|
103
|
-
|
|
97
|
+
{t('crm.accountRef', { id: crmAccountId })}
|
|
104
98
|
</p>
|
|
105
99
|
</div>
|
|
106
100
|
</div>
|
|
@@ -111,7 +105,7 @@ function CrmTab({
|
|
|
111
105
|
className="shrink-0"
|
|
112
106
|
>
|
|
113
107
|
<ExternalLink className="mr-2 h-4 w-4" />
|
|
114
|
-
|
|
108
|
+
{t('crm.openButton')}
|
|
115
109
|
</Button>
|
|
116
110
|
</div>
|
|
117
111
|
|
|
@@ -120,22 +114,20 @@ function CrmTab({
|
|
|
120
114
|
<dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
121
115
|
<div>
|
|
122
116
|
<dt className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
123
|
-
|
|
117
|
+
{t('crm.accountNameLabel')}
|
|
124
118
|
</dt>
|
|
125
119
|
<dd className="mt-1 text-sm">{crmAccountName}</dd>
|
|
126
120
|
</div>
|
|
127
121
|
<div>
|
|
128
122
|
<dt className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
129
|
-
|
|
123
|
+
{t('crm.crmIdLabel')}
|
|
130
124
|
</dt>
|
|
131
125
|
<dd className="mt-1 font-mono text-sm">#{crmAccountId}</dd>
|
|
132
126
|
</div>
|
|
133
127
|
</dl>
|
|
134
128
|
|
|
135
129
|
<p className="mt-5 text-xs text-muted-foreground/60">
|
|
136
|
-
|
|
137
|
-
<span className="font-medium">Open in CRM</span> to manage the
|
|
138
|
-
commercial account directly.
|
|
130
|
+
{t('crm.readNote')}
|
|
139
131
|
</p>
|
|
140
132
|
</CardContent>
|
|
141
133
|
</Card>
|
|
@@ -145,35 +137,36 @@ function CrmTab({
|
|
|
145
137
|
// ── Overview Tab ──────────────────────────────────────────────────────────────
|
|
146
138
|
|
|
147
139
|
function OverviewTab({ account }: { account: EnterpriseAccount }) {
|
|
140
|
+
const t = useTranslations('lms.EnterpriseDetailPage');
|
|
148
141
|
const { currentLocaleCode, getSettingValue } = useApp();
|
|
149
142
|
|
|
150
143
|
const kpiItems: KpiCardItem[] = [
|
|
151
144
|
{
|
|
152
145
|
key: 'users',
|
|
153
|
-
title: '
|
|
146
|
+
title: t('kpis.users.label'),
|
|
154
147
|
value: account.usersCount,
|
|
155
|
-
description: '
|
|
148
|
+
description: t('kpis.users.description'),
|
|
156
149
|
icon: Users,
|
|
157
150
|
},
|
|
158
151
|
{
|
|
159
152
|
key: 'students',
|
|
160
|
-
title: '
|
|
153
|
+
title: t('kpis.students.label'),
|
|
161
154
|
value: account.studentsCount,
|
|
162
|
-
description: '
|
|
155
|
+
description: t('kpis.students.description'),
|
|
163
156
|
icon: UserCheck,
|
|
164
157
|
},
|
|
165
158
|
{
|
|
166
159
|
key: 'classes',
|
|
167
|
-
title: '
|
|
160
|
+
title: t('kpis.contractedClasses.label'),
|
|
168
161
|
value: account.classesCount,
|
|
169
|
-
description: '
|
|
162
|
+
description: t('kpis.contractedClasses.description'),
|
|
170
163
|
icon: CalendarDays,
|
|
171
164
|
},
|
|
172
165
|
{
|
|
173
166
|
key: 'courses',
|
|
174
|
-
title: '
|
|
167
|
+
title: t('kpis.courses.label'),
|
|
175
168
|
value: account.coursesCount,
|
|
176
|
-
description: '
|
|
169
|
+
description: t('kpis.courses.description'),
|
|
177
170
|
icon: BookOpen,
|
|
178
171
|
},
|
|
179
172
|
];
|
|
@@ -183,19 +176,19 @@ function OverviewTab({ account }: { account: EnterpriseAccount }) {
|
|
|
183
176
|
<KpiCardsGrid items={kpiItems} />
|
|
184
177
|
<div className="flex flex-wrap items-center gap-x-6 gap-y-1 border-t pt-3 text-xs text-muted-foreground">
|
|
185
178
|
<span>
|
|
186
|
-
|
|
179
|
+
{t('overview.slug')}{' '}
|
|
187
180
|
<span className="font-mono text-foreground/70">{account.slug}</span>
|
|
188
181
|
</span>
|
|
189
182
|
<span>
|
|
190
|
-
|
|
183
|
+
{t('overview.created')}{' '}
|
|
191
184
|
{formatDate(account.createdAt, getSettingValue, currentLocaleCode)}
|
|
192
185
|
</span>
|
|
193
186
|
<span>
|
|
194
|
-
|
|
187
|
+
{t('overview.updated')}{' '}
|
|
195
188
|
{formatDate(account.updatedAt, getSettingValue, currentLocaleCode)}
|
|
196
189
|
</span>
|
|
197
190
|
{account.portalEnabled && (
|
|
198
|
-
<span className="text-primary/70">
|
|
191
|
+
<span className="text-primary/70">{t('overview.portalEnabled')}</span>
|
|
199
192
|
)}
|
|
200
193
|
</div>
|
|
201
194
|
</div>
|
|
@@ -207,6 +200,7 @@ function OverviewTab({ account }: { account: EnterpriseAccount }) {
|
|
|
207
200
|
export default function EnterpriseDetailPage() {
|
|
208
201
|
const { id } = useParams<{ id: string }>();
|
|
209
202
|
const { request } = useApp();
|
|
203
|
+
const t = useTranslations('lms.EnterpriseDetailPage');
|
|
210
204
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
211
205
|
|
|
212
206
|
const {
|
|
@@ -233,9 +227,9 @@ export default function EnterpriseDetailPage() {
|
|
|
233
227
|
<Page>
|
|
234
228
|
<PageHeader
|
|
235
229
|
breadcrumbs={[
|
|
236
|
-
{ label: '
|
|
237
|
-
{ label: '
|
|
238
|
-
{ label: '
|
|
230
|
+
{ label: t('breadcrumbs.home'), href: '/' },
|
|
231
|
+
{ label: t('breadcrumbs.lms'), href: '/lms' },
|
|
232
|
+
{ label: t('breadcrumbs.enterprise'), href: '/lms/enterprise' },
|
|
239
233
|
{
|
|
240
234
|
label: isLoading ? '…' : (account?.name ?? ''),
|
|
241
235
|
},
|
|
@@ -243,7 +237,7 @@ export default function EnterpriseDetailPage() {
|
|
|
243
237
|
extraContent={
|
|
244
238
|
account ? (
|
|
245
239
|
<Badge variant={STATUS_VARIANT[account.status]}>
|
|
246
|
-
{
|
|
240
|
+
{t(`status.${account.status}`)}
|
|
247
241
|
</Badge>
|
|
248
242
|
) : (
|
|
249
243
|
<Skeleton className="h-5 w-16 rounded-full" />
|
|
@@ -251,7 +245,7 @@ export default function EnterpriseDetailPage() {
|
|
|
251
245
|
}
|
|
252
246
|
actions={[
|
|
253
247
|
{
|
|
254
|
-
label: '
|
|
248
|
+
label: t('actions.edit'),
|
|
255
249
|
onClick: () => setIsSheetOpen(true),
|
|
256
250
|
icon: <Pencil className="h-4 w-4" />,
|
|
257
251
|
variant: 'outline',
|
|
@@ -268,12 +262,14 @@ export default function EnterpriseDetailPage() {
|
|
|
268
262
|
) : (
|
|
269
263
|
<Tabs defaultValue="overview">
|
|
270
264
|
<TabsList>
|
|
271
|
-
<TabsTrigger value="overview">
|
|
272
|
-
<TabsTrigger value="crm">
|
|
273
|
-
<TabsTrigger value="classes">
|
|
274
|
-
<TabsTrigger value="courses">
|
|
275
|
-
<TabsTrigger value="students">
|
|
276
|
-
<TabsTrigger value="administrators">
|
|
265
|
+
<TabsTrigger value="overview">{t('tabs.overview')}</TabsTrigger>
|
|
266
|
+
<TabsTrigger value="crm">{t('tabs.crm')}</TabsTrigger>
|
|
267
|
+
<TabsTrigger value="classes">{t('tabs.classes')}</TabsTrigger>
|
|
268
|
+
<TabsTrigger value="courses">{t('tabs.courses')}</TabsTrigger>
|
|
269
|
+
<TabsTrigger value="students">{t('tabs.students')}</TabsTrigger>
|
|
270
|
+
<TabsTrigger value="administrators">
|
|
271
|
+
{t('tabs.administrators')}
|
|
272
|
+
</TabsTrigger>
|
|
277
273
|
</TabsList>
|
|
278
274
|
|
|
279
275
|
<TabsContent value="overview" className="mt-6">
|
|
@@ -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('
|
|
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: '
|
|
108
|
-
success: '
|
|
109
|
-
error: '
|
|
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: '
|
|
131
|
-
success: '
|
|
132
|
-
error: '
|
|
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('
|
|
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('
|
|
325
|
+
toast.error(t('sheet.adminLinkError'));
|
|
324
326
|
}
|
|
325
327
|
setCreateSheetOpen(false);
|
|
326
328
|
refetch();
|