refactor(ui): migrate positions workflow controls

This commit is contained in:
Saravana Achu Mac 2026-05-06 14:04:24 -07:00
parent 95c3c5e8bf
commit d5a8f36b35

View File

@ -7,6 +7,7 @@ import { createRequestId } from '../../../shared/request-id.js';
import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react';
import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle';
import { fetchPositionsBootstrap } from '../lib/positionsApi'; import { fetchPositionsBootstrap } from '../lib/positionsApi';
import { Button, Input, Select } from '../components/ui/Primitives';
interface PositionsTabProps { interface PositionsTabProps {
botState: BotState; botState: BotState;
@ -1291,26 +1292,32 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
</div> </div>
<div className="flex gap-2 border-b border-white/5 items-end"> <div className="flex gap-2 border-b border-white/5 items-end">
<button <Button
onClick={() => setSelectedProfileId('all')} type="button"
className={`px-4 py-3 text-sm font-semibold transition-all border-b-2 ${selectedProfileId === 'all' onClick={() => setSelectedProfileId('all')}
? 'border-[#00ff88] text-[#00ff88]' variant="ghost"
: 'border-transparent text-gray-500 hover:text-white' size="sm"
}`} className={`h-auto rounded-none px-4 py-3 text-sm font-semibold transition-all border-b-2 ${selectedProfileId === 'all'
> ? 'border-[#00ff88] text-[#00ff88]'
Global : 'border-transparent text-gray-500 hover:text-white'
</button> }`}
{profiles.map(p => ( >
<button Global
key={p.id} </Button>
onClick={() => setSelectedProfileId(p.id)} {profiles.map(p => (
className={`px-4 py-3 text-sm font-semibold transition-all border-b-2 ${selectedProfileId === p.id <Button
? 'border-[#00ff88] text-[#00ff88]' key={p.id}
: 'border-transparent text-gray-500 hover:text-white' type="button"
}`} onClick={() => setSelectedProfileId(p.id)}
> variant="ghost"
{p.name} size="sm"
</button> className={`h-auto rounded-none px-4 py-3 text-sm font-semibold transition-all border-b-2 ${selectedProfileId === p.id
? 'border-[#00ff88] text-[#00ff88]'
: 'border-transparent text-gray-500 hover:text-white'
}`}
>
{p.name}
</Button>
))} ))}
</div> </div>
</header> </header>
@ -1587,52 +1594,64 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
Profile + Date filters Profile + Date filters
</th> </th>
<th className="px-4 py-2"> <th className="px-4 py-2">
<select <Select
value={selectedProfileId} value={selectedProfileId}
onChange={(e) => setSelectedProfileId(e.target.value)} onChange={(e) => setSelectedProfileId(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] font-semibold text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40" options={[
> { value: 'all', label: 'All Profiles' },
<option value="all">All Profiles</option> ...profiles.map((profileOption) => ({
{profiles.map((profileOption) => ( value: profileOption.id,
<option key={profileOption.id} value={profileOption.id}> label: profileOption.name,
{profileOption.name} })),
</option> ]}
))} controlSize="sm"
</select> variant="muted"
</th> className="w-full bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] font-semibold text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40"
<th className="px-4 py-2"> />
<div className="flex flex-col gap-1"> </th>
<input <th className="px-4 py-2">
type="date" <div className="flex flex-col gap-1">
value={ordersDateFrom} <Input
onChange={(e) => setOrdersDateFrom(e.target.value)} type="date"
className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40" value={ordersDateFrom}
/> onChange={(e) => setOrdersDateFrom(e.target.value)}
<input controlSize="sm"
type="date" variant="muted"
value={ordersDateTo} className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40"
onChange={(e) => setOrdersDateTo(e.target.value)} />
className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40" <Input
/> type="date"
<div className="flex items-center gap-2"> value={ordersDateTo}
<button onChange={(e) => setOrdersDateTo(e.target.value)}
onClick={() => { controlSize="sm"
setOrdersDateFrom(''); variant="muted"
setOrdersDateTo(''); className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500/40"
}} />
className="px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors" <div className="flex items-center gap-2">
> <Button
Clear type="button"
</button> onClick={() => {
<button setOrdersDateFrom('');
onClick={() => setOrdersSortDirection((current) => current === 'desc' ? 'asc' : 'desc')} setOrdersDateTo('');
className="px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors" }}
> variant="outline"
{ordersSortDirection === 'desc' ? 'Newest' : 'Oldest'} size="sm"
</button> className="h-auto px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors"
</div> >
</div> Clear
</th> </Button>
<Button
type="button"
onClick={() => setOrdersSortDirection((current) => current === 'desc' ? 'asc' : 'desc')}
variant="outline"
size="sm"
className="h-auto px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors"
>
{ordersSortDirection === 'desc' ? 'Newest' : 'Oldest'}
</Button>
</div>
</div>
</th>
<th colSpan={8} className="px-4 py-2 text-[10px] text-gray-500 font-medium"> <th colSpan={8} className="px-4 py-2 text-[10px] text-gray-500 font-medium">
Sorting is applied by time on this table. Sorting is applied by time on this table.
</th> </th>
@ -1775,23 +1794,29 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
-{Math.min(ordersPage * ORDER_ACTIVITY_PAGE_SIZE, sortedOrdersForActivity.length)} of {sortedOrdersForActivity.length} -{Math.min(ordersPage * ORDER_ACTIVITY_PAGE_SIZE, sortedOrdersForActivity.length)} of {sortedOrdersForActivity.length}
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <Button
onClick={() => setOrdersPage((current) => Math.max(1, current - 1))} type="button"
disabled={ordersPage === 1} onClick={() => setOrdersPage((current) => Math.max(1, current - 1))}
className="px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors" disabled={ordersPage === 1}
> variant="outline"
Prev size="sm"
</button> className="h-auto px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors"
>
Prev
</Button>
<span className="text-[10px] uppercase tracking-widest text-gray-500"> <span className="text-[10px] uppercase tracking-widest text-gray-500">
Page {ordersPage} / {ordersTotalPages} Page {ordersPage} / {ordersTotalPages}
</span> </span>
<button <Button
onClick={() => setOrdersPage((current) => Math.min(ordersTotalPages, current + 1))} type="button"
disabled={ordersPage >= ordersTotalPages} onClick={() => setOrdersPage((current) => Math.min(ordersTotalPages, current + 1))}
className="px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors" disabled={ordersPage >= ordersTotalPages}
> variant="outline"
Next size="sm"
</button> className="h-auto px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors"
>
Next
</Button>
</div> </div>
</div> </div>
</td> </td>
@ -1836,52 +1861,64 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
Profile + Date filters Profile + Date filters
</th> </th>
<th className="px-4 py-2"> <th className="px-4 py-2">
<select <Select
value={selectedProfileId} value={selectedProfileId}
onChange={(e) => setSelectedProfileId(e.target.value)} onChange={(e) => setSelectedProfileId(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] font-semibold text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40" options={[
> { value: 'all', label: 'All Profiles' },
<option value="all">All Profiles</option> ...profiles.map((profileOption) => ({
{profiles.map((profileOption) => ( value: profileOption.id,
<option key={profileOption.id} value={profileOption.id}> label: profileOption.name,
{profileOption.name} })),
</option> ]}
))} controlSize="sm"
</select> variant="muted"
className="w-full bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] font-semibold text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40"
/>
</th> </th>
<th className="px-4 py-2 text-[10px] text-gray-500 font-medium"> <th className="px-4 py-2 text-[10px] text-gray-500 font-medium">
Filter in headers Filter in headers
</th> </th>
<th className="px-4 py-2"> <th className="px-4 py-2">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<input <Input
type="date" type="date"
value={lifecycleDateFrom} value={lifecycleDateFrom}
onChange={(e) => setLifecycleDateFrom(e.target.value)} onChange={(e) => setLifecycleDateFrom(e.target.value)}
className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40" controlSize="sm"
/> variant="muted"
<input className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40"
type="date" />
value={lifecycleDateTo} <Input
onChange={(e) => setLifecycleDateTo(e.target.value)} type="date"
className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40" value={lifecycleDateTo}
/> onChange={(e) => setLifecycleDateTo(e.target.value)}
<div className="flex items-center gap-2"> controlSize="sm"
<button variant="muted"
onClick={() => { className="bg-white/5 border border-white/10 rounded px-2 py-1 text-[10px] text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500/40"
setLifecycleDateFrom(''); />
setLifecycleDateTo(''); <div className="flex items-center gap-2">
}} <Button
className="px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors" type="button"
> onClick={() => {
Clear setLifecycleDateFrom('');
</button> setLifecycleDateTo('');
<button }}
onClick={() => setLifecycleSortDirection((current) => current === 'desc' ? 'asc' : 'desc')} variant="outline"
className="px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors" size="sm"
> className="h-auto px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors"
{lifecycleSortDirection === 'desc' ? 'Newest' : 'Oldest'} >
</button> Clear
</Button>
<Button
type="button"
onClick={() => setLifecycleSortDirection((current) => current === 'desc' ? 'asc' : 'desc')}
variant="outline"
size="sm"
className="h-auto px-2 py-1 rounded border border-white/10 text-[9px] font-bold uppercase tracking-wider text-gray-300 hover:bg-white/5 transition-colors"
>
{lifecycleSortDirection === 'desc' ? 'Newest' : 'Oldest'}
</Button>
</div> </div>
</div> </div>
</th> </th>
@ -1992,23 +2029,29 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
-{Math.min(lifecyclePage * TRACE_PAGE_SIZE, sortedLifecycleTraces.length)} of {sortedLifecycleTraces.length} -{Math.min(lifecyclePage * TRACE_PAGE_SIZE, sortedLifecycleTraces.length)} of {sortedLifecycleTraces.length}
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <Button
onClick={() => setLifecyclePage((current) => Math.max(1, current - 1))} type="button"
disabled={lifecyclePage === 1} onClick={() => setLifecyclePage((current) => Math.max(1, current - 1))}
className="px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors" disabled={lifecyclePage === 1}
> variant="outline"
Prev size="sm"
</button> className="h-auto px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors"
>
Prev
</Button>
<span className="text-[10px] uppercase tracking-widest text-gray-500"> <span className="text-[10px] uppercase tracking-widest text-gray-500">
Page {lifecyclePage} / {lifecycleTotalPages} Page {lifecyclePage} / {lifecycleTotalPages}
</span> </span>
<button <Button
onClick={() => setLifecyclePage((current) => Math.min(lifecycleTotalPages, current + 1))} type="button"
disabled={lifecyclePage >= lifecycleTotalPages} onClick={() => setLifecyclePage((current) => Math.min(lifecycleTotalPages, current + 1))}
className="px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors" disabled={lifecyclePage >= lifecycleTotalPages}
> variant="outline"
Next size="sm"
</button> className="h-auto px-3 py-1.5 rounded border border-white/10 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-white/5 transition-colors"
>
Next
</Button>
</div> </div>
</div> </div>
</td> </td>