@hed-hog/lms 0.0.330 → 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 (31) hide show
  1. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
  2. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
  3. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +17 -17
  4. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
  5. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +3 -3
  6. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
  7. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
  8. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +71 -31
  9. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
  10. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
  11. package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
  12. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
  13. package/hedhog/frontend/app/instructors/page.tsx.ejs +71 -52
  14. package/hedhog/frontend/app/paths/page.tsx.ejs +11 -7
  15. package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
  16. package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
  17. package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
  18. package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
  19. package/hedhog/frontend/app/training/page.tsx.ejs +5 -5
  20. package/hedhog/frontend/messages/en.json +294 -46
  21. package/hedhog/frontend/messages/pt.json +289 -39
  22. package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
  23. package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
  24. package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
  25. package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
  26. package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
  27. package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
  28. package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
  29. package/hedhog/table/instructor_qualification.yaml +1 -1
  30. package/hedhog/table/instructor_skill.yaml +1 -1
  31. package/package.json +7 -7
@@ -18,36 +18,7 @@ import { Separator } from '@/components/ui/separator';
18
18
  const SHORTCUT_GROUPS: {
19
19
  heading: string;
20
20
  items: { keys: string[]; description: string }[];
21
- }[] = [
22
- {
23
- heading: 'Navegação',
24
- items: [
25
- { keys: ['↑', '↓'], description: 'Navegar entre itens' },
26
- { keys: ['→'], description: 'Expandir sessão selecionada' },
27
- { keys: ['←'], description: 'Recolher sessão / ir para pai' },
28
- { keys: ['Enter'], description: 'Focar primeiro campo do editor' },
29
- ],
30
- },
31
- {
32
- heading: 'Ações',
33
- items: [
34
- { keys: ['Ctrl', 'S'], description: 'Salvar formulário do painel' },
35
- { keys: ['Ctrl', 'N'], description: 'Criar nova sessão ou aula' },
36
- { keys: ['Ctrl', 'C'], description: 'Copiar item selecionado' },
37
- { keys: ['Ctrl', 'V'], description: 'Colar no contexto atual' },
38
- { keys: ['Ctrl', 'D'], description: 'Duplicar item' },
39
- { keys: ['Delete'], description: 'Excluir item(ns) selecionado(s)' },
40
- ],
41
- },
42
- {
43
- heading: 'Busca & Interface',
44
- items: [
45
- { keys: ['Ctrl', 'F'], description: 'Focar campo de busca' },
46
- { keys: ['Ctrl', '/'], description: 'Abrir ajuda de atalhos' },
47
- { keys: ['Esc'], description: 'Limpar busca / seleção / foco' },
48
- ],
49
- },
50
- ];
21
+ }[] = [];
51
22
 
52
23
  // ── Component ─────────────────────────────────────────────────────────────────
53
24
 
