@hed-hog/lms 0.0.328 → 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.
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +22 -16
- package/dist/instructor/instructor.service.js.map +1 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +7 -5
- package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
- package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
- package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
- package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +10 -10
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +7 -5
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +92 -26
- package/hedhog/frontend/app/instructors/page.tsx.ejs +4 -2
- package/hedhog/frontend/messages/en.json +619 -13
- package/hedhog/frontend/messages/pt.json +619 -13
- package/package.json +7 -7
- package/src/instructor/instructor.service.ts +22 -19
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +0 -591
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +0 -109
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +0 -60
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +0 -134
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +0 -113
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +0 -314
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +0 -174
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +0 -185
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +0 -277
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +0 -207
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
Trash2,
|
|
27
27
|
Unlock,
|
|
28
28
|
} from 'lucide-react';
|
|
29
|
+
import { useTranslations } from 'next-intl';
|
|
29
30
|
import { useCallback, useRef, useState } from 'react';
|
|
30
31
|
import { toast } from 'sonner';
|
|
31
32
|
import { getCanvasAPI } from '../../_lib/editor/canvasInstance';
|
|
@@ -35,6 +36,7 @@ import { FONT_FAMILIES, getObjectLabel } from '../../_lib/editor/types';
|
|
|
35
36
|
import { useTemplateStore } from '../../_lib/store/useTemplateStore';
|
|
36
37
|
|
|
37
38
|
export default function RightPanel() {
|
|
39
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
38
40
|
return (
|
|
39
41
|
<aside className="flex w-75 shrink-0 flex-col border-l border-border bg-background">
|
|
40
42
|
<Tabs
|
|
@@ -43,13 +45,13 @@ export default function RightPanel() {
|
|
|
43
45
|
>
|
|
44
46
|
<TabsList className="mx-3 mt-3 w-auto">
|
|
45
47
|
<TabsTrigger value="props" className="flex-1">
|
|
46
|
-
|
|
48
|
+
{t('rightPanel.tabs.properties')}
|
|
47
49
|
</TabsTrigger>
|
|
48
50
|
<TabsTrigger value="layers" className="flex-1">
|
|
49
|
-
|
|
51
|
+
{t('rightPanel.tabs.layers')}
|
|
50
52
|
</TabsTrigger>
|
|
51
53
|
<TabsTrigger value="data" className="flex-1">
|
|
52
|
-
|
|
54
|
+
{t('rightPanel.tabs.data')}
|
|
53
55
|
</TabsTrigger>
|
|
54
56
|
</TabsList>
|
|
55
57
|
|
|
@@ -80,6 +82,7 @@ export default function RightPanel() {
|
|
|
80
82
|
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
81
83
|
|
|
82
84
|
function PropertiesInspector() {
|
|
85
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
83
86
|
const selectedId = useTemplateStore((s) => s.selectedObjectId);
|
|
84
87
|
const objects = useTemplateStore((s) => s.template.objects);
|
|
85
88
|
const obj = objects.find((o) => o.id === selectedId);
|
|
@@ -96,9 +99,7 @@ function PropertiesInspector() {
|
|
|
96
99
|
return (
|
|
97
100
|
<div className="flex flex-col items-center justify-center gap-2 p-8 text-center text-muted-foreground">
|
|
98
101
|
<MousePointer2 className="size-8" />
|
|
99
|
-
<p className="text-sm">
|
|
100
|
-
Selecione um objeto no canvas para editar suas propriedades.
|
|
101
|
-
</p>
|
|
102
|
+
<p className="text-sm">{t('rightPanel.emptySelection')}</p>
|
|
102
103
|
</div>
|
|
103
104
|
);
|
|
104
105
|
}
|
|
@@ -131,12 +132,13 @@ function TextProperties({
|
|
|
131
132
|
obj: TemplateObject;
|
|
132
133
|
setProp: (p: Record<string, unknown>) => void;
|
|
133
134
|
}) {
|
|
135
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
134
136
|
const ts = obj.textStyle;
|
|
135
137
|
|
|
136
138
|
return (
|
|
137
139
|
<>
|
|
138
140
|
<div className="flex flex-col gap-1.5">
|
|
139
|
-
<Label className="text-xs">
|
|
141
|
+
<Label className="text-xs">{t('rightPanel.text.font')}</Label>
|
|
140
142
|
<Select
|
|
141
143
|
value={ts?.fontFamily ?? 'Inter'}
|
|
142
144
|
onValueChange={(v) => setProp({ fontFamily: v })}
|
|
@@ -156,7 +158,9 @@ function TextProperties({
|
|
|
156
158
|
|
|
157
159
|
<div className="flex flex-col gap-1.5">
|
|
158
160
|
<Label className="text-xs">
|
|
159
|
-
|
|
161
|
+
{t('rightPanel.text.size', {
|
|
162
|
+
size: Math.round(fromPctH(ts?.fontSizePct ?? 2.12)),
|
|
163
|
+
})}
|
|
160
164
|
</Label>
|
|
161
165
|
<Slider
|
|
162
166
|
min={8}
|
|
@@ -168,7 +172,7 @@ function TextProperties({
|
|
|
168
172
|
</div>
|
|
169
173
|
|
|
170
174
|
<div className="flex flex-col gap-1.5">
|
|
171
|
-
<Label className="text-xs">
|
|
175
|
+
<Label className="text-xs">{t('rightPanel.text.weight')}</Label>
|
|
172
176
|
<Select
|
|
173
177
|
value={String(ts?.fontWeight ?? 400)}
|
|
174
178
|
onValueChange={(v) => setProp({ fontWeight: v })}
|
|
@@ -177,15 +181,21 @@ function TextProperties({
|
|
|
177
181
|
<SelectValue />
|
|
178
182
|
</SelectTrigger>
|
|
179
183
|
<SelectContent>
|
|
180
|
-
<SelectItem value="400">
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
<SelectItem value="400">
|
|
185
|
+
{t('rightPanel.text.weights.normal')}
|
|
186
|
+
</SelectItem>
|
|
187
|
+
<SelectItem value="600">
|
|
188
|
+
{t('rightPanel.text.weights.semiBold')}
|
|
189
|
+
</SelectItem>
|
|
190
|
+
<SelectItem value="700">
|
|
191
|
+
{t('rightPanel.text.weights.bold')}
|
|
192
|
+
</SelectItem>
|
|
183
193
|
</SelectContent>
|
|
184
194
|
</Select>
|
|
185
195
|
</div>
|
|
186
196
|
|
|
187
197
|
<div className="flex items-center justify-between">
|
|
188
|
-
<Label className="text-xs">
|
|
198
|
+
<Label className="text-xs">{t('rightPanel.text.italic')}</Label>
|
|
189
199
|
<Switch
|
|
190
200
|
checked={ts?.italic ?? false}
|
|
191
201
|
onCheckedChange={(v) =>
|
|
@@ -195,7 +205,7 @@ function TextProperties({
|
|
|
195
205
|
</div>
|
|
196
206
|
|
|
197
207
|
<div className="flex flex-col gap-1.5">
|
|
198
|
-
<Label className="text-xs">
|
|
208
|
+
<Label className="text-xs">{t('rightPanel.text.color')}</Label>
|
|
199
209
|
<div className="flex items-center gap-2">
|
|
200
210
|
<input
|
|
201
211
|
type="color"
|
|
@@ -210,7 +220,7 @@ function TextProperties({
|
|
|
210
220
|
</div>
|
|
211
221
|
|
|
212
222
|
<div className="flex flex-col gap-1.5">
|
|
213
|
-
<Label className="text-xs">
|
|
223
|
+
<Label className="text-xs">{t('rightPanel.text.alignment')}</Label>
|
|
214
224
|
<Select
|
|
215
225
|
value={ts?.align ?? 'left'}
|
|
216
226
|
onValueChange={(v) => setProp({ textAlign: v })}
|
|
@@ -219,9 +229,15 @@ function TextProperties({
|
|
|
219
229
|
<SelectValue />
|
|
220
230
|
</SelectTrigger>
|
|
221
231
|
<SelectContent>
|
|
222
|
-
<SelectItem value="left">
|
|
223
|
-
|
|
224
|
-
|
|
232
|
+
<SelectItem value="left">
|
|
233
|
+
{t('rightPanel.text.alignments.left')}
|
|
234
|
+
</SelectItem>
|
|
235
|
+
<SelectItem value="center">
|
|
236
|
+
{t('rightPanel.text.alignments.center')}
|
|
237
|
+
</SelectItem>
|
|
238
|
+
<SelectItem value="right">
|
|
239
|
+
{t('rightPanel.text.alignments.right')}
|
|
240
|
+
</SelectItem>
|
|
225
241
|
</SelectContent>
|
|
226
242
|
</Select>
|
|
227
243
|
</div>
|
|
@@ -237,13 +253,16 @@ function TextStrokeProperties({
|
|
|
237
253
|
obj: TemplateObject;
|
|
238
254
|
setProp: (p: Record<string, unknown>) => void;
|
|
239
255
|
}) {
|
|
256
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
240
257
|
const stroke = obj.effects?.stroke;
|
|
241
258
|
const hasStroke = !!stroke && fromPctW(stroke.widthPct) > 0;
|
|
242
259
|
|
|
243
260
|
return (
|
|
244
261
|
<div className="flex flex-col gap-2 rounded-md border border-border p-2.5">
|
|
245
262
|
<div className="flex items-center justify-between">
|
|
246
|
-
<Label className="text-xs font-semibold">
|
|
263
|
+
<Label className="text-xs font-semibold">
|
|
264
|
+
{t('rightPanel.stroke.title')}
|
|
265
|
+
</Label>
|
|
247
266
|
<Switch
|
|
248
267
|
checked={hasStroke}
|
|
249
268
|
onCheckedChange={(on) => {
|
|
@@ -258,7 +277,9 @@ function TextStrokeProperties({
|
|
|
258
277
|
{hasStroke && (
|
|
259
278
|
<>
|
|
260
279
|
<div className="flex items-center gap-2">
|
|
261
|
-
<Label className="w-12 text-xs">
|
|
280
|
+
<Label className="w-12 text-xs">
|
|
281
|
+
{t('rightPanel.common.color')}
|
|
282
|
+
</Label>
|
|
262
283
|
<input
|
|
263
284
|
type="color"
|
|
264
285
|
value={stroke?.color ?? '#000000'}
|
|
@@ -271,7 +292,9 @@ function TextStrokeProperties({
|
|
|
271
292
|
</div>
|
|
272
293
|
<div className="flex flex-col gap-1">
|
|
273
294
|
<Label className="text-xs">
|
|
274
|
-
|
|
295
|
+
{t('rightPanel.stroke.width', {
|
|
296
|
+
size: Math.round(fromPctW(stroke?.widthPct ?? 0)),
|
|
297
|
+
})}
|
|
275
298
|
</Label>
|
|
276
299
|
<Slider
|
|
277
300
|
min={1}
|
|
@@ -295,6 +318,7 @@ function TextShadowProperties({
|
|
|
295
318
|
obj: TemplateObject;
|
|
296
319
|
setProp: (p: Record<string, unknown>) => void;
|
|
297
320
|
}) {
|
|
321
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
298
322
|
const shadow = obj.effects?.shadow;
|
|
299
323
|
const hasShadow = !!shadow;
|
|
300
324
|
|
|
@@ -321,7 +345,9 @@ function TextShadowProperties({
|
|
|
321
345
|
return (
|
|
322
346
|
<div className="flex flex-col gap-2 rounded-md border border-border p-2.5">
|
|
323
347
|
<div className="flex items-center justify-between">
|
|
324
|
-
<Label className="text-xs font-semibold">
|
|
348
|
+
<Label className="text-xs font-semibold">
|
|
349
|
+
{t('rightPanel.shadow.title')}
|
|
350
|
+
</Label>
|
|
325
351
|
<Switch
|
|
326
352
|
checked={hasShadow}
|
|
327
353
|
onCheckedChange={(on) => {
|
|
@@ -338,7 +364,9 @@ function TextShadowProperties({
|
|
|
338
364
|
{hasShadow && (
|
|
339
365
|
<>
|
|
340
366
|
<div className="flex items-center gap-2">
|
|
341
|
-
<Label className="w-12 text-xs">
|
|
367
|
+
<Label className="w-12 text-xs">
|
|
368
|
+
{t('rightPanel.common.color')}
|
|
369
|
+
</Label>
|
|
342
370
|
<input
|
|
343
371
|
type="color"
|
|
344
372
|
value={shadow?.color?.startsWith('#') ? shadow.color : '#000000'}
|
|
@@ -348,7 +376,9 @@ function TextShadowProperties({
|
|
|
348
376
|
</div>
|
|
349
377
|
<div className="flex flex-col gap-1">
|
|
350
378
|
<Label className="text-xs">
|
|
351
|
-
|
|
379
|
+
{t('rightPanel.shadow.offsetX', {
|
|
380
|
+
size: Math.round(fromPctW(shadow?.xPct ?? 0)),
|
|
381
|
+
})}
|
|
352
382
|
</Label>
|
|
353
383
|
<Slider
|
|
354
384
|
min={-20}
|
|
@@ -360,7 +390,9 @@ function TextShadowProperties({
|
|
|
360
390
|
</div>
|
|
361
391
|
<div className="flex flex-col gap-1">
|
|
362
392
|
<Label className="text-xs">
|
|
363
|
-
|
|
393
|
+
{t('rightPanel.shadow.offsetY', {
|
|
394
|
+
size: Math.round(fromPctH(shadow?.yPct ?? 0)),
|
|
395
|
+
})}
|
|
364
396
|
</Label>
|
|
365
397
|
<Slider
|
|
366
398
|
min={-20}
|
|
@@ -372,7 +404,9 @@ function TextShadowProperties({
|
|
|
372
404
|
</div>
|
|
373
405
|
<div className="flex flex-col gap-1">
|
|
374
406
|
<Label className="text-xs">
|
|
375
|
-
|
|
407
|
+
{t('rightPanel.shadow.blur', {
|
|
408
|
+
size: Math.round(fromPctW(shadow?.blurPct ?? 0)),
|
|
409
|
+
})}
|
|
376
410
|
</Label>
|
|
377
411
|
<Slider
|
|
378
412
|
min={0}
|
|
@@ -396,13 +430,14 @@ function ShapeProperties({
|
|
|
396
430
|
obj: TemplateObject;
|
|
397
431
|
setProp: (p: Record<string, unknown>) => void;
|
|
398
432
|
}) {
|
|
433
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
399
434
|
const stroke = obj.effects?.stroke;
|
|
400
435
|
|
|
401
436
|
return (
|
|
402
437
|
<>
|
|
403
438
|
{obj.shape !== 'line' && (
|
|
404
439
|
<div className="flex flex-col gap-1.5">
|
|
405
|
-
<Label className="text-xs">
|
|
440
|
+
<Label className="text-xs">{t('rightPanel.shape.fill')}</Label>
|
|
406
441
|
<div className="flex items-center gap-2">
|
|
407
442
|
<input
|
|
408
443
|
type="color"
|
|
@@ -418,7 +453,7 @@ function ShapeProperties({
|
|
|
418
453
|
)}
|
|
419
454
|
|
|
420
455
|
<div className="flex flex-col gap-1.5">
|
|
421
|
-
<Label className="text-xs">
|
|
456
|
+
<Label className="text-xs">{t('rightPanel.shape.borderColor')}</Label>
|
|
422
457
|
<div className="flex items-center gap-2">
|
|
423
458
|
<input
|
|
424
459
|
type="color"
|
|
@@ -434,7 +469,9 @@ function ShapeProperties({
|
|
|
434
469
|
|
|
435
470
|
<div className="flex flex-col gap-1.5">
|
|
436
471
|
<Label className="text-xs">
|
|
437
|
-
|
|
472
|
+
{t('rightPanel.shape.borderWidth', {
|
|
473
|
+
size: Math.round(fromPctW(stroke?.widthPct ?? 0.0625)),
|
|
474
|
+
})}
|
|
438
475
|
</Label>
|
|
439
476
|
<Slider
|
|
440
477
|
min={0}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useApp } from '@hed-hog/next-app-provider';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import dynamic from 'next/dynamic';
|
|
5
6
|
import { useSearchParams } from 'next/navigation';
|
|
6
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
@@ -26,6 +27,7 @@ type CertificateTemplateResponse = {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export default function TemplateEditorPage() {
|
|
30
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
29
31
|
const { request } = useApp();
|
|
30
32
|
const searchParams = useSearchParams();
|
|
31
33
|
const templateId = useMemo(() => {
|
|
@@ -65,14 +67,14 @@ export default function TemplateEditorPage() {
|
|
|
65
67
|
const fallback = createDefaultTemplate();
|
|
66
68
|
fallback.name = templateRecord.name;
|
|
67
69
|
setTemplate(fallback);
|
|
68
|
-
toast.error('
|
|
70
|
+
toast.error(t('page.toasts.invalidTemplate'));
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
parsedContent.name = templateRecord.name;
|
|
73
75
|
setTemplate(parsedContent);
|
|
74
76
|
} catch {
|
|
75
|
-
toast.error('
|
|
77
|
+
toast.error(t('page.toasts.loadError'));
|
|
76
78
|
} finally {
|
|
77
79
|
setIsLoadingTemplate(false);
|
|
78
80
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
ZoomIn,
|
|
22
22
|
ZoomOut,
|
|
23
23
|
} from 'lucide-react';
|
|
24
|
+
import { useTranslations } from 'next-intl';
|
|
24
25
|
import { useCallback, useEffect, useRef } from 'react';
|
|
25
26
|
import { toast } from 'sonner';
|
|
26
27
|
import { getCanvasAPI } from '../../_lib/editor/canvasInstance';
|
|
@@ -44,6 +45,7 @@ type TopbarProps = {
|
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
export default function Topbar({ templateContext }: TopbarProps) {
|
|
48
|
+
const t = useTranslations('lms.CertificateTemplateEditor');
|
|
47
49
|
const { request } = useApp();
|
|
48
50
|
const name = useTemplateStore((s) => s.template.name);
|
|
49
51
|
const setName = useTemplateStore((s) => s.setName);
|
|
@@ -96,7 +98,7 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
96
98
|
},
|
|
97
99
|
})
|
|
98
100
|
.catch(() => {
|
|
99
|
-
toast.error('
|
|
101
|
+
toast.error(t('topBar.toasts.autoSaveError'));
|
|
100
102
|
});
|
|
101
103
|
}, 1500);
|
|
102
104
|
|
|
@@ -109,13 +111,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
109
111
|
const handleNew = useCallback(() => {
|
|
110
112
|
resetTemplate();
|
|
111
113
|
getCanvasAPI()?.loadTemplate(useTemplateStore.getState().template);
|
|
112
|
-
toast.success('
|
|
114
|
+
toast.success(t('topBar.toasts.created'));
|
|
113
115
|
}, [resetTemplate]);
|
|
114
116
|
|
|
115
117
|
const handleSave = useCallback(() => {
|
|
116
118
|
const run = async () => {
|
|
117
119
|
if (!templateContext) {
|
|
118
|
-
toast.success('
|
|
120
|
+
toast.success(t('topBar.toasts.savedLocalStorage'));
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -130,11 +132,11 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
130
132
|
},
|
|
131
133
|
});
|
|
132
134
|
|
|
133
|
-
toast.success('
|
|
135
|
+
toast.success(t('topBar.toasts.saved'));
|
|
134
136
|
};
|
|
135
137
|
|
|
136
138
|
run().catch(() => {
|
|
137
|
-
toast.error('
|
|
139
|
+
toast.error(t('topBar.toasts.saveError'));
|
|
138
140
|
});
|
|
139
141
|
}, [request, templateContext, templateState]);
|
|
140
142
|
|
|
@@ -148,7 +150,7 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
148
150
|
a.download = `${templateState.name.replace(/\s+/g, '_')}.json`;
|
|
149
151
|
a.click();
|
|
150
152
|
URL.revokeObjectURL(url);
|
|
151
|
-
toast.success('
|
|
153
|
+
toast.success(t('topBar.toasts.exportedJson'));
|
|
152
154
|
}, [templateState]);
|
|
153
155
|
|
|
154
156
|
const handleImport = useCallback(
|
|
@@ -160,14 +162,14 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
160
162
|
try {
|
|
161
163
|
const parsed = JSON.parse(ev.target?.result as string);
|
|
162
164
|
if (!isValidTemplate(parsed)) {
|
|
163
|
-
toast.error('
|
|
165
|
+
toast.error(t('topBar.toasts.invalidJson'));
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
166
168
|
setTemplate(parsed);
|
|
167
169
|
getCanvasAPI()?.loadTemplate(parsed);
|
|
168
|
-
toast.success('
|
|
170
|
+
toast.success(t('topBar.toasts.imported'));
|
|
169
171
|
} catch {
|
|
170
|
-
toast.error('
|
|
172
|
+
toast.error(t('topBar.toasts.readJsonError'));
|
|
171
173
|
}
|
|
172
174
|
};
|
|
173
175
|
reader.readAsText(file);
|
|
@@ -195,7 +197,7 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
195
197
|
value={name}
|
|
196
198
|
onChange={(e) => setName(e.target.value)}
|
|
197
199
|
className="h-8 w-52 text-sm font-medium"
|
|
198
|
-
aria-label=
|
|
200
|
+
aria-label={t('topBar.templateNameAriaLabel')}
|
|
199
201
|
/>
|
|
200
202
|
|
|
201
203
|
<div className="mx-1 h-5 w-px bg-border" />
|
|
@@ -210,10 +212,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
210
212
|
onClick={handleNew}
|
|
211
213
|
>
|
|
212
214
|
<FilePlus className="size-4" />
|
|
213
|
-
<span className="sr-only">
|
|
215
|
+
<span className="sr-only">{t('topBar.actions.new')}</span>
|
|
214
216
|
</Button>
|
|
215
217
|
</TooltipTrigger>
|
|
216
|
-
<TooltipContent>
|
|
218
|
+
<TooltipContent>{t('topBar.tooltips.newTemplate')}</TooltipContent>
|
|
217
219
|
</Tooltip>
|
|
218
220
|
|
|
219
221
|
<Tooltip>
|
|
@@ -225,11 +227,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
225
227
|
onClick={handleSave}
|
|
226
228
|
>
|
|
227
229
|
<Save className="size-4" />
|
|
228
|
-
<span className="sr-only">
|
|
230
|
+
<span className="sr-only">{t('topBar.actions.save')}</span>
|
|
229
231
|
</Button>
|
|
230
232
|
</TooltipTrigger>
|
|
231
233
|
<TooltipContent>
|
|
232
|
-
{templateContext
|
|
234
|
+
{templateContext
|
|
235
|
+
? t('topBar.tooltips.saveDatabase')
|
|
236
|
+
: t('topBar.tooltips.saveLocalStorage')}
|
|
233
237
|
</TooltipContent>
|
|
234
238
|
</Tooltip>
|
|
235
239
|
|
|
@@ -242,10 +246,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
242
246
|
onClick={handleExport}
|
|
243
247
|
>
|
|
244
248
|
<Download className="size-4" />
|
|
245
|
-
<span className="sr-only">
|
|
249
|
+
<span className="sr-only">{t('topBar.actions.exportJson')}</span>
|
|
246
250
|
</Button>
|
|
247
251
|
</TooltipTrigger>
|
|
248
|
-
<TooltipContent>
|
|
252
|
+
<TooltipContent>{t('topBar.tooltips.exportJson')}</TooltipContent>
|
|
249
253
|
</Tooltip>
|
|
250
254
|
|
|
251
255
|
<Tooltip>
|
|
@@ -257,10 +261,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
257
261
|
onClick={() => importRef.current?.click()}
|
|
258
262
|
>
|
|
259
263
|
<Upload className="size-4" />
|
|
260
|
-
<span className="sr-only">
|
|
264
|
+
<span className="sr-only">{t('topBar.actions.importJson')}</span>
|
|
261
265
|
</Button>
|
|
262
266
|
</TooltipTrigger>
|
|
263
|
-
<TooltipContent>
|
|
267
|
+
<TooltipContent>{t('topBar.tooltips.importJson')}</TooltipContent>
|
|
264
268
|
</Tooltip>
|
|
265
269
|
<input
|
|
266
270
|
ref={importRef}
|
|
@@ -282,11 +286,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
282
286
|
onClick={toggleSnap}
|
|
283
287
|
>
|
|
284
288
|
<Magnet className="size-4" />
|
|
285
|
-
<span className="sr-only">
|
|
289
|
+
<span className="sr-only">{t('topBar.actions.snap')}</span>
|
|
286
290
|
</Button>
|
|
287
291
|
</TooltipTrigger>
|
|
288
292
|
<TooltipContent>
|
|
289
|
-
{snapEnabled
|
|
293
|
+
{snapEnabled
|
|
294
|
+
? t('topBar.tooltips.disableSnap')
|
|
295
|
+
: t('topBar.tooltips.enableSnap')}
|
|
290
296
|
</TooltipContent>
|
|
291
297
|
</Tooltip>
|
|
292
298
|
|
|
@@ -299,11 +305,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
299
305
|
onClick={toggleGrid}
|
|
300
306
|
>
|
|
301
307
|
<Grid3X3 className="size-4" />
|
|
302
|
-
<span className="sr-only">
|
|
308
|
+
<span className="sr-only">{t('topBar.actions.grid')}</span>
|
|
303
309
|
</Button>
|
|
304
310
|
</TooltipTrigger>
|
|
305
311
|
<TooltipContent>
|
|
306
|
-
{gridEnabled
|
|
312
|
+
{gridEnabled
|
|
313
|
+
? t('topBar.tooltips.hideGrid')
|
|
314
|
+
: t('topBar.tooltips.showGrid')}
|
|
307
315
|
</TooltipContent>
|
|
308
316
|
</Tooltip>
|
|
309
317
|
|
|
@@ -316,11 +324,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
316
324
|
onClick={toggleMargins}
|
|
317
325
|
>
|
|
318
326
|
<Square className="size-4" />
|
|
319
|
-
<span className="sr-only">
|
|
327
|
+
<span className="sr-only">{t('topBar.actions.margins')}</span>
|
|
320
328
|
</Button>
|
|
321
329
|
</TooltipTrigger>
|
|
322
330
|
<TooltipContent>
|
|
323
|
-
{marginsEnabled
|
|
331
|
+
{marginsEnabled
|
|
332
|
+
? t('topBar.tooltips.hideMargins')
|
|
333
|
+
: t('topBar.tooltips.showMargins')}
|
|
324
334
|
</TooltipContent>
|
|
325
335
|
</Tooltip>
|
|
326
336
|
|
|
@@ -328,13 +338,13 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
328
338
|
|
|
329
339
|
{/* ── shortcuts hint ── */}
|
|
330
340
|
<div className="hidden items-center gap-2 text-[10px] text-muted-foreground lg:flex">
|
|
331
|
-
<span>
|
|
341
|
+
<span>{t('topBar.shortcuts.ctrlScrollZoom')}</span>
|
|
332
342
|
<span className="text-border">|</span>
|
|
333
|
-
<span>
|
|
343
|
+
<span>{t('topBar.shortcuts.spaceDragPan')}</span>
|
|
334
344
|
<span className="text-border">|</span>
|
|
335
|
-
<span>
|
|
345
|
+
<span>{t('topBar.shortcuts.delDelete')}</span>
|
|
336
346
|
<span className="text-border">|</span>
|
|
337
|
-
<span>
|
|
347
|
+
<span>{t('topBar.shortcuts.ctrlDDuplicate')}</span>
|
|
338
348
|
</div>
|
|
339
349
|
|
|
340
350
|
<div className="mx-1 h-5 w-px bg-border" />
|
|
@@ -350,10 +360,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
350
360
|
onClick={zoomOut}
|
|
351
361
|
>
|
|
352
362
|
<ZoomOut className="size-4" />
|
|
353
|
-
<span className="sr-only">
|
|
363
|
+
<span className="sr-only">{t('topBar.actions.zoomOut')}</span>
|
|
354
364
|
</Button>
|
|
355
365
|
</TooltipTrigger>
|
|
356
|
-
<TooltipContent>
|
|
366
|
+
<TooltipContent>{t('topBar.tooltips.zoomOut')}</TooltipContent>
|
|
357
367
|
</Tooltip>
|
|
358
368
|
|
|
359
369
|
<span className="w-14 text-center text-xs font-medium tabular-nums text-muted-foreground">
|
|
@@ -369,10 +379,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
369
379
|
onClick={zoomIn}
|
|
370
380
|
>
|
|
371
381
|
<ZoomIn className="size-4" />
|
|
372
|
-
<span className="sr-only">
|
|
382
|
+
<span className="sr-only">{t('topBar.actions.zoomIn')}</span>
|
|
373
383
|
</Button>
|
|
374
384
|
</TooltipTrigger>
|
|
375
|
-
<TooltipContent>
|
|
385
|
+
<TooltipContent>{t('topBar.tooltips.zoomIn')}</TooltipContent>
|
|
376
386
|
</Tooltip>
|
|
377
387
|
|
|
378
388
|
<Tooltip>
|
|
@@ -384,10 +394,10 @@ export default function Topbar({ templateContext }: TopbarProps) {
|
|
|
384
394
|
onClick={zoomReset}
|
|
385
395
|
>
|
|
386
396
|
<RotateCcw className="size-4" />
|
|
387
|
-
<span className="sr-only">
|
|
397
|
+
<span className="sr-only">{t('topBar.actions.resetZoom')}</span>
|
|
388
398
|
</Button>
|
|
389
399
|
</TooltipTrigger>
|
|
390
|
-
<TooltipContent>
|
|
400
|
+
<TooltipContent>{t('topBar.tooltips.resetZoom')}</TooltipContent>
|
|
391
401
|
</Tooltip>
|
|
392
402
|
</div>
|
|
393
403
|
</header>
|
|
@@ -1190,7 +1190,7 @@ export default function TurmaDetalhePage() {
|
|
|
1190
1190
|
setGlobalMaterials((prev) => [res.data, ...prev]);
|
|
1191
1191
|
resetGlobalLinkForm();
|
|
1192
1192
|
} catch {
|
|
1193
|
-
toast.error('
|
|
1193
|
+
toast.error(t('messages.linkAddError'));
|
|
1194
1194
|
} finally {
|
|
1195
1195
|
setGlobalSavingLink(false);
|
|
1196
1196
|
}
|
|
@@ -1265,7 +1265,7 @@ export default function TurmaDetalhePage() {
|
|
|
1265
1265
|
});
|
|
1266
1266
|
setGlobalMaterials((prev) => prev.filter((m) => m.id !== materialId));
|
|
1267
1267
|
} catch {
|
|
1268
|
-
toast.error('
|
|
1268
|
+
toast.error(t('messages.materialRemoveError'));
|
|
1269
1269
|
}
|
|
1270
1270
|
};
|
|
1271
1271
|
|
|
@@ -1653,9 +1653,9 @@ export default function TurmaDetalhePage() {
|
|
|
1653
1653
|
});
|
|
1654
1654
|
await refetchCourseDetail();
|
|
1655
1655
|
setCourseSheetOpen(false);
|
|
1656
|
-
toast.success('
|
|
1656
|
+
toast.success(t('messages.courseUpdateSuccess'));
|
|
1657
1657
|
} catch {
|
|
1658
|
-
toast.error('
|
|
1658
|
+
toast.error(t('messages.courseSaveError'));
|
|
1659
1659
|
} finally {
|
|
1660
1660
|
setSavingCourse(false);
|
|
1661
1661
|
}
|
|
@@ -1690,7 +1690,7 @@ export default function TurmaDetalhePage() {
|
|
|
1690
1690
|
);
|
|
1691
1691
|
|
|
1692
1692
|
if (selectedEligiblePeople.length === 0) {
|
|
1693
|
-
toast.error('
|
|
1693
|
+
toast.error(t('messages.noEligiblePersonFound'));
|
|
1694
1694
|
return;
|
|
1695
1695
|
}
|
|
1696
1696
|
|
|
@@ -1718,7 +1718,7 @@ export default function TurmaDetalhePage() {
|
|
|
1718
1718
|
const message = getErrorMessage(error);
|
|
1719
1719
|
|
|
1720
1720
|
if (message?.toLowerCase().includes('already enrolled')) {
|
|
1721
|
-
toast.error('
|
|
1721
|
+
toast.error(t('messages.personAlreadyEnrolled'));
|
|
1722
1722
|
} else {
|
|
1723
1723
|
toast.error(message || t('toasts.error'));
|
|
1724
1724
|
}
|
|
@@ -2208,7 +2208,7 @@ export default function TurmaDetalhePage() {
|
|
|
2208
2208
|
notifyLmsDataUpdated();
|
|
2209
2209
|
setDeleteAulaDialogOpen(false);
|
|
2210
2210
|
setAulaToDelete(null);
|
|
2211
|
-
toast.success('
|
|
2211
|
+
toast.success(t('messages.lessonRemovedSuccess'));
|
|
2212
2212
|
} catch {
|
|
2213
2213
|
toast.error(t('toasts.error'));
|
|
2214
2214
|
} finally {
|
|
@@ -2359,7 +2359,7 @@ export default function TurmaDetalhePage() {
|
|
|
2359
2359
|
}));
|
|
2360
2360
|
resetLinkForm();
|
|
2361
2361
|
} catch {
|
|
2362
|
-
toast.error('
|
|
2362
|
+
toast.error(t('messages.linkAddError'));
|
|
2363
2363
|
} finally {
|
|
2364
2364
|
setSavingLink(false);
|
|
2365
2365
|
}
|
|
@@ -2379,7 +2379,7 @@ export default function TurmaDetalhePage() {
|
|
|
2379
2379
|
}));
|
|
2380
2380
|
}
|
|
2381
2381
|
} catch {
|
|
2382
|
-
toast.error('
|
|
2382
|
+
toast.error(t('messages.materialRemoveError'));
|
|
2383
2383
|
}
|
|
2384
2384
|
};
|
|
2385
2385
|
|
|
@@ -2522,7 +2522,7 @@ export default function TurmaDetalhePage() {
|
|
|
2522
2522
|
|
|
2523
2523
|
const handleViewCourse = (): void => {
|
|
2524
2524
|
if (!courseId) {
|
|
2525
|
-
toast.error('
|
|
2525
|
+
toast.error(t('messages.classCourseNotFound'));
|
|
2526
2526
|
return;
|
|
2527
2527
|
}
|
|
2528
2528
|
|