Cesar Mendivil e43686e36d feat(modal-parts): add modular components for modal UI
- 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.
2025-11-06 00:32:08 -07:00

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