@hed-hog/studio 0.0.285

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/index.d.ts +4 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +20 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/studio.controller.d.ts +79 -0
  6. package/dist/studio.controller.d.ts.map +1 -0
  7. package/dist/studio.controller.js +186 -0
  8. package/dist/studio.controller.js.map +1 -0
  9. package/dist/studio.module.d.ts +3 -0
  10. package/dist/studio.module.d.ts.map +1 -0
  11. package/dist/studio.module.js +33 -0
  12. package/dist/studio.module.js.map +1 -0
  13. package/dist/studio.service.d.ts +76 -0
  14. package/dist/studio.service.d.ts.map +1 -0
  15. package/dist/studio.service.js +98 -0
  16. package/dist/studio.service.js.map +1 -0
  17. package/hedhog/data/menu.yaml +114 -0
  18. package/hedhog/data/role.yaml +7 -0
  19. package/hedhog/data/route.yaml +152 -0
  20. package/hedhog/data/setting_group.yaml +8 -0
  21. package/hedhog/frontend/app/_components/studio-status-badge.tsx.ejs +14 -0
  22. package/hedhog/frontend/app/_lib/mocks.ts.ejs +209 -0
  23. package/hedhog/frontend/app/_lib/status.ts.ejs +38 -0
  24. package/hedhog/frontend/app/_lib/types.ts.ejs +148 -0
  25. package/hedhog/frontend/app/assets/page.tsx.ejs +117 -0
  26. package/hedhog/frontend/app/editing/page.tsx.ejs +55 -0
  27. package/hedhog/frontend/app/incidents/page.tsx.ejs +59 -0
  28. package/hedhog/frontend/app/page.tsx.ejs +207 -0
  29. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +323 -0
  30. package/hedhog/frontend/app/projects/form/page.tsx.ejs +129 -0
  31. package/hedhog/frontend/app/projects/page.tsx.ejs +169 -0
  32. package/hedhog/frontend/app/publication/page.tsx.ejs +62 -0
  33. package/hedhog/frontend/app/scenes/form/page.tsx.ejs +110 -0
  34. package/hedhog/frontend/app/sessions/[id]/page.tsx.ejs +118 -0
  35. package/hedhog/frontend/app/sessions/form/page.tsx.ejs +103 -0
  36. package/hedhog/frontend/app/sessions/page.tsx.ejs +80 -0
  37. package/hedhog/frontend/app/storage-profiles/form/page.tsx.ejs +100 -0
  38. package/hedhog/frontend/app/storage-profiles/page.tsx.ejs +80 -0
  39. package/hedhog/frontend/app/takes/[id]/page.tsx.ejs +143 -0
  40. package/hedhog/frontend/app/takes/page.tsx.ejs +74 -0
  41. package/hedhog/table/capture_agent.yaml +48 -0
  42. package/hedhog/table/edit_composition.yaml +67 -0
  43. package/hedhog/table/edit_pipeline.yaml +46 -0
  44. package/hedhog/table/edit_stage_execution.yaml +46 -0
  45. package/hedhog/table/editor_delivery_package.yaml +63 -0
  46. package/hedhog/table/ingestion_job.yaml +74 -0
  47. package/hedhog/table/media_asset.yaml +130 -0
  48. package/hedhog/table/participant_command_ack.yaml +32 -0
  49. package/hedhog/table/participant_recording.yaml +67 -0
  50. package/hedhog/table/production_binding.yaml +34 -0
  51. package/hedhog/table/production_project.yaml +104 -0
  52. package/hedhog/table/publication_target.yaml +44 -0
  53. package/hedhog/table/recorded_file.yaml +74 -0
  54. package/hedhog/table/recording_command.yaml +72 -0
  55. package/hedhog/table/recording_incident.yaml +65 -0
  56. package/hedhog/table/recording_session.yaml +86 -0
  57. package/hedhog/table/scene.yaml +64 -0
  58. package/hedhog/table/scene_take.yaml +83 -0
  59. package/hedhog/table/session_participant.yaml +55 -0
  60. package/hedhog/table/storage_profile.yaml +75 -0
  61. package/hedhog/table/sync_marker.yaml +44 -0
  62. package/package.json +38 -0
  63. package/src/index.ts +4 -0
  64. package/src/language/en.json +8 -0
  65. package/src/language/pt.json +8 -0
  66. package/src/studio.controller.ts +94 -0
  67. package/src/studio.module.ts +20 -0
  68. package/src/studio.service.ts +102 -0
