feat: agregar funcionalidad de edición en el modal de transmisión y mejorar la tabla de transmisiones
This commit is contained in:
parent
e43686e36d
commit
3b524d1c23
@ -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<Props> = ({ open, onClose, onCreate }) => {
|
||||
const NewTransmissionModal: React.FC<Props> = ({ 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<DestinationData[]>([
|
||||
{
|
||||
id: 'yt_1',
|
||||
@ -63,6 +69,42 @@ const NewTransmissionModal: React.FC<Props> = ({ 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<Props> = ({ 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'
|
||||
})
|
||||
}
|
||||
|
||||
// Si estamos editando, llamar a onUpdate, sino onCreate
|
||||
if (isEditMode && onUpdate) {
|
||||
onUpdate(t)
|
||||
} else {
|
||||
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('')
|
||||
}
|
||||
|
||||
const modalTitle = view === 'add-destination' ? 'Agregar destino' : 'Crear transmisión en vivo'
|
||||
resetForm()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const modalTitle = view === 'add-destination' ? 'Agregar destino' : (isEditMode ? 'Editar transmisión' : 'Crear transmisión en vivo')
|
||||
const showBackButton = view === 'add-destination'
|
||||
|
||||
return (
|
||||
@ -353,7 +413,10 @@ const NewTransmissionModal: React.FC<Props> = ({ 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')
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -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<string, React.ReactNode> = {
|
||||
'Facebook': <FaFacebook size={16} color="#1877F2" />,
|
||||
'Twitch': <FaTwitch size={16} color="#9146FF" />,
|
||||
'LinkedIn': <FaLinkedin size={16} color="#0A66C2" />,
|
||||
'Genérico': <MdVideocam size={16} color="#5f6368" />, // Logo genérico para transmisiones sin destino
|
||||
}
|
||||
|
||||
const TransmissionsTable: React.FC<Props> = ({ transmissions, onDelete, onUpdate, isLoading }) => {
|
||||
const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming')
|
||||
const [inviteOpen, setInviteOpen] = useState(false)
|
||||
const [inviteLink, setInviteLink] = useState<string | undefined>(undefined)
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [editTransmission, setEditTransmission] = useState<Transmission | undefined>(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,30 +106,40 @@ const TransmissionsTable: React.FC<Props> = ({ transmissions, onDelete, onUpdate
|
||||
<tbody>
|
||||
{filtered.map(t => (
|
||||
<tr key={t.id} className={styles.tableRow}>
|
||||
<td className={styles.tableCell} colSpan={4}>
|
||||
<div className={styles.tableCard}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<td className={styles.tableCell}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div className={styles.platformAvatar}>
|
||||
<div className={styles.platformIcon}>{platformIcons[t.platform] || platformIcons['YouTube']}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.transmissionTitle}>{t.title}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>{t.scheduled || '—'}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
|
||||
{t.platform === 'Genérico' ? 'Solo grabación' : (t.platform || 'YouTube')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td className={styles.tableCell}>
|
||||
<span style={{ fontSize: 14, color: 'var(--text-primary)' }}>
|
||||
{t.createdAt || '---'}
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.tableCell}>
|
||||
<span style={{ fontSize: 14, color: 'var(--text-primary)' }}>
|
||||
{(t.scheduled && t.scheduled !== 'Próximamente') ? t.scheduled : '---'}
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.tableCell} style={{ textAlign: 'right' }}>
|
||||
<div className={styles.actionsCell}>
|
||||
<button aria-label={`Entrar al estudio ${t.title}`} className={styles.enterStudioButton} onClick={() => {/* enter studio logic placeholder */}}>
|
||||
Entrar al estudio
|
||||
</button>
|
||||
|
||||
<>
|
||||
<Dropdown
|
||||
trigger={<button className={styles.moreOptionsButton} aria-label={`Más opciones ${t.title}`}><MdMoreVert size={20} /></button>}
|
||||
items={[
|
||||
{ label: 'Agregar invitados', icon: <MdPersonAdd size={16} />, onClick: () => { setInviteLink(`https://streamyard.com/${t.id}`); setInviteOpen(true) } },
|
||||
{ label: 'Editar', icon: <MdEdit size={16} />, onClick: () => {/* editar */} },
|
||||
{ label: 'Editar', icon: <MdEdit size={16} />, onClick: () => handleEdit(t) },
|
||||
{ divider: true, label: '', disabled: false },
|
||||
{ label: 'Ver en YouTube', icon: <MdOpenInNew size={16} />, onClick: () => {/* abrir */} },
|
||||
{ divider: true, label: '', disabled: false },
|
||||
@ -127,9 +147,6 @@ const TransmissionsTable: React.FC<Props> = ({ transmissions, onDelete, onUpdate
|
||||
]}
|
||||
/>
|
||||
<InviteGuestsModal open={inviteOpen} onClose={() => setInviteOpen(false)} link={inviteLink} />
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -138,6 +155,14 @@ const TransmissionsTable: React.FC<Props> = ({ transmissions, onDelete, onUpdate
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NewTransmissionModal
|
||||
open={editOpen}
|
||||
onClose={() => { setEditOpen(false); setEditTransmission(undefined) }}
|
||||
onCreate={() => {}}
|
||||
onUpdate={onUpdate}
|
||||
transmission={editTransmission}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,4 +3,5 @@ export interface Transmission {
|
||||
title: string
|
||||
platform: string
|
||||
scheduled: string
|
||||
createdAt?: string // Fecha de creación
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user