← Back
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import type { ScreenerEvent, EventOutcome } from '../../hooks/useEvents';
import { formatVolume, formatPrice, timeUntil } from '../../utils/format';
import { CategoryBadge } from './MarketsTable';
import { useT } from '../../i18n/LanguageContext';

interface Props {
  events: ScreenerEvent[];
  loading: boolean;
}

export default function EventsList({ events, loading }: Props) {
  const { t } = useT();
  if (loading && events.length === 0) {
    return (
      <div className="mc-grid">
        {Array.from({ length: 6 }, (_, i) => (
          <div key={i} className="mc-card mc-skeleton">
            <div className="mc-head">
              <div className="skeleton-img" />
              <div className="skeleton-text" style={{ flex: 1, height: 14 }} />
            </div>
            <div className="skeleton-text" style={{ height: 40, marginTop: 10 }} />
          </div>
        ))}
      </div>
    );
  }

  if (events.length === 0) {
    return (
      <div className="table-container">
        <div className="table-empty">
          <p>{t('mkt.noEvents')}</p>
          <span>{t('mkt.noEventsSub')}</span>
        </div>
      </div>
    );
  }

  return (
    <div className="mc-grid">
      {events.map(ev => (
        <EventCard key={ev.eventId} ev={ev} />
      ))}
    </div>
  );
}

function EventCard({ ev }: { ev: ScreenerEvent }) {
  const { t } = useT();
  const navigate = useNavigate();
  const endsIn = timeUntil(ev.endDate);
  const tops = ev.topOutcomes ?? [];
  const more = (ev.outcomes ?? tops.length) - tops.length;

  const [modalOpen, setModalOpen] = useState(false);
  const [allOutcomes, setAllOutcomes] = useState<EventOutcome[] | null>(null);
  const [loadingAll, setLoadingAll] = useState(false);

  const openAll = async () => {
    setModalOpen(true);
    if (allOutcomes || loadingAll) return;
    setLoadingAll(true);
    try {
      const r = await fetch(`/api/screener/events/${ev.eventId}/outcomes`);
      const d = await r.json();
      if (d.success) setAllOutcomes(d.data?.outcomes ?? []);
    } catch { /* ignore */ }
    finally { setLoadingAll(false); }
  };

  const goMarket = (marketId: string) => { setModalOpen(false); navigate(`/market/${marketId}`); };

  return (
    <div className="ev-card">
      <div className="ev-head">
        {ev.imageUrl && (
          <img src={ev.imageUrl} alt="" className="mc-img" loading="lazy"
            onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />
        )}
        <span className="mc-title">{ev.title}</span>
        <span className="ev-count">{ev.outcomes} {t(ev.outcomes === 1 ? 'mkt.outcomeOne' : 'mkt.outcomeMany')}</span>
      </div>

      <div className="mc-meta-row">
        <CategoryBadge category={ev.category} />
        <span className="mc-stat">{t('mkt.volShort')} {formatVolume(ev.volume24h)}</span>
        <span className="mc-stat">{t('mkt.liqShort')} {formatVolume(ev.liquidity)}</span>
        {endsIn && <span className="mc-stat mc-stat-ends">⏳ {endsIn}</span>}
      </div>

      <div className="ev-outcomes">
        {tops.map(o => {
          const pct = Math.round(Math.max(0, Math.min(100, o.yesPrice * 100)));
          const color = pct >= 70 ? 'var(--profit)' : pct <= 30 ? 'var(--loss)' : 'var(--text-primary)';
          return (
            <div
              key={o.marketId}
              className="ev-out"
              onClick={() => navigate(`/market/${o.marketId}`)}
            >
              <span className="ev-out-label">{o.label || o.question}</span>
              <span className="ev-out-price" style={{ color }}>{formatPrice(o.yesPrice)}</span>
            </div>
          );
        })}
        {more > 0 && (
          <button
            type="button"
            className="ev-more"
            onClick={e => { e.stopPropagation(); openAll(); }}
          >
            +{more} {t(more === 1 ? 'mkt.outcomeOne' : 'mkt.outcomeMany')} →
          </button>
        )}
      </div>

      {modalOpen && (
        <div
          className="ev-modal-backdrop"
          onClick={() => setModalOpen(false)}
          style={{
            position: 'fixed', inset: 0, zIndex: 1000,
            background: 'rgba(0,0,0,0.55)', display: 'flex',
            alignItems: 'center', justifyContent: 'center', padding: 16,
          }}
        >
          <div
            className="ev-modal"
            onClick={e => e.stopPropagation()}
            style={{
              background: 'var(--card-bg)', borderRadius: 12, width: '100%',
              maxWidth: 460, maxHeight: '80vh', overflowY: 'auto',
              border: '1px solid var(--border)', padding: 16,
            }}
          >
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
              <span className="mc-title" style={{ flex: 1 }}>{ev.title}</span>
              <button
                type="button"
                onClick={() => setModalOpen(false)}
                style={{ background: 'none', border: 'none', color: 'var(--text-secondary)', fontSize: 20, cursor: 'pointer', lineHeight: 1 }}
              >×</button>
            </div>
            {loadingAll && <div className="ev-out-label" style={{ padding: 8 }}>…</div>}
            {!loadingAll && (allOutcomes ?? []).map(o => {
              const pct = Math.round(Math.max(0, Math.min(100, o.yesPrice * 100)));
              const color = pct >= 70 ? 'var(--profit)' : pct <= 30 ? 'var(--loss)' : 'var(--text-primary)';
              return (
                <div key={o.marketId} className="ev-out" onClick={() => goMarket(o.marketId)}>
                  <span className="ev-out-label">{o.label || o.question}</span>
                  <span className="ev-out-price" style={{ color }}>{formatPrice(o.yesPrice)}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

📜 Git History

6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...