@@ -58,6 +29,75 @@ interface ShortcutsHelpProps {
58
29
 
59
30
  export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
60
31
  const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
32
+ const shortcutGroups = [
33
+ {
34
+ heading: t('groups.navigation.heading'),
35
+ items: [
36
+ {
37
+ keys: ['↑', '↓'],
38
+ description: t('groups.navigation.items.navigate'),
39
+ },
40
+ {
41
+ keys: ['→'],
42
+ description: t('groups.navigation.items.expand'),
43
+ },
44
+ {
45
+ keys: ['←'],
46
+ description: t('groups.navigation.items.collapse'),
47
+ },
48
+ {
49
+ keys: ['Enter'],
50
+ description: t('groups.navigation.items.focusEditor'),
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ heading: t('groups.actions.heading'),
56
+ items: [
57
+ {
58
+ keys: ['Ctrl', 'S'],
59
+ description: t('groups.actions.items.savePanel'),
60
+ },
61
+ {
62
+ keys: ['Ctrl', 'N'],
63
+ description: t('groups.actions.items.createItem'),
64
+ },
65
+ {
66
+ keys: ['Ctrl', 'C'],
67
+ description: t('groups.actions.items.copyItem'),
68
+ },
69
+ {
70
+ keys: ['Ctrl', 'V'],
71
+ description: t('groups.actions.items.pasteItem'),
72
+ },
73
+ {
74
+ keys: ['Ctrl', 'D'],
75
+ description: t('groups.actions.items.duplicateItem'),
76
+ },
77
+ {
78
+ keys: ['Delete'],
79
+ description: t('groups.actions.items.deleteItems'),
80
+ },
81
+ ],
82
+ },
83
+ {
84
+ heading: t('groups.search.heading'),
85
+ items: [
86
+ {
87
+ keys: ['Ctrl', 'F'],
88
+ description: t('groups.search.items.focusSearch'),
89
+ },
90
+ {
91
+ keys: ['Ctrl', '/'],
92
+ description: t('groups.search.items.openHelp'),
93
+ },
94
+ {
95
+ keys: ['Esc'],
96
+ description: t('groups.search.items.clearState'),
97
+ },
98
+ ],
99
+ },
100
+ ];
61
101
  return (
62
102
  <Dialog open={open} onOpenChange={onOpenChange}>
63
103
  <DialogContent className="max-w-sm">
@@ -69,7 +109,7 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
69
109
  </DialogHeader>
70
110
 
71
111
  <div className="flex flex-col gap-4 mt-1">
72
- {SHORTCUT_GROUPS.map((group, gi) => (
112
+ {shortcutGroups.map((group, gi) => (
73
113
  <div key={gi}>
74
114
  <p className="text-[0.65rem] font-semibold uppercase tracking-wider text-muted-foreground mb-2">
75
115
  {group.heading}
@@ -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
- No CRM account linked
68
+ {t('crm.noLinked')}
74
69
  </p>
75
70
  <p className="mt-1 max-w-xs text-xs text-muted-foreground/60">
76
- This enterprise account is not yet associated with a CRM commercial
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
- Link CRM account
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
- CRM Account #{crmAccountId}
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
- Open in CRM
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
- Account name
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
- CRM ID
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
- Data is read from the CRM. Use{' '}
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: 'Users',
146
+ title: t('kpis.users.label'),
154
147
  value: account.usersCount,
155
- description: 'Total linked users',
148
+ description: t('kpis.users.description'),
156
149
  icon: Users,
157
150
  },
158
151
  {
159
152
  key: 'students',
160
- title: 'Students',
153
+ title: t('kpis.students.label'),
161
154
  value: account.studentsCount,
162
- description: 'Active learners',
155
+ description: t('kpis.students.description'),
163
156
  icon: UserCheck,
164
157
  },
165
158
  {
166
159
  key: 'classes',
167
- title: 'Classes',
160
+ title: t('kpis.contractedClasses.label'),
168
161
  value: account.classesCount,
169
- description: 'Contracted classes',
162
+ description: t('kpis.contractedClasses.description'),
170
163
  icon: CalendarDays,
171
164
  },
172
165
  {
173
166
  key: 'courses',
174
- title: 'Courses',
167
+ title: t('kpis.courses.label'),
175
168
  value: account.coursesCount,
176
- description: 'Available courses',
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
- Slug:{' '}
179
+ {t('overview.slug')}{' '}
187
180
  <span className="font-mono text-foreground/70">{account.slug}</span>
188
181
  </span>
189
182
  <span>
190
- Created:{' '}
183
+ {t('overview.created')}{' '}
191
184
  {formatDate(account.createdAt, getSettingValue, currentLocaleCode)}
192
185
  </span>
193
186
  <span>
194
- Updated:{' '}
187
+ {t('overview.updated')}{' '}
195
188
  {formatDate(account.updatedAt, getSettingValue, currentLocaleCode)}
196
189
  </span>
197
190
  {account.portalEnabled && (
198
- <span className="text-primary/70">Portal enabled</span>
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: 'Home', href: '/' },
237
- { label: 'LMS', href: '/lms' },
238
- { label: 'Enterprise', href: '/lms/enterprise' },
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
- {STATUS_LABEL[account.status]}
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: 'Edit',
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">Overview</TabsTrigger>
272
- <TabsTrigger value="crm">CRM</TabsTrigger>
273
- <TabsTrigger value="classes">Classes</TabsTrigger>
274
- <TabsTrigger value="courses">Courses</TabsTrigger>
275
- <TabsTrigger value="students">Students</TabsTrigger>
276
- <TabsTrigger value="administrators">Administrators</TabsTrigger>
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">
@@ -106,7 +106,7 @@ export function EvaluationTopicFormSheet({
106
106
 
107
107
  return (
108
108
  <Sheet open={open} onOpenChange={onOpenChange}>
109
- <SheetContent side="right" className="w-full overflow-y-auto sm:max-w-md">
109
+ <SheetContent side="right" className="w-full overflow-y-auto sm:max-w-md">
110
110
  <SheetHeader>
111
111
  <SheetTitle>
112
112
  {editingItem ? t('form.editTitle') : t('form.createTitle')}
@@ -1179,7 +1179,9 @@ export default function ExamesPage() {
1179
1179
  </Field>
1180
1180
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
1181
1181
  <Field>
1182
- <FieldLabel htmlFor="primaryColor">Cor Primaria</FieldLabel>
1182
+ <FieldLabel htmlFor="primaryColor">
1183
+ {t('form.fields.primaryColor.label')}
1184
+ </FieldLabel>
1183
1185
  <Controller
1184
1186
  name="primaryColor"
1185
1187
  control={form.control}
@@ -1206,7 +1208,9 @@ export default function ExamesPage() {
1206
1208
  </Field>
1207
1209
 
1208
1210
  <Field>
1209
- <FieldLabel htmlFor="secondaryColor">Cor Secundaria</FieldLabel>
1211
+ <FieldLabel htmlFor="secondaryColor">
1212
+ {t('form.fields.secondaryColor.label')}
1213
+ </FieldLabel>
1210
1214
  <Controller
1211
1215
  name="secondaryColor"
1212
1216
  control={form.control}