refactor(ui): migrate positions workflow controls
This commit is contained in:
parent
95c3c5e8bf
commit
d5a8f36b35
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user