feat(home): 모바일 반응형 — 스와이프 TODO + 풀다운 리프레시
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -803,15 +803,27 @@
|
||||
.home-profile__name {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.home-hero__stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.home-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-card {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.home-posts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.home-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.home-hero__stats {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
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 = [
|
||||
@@ -17,22 +20,24 @@ 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: [] });
|
||||
|
||||
useEffect(() => {
|
||||
getTodos()
|
||||
.then((data) => {
|
||||
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'),
|
||||
});
|
||||
})
|
||||
.catch(() => { /* 조용히 실패 */ });
|
||||
}, []);
|
||||
|
||||
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;
|
||||
@@ -132,7 +137,79 @@ const Home = () => {
|
||||
<h2>TODO</h2>
|
||||
<p>계획 · 진행 중 · 완료 태스크를 한눈에 확인합니다.</p>
|
||||
</div>
|
||||
<PullToRefresh onRefresh={loadTodos}>
|
||||
{isMobile ? (
|
||||
<SwipeableView
|
||||
tabs={[
|
||||
{
|
||||
key: 'todo',
|
||||
label: 'TODO',
|
||||
content: (
|
||||
<div className="home-todo-col__body">
|
||||
{(todosByStatus.todo || []).length === 0 ? (
|
||||
<p className="home-todo-col__empty">태스크가 없습니다.</p>
|
||||
) : (todosByStatus.todo || []).map((todo) => (
|
||||
<div key={todo.id} className="home-todo-card">
|
||||
<p className="home-todo-card__title">{todo.title}</p>
|
||||
{todo.description && <p className="home-todo-card__desc">{todo.description}</p>}
|
||||
<p className="home-todo-card__date">
|
||||
{todo.updated_at
|
||||
? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' })
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'in_progress',
|
||||
label: '진행중',
|
||||
content: (
|
||||
<div className="home-todo-col__body">
|
||||
{(todosByStatus.in_progress || []).length === 0 ? (
|
||||
<p className="home-todo-col__empty">태스크가 없습니다.</p>
|
||||
) : (todosByStatus.in_progress || []).map((todo) => (
|
||||
<div key={todo.id} className="home-todo-card">
|
||||
<p className="home-todo-card__title">{todo.title}</p>
|
||||
{todo.description && <p className="home-todo-card__desc">{todo.description}</p>}
|
||||
<p className="home-todo-card__date">
|
||||
{todo.updated_at
|
||||
? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' })
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'done',
|
||||
label: '완료',
|
||||
content: (
|
||||
<div className="home-todo-col__body">
|
||||
{(todosByStatus.done || []).length === 0 ? (
|
||||
<p className="home-todo-col__empty">태스크가 없습니다.</p>
|
||||
) : (todosByStatus.done || []).map((todo) => (
|
||||
<div key={todo.id} className="home-todo-card">
|
||||
<p className="home-todo-card__title">{todo.title}</p>
|
||||
{todo.description && <p className="home-todo-card__desc">{todo.description}</p>}
|
||||
<p className="home-todo-card__date">
|
||||
{todo.updated_at
|
||||
? new Date(todo.updated_at).toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' })
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<TodoBoard todosByStatus={todosByStatus} />
|
||||
)}
|
||||
</PullToRefresh>
|
||||
</section>
|
||||
|
||||
<section className="home-section">
|
||||
|
||||
Reference in New Issue
Block a user