import React, { useCallback, 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 { useIsMobile } from '../../hooks/useIsMobile'; import SwipeableView from '../../components/SwipeableView'; import PullToRefresh from '../../components/PullToRefresh'; import './Home.css'; 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 theme = getCurrentTheme(); const isMobile = useIsMobile(); const [todosByStatus, setTodosByStatus] = useState({ todo: [], in_progress: [], done: [] }); const [portfolio, setPortfolio] = useState(null); useEffect(() => { fetch('/api/profile/public') .then(r => r.ok ? r.json() : null) .catch(() => null) .then(d => setPortfolio(d)); }, []); const loadTodos = useCallback(async () => { const data = await getTodos(); 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'), }); }, []); useEffect(() => { loadTodos().catch(() => { /* 조용히 실패 */ }); }, [loadTodos]); const totalTasks = todosByStatus.todo.length + todosByStatus.in_progress.length + todosByStatus.done.length; const doneTasks = todosByStatus.done.length; const inProgress = todosByStatus.in_progress.length; return (

Personal Archive

기록을 모으고,
이야기를 이어붙이는 작은 집.

개발, 여행 스냅, 그리고 생각을 모아두는 공간입니다.

블로그 둘러보기 여행 갤러리 열기

이번 달 집중 테마

{theme.theme}

{theme.desc}

전체 태스크

{totalTasks}

진행 중 / 완료

{inProgress} / {doneTasks}

공간 둘러보기

확장 가능한 구조로 구성해 이후에도 쉽게 페이지를 추가할 수 있습니다.

{highlights.map((item) => (
{item.icon}

{item.label}

{item.description}

))}

최근 블로그

마크다운 파일을 추가하면 자동으로 목록에 반영됩니다.

{posts.map((post) => (

{post.title}

{post.excerpt}

{post.date || '작성일 미정'} ))}
{/* ── TODO 보드 ──────────────────────────────────────────── */}

TODO

계획 · 진행 중 · 완료 태스크를 한눈에 확인합니다.

{isMobile ? ( {(todosByStatus.todo || []).length === 0 ? (

태스크가 없습니다.

) : (todosByStatus.todo || []).map((todo) => (

{todo.title}

{todo.description &&

{todo.description}

}

{todo.updated_at ? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }) : ''}

))}
), }, { key: 'in_progress', label: '진행중', content: (
{(todosByStatus.in_progress || []).length === 0 ? (

태스크가 없습니다.

) : (todosByStatus.in_progress || []).map((todo) => (

{todo.title}

{todo.description &&

{todo.description}

}

{todo.updated_at ? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }) : ''}

))}
), }, { key: 'done', label: '완료', content: (
{(todosByStatus.done || []).length === 0 ? (

태스크가 없습니다.

) : (todosByStatus.done || []).map((todo) => (

{todo.title}

{todo.description &&

{todo.description}

}

{todo.updated_at ? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }) : ''}

))}
), }, ]} /> ) : ( )}

Profile

페이지 주인 소개 영역입니다.

Profile

{portfolio?.profile?.role || 'Server Developer'}

{portfolio?.profile?.name || '박 재 오'}

{portfolio?.profile?.bio || '주변 동료와 함께 소통하며 성장하는걸 좋아합니다.'}

{(portfolio?.skills || []).slice(0, 8).map((s) => ( {s.name} ))} {!portfolio && ['C++', 'Git', 'AWS', 'Jira', 'MySQL', 'Docker', 'Kubernetes', 'Linux'].map((tag) => ( {tag} ))}
포트폴리오 보기 연락하기
); }; /* ── TodoBoard ──────────────────────────────────────────────────────── */ const TodoBoard = ({ todosByStatus }) => { const boardRef = useRef(null); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(false); const checkScroll = () => { const el = boardRef.current; if (!el) return; setCanScrollLeft(el.scrollLeft > 4); setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 4); }; useEffect(() => { checkScroll(); const el = boardRef.current; if (!el) return; el.addEventListener('scroll', checkScroll, { passive: true }); const ro = new ResizeObserver(checkScroll); ro.observe(el); return () => { el.removeEventListener('scroll', checkScroll); ro.disconnect(); }; }, [todosByStatus]); const scroll = (dir) => { const el = boardRef.current; if (!el) return; el.scrollBy({ left: dir * 280, behavior: 'smooth' }); }; const isEmpty = TODO_COLUMNS.every((col) => todosByStatus[col.id].length === 0); return (
{canScrollLeft && ( )} {canScrollRight && ( )}
{TODO_COLUMNS.map((col) => { const items = todosByStatus[col.id] ?? []; return (
{col.label} {items.length}
{items.length === 0 ? (

태스크가 없습니다.

) : ( items.map((todo) => (

{todo.title}

{todo.description && (

{todo.description}

)}

{todo.updated_at ? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }) : ''}

)) )}
); })}
Todo 보드 열기 →
); }; export default Home;