@hed-hog/lms 0.0.261 → 0.0.266
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/data/menu.yaml +34 -1
- package/hedhog/frontend/app/courses/page.tsx.ejs +220 -183
- package/hedhog/frontend/app/exams/page.tsx.ejs +1122 -0
- package/hedhog/frontend/app/training/page.tsx.ejs +1164 -0
- package/hedhog/frontend/messages/en.json +469 -1
- package/hedhog/frontend/messages/pt.json +469 -1
- package/hedhog/table/exams.yaml +3 -0
- package/hedhog/table/training.yaml +3 -0
- package/package.json +5 -5
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
Users,
|
|
81
81
|
X,
|
|
82
82
|
} from 'lucide-react';
|
|
83
|
+
import { useTranslations } from 'next-intl';
|
|
83
84
|
import { usePathname, useRouter } from 'next/navigation';
|
|
84
85
|
import { useEffect, useMemo, useState } from 'react';
|
|
85
86
|
import { Controller, useForm } from 'react-hook-form';
|
|
@@ -117,71 +118,54 @@ interface Curso {
|
|
|
117
118
|
|
|
118
119
|
// ── Schema ───────────────────────────────────────────────────────────────────
|
|
119
120
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.string()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.min(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
const createCursoSchema = (t: (key: string) => string) =>
|
|
122
|
+
z.object({
|
|
123
|
+
codigo: z
|
|
124
|
+
.string()
|
|
125
|
+
.min(2, t('form.validation.codeMinLength'))
|
|
126
|
+
.max(16, t('form.validation.codeMaxLength'))
|
|
127
|
+
.regex(/^[A-Z0-9-]+$/i, t('form.validation.codePattern')),
|
|
128
|
+
nomeInterno: z.string().min(3, t('form.validation.internalNameMinLength')),
|
|
129
|
+
tituloComercial: z
|
|
130
|
+
.string()
|
|
131
|
+
.min(3, t('form.validation.commercialTitleMinLength')),
|
|
132
|
+
descricao: z.string().min(10, t('form.validation.descriptionMinLength')),
|
|
133
|
+
nivel: z.enum(['iniciante', 'intermediario', 'avancado'], {
|
|
134
|
+
errorMap: () => ({ message: t('form.validation.levelRequired') }),
|
|
135
|
+
}),
|
|
136
|
+
status: z.enum(['ativo', 'rascunho', 'arquivado'], {
|
|
137
|
+
errorMap: () => ({ message: t('form.validation.statusRequired') }),
|
|
138
|
+
}),
|
|
139
|
+
categorias: z
|
|
140
|
+
.array(z.string())
|
|
141
|
+
.min(1, t('form.validation.categoriesRequired')),
|
|
142
|
+
destaque: z.boolean(),
|
|
143
|
+
certificado: z.boolean(),
|
|
144
|
+
listado: z.boolean(),
|
|
145
|
+
});
|
|
144
146
|
|
|
145
|
-
type CursoForm = z.infer<typeof
|
|
147
|
+
type CursoForm = z.infer<ReturnType<typeof createCursoSchema>>;
|
|
146
148
|
|
|
147
149
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
148
150
|
|
|
149
151
|
const CATEGORIAS = [
|
|
150
|
-
'
|
|
151
|
-
'
|
|
152
|
-
'
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
const NIVEIS = [
|
|
161
|
-
{ value: 'iniciante', label: 'Iniciante' },
|
|
162
|
-
{ value: 'intermediario', label: 'Intermediario' },
|
|
163
|
-
{ value: 'avancado', label: 'Avancado' },
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const STATUS_OPTIONS = [
|
|
167
|
-
{ value: 'ativo', label: 'Ativo' },
|
|
168
|
-
{ value: 'rascunho', label: 'Rascunho' },
|
|
169
|
-
{ value: 'arquivado', label: 'Arquivado' },
|
|
170
|
-
];
|
|
152
|
+
'technology',
|
|
153
|
+
'design',
|
|
154
|
+
'management',
|
|
155
|
+
'marketing',
|
|
156
|
+
'finance',
|
|
157
|
+
'health',
|
|
158
|
+
'languages',
|
|
159
|
+
'law',
|
|
160
|
+
] as const;
|
|
171
161
|
|
|
172
162
|
const STATUS_MAP: Record<
|
|
173
163
|
string,
|
|
174
|
-
{
|
|
164
|
+
{ variant: 'default' | 'secondary' | 'outline' }
|
|
175
165
|
> = {
|
|
176
|
-
ativo: {
|
|
177
|
-
rascunho: {
|
|
178
|
-
arquivado: {
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const NIVEL_MAP: Record<string, string> = {
|
|
182
|
-
iniciante: 'Iniciante',
|
|
183
|
-
intermediario: 'Intermediario',
|
|
184
|
-
avancado: 'Avancado',
|
|
166
|
+
ativo: { variant: 'default' },
|
|
167
|
+
rascunho: { variant: 'secondary' },
|
|
168
|
+
arquivado: { variant: 'outline' },
|
|
185
169
|
};
|
|
186
170
|
|
|
187
171
|
// ── Seed Data ────────────────────────────────────────────────────────────────
|
|
@@ -196,7 +180,7 @@ const initialCursos: Curso[] = [
|
|
|
196
180
|
'Curso completo de React com hooks, context e patterns avancados para aplicacoes modernas',
|
|
197
181
|
nivel: 'avancado',
|
|
198
182
|
status: 'ativo',
|
|
199
|
-
categorias: ['
|
|
183
|
+
categorias: ['technology'],
|
|
200
184
|
destaque: true,
|
|
201
185
|
certificado: true,
|
|
202
186
|
listado: true,
|
|
@@ -212,7 +196,7 @@ const initialCursos: Curso[] = [
|
|
|
212
196
|
'Fundamentos de design de experiencia do usuario com ferramentas modernas e pesquisa',
|
|
213
197
|
nivel: 'iniciante',
|
|
214
198
|
status: 'ativo',
|
|
215
|
-
categorias: ['
|
|
199
|
+
categorias: ['design'],
|
|
216
200
|
destaque: false,
|
|
217
201
|
certificado: true,
|
|
218
202
|
listado: true,
|
|
@@ -228,7 +212,7 @@ const initialCursos: Curso[] = [
|
|
|
228
212
|
'Metodologias ageis para gestao de projetos incluindo Scrum, Kanban e SAFe em equipes',
|
|
229
213
|
nivel: 'intermediario',
|
|
230
214
|
status: 'ativo',
|
|
231
|
-
categorias: ['
|
|
215
|
+
categorias: ['management'],
|
|
232
216
|
destaque: true,
|
|
233
217
|
certificado: true,
|
|
234
218
|
listado: true,
|
|
@@ -244,7 +228,7 @@ const initialCursos: Curso[] = [
|
|
|
244
228
|
'Estrategias de marketing digital para negocios modernos incluindo SEO, SEM e redes sociais',
|
|
245
229
|
nivel: 'intermediario',
|
|
246
230
|
status: 'rascunho',
|
|
247
|
-
categorias: ['
|
|
231
|
+
categorias: ['marketing'],
|
|
248
232
|
destaque: false,
|
|
249
233
|
certificado: false,
|
|
250
234
|
listado: false,
|
|
@@ -260,7 +244,7 @@ const initialCursos: Curso[] = [
|
|
|
260
244
|
'Introducao a Python focado em ciencia de dados, analise estatistica e machine learning basico',
|
|
261
245
|
nivel: 'intermediario',
|
|
262
246
|
status: 'ativo',
|
|
263
|
-
categorias: ['
|
|
247
|
+
categorias: ['technology'],
|
|
264
248
|
destaque: false,
|
|
265
249
|
certificado: true,
|
|
266
250
|
listado: true,
|
|
@@ -276,7 +260,7 @@ const initialCursos: Curso[] = [
|
|
|
276
260
|
'Backend com Node.js, Express e bancos de dados relacionais e NoSQL para APIs robustas',
|
|
277
261
|
nivel: 'avancado',
|
|
278
262
|
status: 'ativo',
|
|
279
|
-
categorias: ['
|
|
263
|
+
categorias: ['technology'],
|
|
280
264
|
destaque: true,
|
|
281
265
|
certificado: true,
|
|
282
266
|
listado: true,
|
|
@@ -292,7 +276,7 @@ const initialCursos: Curso[] = [
|
|
|
292
276
|
'Aprenda a usar o Figma do zero para criar interfaces profissionais e prototipos interativos',
|
|
293
277
|
nivel: 'iniciante',
|
|
294
278
|
status: 'arquivado',
|
|
295
|
-
categorias: ['
|
|
279
|
+
categorias: ['design'],
|
|
296
280
|
destaque: false,
|
|
297
281
|
certificado: true,
|
|
298
282
|
listado: false,
|
|
@@ -308,7 +292,7 @@ const initialCursos: Curso[] = [
|
|
|
308
292
|
'Desenvolva habilidades de lideranca e comunicacao assertiva para ambientes corporativos',
|
|
309
293
|
nivel: 'iniciante',
|
|
310
294
|
status: 'ativo',
|
|
311
|
-
categorias: ['
|
|
295
|
+
categorias: ['management'],
|
|
312
296
|
destaque: false,
|
|
313
297
|
certificado: true,
|
|
314
298
|
listado: true,
|
|
@@ -324,7 +308,7 @@ const initialCursos: Curso[] = [
|
|
|
324
308
|
'Tecnicas avancadas de otimizacao para motores de busca, conteudo web e performance de sites',
|
|
325
309
|
nivel: 'avancado',
|
|
326
310
|
status: 'rascunho',
|
|
327
|
-
categorias: ['
|
|
311
|
+
categorias: ['marketing', 'technology'],
|
|
328
312
|
destaque: false,
|
|
329
313
|
certificado: false,
|
|
330
314
|
listado: false,
|
|
@@ -340,7 +324,7 @@ const initialCursos: Curso[] = [
|
|
|
340
324
|
'TypeScript aplicado em projetos reais com boas praticas, design patterns e testes',
|
|
341
325
|
nivel: 'intermediario',
|
|
342
326
|
status: 'ativo',
|
|
343
|
-
categorias: ['
|
|
327
|
+
categorias: ['technology'],
|
|
344
328
|
destaque: true,
|
|
345
329
|
certificado: true,
|
|
346
330
|
listado: true,
|
|
@@ -356,7 +340,7 @@ const initialCursos: Curso[] = [
|
|
|
356
340
|
'Como criar e manter um design system escalavel para grandes equipes de produto e engenharia',
|
|
357
341
|
nivel: 'avancado',
|
|
358
342
|
status: 'ativo',
|
|
359
|
-
categorias: ['
|
|
343
|
+
categorias: ['design', 'technology'],
|
|
360
344
|
destaque: false,
|
|
361
345
|
certificado: true,
|
|
362
346
|
listado: true,
|
|
@@ -372,7 +356,7 @@ const initialCursos: Curso[] = [
|
|
|
372
356
|
'Domine Excel com formulas avancadas, dashboards profissionais e analise de dados empresariais',
|
|
373
357
|
nivel: 'iniciante',
|
|
374
358
|
status: 'ativo',
|
|
375
|
-
categorias: ['
|
|
359
|
+
categorias: ['management', 'finance'],
|
|
376
360
|
destaque: false,
|
|
377
361
|
certificado: true,
|
|
378
362
|
listado: true,
|
|
@@ -388,7 +372,7 @@ const initialCursos: Curso[] = [
|
|
|
388
372
|
'Aprenda a gerenciar suas financas, investir e planejar sua aposentadoria de forma inteligente',
|
|
389
373
|
nivel: 'iniciante',
|
|
390
374
|
status: 'ativo',
|
|
391
|
-
categorias: ['
|
|
375
|
+
categorias: ['finance'],
|
|
392
376
|
destaque: false,
|
|
393
377
|
certificado: true,
|
|
394
378
|
listado: true,
|
|
@@ -404,7 +388,7 @@ const initialCursos: Curso[] = [
|
|
|
404
388
|
'Desenvolvimento de aplicativos moveis multiplataforma com Flutter e Dart do zero ao deploy',
|
|
405
389
|
nivel: 'intermediario',
|
|
406
390
|
status: 'ativo',
|
|
407
|
-
categorias: ['
|
|
391
|
+
categorias: ['technology'],
|
|
408
392
|
destaque: true,
|
|
409
393
|
certificado: true,
|
|
410
394
|
listado: true,
|
|
@@ -420,7 +404,7 @@ const initialCursos: Curso[] = [
|
|
|
420
404
|
'Conceitos essenciais de direito trabalhista para gestores de RH e empreendedores',
|
|
421
405
|
nivel: 'iniciante',
|
|
422
406
|
status: 'rascunho',
|
|
423
|
-
categorias: ['
|
|
407
|
+
categorias: ['law', 'management'],
|
|
424
408
|
destaque: false,
|
|
425
409
|
certificado: false,
|
|
426
410
|
listado: false,
|
|
@@ -440,12 +424,13 @@ const stagger = {
|
|
|
440
424
|
|
|
441
425
|
const fadeUp = {
|
|
442
426
|
hidden: { opacity: 0, y: 16 },
|
|
443
|
-
show: { opacity: 1, y: 0, transition: { duration: 0.35
|
|
444
|
-
};
|
|
427
|
+
show: { opacity: 1, y: 0, transition: { duration: 0.35 } },
|
|
428
|
+
} as const;
|
|
445
429
|
|
|
446
430
|
// ── Component ────────────────────────────────────────────────────────────────
|
|
447
431
|
|
|
448
432
|
export default function CursosPage() {
|
|
433
|
+
const t = useTranslations('lms.CoursesPage');
|
|
449
434
|
const pathname = usePathname();
|
|
450
435
|
const router = useRouter();
|
|
451
436
|
|
|
@@ -474,7 +459,7 @@ export default function CursosPage() {
|
|
|
474
459
|
const [currentPage, setCurrentPage] = useState(1);
|
|
475
460
|
|
|
476
461
|
const form = useForm<CursoForm>({
|
|
477
|
-
resolver: zodResolver(
|
|
462
|
+
resolver: zodResolver(createCursoSchema(t)),
|
|
478
463
|
defaultValues: {
|
|
479
464
|
codigo: '',
|
|
480
465
|
nomeInterno: '',
|
|
@@ -602,7 +587,7 @@ export default function CursosPage() {
|
|
|
602
587
|
setCursos((prev) =>
|
|
603
588
|
prev.map((c) => (c.id === editingCurso.id ? { ...c, ...data } : c))
|
|
604
589
|
);
|
|
605
|
-
toast.success('
|
|
590
|
+
toast.success(t('toasts.courseUpdated'));
|
|
606
591
|
setSaving(false);
|
|
607
592
|
setSheetOpen(false);
|
|
608
593
|
} else {
|
|
@@ -611,10 +596,10 @@ export default function CursosPage() {
|
|
|
611
596
|
id: newId,
|
|
612
597
|
...data,
|
|
613
598
|
alunosInscritos: 0,
|
|
614
|
-
criadoEm: new Date().toISOString().split('T')[0],
|
|
599
|
+
criadoEm: new Date().toISOString().split('T')[0] || '',
|
|
615
600
|
};
|
|
616
601
|
setCursos((prev) => [newCurso, ...prev]);
|
|
617
|
-
toast.success('
|
|
602
|
+
toast.success(t('toasts.courseCreated'));
|
|
618
603
|
setSaving(false);
|
|
619
604
|
setSheetOpen(false);
|
|
620
605
|
// Redirecionar para pagina dedicada do curso
|
|
@@ -632,7 +617,9 @@ export default function CursosPage() {
|
|
|
632
617
|
next.delete(cursoToDelete.id);
|
|
633
618
|
return next;
|
|
634
619
|
});
|
|
635
|
-
toast.success(
|
|
620
|
+
toast.success(
|
|
621
|
+
t('toasts.courseRemoved', { title: cursoToDelete.tituloComercial })
|
|
622
|
+
);
|
|
636
623
|
setCursoToDelete(null);
|
|
637
624
|
setDeleteDialogOpen(false);
|
|
638
625
|
}
|
|
@@ -641,7 +628,7 @@ export default function CursosPage() {
|
|
|
641
628
|
function bulkDelete() {
|
|
642
629
|
if (selectedIds.size === 0) return;
|
|
643
630
|
setCursos((prev) => prev.filter((c) => !selectedIds.has(c.id)));
|
|
644
|
-
toast.success(
|
|
631
|
+
toast.success(t('toasts.coursesRemoved', { count: selectedIds.size }));
|
|
645
632
|
setSelectedIds(new Set());
|
|
646
633
|
}
|
|
647
634
|
|
|
@@ -652,7 +639,7 @@ export default function CursosPage() {
|
|
|
652
639
|
selectedIds.has(c.id) ? { ...c, status: 'arquivado' as const } : c
|
|
653
640
|
)
|
|
654
641
|
);
|
|
655
|
-
toast.success(
|
|
642
|
+
toast.success(t('toasts.coursesArchived', { count: selectedIds.size }));
|
|
656
643
|
setSelectedIds(new Set());
|
|
657
644
|
}
|
|
658
645
|
|
|
@@ -667,21 +654,21 @@ export default function CursosPage() {
|
|
|
667
654
|
return (
|
|
668
655
|
<Page>
|
|
669
656
|
<PageHeader
|
|
670
|
-
title=
|
|
671
|
-
description=
|
|
657
|
+
title={t('title')}
|
|
658
|
+
description={t('description')}
|
|
672
659
|
breadcrumbs={[
|
|
673
660
|
{
|
|
674
|
-
label: '
|
|
661
|
+
label: t('breadcrumbs.home'),
|
|
675
662
|
href: '/',
|
|
676
663
|
},
|
|
677
664
|
{
|
|
678
|
-
label: '
|
|
665
|
+
label: t('breadcrumbs.courses'),
|
|
679
666
|
},
|
|
680
667
|
]}
|
|
681
668
|
actions={
|
|
682
669
|
<Button onClick={openCreateSheet} className="gap-2">
|
|
683
670
|
<Plus className="size-4" />
|
|
684
|
-
|
|
671
|
+
{t('actions.createCourse')}
|
|
685
672
|
</Button>
|
|
686
673
|
}
|
|
687
674
|
/>
|
|
@@ -695,7 +682,7 @@ export default function CursosPage() {
|
|
|
695
682
|
<div className="relative flex-1">
|
|
696
683
|
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
697
684
|
<Input
|
|
698
|
-
placeholder=
|
|
685
|
+
placeholder={t('filters.searchPlaceholder')}
|
|
699
686
|
value={busca}
|
|
700
687
|
onChange={(e) => setBusca(e.target.value)}
|
|
701
688
|
className="pl-9"
|
|
@@ -704,30 +691,44 @@ export default function CursosPage() {
|
|
|
704
691
|
<div className="flex flex-wrap items-center gap-2">
|
|
705
692
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
706
693
|
<Filter className="size-3.5" />
|
|
707
|
-
|
|
694
|
+
{t('filters.filtersLabel')}
|
|
708
695
|
</div>
|
|
709
696
|
<Select value={filtroStatus} onValueChange={setFiltroStatus}>
|
|
710
697
|
<SelectTrigger className="h-9 w-[130px] text-xs">
|
|
711
|
-
<SelectValue placeholder=
|
|
698
|
+
<SelectValue placeholder={t('status.active')} />
|
|
712
699
|
</SelectTrigger>
|
|
713
700
|
<SelectContent>
|
|
714
|
-
<SelectItem value="todos">
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
<SelectItem value="
|
|
701
|
+
<SelectItem value="todos">
|
|
702
|
+
{t('filters.allStatuses')}
|
|
703
|
+
</SelectItem>
|
|
704
|
+
<SelectItem value="ativo">
|
|
705
|
+
{t('status.active')}
|
|
706
|
+
</SelectItem>
|
|
707
|
+
<SelectItem value="rascunho">
|
|
708
|
+
{t('status.draft')}
|
|
709
|
+
</SelectItem>
|
|
710
|
+
<SelectItem value="arquivado">
|
|
711
|
+
{t('status.archived')}
|
|
712
|
+
</SelectItem>
|
|
718
713
|
</SelectContent>
|
|
719
714
|
</Select>
|
|
720
715
|
<Select value={filtroNivel} onValueChange={setFiltroNivel}>
|
|
721
716
|
<SelectTrigger className="h-9 w-[140px] text-xs">
|
|
722
|
-
<SelectValue placeholder=
|
|
717
|
+
<SelectValue placeholder={t('levels.beginner')} />
|
|
723
718
|
</SelectTrigger>
|
|
724
719
|
<SelectContent>
|
|
725
|
-
<SelectItem value="todos">
|
|
726
|
-
|
|
720
|
+
<SelectItem value="todos">
|
|
721
|
+
{t('filters.allLevels')}
|
|
722
|
+
</SelectItem>
|
|
723
|
+
<SelectItem value="iniciante">
|
|
724
|
+
{t('levels.beginner')}
|
|
725
|
+
</SelectItem>
|
|
727
726
|
<SelectItem value="intermediario">
|
|
728
|
-
|
|
727
|
+
{t('levels.intermediate')}
|
|
728
|
+
</SelectItem>
|
|
729
|
+
<SelectItem value="avancado">
|
|
730
|
+
{t('levels.advanced')}
|
|
729
731
|
</SelectItem>
|
|
730
|
-
<SelectItem value="avancado">Avancado</SelectItem>
|
|
731
732
|
</SelectContent>
|
|
732
733
|
</Select>
|
|
733
734
|
<Select
|
|
@@ -735,13 +736,15 @@ export default function CursosPage() {
|
|
|
735
736
|
onValueChange={setFiltroCategoria}
|
|
736
737
|
>
|
|
737
738
|
<SelectTrigger className="h-9 w-[140px] text-xs">
|
|
738
|
-
<SelectValue placeholder=
|
|
739
|
+
<SelectValue placeholder={t('categories.technology')} />
|
|
739
740
|
</SelectTrigger>
|
|
740
741
|
<SelectContent>
|
|
741
|
-
<SelectItem value="todos">
|
|
742
|
+
<SelectItem value="todos">
|
|
743
|
+
{t('filters.allCategories')}
|
|
744
|
+
</SelectItem>
|
|
742
745
|
{CATEGORIAS.map((cat) => (
|
|
743
746
|
<SelectItem key={cat} value={cat}>
|
|
744
|
-
{cat}
|
|
747
|
+
{t(`categories.${cat}`)}
|
|
745
748
|
</SelectItem>
|
|
746
749
|
))}
|
|
747
750
|
</SelectContent>
|
|
@@ -762,7 +765,7 @@ export default function CursosPage() {
|
|
|
762
765
|
}}
|
|
763
766
|
>
|
|
764
767
|
<X className="mr-1 size-3" />
|
|
765
|
-
|
|
768
|
+
{t('filters.clear')}
|
|
766
769
|
</Button>
|
|
767
770
|
)}
|
|
768
771
|
</div>
|
|
@@ -784,7 +787,7 @@ export default function CursosPage() {
|
|
|
784
787
|
<div className="flex items-center gap-3 rounded-lg border bg-muted/50 px-4 py-2.5">
|
|
785
788
|
<CheckSquare className="size-4 text-muted-foreground" />
|
|
786
789
|
<span className="text-sm font-medium">
|
|
787
|
-
{selectedIds.size}
|
|
790
|
+
{t('bulkActions.selected', { count: selectedIds.size })}
|
|
788
791
|
</span>
|
|
789
792
|
<Separator orientation="vertical" className="h-5" />
|
|
790
793
|
<Button
|
|
@@ -794,7 +797,7 @@ export default function CursosPage() {
|
|
|
794
797
|
onClick={bulkArchive}
|
|
795
798
|
>
|
|
796
799
|
<Archive className="size-3" />
|
|
797
|
-
|
|
800
|
+
{t('bulkActions.archive')}
|
|
798
801
|
</Button>
|
|
799
802
|
<Button
|
|
800
803
|
variant="outline"
|
|
@@ -803,7 +806,7 @@ export default function CursosPage() {
|
|
|
803
806
|
onClick={bulkDelete}
|
|
804
807
|
>
|
|
805
808
|
<Trash2 className="size-3" />
|
|
806
|
-
|
|
809
|
+
{t('bulkActions.delete')}
|
|
807
810
|
</Button>
|
|
808
811
|
<Button
|
|
809
812
|
variant="ghost"
|
|
@@ -811,7 +814,7 @@ export default function CursosPage() {
|
|
|
811
814
|
className="ml-auto h-7 text-xs"
|
|
812
815
|
onClick={() => setSelectedIds(new Set())}
|
|
813
816
|
>
|
|
814
|
-
|
|
817
|
+
{t('bulkActions.clearSelection')}
|
|
815
818
|
</Button>
|
|
816
819
|
</div>
|
|
817
820
|
</motion.div>
|
|
@@ -852,20 +855,22 @@ export default function CursosPage() {
|
|
|
852
855
|
<Checkbox
|
|
853
856
|
checked={allPageSelected}
|
|
854
857
|
onCheckedChange={toggleSelectAll}
|
|
855
|
-
aria-label=
|
|
858
|
+
aria-label={t('table.selectAll')}
|
|
856
859
|
/>
|
|
857
860
|
</TableHead>
|
|
858
|
-
<TableHead className="w-[100px]">
|
|
859
|
-
|
|
861
|
+
<TableHead className="w-[100px]">
|
|
862
|
+
{t('table.headers.code')}
|
|
863
|
+
</TableHead>
|
|
864
|
+
<TableHead>{t('table.headers.title')}</TableHead>
|
|
860
865
|
<TableHead className="hidden md:table-cell">
|
|
861
|
-
|
|
866
|
+
{t('table.headers.level')}
|
|
862
867
|
</TableHead>
|
|
863
|
-
<TableHead>
|
|
868
|
+
<TableHead>{t('table.headers.status')}</TableHead>
|
|
864
869
|
<TableHead className="hidden lg:table-cell">
|
|
865
|
-
|
|
870
|
+
{t('table.headers.categories')}
|
|
866
871
|
</TableHead>
|
|
867
872
|
<TableHead className="hidden sm:table-cell text-right">
|
|
868
|
-
|
|
873
|
+
{t('table.headers.students')}
|
|
869
874
|
</TableHead>
|
|
870
875
|
<TableHead className="w-[50px]" />
|
|
871
876
|
</TableRow>
|
|
@@ -889,7 +894,9 @@ export default function CursosPage() {
|
|
|
889
894
|
<Checkbox
|
|
890
895
|
checked={selectedIds.has(curso.id)}
|
|
891
896
|
onCheckedChange={() => toggleSelect(curso.id)}
|
|
892
|
-
aria-label={
|
|
897
|
+
aria-label={t('table.selectCourse', {
|
|
898
|
+
title: curso.tituloComercial,
|
|
899
|
+
})}
|
|
893
900
|
/>
|
|
894
901
|
</TableCell>
|
|
895
902
|
<TableCell>
|
|
@@ -912,12 +919,20 @@ export default function CursosPage() {
|
|
|
912
919
|
variant="outline"
|
|
913
920
|
className="text-xs font-normal"
|
|
914
921
|
>
|
|
915
|
-
{
|
|
922
|
+
{t(
|
|
923
|
+
`levels.${curso.nivel === 'iniciante' ? 'beginner' : curso.nivel === 'intermediario' ? 'intermediate' : 'advanced'}`
|
|
924
|
+
)}
|
|
916
925
|
</Badge>
|
|
917
926
|
</TableCell>
|
|
918
927
|
<TableCell>
|
|
919
|
-
<Badge
|
|
920
|
-
{
|
|
928
|
+
<Badge
|
|
929
|
+
variant={
|
|
930
|
+
STATUS_MAP[curso.status]?.variant || 'default'
|
|
931
|
+
}
|
|
932
|
+
>
|
|
933
|
+
{t(
|
|
934
|
+
`status.${curso.status === 'ativo' ? 'active' : curso.status === 'rascunho' ? 'draft' : 'archived'}`
|
|
935
|
+
)}
|
|
921
936
|
</Badge>
|
|
922
937
|
</TableCell>
|
|
923
938
|
<TableCell className="hidden lg:table-cell">
|
|
@@ -928,7 +943,7 @@ export default function CursosPage() {
|
|
|
928
943
|
variant="secondary"
|
|
929
944
|
className="text-[10px] font-normal"
|
|
930
945
|
>
|
|
931
|
-
{cat}
|
|
946
|
+
{t(`categories.${cat}`)}
|
|
932
947
|
</Badge>
|
|
933
948
|
))}
|
|
934
949
|
</div>
|
|
@@ -945,7 +960,9 @@ export default function CursosPage() {
|
|
|
945
960
|
className="size-8 opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100"
|
|
946
961
|
>
|
|
947
962
|
<MoreHorizontal className="size-4" />
|
|
948
|
-
<span className="sr-only">
|
|
963
|
+
<span className="sr-only">
|
|
964
|
+
{t('table.actions.label')}
|
|
965
|
+
</span>
|
|
949
966
|
</Button>
|
|
950
967
|
</DropdownMenuTrigger>
|
|
951
968
|
<DropdownMenuContent
|
|
@@ -959,14 +976,14 @@ export default function CursosPage() {
|
|
|
959
976
|
className="gap-2"
|
|
960
977
|
>
|
|
961
978
|
<Eye className="size-3.5" />
|
|
962
|
-
|
|
979
|
+
{t('table.actions.viewDetails')}
|
|
963
980
|
</DropdownMenuItem>
|
|
964
981
|
<DropdownMenuItem
|
|
965
982
|
onClick={() => openEditSheet(curso)}
|
|
966
983
|
className="gap-2"
|
|
967
984
|
>
|
|
968
985
|
<Pencil className="size-3.5" />
|
|
969
|
-
|
|
986
|
+
{t('table.actions.edit')}
|
|
970
987
|
</DropdownMenuItem>
|
|
971
988
|
<DropdownMenuItem
|
|
972
989
|
onClick={() => {
|
|
@@ -974,13 +991,15 @@ export default function CursosPage() {
|
|
|
974
991
|
curso.codigo
|
|
975
992
|
);
|
|
976
993
|
toast.info(
|
|
977
|
-
|
|
994
|
+
t('toasts.codeCopied', {
|
|
995
|
+
code: curso.codigo,
|
|
996
|
+
})
|
|
978
997
|
);
|
|
979
998
|
}}
|
|
980
999
|
className="gap-2"
|
|
981
1000
|
>
|
|
982
1001
|
<Copy className="size-3.5" />
|
|
983
|
-
|
|
1002
|
+
{t('table.actions.copyCode')}
|
|
984
1003
|
</DropdownMenuItem>
|
|
985
1004
|
<DropdownMenuSeparator />
|
|
986
1005
|
<DropdownMenuItem
|
|
@@ -991,7 +1010,7 @@ export default function CursosPage() {
|
|
|
991
1010
|
className="gap-2 text-destructive focus:text-destructive"
|
|
992
1011
|
>
|
|
993
1012
|
<Trash2 className="size-3.5" />
|
|
994
|
-
|
|
1013
|
+
{t('table.actions.delete')}
|
|
995
1014
|
</DropdownMenuItem>
|
|
996
1015
|
</DropdownMenuContent>
|
|
997
1016
|
</DropdownMenu>
|
|
@@ -1005,10 +1024,10 @@ export default function CursosPage() {
|
|
|
1005
1024
|
<div className="flex flex-col items-center gap-2">
|
|
1006
1025
|
<BookOpen className="size-10 text-muted-foreground/40" />
|
|
1007
1026
|
<p className="text-sm font-medium text-muted-foreground">
|
|
1008
|
-
|
|
1027
|
+
{t('table.empty.title')}
|
|
1009
1028
|
</p>
|
|
1010
1029
|
<p className="text-xs text-muted-foreground/70">
|
|
1011
|
-
|
|
1030
|
+
{t('table.empty.description')}
|
|
1012
1031
|
</p>
|
|
1013
1032
|
</div>
|
|
1014
1033
|
</TableCell>
|
|
@@ -1022,22 +1041,22 @@ export default function CursosPage() {
|
|
|
1022
1041
|
{filteredCursos.length > 0 && (
|
|
1023
1042
|
<div className="flex flex-col items-center justify-between gap-3 border-t px-4 py-3 sm:flex-row">
|
|
1024
1043
|
<p className="text-xs text-muted-foreground">
|
|
1025
|
-
|
|
1044
|
+
{t('pagination.showing')}{' '}
|
|
1026
1045
|
<span className="font-medium text-foreground">
|
|
1027
1046
|
{(safePage - 1) * ITEMS_PER_PAGE + 1}
|
|
1028
1047
|
</span>{' '}
|
|
1029
|
-
|
|
1048
|
+
{t('pagination.to')}{' '}
|
|
1030
1049
|
<span className="font-medium text-foreground">
|
|
1031
1050
|
{Math.min(
|
|
1032
1051
|
safePage * ITEMS_PER_PAGE,
|
|
1033
1052
|
filteredCursos.length
|
|
1034
1053
|
)}
|
|
1035
1054
|
</span>{' '}
|
|
1036
|
-
|
|
1055
|
+
{t('pagination.of')}{' '}
|
|
1037
1056
|
<span className="font-medium text-foreground">
|
|
1038
1057
|
{filteredCursos.length}
|
|
1039
1058
|
</span>{' '}
|
|
1040
|
-
|
|
1059
|
+
{t('pagination.results')}
|
|
1041
1060
|
</p>
|
|
1042
1061
|
<div className="flex items-center gap-1">
|
|
1043
1062
|
<Button
|
|
@@ -1046,7 +1065,7 @@ export default function CursosPage() {
|
|
|
1046
1065
|
className="size-8"
|
|
1047
1066
|
disabled={safePage === 1}
|
|
1048
1067
|
onClick={() => setCurrentPage((p) => p - 1)}
|
|
1049
|
-
aria-label=
|
|
1068
|
+
aria-label={t('pagination.previousPage')}
|
|
1050
1069
|
>
|
|
1051
1070
|
<ChevronLeft className="size-4" />
|
|
1052
1071
|
</Button>
|
|
@@ -1067,7 +1086,7 @@ export default function CursosPage() {
|
|
|
1067
1086
|
className="size-8"
|
|
1068
1087
|
disabled={safePage === totalPages}
|
|
1069
1088
|
onClick={() => setCurrentPage((p) => p + 1)}
|
|
1070
|
-
aria-label=
|
|
1089
|
+
aria-label={t('pagination.nextPage')}
|
|
1071
1090
|
>
|
|
1072
1091
|
<ChevronRight className="size-4" />
|
|
1073
1092
|
</Button>
|
|
@@ -1085,28 +1104,28 @@ export default function CursosPage() {
|
|
|
1085
1104
|
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
1086
1105
|
<SheetHeader>
|
|
1087
1106
|
<SheetTitle>
|
|
1088
|
-
{editingCurso ? '
|
|
1107
|
+
{editingCurso ? t('form.title.edit') : t('form.title.create')}
|
|
1089
1108
|
</SheetTitle>
|
|
1090
1109
|
<SheetDescription>
|
|
1091
1110
|
{editingCurso
|
|
1092
|
-
? '
|
|
1093
|
-
: '
|
|
1111
|
+
? t('form.description.edit')
|
|
1112
|
+
: t('form.description.create')}
|
|
1094
1113
|
</SheetDescription>
|
|
1095
1114
|
</SheetHeader>
|
|
1096
1115
|
|
|
1097
1116
|
<form
|
|
1098
1117
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
1099
|
-
className="mt-6 flex flex-col gap-5
|
|
1118
|
+
className="mt-6 flex flex-col px-4 gap-5"
|
|
1100
1119
|
>
|
|
1101
1120
|
{/* Codigo */}
|
|
1102
1121
|
<Field data-invalid={!!form.formState.errors.codigo}>
|
|
1103
1122
|
<FieldLabel htmlFor="codigo">
|
|
1104
1123
|
<Hash className="size-3.5 text-muted-foreground" />
|
|
1105
|
-
|
|
1124
|
+
{t('form.fields.code.label')}
|
|
1106
1125
|
</FieldLabel>
|
|
1107
1126
|
<Input
|
|
1108
1127
|
id="codigo"
|
|
1109
|
-
placeholder=
|
|
1128
|
+
placeholder={t('form.fields.code.placeholder')}
|
|
1110
1129
|
className="uppercase"
|
|
1111
1130
|
{...form.register('codigo')}
|
|
1112
1131
|
/>
|
|
@@ -1114,16 +1133,18 @@ export default function CursosPage() {
|
|
|
1114
1133
|
<FieldError>{form.formState.errors.codigo.message}</FieldError>
|
|
1115
1134
|
)}
|
|
1116
1135
|
<FieldDescription>
|
|
1117
|
-
|
|
1136
|
+
{t('form.fields.code.description')}
|
|
1118
1137
|
</FieldDescription>
|
|
1119
1138
|
</Field>
|
|
1120
1139
|
|
|
1121
1140
|
{/* Nome Interno */}
|
|
1122
1141
|
<Field data-invalid={!!form.formState.errors.nomeInterno}>
|
|
1123
|
-
<FieldLabel htmlFor="nomeInterno">
|
|
1142
|
+
<FieldLabel htmlFor="nomeInterno">
|
|
1143
|
+
{t('form.fields.internalName.label')}
|
|
1144
|
+
</FieldLabel>
|
|
1124
1145
|
<Input
|
|
1125
1146
|
id="nomeInterno"
|
|
1126
|
-
placeholder=
|
|
1147
|
+
placeholder={t('form.fields.internalName.placeholder')}
|
|
1127
1148
|
{...form.register('nomeInterno')}
|
|
1128
1149
|
/>
|
|
1129
1150
|
{form.formState.errors.nomeInterno && (
|
|
@@ -1132,18 +1153,18 @@ export default function CursosPage() {
|
|
|
1132
1153
|
</FieldError>
|
|
1133
1154
|
)}
|
|
1134
1155
|
<FieldDescription>
|
|
1135
|
-
|
|
1156
|
+
{t('form.fields.internalName.description')}
|
|
1136
1157
|
</FieldDescription>
|
|
1137
1158
|
</Field>
|
|
1138
1159
|
|
|
1139
1160
|
{/* Titulo Comercial */}
|
|
1140
1161
|
<Field data-invalid={!!form.formState.errors.tituloComercial}>
|
|
1141
1162
|
<FieldLabel htmlFor="tituloComercial">
|
|
1142
|
-
|
|
1163
|
+
{t('form.fields.commercialTitle.label')}
|
|
1143
1164
|
</FieldLabel>
|
|
1144
1165
|
<Input
|
|
1145
1166
|
id="tituloComercial"
|
|
1146
|
-
placeholder=
|
|
1167
|
+
placeholder={t('form.fields.commercialTitle.placeholder')}
|
|
1147
1168
|
{...form.register('tituloComercial')}
|
|
1148
1169
|
/>
|
|
1149
1170
|
{form.formState.errors.tituloComercial && (
|
|
@@ -1155,10 +1176,12 @@ export default function CursosPage() {
|
|
|
1155
1176
|
|
|
1156
1177
|
{/* Descricao */}
|
|
1157
1178
|
<Field data-invalid={!!form.formState.errors.descricao}>
|
|
1158
|
-
<FieldLabel htmlFor="descricao">
|
|
1179
|
+
<FieldLabel htmlFor="descricao">
|
|
1180
|
+
{t('form.fields.description.label')}
|
|
1181
|
+
</FieldLabel>
|
|
1159
1182
|
<Textarea
|
|
1160
1183
|
id="descricao"
|
|
1161
|
-
placeholder=
|
|
1184
|
+
placeholder={t('form.fields.description.placeholder')}
|
|
1162
1185
|
rows={4}
|
|
1163
1186
|
{...form.register('descricao')}
|
|
1164
1187
|
/>
|
|
@@ -1172,21 +1195,27 @@ export default function CursosPage() {
|
|
|
1172
1195
|
{/* Nivel + Status side by side */}
|
|
1173
1196
|
<div className="grid grid-cols-2 gap-4">
|
|
1174
1197
|
<Field data-invalid={!!form.formState.errors.nivel}>
|
|
1175
|
-
<FieldLabel>
|
|
1198
|
+
<FieldLabel>{t('form.fields.level.label')}</FieldLabel>
|
|
1176
1199
|
<Controller
|
|
1177
1200
|
control={form.control}
|
|
1178
1201
|
name="nivel"
|
|
1179
1202
|
render={({ field }) => (
|
|
1180
1203
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
1181
1204
|
<SelectTrigger>
|
|
1182
|
-
<SelectValue
|
|
1205
|
+
<SelectValue
|
|
1206
|
+
placeholder={t('form.fields.level.placeholder')}
|
|
1207
|
+
/>
|
|
1183
1208
|
</SelectTrigger>
|
|
1184
1209
|
<SelectContent>
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1210
|
+
<SelectItem value="iniciante">
|
|
1211
|
+
{t('levels.beginner')}
|
|
1212
|
+
</SelectItem>
|
|
1213
|
+
<SelectItem value="intermediario">
|
|
1214
|
+
{t('levels.intermediate')}
|
|
1215
|
+
</SelectItem>
|
|
1216
|
+
<SelectItem value="avancado">
|
|
1217
|
+
{t('levels.advanced')}
|
|
1218
|
+
</SelectItem>
|
|
1190
1219
|
</SelectContent>
|
|
1191
1220
|
</Select>
|
|
1192
1221
|
)}
|
|
@@ -1197,21 +1226,27 @@ export default function CursosPage() {
|
|
|
1197
1226
|
</Field>
|
|
1198
1227
|
|
|
1199
1228
|
<Field data-invalid={!!form.formState.errors.status}>
|
|
1200
|
-
<FieldLabel>
|
|
1229
|
+
<FieldLabel>{t('form.fields.status.label')}</FieldLabel>
|
|
1201
1230
|
<Controller
|
|
1202
1231
|
control={form.control}
|
|
1203
1232
|
name="status"
|
|
1204
1233
|
render={({ field }) => (
|
|
1205
1234
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
1206
1235
|
<SelectTrigger>
|
|
1207
|
-
<SelectValue
|
|
1236
|
+
<SelectValue
|
|
1237
|
+
placeholder={t('form.fields.status.placeholder')}
|
|
1238
|
+
/>
|
|
1208
1239
|
</SelectTrigger>
|
|
1209
1240
|
<SelectContent>
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1241
|
+
<SelectItem value="ativo">
|
|
1242
|
+
{t('status.active')}
|
|
1243
|
+
</SelectItem>
|
|
1244
|
+
<SelectItem value="rascunho">
|
|
1245
|
+
{t('status.draft')}
|
|
1246
|
+
</SelectItem>
|
|
1247
|
+
<SelectItem value="arquivado">
|
|
1248
|
+
{t('status.archived')}
|
|
1249
|
+
</SelectItem>
|
|
1215
1250
|
</SelectContent>
|
|
1216
1251
|
</Select>
|
|
1217
1252
|
)}
|
|
@@ -1228,9 +1263,9 @@ export default function CursosPage() {
|
|
|
1228
1263
|
|
|
1229
1264
|
{/* Categorias - Multi select via checkboxes */}
|
|
1230
1265
|
<Field data-invalid={!!form.formState.errors.categorias}>
|
|
1231
|
-
<FieldLabel>
|
|
1266
|
+
<FieldLabel>{t('form.fields.categories.label')}</FieldLabel>
|
|
1232
1267
|
<FieldDescription>
|
|
1233
|
-
|
|
1268
|
+
{t('form.fields.categories.description')}
|
|
1234
1269
|
</FieldDescription>
|
|
1235
1270
|
<Controller
|
|
1236
1271
|
control={form.control}
|
|
@@ -1256,7 +1291,7 @@ export default function CursosPage() {
|
|
|
1256
1291
|
}
|
|
1257
1292
|
}}
|
|
1258
1293
|
/>
|
|
1259
|
-
{cat}
|
|
1294
|
+
{t(`categories.${cat}`)}
|
|
1260
1295
|
</label>
|
|
1261
1296
|
);
|
|
1262
1297
|
})}
|
|
@@ -1274,16 +1309,18 @@ export default function CursosPage() {
|
|
|
1274
1309
|
|
|
1275
1310
|
{/* Flags */}
|
|
1276
1311
|
<div className="flex flex-col gap-4">
|
|
1277
|
-
<p className="text-sm font-medium">
|
|
1312
|
+
<p className="text-sm font-medium">{t('form.flags.title')}</p>
|
|
1278
1313
|
<Controller
|
|
1279
1314
|
control={form.control}
|
|
1280
1315
|
name="destaque"
|
|
1281
1316
|
render={({ field }) => (
|
|
1282
1317
|
<div className="flex items-center justify-between rounded-md border px-4 py-3">
|
|
1283
1318
|
<div className="flex flex-col gap-0.5">
|
|
1284
|
-
<span className="text-sm font-medium">
|
|
1319
|
+
<span className="text-sm font-medium">
|
|
1320
|
+
{t('form.flags.featured.label')}
|
|
1321
|
+
</span>
|
|
1285
1322
|
<span className="text-xs text-muted-foreground">
|
|
1286
|
-
|
|
1323
|
+
{t('form.flags.featured.description')}
|
|
1287
1324
|
</span>
|
|
1288
1325
|
</div>
|
|
1289
1326
|
<Switch
|
|
@@ -1299,9 +1336,11 @@ export default function CursosPage() {
|
|
|
1299
1336
|
render={({ field }) => (
|
|
1300
1337
|
<div className="flex items-center justify-between rounded-md border px-4 py-3">
|
|
1301
1338
|
<div className="flex flex-col gap-0.5">
|
|
1302
|
-
<span className="text-sm font-medium">
|
|
1339
|
+
<span className="text-sm font-medium">
|
|
1340
|
+
{t('form.flags.certificate.label')}
|
|
1341
|
+
</span>
|
|
1303
1342
|
<span className="text-xs text-muted-foreground">
|
|
1304
|
-
|
|
1343
|
+
{t('form.flags.certificate.description')}
|
|
1305
1344
|
</span>
|
|
1306
1345
|
</div>
|
|
1307
1346
|
<Switch
|
|
@@ -1317,9 +1356,11 @@ export default function CursosPage() {
|
|
|
1317
1356
|
render={({ field }) => (
|
|
1318
1357
|
<div className="flex items-center justify-between rounded-md border px-4 py-3">
|
|
1319
1358
|
<div className="flex flex-col gap-0.5">
|
|
1320
|
-
<span className="text-sm font-medium">
|
|
1359
|
+
<span className="text-sm font-medium">
|
|
1360
|
+
{t('form.flags.listed.label')}
|
|
1361
|
+
</span>
|
|
1321
1362
|
<span className="text-xs text-muted-foreground">
|
|
1322
|
-
|
|
1363
|
+
{t('form.flags.listed.description')}
|
|
1323
1364
|
</span>
|
|
1324
1365
|
</div>
|
|
1325
1366
|
<Switch
|
|
@@ -1332,16 +1373,11 @@ export default function CursosPage() {
|
|
|
1332
1373
|
</div>
|
|
1333
1374
|
|
|
1334
1375
|
<SheetFooter className="mt-2 gap-2 pb-6">
|
|
1335
|
-
<Button
|
|
1336
|
-
type="button"
|
|
1337
|
-
variant="outline"
|
|
1338
|
-
onClick={() => setSheetOpen(false)}
|
|
1339
|
-
>
|
|
1340
|
-
Cancelar
|
|
1341
|
-
</Button>
|
|
1342
1376
|
<Button type="submit" disabled={saving} className="gap-2">
|
|
1343
1377
|
{saving && <Loader2 className="size-4 animate-spin" />}
|
|
1344
|
-
{editingCurso
|
|
1378
|
+
{editingCurso
|
|
1379
|
+
? t('form.actions.save')
|
|
1380
|
+
: t('form.actions.create')}
|
|
1345
1381
|
</Button>
|
|
1346
1382
|
</SheetFooter>
|
|
1347
1383
|
</form>
|
|
@@ -1354,10 +1390,10 @@ export default function CursosPage() {
|
|
|
1354
1390
|
<DialogHeader>
|
|
1355
1391
|
<DialogTitle className="flex items-center gap-2">
|
|
1356
1392
|
<AlertTriangle className="size-5 text-destructive" />
|
|
1357
|
-
|
|
1393
|
+
{t('deleteDialog.title')}
|
|
1358
1394
|
</DialogTitle>
|
|
1359
1395
|
<DialogDescription>
|
|
1360
|
-
|
|
1396
|
+
{t('deleteDialog.description')}{' '}
|
|
1361
1397
|
<strong className="text-foreground">
|
|
1362
1398
|
{cursoToDelete?.tituloComercial}
|
|
1363
1399
|
</strong>
|
|
@@ -1365,8 +1401,9 @@ export default function CursosPage() {
|
|
|
1365
1401
|
{cursoToDelete && cursoToDelete.alunosInscritos > 0 && (
|
|
1366
1402
|
<span className="mt-2 flex items-center gap-1.5 rounded-md bg-amber-50 px-3 py-2 text-xs font-medium text-amber-700">
|
|
1367
1403
|
<AlertTriangle className="size-3.5" />
|
|
1368
|
-
|
|
1369
|
-
|
|
1404
|
+
{t('deleteDialog.warning', {
|
|
1405
|
+
count: cursoToDelete.alunosInscritos,
|
|
1406
|
+
})}
|
|
1370
1407
|
</span>
|
|
1371
1408
|
)}
|
|
1372
1409
|
</DialogDescription>
|
|
@@ -1376,7 +1413,7 @@ export default function CursosPage() {
|
|
|
1376
1413
|
variant="outline"
|
|
1377
1414
|
onClick={() => setDeleteDialogOpen(false)}
|
|
1378
1415
|
>
|
|
1379
|
-
|
|
1416
|
+
{t('deleteDialog.actions.cancel')}
|
|
1380
1417
|
</Button>
|
|
1381
1418
|
<Button
|
|
1382
1419
|
variant="destructive"
|
|
@@ -1384,7 +1421,7 @@ export default function CursosPage() {
|
|
|
1384
1421
|
className="gap-2"
|
|
1385
1422
|
>
|
|
1386
1423
|
<Trash2 className="size-4" />
|
|
1387
|
-
|
|
1424
|
+
{t('deleteDialog.actions.delete')}
|
|
1388
1425
|
</Button>
|
|
1389
1426
|
</DialogFooter>
|
|
1390
1427
|
</DialogContent>
|