← back to Bertha

kalshi-dash/src/pages/Orders.jsx

303 lines

import React, { useState, useEffect } from 'react';
import { post } from '../api';

// ── Status badge mapping ──
function statusBadge(status) {
  if (status === 'filled') return 'bg-green';
  if (status === 'resting') return 'bg-orange';
  if (status === 'canceled') return 'bg-red';
  return 'bg-blue';
}

// ── Empty state ──
function EmptyState({ label }) {
  return (
    <tr>
      <td colSpan={20}>
        <div className="flex flex-col items-center justify-center py-16 gap-3">
          <svg
            width="40"
            height="40"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
            strokeLinecap="round"
            strokeLinejoin="round"
            style={{ color: 'var(--text-muted)' }}
          >
            <path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" />
            <rect x="9" y="3" width="6" height="4" rx="1" />
            <path d="M9 12h6M9 16h4" />
          </svg>
          <p style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-body)', fontSize: '0.875rem' }}>
            {label}
          </p>
        </div>
      </td>
    </tr>
  );
}

// ── Spinner ──
function Spinner() {
  return (
    <div className="flex items-center justify-center h-64">
      <div
        className="w-9 h-9 rounded-full border-2 animate-spin"
        style={{ borderColor: 'var(--border)', borderTopColor: 'var(--cyan)' }}
      />
    </div>
  );
}

