@hed-hog/lms 0.0.354 → 0.0.357

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  2. package/dist/course/course-audio-transcription.service.js +15 -7
  3. package/dist/course/course-audio-transcription.service.js.map +1 -1
  4. package/dist/course/course-structure.controller.d.ts +12 -0
  5. package/dist/course/course-structure.controller.d.ts.map +1 -1
  6. package/dist/course/course-structure.service.d.ts +22 -0
  7. package/dist/course/course-structure.service.d.ts.map +1 -1
  8. package/dist/course/course-structure.service.js +147 -18
  9. package/dist/course/course-structure.service.js.map +1 -1
  10. package/dist/course/course.service.d.ts +4 -2
  11. package/dist/course/course.service.d.ts.map +1 -1
  12. package/dist/course/course.service.js +61 -2
  13. package/dist/course/course.service.js.map +1 -1
  14. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  15. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  16. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  17. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  18. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  19. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  20. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  21. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  22. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  23. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  24. package/dist/enterprise/training/training-student.controller.js +1 -34
  25. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  26. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  27. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  28. package/dist/enterprise/training/training-student.service.js +320 -4
  29. package/dist/enterprise/training/training-student.service.js.map +1 -1
  30. package/dist/lms.module.d.ts.map +1 -1
  31. package/dist/lms.module.js +2 -0
  32. package/dist/lms.module.js.map +1 -1
  33. package/dist/platforma/platforma.controller.d.ts +287 -0
  34. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  35. package/dist/platforma/platforma.controller.js +147 -0
  36. package/dist/platforma/platforma.controller.js.map +1 -0
  37. package/hedhog/data/route.yaml +75 -9
  38. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  39. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  40. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  41. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +14 -7
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -4
  52. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
  53. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
  54. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  55. package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
  56. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  58. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  59. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  60. package/hedhog/frontend/messages/en.json +7 -2
  61. package/hedhog/frontend/messages/pt.json +7 -2
  62. package/hedhog/table/course_lesson.yaml +2 -2
  63. package/hedhog/table/course_module.yaml +3 -0
  64. package/package.json +9 -9
  65. package/src/course/course-audio-transcription.service.ts +21 -8
  66. package/src/course/course-structure.service.ts +204 -3
  67. package/src/course/course.service.ts +67 -1
  68. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  69. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  70. package/src/enterprise/training/training-student.controller.ts +3 -27
  71. package/src/enterprise/training/training-student.service.ts +350 -2
  72. package/src/lms.module.ts +2 -0
  73. package/src/platforma/platforma.controller.ts +92 -0
