Lab 설정 변경 및 유틸 추가
This commit is contained in:
275
src/pages/effect-lab/DayCalc.jsx
Normal file
275
src/pages/effect-lab/DayCalc.jsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './DayCalc.css';
|
||||
|
||||
const today = () => new Date().toISOString().slice(0, 10);
|
||||
|
||||
const fmt = (d) => {
|
||||
if (!d) return '';
|
||||
const [y, m, day] = d.split('-');
|
||||
return `${y}년 ${parseInt(m)}월 ${parseInt(day)}일`;
|
||||
};
|
||||
|
||||
// 두 날짜 사이 diff 계산
|
||||
const calcDiff = (from, to) => {
|
||||
const f = new Date(from);
|
||||
const t = new Date(to);
|
||||
|
||||
const totalMs = t - f;
|
||||
const totalDays = Math.round(totalMs / 86400000);
|
||||
|
||||
// 연/월/일 분리 계산
|
||||
let years = t.getFullYear() - f.getFullYear();
|
||||
let months = t.getMonth() - f.getMonth();
|
||||
let days = t.getDate() - f.getDate();
|
||||
|
||||
if (days < 0) {
|
||||
months -= 1;
|
||||
const prevMonth = new Date(t.getFullYear(), t.getMonth(), 0);
|
||||
days += prevMonth.getDate();
|
||||
}
|
||||
if (months < 0) {
|
||||
years -= 1;
|
||||
months += 12;
|
||||
}
|
||||
|
||||
const totalMonths = years * 12 + months;
|
||||
const weeks = Math.floor(Math.abs(totalDays) / 7);
|
||||
const remDays = Math.abs(totalDays) % 7;
|
||||
|
||||
return { totalDays, totalMonths, years, months, days, weeks, remDays };
|
||||
};
|
||||
|
||||
// 특정 날짜로부터 N일 후 날짜 계산
|
||||
const addDays = (dateStr, n) => {
|
||||
const d = new Date(dateStr);
|
||||
d.setDate(d.getDate() + n);
|
||||
return d.toISOString().slice(0, 10);
|
||||
};
|
||||
|
||||
// 기념일 체크포인트
|
||||
const MILESTONES = [100, 200, 365, 500, 730, 1000, 1461, 2000, 3000];
|
||||
|
||||
const QUICK_PRESETS = [
|
||||
{ label: '오늘 기준', offset: 0 },
|
||||
{ label: '1주 후', offset: 7 },
|
||||
{ label: '1개월 후', offset: 30 },
|
||||
{ label: '3개월 후', offset: 90 },
|
||||
{ label: '6개월 후', offset: 180 },
|
||||
{ label: '1년 후', offset: 365 },
|
||||
];
|
||||
|
||||
const DayCalc = () => {
|
||||
const [fromDate, setFromDate] = useState('');
|
||||
const [toDate, setToDate] = useState(today());
|
||||
const [tab, setTab] = useState('diff'); // diff | milestone | future
|
||||
|
||||
const result = useMemo(() => {
|
||||
if (!fromDate || !toDate) return null;
|
||||
try {
|
||||
return calcDiff(fromDate, toDate);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [fromDate, toDate]);
|
||||
|
||||
const milestones = useMemo(() => {
|
||||
if (!fromDate) return [];
|
||||
return MILESTONES.map((n) => ({
|
||||
days: n,
|
||||
date: addDays(fromDate, n - 1),
|
||||
}));
|
||||
}, [fromDate]);
|
||||
|
||||
const isForward = result ? result.totalDays >= 0 : true;
|
||||
|
||||
const applyPreset = (offset) => {
|
||||
setToDate(addDays(today(), offset));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="daycalc">
|
||||
<header className="daycalc__header">
|
||||
<div>
|
||||
<Link to="/lab" className="daycalc__back">← Lab</Link>
|
||||
<p className="daycalc__kicker">Lab · 날짜 도구</p>
|
||||
<h1>일수 계산기</h1>
|
||||
<p className="daycalc__desc">두 날짜 사이의 기간과 기념일 날짜를 계산합니다.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 날짜 입력 */}
|
||||
<section className="daycalc__input-section">
|
||||
<div className="daycalc__date-row">
|
||||
<div className="daycalc__date-field">
|
||||
<label>시작일</label>
|
||||
<input
|
||||
type="date"
|
||||
value={fromDate}
|
||||
onChange={(e) => setFromDate(e.target.value)}
|
||||
max={toDate || undefined}
|
||||
/>
|
||||
{fromDate && <span className="daycalc__date-fmt">{fmt(fromDate)}</span>}
|
||||
</div>
|
||||
|
||||
<div className="daycalc__arrow">
|
||||
{result
|
||||
? <span className={isForward ? 'fwd' : 'bwd'}>{isForward ? '→' : '←'}</span>
|
||||
: <span>↔</span>}
|
||||
</div>
|
||||
|
||||
<div className="daycalc__date-field">
|
||||
<label>종료일</label>
|
||||
<input
|
||||
type="date"
|
||||
value={toDate}
|
||||
onChange={(e) => setToDate(e.target.value)}
|
||||
/>
|
||||
{toDate && <span className="daycalc__date-fmt">{fmt(toDate)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 빠른 종료일 설정 */}
|
||||
<div className="daycalc__presets">
|
||||
<span className="daycalc__presets-label">빠른 설정</span>
|
||||
{QUICK_PRESETS.map((p) => (
|
||||
<button
|
||||
key={p.label}
|
||||
className="daycalc__preset-btn"
|
||||
onClick={() => applyPreset(p.offset)}
|
||||
>
|
||||
{p.label}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
className="daycalc__preset-btn daycalc__preset-btn--clear"
|
||||
onClick={() => { setFromDate(''); setToDate(today()); }}
|
||||
>
|
||||
초기화
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 결과 탭 */}
|
||||
{fromDate && (
|
||||
<>
|
||||
<div className="daycalc__tabs">
|
||||
{[
|
||||
{ id: 'diff', label: '기간 계산' },
|
||||
{ id: 'milestone', label: '기념일' },
|
||||
].map((t) => (
|
||||
<button
|
||||
key={t.id}
|
||||
className={`daycalc__tab${tab === t.id ? ' is-active' : ''}`}
|
||||
onClick={() => setTab(t.id)}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 기간 계산 탭 */}
|
||||
{tab === 'diff' && result && (
|
||||
<section className="daycalc__result">
|
||||
{/* 메인 수치 */}
|
||||
<div className="daycalc__big-cards">
|
||||
<div className="daycalc__big-card daycalc__big-card--primary">
|
||||
<p className="daycalc__big-num">
|
||||
{isForward ? '+' : ''}{result.totalDays.toLocaleString()}
|
||||
</p>
|
||||
<p className="daycalc__big-label">일</p>
|
||||
<p className="daycalc__big-sub">
|
||||
{isForward ? '경과' : '이전'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="daycalc__big-card">
|
||||
<p className="daycalc__big-num">{result.totalMonths.toLocaleString()}</p>
|
||||
<p className="daycalc__big-label">개월</p>
|
||||
<p className="daycalc__big-sub">총 개월 수</p>
|
||||
</div>
|
||||
|
||||
<div className="daycalc__big-card">
|
||||
<p className="daycalc__big-num">{result.weeks.toLocaleString()}</p>
|
||||
<p className="daycalc__big-label">주 {result.remDays}일</p>
|
||||
<p className="daycalc__big-sub">주 단위</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 세부 분해 */}
|
||||
<div className="daycalc__breakdown">
|
||||
<h3>상세 기간</h3>
|
||||
<div className="daycalc__breakdown-row">
|
||||
{result.years > 0 && (
|
||||
<div className="daycalc__breakdown-item">
|
||||
<span className="daycalc__breakdown-num">{result.years}</span>
|
||||
<span className="daycalc__breakdown-unit">년</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="daycalc__breakdown-item">
|
||||
<span className="daycalc__breakdown-num">{result.months}</span>
|
||||
<span className="daycalc__breakdown-unit">개월</span>
|
||||
</div>
|
||||
<div className="daycalc__breakdown-item">
|
||||
<span className="daycalc__breakdown-num">{result.days}</span>
|
||||
<span className="daycalc__breakdown-unit">일</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="daycalc__breakdown-summary">
|
||||
{fmt(fromDate)} 부터 {fmt(toDate)} 까지
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 기념일 탭 */}
|
||||
{tab === 'milestone' && (
|
||||
<section className="daycalc__milestones">
|
||||
<p className="daycalc__milestones-desc">
|
||||
<strong>{fmt(fromDate)}</strong> 을 기준으로 한 기념일 날짜입니다.
|
||||
</p>
|
||||
<div className="daycalc__milestone-list">
|
||||
{milestones.map(({ days, date }) => {
|
||||
const isPast = date < today();
|
||||
const isToday = date === today();
|
||||
const diff = calcDiff(today(), date);
|
||||
return (
|
||||
<div
|
||||
key={days}
|
||||
className={`daycalc__milestone-row${isPast ? ' is-past' : ''}${isToday ? ' is-today' : ''}`}
|
||||
>
|
||||
<div className="daycalc__milestone-badge">
|
||||
{days < 365
|
||||
? `D+${days}`
|
||||
: days % 365 === 0
|
||||
? `${days / 365}주년`
|
||||
: `D+${days}`}
|
||||
</div>
|
||||
<div className="daycalc__milestone-date">{fmt(date)}</div>
|
||||
<div className="daycalc__milestone-dday">
|
||||
{isToday
|
||||
? '🎉 오늘'
|
||||
: isPast
|
||||
? `${Math.abs(diff.totalDays)}일 전`
|
||||
: `D-${diff.totalDays}`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!fromDate && (
|
||||
<div className="daycalc__empty">
|
||||
<p className="daycalc__empty-icon">📅</p>
|
||||
<p>시작일을 입력하면 기간 계산을 시작합니다.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DayCalc;
|
||||
Reference in New Issue
Block a user