|
|
@@ -1,10 +1,12 @@
|
|
|
'use client';
|
|
|
|
|
|
-import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
|
+import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
|
import { SignalTable } from '@/components/SignalTable';
|
|
|
import { OrderPanel } from '@/components/OrderPanel';
|
|
|
import { HKOTempCard, HKODailyHighCard } from './components/HKOTempCard';
|
|
|
import { HKOForecastChart } from './components/HKOForecastChart';
|
|
|
+import { HKODailyCurve } from './components/HKODailyCurve';
|
|
|
+import { Switch } from '@/components/ui/switch';
|
|
|
import type { Signal, WeatherMarket, HKOCurrentTemp, HKOForecast } from '@/lib/types';
|
|
|
|
|
|
const strategyTypes = ['ALL', 'TAIL_NO', 'OBSERVED_YES', 'OBSERVED_NO'] as const;
|
|
|
@@ -30,6 +32,10 @@ export function HongKongClient({ signals, markets }: Props) {
|
|
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
|
const [showOrderPanel, setShowOrderPanel] = useState(false);
|
|
|
const [hktTime, setHktTime] = useState('');
|
|
|
+ const [autoExecute, setAutoExecute] = useState(false);
|
|
|
+ const [botConnected, setBotConnected] = useState(false);
|
|
|
+ const [toggling, setToggling] = useState(false);
|
|
|
+ const togglingRef = useRef(false);
|
|
|
|
|
|
// Fetch HKO current temperature
|
|
|
const fetchCurrent = useCallback(async () => {
|
|
|
@@ -61,17 +67,55 @@ export function HongKongClient({ signals, markets }: Props) {
|
|
|
}
|
|
|
}, []);
|
|
|
|
|
|
+ // Fetch bot status including auto-execute state
|
|
|
+ const fetchBotStatus = useCallback(async () => {
|
|
|
+ try {
|
|
|
+ const res = await fetch('/api/bot-control');
|
|
|
+ if (res.ok) {
|
|
|
+ const data = await res.json();
|
|
|
+ setBotConnected(data.connected);
|
|
|
+ if (!togglingRef.current) {
|
|
|
+ setAutoExecute((data.autoExecuteCities ?? []).includes('hong_kong'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ setBotConnected(false);
|
|
|
+ }
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const toggleAutoExecute = useCallback(async (enabled: boolean) => {
|
|
|
+ togglingRef.current = true;
|
|
|
+ setToggling(true);
|
|
|
+ setAutoExecute(enabled); // optimistic
|
|
|
+ try {
|
|
|
+ await fetch('/api/bot-control', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify({ city: 'hong_kong', enabled }),
|
|
|
+ });
|
|
|
+ } catch {
|
|
|
+ setAutoExecute(!enabled); // revert
|
|
|
+ } finally {
|
|
|
+ setToggling(false);
|
|
|
+ // Allow polling to resume after a short delay so the bot state is updated
|
|
|
+ setTimeout(() => { togglingRef.current = false; }, 2000);
|
|
|
+ }
|
|
|
+ }, []);
|
|
|
+
|
|
|
// Initialize data + refresh intervals
|
|
|
useEffect(() => {
|
|
|
fetchCurrent();
|
|
|
fetchForecast();
|
|
|
+ fetchBotStatus();
|
|
|
const tempInterval = setInterval(fetchCurrent, 3 * 60 * 1000); // 3 min
|
|
|
const forecastInterval = setInterval(fetchForecast, 30 * 60 * 1000); // 30 min
|
|
|
+ const botInterval = setInterval(fetchBotStatus, 10_000);
|
|
|
return () => {
|
|
|
clearInterval(tempInterval);
|
|
|
clearInterval(forecastInterval);
|
|
|
+ clearInterval(botInterval);
|
|
|
};
|
|
|
- }, [fetchCurrent, fetchForecast]);
|
|
|
+ }, [fetchCurrent, fetchForecast, fetchBotStatus]);
|
|
|
|
|
|
// Live HKT clock
|
|
|
useEffect(() => {
|
|
|
@@ -125,23 +169,36 @@ export function HongKongClient({ signals, markets }: Props) {
|
|
|
HKO-powered temperature trading • {hktTime} HKT
|
|
|
</p>
|
|
|
</div>
|
|
|
- <a
|
|
|
- href="https://www.weather.gov.hk/en/wxinfo/currwx/current.htm"
|
|
|
- target="_blank"
|
|
|
- rel="noopener noreferrer"
|
|
|
- className="btn-secondary text-xs px-3 py-1.5 rounded-lg"
|
|
|
- >
|
|
|
- HKO Website →
|
|
|
- </a>
|
|
|
+ <div className="flex items-center gap-4">
|
|
|
+ {botConnected && (
|
|
|
+ <div className="flex items-center gap-3 px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800/50">
|
|
|
+ <div className="flex flex-col">
|
|
|
+ <span className="text-xs font-medium text-heading">Auto-Execute</span>
|
|
|
+ <span className="text-[10px] text-dim">OBSERVED_NO only</span>
|
|
|
+ </div>
|
|
|
+ <Switch
|
|
|
+ checked={autoExecute}
|
|
|
+ onCheckedChange={toggleAutoExecute}
|
|
|
+ disabled={toggling}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <a
|
|
|
+ href="https://www.weather.gov.hk/en/wxinfo/currwx/current.htm"
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer"
|
|
|
+ className="btn-secondary text-xs px-3 py-1.5 rounded-lg"
|
|
|
+ >
|
|
|
+ HKO Website →
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
{/* Top row: Temp card + Daily High + Forecast chart */}
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
<div className="lg:col-span-1 flex flex-col gap-6">
|
|
|
<HKOTempCard data={currentTemp} loading={tempLoading} />
|
|
|
- <div className="flex-1">
|
|
|
- <HKODailyHighCard data={currentTemp} loading={tempLoading} />
|
|
|
- </div>
|
|
|
+ <HKODailyHighCard data={currentTemp} loading={tempLoading} />
|
|
|
</div>
|
|
|
<div className="lg:col-span-2">
|
|
|
<HKOForecastChart
|
|
|
@@ -152,6 +209,9 @@ export function HongKongClient({ signals, markets }: Props) {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ {/* Today's temperature curve */}
|
|
|
+ <HKODailyCurve />
|
|
|
+
|
|
|
{/* Markets section */}
|
|
|
{markets.length > 0 && (
|
|
|
<div className="card p-6">
|