Files
saju-web/app/fortune/page.tsx
gahusb f85e857bea feat: 토정비결 및 PDF 저장 기능 추가
- 토정비결 페이지 구현 (연간/월별 운세)
- 분야별 운세 (재물, 건강, 관운, 애정)
- PDF 저장 기능 구현 (jsPDF + html2canvas)
- 모든 결과 페이지에 PDF 다운로드 기능 추가
- PDFButton 재사용 가능한 컴포넌트 생성
- 홈페이지에 토정비결 링크 추가
- 페이지 간 네비게이션 링크 업데이트

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 23:44:55 +09:00

276 lines
12 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 Link from 'next/link';
import { calculateSaju } from '@/lib/saju-calculator';
import PDFButton from '../components/PDFButton';
interface PageProps {
searchParams: Promise<{
year: string;
month: string;
day: string;
hour?: string;
gender: 'male' | 'female';
calendarType: 'solar' | 'lunar';
}>;
}
export default async function FortunePage({ searchParams }: PageProps) {
const params = await searchParams;
const { year, month, day, hour, gender } = params;
const yearNum = parseInt(year);
const monthNum = parseInt(month);
const dayNum = parseInt(day);
const hourNum = hour ? parseInt(hour) : null;
const sajuData = calculateSaju(yearNum, monthNum, dayNum, hourNum, gender);
// 오늘 날짜
const today = new Date();
const todayYear = today.getFullYear();
const todayMonth = today.getMonth() + 1;
const todayDay = today.getDate();
// 오늘의 간지 계산
const todayGanzi = calculateSaju(todayYear, todayMonth, todayDay, null, gender);
// 운세 점수 계산 (간단한 알고리즘)
const calculateFortuneScore = (category: string): number => {
const seed = sajuData.day.stem.charCodeAt(0) + todayGanzi.day.stem.charCodeAt(0) + category.charCodeAt(0);
return 60 + (seed % 40);
};
const fortuneCategories = [
{ name: '종합운', icon: '⭐', score: calculateFortuneScore('종합'), color: 'indigo' },
{ name: '금전운', icon: '💰', score: calculateFortuneScore('금전'), color: 'green' },
{ name: '애정운', icon: '💕', score: calculateFortuneScore('애정'), color: 'pink' },
{ name: '건강운', icon: '🏥', score: calculateFortuneScore('건강'), color: 'red' },
{ name: '직장운', icon: '💼', score: calculateFortuneScore('직장'), color: 'blue' },
{ name: '학업운', icon: '📚', score: calculateFortuneScore('학업'), color: 'purple' },
];
const getScoreText = (score: number): string => {
if (score >= 90) return '최고';
if (score >= 80) return '좋음';
if (score >= 70) return '보통';
if (score >= 60) return '주의';
return '나쁨';
};
const getScoreColor = (score: number): string => {
if (score >= 90) return 'text-green-600';
if (score >= 80) return 'text-blue-600';
if (score >= 70) return 'text-yellow-600';
if (score >= 60) return 'text-orange-600';
return 'text-red-600';
};
// 행운의 방향과 색깔 (일간 기준)
const getLuckyDirection = (stem: string): string => {
const directions: { [key: string]: string } = {
'甲': '동쪽', '乙': '동쪽',
'丙': '남쪽', '丁': '남쪽',
'戊': '중앙', '己': '중앙',
'庚': '서쪽', '辛': '서쪽',
'壬': '북쪽', '癸': '북쪽',
};
return directions[stem] || '동쪽';
};
const getLuckyColor = (element: string): string => {
const colors: { [key: string]: string } = {
'木': '녹색',
'火': '빨강색',
'土': '노란색',
'金': '흰색',
'水': '검정색',
};
return colors[element] || '녹색';
};
const getLuckyNumber = (stem: string): number => {
return (stem.charCodeAt(0) % 9) + 1;
};
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 gap-4">
<Link
href={`/result?${new URLSearchParams(params as any).toString()}`}
className="text-gray-700 hover:text-indigo-600 transition font-medium"
>
</Link>
<Link
href="/"
className="text-gray-700 hover:text-indigo-600 transition font-medium"
>
</Link>
</div>
</div>
</div>
</nav>
{/* 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">
{todayYear} {todayMonth} {todayDay} ({todayGanzi.day.stem}{todayGanzi.day.branch})
</p>
<p className="text-lg text-gray-500 mt-2">
{sajuData.birthDate.year} {gender === 'male' ? '남성' : '여성'}
</p>
</div>
{/* 운세 점수 카드들 */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
{fortuneCategories.map((category) => (
<div key={category.name} className="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<span className="text-4xl">{category.icon}</span>
<h3 className="text-xl font-bold text-gray-900">{category.name}</h3>
</div>
<span className={`text-2xl font-bold ${getScoreColor(category.score)}`}>
{category.score}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3 mb-2">
<div
className={`bg-gradient-to-r from-${category.color}-400 to-${category.color}-600 h-3 rounded-full transition-all duration-500`}
style={{ width: `${category.score}%` }}
></div>
</div>
<p className="text-sm text-gray-600 text-right">{getScoreText(category.score)}</p>
</div>
))}
</div>
{/* 오늘의 한마디 */}
<div className="bg-gradient-to-r from-indigo-600 to-purple-600 rounded-3xl shadow-2xl p-8 md:p-12 mb-8 text-white">
<h2 className="text-3xl font-bold mb-6 text-center">💬 </h2>
<p className="text-xl leading-relaxed text-center">
{sajuData.day.element === '木' && '나무가 자라듯 꾸준히 노력하면 좋은 결과가 있을 것입니다. 새로운 시작에 좋은 날입니다.'}
{sajuData.day.element === '火' && '활발한 기운이 가득한 날입니다. 적극적으로 행동하면 좋은 기회를 잡을 수 있습니다.'}
{sajuData.day.element === '土' && '안정적인 하루가 될 것입니다. 차근차근 계획을 세우고 실행하면 좋습니다.'}
{sajuData.day.element === '金' && '명확한 판단이 필요한 날입니다. 원칙을 지키며 행동하면 좋은 결과가 있을 것입니다.'}
{sajuData.day.element === '水' && '유연한 사고가 도움이 되는 날입니다. 주변 사람들과의 소통을 중요하게 여기세요.'}
</p>
</div>
{/* 행운의 요소들 */}
<div className="grid md:grid-cols-3 gap-8 mb-8">
{/* 행운의 방향 */}
<div className="bg-white rounded-2xl shadow-lg p-8 text-center">
<div className="text-5xl mb-4">🧭</div>
<h3 className="text-2xl font-bold text-gray-900 mb-3"> </h3>
<p className="text-3xl font-bold text-indigo-600">{getLuckyDirection(sajuData.day.stem)}</p>
<p className="text-sm text-gray-600 mt-2"> </p>
</div>
{/* 행운의 색깔 */}
<div className="bg-white rounded-2xl shadow-lg p-8 text-center">
<div className="text-5xl mb-4">🎨</div>
<h3 className="text-2xl font-bold text-gray-900 mb-3"> </h3>
<p className="text-3xl font-bold text-purple-600">{getLuckyColor(sajuData.day.element)}</p>
<p className="text-sm text-gray-600 mt-2"> </p>
</div>
{/* 행운의 숫자 */}
<div className="bg-white rounded-2xl shadow-lg p-8 text-center">
<div className="text-5xl mb-4">🎲</div>
<h3 className="text-2xl font-bold text-gray-900 mb-3"> </h3>
<p className="text-3xl font-bold text-pink-600">{getLuckyNumber(sajuData.day.stem)}</p>
<p className="text-sm text-gray-600 mt-2"> </p>
</div>
</div>
{/* 주의사항 */}
<div className="bg-white rounded-2xl shadow-lg p-8 mb-8">
<h3 className="text-2xl font-bold text-gray-900 mb-6 flex items-center">
<span className="text-3xl mr-3"></span>
</h3>
<ul className="space-y-3 text-gray-700">
<li className="flex items-start">
<span className="text-indigo-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-indigo-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-indigo-600 mr-2"></span>
<span> .</span>
</li>
</ul>
</div>
{/* 다른 메뉴 */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<Link
href={`/result?${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>
<Link
href="/tojeong"
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={`오늘의운세_${todayYear}${todayMonth}${todayDay}.pdf`}
buttonText="운세 PDF 저장"
/>
</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>
);
}