- Introduced ModalDestinationButton for destination selection with customizable icons and labels. - Added ModalInput for text input with optional character counter. - Implemented ModalLink for reusable links styled as underlined text. - Created ModalPlatformCard for platform selection with badges. - Developed ModalRadioGroup for radio button groups with custom styling. - Added ModalSection for grouping modal content with optional labels. - Implemented ModalSelect for dropdown selections with custom styling. - Created ModalShareButtons for sharing options via Gmail, Email, and Messenger. - Developed ModalTextarea for multi-line text input with character counter. - Introduced ModalToggle for toggle switches with optional help text and links. - Updated README.md with component descriptions, usage examples, and design guidelines. - Added index.ts for centralized exports of modal components.
146 lines
6.0 KiB
TypeScript
146 lines
6.0 KiB
TypeScript
import React, { useState } from 'react'
|
|
import { MdMoreVert, MdVideocam, MdPersonAdd, MdEdit, MdOpenInNew, MdDelete } from 'react-icons/md'
|
|
import { Dropdown } from './Dropdown'
|
|
import { FaYoutube, FaFacebook, FaTwitch, FaLinkedin } from 'react-icons/fa'
|
|
import { SkeletonTable } from './Skeleton'
|
|
import styles from './TransmissionsTable.module.css'
|
|
import InviteGuestsModal from './InviteGuestsModal'
|
|
import type { Transmission } from '../types'
|
|
|
|
interface Props {
|
|
transmissions: Transmission[]
|
|
onDelete: (id: string) => void
|
|
onUpdate: (t: Transmission) => void
|
|
isLoading?: boolean
|
|
}
|
|
|
|
const platformIcons: Record<string, React.ReactNode> = {
|
|
'YouTube': <FaYoutube size={16} color="#FF0000" />,
|
|
'Facebook': <FaFacebook size={16} color="#1877F2" />,
|
|
'Twitch': <FaTwitch size={16} color="#9146FF" />,
|
|
'LinkedIn': <FaLinkedin size={16} color="#0A66C2" />,
|
|
}
|
|
|
|
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)
|
|
|
|
// Filtrado por fechas
|
|
const filtered = transmissions.filter(t => {
|
|
if (!t.scheduled) return activeTab === 'upcoming'
|
|
|
|
const scheduledDate = new Date(t.scheduled)
|
|
const now = new Date()
|
|
|
|
if (activeTab === 'upcoming') {
|
|
return scheduledDate >= now
|
|
} else {
|
|
return scheduledDate < now
|
|
}
|
|
})
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className={styles.transmissionsSection}>
|
|
<div className={styles.tabContainer}>
|
|
<button className={`${styles.tabButton} ${styles.activeTab}`}>
|
|
Próximamente
|
|
</button>
|
|
<button className={styles.tabButton}>
|
|
Anteriores
|
|
</button>
|
|
</div>
|
|
<SkeletonTable rows={5} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className={styles.transmissionsSection}>
|
|
<div className={styles.tabContainer}>
|
|
<button
|
|
onClick={() => setActiveTab('upcoming')}
|
|
className={`${styles.tabButton} ${activeTab === 'upcoming' ? styles.activeTab : ''}`}
|
|
>
|
|
Próximamente
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('past')}
|
|
className={`${styles.tabButton} ${activeTab === 'past' ? styles.activeTab : ''}`}
|
|
>
|
|
Anteriores
|
|
</button>
|
|
</div>
|
|
|
|
{!filtered || filtered.length === 0 ? (
|
|
<div className={styles.tableWrapper}>
|
|
<div className={styles.noDataCell}>
|
|
{activeTab === 'upcoming'
|
|
? 'No hay transmisiones programadas todavía.'
|
|
: 'No hay transmisiones anteriores.'
|
|
}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className={styles.tableWrapper}>
|
|
<table className={styles.transmissionsTable}>
|
|
<thead>
|
|
<tr>
|
|
<th className={styles.tableHeader}>Título</th>
|
|
<th className={styles.tableHeader}>Creado</th>
|
|
<th className={styles.tableHeader}>Programado</th>
|
|
<th className={styles.tableHeader} style={{ textAlign: 'right' }}></th>
|
|
</tr>
|
|
</thead>
|
|
<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%' }}>
|
|
<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>
|
|
</div>
|
|
|
|
<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 */} },
|
|
{ divider: true, label: '', disabled: false },
|
|
{ label: 'Ver en YouTube', icon: <MdOpenInNew size={16} />, onClick: () => {/* abrir */} },
|
|
{ divider: true, label: '', disabled: false },
|
|
{ label: 'Eliminar transmisión', icon: <MdDelete size={16} />, onClick: () => onDelete(t.id), containerProps: { className: styles.deleteItem }, labelProps: { className: styles.dangerLabel } }
|
|
]}
|
|
/>
|
|
<InviteGuestsModal open={inviteOpen} onClose={() => setInviteOpen(false)} link={inviteLink} />
|
|
</>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default TransmissionsTable
|