@@ -8,7 +8,6 @@ import {
8
8
  EyeOff,
9
9
  Layers,
10
10
  Loader2,
11
- Lock,
12
11
  Plus,
13
12
  Save,
14
13
  Trash2,
@@ -19,7 +18,6 @@ import { useEffect, useMemo } from 'react';
19
18
  import { useForm } from 'react-hook-form';
20
19
  import { z } from 'zod';
21
20
 
22
- import { Badge } from '@/components/ui/badge';
23
21
  import { Button } from '@/components/ui/button';
24
22
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
25
23
  import {
@@ -32,16 +30,14 @@ import {
32
30
  } from '@/components/ui/form';
33
31
  import { Input } from '@/components/ui/input';
34
32
  import { ScrollArea } from '@/components/ui/scroll-area';
35
- import {
36
- Select,
37
- SelectContent,
38
- SelectItem,
39
- SelectTrigger,
40
- SelectValue,
41
- } from '@/components/ui/select';
42
33
  import { Separator } from '@/components/ui/separator';
43
34
  import { Switch } from '@/components/ui/switch';
44
35
  import { Textarea } from '@/components/ui/textarea';
36
+ import {
37
+ Tooltip,
38
+ TooltipContent,
39
+ TooltipTrigger,
40
+ } from '@/components/ui/tooltip';
45
41
 
46
42
  import {
47
43
  useCreateLessonMutation,
@@ -50,7 +46,6 @@ import {
50
46
  } from '../_data/use-course-structure-mutations';
51
47
  import { IconActionTooltip } from './icon-action-tooltip';
52
48
  import { useStructureStore } from './store';
53
- import type { Visibility } from './types';
54
49
 
55
50
  // ── Schema ────────────────────────────────────────────────────────────────────
56
51
 
@@ -59,7 +54,6 @@ const schema = z.object({
59
54
  title: z.string().min(1, 'Título obrigatório'),
60
55
  description: z.string(),
61
56
  duration: z.coerce.number().min(0),
62
- visibility: z.enum(['publico', 'privado', 'restrito'] as const),
63
57
  published: z.boolean(),
64
58
  });
65
59
 
@@ -95,8 +89,7 @@ export function EditorSession({ sessionId }: EditorSessionProps) {
95
89
  title: session?.title ?? '',
96
90
  description: session?.description ?? '',
97
91
  duration: session?.duration ?? 0,
98
- visibility: (session?.visibility ?? 'publico') as Visibility,
99
- published: session?.published ?? true,
92
+ published: session?.published ?? false,
100
93
  };
101
94
 
102
95
  const form = useForm<FormValues>({
@@ -113,8 +106,7 @@ export function EditorSession({ sessionId }: EditorSessionProps) {
113
106
  title: session.title,
114
107
  description: session.description ?? '',
115
108
  duration: session.duration,
116
- visibility: session.visibility ?? 'publico',
117
- published: session.published ?? true,
109
+ published: session.published ?? false,
118
110
  });
119
111
  }, [session?.id]); // eslint-disable-line react-hooks/exhaustive-deps
120
112
 
@@ -156,12 +148,23 @@ export function EditorSession({ sessionId }: EditorSessionProps) {
156
148
  {session.code}
157
149
  </p>
158
150
  </div>
159
- <Badge
160
- variant={session.published ? 'default' : 'secondary'}
161
- className="shrink-0 text-xs"
162
- >
163
- {session.published ? 'Publicada' : 'Oculta'}
164
- </Badge>
151
+ <Tooltip>
152
+ <TooltipTrigger asChild>
153
+ <span
154
+ className="inline-flex shrink-0 items-center"
155
+ aria-label={session.published ? 'Publicada' : 'Rascunho'}
156
+ >
157
+ {session.published ? (
158
+ <Eye className="size-4 text-emerald-600 dark:text-emerald-400" />
159
+ ) : (
160
+ <EyeOff className="size-4 text-muted-foreground" />
161
+ )}
162
+ </span>
163
+ </TooltipTrigger>
164
+ <TooltipContent>
165
+ {session.published ? 'Publicada' : 'Rascunho'}
166
+ </TooltipContent>
167
+ </Tooltip>
165
168
  <IconActionTooltip
166
169
  label="Excluir sessão"
167
170
  asWrapper={deleteSession.isPending}
@@ -285,45 +288,7 @@ export function EditorSession({ sessionId }: EditorSessionProps) {
285
288
  </CardTitle>
286
289
  </CardHeader>
287
290
  <CardContent className="px-3 pb-2">
288
- <div className="grid grid-cols-1 gap-2 items-end sm:grid-cols-2">
289
- <FormField
290
- control={form.control}
291
- name="visibility"
292
- render={({ field }) => (
293
- <FormItem>
294
- <FormLabel className="text-xs">Visibilidade</FormLabel>
295
- <Select
296
- value={field.value}
297
- onValueChange={field.onChange}
298
- >
299
- <FormControl>
300
- <SelectTrigger className="h-8 text-xs w-full">
301
- <SelectValue />
302
- </SelectTrigger>
303
- </FormControl>
304
- <SelectContent>
305
- <SelectItem value="publico">
306
- <span className="flex items-center gap-1.5">
307
- <Eye className="size-3" /> Público
308
- </span>
309
- </SelectItem>
310
- <SelectItem value="privado">
311
- <span className="flex items-center gap-1.5">
312
- <EyeOff className="size-3" /> Privado
313
- </span>
314
- </SelectItem>
315
- <SelectItem value="restrito">
316
- <span className="flex items-center gap-1.5">
317
- <Lock className="size-3" /> Restrito
318
- </span>
319
- </SelectItem>
320
- </SelectContent>
321
- </Select>
322
- <FormMessage className="text-xs" />
323
- </FormItem>
324
- )}
325
- />
326
-
291
+ <div className="grid grid-cols-1 gap-2 items-end">
327
292
  <FormField
328
293
  control={form.control}
329
294
  name="published"
@@ -63,6 +63,7 @@ export const MOCK_SESSIONS: Session[] = SESSION_CONFIG.map((s, i) => ({
63
63
  title: s.title,
64
64
  duration: s.lessonCount * 12,
65
65
  order: i,
66
+ published: true,
66
67
  }));
67
68
 
68
69
  // Lesson title patterns
@@ -72,6 +72,8 @@ interface StructureState {
72
72
  open: boolean;
73
73
  title: string;
74
74
  description: string;
75
+ confirmText: string;
76
+ destructive: boolean;
75
77
  onConfirm: (() => void) | null;
76
78
  };
77
79
 
@@ -122,6 +124,7 @@ interface StructureState {
122
124
  data: Partial<
123
125
  LessonFormValues & {
124
126
  transcription?: string;
127
+ videoConversionJobId?: number;
125
128
  resources?: Lesson['resources'];
126
129
  }
127
130
  >
@@ -183,6 +186,8 @@ interface StructureState {
183
186
  showConfirm: (opts: {
184
187
  title: string;
185
188
  description: string;
189
+ confirmText?: string;
190
+ destructive?: boolean;
186
191
  onConfirm: () => void;
187
192
  }) => void;
188
193
  closeConfirm: () => void;
@@ -282,6 +287,8 @@ export const useStructureStore = create<StructureState>((set, get) => ({
282
287
  open: false,
283
288
  title: '',
284
289
  description: '',
290
+ confirmText: '',
291
+ destructive: true,
285
292
  onConfirm: null as (() => void) | null,
286
293
  },
287
294
  sessionPickerDialog: {
@@ -380,6 +387,7 @@ export const useStructureStore = create<StructureState>((set, get) => ({
380
387
  title: `Nova Sessão ${code}`,
381
388
  duration: 30,
382
389
  order: sessions.length,
390
+ published: false,
383
391
  };
384
392
  set((s) => {
385
393
  const nextExpandedIds = new Set([...s.expandedIds, newSession.id]);
@@ -433,6 +441,7 @@ export const useStructureStore = create<StructureState>((set, get) => ({
433
441
  duration: 10,
434
442
  sessionId,
435
443
  order: sessionLessons.length,
444
+ published: false,
436
445
  resources: [],
437
446
  };
438
447
  set((s) => {
@@ -919,6 +928,8 @@ export const useStructureStore = create<StructureState>((set, get) => ({
919
928
  open: true,
920
929
  title: opts.title,
921
930
  description: opts.description,
931
+ confirmText: opts.confirmText ?? '',
932
+ destructive: opts.destructive ?? true,
922
933
  onConfirm: opts.onConfirm,
923
934
  },
924
935
  }),
@@ -328,6 +328,7 @@ function LessonMenu({ data }: { data: Lesson }) {
328
328
 
329
329
  const hasLessonClipboard = copiedType === 'lesson' && copiedIds.length > 0;
330
330
  const hasOtherSessions = sessions.some((ss) => ss.id !== data.sessionId);
331
+ const session = sessions.find((ss) => ss.id === data.sessionId);
331
332
 
332
333
  // Ids to move when "Mover" is triggered
333
334
  const idsToMove = multiLessons ? selectedLessonIds : [data.id];
@@ -373,6 +374,27 @@ function LessonMenu({ data }: { data: Lesson }) {
373
374
  Copiar aula
374
375
  </ContextMenuItem>
375
376
 
377
+ <ContextMenuItem
378
+ onSelect={async () => {
379
+ if (!session) {
380
+ toast.error('Sessão da aula não encontrada');
381
+ return;
382
+ }
383
+
384
+ const lessonCopyCode = `${session.code}_${data.code}_${data.title}`;
385
+
386
+ try {
387
+ await navigator.clipboard.writeText(lessonCopyCode);
388
+ toast.success('Código da aula copiado');
389
+ } catch {
390
+ toast.error('Não foi possível copiar o código da aula');
391
+ }
392
+ }}
393
+ >
394
+ <Clipboard className="size-3.5 mr-2 text-muted-foreground" />
395
+ Copiar código da aula
396
+ </ContextMenuItem>
397
+
376
398
  {multiLessons && (
377
399
  <ContextMenuItem
378
400
  onSelect={() => {
@@ -16,7 +16,6 @@ import {
16
16
  Film,
17
17
  HelpCircle,
18
18
  Loader2,
19
- Lock,
20
19
  Paperclip,
21
20
  ScrollText,
22
21
  Video,
@@ -32,7 +31,7 @@ import {
32
31
  } from '../_data/services/course-structure.service';
33
32
  import { HighlightedText } from './highlighted-text';
34
33
  import { useStructureStore } from './store';
35
- import type { Lesson, LessonStatus, LessonType, Visibility } from './types';
34
+ import type { Lesson, LessonStatus, LessonType } from './types';
36
35
  import {
37
36
  getLessonAttachedResourceCount,
38
37
  getLessonUploadedVideoCount,
@@ -63,15 +62,6 @@ const STATUS_CONFIG: Record<LessonStatus, { label: string; dot: string }> = {
63
62
  publicada: { label: 'Publicada', dot: 'bg-emerald-500' },
64
63
  };
65
64
 
66
- const VISIBILITY_CONFIG: Record<
67
- Visibility,
68
- { icon: LucideIcon; color: string; label: string }
69
- > = {
70
- publico: { icon: Eye, color: 'text-emerald-500', label: 'Público' },
71
- privado: { icon: EyeOff, color: 'text-muted-foreground', label: 'Privado' },
72
- restrito: { icon: Lock, color: 'text-amber-500', label: 'Restrito' },
73
- };
74
-
75
65
  // ── Props ─────────────────────────────────────────────────────────────────────
76
66
 
77
67
  interface TreeRowLessonProps {
@@ -98,9 +88,7 @@ export function TreeRowLesson({
98
88
  const { request } = useApp();
99
89
 
100
90
  const statusCfg = data.status ? STATUS_CONFIG[data.status] : null;
101
- const visibilityCfg = data.visibility
102
- ? VISIBILITY_CONFIG[data.visibility]
103
- : null;
91
+ const isPublished = data.published === true;
104
92
 
105
93
  const {
106
94
  showStatusDot,
@@ -171,7 +159,6 @@ export function TreeRowLesson({
171
159
  publicDescription: data.publicDescription,
172
160
  privateDescription: data.privateDescription,
173
161
  status: data.status,
174
- visibility: data.visibility,
175
162
  videoProvider: data.videoProvider,
176
163
  videoUrl: data.videoUrl,
177
164
  autoDuration: data.autoDuration,
@@ -215,24 +202,32 @@ export function TreeRowLesson({
215
202
  data.type !== 'video' ? (
216
203
  <Icon className={cn('size-3.5 shrink-0', cfg.color)} aria-hidden />
217
204
  ) : videoJobVisualState === 'waiting' ? (
218
- <span
219
- title="Conversão aguardando processamento"
220
- aria-label="Conversão aguardando processamento"
221
- className="inline-flex items-center"
222
- >
223
- <Clock3 className="size-3.5 shrink-0 text-amber-500" aria-hidden />
224
- </span>
205
+ <Tooltip>
206
+ <TooltipTrigger asChild>
207
+ <span
208
+ aria-label="Conversão aguardando processamento"
209
+ className="inline-flex items-center"
210
+ >
211
+ <Clock3 className="size-3.5 shrink-0 text-amber-500" aria-hidden />
212
+ </span>
213
+ </TooltipTrigger>
214
+ <TooltipContent>Conversão aguardando processamento</TooltipContent>
215
+ </Tooltip>
225
216
  ) : videoJobVisualState === 'processing' ? (
226
- <span
227
- title="Conversão em processamento"
228
- aria-label="Conversão em processamento"
229
- className="inline-flex items-center"
230
- >
231
- <Loader2
232
- className="size-3.5 shrink-0 animate-spin text-blue-500"
233
- aria-hidden
234
- />
235
- </span>
217
+ <Tooltip>
218
+ <TooltipTrigger asChild>
219
+ <span
220
+ aria-label="Conversão em processamento"
221
+ className="inline-flex items-center"
222
+ >
223
+ <Loader2
224
+ className="size-3.5 shrink-0 animate-spin text-blue-500"
225
+ aria-hidden
226
+ />
227
+ </span>
228
+ </TooltipTrigger>
229
+ <TooltipContent>Conversão em processamento</TooltipContent>
230
+ </Tooltip>
236
231
  ) : (
237
232
  <Video className={cn('size-3.5 shrink-0', cfg.color)} aria-hidden />
238
233
  );
@@ -246,7 +241,7 @@ export function TreeRowLesson({
246
241
  }}
247
242
  role="treeitem"
248
243
  aria-selected={isActive}
249
- title={`${cfg.label} · ${data.duration}min`}
244
+ aria-label={`${cfg.label} · ${data.duration}min`}
250
245
  className={cn(
251
246
  'flex items-center gap-1.5 pl-7 pr-2 rounded-md cursor-pointer select-none text-sm h-full group',
252
247
  'transition-colors duration-100',
@@ -290,39 +285,55 @@ export function TreeRowLesson({
290
285
  {durationLabel}
291
286
  </span>
292
287
  {/* Visibility icon */}
293
- {showVisibility &&
294
- visibilityCfg &&
295
- (() => {
296
- const VisIcon = visibilityCfg.icon;
297
- return (
288
+ {showVisibility && (
289
+ <Tooltip>
290
+ <TooltipTrigger asChild>
298
291
  <span
299
- title={t('visibility', { value: visibilityCfg.label })}
300
- aria-label={t('visibility', { value: visibilityCfg.label })}
292
+ aria-label={isPublished ? t('published') : t('draft')}
301
293
  className="inline-flex items-center"
302
294
  >
303
- <VisIcon
304
- className={cn('size-3 shrink-0', visibilityCfg.color)}
305
- />
295
+ {isPublished ? (
296
+ <Eye className="size-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
297
+ ) : (
298
+ <EyeOff className="size-3 shrink-0 text-muted-foreground" />
299
+ )}
306
300
  </span>
307
- );
308
- })()}
301
+ </TooltipTrigger>
302
+ <TooltipContent>
303
+ {isPublished ? t('published') : t('draft')}
304
+ </TooltipContent>
305
+ </Tooltip>
306
+ )}
309
307
  {/* Status dot */}
310
308
  {showStatusDot && statusCfg && (
311
- <span
312
- className={cn('size-1.5 rounded-full shrink-0', statusCfg.dot)}
313
- title={t('status', { value: statusCfg.label })}
314
- aria-label={t('status', { value: statusCfg.label })}
315
- />
309
+ <Tooltip>
310
+ <TooltipTrigger asChild>
311
+ <span
312
+ className={cn(
313
+ 'size-1.5 rounded-full shrink-0',
314
+ statusCfg.dot
315
+ )}
316
+ aria-label={t('status', { value: statusCfg.label })}
317
+ />
318
+ </TooltipTrigger>
319
+ <TooltipContent>
320
+ {t('status', { value: statusCfg.label })}
321
+ </TooltipContent>
322
+ </Tooltip>
316
323
  )}
317
324
  {/* Video linked indicator */}
318
325
  {showVideoIndicator && data.type === 'video' && data.videoUrl && (
319
- <span
320
- title={t('videoLinked')}
321
- aria-label={t('videoLinked')}
322
- className="inline-flex items-center"
323
- >
324
- <Film className="size-3 shrink-0 text-violet-500" />
325
- </span>
326
+ <Tooltip>
327
+ <TooltipTrigger asChild>
328
+ <span
329
+ aria-label={t('videoLinked')}
330
+ className="inline-flex items-center"
331
+ >
332
+ <Film className="size-3 shrink-0 text-violet-500" />
333
+ </span>
334
+ </TooltipTrigger>
335
+ <TooltipContent>{t('videoLinked')}</TooltipContent>
336
+ </Tooltip>
326
337
  )}
327
338
  {/* Uploaded videos indicator */}
328
339
  {showVideoIndicator && uploadedVideoCount > 0 && (
@@ -364,13 +375,17 @@ export function TreeRowLesson({
364
375
  {showTranscriptionIndicator &&
365
376
  data.type === 'video' &&
366
377
  data.transcription && (
367
- <span
368
- title={t('hasTranscription')}
369
- aria-label={t('hasTranscription')}
370
- className="inline-flex items-center"
371
- >
372
- <ScrollText className="size-3 shrink-0 text-emerald-500" />
373
- </span>
378
+ <Tooltip>
379
+ <TooltipTrigger asChild>
380
+ <span
381
+ aria-label={t('hasTranscription')}
382
+ className="inline-flex items-center"
383
+ >
384
+ <ScrollText className="size-3 shrink-0 text-emerald-500" />
385
+ </span>
386
+ </TooltipTrigger>
387
+ <TooltipContent>{t('hasTranscription')}</TooltipContent>
388
+ </Tooltip>
374
389
  )}
375
390
  </div>
376
391
  )}
@@ -10,6 +10,8 @@ import { cn } from '@/lib/utils';
10
10
  import {
11
11
  ChevronDown,
12
12
  ChevronRight,
13
+ Eye,
14
+ EyeOff,
13
15
  Layers,
14
16
  Paperclip,
15
17
  Video,
@@ -54,7 +56,7 @@ export function TreeRowSession({
54
56
  const cancelRename = useStructureStore((s) => s.cancelRename);
55
57
  const startRename = useStructureStore((s) => s.startRename);
56
58
  const renameItem = useStructureStore((s) => s.renameItem);
57
- const { showCode } = useTreeDisplaySettings();
59
+ const { showCode, showVisibility } = useTreeDisplaySettings();
58
60
 
59
61
  const updateSession = useUpdateSessionMutation();
60
62
 
@@ -79,8 +81,7 @@ export function TreeRowSession({
79
81
  duration: data.duration,
80
82
  code: data.code,
81
83
  description: '',
82
- visibility: data.visibility ?? 'publico',
83
- published: data.published ?? false,
84
+ published: data.published,
84
85
  },
85
86
  });
86
87
  }
@@ -96,6 +97,7 @@ export function TreeRowSession({
96
97
 
97
98
  const durationH = Math.floor((data.duration ?? 0) / 60);
98
99
  const durationM = (data.duration ?? 0) % 60;
100
+ const isPublished = data.published === true;
99
101
  const durationLabel =
100
102
  durationH > 0
101
103
  ? `${durationH}h${durationM > 0 ? ` ${durationM}m` : ''}`
@@ -138,7 +140,12 @@ export function TreeRowSession({
138
140
 
139
141
  {/* Session icon */}
140
142
  <Layers
141
- className="size-3.5 shrink-0 text-blue-500 dark:text-blue-400"
143
+ className={cn(
144
+ 'size-3.5 shrink-0 transition-colors',
145
+ isPublished
146
+ ? 'text-emerald-600 dark:text-emerald-400'
147
+ : 'text-muted-foreground/70'
148
+ )}
142
149
  aria-hidden
143
150
  />
144
151
 
@@ -162,6 +169,26 @@ export function TreeRowSession({
162
169
  {/* Meta badges (hidden during rename) */}
163
170
  {!isRenaming && (
164
171
  <div className="flex items-center gap-1 shrink-0">
172
+ {showVisibility && (
173
+ <Tooltip>
174
+ <TooltipTrigger asChild>
175
+ <span
176
+ className="inline-flex items-center"
177
+ aria-label={isPublished ? t('published') : t('draft')}
178
+ >
179
+ {isPublished ? (
180
+ <Eye className="size-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
181
+ ) : (
182
+ <EyeOff className="size-3 shrink-0 text-muted-foreground" />
183
+ )}
184
+ </span>
185
+ </TooltipTrigger>
186
+ <TooltipContent>
187
+ {isPublished ? t('published') : t('draft')}
188
+ </TooltipContent>
189
+ </Tooltip>
190
+ )}
191
+
165
192
  {showCode && (
166
193
  <span className="inline-flex items-center rounded border border-border/60 bg-muted/60 px-1 text-[0.6rem] font-mono text-muted-foreground leading-4">
167
194
  {data.code}
@@ -207,16 +234,6 @@ export function TreeRowSession({
207
234
  </TooltipContent>
208
235
  </Tooltip>
209
236
  )}
210
- {/* Published status dot */}
211
- <span
212
- className={cn(
213
- 'size-1.5 rounded-full shrink-0',
214
- data.published === false
215
- ? 'bg-muted-foreground/30'
216
- : 'bg-emerald-500'
217
- )}
218
- title={data.published === false ? t('draft') : t('published')}
219
- />
220
237
  </div>
221
238
  )}
222
239
  </div>
@@ -35,8 +35,7 @@ export interface Session {
35
35
  description?: string;
36
36
  duration: number;
37
37
  order: number;
38
- visibility?: Visibility;
39
- published?: boolean;
38
+ published: boolean;
40
39
  }
41
40
 
42
41
  export interface Resource {
@@ -95,6 +94,7 @@ export interface Lesson {
95
94
  order: number;
96
95
  status?: LessonStatus;
97
96
  visibility?: Visibility;
97
+ published?: boolean;
98
98
  // Video
99
99
  videoProvider?: VideoProvider;
100
100
  videoUrl?: string;
@@ -127,7 +127,6 @@ export type SessionFormValues = {
127
127
  title: string;
128
128
  description: string;
129
129
  duration: number;
130
- visibility: Visibility;
131
130
  published: boolean;
132
131
  };
133
132
 
@@ -139,7 +138,7 @@ export type LessonFormValues = {
139
138
  type: LessonType;
140
139
  duration: number;
141
140
  status: LessonStatus;
142
- visibility: Visibility;
141
+ published: boolean;
143
142
  videoProvider?: VideoProvider;
144
143
  videoUrl?: string;
145
144
  autoDuration?: boolean;