diff --git a/src/data/heroConfig.js b/src/data/heroConfig.js new file mode 100644 index 0000000..11f84fc --- /dev/null +++ b/src/data/heroConfig.js @@ -0,0 +1,83 @@ +/** + * 홈 히어로 카드 월별 테마 설정 + * 매달 month, theme, desc, nextUpdate 를 수정해 적용하세요. + */ +export const MONTHLY_THEMES = [ + { + month: 1, + theme: '새해 목표 설정', + desc: '연초를 맞아 올해 개발·기록 목표를 구체적으로 정리하고 실행 계획을 세웁니다.', + nextUpdate: '매주 일요일', + }, + { + month: 2, + theme: '코드 품질 개선', + desc: '리팩토링과 테스트 커버리지 향상에 집중합니다. 작은 개선도 꾸준히 쌓아갑니다.', + nextUpdate: '매주 토요일', + }, + { + month: 3, + theme: '웹 UI 고도화', + desc: '대시보드 형태의 UI를 사이버펑크 스타일로 전면 개편하고, 새 기능을 추가합니다.', + nextUpdate: '이번 주말', + }, + { + month: 4, + theme: '백엔드 성능 최적화', + desc: 'API 응답 속도와 데이터베이스 쿼리를 분석하고 병목을 개선하는 달입니다.', + nextUpdate: '이번 주말', + }, + { + month: 5, + theme: '인프라 자동화', + desc: 'Docker/Kubernetes 파이프라인을 정비하고 배포 자동화를 강화합니다.', + nextUpdate: '격주 일요일', + }, + { + month: 6, + theme: '여름 사이드 프로젝트', + desc: '새로운 기술 스택을 탐구하며 소규모 실험 프로젝트를 진행합니다.', + nextUpdate: '매주 금요일', + }, + { + month: 7, + theme: '기록과 문서화', + desc: '그동안 미뤄뒀던 개발 노트와 블로그 글 작성에 집중합니다.', + nextUpdate: '매주 화요일', + }, + { + month: 8, + theme: '보안 점검', + desc: '서비스 취약점을 점검하고 인증·인가 로직을 강화합니다.', + nextUpdate: '격주 토요일', + }, + { + month: 9, + theme: '모니터링 강화', + desc: '로그 수집과 알림 파이프라인을 개선해 운영 가시성을 높입니다.', + nextUpdate: '이번 주말', + }, + { + month: 10, + theme: '오픈소스 기여', + desc: '사용 중인 라이브러리에 이슈를 제보하거나 PR을 올려봅니다.', + nextUpdate: '매주 목요일', + }, + { + month: 11, + theme: '연말 회고 준비', + desc: '올 한 해의 개발 성과를 정리하고 내년 로드맵 초안을 그립니다.', + nextUpdate: '매주 일요일', + }, + { + month: 12, + theme: '느린 기록, 깊은 회고', + desc: '빠르게 달려온 한 해를 천천히 돌아보며 가장 의미 있었던 작업을 기록합니다.', + nextUpdate: '크리스마스 주간', + }, +]; + +export function getCurrentTheme() { + const month = new Date().getMonth() + 1; + return MONTHLY_THEMES.find((t) => t.month === month) ?? MONTHLY_THEMES[0]; +} diff --git a/src/pages/home/Home.css b/src/pages/home/Home.css index 7dbfbfb..0a2d6ac 100644 --- a/src/pages/home/Home.css +++ b/src/pages/home/Home.css @@ -367,91 +367,207 @@ padding-top: 4px; } -/* ── Dev Log ─────────────────────────────────────────────────────────── */ +/* ── TODO Board ──────────────────────────────────────────────────────── */ -.home-dev-log { - display: grid; - gap: 8px; +.home-todo-wrapper { + position: relative; } -.home-dev-log__empty { - margin: 0; - color: var(--text-muted); - font-size: 13px; - padding: 16px 0; +.home-todo-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 2; + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid var(--line-bright); + background: var(--surface-raised); + color: var(--text-bright); + font-size: 20px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s ease, border-color 0.2s ease; + box-shadow: var(--shadow-md); } -.home-dev-log__item { +.home-todo-nav:hover { + background: var(--bg-tertiary); + border-color: var(--neon-cyan); +} + +.home-todo-nav--left { left: -16px; } +.home-todo-nav--right { right: -16px; } + +.home-todo-board { + display: flex; + gap: 12px; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + scrollbar-color: var(--line) transparent; + padding-bottom: 4px; +} + +.home-todo-board::-webkit-scrollbar { + height: 4px; +} + +.home-todo-board::-webkit-scrollbar-track { + background: transparent; +} + +.home-todo-board::-webkit-scrollbar-thumb { + background: var(--line); + border-radius: 2px; +} + +.home-todo-col { + flex: 1 0 260px; + min-width: 0; + max-width: 340px; + scroll-snap-align: start; + display: flex; + flex-direction: column; border: 1px solid var(--line); - padding: 14px 18px; border-radius: var(--radius-md); background: var(--surface-card); - display: grid; - grid-template-columns: auto 1fr auto; - align-items: start; - gap: 14px; box-shadow: var(--shadow-card); - transition: border-color 0.2s ease, background 0.2s ease; + overflow: hidden; } -.home-dev-log__item:hover { - border-color: rgba(52, 211, 153, 0.25); - background: var(--surface-raised); -} - -.home-dev-log__dot { - width: 7px; - height: 7px; - border-radius: 50%; - background: #34d399; - box-shadow: 0 0 6px rgba(52, 211, 153, 0.8); - margin-top: 7px; +.home-todo-col__head { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 14px; + border-bottom: 1px solid var(--line); + background: rgba(255, 255, 255, 0.02); flex-shrink: 0; } -.home-dev-log__content { +.home-todo-col__dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.home-todo-col__label { + font-size: 12px; + font-weight: 600; + color: var(--text-bright); + letter-spacing: 0.04em; + text-transform: uppercase; + font-family: var(--font-display); + flex: 1; +} + +.home-todo-col__count { + font-size: 11px; + color: var(--text-muted); + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + border-radius: 999px; + padding: 1px 7px; + font-family: var(--font-display); +} + +.home-todo-col__body { + flex: 1; + overflow-y: auto; + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; + max-height: calc(40vh); + min-height: 60px; + scrollbar-width: thin; + scrollbar-color: var(--line) transparent; +} + +.home-todo-col__body::-webkit-scrollbar { + width: 3px; +} + +.home-todo-col__body::-webkit-scrollbar-thumb { + background: var(--line); + border-radius: 2px; +} + +.home-todo-col__empty { + margin: auto; + color: var(--text-muted); + font-size: 12px; + text-align: center; + padding: 16px 0; +} + +.home-todo-card { + border: 1px solid var(--line); + border-radius: var(--radius-sm); + padding: 11px 13px; + background: rgba(255, 255, 255, 0.02); display: grid; gap: 4px; + transition: border-color 0.2s ease, background 0.2s ease; } -.home-dev-log__title { +.home-todo-card:hover { + border-color: rgba(0, 212, 255, 0.18); + background: rgba(0, 212, 255, 0.03); +} + +.home-todo-card__title { margin: 0; + font-size: 13px; font-weight: 600; - font-size: 15px; color: var(--text-bright); letter-spacing: -0.01em; + line-height: 1.4; } -.home-dev-log__desc { +.home-todo-card__desc { margin: 0; - color: var(--text-dim); - font-size: 12px; - line-height: 1.6; -} - -.home-dev-log__date { font-size: 11px; - color: rgba(52, 211, 153, 0.7); - text-transform: uppercase; - letter-spacing: 0.08em; - white-space: nowrap; - padding-top: 4px; + color: var(--text-dim); + line-height: 1.55; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } -.home-dev-log__link { +.home-todo-card__date { + margin: 2px 0 0; + font-size: 10px; + color: var(--text-muted); + letter-spacing: 0.04em; +} + +.home-todo-footer { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} + +.home-todo-footer__link { display: inline-flex; align-items: center; gap: 6px; - margin-top: 4px; font-size: 13px; color: #34d399; text-decoration: none; - padding: 8px 0; + padding: 6px 0; transition: opacity 0.2s ease; font-weight: 500; } -.home-dev-log__link:hover { +.home-todo-footer__link:hover { opacity: 0.75; } @@ -626,6 +742,19 @@ gap: 24px; } + .home-todo-col { + flex: 0 0 80vw; + max-width: 80vw; + } + + .home-todo-col__body { + max-height: 30vh; + } + + .home-todo-nav { + display: none; + } + .home-hero h1 { font-size: clamp(22px, 6vw, 32px); } diff --git a/src/pages/home/Home.jsx b/src/pages/home/Home.jsx index c4d45d4..565e1b5 100644 --- a/src/pages/home/Home.jsx +++ b/src/pages/home/Home.jsx @@ -1,31 +1,42 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { navLinks } from '../../routes.jsx'; import { getBlogPosts } from '../../data/blog'; import { getTodos } from '../../api'; +import { getCurrentTheme } from '../../data/heroConfig'; import myPhoto from '../../assets/myPhoto.jpg'; import './Home.css'; -const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; +const TODO_COLUMNS = [ + { id: 'todo', label: '계획', color: 'var(--neon-purple)' }, + { id: 'in_progress', label: '진행 중', color: '#f59e0b' }, + { id: 'done', label: '완료', color: '#34d399' }, +]; const Home = () => { const posts = getBlogPosts().slice(0, 3); const highlights = navLinks.filter((link) => link.id !== 'home'); - const [recentDev, setRecentDev] = useState([]); + const theme = getCurrentTheme(); + + const [todosByStatus, setTodosByStatus] = useState({ todo: [], in_progress: [], done: [] }); useEffect(() => { getTodos() - .then((todos) => { - if (!Array.isArray(todos)) return; - const now = Date.now(); - const filtered = todos - .filter((t) => t.status === 'done' && t.updated_at && (now - new Date(t.updated_at).getTime()) <= SEVEN_DAYS_MS) - .slice(0, 5); - setRecentDev(filtered); + .then((data) => { + if (!Array.isArray(data)) return; + setTodosByStatus({ + todo: data.filter((t) => t.status === 'todo'), + in_progress: data.filter((t) => t.status === 'in_progress'), + done: data.filter((t) => t.status === 'done'), + }); }) .catch(() => { /* 조용히 실패 */ }); }, []); + const totalTasks = todosByStatus.todo.length + todosByStatus.in_progress.length + todosByStatus.done.length; + const doneTasks = todosByStatus.done.length; + const inProgress = todosByStatus.in_progress.length; + return (
이번 달 집중 테마
- 빠르게 업데이트하는 대신, 한 번쯤 되돌아보며 기록하는 걸 목표로 - 합니다. 글은 매주 한 편씩 추가될 예정이에요. -
+{theme.desc}
게시 글
-{posts.length}편
+전체 태스크
++ {totalTasks}개 +
다음 업데이트
-이번 주말
+진행 중 / 완료
++ {inProgress} / {doneTasks} +
최근 7일 내 완료된 태스크를 보여줍니다.
-완료된 태스크가 없습니다.
- ) : ( - recentDev.map((todo) => ( -{todo.title}
- {todo.description && ( -{todo.description}
- )} -계획 · 진행 중 · 완료 태스크를 한눈에 확인합니다.
태스크가 없습니다.
+ ) : ( + items.map((todo) => ( +{todo.title}
+ {todo.description && ( +{todo.description}
+ )} ++ {todo.updated_at + ? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }) + : ''} +
+