@hed-hog/lms 0.0.314 → 0.0.315

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 (68) hide show
  1. package/dist/class-group/class-group.controller.d.ts +2 -2
  2. package/dist/class-group/class-group.service.d.ts +2 -2
  3. package/dist/enterprise/dto/enterprise-profile.dto.d.ts +13 -0
  4. package/dist/enterprise/dto/enterprise-profile.dto.d.ts.map +1 -0
  5. package/dist/enterprise/dto/enterprise-profile.dto.js +3 -0
  6. package/dist/enterprise/dto/enterprise-profile.dto.js.map +1 -0
  7. package/dist/enterprise/enterprise.controller.d.ts +3 -0
  8. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  9. package/dist/enterprise/enterprise.controller.js +14 -0
  10. package/dist/enterprise/enterprise.controller.js.map +1 -1
  11. package/dist/enterprise/enterprise.service.d.ts +3 -0
  12. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  13. package/dist/enterprise/enterprise.service.js +128 -1
  14. package/dist/enterprise/enterprise.service.js.map +1 -1
  15. package/dist/instructor/instructor.controller.d.ts +23 -0
  16. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  17. package/dist/instructor/instructor.controller.js +41 -0
  18. package/dist/instructor/instructor.controller.js.map +1 -1
  19. package/dist/instructor/instructor.service.d.ts +25 -0
  20. package/dist/instructor/instructor.service.d.ts.map +1 -1
  21. package/dist/instructor/instructor.service.js +126 -8
  22. package/dist/instructor/instructor.service.js.map +1 -1
  23. package/hedhog/data/menu.yaml +23 -7
  24. package/hedhog/data/role.yaml +9 -1
  25. package/hedhog/data/route.yaml +54 -0
  26. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -1
  27. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +44 -44
  28. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -362
  29. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -111
  30. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -134
  31. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -113
  32. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -314
  33. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -62
  34. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +173 -173
  35. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -58
  36. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +51 -51
  37. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -276
  38. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -1216
  39. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1824 -1824
  40. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -443
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +40 -40
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -185
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -264
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +95 -95
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +73 -73
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -136
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -80
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +949 -949
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -525
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +181 -181
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +51 -51
  52. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -271
  53. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -167
  54. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -108
  55. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -318
  56. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -10
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +2 -1
  58. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +438 -0
  59. package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +40 -0
  60. package/hedhog/frontend/app/instructors/page.tsx.ejs +696 -0
  61. package/hedhog/frontend/app/training/page.tsx.ejs +2339 -0
  62. package/hedhog/table/enterprise_user.yaml +1 -1
  63. package/package.json +8 -8
  64. package/src/enterprise/dto/enterprise-profile.dto.ts +17 -0
  65. package/src/enterprise/enterprise.controller.ts +9 -1
  66. package/src/enterprise/enterprise.service.ts +147 -4
  67. package/src/instructor/instructor.controller.ts +36 -9
  68. package/src/instructor/instructor.service.ts +140 -10
