@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
@@ -60,6 +60,7 @@ import { cn } from '@/lib/utils';
60
60
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
61
61
  import { zodResolver } from '@hookform/resolvers/zod';
62
62
  import { MoreHorizontal, Pencil, Plus, Sparkles, Trash2 } from 'lucide-react';
63
+ import { useTranslations } from 'next-intl';
63
64
  import { useEffect, useState } from 'react';
64
65
  import { useForm } from 'react-hook-form';
65
66
  import { toast } from 'sonner';
@@ -85,22 +86,26 @@ type InstructorSkillPaginatedResult = {
85
86
 
86
87
  // ─── Zod schema ───────────────────────────────────────────────────────────────
87
88
 
88
- const skillSchema = z.object({
89
- slug: z
90
- .string()
91
- .min(1, 'Obrigatório')
92
- .max(100, 'Máximo 100 caracteres')
93
- .regex(/^[a-z0-9-]+$/, 'Use apenas letras minúsculas, números e hifens'),
94
- namePt: z.string().min(1, 'Obrigatório').max(255, 'Máximo 255 caracteres'),
95
- nameEn: z
96
- .string()
97
- .max(255, 'Máximo 255 caracteres')
98
- .optional()
99
- .or(z.literal('')),
100
- status: z.enum(['active', 'inactive']).default('active'),
101
- });
102
-
103
- type SkillFormValues = z.infer<typeof skillSchema>;
89
+ const createSkillSchema = (t: ReturnType<typeof useTranslations>) =>
90
+ z.object({
91
+ slug: z
92
+ .string()
93
+ .min(1, t('form.validation.required'))
94
+ .max(100, t('form.validation.max100'))
95
+ .regex(/^[a-z0-9-]+$/, t('form.validation.slugPattern')),
96
+ namePt: z
97
+ .string()
98
+ .min(1, t('form.validation.required'))
99
+ .max(255, t('form.validation.max255')),
100
+ nameEn: z
101
+ .string()
102
+ .max(255, t('form.validation.max255'))
103
+ .optional()
104
+ .or(z.literal('')),
105
+ status: z.enum(['active', 'inactive']).default('active'),
106
+ });
107
+
108
+ type SkillFormValues = z.infer<ReturnType<typeof createSkillSchema>>;
104
109
 
105
110
  // ─── SkillFormSheet ────────────────────────────────────────────────────────────
106
111
 
@@ -118,10 +123,11 @@ function SkillFormSheet({
118
123
  onSaved,
119
124
  }: SkillFormSheetProps) {
120
125
  const { request } = useApp();
126
+ const t = useTranslations('lms.InstructorSkillsPage');
121
127
  const [isSaving, setIsSaving] = useState(false);
122
128
 
123
129
  const form = useForm<SkillFormValues>({
124
- resolver: zodResolver(skillSchema),
130
+ resolver: zodResolver(createSkillSchema(t)),
125
131
  defaultValues: {
126
132
  slug: '',
127
133
  namePt: '',
@@ -167,20 +173,20 @@ function SkillFormSheet({
167
173
  method: 'PATCH',
168
174
  data: payload,
169
175
  });
170
- toast.success('Skill atualizada com sucesso.');
176
+ toast.success(t('messages.updateSuccess'));
171
177
  } else {
172
178
  await request({
173
179
  url: '/lms/instructor-skills',
174
180
  method: 'POST',
175
181
  data: payload,
176
182
  });
177
- toast.success('Skill criada com sucesso.');
183
+ toast.success(t('messages.createSuccess'));
178
184
  }
179
185
 
180
186
  onSaved();
181
187
  onOpenChange(false);
182
188
  } catch {
183
- toast.error('Erro ao salvar skill. Tente novamente.');
189
+ toast.error(t('messages.saveError'));
184
190
  } finally {
185
191
  setIsSaving(false);
186
192
  }
@@ -188,9 +194,11 @@ function SkillFormSheet({
188
194
 
189
195
  return (
190
196
  <Sheet open={open} onOpenChange={onOpenChange}>
191
- <SheetContent className="w-full overflow-y-auto sm:max-w-md">
197
+ <SheetContent className="w-full overflow-y-auto sm:max-w-md">
192
198
  <SheetHeader>
193
- <SheetTitle>{skillToEdit ? 'Editar Skill' : 'Nova Skill'}</SheetTitle>
199
+ <SheetTitle>
200
+ {skillToEdit ? t('sheet.editTitle') : t('sheet.createTitle')}
201
+ </SheetTitle>
194
202
  </SheetHeader>
195
203
 
196
204
  <Form {...form}>
@@ -203,9 +211,9 @@ function SkillFormSheet({
203
211
  name="slug"
204
212
  render={({ field }) => (
205
213
  <FormItem>
206
- <FormLabel>Slug</FormLabel>
214
+ <FormLabel>{t('form.slug')}</FormLabel>
207
215
  <FormControl>
208
- <Input placeholder="ex: javascript, react-js" {...field} />
216
+ <Input placeholder={t('form.slugPlaceholder')} {...field} />
209
217
  </FormControl>
210
218
  <FormMessage />
211
219
  </FormItem>
@@ -217,9 +225,12 @@ function SkillFormSheet({
217
225
  name="namePt"
218
226
  render={({ field }) => (
219
227
  <FormItem>
220
- <FormLabel>Nome (PT)</FormLabel>
228
+ <FormLabel>{t('form.namePt')}</FormLabel>
221
229
  <FormControl>
222
- <Input placeholder="Nome em português" {...field} />
230
+ <Input
231
+ placeholder={t('form.namePtPlaceholder')}
232
+ {...field}
233
+ />
223
234
  </FormControl>
224
235
  <FormMessage />
225
236
  </FormItem>
@@ -231,10 +242,10 @@ function SkillFormSheet({
231
242
  name="nameEn"
232
243
  render={({ field }) => (
233
244
  <FormItem>
234
- <FormLabel>Nome (EN)</FormLabel>
245
+ <FormLabel>{t('form.nameEn')}</FormLabel>
235
246
  <FormControl>
236
247
  <Input
237
- placeholder="Name in English (optional)"
248
+ placeholder={t('form.nameEnPlaceholder')}
238
249
  {...field}
239
250
  />
240
251
  </FormControl>
@@ -248,16 +259,22 @@ function SkillFormSheet({
248
259
  name="status"
249
260
  render={({ field }) => (
250
261
  <FormItem>
251
- <FormLabel>Status</FormLabel>
262
+ <FormLabel>{t('form.status')}</FormLabel>
252
263
  <Select onValueChange={field.onChange} value={field.value}>
253
264
  <FormControl>
254
265
  <SelectTrigger className="w-full">
255
- <SelectValue placeholder="Selecione o status" />
266
+ <SelectValue
267
+ placeholder={t('form.statusPlaceholder')}
268
+ />
256
269
  </SelectTrigger>
257
270
  </FormControl>
258
271
  <SelectContent>
259
- <SelectItem value="active">Ativo</SelectItem>
260
- <SelectItem value="inactive">Inativo</SelectItem>
272
+ <SelectItem value="active">
273
+ {t('status.active')}
274
+ </SelectItem>
275
+ <SelectItem value="inactive">
276
+ {t('status.inactive')}
277
+ </SelectItem>
261
278
  </SelectContent>
262
279
  </Select>
263
280
  <FormMessage />
@@ -272,10 +289,10 @@ function SkillFormSheet({
272
289
  onClick={() => onOpenChange(false)}
273
290
  disabled={isSaving}
274
291
  >
275
- Cancelar
292
+ {t('actions.cancel')}
276
293
  </Button>
277
294
  <Button type="submit" disabled={isSaving}>
278
- {isSaving ? 'Salvando...' : 'Salvar'}
295
+ {isSaving ? t('actions.saving') : t('actions.save')}
279
296
  </Button>
280
297
  </div>
281
298
  </form>
@@ -289,6 +306,7 @@ function SkillFormSheet({
289
306
 
290
307
  export default function InstructorSkillsPage() {
291
308
  const { request } = useApp();
309
+ const t = useTranslations('lms.InstructorSkillsPage');
292
310
 
293
311
  const [page, setPage] = useState(1);
294
312
  const [pageSize, setPageSize] = useState(15);
@@ -372,12 +390,12 @@ export default function InstructorSkillsPage() {
372
390
  url: `/lms/instructor-skills/${skillToDelete.id}`,
373
391
  method: 'DELETE',
374
392
  });
375
- toast.success('Skill removida com sucesso.');
393
+ toast.success(t('messages.deleteSuccess'));
376
394
  setDeleteDialogOpen(false);
377
395
  setSkillToDelete(null);
378
396
  await refetchList();
379
397
  } catch {
380
- toast.error('Erro ao remover skill. Tente novamente.');
398
+ toast.error(t('messages.deleteError'));
381
399
  } finally {
382
400
  setIsDeleting(false);
383
401
  }
@@ -387,15 +405,15 @@ export default function InstructorSkillsPage() {
387
405
  <Page>
388
406
  <PageHeader
389
407
  breadcrumbs={[
390
- { label: 'Home', href: '/' },
391
- { label: 'LMS', href: '/lms' },
392
- { label: 'Skills de Instrutor' },
408
+ { label: t('breadcrumbs.home'), href: '/' },
409
+ { label: t('breadcrumbs.lms'), href: '/lms' },
410
+ { label: t('breadcrumbs.current') },
393
411
  ]}
394
- title="Skills de Instrutor"
395
- description="Gerencie o catálogo de skills disponíveis para instrutores."
412
+ title={t('title')}
413
+ description={t('description')}
396
414
  actions={[
397
415
  {
398
- label: 'Nova Skill',
416
+ label: t('actions.create'),
399
417
  onClick: openCreateSheet,
400
418
  icon: <Plus className="h-4 w-4" />,
401
419
  },
@@ -406,7 +424,7 @@ export default function InstructorSkillsPage() {
406
424
  searchQuery={searchInput}
407
425
  onSearchChange={(value) => setSearchInput(value)}
408
426
  onSearch={() => setPage(1)}
409
- placeholder="Buscar por slug ou nome..."
427
+ placeholder={t('filters.searchPlaceholder')}
410
428
  />
411
429
 
412
430
  {isLoading ? (
@@ -418,9 +436,9 @@ export default function InstructorSkillsPage() {
418
436
  ) : paginate.data.length === 0 ? (
419
437
  <EmptyState
420
438
  icon={<Sparkles className="h-12 w-12" />}
421
- title="Nenhuma skill encontrada"
422
- description="Crie uma nova skill ou ajuste os filtros de busca."
423
- actionLabel="Nova Skill"
439
+ title={t('empty.title')}
440
+ description={t('empty.description')}
441
+ actionLabel={t('actions.create')}
424
442
  actionIcon={<Plus className="mr-2 h-4 w-4" />}
425
443
  onAction={openCreateSheet}
426
444
  />
@@ -429,10 +447,10 @@ export default function InstructorSkillsPage() {
429
447
  <Table>
430
448
  <TableHeader>
431
449
  <TableRow>
432
- <TableHead>Slug</TableHead>
433
- <TableHead>Nome (PT)</TableHead>
434
- <TableHead>Nome (EN)</TableHead>
435
- <TableHead>Status</TableHead>
450
+ <TableHead>{t('table.slug')}</TableHead>
451
+ <TableHead>{t('table.namePt')}</TableHead>
452
+ <TableHead>{t('table.nameEn')}</TableHead>
453
+ <TableHead>{t('table.status')}</TableHead>
436
454
  <TableHead className="w-10" />
437
455
  </TableRow>
438
456
  </TableHeader>
@@ -464,7 +482,9 @@ export default function InstructorSkillsPage() {
464
482
  : 'border-gray-500/20 bg-gray-500/10 text-gray-600'
465
483
  )}
466
484
  >
467
- {skill.status === 'active' ? 'Ativo' : 'Inativo'}
485
+ {skill.status === 'active'
486
+ ? t('status.active')
487
+ : t('status.inactive')}
468
488
  </Badge>
469
489
  </TableCell>
470
490
  <TableCell>
@@ -477,7 +497,7 @@ export default function InstructorSkillsPage() {
477
497
  <DropdownMenuContent align="end">
478
498
  <DropdownMenuItem onClick={() => openEditSheet(skill)}>
479
499
  <Pencil className="mr-2 h-4 w-4" />
480
- Editar
500
+ {t('actions.edit')}
481
501
  </DropdownMenuItem>
482
502
  <DropdownMenuSeparator />
483
503
  <DropdownMenuItem
@@ -488,7 +508,7 @@ export default function InstructorSkillsPage() {
488
508
  }}
489
509
  >
490
510
  <Trash2 className="mr-2 h-4 w-4" />
491
- Excluir
511
+ {t('actions.delete')}
492
512
  </DropdownMenuItem>
493
513
  </DropdownMenuContent>
494
514
  </DropdownMenu>
@@ -521,23 +541,23 @@ export default function InstructorSkillsPage() {
521
541
  <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
522
542
  <AlertDialogContent>
523
543
  <AlertDialogHeader>
524
- <AlertDialogTitle>Excluir skill</AlertDialogTitle>
544
+ <AlertDialogTitle>{t('deleteDialog.title')}</AlertDialogTitle>
525
545
  <AlertDialogDescription>
526
- Tem certeza que deseja excluir a skill{' '}
527
- <strong>{skillToDelete?.slug}</strong>? Esta ação não pode ser
528
- desfeita.
546
+ {t('deleteDialog.description', {
547
+ slug: skillToDelete?.slug ?? '',
548
+ })}
529
549
  </AlertDialogDescription>
530
550
  </AlertDialogHeader>
531
551
  <div className="flex justify-end gap-2">
532
552
  <AlertDialogCancel disabled={isDeleting}>
533
- Cancelar
553
+ {t('actions.cancel')}
534
554
  </AlertDialogCancel>
535
555
  <AlertDialogAction
536
556
  onClick={handleDeleteConfirm}
537
557
  disabled={isDeleting}
538
558
  className="bg-red-600 hover:bg-red-700"
539
559
  >
540
- {isDeleting ? 'Excluindo...' : 'Excluir'}
560
+ {isDeleting ? t('actions.deleting') : t('actions.delete')}
541
561
  </AlertDialogAction>
542
562
  </div>
543
563
  </AlertDialogContent>
@@ -54,6 +54,7 @@ import {
54
54
  UserRoundPen,
55
55
  Users,
56
56
  } from 'lucide-react';
57
+ import { useTranslations } from 'next-intl';
57
58
  import { useEffect, useState } from 'react';
58
59
  import { toast } from 'sonner';
59
60
  import { InstructorFormSheet } from './_components/instructor-form-sheet';
@@ -89,6 +90,7 @@ function getAvatarUrl(avatarId?: number | null) {
89
90
 
90
91
  export default function InstructorsPage() {
91
92
  const { request } = useApp();
93
+ const t = useTranslations('lms.InstructorsPage');
92
94
 
93
95
  const [page, setPage] = useState(1);
94
96
  const [pageSize, setPageSize] = useState(12);
@@ -223,12 +225,12 @@ export default function InstructorsPage() {
223
225
  url: `/lms/instructors/${instructorToDelete.id}`,
224
226
  method: 'DELETE',
225
227
  });
226
- toast.success('Instrutor removido com sucesso.');
228
+ toast.success(t('messages.instructorRemovedSuccess'));
227
229
  setDeleteDialogOpen(false);
228
230
  setInstructorToDelete(null);
229
231
  await Promise.all([refetchList(), refetchStats()]);
230
232
  } catch {
231
- toast.error('Erro ao remover instrutor. Tente novamente.');
233
+ toast.error(t('messages.instructorRemoveError'));
232
234
  } finally {
233
235
  setIsDeleting(false);
234
236
  }