152 lines
5.4 KiB
TypeScript
152 lines
5.4 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useMemo, useState } from 'react';
|
||
import { formatToTaipeiTime } from '@/lib/timezone';
|
||
import { getDailyCard, type DailyCardItem } from '@/lib/analytics-api';
|
||
import { ActionableBetCard } from '@/components/ActionableBetCard';
|
||
|
||
const TAB_MAP: Record<'safe' | 'risk' | 'parlay' | 'sgp', string> = {
|
||
safe: 'SAFE_SINGLE',
|
||
risk: 'HIGH_RISK_SINGLE',
|
||
parlay: 'SAFE_PARLAY',
|
||
sgp: 'SGP_LOTTERY',
|
||
};
|
||
|
||
const sampleDate = formatToTaipeiTime(new Date().toISOString(), 'yyyy-MM-dd');
|
||
|
||
export default function DailyCardPage() {
|
||
const [targetDate] = useState(sampleDate);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState('');
|
||
const [activeTab, setActiveTab] = useState<'safe' | 'risk' | 'parlay' | 'sgp'>('safe');
|
||
const [selectedCount, setSelectedCount] = useState(0);
|
||
const [data, setData] = useState<Awaited<ReturnType<typeof getDailyCard>> | null>(null);
|
||
|
||
const tabCards: Record<'safe' | 'risk' | 'parlay' | 'sgp', DailyCardItem[]> = useMemo(() => {
|
||
if (!data) {
|
||
return { safe: [], risk: [], parlay: [], sgp: [] };
|
||
}
|
||
|
||
return {
|
||
safe: data.safe_singles,
|
||
risk: data.high_risk_singles,
|
||
parlay: data.safe_parlays,
|
||
sgp: data.sgp_lotteries,
|
||
};
|
||
}, [data]);
|
||
|
||
const briefing = useMemo(() => {
|
||
if (!data) {
|
||
return '系統載入中,將於短時間內給出當日全場賽前簡報。';
|
||
}
|
||
|
||
return `AI 總結:今日比賽共 ${data.matched_matches} 場,策略核心為 ${
|
||
data.total_daily_unit_recommendation
|
||
} Units。${
|
||
data.safe_singles.length > 0
|
||
? `檢測到 ${data.safe_singles.length} 組高穩定單關機會,重點偏向亞盤與低風險連續進位。`
|
||
: '低風險盤口偏弱,建議保守減碼。'
|
||
} ${
|
||
data.high_risk_singles.length > 0
|
||
? `另有 ${data.high_risk_singles.length} 組高賠搏冷訊號可做小額槓桿。`
|
||
: ''
|
||
}`;
|
||
}, [data]);
|
||
|
||
useEffect(() => {
|
||
const load = async () => {
|
||
setLoading(true);
|
||
setError('');
|
||
|
||
try {
|
||
const response = await getDailyCard(targetDate);
|
||
setData(response);
|
||
} catch (payloadError) {
|
||
setError(payloadError instanceof Error ? payloadError.message : '每日作戰卡暫時無法抓取');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
load().catch(() => undefined);
|
||
}, [targetDate]);
|
||
|
||
function handleAddToSlip(item: DailyCardItem) {
|
||
setSelectedCount((count) => count + 1);
|
||
// 未建立後續注單 API,先保留為前端選單暫存
|
||
window.alert(`已加入注單追蹤:${item.match_label} | ${item.selection}`);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<section className="panel-glow rounded-2xl p-5">
|
||
<h2 className="dot-matrix text-2xl text-[#7d2a15]">每日操盤戰情室</h2>
|
||
<p className="mt-1 text-xs text-[#8a6b58]">總曝險單位:{data ? data.total_daily_unit_recommendation.toFixed(2) : '-'}</p>
|
||
<p className="mt-2 text-sm text-[#7a5b46]">日期:{targetDate}</p>
|
||
<p className="mt-2 text-sm text-[#6f4f3c]">{briefing}</p>
|
||
<p className="mt-2 text-xs text-[#8c2f2f]">已加入注單:{selectedCount} 單</p>
|
||
</section>
|
||
|
||
<section className="panel-glow rounded-2xl p-4">
|
||
<p className="dot-matrix text-lg text-[#7d2a15]">Hard Filters</p>
|
||
<div className="mt-2 flex flex-wrap gap-2">
|
||
<button
|
||
type="button"
|
||
className={`rounded-full px-3 py-2 text-sm ${
|
||
activeTab === 'safe' ? 'bg-[#7d2a15] text-white' : 'bg-white/70 text-[#5f4330]'
|
||
}`}
|
||
onClick={() => setActiveTab('safe')}
|
||
>
|
||
Safe Singles(高穩)
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`rounded-full px-3 py-2 text-sm ${
|
||
activeTab === 'risk' ? 'bg-[#7d2a15] text-white' : 'bg-white/70 text-[#5f4330]'
|
||
}`}
|
||
onClick={() => setActiveTab('risk')}
|
||
>
|
||
High-Risk Underdog(搏冷)
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`rounded-full px-3 py-2 text-sm ${
|
||
activeTab === 'parlay' ? 'bg-[#7d2a15] text-white' : 'bg-white/70 text-[#5f4330]'
|
||
}`}
|
||
onClick={() => setActiveTab('parlay')}
|
||
>
|
||
Safe Parlays(跨場)
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`rounded-full px-3 py-2 text-sm ${
|
||
activeTab === 'sgp' ? 'bg-[#7d2a15] text-white' : 'bg-white/70 text-[#5f4330]'
|
||
}`}
|
||
onClick={() => setActiveTab('sgp')}
|
||
>
|
||
SGP(同場關聯)
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
{loading ? <p className="text-sm text-[#8a6b58]">載入中...</p> : null}
|
||
{error ? <p className="text-sm text-[#8c2f2f]">{error}</p> : null}
|
||
|
||
<section className="grid gap-4 md:grid-cols-2">
|
||
{tabCards[activeTab].map((item) => (
|
||
<ActionableBetCard
|
||
key={`${item.match_id}-${item.selection}-${item.market_type}`}
|
||
item={item}
|
||
onAddToSlip={handleAddToSlip}
|
||
className="panel-glow"
|
||
/>
|
||
))}
|
||
|
||
{!loading && tabCards[activeTab].length === 0 ? (
|
||
<p className="panel-glow rounded-2xl p-4 text-sm text-[#7a5b46]">目前此策略區塊沒有符合條件的建議。</p>
|
||
) : null}
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|