Files
saju-web/app/result/page.tsx

545 lines
26 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { calculateSaju } from '@/lib/saju-calculator';
import Link from 'next/link';
import PDFButton from '../components/PDFButton';
import ShareButtons from '../components/ShareButtons';
import UserMenu from '@/components/UserMenu';
import { calculateDaeun, getCurrentDaeun, getDaeunDescription } from '@/lib/daeun-calculator';
import { getCurrentSolarTerm, getSolarTermName, getSolarTermMonthBranch } from '@/lib/solar-terms';
import { EARTHLY_BRANCHES_KR, FIVE_ELEMENTS_KR, FIVE_ELEMENTS } from '@/lib/saju-calculator';
import { calculateElementScore, performFullAnalysis } from '@/lib/ai-interpretation';
import AiInterpretationSection from '@/components/AiInterpretationSection';
interface PageProps {
searchParams: Promise<{
year: string;
month: string;
day: string;
hour?: string;
gender: 'male' | 'female';
calendarType: 'solar' | 'lunar';
originalYear?: string;
originalMonth?: string;
originalDay?: string;
isLeapMonth?: string;
}>;
}
export default async function ResultPage({ searchParams }: PageProps) {
const params = await searchParams;
const {
year, month, day, hour, gender, calendarType,
originalYear, originalMonth, originalDay, isLeapMonth
} = params;
const yearNum = parseInt(year);
const monthNum = parseInt(month);
const dayNum = parseInt(day);
const hourNum = hour ? parseInt(hour) : null;
const inputYear = originalYear ? parseInt(originalYear) : yearNum;
const inputMonth = originalMonth ? parseInt(originalMonth) : monthNum;
const inputDay = originalDay ? parseInt(originalDay) : dayNum;
const isLunar = calendarType === 'lunar';
const isLeap = isLeapMonth === 'true';
const sajuData = calculateSaju(yearNum, monthNum, dayNum, hourNum, gender);
// 절기 정보
const solarTermIndex = getCurrentSolarTerm(yearNum, monthNum, dayNum);
const solarTermName = getSolarTermName(solarTermIndex);
const monthBranchIndex = getSolarTermMonthBranch(yearNum, monthNum, dayNum);
const monthBranchName = EARTHLY_BRANCHES_KR[monthBranchIndex];
// 종합 분석 수행
const analysis = performFullAnalysis(sajuData);
const elementScores = analysis.elementScores;
// 대운 계산
const daeunList = calculateDaeun(
yearNum, monthNum, dayNum, gender,
sajuData.month.stem, sajuData.month.branch
);
const currentYear = new Date().getFullYear();
const currentDaeun = getCurrentDaeun(daeunList, currentYear);
// 오행 색상 매핑
const elementColors: { [key: string]: string } = {
'木': 'text-green-600', '火': 'text-red-500', '土': 'text-yellow-600',
'金': 'text-gray-500', '水': 'text-blue-600',
};
const elementBgColors: { [key: string]: string } = {
'木': 'bg-green-100 border-green-300', '火': 'bg-red-100 border-red-300',
'土': 'bg-yellow-100 border-yellow-300', '金': 'bg-gray-100 border-gray-300',
'水': 'bg-blue-100 border-blue-300',
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50">
{/* Navigation */}
<nav className="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<Link href="/" className="text-2xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
</Link>
<div className="flex items-center space-x-4">
<Link href="/" className="text-gray-700 hover:text-indigo-600 transition font-medium">
</Link>
<UserMenu />
</div>
</div>
</div>
</nav>
{/* Result Content */}
<div id="pdf-content" className="max-w-6xl mx-auto px-4 py-12">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
</h1>
<p className="text-xl text-gray-600">
{isLunar ? (
<>
{inputYear} {inputMonth} {inputDay}{isLeap && ' (윤달)'} {hourNum !== null && `${hourNum}`}
<br />
<span className="text-base text-gray-500">
( {yearNum} {monthNum} {dayNum})
</span>
</>
) : (
<>
{yearNum} {monthNum} {dayNum} {hourNum !== null && `${hourNum}`}
</>
)}
{gender === 'male' ? ' 남성' : ' 여성'}
</p>
</div>
{/* 사주팔자 표 */}
<div className="bg-white rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center"> ()</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
<th className="py-4 px-6 text-center font-bold text-lg"></th>
{sajuData.hour && <th className="py-4 px-6 text-center font-bold text-lg"> ()</th>}
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
</tr>
</thead>
<tbody>
{/* 천간 */}
<tr className="border-b border-gray-200 hover:bg-indigo-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.hour.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.hour.stemKr}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-3xl font-bold text-blue-600">{sajuData.day.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.day.stemKr}</div>
<div className="text-xs text-blue-600 font-semibold mt-1"> ()</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.month.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.month.stemKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.year.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.year.stemKr}</div>
</td>
</tr>
{/* 지지 */}
<tr className="border-b border-gray-200 hover:bg-purple-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.hour.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.hour.branchKr}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-3xl font-bold text-blue-600">{sajuData.day.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.day.branchKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.month.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.month.branchKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.year.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.year.branchKr}</div>
</td>
</tr>
{/* 지장간 (NEW) */}
<tr className="border-b border-gray-200 hover:bg-amber-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700">
()
<div className="text-xs text-gray-400 mt-1"> </div>
</td>
{(() => {
const pillars = sajuData.hour
? [analysis.hiddenStems.find(h => h.pillar === '시주'), analysis.hiddenStems.find(h => h.pillar === '일주'), analysis.hiddenStems.find(h => h.pillar === '월주'), analysis.hiddenStems.find(h => h.pillar === '년주')]
: [analysis.hiddenStems.find(h => h.pillar === '일주'), analysis.hiddenStems.find(h => h.pillar === '월주'), analysis.hiddenStems.find(h => h.pillar === '년주')];
return pillars.map((h, idx) => (
<td key={idx} className={`py-3 px-4 text-center ${h?.pillar === '일주' ? 'bg-blue-50' : ''}`}>
{h && (
<div className="flex flex-wrap justify-center gap-1">
{h.stems.map((s, si) => (
<span
key={si}
className={`inline-block px-2 py-1 rounded text-xs font-semibold border ${elementBgColors[s.element] || 'bg-gray-100'}`}
title={s.role}
>
{s.stemKr}({FIVE_ELEMENTS_KR[s.element as keyof typeof FIVE_ELEMENTS_KR]})
</span>
))}
</div>
)}
</td>
));
})()}
</tr>
{/* 십성 */}
<tr className="border-b border-gray-200 hover:bg-emerald-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.hour.tenGod}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-lg font-semibold text-blue-600">{sajuData.day.tenGod}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.month.tenGod}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.year.tenGod}</div>
</td>
</tr>
{/* 십이운성 */}
<tr className="hover:bg-pink-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"></td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.hour.fortune}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-lg font-semibold text-blue-600">{sajuData.day.fortune}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.month.fortune}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.year.fortune}</div>
</td>
</tr>
</tbody>
</table>
</div>
{/* 지지 상호작용 (합/충/형/파/해) */}
{analysis.branchInteractions.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-100">
<h3 className="text-lg font-bold text-gray-900 mb-3 flex items-center justify-center">
<span className="mr-2">🔗</span>
</h3>
<div className="flex flex-wrap justify-center gap-2">
{analysis.branchInteractions.map((inter, idx) => {
const isPositive = inter.type.includes('합');
const isNegative = inter.type.includes('충') || inter.type.includes('형');
const colorClass = isPositive
? 'bg-emerald-50 border-emerald-300 text-emerald-800'
: isNegative
? 'bg-red-50 border-red-300 text-red-800'
: 'bg-amber-50 border-amber-300 text-amber-800';
return (
<span key={idx} className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-semibold border ${colorClass}`}>
{inter.type} {inter.branchesKr.join('')}
{inter.resultElement && `${FIVE_ELEMENTS_KR[inter.resultElement as keyof typeof FIVE_ELEMENTS_KR]}`}
</span>
);
})}
</div>
</div>
)}
{/* 오행 균형 시각화 */}
<div className="mt-8 pt-8 border-t border-gray-100">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center justify-center">
<span className="text-2xl mr-2"></span>
<span className="text-sm font-normal text-gray-500 ml-2">( )</span>
</h3>
<div className="grid grid-cols-5 gap-3 max-w-2xl mx-auto">
{Object.entries(elementScores).map(([element, score]) => (
<div key={element} className="text-center">
<div className={`text-2xl font-bold mb-1 ${elementColors[element] || ''}`}>{element}</div>
<div className="text-sm text-gray-600 mb-2">
{FIVE_ELEMENTS_KR[element as keyof typeof FIVE_ELEMENTS_KR]}
<span className="text-xs text-gray-400 ml-1">
({analysis.elementBalance[element as keyof typeof analysis.elementBalance]})
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3 mb-1">
<div
className={`h-3 rounded-full transition-all ${element === sajuData.day.element
? 'bg-gradient-to-r from-indigo-500 to-purple-500'
: 'bg-gray-400'
}`}
style={{ width: `${Math.max(score, 5)}%` }}
></div>
</div>
<div className="text-xs font-semibold text-gray-700">{score}%</div>
</div>
))}
</div>
</div>
</div>
{/* 신강/신약 + 용신 + 신살 카드 */}
<div className="grid md:grid-cols-2 gap-6 mb-8">
{/* 신강/신약 + 용신 카드 */}
<div className="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="mr-2"></span>
</h3>
<div className="flex items-center gap-3 mb-4">
<span className={`inline-block px-4 py-2 rounded-xl text-lg font-bold ${
analysis.dayMasterStrength.result === '신강'
? 'bg-red-100 text-red-700'
: analysis.dayMasterStrength.result === '신약'
? 'bg-blue-100 text-blue-700'
: 'bg-green-100 text-green-700'
}`}>
{analysis.dayMasterStrength.result}
</span>
<span className="text-gray-500 text-sm">: {analysis.dayMasterStrength.score}</span>
</div>
<ul className="space-y-1.5 text-sm text-gray-600 mb-6">
{analysis.dayMasterStrength.reasons.map((r, i) => (
<li key={i} className="flex items-start">
<span className="text-indigo-400 mr-2 mt-0.5">-</span>
<span>{r}</span>
</li>
))}
</ul>
<div className="border-t border-gray-100 pt-4">
<h4 className="font-bold text-gray-800 mb-3"> / / </h4>
<div className="flex flex-wrap gap-2 mb-3">
<span className={`px-3 py-1.5 rounded-lg text-sm font-bold border ${elementBgColors[analysis.yongShin.yongShin] || 'bg-gray-100'}`}>
: {analysis.yongShin.yongShinKr}({analysis.yongShin.yongShin})
</span>
<span className={`px-3 py-1.5 rounded-lg text-sm font-bold border ${elementBgColors[analysis.yongShin.heeShin] || 'bg-gray-100'}`}>
: {analysis.yongShin.heeShinKr}({analysis.yongShin.heeShin})
</span>
<span className="px-3 py-1.5 rounded-lg text-sm font-bold bg-gray-200 border border-gray-400 text-gray-700">
: {analysis.yongShin.giShinKr}({analysis.yongShin.giShin})
</span>
</div>
<p className="text-sm text-gray-600 leading-relaxed">{analysis.yongShin.explanation}</p>
</div>
</div>
{/* 신살 + 공망 카드 */}
<div className="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="mr-2">🌟</span> ()
</h3>
{analysis.shinsal.length > 0 ? (
<div className="space-y-3 mb-6">
{analysis.shinsal.map((s, i) => (
<div key={i} className="flex items-start gap-3 p-3 rounded-xl bg-gray-50">
<span className="inline-block px-2 py-1 bg-indigo-100 text-indigo-700 rounded-lg text-xs font-bold whitespace-nowrap">
{s.name}
</span>
<div>
<div className="text-sm font-semibold text-gray-800">
{s.pillar} {s.branchKr}({s.branch})
</div>
<div className="text-xs text-gray-500 mt-0.5">{s.description}</div>
</div>
</div>
))}
</div>
) : (
<p className="text-gray-500 text-sm mb-6"> .</p>
)}
<div className="border-t border-gray-100 pt-4">
<h4 className="font-bold text-gray-800 mb-2 flex items-center">
<span className="mr-2">🕳</span> ()
</h4>
<div className="flex gap-2 mb-2">
{analysis.gongmang.branchesKr.map((bk, i) => (
<span key={i} className="px-3 py-1.5 bg-gray-800 text-white rounded-lg text-sm font-bold">
{bk}({analysis.gongmang.branches[i]})
</span>
))}
</div>
<p className="text-xs text-gray-500 leading-relaxed">{analysis.gongmang.description}</p>
</div>
{/* 세운 정보 */}
<div className="border-t border-gray-100 pt-4 mt-4">
<h4 className="font-bold text-gray-800 mb-2 flex items-center">
<span className="mr-2">📅</span> {analysis.seun.year}
</h4>
<div className="flex items-center gap-2 mb-2">
<span className={`px-3 py-1.5 rounded-lg text-sm font-bold border ${elementBgColors[analysis.seun.element] || 'bg-gray-100'}`}>
{analysis.seun.stemKr}{analysis.seun.branchKr} ({analysis.seun.stem}{analysis.seun.branch})
</span>
<span className="text-sm text-gray-500">{analysis.seun.elementKr}({analysis.seun.element}) </span>
</div>
{analysis.seun.interactions.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-2">
{analysis.seun.interactions.map((si, i) => (
<span key={i} className={`text-xs px-2 py-1 rounded-full font-semibold ${
si.type.includes('합') ? 'bg-emerald-50 text-emerald-700' : 'bg-red-50 text-red-700'
}`}>
{si.type} {si.branchesKr.join('')}
</span>
))}
</div>
)}
</div>
</div>
</div>
{/* AI 상세 해석 */}
<AiInterpretationSection sajuData={sajuData} currentDaeun={currentDaeun} daeunList={daeunList} />
{/* 대운 (大運) */}
<div className="bg-white rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center">
() - 10
</h2>
{currentDaeun && (
<div className="bg-gradient-to-r from-indigo-500 to-purple-500 rounded-2xl p-6 mb-8 text-white">
<h3 className="text-2xl font-bold mb-4 text-center"> </h3>
<div className="text-center mb-4">
<div className="text-5xl font-bold mb-2">
{currentDaeun.stem}{currentDaeun.branch}
</div>
<div className="text-xl mb-2">
{currentDaeun.stemKr}{currentDaeun.branchKr}
</div>
<div className="text-lg opacity-90">
{currentDaeun.age} ~ {currentDaeun.age + 9} ({currentDaeun.startYear} ~ {currentDaeun.endYear})
</div>
</div>
<p className="text-center leading-relaxed">
{getDaeunDescription(currentDaeun, sajuData.day.stem)}
</p>
</div>
)}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
{daeunList.map((daeun, index) => {
const isCurrent = currentDaeun &&
daeun.startYear === currentDaeun.startYear &&
daeun.endYear === currentDaeun.endYear;
return (
<div
key={index}
className={`rounded-xl p-4 border-2 transition ${isCurrent
? 'bg-indigo-50 border-indigo-400'
: 'bg-gray-50 border-gray-200 hover:border-indigo-300'
}`}
>
<div className="text-center">
<div className="text-3xl font-bold text-gray-900 mb-1">
{daeun.stem}{daeun.branch}
</div>
<div className="text-sm text-gray-600 mb-2">
{daeun.stemKr}{daeun.branchKr}
</div>
<div className="text-xs text-gray-500">
{daeun.age} ~ {daeun.age + 9}
</div>
<div className="text-xs text-gray-400">
{daeun.startYear} ~ {daeun.endYear}
</div>
{isCurrent && (
<div className="mt-2">
<span className="inline-block bg-indigo-600 text-white text-xs px-3 py-1 rounded-full font-semibold">
</span>
</div>
)}
</div>
</div>
);
})}
</div>
</div>
{/* 추가 기능 버튼 */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<Link
href={`/fortune?${new URLSearchParams(params as any).toString()}`}
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">🌟</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<Link
href="/compatibility"
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">💕</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<PDFButton
elementId="pdf-content"
filename={`사주팔자_${yearNum}${monthNum}${dayNum}.pdf`}
buttonText="사주 PDF 저장"
/>
<ShareButtons
title={`내 사주팔자 - ${yearNum}년생 ${gender === 'male' ? '남성' : '여성'}`}
description={`일간: ${sajuData.day.stem}(${sajuData.day.stemKr}) | ${sajuData.day.element}`}
/>
</div>
</div>
{/* Footer */}
<footer className="bg-gray-900 text-white py-12 px-4 mt-20">
<div className="max-w-7xl mx-auto text-center">
<div className="text-2xl font-bold mb-4 bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
</div>
<p className="text-gray-400 mb-6">
</p>
<div className="text-sm text-gray-500">
<p>문의: bgg8988@gmail.com | <a href="https://jaengseung-made.com" target="_blank" rel="noopener noreferrer" className="hover:text-indigo-400"></a></p>
<p className="mt-2">&copy; 2025 . All rights reserved.</p>
</div>
</div>
</footer>
</div>
);
}