@@ -0,0 +1,152 @@
1
+ - url: /studio/data
2
+ method: GET
3
+ relations:
4
+ role:
5
+ - where:
6
+ slug: admin
7
+ - where:
8
+ slug: admin-studio
9
+
10
+ - url: /studio/projects
11
+ method: GET
12
+ relations:
13
+ role:
14
+ - where:
15
+ slug: admin
16
+ - where:
17
+ slug: admin-studio
18
+
19
+ - url: /studio/projects/:id
20
+ method: GET
21
+ relations:
22
+ role:
23
+ - where:
24
+ slug: admin
25
+ - where:
26
+ slug: admin-studio
27
+
28
+ - url: /studio/projects/form
29
+ method: GET
30
+ relations:
31
+ role:
32
+ - where:
33
+ slug: admin
34
+ - where:
35
+ slug: admin-studio
36
+
37
+ - url: /studio/scenes
38
+ method: GET
39
+ relations:
40
+ role:
41
+ - where:
42
+ slug: admin
43
+ - where:
44
+ slug: admin-studio
45
+
46
+ - url: /studio/scenes/form
47
+ method: GET
48
+ relations:
49
+ role:
50
+ - where:
51
+ slug: admin
52
+ - where:
53
+ slug: admin-studio
54
+
55
+ - url: /studio/sessions
56
+ method: GET
57
+ relations:
58
+ role:
59
+ - where:
60
+ slug: admin
61
+ - where:
62
+ slug: admin-studio
63
+
64
+ - url: /studio/sessions/:id
65
+ method: GET
66
+ relations:
67
+ role:
68
+ - where:
69
+ slug: admin
70
+ - where:
71
+ slug: admin-studio
72
+
73
+ - url: /studio/sessions/form
74
+ method: GET
75
+ relations:
76
+ role:
77
+ - where:
78
+ slug: admin
79
+ - where:
80
+ slug: admin-studio
81
+
82
+ - url: /studio/takes
83
+ method: GET
84
+ relations:
85
+ role:
86
+ - where:
87
+ slug: admin
88
+ - where:
89
+ slug: admin-studio
90
+
91
+ - url: /studio/takes/:id
92
+ method: GET
93
+ relations:
94
+ role:
95
+ - where:
96
+ slug: admin
97
+ - where:
98
+ slug: admin-studio
99
+
100
+ - url: /studio/assets
101
+ method: GET
102
+ relations:
103
+ role:
104
+ - where:
105
+ slug: admin
106
+ - where:
107
+ slug: admin-studio
108
+
109
+ - url: /studio/storage-profiles
110
+ method: GET
111
+ relations:
112
+ role:
113
+ - where:
114
+ slug: admin
115
+ - where:
116
+ slug: admin-studio
117
+
118
+ - url: /studio/storage-profiles/form
119
+ method: GET
120
+ relations:
121
+ role:
122
+ - where:
123
+ slug: admin
124
+ - where:
125
+ slug: admin-studio
126
+
127
+ - url: /studio/incidents
128
+ method: GET
129
+ relations:
130
+ role:
131
+ - where:
132
+ slug: admin
133
+ - where:
134
+ slug: admin-studio
135
+
136
+ - url: /studio/editing
137
+ method: GET
138
+ relations:
139
+ role:
140
+ - where:
141
+ slug: admin
142
+ - where:
143
+ slug: admin-studio
144
+
145
+ - url: /studio/publication
146
+ method: GET
147
+ relations:
148
+ role:
149
+ - where:
150
+ slug: admin
151
+ - where:
152
+ slug: admin-studio
@@ -0,0 +1,8 @@
1
+ - slug: studio
2
+ name:
3
+ en: Studio
4
+ pt: Studio
5
+ description:
6
+ en: Studio module settings.
7
+ pt: Configuracoes do modulo Studio.
8
+ icon: clapperboard
@@ -0,0 +1,14 @@
1
+ import { Badge } from '@/components/ui/badge';
2
+ import { getStudioBadgeClass, toStudioLabel } from '../_lib/status';
3
+
4
+ type StudioStatusBadgeProps = {
5
+ value: string;
6
+ };
7
+
8
+ export function StudioStatusBadge({ value }: StudioStatusBadgeProps) {
9
+ return (
10
+ <Badge variant="secondary" className={getStudioBadgeClass(value)}>
11
+ {toStudioLabel(value)}
12
+ </Badge>
13
+ );
14
+ }
@@ -0,0 +1,209 @@
1
+ import {
2
+ PublicationTarget,
3
+ StorageProfile,
4
+ StudioAsset,
5
+ StudioIncident,
6
+ StudioProject,
7
+ StudioScene,
8
+ StudioSession,
9
+ StudioTake,
10
+ } from './types';
11
+
12
+ export const STUDIO_PROJECTS: StudioProject[] = [
13
+ {
14
+ id: 101,
15
+ title: 'LMS Aula 07 - API de Pagamentos',
16
+ projectType: 'course_lesson',
17
+ status: 'in_recording',
18
+ currentStage: 'recording',
19
+ binding: 'lms.lesson #4401',
20
+ owner: 'Ana Lima',
21
+ updatedAt: '2026-03-17T18:20:00Z',
22
+ },
23
+ {
24
+ id: 102,
25
+ title: 'Campanha Black Week - Teaser Vertical',
26
+ projectType: 'social_content',
27
+ status: 'in_editing',
28
+ currentStage: 'rough_cut',
29
+ binding: 'campaign #BW-2026',
30
+ owner: 'Bruno Costa',
31
+ updatedAt: '2026-03-17T17:03:00Z',
32
+ },
33
+ {
34
+ id: 103,
35
+ title: 'Video Institucional Produto Atlas',
36
+ projectType: 'standalone',
37
+ status: 'ready_to_publish',
38
+ currentStage: 'publication',
39
+ binding: 'external_reference ATLAS-VID',
40
+ owner: 'Camila Ramos',
41
+ updatedAt: '2026-03-16T14:31:00Z',
42
+ },
43
+ ];
44
+
45
+ export const STUDIO_SCENES: StudioScene[] = [
46
+ {
47
+ id: 1,
48
+ projectId: 101,
49
+ name: 'Intro do modulo',
50
+ status: 'recorded',
51
+ estimatedSeconds: 90,
52
+ takes: 3,
53
+ },
54
+ {
55
+ id: 2,
56
+ projectId: 101,
57
+ name: 'Demonstracao em tela',
58
+ status: 'ready_to_record',
59
+ estimatedSeconds: 420,
60
+ takes: 1,
61
+ },
62
+ {
63
+ id: 3,
64
+ projectId: 101,
65
+ name: 'Resumo final',
66
+ status: 'pending',
67
+ estimatedSeconds: 120,
68
+ takes: 0,
69
+ },
70
+ ];
71
+
72
+ export const STUDIO_SESSIONS: StudioSession[] = [
73
+ {
74
+ id: 501,
75
+ projectId: 101,
76
+ title: 'Sessao principal - Aula 07',
77
+ status: 'recording',
78
+ sessionType: 'remote_collaborative',
79
+ host: 'Ana Lima',
80
+ participants: 4,
81
+ roomKey: 'studio-room-7a',
82
+ startedAt: '2026-03-17T18:00:00Z',
83
+ },
84
+ {
85
+ id: 502,
86
+ projectId: 102,
87
+ title: 'Sessao teaser vertical',
88
+ status: 'finished',
89
+ sessionType: 'hybrid',
90
+ host: 'Bruno Costa',
91
+ participants: 3,
92
+ roomKey: 'studio-room-bw',
93
+ startedAt: '2026-03-16T15:20:00Z',
94
+ },
95
+ ];
96
+
97
+ export const STUDIO_TAKES: StudioTake[] = [
98
+ {
99
+ id: 9001,
100
+ projectId: 101,
101
+ sceneName: 'Intro do modulo',
102
+ sessionTitle: 'Sessao principal - Aula 07',
103
+ status: 'selected',
104
+ participants: 4,
105
+ expectedFiles: 12,
106
+ uploadedFiles: 12,
107
+ integrity: 'ok',
108
+ selected: true,
109
+ },
110
+ {
111
+ id: 9002,
112
+ projectId: 101,
113
+ sceneName: 'Demonstracao em tela',
114
+ sessionTitle: 'Sessao principal - Aula 07',
115
+ status: 'partial',
116
+ participants: 4,
117
+ expectedFiles: 12,
118
+ uploadedFiles: 9,
119
+ integrity: 'warning',
120
+ selected: false,
121
+ },
122
+ ];
123
+
124
+ export const STUDIO_ASSETS: StudioAsset[] = [
125
+ {
126
+ id: 1,
127
+ participant: 'Ana Lima',
128
+ takeId: 9001,
129
+ sourceType: 'camera_video',
130
+ sizeMb: 650,
131
+ durationSec: 92,
132
+ storage: 'studio-main',
133
+ availability: 'available',
134
+ origin: 'uploaded_raw',
135
+ },
136
+ {
137
+ id: 2,
138
+ participant: 'Bruno Costa',
139
+ takeId: 9002,
140
+ sourceType: 'screen_capture',
141
+ sizeMb: 1200,
142
+ durationSec: 410,
143
+ storage: 'studio-main',
144
+ availability: 'pending',
145
+ origin: 'uploaded_raw',
146
+ },
147
+ ];
148
+
149
+ export const STUDIO_STORAGE_PROFILES: StorageProfile[] = [
150
+ {
151
+ id: 1,
152
+ name: 'studio-main',
153
+ provider: 's3',
154
+ bucket: 'hedhog-studio-prod',
155
+ region: 'us-east-1',
156
+ basePath: 'studio/raw',
157
+ status: 'active',
158
+ testStatus: 'success',
159
+ lastTestedAt: '2026-03-17T12:20:00Z',
160
+ },
161
+ {
162
+ id: 2,
163
+ name: 'studio-proxy',
164
+ provider: 'r2',
165
+ bucket: 'hedhog-proxy',
166
+ region: 'auto',
167
+ basePath: 'studio/proxy',
168
+ status: 'active',
169
+ testStatus: 'unknown',
170
+ lastTestedAt: '2026-03-15T08:00:00Z',
171
+ },
172
+ ];
173
+
174
+ export const STUDIO_INCIDENTS: StudioIncident[] = [
175
+ {
176
+ id: 1,
177
+ projectId: 101,
178
+ type: 'upload_failed',
179
+ severity: 'high',
180
+ target: 'take 9002 / participante Bruno',
181
+ status: 'open',
182
+ detectedAt: '2026-03-17T18:34:00Z',
183
+ },
184
+ {
185
+ id: 2,
186
+ projectId: 101,
187
+ type: 'delayed_start',
188
+ severity: 'medium',
189
+ target: 'sessao 501',
190
+ status: 'resolved',
191
+ detectedAt: '2026-03-17T18:02:00Z',
192
+ },
193
+ ];
194
+
195
+ export const STUDIO_PUBLICATION_TARGETS: PublicationTarget[] = [
196
+ {
197
+ id: 1,
198
+ projectId: 103,
199
+ targetType: 'youtube',
200
+ status: 'scheduled',
201
+ scheduledAt: '2026-03-18T19:00:00Z',
202
+ },
203
+ {
204
+ id: 2,
205
+ projectId: 101,
206
+ targetType: 'lms',
207
+ status: 'pending',
208
+ },
209
+ ];
@@ -0,0 +1,38 @@
1
+ const palette: Record<string, string> = {
2
+ draft: 'bg-slate-100 text-slate-700',
3
+ planned: 'bg-blue-100 text-blue-700',
4
+ in_recording: 'bg-orange-100 text-orange-700',
5
+ recorded: 'bg-emerald-100 text-emerald-700',
6
+ in_editing: 'bg-violet-100 text-violet-700',
7
+ in_finalization: 'bg-indigo-100 text-indigo-700',
8
+ ready_to_publish: 'bg-cyan-100 text-cyan-700',
9
+ published: 'bg-green-100 text-green-700',
10
+ archived: 'bg-gray-200 text-gray-700',
11
+ cancelled: 'bg-rose-100 text-rose-700',
12
+ pending: 'bg-amber-100 text-amber-700',
13
+ approved: 'bg-green-100 text-green-700',
14
+ selected: 'bg-lime-100 text-lime-700',
15
+ failed: 'bg-red-100 text-red-700',
16
+ recording: 'bg-orange-100 text-orange-700',
17
+ processing: 'bg-blue-100 text-blue-700',
18
+ ready: 'bg-teal-100 text-teal-700',
19
+ finished: 'bg-green-100 text-green-700',
20
+ idle: 'bg-zinc-100 text-zinc-700',
21
+ low: 'bg-sky-100 text-sky-700',
22
+ medium: 'bg-yellow-100 text-yellow-700',
23
+ high: 'bg-orange-100 text-orange-700',
24
+ critical: 'bg-red-100 text-red-700',
25
+ available: 'bg-green-100 text-green-700',
26
+ open: 'bg-red-100 text-red-700',
27
+ resolved: 'bg-green-100 text-green-700',
28
+ };
29
+
30
+ export function getStudioBadgeClass(value: string) {
31
+ return palette[value] || 'bg-slate-100 text-slate-700';
32
+ }
33
+
34
+ export function toStudioLabel(value: string) {
35
+ return value
36
+ .replace(/_/g, ' ')
37
+ .replace(/\b\w/g, (match) => match.toUpperCase());
38
+ }
@@ -0,0 +1,148 @@
1
+ export type StudioProjectType =
2
+ | 'course_lesson'
3
+ | 'standalone'
4
+ | 'social_content'
5
+ | 'campaign_asset'
6
+ | 'other';
7
+
8
+ export type StudioProjectStatus =
9
+ | 'draft'
10
+ | 'planned'
11
+ | 'in_recording'
12
+ | 'recorded'
13
+ | 'in_editing'
14
+ | 'in_finalization'
15
+ | 'ready_to_publish'
16
+ | 'published'
17
+ | 'archived'
18
+ | 'cancelled';
19
+
20
+ export type StudioStage =
21
+ | 'preparation'
22
+ | 'recording'
23
+ | 'rough_cut'
24
+ | 'cleanup'
25
+ | 'finalization'
26
+ | 'publication';
27
+
28
+ export type SceneStatus =
29
+ | 'pending'
30
+ | 'ready_to_record'
31
+ | 'recording'
32
+ | 'recorded'
33
+ | 'approved_for_edit'
34
+ | 'edited'
35
+ | 'archived';
36
+
37
+ export type SessionStatus =
38
+ | 'idle'
39
+ | 'arming'
40
+ | 'ready'
41
+ | 'start_requested'
42
+ | 'recording'
43
+ | 'stop_requested'
44
+ | 'processing'
45
+ | 'finished'
46
+ | 'cancelled';
47
+
48
+ export type TakeStatus =
49
+ | 'pending'
50
+ | 'armed'
51
+ | 'recording'
52
+ | 'stopped'
53
+ | 'uploaded'
54
+ | 'validated'
55
+ | 'partial'
56
+ | 'approved'
57
+ | 'discarded'
58
+ | 'selected';
59
+
60
+ export type Severity = 'low' | 'medium' | 'high' | 'critical';
61
+
62
+ export interface StudioProject {
63
+ id: number;
64
+ title: string;
65
+ projectType: StudioProjectType;
66
+ status: StudioProjectStatus;
67
+ currentStage: StudioStage;
68
+ binding: string;
69
+ owner: string;
70
+ updatedAt: string;
71
+ }
72
+
73
+ export interface StudioScene {
74
+ id: number;
75
+ projectId: number;
76
+ name: string;
77
+ status: SceneStatus;
78
+ estimatedSeconds: number;
79
+ takes: number;
80
+ }
81
+
82
+ export interface StudioSession {
83
+ id: number;
84
+ projectId: number;
85
+ title: string;
86
+ status: SessionStatus;
87
+ sessionType: 'local' | 'remote_collaborative' | 'hybrid';
88
+ host: string;
89
+ participants: number;
90
+ roomKey: string;
91
+ startedAt: string;
92
+ }
93
+
94
+ export interface StudioTake {
95
+ id: number;
96
+ projectId: number;
97
+ sceneName: string;
98
+ sessionTitle: string;
99
+ status: TakeStatus;
100
+ participants: number;
101
+ expectedFiles: number;
102
+ uploadedFiles: number;
103
+ integrity: 'ok' | 'warning' | 'corrupted';
104
+ selected: boolean;
105
+ }
106
+
107
+ export interface StudioAsset {
108
+ id: number;
109
+ participant: string;
110
+ takeId: number;
111
+ sourceType: string;
112
+ sizeMb: number;
113
+ durationSec: number;
114
+ storage: string;
115
+ availability: 'pending' | 'available' | 'archived' | 'failed';
116
+ origin: string;
117
+ }
118
+
119
+ export interface StorageProfile {
120
+ id: number;
121
+ name: string;
122
+ provider: 's3' | 'minio' | 'r2' | 'compatible_s3';
123
+ bucket: string;
124
+ region: string;
125
+ basePath: string;
126
+ status: 'active' | 'inactive';
127
+ testStatus: 'unknown' | 'success' | 'failed';
128
+ lastTestedAt: string;
129
+ }
130
+
131
+ export interface StudioIncident {
132
+ id: number;
133
+ projectId: number;
134
+ type: string;
135
+ severity: Severity;
136
+ target: string;
137
+ status: 'open' | 'resolved';
138
+ detectedAt: string;
139
+ }
140
+
141
+ export interface PublicationTarget {
142
+ id: number;
143
+ projectId: number;
144
+ targetType: string;
145
+ status: 'pending' | 'scheduled' | 'processing' | 'published' | 'failed';
146
+ scheduledAt?: string;
147
+ publishedUrl?: string;
148
+ }
@@ -0,0 +1,117 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@/components/ui/table';
13
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
14
+ import { STUDIO_ASSETS } from '../_lib/mocks';
15
+ import { StudioStatusBadge } from '../_components/studio-status-badge';
16
+
17
+ export default function StudioAssetsPage() {
18
+ return (
19
+ <Page>
20
+ <PageHeader
21
+ title="Arquivos e Assets"
22
+ description="Catalogo tecnico com filtros de visualizacao"
23
+ breadcrumbs={[
24
+ { label: 'Home', href: '/' },
25
+ { label: 'Studio', href: '/studio' },
26
+ { label: 'Assets' },
27
+ ]}
28
+ />
29
+
30
+ <Tabs defaultValue="table" className="space-y-4">
31
+ <TabsList>
32
+ <TabsTrigger value="table">Tabela</TabsTrigger>
33
+ <TabsTrigger value="participant">Por participante</TabsTrigger>
34
+ <TabsTrigger value="take">Por take</TabsTrigger>
35
+ </TabsList>
36
+
37
+ <TabsContent value="table">
38
+ <div className="rounded-md border">
39
+ <Table>
40
+ <TableHeader>
41
+ <TableRow>
42
+ <TableHead>Participante</TableHead>
43
+ <TableHead>Take</TableHead>
44
+ <TableHead>Tipo</TableHead>
45
+ <TableHead>Tamanho (MB)</TableHead>
46
+ <TableHead>Duracao (s)</TableHead>
47
+ <TableHead>Storage</TableHead>
48
+ <TableHead>Disponibilidade</TableHead>
49
+ <TableHead>Origem</TableHead>
50
+ </TableRow>
51
+ </TableHeader>
52
+ <TableBody>
53
+ {STUDIO_ASSETS.map((asset) => (
54
+ <TableRow key={asset.id}>
55
+ <TableCell>{asset.participant}</TableCell>
56
+ <TableCell>#{asset.takeId}</TableCell>
57
+ <TableCell>{asset.sourceType}</TableCell>
58
+ <TableCell>{asset.sizeMb}</TableCell>
59
+ <TableCell>{asset.durationSec}</TableCell>
60
+ <TableCell>{asset.storage}</TableCell>
61
+ <TableCell>
62
+ <StudioStatusBadge value={asset.availability} />
63
+ </TableCell>
64
+ <TableCell>{asset.origin}</TableCell>
65
+ </TableRow>
66
+ ))}
67
+ </TableBody>
68
+ </Table>
69
+ </div>
70
+ </TabsContent>
71
+
72
+ <TabsContent value="participant" className="grid gap-4 md:grid-cols-2">
73
+ {[...new Set(STUDIO_ASSETS.map((item) => item.participant))].map(
74
+ (name) => (
75
+ <Card key={name}>
76
+ <CardHeader>
77
+ <CardTitle className="text-base">{name}</CardTitle>
78
+ </CardHeader>
79
+ <CardContent className="space-y-2 text-sm">
80
+ {STUDIO_ASSETS.filter(
81
+ (item) => item.participant === name
82
+ ).map((item) => (
83
+ <div key={item.id} className="rounded-md border p-2">
84
+ #{item.takeId} • {item.sourceType} • {item.sizeMb}MB
85
+ </div>
86
+ ))}
87
+ </CardContent>
88
+ </Card>
89
+ )
90
+ )}
91
+ </TabsContent>
92
+
93
+ <TabsContent value="take" className="grid gap-4 md:grid-cols-2">
94
+ {[...new Set(STUDIO_ASSETS.map((item) => item.takeId))].map(
95
+ (takeId) => (
96
+ <Card key={takeId}>
97
+ <CardHeader>
98
+ <CardTitle className="text-base">Take #{takeId}</CardTitle>
99
+ </CardHeader>
100
+ <CardContent className="space-y-2 text-sm">
101
+ {STUDIO_ASSETS.filter((item) => item.takeId === takeId).map(
102
+ (item) => (
103
+ <div key={item.id} className="rounded-md border p-2">
104
+ {item.participant} • {item.sourceType} •{' '}
105
+ {item.durationSec}s
106
+ </div>
107
+ )
108
+ )}
109
+ </CardContent>
110
+ </Card>
111
+ )
112
+ )}
113
+ </TabsContent>
114
+ </Tabs>
115
+ </Page>
116
+ );
117
+ }