@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,129 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Form,
7
+ FormControl,
8
+ FormField,
9
+ FormItem,
10
+ FormLabel,
11
+ FormMessage,
12
+ } from '@/components/ui/form';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Textarea } from '@/components/ui/textarea';
15
+ import { zodResolver } from '@hookform/resolvers/zod';
16
+ import { useForm } from 'react-hook-form';
17
+ import { z } from 'zod';
18
+
19
+ const schema = z.object({
20
+ title: z.string().min(3),
21
+ description: z.string().optional(),
22
+ projectType: z.string().min(1),
23
+ binding: z.string().optional(),
24
+ owner: z.string().min(1),
25
+ editor: z.string().optional(),
26
+ currentStage: z.string().min(1),
27
+ status: z.string().min(1),
28
+ plannedAt: z.string().optional(),
29
+ thumbnail: z.string().optional(),
30
+ notes: z.string().optional(),
31
+ });
32
+
33
+ export default function StudioProjectFormPage() {
34
+ const form = useForm<z.infer<typeof schema>>({
35
+ resolver: zodResolver(schema),
36
+ defaultValues: {
37
+ title: '',
38
+ description: '',
39
+ projectType: 'course_lesson',
40
+ binding: '',
41
+ owner: '',
42
+ editor: '',
43
+ currentStage: 'preparation',
44
+ status: 'draft',
45
+ plannedAt: '',
46
+ thumbnail: '',
47
+ notes: '',
48
+ },
49
+ });
50
+
51
+ return (
52
+ <Page>
53
+ <PageHeader
54
+ title="Formulario de Projeto"
55
+ description="Cadastro estatico para validacao de UX"
56
+ breadcrumbs={[
57
+ { label: 'Home', href: '/' },
58
+ { label: 'Studio', href: '/studio' },
59
+ { label: 'Projeto' },
60
+ ]}
61
+ />
62
+
63
+ <Form {...form}>
64
+ <form
65
+ className="grid gap-4"
66
+ onSubmit={form.handleSubmit(() => undefined)}
67
+ >
68
+ <div className="grid gap-4 md:grid-cols-2">
69
+ {[
70
+ ['title', 'Titulo', 'Nome do projeto'],
71
+ ['projectType', 'Tipo', 'course_lesson | standalone'],
72
+ ['binding', 'Vinculo externo', 'lms.lesson #123'],
73
+ ['owner', 'Responsavel', 'Usuario responsavel'],
74
+ ['editor', 'Editor', 'Usuario editor'],
75
+ ['currentStage', 'Etapa atual', 'preparation | recording'],
76
+ ['status', 'Status', 'draft | planned | in_editing'],
77
+ ['plannedAt', 'Data planejada', '2026-03-20T09:00'],
78
+ ['thumbnail', 'Thumbnail', 'ID/URL da imagem'],
79
+ ].map(([name, label, placeholder]) => (
80
+ <FormField
81
+ key={name}
82
+ control={form.control}
83
+ name={name as keyof z.infer<typeof schema>}
84
+ render={({ field }) => (
85
+ <FormItem>
86
+ <FormLabel>{label}</FormLabel>
87
+ <FormControl>
88
+ <Input placeholder={placeholder} {...field} />
89
+ </FormControl>
90
+ <FormMessage />
91
+ </FormItem>
92
+ )}
93
+ />
94
+ ))}
95
+ </div>
96
+ <FormField
97
+ control={form.control}
98
+ name="description"
99
+ render={({ field }) => (
100
+ <FormItem>
101
+ <FormLabel>Descricao</FormLabel>
102
+ <FormControl>
103
+ <Textarea placeholder="Resumo do projeto" {...field} />
104
+ </FormControl>
105
+ <FormMessage />
106
+ </FormItem>
107
+ )}
108
+ />
109
+ <FormField
110
+ control={form.control}
111
+ name="notes"
112
+ render={({ field }) => (
113
+ <FormItem>
114
+ <FormLabel>Observacoes</FormLabel>
115
+ <FormControl>
116
+ <Textarea placeholder="Notas de producao" {...field} />
117
+ </FormControl>
118
+ <FormMessage />
119
+ </FormItem>
120
+ )}
121
+ />
122
+ <Button type="submit" className="w-full">
123
+ Salvar Projeto
124
+ </Button>
125
+ </form>
126
+ </Form>
127
+ </Page>
128
+ );
129
+ }
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Page,
5
+ PageHeader,
6
+ PaginationFooter,
7
+ SearchBar,
8
+ } from '@/components/entity-list';
9
+ import type { PageHeaderProps } from '@/components/entity-list/page-header';
10
+ import { Button } from '@/components/ui/button';
11
+ import {
12
+ Table,
13
+ TableBody,
14
+ TableCell,
15
+ TableHead,
16
+ TableHeader,
17
+ TableRow,
18
+ } from '@/components/ui/table';
19
+ import Link from 'next/link';
20
+ import { useMemo, useState } from 'react';
21
+ import { StudioStatusBadge } from '../_components/studio-status-badge';
22
+ import { STUDIO_PROJECTS } from '../_lib/mocks';
23
+
24
+ export default function StudioProjectsPage() {
25
+ const breadcrumbs: PageHeaderProps['breadcrumbs'] = [
26
+ { label: 'Home', href: '/' },
27
+ { label: 'Studio', href: '/studio' },
28
+ { label: 'Projetos' },
29
+ ];
30
+
31
+ const [search, setSearch] = useState('');
32
+ const [status, setStatus] = useState('all');
33
+ const [type, setType] = useState('all');
34
+ const [stage, setStage] = useState('all');
35
+ const [page, setPage] = useState(1);
36
+ const [pageSize, setPageSize] = useState(10);
37
+
38
+ const filtered = useMemo(() => {
39
+ return STUDIO_PROJECTS.filter((item) => {
40
+ if (status !== 'all' && item.status !== status) return false;
41
+ if (type !== 'all' && item.projectType !== type) return false;
42
+ if (stage !== 'all' && item.currentStage !== stage) return false;
43
+ if (
44
+ search &&
45
+ !item.title.toLowerCase().includes(search.toLowerCase()) &&
46
+ !item.binding.toLowerCase().includes(search.toLowerCase())
47
+ ) {
48
+ return false;
49
+ }
50
+ return true;
51
+ });
52
+ }, [search, stage, status, type]);
53
+
54
+ const paged = filtered.slice((page - 1) * pageSize, page * pageSize);
55
+
56
+ return (
57
+ <Page>
58
+ <PageHeader
59
+ title="Projetos"
60
+ description="Gestao de projetos audiovisuais"
61
+ breadcrumbs={breadcrumbs as { label: string; href?: string }[]}
62
+ actions={
63
+ <Button asChild size="sm">
64
+ <Link href="/studio/projects/form">Novo Projeto</Link>
65
+ </Button>
66
+ }
67
+ />
68
+
69
+ <SearchBar
70
+ searchQuery={search}
71
+ onSearchChange={setSearch}
72
+ onSearch={() => setPage(1)}
73
+ placeholder="Buscar por titulo ou vinculo"
74
+ controls={[
75
+ {
76
+ id: 'status',
77
+ type: 'select',
78
+ value: status,
79
+ onChange: setStatus,
80
+ options: [
81
+ { value: 'all', label: 'Status' },
82
+ { value: 'draft', label: 'Draft' },
83
+ { value: 'in_recording', label: 'In recording' },
84
+ { value: 'in_editing', label: 'In editing' },
85
+ { value: 'ready_to_publish', label: 'Ready to publish' },
86
+ ],
87
+ },
88
+ {
89
+ id: 'type',
90
+ type: 'select',
91
+ value: type,
92
+ onChange: setType,
93
+ options: [
94
+ { value: 'all', label: 'Tipo' },
95
+ { value: 'course_lesson', label: 'Course lesson' },
96
+ { value: 'standalone', label: 'Standalone' },
97
+ { value: 'social_content', label: 'Social content' },
98
+ ],
99
+ },
100
+ {
101
+ id: 'stage',
102
+ type: 'select',
103
+ value: stage,
104
+ onChange: setStage,
105
+ options: [
106
+ { value: 'all', label: 'Etapa' },
107
+ { value: 'preparation', label: 'Preparation' },
108
+ { value: 'recording', label: 'Recording' },
109
+ { value: 'rough_cut', label: 'Rough cut' },
110
+ { value: 'publication', label: 'Publication' },
111
+ ],
112
+ },
113
+ ]}
114
+ />
115
+
116
+ <div className="rounded-md border">
117
+ <Table>
118
+ <TableHeader>
119
+ <TableRow>
120
+ <TableHead>Titulo</TableHead>
121
+ <TableHead>Tipo</TableHead>
122
+ <TableHead>Status</TableHead>
123
+ <TableHead>Etapa</TableHead>
124
+ <TableHead>Vinculo</TableHead>
125
+ <TableHead>Responsavel</TableHead>
126
+ <TableHead>Atualizado</TableHead>
127
+ <TableHead className="text-right">Acoes</TableHead>
128
+ </TableRow>
129
+ </TableHeader>
130
+ <TableBody>
131
+ {paged.map((project) => (
132
+ <TableRow key={project.id}>
133
+ <TableCell className="font-medium">{project.title}</TableCell>
134
+ <TableCell>{project.projectType}</TableCell>
135
+ <TableCell>
136
+ <StudioStatusBadge value={project.status} />
137
+ </TableCell>
138
+ <TableCell>
139
+ <StudioStatusBadge value={project.currentStage} />
140
+ </TableCell>
141
+ <TableCell>{project.binding}</TableCell>
142
+ <TableCell>{project.owner}</TableCell>
143
+ <TableCell>
144
+ {new Date(project.updatedAt).toLocaleString('pt-BR')}
145
+ </TableCell>
146
+ <TableCell className="text-right">
147
+ <Button asChild variant="outline" size="sm">
148
+ <Link href={`/studio/projects/${project.id}`}>Abrir</Link>
149
+ </Button>
150
+ </TableCell>
151
+ </TableRow>
152
+ ))}
153
+ </TableBody>
154
+ </Table>
155
+ </div>
156
+
157
+ <PaginationFooter
158
+ currentPage={page}
159
+ pageSize={pageSize}
160
+ totalItems={filtered.length}
161
+ onPageChange={setPage}
162
+ onPageSizeChange={(value) => {
163
+ setPageSize(value);
164
+ setPage(1);
165
+ }}
166
+ />
167
+ </Page>
168
+ );
169
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@/components/ui/table';
12
+ import { Button } from '@/components/ui/button';
13
+ import { STUDIO_PUBLICATION_TARGETS } from '../_lib/mocks';
14
+ import { StudioStatusBadge } from '../_components/studio-status-badge';
15
+
16
+ export default function StudioPublicationPage() {
17
+ return (
18
+ <Page>
19
+ <PageHeader
20
+ title="Publicacao"
21
+ description="Destinos e estado de publicacao"
22
+ breadcrumbs={[
23
+ { label: 'Home', href: '/' },
24
+ { label: 'Studio', href: '/studio' },
25
+ { label: 'Publicacao' },
26
+ ]}
27
+ />
28
+ <div className="rounded-md border">
29
+ <Table>
30
+ <TableHeader>
31
+ <TableRow>
32
+ <TableHead>Destino</TableHead>
33
+ <TableHead>Status</TableHead>
34
+ <TableHead>Agendamento</TableHead>
35
+ <TableHead>URL</TableHead>
36
+ <TableHead className="text-right">Acoes</TableHead>
37
+ </TableRow>
38
+ </TableHeader>
39
+ <TableBody>
40
+ {STUDIO_PUBLICATION_TARGETS.map((target) => (
41
+ <TableRow key={target.id}>
42
+ <TableCell>{target.targetType}</TableCell>
43
+ <TableCell>
44
+ <StudioStatusBadge value={target.status} />
45
+ </TableCell>
46
+ <TableCell>
47
+ {target.scheduledAt
48
+ ? new Date(target.scheduledAt).toLocaleString('pt-BR')
49
+ : '-'}
50
+ </TableCell>
51
+ <TableCell>{target.publishedUrl || '-'}</TableCell>
52
+ <TableCell className="text-right">
53
+ <Button size="sm">Publicar</Button>
54
+ </TableCell>
55
+ </TableRow>
56
+ ))}
57
+ </TableBody>
58
+ </Table>
59
+ </div>
60
+ </Page>
61
+ );
62
+ }
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Form,
7
+ FormControl,
8
+ FormField,
9
+ FormItem,
10
+ FormLabel,
11
+ FormMessage,
12
+ } from '@/components/ui/form';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Textarea } from '@/components/ui/textarea';
15
+ import { zodResolver } from '@hookform/resolvers/zod';
16
+ import { useForm } from 'react-hook-form';
17
+ import { z } from 'zod';
18
+
19
+ const schema = z.object({
20
+ name: z.string().min(2),
21
+ description: z.string().optional(),
22
+ scriptExcerpt: z.string().optional(),
23
+ sequenceOrder: z.string().min(1),
24
+ estimatedDuration: z.string().optional(),
25
+ status: z.string().min(1),
26
+ });
27
+
28
+ export default function StudioSceneFormPage() {
29
+ const form = useForm<z.infer<typeof schema>>({
30
+ resolver: zodResolver(schema),
31
+ defaultValues: {
32
+ name: '',
33
+ description: '',
34
+ scriptExcerpt: '',
35
+ sequenceOrder: '1',
36
+ estimatedDuration: '',
37
+ status: 'pending',
38
+ },
39
+ });
40
+
41
+ return (
42
+ <Page>
43
+ <PageHeader
44
+ title="Formulario de Cena"
45
+ description="Cadastro estatico de cena"
46
+ breadcrumbs={[
47
+ { label: 'Home', href: '/' },
48
+ { label: 'Studio', href: '/studio' },
49
+ { label: 'Cena' },
50
+ ]}
51
+ />
52
+ <Form {...form}>
53
+ <form
54
+ className="grid gap-4"
55
+ onSubmit={form.handleSubmit(() => undefined)}
56
+ >
57
+ {[
58
+ ['name', 'Nome', 'Cena de abertura'],
59
+ ['sequenceOrder', 'Ordem', '1'],
60
+ ['estimatedDuration', 'Duracao estimada (s)', '120'],
61
+ ['status', 'Status', 'pending | recorded'],
62
+ ].map(([name, label, placeholder]) => (
63
+ <FormField
64
+ key={name}
65
+ control={form.control}
66
+ name={name as keyof z.infer<typeof schema>}
67
+ render={({ field }) => (
68
+ <FormItem>
69
+ <FormLabel>{label}</FormLabel>
70
+ <FormControl>
71
+ <Input placeholder={placeholder} {...field} />
72
+ </FormControl>
73
+ <FormMessage />
74
+ </FormItem>
75
+ )}
76
+ />
77
+ ))}
78
+
79
+ <FormField
80
+ control={form.control}
81
+ name="description"
82
+ render={({ field }) => (
83
+ <FormItem>
84
+ <FormLabel>Descricao</FormLabel>
85
+ <FormControl>
86
+ <Textarea placeholder="Descricao da cena" {...field} />
87
+ </FormControl>
88
+ </FormItem>
89
+ )}
90
+ />
91
+ <FormField
92
+ control={form.control}
93
+ name="scriptExcerpt"
94
+ render={({ field }) => (
95
+ <FormItem>
96
+ <FormLabel>Trecho do script</FormLabel>
97
+ <FormControl>
98
+ <Textarea placeholder="Fal falada ou orientacao" {...field} />
99
+ </FormControl>
100
+ </FormItem>
101
+ )}
102
+ />
103
+ <Button type="submit" className="w-full">
104
+ Salvar Cena
105
+ </Button>
106
+ </form>
107
+ </Form>
108
+ </Page>
109
+ );
110
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import type { PageHeaderProps } from '@/components/entity-list/page-header';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
7
+ import { useParams } from 'next/navigation';
8
+ import { StudioStatusBadge } from '../../_components/studio-status-badge';
9
+ import { STUDIO_SESSIONS } from '../../_lib/mocks';
10
+
11
+ const participantStatus = [
12
+ { name: 'Ana Lima', status: 'recording', device: 'desktop_app ok' },
13
+ { name: 'Bruno Costa', status: 'ready', device: 'desktop_app ok' },
14
+ { name: 'Diego Nunes', status: 'recording', device: 'desktop_app warning' },
15
+ { name: 'Eva Souza', status: 'connected', device: 'browser ok' },
16
+ ];
17
+
18
+ export default function StudioSessionDetailPage() {
19
+ const params = useParams<{ id: string }>();
20
+ const session = STUDIO_SESSIONS.find((item) => item.id === Number(params.id));
21
+
22
+ if (!session) {
23
+ return null;
24
+ }
25
+
26
+ const currentSession = session!;
27
+
28
+ const breadcrumbs: PageHeaderProps['breadcrumbs'] = [
29
+ { label: 'Home', href: '/' },
30
+ { label: 'Studio', href: '/studio' },
31
+ { label: 'Sessoes', href: '/studio/sessions' },
32
+ { label: `Sessao #${currentSession.id}` },
33
+ ];
34
+
35
+ return (
36
+ <Page>
37
+ <PageHeader
38
+ title={currentSession.title}
39
+ description="Monitoramento de sessao ativa"
40
+ breadcrumbs={breadcrumbs as { label: string; href?: string }[]}
41
+ />
42
+
43
+ <div className="grid gap-4 lg:grid-cols-3">
44
+ <Card>
45
+ <CardHeader>
46
+ <CardTitle className="text-base">Estado da sessao</CardTitle>
47
+ </CardHeader>
48
+ <CardContent className="space-y-2 text-sm">
49
+ <div>
50
+ Status: <StudioStatusBadge value={currentSession.status} />
51
+ </div>
52
+ <div>Room: {currentSession.roomKey}</div>
53
+ <div>Cena atual: Demonstracao em tela</div>
54
+ <div>Host: {currentSession.host}</div>
55
+ </CardContent>
56
+ </Card>
57
+ <Card className="lg:col-span-2">
58
+ <CardHeader>
59
+ <CardTitle className="text-base">Comandos</CardTitle>
60
+ </CardHeader>
61
+ <CardContent className="grid gap-2 sm:grid-cols-2 lg:grid-cols-5">
62
+ <Button variant="outline">Armar</Button>
63
+ <Button>Iniciar gravacao</Button>
64
+ <Button variant="secondary">Parar</Button>
65
+ <Button variant="destructive">Cancelar take</Button>
66
+ <Button variant="outline">Proximo take</Button>
67
+ </CardContent>
68
+ </Card>
69
+ </div>
70
+
71
+ <div className="grid gap-4 lg:grid-cols-2">
72
+ <Card>
73
+ <CardHeader>
74
+ <CardTitle className="text-base">
75
+ Participantes conectados
76
+ </CardTitle>
77
+ </CardHeader>
78
+ <CardContent className="space-y-2 text-sm">
79
+ {participantStatus.map((participant) => (
80
+ <div
81
+ key={participant.name}
82
+ className="flex items-center justify-between rounded-md border p-2"
83
+ >
84
+ <div>
85
+ <div className="font-medium">{participant.name}</div>
86
+ <div className="text-muted-foreground text-xs">
87
+ {participant.device}
88
+ </div>
89
+ </div>
90
+ <StudioStatusBadge value={participant.status} />
91
+ </div>
92
+ ))}
93
+ </CardContent>
94
+ </Card>
95
+ <Card>
96
+ <CardHeader>
97
+ <CardTitle className="text-base">Timeline de eventos</CardTitle>
98
+ </CardHeader>
99
+ <CardContent className="space-y-2 text-sm">
100
+ <div className="rounded-md border p-2">18:00 - Sessao iniciou</div>
101
+ <div className="rounded-md border p-2">
102
+ 18:02 - Command arm emitido
103
+ </div>
104
+ <div className="rounded-md border p-2">
105
+ 18:03 - Command start emitido
106
+ </div>
107
+ <div className="rounded-md border p-2">
108
+ 18:08 - Incident delayed_stop detectado
109
+ </div>
110
+ <div className="rounded-md border p-2">
111
+ 18:10 - Command stop emitido
112
+ </div>
113
+ </CardContent>
114
+ </Card>
115
+ </div>
116
+ </Page>
117
+ );
118
+ }
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Form,
7
+ FormControl,
8
+ FormField,
9
+ FormItem,
10
+ FormLabel,
11
+ FormMessage,
12
+ } from '@/components/ui/form';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Textarea } from '@/components/ui/textarea';
15
+ import { zodResolver } from '@hookform/resolvers/zod';
16
+ import { useForm } from 'react-hook-form';
17
+ import { z } from 'zod';
18
+
19
+ const schema = z.object({
20
+ title: z.string().min(3),
21
+ description: z.string().optional(),
22
+ sessionType: z.string().min(1),
23
+ roomKey: z.string().min(1),
24
+ host: z.string().min(1),
25
+ status: z.string().min(1),
26
+ syncMode: z.string().min(1),
27
+ });
28
+
29
+ export default function StudioSessionFormPage() {
30
+ const form = useForm<z.infer<typeof schema>>({
31
+ resolver: zodResolver(schema),
32
+ defaultValues: {
33
+ title: '',
34
+ description: '',
35
+ sessionType: 'remote_collaborative',
36
+ roomKey: '',
37
+ host: '',
38
+ status: 'idle',
39
+ syncMode: 'server_authoritative',
40
+ },
41
+ });
42
+
43
+ return (
44
+ <Page>
45
+ <PageHeader
46
+ title="Formulario de Sessao"
47
+ description="Cadastro estatico de sessao de gravacao"
48
+ breadcrumbs={[
49
+ { label: 'Home', href: '/' },
50
+ { label: 'Studio', href: '/studio' },
51
+ { label: 'Sessao' },
52
+ ]}
53
+ />
54
+
55
+ <Form {...form}>
56
+ <form
57
+ className="grid gap-4"
58
+ onSubmit={form.handleSubmit(() => undefined)}
59
+ >
60
+ {[
61
+ ['title', 'Titulo', 'Sessao principal do projeto X'],
62
+ ['sessionType', 'Tipo', 'local | remote_collaborative | hybrid'],
63
+ ['roomKey', 'Room key', 'studio-room-abc'],
64
+ ['host', 'Host', 'Usuario host'],
65
+ ['status', 'Status', 'idle | ready | recording'],
66
+ ['syncMode', 'Modo de sincronizacao', 'server_authoritative'],
67
+ ].map(([name, label, placeholder]) => (
68
+ <FormField
69
+ key={name}
70
+ control={form.control}
71
+ name={name as keyof z.infer<typeof schema>}
72
+ render={({ field }) => (
73
+ <FormItem>
74
+ <FormLabel>{label}</FormLabel>
75
+ <FormControl>
76
+ <Input placeholder={placeholder} {...field} />
77
+ </FormControl>
78
+ <FormMessage />
79
+ </FormItem>
80
+ )}
81
+ />
82
+ ))}
83
+ <FormField
84
+ control={form.control}
85
+ name="description"
86
+ render={({ field }) => (
87
+ <FormItem>
88
+ <FormLabel>Descricao</FormLabel>
89
+ <FormControl>
90
+ <Textarea placeholder="Contexto da sessao" {...field} />
91
+ </FormControl>
92
+ <FormMessage />
93
+ </FormItem>
94
+ )}
95
+ />
96
+ <Button type="submit" className="w-full">
97
+ Salvar Sessao
98
+ </Button>
99
+ </form>
100
+ </Form>
101
+ </Page>
102
+ );
103
+ }