@hed-hog/lms 0.0.329 → 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/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -8
- 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/certificates/models/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +27 -27
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +8 -6
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- 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/editor-lesson.tsx.ejs +228 -152
- 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 +78 -36
- 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/[id]/page.tsx.ejs +37 -41
- 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/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
- package/hedhog/frontend/app/instructors/page.tsx.ejs +75 -54
- 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 +899 -45
- package/hedhog/frontend/messages/pt.json +894 -38
- 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
|
@@ -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>
|
|
@@ -789,7 +789,7 @@ export default function ModelsPage() {
|
|
|
789
789
|
}
|
|
790
790
|
}}
|
|
791
791
|
>
|
|
792
|
-
<SheetContent className="overflow-y-auto sm:max-w-xl">
|
|
792
|
+
<SheetContent className="overflow-y-auto sm:max-w-xl">
|
|
793
793
|
<SheetHeader>
|
|
794
794
|
<SheetTitle>{t('editSheet.title')}</SheetTitle>
|
|
795
795
|
<SheetDescription>{t('editSheet.description')}</SheetDescription>
|
|
@@ -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
|
|
|
@@ -2452,13 +2452,13 @@ export default function TurmaDetalhePage() {
|
|
|
2452
2452
|
},
|
|
2453
2453
|
{
|
|
2454
2454
|
key: 'next-session',
|
|
2455
|
-
title: '
|
|
2455
|
+
title: t('kpis.nextClass'),
|
|
2456
2456
|
value: nextSession
|
|
2457
2457
|
? format(nextSessionStartsAt!, 'dd/MM', { locale: dateLocale })
|
|
2458
|
-
: '
|
|
2458
|
+
: t('kpis.noClass'),
|
|
2459
2459
|
description: nextSession
|
|
2460
2460
|
? `${nextSession.horaInicio} - ${nextSession.horaFim}`
|
|
2461
|
-
: '
|
|
2461
|
+
: t('kpis.createNext'),
|
|
2462
2462
|
icon: Clock,
|
|
2463
2463
|
iconContainerClassName: 'bg-emerald-500/10 text-emerald-700',
|
|
2464
2464
|
accentClassName: 'from-emerald-500/25 via-green-500/10 to-transparent',
|
|
@@ -2466,9 +2466,9 @@ export default function TurmaDetalhePage() {
|
|
|
2466
2466
|
},
|
|
2467
2467
|
{
|
|
2468
2468
|
key: 'calendar',
|
|
2469
|
-
title: '
|
|
2469
|
+
title: t('kpis.calendar'),
|
|
2470
2470
|
value: totalSessionsCount,
|
|
2471
|
-
description:
|
|
2471
|
+
description: t('kpis.completed', { count: completedSessionsCount }),
|
|
2472
2472
|
icon: CalendarIcon,
|
|
2473
2473
|
iconContainerClassName: 'bg-violet-500/10 text-violet-700',
|
|
2474
2474
|
accentClassName: 'from-violet-500/25 via-violet-500/10 to-transparent',
|
|
@@ -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
|
|
|
@@ -2582,7 +2582,7 @@ export default function TurmaDetalhePage() {
|
|
|
2582
2582
|
variant: 'outline',
|
|
2583
2583
|
},
|
|
2584
2584
|
{
|
|
2585
|
-
label: '
|
|
2585
|
+
label: t('actions.editClass'),
|
|
2586
2586
|
onClick: () => setEditSheetOpen(true),
|
|
2587
2587
|
variant: 'default',
|
|
2588
2588
|
},
|
|
@@ -2660,7 +2660,7 @@ export default function TurmaDetalhePage() {
|
|
|
2660
2660
|
key={i}
|
|
2661
2661
|
className="overflow-hidden border-border/70 py-0"
|
|
2662
2662
|
>
|
|
2663
|
-
<div className="h-1 w-full bg-
|
|
2663
|
+
<div className="h-1 w-full bg-linear-to-r from-slate-300/70 via-slate-200 to-transparent" />
|
|
2664
2664
|
<CardContent className="p-4">
|
|
2665
2665
|
<Skeleton className="mb-2 h-8 w-16" />
|
|
2666
2666
|
<Skeleton className="h-4 w-28" />
|
|
@@ -2803,7 +2803,7 @@ export default function TurmaDetalhePage() {
|
|
|
2803
2803
|
#
|
|
2804
2804
|
</TableHead>
|
|
2805
2805
|
<TableHead>Sessão</TableHead>
|
|
2806
|
-
<TableHead className="w-
|
|
2806
|
+
<TableHead className="w-30">Data</TableHead>
|
|
2807
2807
|
<TableHead className="w-[105px]">Horário</TableHead>
|
|
2808
2808
|
<TableHead className="w-[90px]">Tipo</TableHead>
|
|
2809
2809
|
<TableHead>Instrutor</TableHead>
|
|
@@ -2814,7 +2814,7 @@ export default function TurmaDetalhePage() {
|
|
|
2814
2814
|
Materiais
|
|
2815
2815
|
</TableHead>
|
|
2816
2816
|
<TableHead className="w-[90px]">Link</TableHead>
|
|
2817
|
-
<TableHead className="w-
|
|
2817
|
+
<TableHead className="w-11" />
|
|
2818
2818
|
</TableRow>
|
|
2819
2819
|
</TableHeader>
|
|
2820
2820
|
<TableBody>
|
|
@@ -3359,7 +3359,7 @@ export default function TurmaDetalhePage() {
|
|
|
3359
3359
|
src={getPersonAvatarUrl(aluno.avatarId)}
|
|
3360
3360
|
alt={aluno.nome}
|
|
3361
3361
|
/>
|
|
3362
|
-
<AvatarFallback className="bg-
|
|
3362
|
+
<AvatarFallback className="bg-linear-to-br from-blue-100 to-blue-200 text-[11px] font-medium text-blue-700">
|
|
3363
3363
|
{getPersonInitials(aluno.nome)}
|
|
3364
3364
|
</AvatarFallback>
|
|
3365
3365
|
</Avatar>
|
|
@@ -3616,7 +3616,7 @@ export default function TurmaDetalhePage() {
|
|
|
3616
3616
|
{tClasses(`type.${aula.tipo}`)}
|
|
3617
3617
|
</Badge>
|
|
3618
3618
|
</TableCell>
|
|
3619
|
-
<TableCell className="max-w-
|
|
3619
|
+
<TableCell className="max-w-45 truncate text-sm text-muted-foreground">
|
|
3620
3620
|
{aula.meetingUrl || aula.local || '—'}
|
|
3621
3621
|
</TableCell>
|
|
3622
3622
|
<TableCell className="text-sm text-muted-foreground">
|
|
@@ -3857,7 +3857,7 @@ export default function TurmaDetalhePage() {
|
|
|
3857
3857
|
<Table>
|
|
3858
3858
|
<TableHeader>
|
|
3859
3859
|
<TableRow className="bg-muted/40 hover:bg-muted/40">
|
|
3860
|
-
<TableHead className="sticky left-0 z-10 min-w-
|
|
3860
|
+
<TableHead className="sticky left-0 z-10 min-w-65 bg-muted/40 font-semibold">
|
|
3861
3861
|
{t('tabs.students')}
|
|
3862
3862
|
</TableHead>
|
|
3863
3863
|
{aulasState.map((aula) => {
|
|
@@ -3886,14 +3886,14 @@ export default function TurmaDetalhePage() {
|
|
|
3886
3886
|
key={aluno.id}
|
|
3887
3887
|
className="hover:bg-muted/30"
|
|
3888
3888
|
>
|
|
3889
|
-
<TableCell className="sticky left-0 z-10 min-w-
|
|
3889
|
+
<TableCell className="sticky left-0 z-10 min-w-65 border-r border-border/40 bg-background">
|
|
3890
3890
|
<div className="flex min-w-0 items-center gap-3">
|
|
3891
3891
|
<Avatar className="size-8 shrink-0">
|
|
3892
3892
|
<AvatarImage
|
|
3893
3893
|
src={getPersonAvatarUrl(aluno.avatarId)}
|
|
3894
3894
|
alt={aluno.nome}
|
|
3895
3895
|
/>
|
|
3896
|
-
<AvatarFallback className="bg-
|
|
3896
|
+
<AvatarFallback className="bg-linear-to-br from-blue-100 to-blue-200 text-[11px] font-medium text-blue-700">
|
|
3897
3897
|
{getPersonInitials(aluno.nome)}
|
|
3898
3898
|
</AvatarFallback>
|
|
3899
3899
|
</Avatar>
|
|
@@ -4195,7 +4195,7 @@ export default function TurmaDetalhePage() {
|
|
|
4195
4195
|
globalLinkTitleEditedRef.current =
|
|
4196
4196
|
e.target.value.length > 0;
|
|
4197
4197
|
}}
|
|
4198
|
-
placeholder=
|
|
4198
|
+
placeholder={t('links.studyRefPlaceholder')}
|
|
4199
4199
|
/>
|
|
4200
4200
|
</Field>
|
|
4201
4201
|
<div className="flex gap-2">
|
|
@@ -4615,7 +4615,7 @@ export default function TurmaDetalhePage() {
|
|
|
4615
4615
|
src={getPersonAvatarUrl(selectedStudentProfile.avatarId)}
|
|
4616
4616
|
alt={selectedStudentProfile.nome}
|
|
4617
4617
|
/>
|
|
4618
|
-
<AvatarFallback className="bg-
|
|
4618
|
+
<AvatarFallback className="bg-linear-to-br from-blue-100 to-blue-200 text-xs font-medium text-blue-700">
|
|
4619
4619
|
{getPersonInitials(selectedStudentProfile.nome)}
|
|
4620
4620
|
</AvatarFallback>
|
|
4621
4621
|
</Avatar>
|
|
@@ -5655,7 +5655,7 @@ export default function TurmaDetalhePage() {
|
|
|
5655
5655
|
linkTitleEditedRef.current =
|
|
5656
5656
|
e.target.value.length > 0;
|
|
5657
5657
|
}}
|
|
5658
|
-
placeholder=
|
|
5658
|
+
placeholder={t('links.lessonSlidesPlaceholder')}
|
|
5659
5659
|
/>
|
|
5660
5660
|
</Field>
|
|
5661
5661
|
<div className="flex gap-2">
|
|
@@ -1522,7 +1522,7 @@ export default function TurmasPage() {
|
|
|
1522
1522
|
setCourseSheetOpen(false);
|
|
1523
1523
|
toast.success(courseSheetT('toasts.courseCreated'));
|
|
1524
1524
|
} catch {
|
|
1525
|
-
toast.error('
|
|
1525
|
+
toast.error(t('messages.classCreateCourseError'));
|
|
1526
1526
|
} finally {
|
|
1527
1527
|
setSavingCourse(false);
|
|
1528
1528
|
}
|
|
@@ -1606,7 +1606,7 @@ export default function TurmasPage() {
|
|
|
1606
1606
|
notifyLmsDashboardUpdated();
|
|
1607
1607
|
setSheetOpen(false);
|
|
1608
1608
|
} catch {
|
|
1609
|
-
toast.error('
|
|
1609
|
+
toast.error(t('messages.classSaveError'));
|
|
1610
1610
|
} finally {
|
|
1611
1611
|
setSaving(false);
|
|
1612
1612
|
}
|
|
@@ -1627,7 +1627,7 @@ export default function TurmasPage() {
|
|
|
1627
1627
|
setTurmaToDelete(null);
|
|
1628
1628
|
setDeleteDialogOpen(false);
|
|
1629
1629
|
} catch {
|
|
1630
|
-
toast.error('
|
|
1630
|
+
toast.error(t('messages.classDeleteError'));
|
|
1631
1631
|
}
|
|
1632
1632
|
}
|
|
1633
1633
|
|
|
@@ -2321,9 +2321,15 @@ export default function TurmasPage() {
|
|
|
2321
2321
|
entityLabel={t('form.fields.course.label')}
|
|
2322
2322
|
initialSelectedLabel={selectedCourseTitle}
|
|
2323
2323
|
searchPlaceholder={t('form.fields.course.placeholder')}
|
|
2324
|
-
emptyStateDescription=
|
|
2325
|
-
|
|
2326
|
-
|
|
2324
|
+
emptyStateDescription={t(
|
|
2325
|
+
'components.entityPicker.courses.empty'
|
|
2326
|
+
)}
|
|
2327
|
+
loadingLabel={t(
|
|
2328
|
+
'components.entityPicker.courses.loading'
|
|
2329
|
+
)}
|
|
2330
|
+
noResultsLabel={t(
|
|
2331
|
+
'components.entityPicker.courses.empty'
|
|
2332
|
+
)}
|
|
2327
2333
|
showCreateButton={false}
|
|
2328
2334
|
renderOption={({ option }) => (
|
|
2329
2335
|
<div className="flex items-center gap-2 py-0.5">
|
|
@@ -2398,7 +2404,7 @@ export default function TurmasPage() {
|
|
|
2398
2404
|
size="icon"
|
|
2399
2405
|
className="shrink-0"
|
|
2400
2406
|
onClick={openCourseCreateSheet}
|
|
2401
|
-
aria-label=
|
|
2407
|
+
aria-label={courseSheetT('actions.createCourse')}
|
|
2402
2408
|
>
|
|
2403
2409
|
<Plus className="h-4 w-4" />
|
|
2404
2410
|
</Button>
|
|
@@ -2833,8 +2839,10 @@ export default function TurmasPage() {
|
|
|
2833
2839
|
placeholder={t('form.fields.professor.placeholder')}
|
|
2834
2840
|
initialSelectedLabel={watchedFormValues.professor ?? ''}
|
|
2835
2841
|
searchPlaceholder={t('form.fields.professor.placeholder')}
|
|
2836
|
-
emptyStateDescription=
|
|
2837
|
-
|
|
2842
|
+
emptyStateDescription={t(
|
|
2843
|
+
'form.fields.professor.emptyState'
|
|
2844
|
+
)}
|
|
2845
|
+
noResultsLabel={t('form.fields.professor.noResults')}
|
|
2838
2846
|
showCreateButton={false}
|
|
2839
2847
|
clearable={false}
|
|
2840
2848
|
getOptionValue={(opt) => opt.id}
|
|
@@ -2952,7 +2960,7 @@ export default function TurmasPage() {
|
|
|
2952
2960
|
size="icon"
|
|
2953
2961
|
className="shrink-0"
|
|
2954
2962
|
onClick={() => setCreateProfessorDialogOpen(true)}
|
|
2955
|
-
aria-label=
|
|
2963
|
+
aria-label={t('sheet.lessonForm.createInstructor')}
|
|
2956
2964
|
>
|
|
2957
2965
|
<Plus className="h-4 w-4" />
|
|
2958
2966
|
</Button>
|
|
@@ -2991,11 +2999,11 @@ export default function TurmasPage() {
|
|
|
2991
2999
|
open={createProfessorDialogOpen}
|
|
2992
3000
|
onOpenChange={setCreateProfessorDialogOpen}
|
|
2993
3001
|
onCreated={handleProfessorCreated}
|
|
2994
|
-
title=
|
|
2995
|
-
description=
|
|
2996
|
-
submitLabel=
|
|
2997
|
-
successMessage=
|
|
2998
|
-
errorMessage=
|
|
3002
|
+
title={t('sheet.lessonForm.createInstructorTitle')}
|
|
3003
|
+
description={t('sheet.lessonForm.createInstructorDescription')}
|
|
3004
|
+
submitLabel={t('sheet.lessonForm.createInstructorSubmit')}
|
|
3005
|
+
successMessage={t('sheet.lessonForm.createInstructorSuccess')}
|
|
3006
|
+
errorMessage={t('sheet.lessonForm.createInstructorError')}
|
|
2999
3007
|
defaultQualificationSlugs={['class-sessions']}
|
|
3000
3008
|
/>
|
|
3001
3009
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
2
|
+
import { Badge } from '@/components/ui/badge';
|
|
2
3
|
import { Button } from '@/components/ui/button';
|
|
3
4
|
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
4
|
-
import { Badge } from '@/components/ui/badge';
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
6
|
import { Grip, Plus, X } from 'lucide-react';
|
|
7
7
|
import { useMemo, useState } from 'react';
|
|
@@ -211,7 +211,7 @@ export function CourseMultiEntityPicker({
|
|
|
211
211
|
variant="secondary"
|
|
212
212
|
className="flex items-center gap-1.5 rounded-full border border-border/60 bg-background px-3 py-1.5 text-xs font-medium text-foreground"
|
|
213
213
|
>
|
|
214
|
-
<span className="max-w-
|
|
214
|
+
<span className="max-w-45 truncate">{option.label}</span>
|
|
215
215
|
<button
|
|
216
216
|
type="button"
|
|
217
217
|
className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
|
|
@@ -27,8 +27,10 @@ import {
|
|
|
27
27
|
RefreshCw,
|
|
28
28
|
Video,
|
|
29
29
|
} from 'lucide-react';
|
|
30
|
+
import { useTranslations } from 'next-intl';
|
|
30
31
|
import { useRouter } from 'next/navigation';
|
|
31
32
|
|
|
33
|
+
import { CourseAvatar } from '../../_components/course-avatar';
|
|
32
34
|
import { ConfirmDialog } from './structure/_components/confirm-dialog';
|
|
33
35
|
import { CourseTreePanel } from './structure/_components/course-tree-panel';
|
|
34
36
|
import { CourseTreeSkeleton } from './structure/_components/course-tree-skeleton';
|
|
@@ -42,7 +44,6 @@ import {
|
|
|
42
44
|
import { useStructureStore } from './structure/_components/store';
|
|
43
45
|
import { TreeDisplaySettingsPopover } from './structure/_components/tree-display-settings-popover';
|
|
44
46
|
import { useCourseStructureShortcuts } from './structure/_components/use-course-structure-shortcuts';
|
|
45
|
-
import { CourseAvatar } from '../../_components/course-avatar';
|
|
46
47
|
import {
|
|
47
48
|
useDuplicateLessonMutation,
|
|
48
49
|
useDuplicateSessionMutation,
|
|
@@ -66,6 +67,7 @@ type ApiCourseSummary = {
|
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
export default function CourseStructurePage({ params }: Props) {
|
|
70
|
+
const t = useTranslations('lms.CoursesPage.StructurePage');
|
|
69
71
|
const { id } = use(params);
|
|
70
72
|
const isMobile = useIsMobile();
|
|
71
73
|
const router = useRouter();
|
|
@@ -259,8 +261,8 @@ export default function CourseStructurePage({ params }: Props) {
|
|
|
259
261
|
<Page>
|
|
260
262
|
<PageHeader
|
|
261
263
|
breadcrumbs={[
|
|
262
|
-
{ label: '
|
|
263
|
-
{ label: '
|
|
264
|
+
{ label: t('breadcrumbs.lms'), href: '/lms' },
|
|
265
|
+
{ label: t('breadcrumbs.courses'), href: '/lms/courses' },
|
|
264
266
|
{ label: course.name },
|
|
265
267
|
]}
|
|
266
268
|
title={
|
|
@@ -349,10 +351,10 @@ export default function CourseStructurePage({ params }: Props) {
|
|
|
349
351
|
variant="outline"
|
|
350
352
|
onClick={() => setMobileSheetOpen(true)}
|
|
351
353
|
className="gap-2"
|
|
352
|
-
aria-label=
|
|
354
|
+
aria-label={t('mobile.openStructure')}
|
|
353
355
|
>
|
|
354
356
|
<Menu className="size-4" />
|
|
355
|
-
|
|
357
|
+
{t('breadcrumbs.structure')}
|
|
356
358
|
</Button>
|
|
357
359
|
</div>
|
|
358
360
|
|
|
@@ -367,7 +369,7 @@ export default function CourseStructurePage({ params }: Props) {
|
|
|
367
369
|
<SheetContent side="left" className="w-[320px] p-0 flex flex-col">
|
|
368
370
|
<SheetHeader className="px-4 py-3 border-b shrink-0">
|
|
369
371
|
<SheetTitle className="text-sm">
|
|
370
|
-
{course.title}
|
|
372
|
+
{t('mobile.sheetTitle', { title: course.title })}
|
|
371
373
|
</SheetTitle>
|
|
372
374
|
</SheetHeader>
|
|
373
375
|
<div className="flex-1 min-h-0 overflow-hidden">
|