@hed-hog/lms 0.0.314 → 0.0.316

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 (67) hide show
  1. package/dist/enterprise/dto/enterprise-profile.dto.d.ts +13 -0
  2. package/dist/enterprise/dto/enterprise-profile.dto.d.ts.map +1 -0
  3. package/dist/enterprise/dto/enterprise-profile.dto.js +3 -0
  4. package/dist/enterprise/dto/enterprise-profile.dto.js.map +1 -0
  5. package/dist/enterprise/enterprise.controller.d.ts +3 -0
  6. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  7. package/dist/enterprise/enterprise.controller.js +14 -0
  8. package/dist/enterprise/enterprise.controller.js.map +1 -1
  9. package/dist/enterprise/enterprise.service.d.ts +3 -0
  10. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  11. package/dist/enterprise/enterprise.service.js +128 -1
  12. package/dist/enterprise/enterprise.service.js.map +1 -1
  13. package/dist/instructor/instructor.controller.d.ts +23 -0
  14. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  15. package/dist/instructor/instructor.controller.js +41 -0
  16. package/dist/instructor/instructor.controller.js.map +1 -1
  17. package/dist/instructor/instructor.service.d.ts +25 -0
  18. package/dist/instructor/instructor.service.d.ts.map +1 -1
  19. package/dist/instructor/instructor.service.js +126 -8
  20. package/dist/instructor/instructor.service.js.map +1 -1
  21. package/hedhog/data/menu.yaml +23 -7
  22. package/hedhog/data/role.yaml +17 -1
  23. package/hedhog/data/route.yaml +48 -0
  24. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -1
  25. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +44 -44
  26. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -362
  27. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -111
  28. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -134
  29. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -113
  30. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -314
  31. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -62
  32. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +173 -173
  33. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -58
  34. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +51 -51
  35. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -276
  36. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -1216
  37. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1824 -1824
  38. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -443
  39. package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +40 -40
  40. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -185
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -264
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +95 -95
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +73 -73
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -136
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -80
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +949 -949
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -525
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +181 -181
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +51 -51
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -271
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -167
  52. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -108
  53. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -318
  54. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -10
  55. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +2 -1
  56. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +438 -0
  57. package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +40 -0
  58. package/hedhog/frontend/app/instructors/page.tsx.ejs +696 -0
  59. package/hedhog/frontend/app/training/page.tsx.ejs +2339 -0
  60. package/hedhog/query/add_route_role.sql +15 -0
  61. package/hedhog/table/enterprise_user.yaml +1 -1
  62. package/package.json +6 -6
  63. package/src/enterprise/dto/enterprise-profile.dto.ts +17 -0
  64. package/src/enterprise/enterprise.controller.ts +9 -1
  65. package/src/enterprise/enterprise.service.ts +147 -4
  66. package/src/instructor/instructor.controller.ts +36 -9
  67. 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
+ }