aboutsummaryrefslogtreecommitdiff
path: root/client/src/components/BidsPage.jsx
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2025-04-27 22:44:20 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2025-04-27 22:44:20 -0600
commit37787895d56888ab44362252f21fb05c05e97250 (patch)
tree5ff7eaababa6fffd5dc950920f521c5f242010af /client/src/components/BidsPage.jsx
parentf32142947b853076889801913d47b8c2c0f4f456 (diff)
reorganize
Diffstat (limited to 'client/src/components/BidsPage.jsx')
-rw-r--r--client/src/components/BidsPage.jsx182
1 files changed, 182 insertions, 0 deletions
diff --git a/client/src/components/BidsPage.jsx b/client/src/components/BidsPage.jsx
new file mode 100644
index 0000000..e0c4128
--- /dev/null
+++ b/client/src/components/BidsPage.jsx
@@ -0,0 +1,182 @@
+import React, { useEffect, useState } from 'react';
+import { Table, Typography, Spin, Message, Card, Empty, Badge } from '@arco-design/web-react';
+import { IconArrowRise, IconArrowFall } from '@arco-design/web-react/icon';
+import '@arco-design/web-react/dist/css/arco.css';
+import API_BASE_URL from './config';
+
+const columns = (bids) => [
+ {
+ title: 'Timestamp',
+ dataIndex: 'timestamp',
+ sorter: (a, b) => new Date(a.timestamp) - new Date(b.timestamp),
+ render: (val) => new Date(val).toLocaleString(),
+ width: 180,
+ },
+ {
+ title: 'Quantity (MW)',
+ dataIndex: 'quantity',
+ sorter: (a, b) => a.quantity - b.quantity,
+ render: (val) => val.toFixed(2),
+ width: 180,
+ },
+ {
+ title: 'Price ($/MWh)',
+ dataIndex: 'price',
+ sorter: (a, b) => a.price - b.price,
+ render: (val) => `$${val.toFixed(2)}`,
+ width: 180,
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ sorter: (a, b) => a.status.localeCompare(b.status),
+ filters: [
+ { text: 'Submitted', value: 'Submitted' },
+ { text: 'Success', value: 'Success' },
+ { text: 'Fail', value: 'Fail' },
+ ],
+ onFilter: (value, record) => record.status === value,
+ render: (val) => (
+ <Badge status={val === 'Success' ? 'success' : val === 'Fail' ? 'error' : 'processing'} text={val} />
+ ),
+ width: 180,
+ },
+ {
+ title: 'PnL',
+ dataIndex: 'pnl',
+ sorter: (a, b) => (a.pnl || 0) - (b.pnl || 0),
+ render: (val) => {
+ if (val === null) return 'N/A';
+ const isProfit = val >= 0;
+ const color = isProfit ? 'green' : 'red';
+ return (
+ <Typography.Text style={{ color, display: 'flex', alignItems: 'center', gap: 4 }}>
+ {isProfit ? <IconArrowRise /> : <IconArrowFall />}
+ {val.toFixed(2)}
+ </Typography.Text>
+ );
+ },
+ width: 180,
+ },
+ {
+ title: 'Market',
+ dataIndex: 'market',
+ sorter: (a, b) => a.market.localeCompare(b.market),
+ filters: [
+ { text: 'ISONE', value: 'ISONE' },
+ { text: 'NYISO', value: 'NYISO' },
+ { text: 'MISO', value: 'MISO' },
+ ],
+ onFilter: (value, record) => record.market === value,
+ render: (val) => val,
+ width: 180,
+ },
+];
+
+function BidsPage() {
+ const [bids, setBids] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ fetch(`${API_BASE_URL}/bids/`)
+ .then((res) => {
+ if (!res.ok) throw new Error('Failed to fetch bids');
+ return res.json();
+ })
+ .then((json) => setBids(json))
+ .catch((err) => {
+ console.error(err);
+ Message.error('Failed to load bids.');
+ })
+ .finally(() => setLoading(false));
+ }, []);
+
+ return (
+ <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)', minHeight: 'calc(100vh - 150px)', animation: 'fadeSlideIn 0.6s ease' }}>
+ <Typography.Title heading={3} style={{ textAlign: 'center', marginBottom: 20 }}>
+ Your Submitted Bids
+ </Typography.Title>
+
+ <Card
+ style={{
+ marginTop: 16,
+ borderRadius: 16,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.08)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(245,245,245,0.95) 100%)',
+ backdropFilter: 'blur(8px)',
+ overflow: 'hidden',
+ padding: 20
+ }}
+ >
+ {loading ? (
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Spin />
+ </div>
+ ) : bids.length === 0 ? (
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Empty description="No bids found yet." />
+ </div>
+ ) : (
+ <div className="responsive-table-wrapper">
+ <Table
+ columns={columns(bids)}
+ data={bids}
+ rowKey="id"
+ pagination={{
+ pageSize: 50,
+ sizeCanChange: true,
+ showTotal: true,
+ }}
+ scroll={{ x: true }}
+ border
+ style={{ transition: 'opacity 0.5s ease-in-out' }}
+ rowClassName={() => 'table-row-hover'}
+ />
+ </div>
+ )}
+ </Card>
+
+ <style>{`
+ @keyframes fadeSlideIn {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+ .table-row-hover:hover {
+ background-color: var(--color-fill-3);
+ transition: background-color 0.3s ease;
+ }
+
+ responsive-table-wrapper {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ /* Prevent wrapping in header and cells */
+ .responsive-table-wrapper table th,
+ .responsive-table-wrapper table td {
+ white-space: nowrap;
+ }
+
+ /* Optional: smaller fonts on mobile */
+ @media (max-width: 768px) {
+ .responsive-table-wrapper table {
+ font-size: 12px;
+ }
+ .responsive-table-wrapper th,
+ .responsive-table-wrapper td {
+ padding: 8px;
+ }
+ }
+
+ `}</style>
+ </div>
+ );
+}
+
+export default BidsPage;