@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.
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +17 -17
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +3 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +71 -31
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
- 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/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
- package/hedhog/frontend/app/instructors/page.tsx.ejs +71 -52
- 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 +294 -46
- package/hedhog/frontend/messages/pt.json +289 -39
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import {
|
|
5
6
|
CircleDot,
|
|
6
7
|
CircleOff,
|
|
@@ -131,42 +132,34 @@ function formatFileSize(bytes: number): string {
|
|
|
131
132
|
|
|
132
133
|
const TYPE_CONFIG: Record<
|
|
133
134
|
LessonType,
|
|
134
|
-
{ icon: LucideIcon; color: string; bg: string;
|
|
135
|
+
{ icon: LucideIcon; color: string; bg: string; labelKey: string }
|
|
135
136
|
> = {
|
|
136
137
|
video: {
|
|
137
138
|
icon: Video,
|
|
138
139
|
color: 'text-blue-500',
|
|
139
140
|
bg: 'bg-blue-500/10',
|
|
140
|
-
|
|
141
|
+
labelKey: 'types.video',
|
|
141
142
|
},
|
|
142
143
|
post: {
|
|
143
144
|
icon: FileText,
|
|
144
145
|
color: 'text-emerald-500',
|
|
145
146
|
bg: 'bg-emerald-500/10',
|
|
146
|
-
|
|
147
|
+
labelKey: 'types.post',
|
|
147
148
|
},
|
|
148
149
|
questao: {
|
|
149
150
|
icon: HelpCircle,
|
|
150
151
|
color: 'text-amber-500',
|
|
151
152
|
bg: 'bg-amber-500/10',
|
|
152
|
-
|
|
153
|
+
labelKey: 'types.questao',
|
|
153
154
|
},
|
|
154
155
|
exercicio: {
|
|
155
156
|
icon: ClipboardList,
|
|
156
157
|
color: 'text-purple-500',
|
|
157
158
|
bg: 'bg-purple-500/10',
|
|
158
|
-
|
|
159
|
+
labelKey: 'types.exercicio',
|
|
159
160
|
},
|
|
160
161
|
};
|
|
161
162
|
|
|
162
|
-
const STATUS_LABELS: Record<LessonStatus, string> = {
|
|
163
|
-
preparada: 'Preparada',
|
|
164
|
-
gravada: 'Gravada',
|
|
165
|
-
editada: 'Editada',
|
|
166
|
-
finalizada: 'Finalizada',
|
|
167
|
-
publicada: 'Publicada',
|
|
168
|
-
};
|
|
169
|
-
|
|
170
163
|
const STATUS_COLORS: Record<LessonStatus, string> = {
|
|
171
164
|
preparada:
|
|
172
165
|
'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300',
|
|
@@ -180,13 +173,6 @@ const STATUS_COLORS: Record<LessonStatus, string> = {
|
|
|
180
173
|
'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300',
|
|
181
174
|
};
|
|
182
175
|
|
|
183
|
-
const VIDEO_PROVIDERS: { value: VideoProvider; label: string }[] = [
|
|
184
|
-
{ value: 'youtube', label: 'YouTube' },
|
|
185
|
-
{ value: 'vimeo', label: 'Vimeo' },
|
|
186
|
-
{ value: 'bunny', label: 'Bunny.net' },
|
|
187
|
-
{ value: 'custom', label: 'URL Direta' },
|
|
188
|
-
];
|
|
189
|
-
|
|
190
176
|
// ── Question sheet types ──────────────────────────────────────────────────────
|
|
191
177
|
|
|
192
178
|
type QuestionType =
|
|
@@ -196,14 +182,6 @@ type QuestionType =
|
|
|
196
182
|
| 'fill_blank'
|
|
197
183
|
| 'matching';
|
|
198
184
|
|
|
199
|
-
const QUESTION_TYPE_LABELS: Record<QuestionType, string> = {
|
|
200
|
-
multiple_choice: 'Múltipla escolha',
|
|
201
|
-
true_false: 'Verdadeiro / Falso',
|
|
202
|
-
essay: 'Dissertativa',
|
|
203
|
-
fill_blank: 'Lacuna',
|
|
204
|
-
matching: 'Associação',
|
|
205
|
-
};
|
|
206
|
-
|
|
207
185
|
type QAlt = { id: string; texto: string; correta: boolean };
|
|
208
186
|
type QFillBlank = { id: string; answer: string; alternativesText: string };
|
|
209
187
|
type QMatchingPair = { id: string; leftText: string; rightText: string };
|
|
@@ -257,33 +235,21 @@ const MOCK_QUESTIONS: MockQuestion[] = [
|
|
|
257
235
|
},
|
|
258
236
|
];
|
|
259
237
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
publicDescription: z.string(),
|
|
276
|
-
privateDescription: z.string(),
|
|
277
|
-
videoProvider: z
|
|
278
|
-
.enum(['youtube', 'vimeo', 'bunny', 'custom'] as const)
|
|
279
|
-
.optional(),
|
|
280
|
-
videoUrl: z.string().optional(),
|
|
281
|
-
transcription: z.string().optional(),
|
|
282
|
-
postContent: z.string().optional(),
|
|
283
|
-
questionId: z.string().nullable().optional(),
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
type FormValues = z.infer<typeof schema>;
|
|
238
|
+
type FormValues = {
|
|
239
|
+
code: string;
|
|
240
|
+
title: string;
|
|
241
|
+
type: LessonType;
|
|
242
|
+
duration: number;
|
|
243
|
+
status: LessonStatus;
|
|
244
|
+
visibility: 'publico' | 'privado' | 'restrito';
|
|
245
|
+
publicDescription: string;
|
|
246
|
+
privateDescription: string;
|
|
247
|
+
videoProvider?: VideoProvider;
|
|
248
|
+
videoUrl?: string;
|
|
249
|
+
transcription?: string;
|
|
250
|
+
postContent?: string;
|
|
251
|
+
questionId?: string | null;
|
|
252
|
+
};
|
|
287
253
|
|
|
288
254
|
// ── SortableAlternativa ───────────────────────────────────────────────────────
|
|
289
255
|
|
|
@@ -295,6 +261,11 @@ function SortableAlternativa({
|
|
|
295
261
|
onRemove,
|
|
296
262
|
canRemove,
|
|
297
263
|
disableText,
|
|
264
|
+
dragLabel,
|
|
265
|
+
textPlaceholder,
|
|
266
|
+
markIncorrectLabel,
|
|
267
|
+
markCorrectLabel,
|
|
268
|
+
removeLabel,
|
|
298
269
|
}: {
|
|
299
270
|
alt: QAlt;
|
|
300
271
|
index: number;
|
|
@@ -303,6 +274,11 @@ function SortableAlternativa({
|
|
|
303
274
|
onRemove: () => void;
|
|
304
275
|
canRemove: boolean;
|
|
305
276
|
disableText?: boolean;
|
|
277
|
+
dragLabel: string;
|
|
278
|
+
textPlaceholder: string;
|
|
279
|
+
markIncorrectLabel: string;
|
|
280
|
+
markCorrectLabel: string;
|
|
281
|
+
removeLabel: string;
|
|
306
282
|
}) {
|
|
307
283
|
const {
|
|
308
284
|
attributes,
|
|
@@ -328,7 +304,7 @@ function SortableAlternativa({
|
|
|
328
304
|
className="cursor-grab touch-none text-muted-foreground hover:text-foreground"
|
|
329
305
|
{...attributes}
|
|
330
306
|
{...listeners}
|
|
331
|
-
aria-label=
|
|
307
|
+
aria-label={dragLabel}
|
|
332
308
|
>
|
|
333
309
|
<GripVertical className="size-4" />
|
|
334
310
|
</button>
|
|
@@ -339,7 +315,7 @@ function SortableAlternativa({
|
|
|
339
315
|
value={alt.texto}
|
|
340
316
|
onChange={(e) => onChangeTexto(e.target.value)}
|
|
341
317
|
className="flex-1 border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0"
|
|
342
|
-
placeholder=
|
|
318
|
+
placeholder={textPlaceholder}
|
|
343
319
|
disabled={disableText}
|
|
344
320
|
/>
|
|
345
321
|
<button
|
|
@@ -352,7 +328,7 @@ function SortableAlternativa({
|
|
|
352
328
|
: 'text-muted-foreground hover:text-foreground'
|
|
353
329
|
)}
|
|
354
330
|
aria-label={
|
|
355
|
-
alt.correta ?
|
|
331
|
+
alt.correta ? markIncorrectLabel : markCorrectLabel
|
|
356
332
|
}
|
|
357
333
|
>
|
|
358
334
|
{alt.correta ? (
|
|
@@ -366,7 +342,7 @@ function SortableAlternativa({
|
|
|
366
342
|
type="button"
|
|
367
343
|
onClick={onRemove}
|
|
368
344
|
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:text-destructive"
|
|
369
|
-
aria-label=
|
|
345
|
+
aria-label={removeLabel}
|
|
370
346
|
>
|
|
371
347
|
<X className="size-4" />
|
|
372
348
|
</button>
|
|
@@ -382,6 +358,7 @@ interface EditorLessonProps {
|
|
|
382
358
|
}
|
|
383
359
|
|
|
384
360
|
export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
361
|
+
const t = useTranslations('lms.CoursesPage.StructurePage');
|
|
385
362
|
const lesson = useStructureStore((s) =>
|
|
386
363
|
s.lessons.find((l) => l.id === lessonId)
|
|
387
364
|
);
|
|
@@ -391,6 +368,49 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
391
368
|
const courseId = useStructureStore((s) => s.courseId);
|
|
392
369
|
const { request } = useApp();
|
|
393
370
|
const queryClient = useQueryClient();
|
|
371
|
+
const statusLabels: Record<LessonStatus, string> = {
|
|
372
|
+
preparada: t('statuses.preparada'),
|
|
373
|
+
gravada: t('statuses.gravada'),
|
|
374
|
+
editada: t('statuses.editada'),
|
|
375
|
+
finalizada: t('statuses.finalizada'),
|
|
376
|
+
publicada: t('statuses.publicada'),
|
|
377
|
+
};
|
|
378
|
+
const videoProviders: { value: VideoProvider; label: string }[] = [
|
|
379
|
+
{ value: 'youtube', label: 'YouTube' },
|
|
380
|
+
{ value: 'vimeo', label: 'Vimeo' },
|
|
381
|
+
{ value: 'bunny', label: 'Bunny.net' },
|
|
382
|
+
{ value: 'custom', label: t('providers.custom') },
|
|
383
|
+
];
|
|
384
|
+
const questionTypeLabels: Record<QuestionType, string> = {
|
|
385
|
+
multiple_choice: t('questionEditor.types.multipleChoice'),
|
|
386
|
+
true_false: t('questionEditor.types.trueFalse'),
|
|
387
|
+
essay: t('questionEditor.types.essay'),
|
|
388
|
+
fill_blank: t('questionEditor.types.fillBlank'),
|
|
389
|
+
matching: t('questionEditor.types.matching'),
|
|
390
|
+
};
|
|
391
|
+
const schema = z.object({
|
|
392
|
+
code: z.string().min(1, t('questionEditor.validation.codeRequired')),
|
|
393
|
+
title: z.string().min(1, t('questionEditor.validation.titleRequired')),
|
|
394
|
+
type: z.enum(['video', 'post', 'questao', 'exercicio'] as const),
|
|
395
|
+
duration: z.coerce.number().min(0),
|
|
396
|
+
status: z.enum([
|
|
397
|
+
'preparada',
|
|
398
|
+
'gravada',
|
|
399
|
+
'editada',
|
|
400
|
+
'finalizada',
|
|
401
|
+
'publicada',
|
|
402
|
+
] as const),
|
|
403
|
+
visibility: z.enum(['publico', 'privado', 'restrito'] as const),
|
|
404
|
+
publicDescription: z.string(),
|
|
405
|
+
privateDescription: z.string(),
|
|
406
|
+
videoProvider: z
|
|
407
|
+
.enum(['youtube', 'vimeo', 'bunny', 'custom'] as const)
|
|
408
|
+
.optional(),
|
|
409
|
+
videoUrl: z.string().optional(),
|
|
410
|
+
transcription: z.string().optional(),
|
|
411
|
+
postContent: z.string().optional(),
|
|
412
|
+
questionId: z.string().nullable().optional(),
|
|
413
|
+
});
|
|
394
414
|
|
|
395
415
|
const instructorPool =
|
|
396
416
|
queryClient.getQueryData<CourseStructureCacheData>(
|
|
@@ -469,6 +489,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
469
489
|
|
|
470
490
|
const cfg = TYPE_CONFIG[lesson.type];
|
|
471
491
|
const Icon = cfg.icon;
|
|
492
|
+
const lessonTypeLabel = t(cfg.labelKey as any);
|
|
472
493
|
|
|
473
494
|
async function handleResourceFiles(files: File[]) {
|
|
474
495
|
setIsUploading(true);
|
|
@@ -493,7 +514,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
493
514
|
const failedCount = results.filter((r) => r.status === 'rejected').length;
|
|
494
515
|
if (failedCount > 0)
|
|
495
516
|
toast.error(
|
|
496
|
-
|
|
517
|
+
t('questionEditor.resourceUploadFailed', { count: failedCount })
|
|
497
518
|
);
|
|
498
519
|
if (succeeded.length > 0)
|
|
499
520
|
setLocalResources((prev) => [...prev, ...succeeded]);
|
|
@@ -508,7 +529,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
508
529
|
try {
|
|
509
530
|
await deleteFile(request, numId);
|
|
510
531
|
} catch {
|
|
511
|
-
toast.error('
|
|
532
|
+
toast.error(t('questionEditor.resourceRemoveError'));
|
|
512
533
|
return;
|
|
513
534
|
}
|
|
514
535
|
} else {
|
|
@@ -520,7 +541,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
520
541
|
|
|
521
542
|
function handleResourceDownload(res: Resource) {
|
|
522
543
|
if (!res.url) {
|
|
523
|
-
toast(
|
|
544
|
+
toast(t('questionEditor.resourceDownloadSoon', { name: res.name }));
|
|
524
545
|
return;
|
|
525
546
|
}
|
|
526
547
|
const a = document.createElement('a');
|
|
@@ -612,12 +633,13 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
612
633
|
|
|
613
634
|
function saveQuestion() {
|
|
614
635
|
const errors: Record<string, string> = {};
|
|
615
|
-
if (!qSheetStatement.trim())
|
|
636
|
+
if (!qSheetStatement.trim())
|
|
637
|
+
errors.statement = t('questionEditor.validation.statementRequired');
|
|
616
638
|
if (
|
|
617
639
|
(qSheetType === 'multiple_choice' || qSheetType === 'true_false') &&
|
|
618
640
|
!qSheetAlts.some((a) => a.correta)
|
|
619
641
|
)
|
|
620
|
-
errors.alts = '
|
|
642
|
+
errors.alts = t('questionEditor.validation.correctAnswerRequired');
|
|
621
643
|
if (Object.keys(errors).length) {
|
|
622
644
|
setQSheetErrors(errors);
|
|
623
645
|
return;
|
|
@@ -639,13 +661,17 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
639
661
|
setSelectedQuestion(saved);
|
|
640
662
|
form.setValue('questionId', saved.id, { shouldDirty: true });
|
|
641
663
|
setQuestionSheetOpen(false);
|
|
642
|
-
toast.success(
|
|
664
|
+
toast.success(
|
|
665
|
+
editingQuestion
|
|
666
|
+
? t('questionEditor.updated')
|
|
667
|
+
: t('questionEditor.created')
|
|
668
|
+
);
|
|
643
669
|
}
|
|
644
670
|
|
|
645
671
|
function handleDelete() {
|
|
646
672
|
showConfirm({
|
|
647
|
-
title:
|
|
648
|
-
description: '
|
|
673
|
+
title: `${t('deleteLesson.title')} "${lesson!.title}"?`,
|
|
674
|
+
description: t('deleteLesson.warning'),
|
|
649
675
|
onConfirm: () =>
|
|
650
676
|
deleteLesson.mutate({ lessonId, sessionId: lesson!.sessionId }),
|
|
651
677
|
});
|
|
@@ -669,13 +695,15 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
669
695
|
</div>
|
|
670
696
|
<div className="flex-1 min-w-0">
|
|
671
697
|
<div className="flex items-center gap-1.5">
|
|
672
|
-
<span className="text-sm font-semibold truncate">
|
|
698
|
+
<span className="text-sm font-semibold truncate">
|
|
699
|
+
{t('lessonForm.titleCreate')}
|
|
700
|
+
</span>
|
|
673
701
|
{isDirty && (
|
|
674
702
|
<CircleDot className="size-3 text-amber-500 shrink-0" />
|
|
675
703
|
)}
|
|
676
704
|
</div>
|
|
677
705
|
<p className="text-[0.65rem] text-muted-foreground truncate">
|
|
678
|
-
{lesson.code} · {
|
|
706
|
+
{lesson.code} · {lessonTypeLabel}
|
|
679
707
|
</p>
|
|
680
708
|
</div>
|
|
681
709
|
{watchedStatus && (
|
|
@@ -685,7 +713,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
685
713
|
STATUS_COLORS[watchedStatus]
|
|
686
714
|
)}
|
|
687
715
|
>
|
|
688
|
-
{
|
|
716
|
+
{statusLabels[watchedStatus]}
|
|
689
717
|
</span>
|
|
690
718
|
)}
|
|
691
719
|
<Button
|
|
@@ -693,8 +721,8 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
693
721
|
variant="ghost"
|
|
694
722
|
size="icon"
|
|
695
723
|
className="size-7 text-destructive/60 hover:text-destructive shrink-0"
|
|
696
|
-
title=
|
|
697
|
-
aria-label=
|
|
724
|
+
title={t('lesson.delete')}
|
|
725
|
+
aria-label={t('lesson.delete')}
|
|
698
726
|
disabled={deleteLesson.isPending}
|
|
699
727
|
onClick={handleDelete}
|
|
700
728
|
>
|
|
@@ -710,13 +738,13 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
710
738
|
<Tabs defaultValue="dados" className="flex flex-col flex-1 min-h-0">
|
|
711
739
|
<TabsList className="mx-3 mt-2 h-8 w-auto justify-start shrink-0 bg-muted/50">
|
|
712
740
|
<TabsTrigger value="dados" className="text-xs h-7 px-2.5">
|
|
713
|
-
|
|
741
|
+
{t('lessonForm.tabData')}
|
|
714
742
|
</TabsTrigger>
|
|
715
743
|
<TabsTrigger value="conteudo" className="text-xs h-7 px-2.5">
|
|
716
|
-
|
|
744
|
+
{t('lessonForm.postContent')}
|
|
717
745
|
</TabsTrigger>
|
|
718
746
|
<TabsTrigger value="recursos" className="text-xs h-7 px-2.5">
|
|
719
|
-
|
|
747
|
+
{t('lessonForm.tabResources')}
|
|
720
748
|
</TabsTrigger>
|
|
721
749
|
</TabsList>
|
|
722
750
|
|
|
@@ -728,7 +756,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
728
756
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
729
757
|
<CardHeader className="px-3 pt-2 pb-0">
|
|
730
758
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
731
|
-
|
|
759
|
+
{t('questionEditor.identification')}
|
|
732
760
|
</CardTitle>
|
|
733
761
|
</CardHeader>
|
|
734
762
|
<CardContent className="px-3 pb-2 flex flex-col gap-3">
|
|
@@ -738,7 +766,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
738
766
|
name="code"
|
|
739
767
|
render={({ field }) => (
|
|
740
768
|
<FormItem>
|
|
741
|
-
<FormLabel className="text-xs">
|
|
769
|
+
<FormLabel className="text-xs">
|
|
770
|
+
{t('lessonForm.code')}
|
|
771
|
+
</FormLabel>
|
|
742
772
|
<FormControl>
|
|
743
773
|
<Input
|
|
744
774
|
{...field}
|
|
@@ -756,7 +786,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
756
786
|
<FormItem>
|
|
757
787
|
<FormLabel className="text-xs">
|
|
758
788
|
<Clock className="size-3 inline mr-1" />
|
|
759
|
-
|
|
789
|
+
{t('lessonForm.duration')}
|
|
760
790
|
</FormLabel>
|
|
761
791
|
<FormControl>
|
|
762
792
|
<Input
|
|
@@ -774,7 +804,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
774
804
|
name="type"
|
|
775
805
|
render={({ field }) => (
|
|
776
806
|
<FormItem>
|
|
777
|
-
<FormLabel className="text-xs">
|
|
807
|
+
<FormLabel className="text-xs">
|
|
808
|
+
{t('lessonForm.type')}
|
|
809
|
+
</FormLabel>
|
|
778
810
|
<Select
|
|
779
811
|
value={field.value}
|
|
780
812
|
onValueChange={field.onChange}
|
|
@@ -798,7 +830,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
798
830
|
<Ic
|
|
799
831
|
className={cn('size-3', cfg.color)}
|
|
800
832
|
/>
|
|
801
|
-
{cfg.
|
|
833
|
+
{t(cfg.labelKey as any)}
|
|
802
834
|
</span>
|
|
803
835
|
</SelectItem>
|
|
804
836
|
);
|
|
@@ -816,7 +848,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
816
848
|
name="title"
|
|
817
849
|
render={({ field }) => (
|
|
818
850
|
<FormItem>
|
|
819
|
-
<FormLabel className="text-xs">
|
|
851
|
+
<FormLabel className="text-xs">
|
|
852
|
+
{t('lessonForm.title')}
|
|
853
|
+
</FormLabel>
|
|
820
854
|
<FormControl>
|
|
821
855
|
<Input {...field} className="h-8 text-sm" />
|
|
822
856
|
</FormControl>
|
|
@@ -831,7 +865,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
831
865
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
832
866
|
<CardHeader className="px-3 pt-2 pb-0">
|
|
833
867
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
834
|
-
|
|
868
|
+
{t('questionEditor.publication')}
|
|
835
869
|
</CardTitle>
|
|
836
870
|
</CardHeader>
|
|
837
871
|
<CardContent className="px-3 pb-2 flex flex-col gap-3">
|
|
@@ -842,7 +876,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
842
876
|
render={({ field }) => (
|
|
843
877
|
<FormItem>
|
|
844
878
|
<FormLabel className="text-xs">
|
|
845
|
-
|
|
879
|
+
{t('questionEditor.productionStatus')}
|
|
846
880
|
</FormLabel>
|
|
847
881
|
<Select
|
|
848
882
|
value={field.value}
|
|
@@ -855,7 +889,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
855
889
|
</FormControl>
|
|
856
890
|
<SelectContent>
|
|
857
891
|
{(
|
|
858
|
-
Object.entries(
|
|
892
|
+
Object.entries(statusLabels) as [
|
|
859
893
|
LessonStatus,
|
|
860
894
|
string,
|
|
861
895
|
][]
|
|
@@ -867,10 +901,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
867
901
|
STATUS_COLORS[val]
|
|
868
902
|
)}
|
|
869
903
|
>
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
904
|
+
{lbl}
|
|
905
|
+
</span>
|
|
906
|
+
</SelectItem>
|
|
907
|
+
))}
|
|
874
908
|
</SelectContent>
|
|
875
909
|
</Select>
|
|
876
910
|
<FormMessage className="text-xs" />
|
|
@@ -884,7 +918,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
884
918
|
render={({ field }) => (
|
|
885
919
|
<FormItem>
|
|
886
920
|
<FormLabel className="text-xs">
|
|
887
|
-
|
|
921
|
+
{t('questionEditor.visibility')}
|
|
888
922
|
</FormLabel>
|
|
889
923
|
<Select
|
|
890
924
|
value={field.value}
|
|
@@ -898,17 +932,17 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
898
932
|
<SelectContent>
|
|
899
933
|
<SelectItem value="publico">
|
|
900
934
|
<span className="flex items-center gap-1.5">
|
|
901
|
-
<Eye className="size-3" />
|
|
935
|
+
<Eye className="size-3" /> {t('lessonForm.public')}
|
|
902
936
|
</span>
|
|
903
937
|
</SelectItem>
|
|
904
938
|
<SelectItem value="privado">
|
|
905
939
|
<span className="flex items-center gap-1.5">
|
|
906
|
-
<EyeOff className="size-3" />
|
|
940
|
+
<EyeOff className="size-3" /> {t('lessonForm.private')}
|
|
907
941
|
</span>
|
|
908
942
|
</SelectItem>
|
|
909
943
|
<SelectItem value="restrito">
|
|
910
944
|
<span className="flex items-center gap-1.5">
|
|
911
|
-
<Lock className="size-3" />
|
|
945
|
+
<Lock className="size-3" /> {t('questionEditor.restricted')}
|
|
912
946
|
</span>
|
|
913
947
|
</SelectItem>
|
|
914
948
|
</SelectContent>
|
|
@@ -925,7 +959,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
925
959
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
926
960
|
<CardHeader className="px-3 pt-2 pb-0">
|
|
927
961
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
928
|
-
|
|
962
|
+
{t('lessonForm.lessonInstructors')}
|
|
929
963
|
</CardTitle>
|
|
930
964
|
</CardHeader>
|
|
931
965
|
<CardContent className="px-3 pb-2 flex flex-col gap-2">
|
|
@@ -940,10 +974,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
940
974
|
: [...prev, String(val)]
|
|
941
975
|
);
|
|
942
976
|
}}
|
|
943
|
-
placeholder=
|
|
944
|
-
searchPlaceholder=
|
|
945
|
-
emptyLabel=
|
|
946
|
-
entityLabel=
|
|
977
|
+
placeholder={t('questionEditor.addInstructor')}
|
|
978
|
+
searchPlaceholder={t('questionEditor.searchInstructor')}
|
|
979
|
+
emptyLabel={t('questionEditor.noInstructorsAvailable')}
|
|
980
|
+
entityLabel={t('questionEditor.instructorEntity')}
|
|
947
981
|
options={instructorPool.filter(
|
|
948
982
|
(i) => !selectedInstructorIds.includes(String(i.id))
|
|
949
983
|
)}
|
|
@@ -990,7 +1024,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
990
1024
|
prev.filter((id) => id !== sid)
|
|
991
1025
|
)
|
|
992
1026
|
}
|
|
993
|
-
aria-label={
|
|
1027
|
+
aria-label={t('questionEditor.removeInstructor', {
|
|
1028
|
+
name: displayName,
|
|
1029
|
+
})}
|
|
994
1030
|
>
|
|
995
1031
|
<X className="size-3" />
|
|
996
1032
|
</Button>
|
|
@@ -1013,7 +1049,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1013
1049
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
1014
1050
|
<CardHeader className="px-3 pt-2 pb-1">
|
|
1015
1051
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
|
|
1016
|
-
<Eye className="size-3" />
|
|
1052
|
+
<Eye className="size-3" /> {t('lessonForm.publicDescription')}
|
|
1017
1053
|
</CardTitle>
|
|
1018
1054
|
</CardHeader>
|
|
1019
1055
|
<CardContent className="px-3 pb-2">
|
|
@@ -1039,7 +1075,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1039
1075
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
1040
1076
|
<CardHeader className="px-3 pt-2 pb-1">
|
|
1041
1077
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
|
|
1042
|
-
<Lock className="size-3" />
|
|
1078
|
+
<Lock className="size-3" /> {t('lessonForm.privateDescription')}
|
|
1043
1079
|
</CardTitle>
|
|
1044
1080
|
</CardHeader>
|
|
1045
1081
|
<CardContent className="px-3 pb-2">
|
|
@@ -1067,7 +1103,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1067
1103
|
<Card className="bg-muted/20 py-2 gap-2">
|
|
1068
1104
|
<CardHeader className="px-3 pt-2 pb-1">
|
|
1069
1105
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
|
|
1070
|
-
<Video className="size-3 text-blue-500" />
|
|
1106
|
+
<Video className="size-3 text-blue-500" /> {t('types.video')}
|
|
1071
1107
|
</CardTitle>
|
|
1072
1108
|
</CardHeader>
|
|
1073
1109
|
<CardContent className="px-3 pb-2 flex flex-col gap-3">
|
|
@@ -1076,7 +1112,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1076
1112
|
name="videoProvider"
|
|
1077
1113
|
render={({ field }) => (
|
|
1078
1114
|
<FormItem>
|
|
1079
|
-
<FormLabel className="text-xs">
|
|
1115
|
+
<FormLabel className="text-xs">
|
|
1116
|
+
{t('lessonForm.videoProvider')}
|
|
1117
|
+
</FormLabel>
|
|
1080
1118
|
<Select
|
|
1081
1119
|
value={field.value}
|
|
1082
1120
|
onValueChange={field.onChange}
|
|
@@ -1087,7 +1125,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1087
1125
|
</SelectTrigger>
|
|
1088
1126
|
</FormControl>
|
|
1089
1127
|
<SelectContent>
|
|
1090
|
-
{
|
|
1128
|
+
{videoProviders.map((p) => (
|
|
1091
1129
|
<SelectItem key={p.value} value={p.value}>
|
|
1092
1130
|
{p.label}
|
|
1093
1131
|
</SelectItem>
|
|
@@ -1105,14 +1143,14 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1105
1143
|
render={({ field }) => (
|
|
1106
1144
|
<FormItem>
|
|
1107
1145
|
<FormLabel className="text-xs">
|
|
1108
|
-
|
|
1146
|
+
{t('lessonForm.videoUrl')}
|
|
1109
1147
|
</FormLabel>
|
|
1110
1148
|
<FormControl>
|
|
1111
1149
|
<Input
|
|
1112
1150
|
{...field}
|
|
1113
1151
|
value={field.value ?? ''}
|
|
1114
1152
|
className="h-8 text-xs font-mono"
|
|
1115
|
-
placeholder=
|
|
1153
|
+
placeholder={t('lessonForm.videoUrlPlaceholder')}
|
|
1116
1154
|
/>
|
|
1117
1155
|
</FormControl>
|
|
1118
1156
|
<FormMessage className="text-xs" />
|
|
@@ -1126,7 +1164,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1126
1164
|
render={({ field }) => (
|
|
1127
1165
|
<FormItem>
|
|
1128
1166
|
<FormLabel className="text-xs">
|
|
1129
|
-
|
|
1167
|
+
{t('lessonForm.tabTranscription')}
|
|
1130
1168
|
</FormLabel>
|
|
1131
1169
|
<FormControl>
|
|
1132
1170
|
<Textarea
|
|
@@ -1134,7 +1172,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1134
1172
|
value={field.value ?? ''}
|
|
1135
1173
|
rows={5}
|
|
1136
1174
|
className="text-xs resize-none font-mono"
|
|
1137
|
-
placeholder=
|
|
1175
|
+
placeholder={t('lessonForm.transcriptionPlaceholder')}
|
|
1138
1176
|
/>
|
|
1139
1177
|
</FormControl>
|
|
1140
1178
|
<FormMessage className="text-xs" />
|
|
@@ -1150,7 +1188,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1150
1188
|
<CardHeader className="px-3 pt-2 pb-1">
|
|
1151
1189
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
|
|
1152
1190
|
<FileText className="size-3 text-emerald-500" />{' '}
|
|
1153
|
-
|
|
1191
|
+
{t('lessonForm.postContent')}
|
|
1154
1192
|
</CardTitle>
|
|
1155
1193
|
</CardHeader>
|
|
1156
1194
|
<CardContent className="px-3 pb-2">
|
|
@@ -1179,7 +1217,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1179
1217
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center justify-between gap-1.5">
|
|
1180
1218
|
<span className="flex items-center gap-1.5">
|
|
1181
1219
|
<ListChecks className="size-3 text-amber-500" />{' '}
|
|
1182
|
-
|
|
1220
|
+
{t('lessonForm.linkedExam')}
|
|
1183
1221
|
</span>
|
|
1184
1222
|
<Button
|
|
1185
1223
|
type="button"
|
|
@@ -1189,7 +1227,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1189
1227
|
onClick={openCreateQuestion}
|
|
1190
1228
|
>
|
|
1191
1229
|
<Plus className="size-3" />
|
|
1192
|
-
|
|
1230
|
+
{t('questionEditor.newQuestion')}
|
|
1193
1231
|
</Button>
|
|
1194
1232
|
</CardTitle>
|
|
1195
1233
|
</CardHeader>
|
|
@@ -1209,15 +1247,15 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1209
1247
|
null;
|
|
1210
1248
|
setSelectedQuestion(found);
|
|
1211
1249
|
}}
|
|
1212
|
-
placeholder=
|
|
1213
|
-
searchPlaceholder=
|
|
1214
|
-
emptyLabel=
|
|
1215
|
-
entityLabel=
|
|
1250
|
+
placeholder={t('questionEditor.selectQuestion')}
|
|
1251
|
+
searchPlaceholder={t('questionEditor.searchQuestion')}
|
|
1252
|
+
emptyLabel={t('questionEditor.noQuestionsFound')}
|
|
1253
|
+
entityLabel={t('questionEditor.questionEntity')}
|
|
1216
1254
|
options={MOCK_QUESTIONS}
|
|
1217
1255
|
getOptionValue={(o) => o.id}
|
|
1218
1256
|
getOptionLabel={(o) => o.title}
|
|
1219
1257
|
getOptionDescription={(o) =>
|
|
1220
|
-
|
|
1258
|
+
questionTypeLabels[o.type]
|
|
1221
1259
|
}
|
|
1222
1260
|
/>
|
|
1223
1261
|
</FormControl>
|
|
@@ -1233,9 +1271,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1233
1271
|
{selectedQuestion.title}
|
|
1234
1272
|
</p>
|
|
1235
1273
|
<p className="text-xs text-muted-foreground">
|
|
1236
|
-
{
|
|
1274
|
+
{questionTypeLabels[selectedQuestion.type]}
|
|
1237
1275
|
{selectedQuestion.points != null &&
|
|
1238
|
-
` · ${selectedQuestion.points}
|
|
1276
|
+
` · ${selectedQuestion.points} ${t('questionEditor.pointsSuffix')}`}
|
|
1239
1277
|
</p>
|
|
1240
1278
|
</div>
|
|
1241
1279
|
<Button
|
|
@@ -1244,7 +1282,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1244
1282
|
size="icon"
|
|
1245
1283
|
className="size-7 shrink-0"
|
|
1246
1284
|
onClick={() => openEditQuestion(selectedQuestion)}
|
|
1247
|
-
aria-label=
|
|
1285
|
+
aria-label={t('questionEditor.editQuestion')}
|
|
1248
1286
|
>
|
|
1249
1287
|
<Pencil className="size-3.5" />
|
|
1250
1288
|
</Button>
|
|
@@ -1313,11 +1351,13 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1313
1351
|
)}
|
|
1314
1352
|
<div className="text-center">
|
|
1315
1353
|
<p className="text-xs font-medium">
|
|
1316
|
-
{isUploading
|
|
1354
|
+
{isUploading
|
|
1355
|
+
? t('questionEditor.uploading')
|
|
1356
|
+
: t('questionEditor.dropFile')}
|
|
1317
1357
|
</p>
|
|
1318
1358
|
{!isUploading && (
|
|
1319
1359
|
<p className="text-xs text-muted-foreground">
|
|
1320
|
-
|
|
1360
|
+
{t('questionEditor.clickToSelect')}
|
|
1321
1361
|
</p>
|
|
1322
1362
|
)}
|
|
1323
1363
|
</div>
|
|
@@ -1338,15 +1378,16 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1338
1378
|
{/* Counter */}
|
|
1339
1379
|
{localResources.length > 0 && (
|
|
1340
1380
|
<p className="text-xs text-muted-foreground">
|
|
1341
|
-
{
|
|
1342
|
-
|
|
1381
|
+
{t('lessonForm.resourcesCount', {
|
|
1382
|
+
count: localResources.length,
|
|
1383
|
+
})}
|
|
1343
1384
|
</p>
|
|
1344
1385
|
)}
|
|
1345
1386
|
|
|
1346
1387
|
{/* Resource list */}
|
|
1347
1388
|
{localResources.length === 0 ? (
|
|
1348
1389
|
<p className="text-center text-xs text-muted-foreground py-1">
|
|
1349
|
-
|
|
1390
|
+
{t('questionEditor.noLinkedResources')}
|
|
1350
1391
|
</p>
|
|
1351
1392
|
) : (
|
|
1352
1393
|
<div className="flex flex-col gap-1">
|
|
@@ -1374,7 +1415,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1374
1415
|
{res.public && (
|
|
1375
1416
|
<Eye
|
|
1376
1417
|
className="size-3 text-emerald-500 shrink-0"
|
|
1377
|
-
aria-label=
|
|
1418
|
+
aria-label={t('lessonForm.public')}
|
|
1378
1419
|
/>
|
|
1379
1420
|
)}
|
|
1380
1421
|
{/* Abrir em nova aba */}
|
|
@@ -1383,7 +1424,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1383
1424
|
href={res.url}
|
|
1384
1425
|
target="_blank"
|
|
1385
1426
|
rel="noopener noreferrer"
|
|
1386
|
-
aria-label={
|
|
1427
|
+
aria-label={t('questionEditor.openInNewTab', {
|
|
1428
|
+
name: res.name,
|
|
1429
|
+
})}
|
|
1387
1430
|
onClick={(e) => e.stopPropagation()}
|
|
1388
1431
|
className="inline-flex items-center justify-center size-6 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors shrink-0"
|
|
1389
1432
|
>
|
|
@@ -1397,7 +1440,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1397
1440
|
size="icon"
|
|
1398
1441
|
className="size-6 shrink-0 text-muted-foreground hover:text-foreground"
|
|
1399
1442
|
onClick={() => handleResourceDownload(res)}
|
|
1400
|
-
aria-label={
|
|
1443
|
+
aria-label={t('questionEditor.downloadResource', {
|
|
1444
|
+
name: res.name,
|
|
1445
|
+
})}
|
|
1401
1446
|
>
|
|
1402
1447
|
<Download className="size-3" />
|
|
1403
1448
|
</Button>
|
|
@@ -1408,7 +1453,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1408
1453
|
size="icon"
|
|
1409
1454
|
className="size-6 shrink-0 text-muted-foreground hover:text-destructive"
|
|
1410
1455
|
onClick={() => void removeResource(res.id)}
|
|
1411
|
-
aria-label={
|
|
1456
|
+
aria-label={t('questionEditor.removeResource', {
|
|
1457
|
+
name: res.name,
|
|
1458
|
+
})}
|
|
1412
1459
|
>
|
|
1413
1460
|
<X className="size-3" />
|
|
1414
1461
|
</Button>
|
|
@@ -1435,7 +1482,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1435
1482
|
onClick={() => form.reset()}
|
|
1436
1483
|
>
|
|
1437
1484
|
<Undo2 className="size-3 mr-1" />
|
|
1438
|
-
|
|
1485
|
+
{t('lessonForm.cancel')}
|
|
1439
1486
|
</Button>
|
|
1440
1487
|
<div className="flex-1" />
|
|
1441
1488
|
<Button
|
|
@@ -1449,7 +1496,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1449
1496
|
) : (
|
|
1450
1497
|
<Save className="size-3 mr-1" />
|
|
1451
1498
|
)}
|
|
1452
|
-
|
|
1499
|
+
{t('lessonForm.save')}
|
|
1453
1500
|
</Button>
|
|
1454
1501
|
</div>
|
|
1455
1502
|
</div>
|
|
@@ -1462,14 +1509,18 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1462
1509
|
>
|
|
1463
1510
|
<SheetHeader>
|
|
1464
1511
|
<SheetTitle>
|
|
1465
|
-
{editingQuestion
|
|
1512
|
+
{editingQuestion
|
|
1513
|
+
? t('questionEditor.editQuestion')
|
|
1514
|
+
: t('questionEditor.newQuestion')}
|
|
1466
1515
|
</SheetTitle>
|
|
1467
1516
|
</SheetHeader>
|
|
1468
1517
|
|
|
1469
1518
|
<div className="flex flex-1 flex-col gap-5 px-4 pb-4">
|
|
1470
1519
|
{/* Enunciado */}
|
|
1471
1520
|
<div className="flex flex-col gap-1.5">
|
|
1472
|
-
<Label className="text-sm font-medium">
|
|
1521
|
+
<Label className="text-sm font-medium">
|
|
1522
|
+
{t('questionEditor.statement')}
|
|
1523
|
+
</Label>
|
|
1473
1524
|
<RichTextEditor
|
|
1474
1525
|
value={qSheetStatement}
|
|
1475
1526
|
onChange={setQSheetStatement}
|
|
@@ -1484,7 +1535,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1484
1535
|
{/* Tipo + Pontuação */}
|
|
1485
1536
|
<div className="grid grid-cols-2 gap-3">
|
|
1486
1537
|
<div className="flex flex-col gap-1.5">
|
|
1487
|
-
<Label className="text-sm font-medium">
|
|
1538
|
+
<Label className="text-sm font-medium">
|
|
1539
|
+
{t('questionEditor.type')}
|
|
1540
|
+
</Label>
|
|
1488
1541
|
<Select
|
|
1489
1542
|
value={qSheetType}
|
|
1490
1543
|
onValueChange={(v) =>
|
|
@@ -1496,17 +1549,19 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1496
1549
|
</SelectTrigger>
|
|
1497
1550
|
<SelectContent>
|
|
1498
1551
|
{(
|
|
1499
|
-
Object.keys(
|
|
1552
|
+
Object.keys(questionTypeLabels) as QuestionType[]
|
|
1500
1553
|
).map((t) => (
|
|
1501
1554
|
<SelectItem key={t} value={t}>
|
|
1502
|
-
{
|
|
1555
|
+
{questionTypeLabels[t]}
|
|
1503
1556
|
</SelectItem>
|
|
1504
1557
|
))}
|
|
1505
1558
|
</SelectContent>
|
|
1506
1559
|
</Select>
|
|
1507
1560
|
</div>
|
|
1508
1561
|
<div className="flex flex-col gap-1.5">
|
|
1509
|
-
<Label className="text-sm font-medium">
|
|
1562
|
+
<Label className="text-sm font-medium">
|
|
1563
|
+
{t('questionEditor.points')}
|
|
1564
|
+
</Label>
|
|
1510
1565
|
<Input
|
|
1511
1566
|
type="number"
|
|
1512
1567
|
min={0}
|
|
@@ -1523,7 +1578,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1523
1578
|
{qSheetType === 'multiple_choice' && (
|
|
1524
1579
|
<div className="flex flex-col gap-2">
|
|
1525
1580
|
<div className="flex items-center justify-between">
|
|
1526
|
-
<Label className="text-sm font-medium">
|
|
1581
|
+
<Label className="text-sm font-medium">
|
|
1582
|
+
{t('questionEditor.alternatives')}
|
|
1583
|
+
</Label>
|
|
1527
1584
|
<Button
|
|
1528
1585
|
type="button"
|
|
1529
1586
|
variant="outline"
|
|
@@ -1537,7 +1594,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1537
1594
|
}
|
|
1538
1595
|
>
|
|
1539
1596
|
<Plus className="size-3" />
|
|
1540
|
-
|
|
1597
|
+
{t('questionEditor.add')}
|
|
1541
1598
|
</Button>
|
|
1542
1599
|
</div>
|
|
1543
1600
|
{qSheetErrors.alts && (
|
|
@@ -1569,6 +1626,15 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1569
1626
|
key={alt.id}
|
|
1570
1627
|
alt={alt}
|
|
1571
1628
|
index={idx}
|
|
1629
|
+
dragLabel={t('questionEditor.dragAlternative')}
|
|
1630
|
+
textPlaceholder={t(
|
|
1631
|
+
'questionEditor.alternativeTextPlaceholder'
|
|
1632
|
+
)}
|
|
1633
|
+
markIncorrectLabel={t(
|
|
1634
|
+
'questionEditor.markAsIncorrect'
|
|
1635
|
+
)}
|
|
1636
|
+
markCorrectLabel={t('questionEditor.markAsCorrect')}
|
|
1637
|
+
removeLabel={t('questionEditor.removeAlternative')}
|
|
1572
1638
|
canRemove={qSheetAlts.length > 2}
|
|
1573
1639
|
onToggleCorrect={() =>
|
|
1574
1640
|
setQSheetAlts((prev) =>
|
|
@@ -1602,7 +1668,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1602
1668
|
{qSheetType === 'true_false' && (
|
|
1603
1669
|
<div className="flex flex-col gap-2">
|
|
1604
1670
|
<Label className="text-sm font-medium">
|
|
1605
|
-
|
|
1671
|
+
{t('questionEditor.correctAnswer')}
|
|
1606
1672
|
</Label>
|
|
1607
1673
|
{qSheetErrors.alts && (
|
|
1608
1674
|
<p className="text-xs text-destructive">
|
|
@@ -1642,7 +1708,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1642
1708
|
{qSheetType === 'fill_blank' && (
|
|
1643
1709
|
<div className="flex flex-col gap-2">
|
|
1644
1710
|
<div className="flex items-center justify-between">
|
|
1645
|
-
<Label className="text-sm font-medium">
|
|
1711
|
+
<Label className="text-sm font-medium">
|
|
1712
|
+
{t('questionEditor.blanks')}
|
|
1713
|
+
</Label>
|
|
1646
1714
|
<Button
|
|
1647
1715
|
type="button"
|
|
1648
1716
|
variant="outline"
|
|
@@ -1660,7 +1728,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1660
1728
|
}
|
|
1661
1729
|
>
|
|
1662
1730
|
<Plus className="size-3" />
|
|
1663
|
-
|
|
1731
|
+
{t('questionEditor.add')}
|
|
1664
1732
|
</Button>
|
|
1665
1733
|
</div>
|
|
1666
1734
|
{qSheetFillBlanks.map((fb, idx) => (
|
|
@@ -1670,7 +1738,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1670
1738
|
>
|
|
1671
1739
|
<div className="flex items-center justify-between">
|
|
1672
1740
|
<span className="text-xs font-medium text-muted-foreground">
|
|
1673
|
-
|
|
1741
|
+
{t('questionEditor.blankLabel', {
|
|
1742
|
+
count: idx + 1,
|
|
1743
|
+
})}
|
|
1674
1744
|
</span>
|
|
1675
1745
|
{qSheetFillBlanks.length > 1 && (
|
|
1676
1746
|
<button
|
|
@@ -1681,14 +1751,16 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1681
1751
|
prev.filter((f) => f.id !== fb.id)
|
|
1682
1752
|
)
|
|
1683
1753
|
}
|
|
1684
|
-
aria-label=
|
|
1754
|
+
aria-label={t('questionEditor.removeBlank')}
|
|
1685
1755
|
>
|
|
1686
1756
|
<X className="size-3.5" />
|
|
1687
1757
|
</button>
|
|
1688
1758
|
)}
|
|
1689
1759
|
</div>
|
|
1690
1760
|
<Input
|
|
1691
|
-
placeholder=
|
|
1761
|
+
placeholder={t(
|
|
1762
|
+
'questionEditor.correctAnswerPlaceholder'
|
|
1763
|
+
)}
|
|
1692
1764
|
value={fb.answer}
|
|
1693
1765
|
onChange={(e) =>
|
|
1694
1766
|
setQSheetFillBlanks((prev) =>
|
|
@@ -1701,7 +1773,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1701
1773
|
}
|
|
1702
1774
|
/>
|
|
1703
1775
|
<Input
|
|
1704
|
-
placeholder=
|
|
1776
|
+
placeholder={t(
|
|
1777
|
+
'questionEditor.alternativesPlaceholder'
|
|
1778
|
+
)}
|
|
1705
1779
|
value={fb.alternativesText}
|
|
1706
1780
|
onChange={(e) =>
|
|
1707
1781
|
setQSheetFillBlanks((prev) =>
|
|
@@ -1723,7 +1797,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1723
1797
|
<div className="flex flex-col gap-2">
|
|
1724
1798
|
<div className="flex items-center justify-between">
|
|
1725
1799
|
<Label className="text-sm font-medium">
|
|
1726
|
-
|
|
1800
|
+
{t('questionEditor.matchingPairs')}
|
|
1727
1801
|
</Label>
|
|
1728
1802
|
<Button
|
|
1729
1803
|
type="button"
|
|
@@ -1738,7 +1812,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1738
1812
|
}
|
|
1739
1813
|
>
|
|
1740
1814
|
<Plus className="size-3" />
|
|
1741
|
-
|
|
1815
|
+
{t('questionEditor.add')}
|
|
1742
1816
|
</Button>
|
|
1743
1817
|
</div>
|
|
1744
1818
|
{qSheetPairs.map((pair, idx) => (
|
|
@@ -1747,7 +1821,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1747
1821
|
{idx + 1}
|
|
1748
1822
|
</span>
|
|
1749
1823
|
<Input
|
|
1750
|
-
placeholder=
|
|
1824
|
+
placeholder={t('questionEditor.left')}
|
|
1751
1825
|
value={pair.leftText}
|
|
1752
1826
|
onChange={(e) =>
|
|
1753
1827
|
setQSheetPairs((prev) =>
|
|
@@ -1762,7 +1836,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1762
1836
|
/>
|
|
1763
1837
|
<span className="text-muted-foreground text-xs">↔</span>
|
|
1764
1838
|
<Input
|
|
1765
|
-
placeholder=
|
|
1839
|
+
placeholder={t('questionEditor.right')}
|
|
1766
1840
|
value={pair.rightText}
|
|
1767
1841
|
onChange={(e) =>
|
|
1768
1842
|
setQSheetPairs((prev) =>
|
|
@@ -1784,7 +1858,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1784
1858
|
prev.filter((p) => p.id !== pair.id)
|
|
1785
1859
|
)
|
|
1786
1860
|
}
|
|
1787
|
-
aria-label=
|
|
1861
|
+
aria-label={t('questionEditor.removePair')}
|
|
1788
1862
|
>
|
|
1789
1863
|
<X className="size-4" />
|
|
1790
1864
|
</button>
|
|
@@ -1797,7 +1871,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1797
1871
|
{/* Dissertativa */}
|
|
1798
1872
|
{qSheetType === 'essay' && (
|
|
1799
1873
|
<div className="rounded-lg border bg-muted/30 p-4 text-center text-sm text-muted-foreground">
|
|
1800
|
-
|
|
1874
|
+
{t('questionEditor.essayHelp')}
|
|
1801
1875
|
</div>
|
|
1802
1876
|
)}
|
|
1803
1877
|
</div>
|
|
@@ -1809,11 +1883,13 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1809
1883
|
className="flex-1"
|
|
1810
1884
|
onClick={() => setQuestionSheetOpen(false)}
|
|
1811
1885
|
>
|
|
1812
|
-
|
|
1886
|
+
{t('lessonForm.cancel')}
|
|
1813
1887
|
</Button>
|
|
1814
1888
|
<Button type="button" className="flex-1" onClick={saveQuestion}>
|
|
1815
1889
|
<Save className="size-4 mr-1.5" />
|
|
1816
|
-
{editingQuestion
|
|
1890
|
+
{editingQuestion
|
|
1891
|
+
? t('questionEditor.saveChanges')
|
|
1892
|
+
: t('questionEditor.createQuestion')}
|
|
1817
1893
|
</Button>
|
|
1818
1894
|
</SheetFooter>
|
|
1819
1895
|
</SheetContent>
|