import { useEffect, useMemo, useState } from 'react';
import Icon from './Icon';
interface Props {
locale: string;
dashboard: { brandName: string; navHome: string; navStore: string; navDivination: string; navManual: string; navAuto: string; navHistory: string; navLanguage: string; navSettings: string; logout: string };
divination: { questionTitle: string; questionPlaceholder: string; categoryLabel: string; categories: string; timeTitle: string; timeHint: string; guideTitle: string; guideManual: string; yaoTitle: string; coinLabel: string; confirmBtn: string; summaryTitle: string; checkCategory: string; checkMethod: string; checkCost: string; submitBtn: string; progressLabel: string };
}
type CoinFace = 'zi' | 'hua';
type YaoType = 'youngYang' | 'youngYin' | 'oldYang' | 'oldYin';
const TOTAL_YAO_COUNT = 6;
function formatDateTimeInput(value: Date): string {
const pad = (n: number) => String(n).padStart(2, '0');
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`;
}
function fromHuaCount(huaCount: number): YaoType {
switch (huaCount) {
case 0:
return 'oldYin';
case 1:
return 'youngYang';
case 2:
return 'youngYin';
case 3:
return 'oldYang';
default:
throw new RangeError('huaCount must be 0..3');
}
}
function fromCoins(coins: CoinFace[]): YaoType {
return fromHuaCount(coins.filter((coin) => coin === 'hua').length);
}
function CoinImage({ face, selected }: { face: CoinFace; selected?: boolean }) {
return (
);
}
function YaoGlyph({ type, active }: { type?: YaoType; active?: boolean }) {
const color = active || type ? 'bg-violet-700' : 'bg-slate-200';
if (!type || type === 'youngYang' || type === 'oldYang') {
return
;
}
return (
);
}
const copy = {
zh: {
title: '手动起卦',
subtitle: '准备三枚相同的钱币,从初爻到上爻依次录入六次结果。',
balance: '可用 120 积分 · 本次 20 积分',
defaultQuestion: '我接下来三个月的事业发展需要注意什么?',
modify: '修改',
guideLines: ['从初爻开始,按从下往上的顺序记录。', '每一爻由三枚钱币的字面/花面组合决定。', '六爻完成后才可开始解卦。'],
openGuide: '查看手动起卦教程',
guideSteps: [
['手动起卦', '准备三枚相同的钱币。每次记录一爻,按从下往上的顺序共记录六爻。'],
['确认时间', '先确认起卦时间。如需调整,点击右侧「修改」。'],
['依次录入六爻', '从初爻开始逐条选择,未完成前下一爻不可点。每条会弹出三枚钱币选择面板。'],
['开始分析', '六爻都填完后,「开始解卦」按钮会高亮提示,点击即可解卦。'],
],
closeGuide: '结束教程',
nextGuide: '下一步',
prevGuide: '上一步',
lineNames: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'],
pending: '待录入',
zi: '字',
hua: '花',
yaoTypeNames: { youngYang: '少阳', youngYin: '少阴', oldYang: '老阳', oldYin: '老阴' },
questionTypePrefix: '问题类型',
method: '起卦方式:手动起卦',
submit: '开始解卦',
},
zh_Hant: {
title: '手動起卦',
subtitle: '準備三枚相同的錢幣,從初爻到上爻依序錄入六次結果。',
balance: '可用 120 積分 · 本次 20 積分',
defaultQuestion: '我接下來三個月的事業發展需要注意什麼?',
modify: '修改',
guideLines: ['從初爻開始,按從下往上的順序記錄。', '每一爻由三枚錢幣的字面/花面組合決定。', '六爻完成後才可開始解卦。'],
openGuide: '查看手動起卦教程',
guideSteps: [
['手動起卦', '準備三枚相同的錢幣。每次記錄一爻,按從下往上的順序共記錄六爻。'],
['確認時間', '先確認起卦時間。如需調整,點擊右側「修改」。'],
['依序錄入六爻', '從初爻開始逐條選擇,未完成前下一爻不可點擊。每條會彈出三枚錢幣選擇面板。'],
['開始分析', '六爻都填完後,「開始解卦」按鈕會高亮提示,點擊即可解卦。'],
],
closeGuide: '結束教程',
nextGuide: '下一步',
prevGuide: '上一步',
lineNames: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'],
pending: '待錄入',
zi: '字',
hua: '花',
yaoTypeNames: { youngYang: '少陽', youngYin: '少陰', oldYang: '老陽', oldYin: '老陰' },
questionTypePrefix: '問題類型',
method: '起卦方式:手動起卦',
submit: '開始解卦',
},
en: {
title: 'Manual Casting',
subtitle: 'Prepare three identical coins and record six results from the first yao at the bottom to the top yao.',
balance: 'Available 120 credits · This reading 20 credits',
defaultQuestion: 'What should I pay attention to in my career development over the next three months?',
modify: 'Modify',
guideLines: ['Record from the first yao upward.', 'Each yao is determined by the text-side and flower-side combination of three coins.', 'Start interpretation after all six yao are complete.'],
openGuide: 'View Manual Casting Guide',
guideSteps: [
['Manual Casting', 'Prepare three identical coins. Record one yao at a time, from bottom to top, until all six yao are complete.'],
['Confirm Time', 'Check the casting time first. Use Modify on the right if you need to adjust it.'],
['Fill Six Yao in Order', 'Start from the first yao and complete one row at a time. The next row stays locked until the current row is confirmed.'],
['Start Interpretation', 'After all six yao are filled, Start Interpretation becomes active. Select it to continue.'],
],
closeGuide: 'Finish',
nextGuide: 'Next',
prevGuide: 'Back',
lineNames: ['First Yao', 'Second Yao', 'Third Yao', 'Fourth Yao', 'Fifth Yao', 'Top Yao'],
pending: 'Pending',
zi: 'Text',
hua: 'Flower',
yaoTypeNames: { youngYang: 'Young Yang', youngYin: 'Young Yin', oldYang: 'Old Yang', oldYin: 'Old Yin' },
questionTypePrefix: 'Category',
method: 'Method: Manual Casting',
submit: 'Start Interpretation',
},
} as const;
export default function ManualDivinationPage({ locale, divination: d }: Props) {
const text = copy[locale as keyof typeof copy] ?? copy.zh;
const cats = useMemo(() => d.categories.split(','), [d.categories]);
const [category, setCategory] = useState(cats[0]);
const [question, setQuestion] = useState(text.defaultQuestion);
const [selectedTime, setSelectedTime] = useState(() => formatDateTimeInput(new Date()));
const [coins, setCoins] = useState<[CoinFace, CoinFace, CoinFace]>(['zi', 'zi', 'zi']);
const [yaoResults, setYaoResults] = useState([]);
const [guideStep, setGuideStep] = useState(null);
useEffect(() => {
setCategory(cats[0]);
}, [cats]);
const progress = yaoResults.length;
const currentYaoType = fromCoins(coins);
const guideOpen = guideStep !== null;
const guide = guideOpen ? text.guideSteps[guideStep] : null;
const flipCoin = (idx: number) => {
setCoins((current) => {
const next = [...current] as [CoinFace, CoinFace, CoinFace];
next[idx] = next[idx] === 'zi' ? 'hua' : 'zi';
return next;
});
};
const confirmYao = () => {
if (progress >= TOTAL_YAO_COUNT) return;
setYaoResults((current) => [...current, currentYaoType]);
setCoins(['zi', 'zi', 'zi']);
};
const showPreviousGuide = () => setGuideStep((step) => (step === null ? 0 : Math.max(step - 1, 0)));
const showNextGuide = () => setGuideStep((step) => (step === null ? 0 : Math.min(step + 1, text.guideSteps.length - 1)));
return (
{text.title}
{text.subtitle}
{text.balance}
{d.questionTitle}
{d.guideTitle}
{text.guideLines.map((line) => {line}
)}
{d.yaoTitle}
{progress} / {TOTAL_YAO_COUNT}
{[5, 4, 3, 2, 1, 0].map((index) => {
const result = yaoResults[index];
const active = index === progress && progress < TOTAL_YAO_COUNT;
return (
{text.lineNames[index]}
{result ? text.yaoTypeNames[result] : text.pending}
);
})}
{progress < TOTAL_YAO_COUNT && (
<>
{coins.map((face, index) => (
))}
{text.yaoTypeNames[currentYaoType]}
>
)}
{guideOpen && guide && (
{guideStep + 1} / {text.guideSteps.length}
{guide[0]}
{guide[1]}
{guideStep === text.guideSteps.length - 1 ? (
) : (
)}
)}
);
}