export default function Orders() {
  const [orders, setOrders] = useState([]);
  const [fills, setFills] = useState([]);
  const [loading, setLoading] = useState(true);
  const [tab, setTab] = useState('orders');

  useEffect(() => { load(); }, []);

  async function load() {
    setLoading(true);
    try {
      const [o, f] = await Promise.all([
        post('/kalshi', { action: 'get_orders' }),
        post('/kalshi', { action: 'get_fills' }),
      ]);
      setOrders(o.orders || []);
      setFills(f.fills || []);
    } catch {}
    setLoading(false);
  }

  async function cancelOrder(orderId) {
    try {
      await post('/kalshi', { action: 'cancel_order', order_id: orderId });
      load();
    } catch {}
  }

  if (loading) return <Spinner />;

  return (
    <div className="space-y-6 fade-in">

      {/* ── Page Header ── */}
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          {/* Arrows icon */}
          <div
            className="flex items-center justify-center w-10 h-10 rounded-xl"
            style={{ background: 'var(--cyan-dim)', color: 'var(--cyan)' }}
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
            >
              <path d="M7 16V4m0 0L3 8m4-4l4 4" />
              <path d="M17 8v12m0 0l4-4m-4 4l-4-4" />
            </svg>
          </div>
          <div>
            <h1 className="heading text-3xl" style={{ color: 'var(--text)' }}>Orders</h1>
            <p className="section-subtitle mt-0.5">
              {orders.length} orders &bull; {fills.length} fills
            </p>
          </div>
        </div>
        <button onClick={load} className="btn btn-o btn-sm">Refresh</button>
      </div>

      {/* ── Tab Switcher ── */}
      <div
        className="flex rounded-xl p-1 gap-1 w-fit"
        style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
      >
        <button
          onClick={() => setTab('orders')}
          className="btn btn-sm"
          style={
            tab === 'orders'
              ? { background: 'var(--cyan)', color: 'var(--bg)' }
              : { background: 'transparent', color: 'var(--text-muted)', border: 'none' }
          }
        >
          Orders
          <span
            className="badge ml-1"
            style={
              tab === 'orders'
                ? { background: 'rgba(0,0,0,0.2)', color: 'var(--bg)', fontSize: '0.65rem' }
                : { background: 'var(--surface)', color: 'var(--text-muted)', fontSize: '0.65rem' }
            }
          >
            {orders.length}
          </span>
        </button>
        <button
          onClick={() => setTab('fills')}
          className="btn btn-sm"
          style={
            tab === 'fills'
              ? { background: 'var(--cyan)', color: 'var(--bg)' }
              : { background: 'transparent', color: 'var(--text-muted)', border: 'none' }
          }
        >
          Fills
          <span
            className="badge ml-1"
            style={
              tab === 'fills'
                ? { background: 'rgba(0,0,0,0.2)', color: 'var(--bg)', fontSize: '0.65rem' }
                : { background: 'var(--surface)', color: 'var(--text-muted)', fontSize: '0.65rem' }
            }
          >
            {fills.length}
          </span>
        </button>
      </div>

      {/* ── Orders Table ── */}
      {tab === 'orders' && (
        <div className="card overflow-x-auto" style={{ padding: 0 }}>
          <table>
            <thead>
              <tr>
                <th>Ticker</th>
                <th>Side</th>
                <th>Action</th>
                <th>Type</th>
                <th>Price</th>
                <th>Qty</th>
                <th>Filled</th>
                <th>Status</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {orders.length === 0 ? (
                <EmptyState label="No orders yet" />
              ) : (
                orders.map((o, i) => (
                  <tr key={i}>
                    <td>
                      <a
                        href={`https://kalshi.com/search?query=${encodeURIComponent((o.ticker || '').split('-')[0])}`}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="mono text-xs hover:underline transition"
                        style={{ color: 'var(--orange)' }}
                      >
                        {o.ticker}
                      </a>
                    </td>
                    <td>
                      <span className={`badge ${o.side === 'yes' ? 'bg-green' : 'bg-red'}`}>
                        {(o.side || '').toUpperCase()}
                      </span>
                    </td>
                    <td className="text-xs" style={{ color: 'var(--text-dim)' }}>
                      {(o.action || '').toUpperCase()}
                    </td>
                    <td className="text-xs" style={{ color: 'var(--text-muted)' }}>{o.type}</td>
                    <td className="mono" style={{ color: 'var(--cyan)' }}>
                      {o.yes_price || o.no_price || '—'}¢
                    </td>
                    <td className="mono">{o.count || o.remaining_count || 0}</td>
                    <td className="mono text-green">
                      {o.count - (o.remaining_count || 0)}
                    </td>
                    <td>
                      <span className={`badge ${statusBadge(o.status)}`}>{o.status}</span>
                    </td>
                    <td>
                      {o.status === 'resting' && (
                        <button
                          onClick={() => cancelOrder(o.order_id)}
                          className="btn btn-d btn-sm"
                        >
                          Cancel
                        </button>
                      )}
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      )}

      {/* ── Fills Table ── */}
      {tab === 'fills' && (
        <div className="card overflow-x-auto" style={{ padding: 0 }}>
          <table>
            <thead>
              <tr>
                <th>Ticker</th>
                <th>Side</th>
                <th>Action</th>
                <th>Price</th>
                <th>Qty</th>
                <th>Maker/Taker</th>
                <th>Time</th>
              </tr>
            </thead>
            <tbody>
              {fills.length === 0 ? (
                <EmptyState label="No fills yet" />
              ) : (
                fills.map((f, i) => (
                  <tr key={i}>
                    <td>
                      <a
                        href={`https://kalshi.com/search?query=${encodeURIComponent((f.ticker || '').split('-')[0])}`}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="mono text-xs hover:underline transition"
                        style={{ color: 'var(--orange)' }}
                      >
                        {f.ticker}
                      </a>
                    </td>
                    <td>
                      <span className={`badge ${f.side === 'yes' ? 'bg-green' : 'bg-red'}`}>
                        {(f.side || '').toUpperCase()}
                      </span>
                    </td>
                    <td className="text-xs" style={{ color: 'var(--text-dim)' }}>
                      {(f.action || '').toUpperCase()}
                    </td>
                    <td className="mono" style={{ color: 'var(--cyan)' }}>
                      {f.yes_price || f.no_price || '—'}¢
                    </td>
                    <td className="mono">{f.count}</td>
                    <td>
                      {f.is_taker ? (
                        <span className="badge bg-orange">TAKER</span>
                      ) : (
                        <span className="badge bg-green">MAKER</span>
                      )}
                    </td>
                    <td className="mono text-xs" style={{ color: 'var(--text-muted)' }}>
                      {f.created_time ? new Date(f.created_time).toLocaleString() : '—'}
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}