From 3b524d1c23cabea38f07cad04f3aaf9307db879c Mon Sep 17 00:00:00 2001 From: Cesar Mendivil Date: Thu, 6 Nov 2025 00:48:26 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20funcionalidad=20de=20edici?= =?UTF-8?q?=C3=B3n=20en=20el=20modal=20de=20transmisi=C3=B3n=20y=20mejorar?= =?UTF-8?q?=20la=20tabla=20de=20transmisiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/NewTransmissionModal.tsx | 113 ++++++++++++++---- .../src/components/TransmissionsTable.tsx | 93 ++++++++------ packages/broadcast-panel/src/types.ts | 1 + 3 files changed, 148 insertions(+), 59 deletions(-) diff --git a/packages/broadcast-panel/src/components/NewTransmissionModal.tsx b/packages/broadcast-panel/src/components/NewTransmissionModal.tsx index ad994b8..612818c 100644 --- a/packages/broadcast-panel/src/components/NewTransmissionModal.tsx +++ b/packages/broadcast-panel/src/components/NewTransmissionModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import { Modal, ModalRadioGroup, @@ -25,6 +25,8 @@ interface Props { open: boolean onClose: () => void onCreate: (t: Transmission) => void + onUpdate?: (t: Transmission) => void + transmission?: Transmission // Transmisión a editar } interface DestinationData { @@ -34,9 +36,13 @@ interface DestinationData { badge?: React.ReactNode } -const NewTransmissionModal: React.FC = ({ open, onClose, onCreate }) => { +const NewTransmissionModal: React.FC = ({ open, onClose, onCreate, onUpdate, transmission }) => { const [view, setView] = useState<'main' | 'add-destination'>('main') const [source, setSource] = useState('studio') + + // Modo edición: si hay transmission, inicializar con sus datos + const isEditMode = !!transmission + const [destinations, setDestinations] = useState([ { id: 'yt_1', @@ -63,6 +69,42 @@ const NewTransmissionModal: React.FC = ({ open, onClose, onCreate }) => { const [scheduledHour, setScheduledHour] = useState('01') const [scheduledMinute, setScheduledMinute] = useState('10') + // Inicializar campos en modo edición + useEffect(() => { + if (transmission && open) { + setTitle(transmission.title) + + // Si es genérico, activar modo blank + if (transmission.platform === 'Genérico') { + setSelectedDestination('blank') + setBlankTitle(transmission.title) + } else { + // Si tiene una plataforma específica, crear el destino y seleccionarlo + // Por ahora, simplemente no seleccionamos nada hasta que se agregue el destino + setSelectedDestination(null) + } + } else if (!open) { + // Reset cuando se cierra el modal + resetForm() + } + }, [transmission, open]) + + const resetForm = () => { + setView('main') + setSource('studio') + setSelectedDestination(null) + setTitle('') + setDescription('') + setPrivacy('Pública') + setCategory('') + setAddReferral(true) + setScheduleForLater(false) + setScheduledDate('') + setScheduledHour('01') + setScheduledMinute('10') + setBlankTitle('') + } + const generateId = () => `t_${Date.now()}_${Math.floor(Math.random()*1000)}` const handleAddDestination = () => { @@ -115,39 +157,57 @@ const NewTransmissionModal: React.FC = ({ open, onClose, onCreate }) => { return } - // Si es transmisión en blanco, usar el título del formulario blanco + // Si es transmisión en blanco (genérica) if (selectedDestination === 'blank') { - // Por el momento solo cierra el modal - // TODO: navegar a studio con título blankTitle + const blankTransmission: Transmission = { + id: isEditMode && transmission ? transmission.id : generateId(), + title: blankTitle || 'Transmisión en vivo', + platform: 'Genérico', + scheduled: 'Próximamente', + createdAt: isEditMode && transmission?.createdAt ? transmission.createdAt : new Date().toLocaleDateString('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) + } + + // Si estamos editando, llamar a onUpdate, sino onCreate + if (isEditMode && onUpdate) { + onUpdate(blankTransmission) + } else { + onCreate(blankTransmission) + } + + resetForm() onClose() return } + // Transmisión con destino específico const t: Transmission = { - id: generateId(), + id: isEditMode && transmission ? transmission.id : generateId(), title: title || 'Nueva transmisión', platform: destinations.find(d => d.id === selectedDestination)?.platform || 'YouTube', - scheduled: '' + scheduled: '', + createdAt: isEditMode && transmission?.createdAt ? transmission.createdAt : new Date().toLocaleDateString('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) } - onCreate(t) - // Reset form - setView('main') - setSource('studio') - setSelectedDestination(null) - setTitle('') - setDescription('') - setPrivacy('Pública') - setCategory('') - setAddReferral(true) - setScheduleForLater(false) - setScheduledDate('') - setScheduledHour('01') - setScheduledMinute('10') - setBlankTitle('') + // Si estamos editando, llamar a onUpdate, sino onCreate + if (isEditMode && onUpdate) { + onUpdate(t) + } else { + onCreate(t) + } + + resetForm() + onClose() } - const modalTitle = view === 'add-destination' ? 'Agregar destino' : 'Crear transmisión en vivo' + const modalTitle = view === 'add-destination' ? 'Agregar destino' : (isEditMode ? 'Editar transmisión' : 'Crear transmisión en vivo') const showBackButton = view === 'add-destination' return ( @@ -200,7 +260,7 @@ const NewTransmissionModal: React.FC = ({ open, onClose, onCreate }) => { onClick={handleAddDestination} title="Agregar destino" /> - {!selectedDestination && ( + {!selectedDestination && (
Omitir por ahora @@ -353,7 +413,10 @@ const NewTransmissionModal: React.FC = ({ open, onClose, onCreate }) => { className={styles.createButton} disabled={!selectedDestination} > - {selectedDestination === 'blank' ? 'Empezar ahora' : 'Crear transmisión en vivo'} + {isEditMode + ? (selectedDestination === 'blank' ? 'Guardar cambios' : 'Actualizar transmisión') + : (selectedDestination === 'blank' ? 'Empezar ahora' : 'Crear transmisión en vivo') + }
diff --git a/packages/broadcast-panel/src/components/TransmissionsTable.tsx b/packages/broadcast-panel/src/components/TransmissionsTable.tsx index 51b0c83..7b43fa9 100644 --- a/packages/broadcast-panel/src/components/TransmissionsTable.tsx +++ b/packages/broadcast-panel/src/components/TransmissionsTable.tsx @@ -5,6 +5,7 @@ import { FaYoutube, FaFacebook, FaTwitch, FaLinkedin } from 'react-icons/fa' import { SkeletonTable } from './Skeleton' import styles from './TransmissionsTable.module.css' import InviteGuestsModal from './InviteGuestsModal' +import NewTransmissionModal from './NewTransmissionModal' import type { Transmission } from '../types' interface Props { @@ -19,16 +20,25 @@ const platformIcons: Record = { 'Facebook': , 'Twitch': , 'LinkedIn': , + 'Genérico': , // Logo genérico para transmisiones sin destino } const TransmissionsTable: React.FC = ({ transmissions, onDelete, onUpdate, isLoading }) => { const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming') const [inviteOpen, setInviteOpen] = useState(false) const [inviteLink, setInviteLink] = useState(undefined) + const [editOpen, setEditOpen] = useState(false) + const [editTransmission, setEditTransmission] = useState(undefined) + + const handleEdit = (t: Transmission) => { + setEditTransmission(t) + setEditOpen(true) + } // Filtrado por fechas const filtered = transmissions.filter(t => { - if (!t.scheduled) return activeTab === 'upcoming' + // Si es "Próximamente" o no tiene fecha programada, siempre va a "upcoming" + if (!t.scheduled || t.scheduled === 'Próximamente') return activeTab === 'upcoming' const scheduledDate = new Date(t.scheduled) const now = new Date() @@ -96,40 +106,47 @@ const TransmissionsTable: React.FC = ({ transmissions, onDelete, onUpdate {filtered.map(t => ( - -
-
-
-
-
{platformIcons[t.platform] || platformIcons['YouTube']}
-
-
-
{t.title}
-
{t.scheduled || '—'}
-
-
- -
- - - <> - } - items={[ - { label: 'Agregar invitados', icon: , onClick: () => { setInviteLink(`https://streamyard.com/${t.id}`); setInviteOpen(true) } }, - { label: 'Editar', icon: , onClick: () => {/* editar */} }, - { divider: true, label: '', disabled: false }, - { label: 'Ver en YouTube', icon: , onClick: () => {/* abrir */} }, - { divider: true, label: '', disabled: false }, - { label: 'Eliminar transmisión', icon: , onClick: () => onDelete(t.id), containerProps: { className: styles.deleteItem }, labelProps: { className: styles.dangerLabel } } - ]} - /> - setInviteOpen(false)} link={inviteLink} /> - -
+ +
+
+
{platformIcons[t.platform] || platformIcons['YouTube']}
+
+
{t.title}
+
+ {t.platform === 'Genérico' ? 'Solo grabación' : (t.platform || 'YouTube')} +
+
+
+ + + + {t.createdAt || '---'} + + + + + {(t.scheduled && t.scheduled !== 'Próximamente') ? t.scheduled : '---'} + + + +
+ + + } + items={[ + { label: 'Agregar invitados', icon: , onClick: () => { setInviteLink(`https://streamyard.com/${t.id}`); setInviteOpen(true) } }, + { label: 'Editar', icon: , onClick: () => handleEdit(t) }, + { divider: true, label: '', disabled: false }, + { label: 'Ver en YouTube', icon: , onClick: () => {/* abrir */} }, + { divider: true, label: '', disabled: false }, + { label: 'Eliminar transmisión', icon: , onClick: () => onDelete(t.id), containerProps: { className: styles.deleteItem }, labelProps: { className: styles.dangerLabel } } + ]} + /> + setInviteOpen(false)} link={inviteLink} />
@@ -138,6 +155,14 @@ const TransmissionsTable: React.FC = ({ transmissions, onDelete, onUpdate
)} + + { setEditOpen(false); setEditTransmission(undefined) }} + onCreate={() => {}} + onUpdate={onUpdate} + transmission={editTransmission} + />
) } diff --git a/packages/broadcast-panel/src/types.ts b/packages/broadcast-panel/src/types.ts index df4ee04..db6dbdd 100644 --- a/packages/broadcast-panel/src/types.ts +++ b/packages/broadcast-panel/src/types.ts @@ -3,4 +3,5 @@ export interface Transmission { title: string platform: string scheduled: string + createdAt?: string // Fecha de creación }