@@ -1,52 +1,52 @@
1
- 'use client';
2
-
3
- import { DragOverlay as DndDragOverlay } from '@dnd-kit/core';
4
- import { cn } from '@/lib/utils';
5
- import { GripVertical, Layers, Video, FileText, HelpCircle } from 'lucide-react';
6
- import type { FlatItem } from './types';
7
-
8
- interface DragOverlayContentProps {
9
- item: FlatItem;
10
- }
11
-
12
- function DragOverlayContent({ item }: DragOverlayContentProps) {
13
- const isSession = item.type === 'session';
14
- const isLesson = item.type === 'lesson';
15
-
16
- const lessonIconMap = {
17
- video: <Video className="size-3.5 text-blue-500 shrink-0" />,
18
- post: <FileText className="size-3.5 text-emerald-500 shrink-0" />,
19
- questao: <HelpCircle className="size-3.5 text-amber-500 shrink-0" />,
20
- exercicio: <Layers className="size-3.5 text-rose-500 shrink-0" />,
21
- } as const;
22
-
23
- return (
24
- <div
25
- className={cn(
26
- 'flex items-center gap-1.5 px-2 py-1.5 rounded-md text-sm',
27
- 'bg-card border shadow-lg ring-1 ring-primary/20',
28
- 'opacity-95 max-w-[260px]',
29
- isLesson && 'pl-4'
30
- )}
31
- >
32
- <GripVertical className="size-3.5 text-muted-foreground/60 shrink-0" />
33
-
34
- {isSession && <Layers className="size-3.5 text-muted-foreground shrink-0" />}
35
- {isLesson && item.type === 'lesson' && lessonIconMap[item.data.type]}
36
-
37
- <span className="truncate font-medium text-xs">{item.data.title}</span>
38
- </div>
39
- );
40
- }
41
-
42
- interface TreeDragOverlayProps {
43
- activeItem: FlatItem | null;
44
- }
45
-
46
- export function TreeDragOverlay({ activeItem }: TreeDragOverlayProps) {
47
- return (
48
- <DndDragOverlay dropAnimation={{ duration: 150, easing: 'ease' }}>
49
- {activeItem ? <DragOverlayContent item={activeItem} /> : null}
50
- </DndDragOverlay>
51
- );
1
+ 'use client';
2
+
3
+ import { DragOverlay as DndDragOverlay } from '@dnd-kit/core';
4
+ import { cn } from '@/lib/utils';
5
+ import { GripVertical, Layers, Video, FileText, HelpCircle } from 'lucide-react';
6
+ import type { FlatItem } from './types';
7
+
8
+ interface DragOverlayContentProps {
9
+ item: FlatItem;
10
+ }
11
+
12
+ function DragOverlayContent({ item }: DragOverlayContentProps) {
13
+ const isSession = item.type === 'session';
14
+ const isLesson = item.type === 'lesson';
15
+
16
+ const lessonIconMap = {
17
+ video: <Video className="size-3.5 text-blue-500 shrink-0" />,
18
+ post: <FileText className="size-3.5 text-emerald-500 shrink-0" />,
19
+ questao: <HelpCircle className="size-3.5 text-amber-500 shrink-0" />,
20
+ exercicio: <Layers className="size-3.5 text-rose-500 shrink-0" />,
21
+ } as const;
22
+
23
+ return (
24
+ <div
25
+ className={cn(
26
+ 'flex items-center gap-1.5 px-2 py-1.5 rounded-md text-sm',
27
+ 'bg-card border shadow-lg ring-1 ring-primary/20',
28
+ 'opacity-95 max-w-[260px]',
29
+ isLesson && 'pl-4'
30
+ )}
31
+ >
32
+ <GripVertical className="size-3.5 text-muted-foreground/60 shrink-0" />
33
+
34
+ {isSession && <Layers className="size-3.5 text-muted-foreground shrink-0" />}
35
+ {isLesson && item.type === 'lesson' && lessonIconMap[item.data.type]}
36
+
37
+ <span className="truncate font-medium text-xs">{item.data.title}</span>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ interface TreeDragOverlayProps {
43
+ activeItem: FlatItem | null;
44
+ }
45
+
46
+ export function TreeDragOverlay({ activeItem }: TreeDragOverlayProps) {
47
+ return (
48
+ <DndDragOverlay dropAnimation={{ duration: 150, easing: 'ease' }}>
49
+ {activeItem ? <DragOverlayContent item={activeItem} /> : null}
50
+ </DndDragOverlay>
51
+ );
52
52
  }
@@ -1,276 +1,276 @@
1
- 'use client';
2
-
3
- import {
4
- Eye,
5
- EyeOff,
6
- FolderInput,
7
- Layers,
8
- Lock,
9
- Save,
10
- Video,
11
- X,
12
- } from 'lucide-react';
13
- import { useState } from 'react';
14
- import { toast } from 'sonner';
15
-
16
- import { Badge } from '@/components/ui/badge';
17
- import { Button } from '@/components/ui/button';
18
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
19
- import { ScrollArea } from '@/components/ui/scroll-area';
20
- import {
21
- Select,
22
- SelectContent,
23
- SelectItem,
24
- SelectTrigger,
25
- SelectValue,
26
- } from '@/components/ui/select';
27
- import { Separator } from '@/components/ui/separator';
28
- import { cn } from '@/lib/utils';
29
-
30
- import { useStructureStore } from './store';
31
- import type { LessonStatus, Visibility } from './types';
32
-
33
- // ── Config ────────────────────────────────────────────────────────────────────
34
-
35
- const STATUS_LABELS: Record<LessonStatus, string> = {
36
- preparada: 'Preparada',
37
- gravada: 'Gravada',
38
- editada: 'Editada',
39
- finalizada: 'Finalizada',
40
- publicada: 'Publicada',
41
- };
42
-
43
- // ── Component ─────────────────────────────────────────────────────────────────
44
-
45
- export function EditorBulk() {
46
- const selectedIds = useStructureStore((s) => s.selectedIds);
47
- const sessions = useStructureStore((s) => s.sessions);
48
- const lessons = useStructureStore((s) => s.lessons);
49
- const clearSelection = useStructureStore((s) => s.clearSelection);
50
-
51
- const selectedArray = [...selectedIds];
52
- const lessonIds = selectedArray.filter((id) =>
53
- lessons.some((l) => l.id === id)
54
- );
55
- const sessionIds = selectedArray.filter((id) =>
56
- sessions.some((s) => s.id === id)
57
- );
58
-
59
- const allLessons = lessonIds.length === selectedArray.length;
60
- const allSessions = sessionIds.length === selectedArray.length;
61
-
62
- // Local form state (mock — not wired to store actions yet)
63
- const [status, setStatus] = useState<LessonStatus | ''>('');
64
- const [visibility, setVisibility] = useState<Visibility | ''>('');
65
- const [targetSession, setTargetSession] = useState<string>('');
66
-
67
- function handleSave() {
68
- const lines: string[] = [];
69
- if (status) lines.push(`status → ${STATUS_LABELS[status as LessonStatus]}`);
70
- if (visibility) lines.push(`visibilidade → ${visibility}`);
71
- if (targetSession)
72
- lines.push(
73
- `mover para sessão ${sessions.find((s) => s.id === targetSession)?.title ?? targetSession}`
74
- );
75
- toast.success(
76
- lines.length
77
- ? `Edição em massa: ${lines.join(', ')} (mock)`
78
- : 'Nenhuma alteração selecionada'
79
- );
80
- }
81
-
82
- return (
83
- <div className="flex flex-col h-full min-h-0">
84
- {/* ── Header ───────────────────────────────────────────────────────── */}
85
- <div className="flex items-center gap-3 px-4 py-3 border-b bg-muted/30 shrink-0">
86
- <div className="flex size-9 items-center justify-center rounded-lg bg-primary/10 shrink-0">
87
- {allLessons ? (
88
- <Video className="size-4 text-primary" />
89
- ) : allSessions ? (
90
- <Layers className="size-4 text-primary" />
91
- ) : (
92
- <Layers className="size-4 text-primary" />
93
- )}
94
- </div>
95
- <div className="flex-1 min-w-0">
96
- <p className="text-sm font-semibold">Edição em massa</p>
97
- <p className="text-[0.65rem] text-muted-foreground">
98
- {selectedIds.size}{' '}
99
- {allLessons ? 'aulas' : allSessions ? 'sessões' : 'itens'}{' '}
100
- selecionados
101
- </p>
102
- </div>
103
- <Button
104
- variant="ghost"
105
- size="icon"
106
- className="size-7 shrink-0"
107
- onClick={clearSelection}
108
- title="Limpar seleção"
109
- >
110
- <X className="size-3.5" />
111
- </Button>
112
- </div>
113
-
114
- {/* ── Body ─────────────────────────────────────────────────────────── */}
115
- <ScrollArea className="flex-1 min-h-0">
116
- <div className="flex flex-col gap-3 p-3">
117
- {/* Badges dos itens selecionados */}
118
- <div className="flex flex-wrap gap-1">
119
- {selectedArray.slice(0, 12).map((id) => {
120
- const lesson = lessons.find((l) => l.id === id);
121
- const session = sessions.find((s) => s.id === id);
122
- const label = lesson?.code ?? session?.code ?? id;
123
- const isLesson = !!lesson;
124
- return (
125
- <Badge
126
- key={id}
127
- variant="secondary"
128
- className={cn(
129
- 'text-[0.65rem] h-5',
130
- isLesson
131
- ? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
132
- : 'bg-muted'
133
- )}
134
- >
135
- {label}
136
- </Badge>
137
- );
138
- })}
139
- {selectedArray.length > 12 && (
140
- <Badge variant="outline" className="text-[0.65rem] h-5">
141
- +{selectedArray.length - 12} mais
142
- </Badge>
143
- )}
144
- </div>
145
-
146
- {/* Status — só mostra para aulas */}
147
- {(allLessons || (!allSessions && lessonIds.length > 0)) && (
148
- <Card>
149
- <CardHeader className="px-3 pt-3 pb-2">
150
- <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
151
- Status de produção
152
- </CardTitle>
153
- </CardHeader>
154
- <CardContent className="px-3 pb-3">
155
- <Select
156
- value={status}
157
- onValueChange={(v) => setStatus(v as LessonStatus)}
158
- >
159
- <SelectTrigger className="h-8 text-xs w-full">
160
- <SelectValue placeholder="Manter atual" />
161
- </SelectTrigger>
162
- <SelectContent>
163
- <SelectItem value="preparada">Preparada</SelectItem>
164
- <SelectItem value="gravada">Gravada</SelectItem>
165
- <SelectItem value="editada">Editada</SelectItem>
166
- <SelectItem value="finalizada">Finalizada</SelectItem>
167
- <SelectItem value="publicada">Publicada</SelectItem>
168
- </SelectContent>
169
- </Select>
170
- </CardContent>
171
- </Card>
172
- )}
173
-
174
- {/* Visibilidade */}
175
- <Card>
176
- <CardHeader className="px-3 pt-3 pb-2">
177
- <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
178
- Visibilidade
179
- </CardTitle>
180
- </CardHeader>
181
- <CardContent className="px-3 pb-3">
182
- <Select
183
- value={visibility}
184
- onValueChange={(v) => setVisibility(v as Visibility)}
185
- >
186
- <SelectTrigger className="h-8 text-xs w-full">
187
- <SelectValue placeholder="Manter atual" />
188
- </SelectTrigger>
189
- <SelectContent>
190
- <SelectItem value="publico">
191
- <span className="flex items-center gap-1.5">
192
- <Eye className="size-3" /> Público
193
- </span>
194
- </SelectItem>
195
- <SelectItem value="privado">
196
- <span className="flex items-center gap-1.5">
197
- <EyeOff className="size-3" /> Privado
198
- </span>
199
- </SelectItem>
200
- <SelectItem value="restrito">
201
- <span className="flex items-center gap-1.5">
202
- <Lock className="size-3" /> Restrito
203
- </span>
204
- </SelectItem>
205
- </SelectContent>
206
- </Select>
207
- </CardContent>
208
- </Card>
209
-
210
- {/* Mover para sessão — só para aulas */}
211
- {(allLessons || lessonIds.length > 0) && (
212
- <Card>
213
- <CardHeader className="px-3 pt-3 pb-2">
214
- <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
215
- <FolderInput className="size-3" />
216
- Mover para sessão
217
- </CardTitle>
218
- </CardHeader>
219
- <CardContent className="px-3 pb-3">
220
- <Select value={targetSession} onValueChange={setTargetSession}>
221
- <SelectTrigger className="h-8 text-xs w-full">
222
- <SelectValue placeholder="Selecionar sessão…" />
223
- </SelectTrigger>
224
- <SelectContent>
225
- {sessions.map((s) => (
226
- <SelectItem key={s.id} value={s.id}>
227
- <span className="font-mono mr-1 text-muted-foreground">
228
- {s.code}
229
- </span>
230
- {s.title}
231
- </SelectItem>
232
- ))}
233
- </SelectContent>
234
- </Select>
235
- </CardContent>
236
- </Card>
237
- )}
238
-
239
- <div className="rounded-md border border-amber-200 bg-amber-50/60 dark:border-amber-800 dark:bg-amber-950/30 px-3 py-2">
240
- <p className="text-[0.65rem] text-amber-700 dark:text-amber-400">
241
- As alterações em massa ainda não estão integradas com a API.
242
- Clique em salvar para ver a pré-visualização.
243
- </p>
244
- </div>
245
- </div>
246
- </ScrollArea>
247
-
248
- {/* ── Footer ───────────────────────────────────────────────────────── */}
249
- <div className="shrink-0 border-t bg-background">
250
- <Separator />
251
- <div className="flex items-center gap-2 px-3 py-2">
252
- <Button
253
- type="button"
254
- variant="ghost"
255
- size="sm"
256
- className="h-7 text-xs"
257
- onClick={clearSelection}
258
- >
259
- <X className="size-3 mr-1" />
260
- Limpar seleção
261
- </Button>
262
- <div className="flex-1" />
263
- <Button
264
- type="button"
265
- size="sm"
266
- className="h-7 text-xs"
267
- onClick={handleSave}
268
- >
269
- <Save className="size-3 mr-1" />
270
- Aplicar (mock)
271
- </Button>
272
- </div>
273
- </div>
274
- </div>
275
- );
276
- }
1
+ 'use client';
2
+
3
+ import {
4
+ Eye,
5
+ EyeOff,
6
+ FolderInput,
7
+ Layers,
8
+ Lock,
9
+ Save,
10
+ Video,
11
+ X,
12
+ } from 'lucide-react';
13
+ import { useState } from 'react';
14
+ import { toast } from 'sonner';
15
+
16
+ import { Badge } from '@/components/ui/badge';
17
+ import { Button } from '@/components/ui/button';
18
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
19
+ import { ScrollArea } from '@/components/ui/scroll-area';
20
+ import {
21
+ Select,
22
+ SelectContent,
23
+ SelectItem,
24
+ SelectTrigger,
25
+ SelectValue,
26
+ } from '@/components/ui/select';
27
+ import { Separator } from '@/components/ui/separator';
28
+ import { cn } from '@/lib/utils';
29
+
30
+ import { useStructureStore } from './store';
31
+ import type { LessonStatus, Visibility } from './types';
32
+
33
+ // ── Config ────────────────────────────────────────────────────────────────────
34
+
35
+ const STATUS_LABELS: Record<LessonStatus, string> = {
36
+ preparada: 'Preparada',
37
+ gravada: 'Gravada',
38
+ editada: 'Editada',
39
+ finalizada: 'Finalizada',
40
+ publicada: 'Publicada',
41
+ };
42
+
43
+ // ── Component ─────────────────────────────────────────────────────────────────
44
+
45
+ export function EditorBulk() {
46
+ const selectedIds = useStructureStore((s) => s.selectedIds);
47
+ const sessions = useStructureStore((s) => s.sessions);
48
+ const lessons = useStructureStore((s) => s.lessons);
49
+ const clearSelection = useStructureStore((s) => s.clearSelection);
50
+
51
+ const selectedArray = [...selectedIds];
52
+ const lessonIds = selectedArray.filter((id) =>
53
+ lessons.some((l) => l.id === id)
54
+ );
55
+ const sessionIds = selectedArray.filter((id) =>
56
+ sessions.some((s) => s.id === id)
57
+ );
58
+
59
+ const allLessons = lessonIds.length === selectedArray.length;
60
+ const allSessions = sessionIds.length === selectedArray.length;
61
+
62
+ // Local form state (mock — not wired to store actions yet)
63
+ const [status, setStatus] = useState<LessonStatus | ''>('');
64
+ const [visibility, setVisibility] = useState<Visibility | ''>('');
65
+ const [targetSession, setTargetSession] = useState<string>('');
66
+
67
+ function handleSave() {
68
+ const lines: string[] = [];
69
+ if (status) lines.push(`status → ${STATUS_LABELS[status as LessonStatus]}`);
70
+ if (visibility) lines.push(`visibilidade → ${visibility}`);
71
+ if (targetSession)
72
+ lines.push(
73
+ `mover para sessão ${sessions.find((s) => s.id === targetSession)?.title ?? targetSession}`
74
+ );
75
+ toast.success(
76
+ lines.length
77
+ ? `Edição em massa: ${lines.join(', ')} (mock)`
78
+ : 'Nenhuma alteração selecionada'
79
+ );
80
+ }
81
+
82
+ return (
83
+ <div className="flex flex-col h-full min-h-0">
84
+ {/* ── Header ───────────────────────────────────────────────────────── */}
85
+ <div className="flex items-center gap-3 px-4 py-3 border-b bg-muted/30 shrink-0">
86
+ <div className="flex size-9 items-center justify-center rounded-lg bg-primary/10 shrink-0">
87
+ {allLessons ? (
88
+ <Video className="size-4 text-primary" />
89
+ ) : allSessions ? (
90
+ <Layers className="size-4 text-primary" />
91
+ ) : (
92
+ <Layers className="size-4 text-primary" />
93
+ )}
94
+ </div>
95
+ <div className="flex-1 min-w-0">
96
+ <p className="text-sm font-semibold">Edição em massa</p>
97
+ <p className="text-[0.65rem] text-muted-foreground">
98
+ {selectedIds.size}{' '}
99
+ {allLessons ? 'aulas' : allSessions ? 'sessões' : 'itens'}{' '}
100
+ selecionados
101
+ </p>
102
+ </div>
103
+ <Button
104
+ variant="ghost"
105
+ size="icon"
106
+ className="size-7 shrink-0"
107
+ onClick={clearSelection}
108
+ title="Limpar seleção"
109
+ >
110
+ <X className="size-3.5" />
111
+ </Button>
112
+ </div>
113
+
114
+ {/* ── Body ─────────────────────────────────────────────────────────── */}
115
+ <ScrollArea className="flex-1 min-h-0">
116
+ <div className="flex flex-col gap-3 p-3">
117
+ {/* Badges dos itens selecionados */}
118
+ <div className="flex flex-wrap gap-1">
119
+ {selectedArray.slice(0, 12).map((id) => {
120
+ const lesson = lessons.find((l) => l.id === id);
121
+ const session = sessions.find((s) => s.id === id);
122
+ const label = lesson?.code ?? session?.code ?? id;
123
+ const isLesson = !!lesson;
124
+ return (
125
+ <Badge
126
+ key={id}
127
+ variant="secondary"
128
+ className={cn(
129
+ 'text-[0.65rem] h-5',
130
+ isLesson
131
+ ? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
132
+ : 'bg-muted'
133
+ )}
134
+ >
135
+ {label}
136
+ </Badge>
137
+ );
138
+ })}
139
+ {selectedArray.length > 12 && (
140
+ <Badge variant="outline" className="text-[0.65rem] h-5">
141
+ +{selectedArray.length - 12} mais
142
+ </Badge>
143
+ )}
144
+ </div>
145
+
146
+ {/* Status — só mostra para aulas */}
147
+ {(allLessons || (!allSessions && lessonIds.length > 0)) && (
148
+ <Card>
149
+ <CardHeader className="px-3 pt-3 pb-2">
150
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
151
+ Status de produção
152
+ </CardTitle>
153
+ </CardHeader>
154
+ <CardContent className="px-3 pb-3">
155
+ <Select
156
+ value={status}
157
+ onValueChange={(v) => setStatus(v as LessonStatus)}
158
+ >
159
+ <SelectTrigger className="h-8 text-xs w-full">
160
+ <SelectValue placeholder="Manter atual" />
161
+ </SelectTrigger>
162
+ <SelectContent>
163
+ <SelectItem value="preparada">Preparada</SelectItem>
164
+ <SelectItem value="gravada">Gravada</SelectItem>
165
+ <SelectItem value="editada">Editada</SelectItem>
166
+ <SelectItem value="finalizada">Finalizada</SelectItem>
167
+ <SelectItem value="publicada">Publicada</SelectItem>
168
+ </SelectContent>
169
+ </Select>
170
+ </CardContent>
171
+ </Card>
172
+ )}
173
+
174
+ {/* Visibilidade */}
175
+ <Card>
176
+ <CardHeader className="px-3 pt-3 pb-2">
177
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
178
+ Visibilidade
179
+ </CardTitle>
180
+ </CardHeader>
181
+ <CardContent className="px-3 pb-3">
182
+ <Select
183
+ value={visibility}
184
+ onValueChange={(v) => setVisibility(v as Visibility)}
185
+ >
186
+ <SelectTrigger className="h-8 text-xs w-full">
187
+ <SelectValue placeholder="Manter atual" />
188
+ </SelectTrigger>
189
+ <SelectContent>
190
+ <SelectItem value="publico">
191
+ <span className="flex items-center gap-1.5">
192
+ <Eye className="size-3" /> Público
193
+ </span>
194
+ </SelectItem>
195
+ <SelectItem value="privado">
196
+ <span className="flex items-center gap-1.5">
197
+ <EyeOff className="size-3" /> Privado
198
+ </span>
199
+ </SelectItem>
200
+ <SelectItem value="restrito">
201
+ <span className="flex items-center gap-1.5">
202
+ <Lock className="size-3" /> Restrito
203
+ </span>
204
+ </SelectItem>
205
+ </SelectContent>
206
+ </Select>
207
+ </CardContent>
208
+ </Card>
209
+
210
+ {/* Mover para sessão — só para aulas */}
211
+ {(allLessons || lessonIds.length > 0) && (
212
+ <Card>
213
+ <CardHeader className="px-3 pt-3 pb-2">
214
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
215
+ <FolderInput className="size-3" />
216
+ Mover para sessão
217
+ </CardTitle>
218
+ </CardHeader>
219
+ <CardContent className="px-3 pb-3">
220
+ <Select value={targetSession} onValueChange={setTargetSession}>
221
+ <SelectTrigger className="h-8 text-xs w-full">
222
+ <SelectValue placeholder="Selecionar sessão…" />
223
+ </SelectTrigger>
224
+ <SelectContent>
225
+ {sessions.map((s) => (
226
+ <SelectItem key={s.id} value={s.id}>
227
+ <span className="font-mono mr-1 text-muted-foreground">
228
+ {s.code}
229
+ </span>
230
+ {s.title}
231
+ </SelectItem>
232
+ ))}
233
+ </SelectContent>
234
+ </Select>
235
+ </CardContent>
236
+ </Card>
237
+ )}
238
+
239
+ <div className="rounded-md border border-amber-200 bg-amber-50/60 dark:border-amber-800 dark:bg-amber-950/30 px-3 py-2">
240
+ <p className="text-[0.65rem] text-amber-700 dark:text-amber-400">
241
+ As alterações em massa ainda não estão integradas com a API.
242
+ Clique em salvar para ver a pré-visualização.
243
+ </p>
244
+ </div>
245
+ </div>
246
+ </ScrollArea>
247
+
248
+ {/* ── Footer ───────────────────────────────────────────────────────── */}
249
+ <div className="shrink-0 border-t bg-background">
250
+ <Separator />
251
+ <div className="flex items-center gap-2 px-3 py-2">
252
+ <Button
253
+ type="button"
254
+ variant="ghost"
255
+ size="sm"
256
+ className="h-7 text-xs"
257
+ onClick={clearSelection}
258
+ >
259
+ <X className="size-3 mr-1" />
260
+ Limpar seleção
261
+ </Button>
262
+ <div className="flex-1" />
263
+ <Button
264
+ type="button"
265
+ size="sm"
266
+ className="h-7 text-xs"
267
+ onClick={handleSave}
268
+ >
269
+ <Save className="size-3 mr-1" />
270
+ Aplicar (mock)
271
+ </Button>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ );
276
+ }