-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 계획 관리 페이지 UI 구현 #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
72275f5
dc7e306
f6c3603
ada79c4
2aee2ca
cd051cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import FormField from '@/components/auth/FormField'; | ||
| import PageHeader from '@/components/common/PageHeader'; | ||
| import Card from '@/components/home/Card'; | ||
| import AddPlanButton from '@/components/plans/AddPlanButton'; | ||
| import PlanSection from '@/components/plans/PlanSection'; | ||
| import SearchBar from '@/components/plans/SearchBar'; | ||
|
|
||
| import { Target, Calendar, CheckCircle2 } from 'lucide-react'; | ||
|
|
||
| const sampleTasks = [ | ||
| { | ||
| id: 1, | ||
| text: 'useState, useEffect 기초', | ||
| date: '2025-01-20', | ||
| isChecked: true, | ||
| }, | ||
| { | ||
| id: 2, | ||
| text: 'useContext, useReducer', | ||
| date: '2025-01-22', | ||
| isChecked: false, | ||
| }, | ||
| { | ||
| id: 3, | ||
| text: 'Custom Hooks 만들기', | ||
| date: '2025-01-22', | ||
| isChecked: false, | ||
| }, | ||
| ]; | ||
|
|
||
| const Page = () => { | ||
| return ( | ||
| <div className="bg-background min-h-screen p-11"> | ||
| {/* 1. 페이지 헤더 */} | ||
| <PageHeader | ||
| title="플랜" | ||
| highlight="관리하기" | ||
| description="학습 주제를 만들고 세부 과제를 관리하세요" | ||
| /> | ||
|
|
||
| {/* 2. 상단 통계 카드 (Grid) */} | ||
| <div className="mb-4 grid grid-cols-1 gap-3.5 md:grid-cols-3"> | ||
| <Card className="flex items-center justify-between border-2 border-[#D5DCFB]"> | ||
| {/* 왼쪽: 텍스트 영역 */} | ||
| <div className="flex flex-col gap-1"> | ||
| <span className="text-text-sub text-sm font-medium"> | ||
| 전체 플랜 수 | ||
| </span> | ||
| <span className="text-4xl font-bold text-[#4757D3]">1</span> | ||
| </div> | ||
|
|
||
| {/* 오른쪽: 아이콘 영역 */} | ||
| <div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#D5DCFB] text-[#556BD6]"> | ||
| <Target size={24} strokeWidth={2.5} /> | ||
| </div> | ||
| </Card> | ||
| <Card className="flex items-center justify-between border-2 border-[#EBDBFC]"> | ||
| {/* 왼쪽: 텍스트 영역 */} | ||
| <div className="flex flex-col gap-1"> | ||
| <span className="text-text-sub text-sm font-medium"> | ||
| 진행중인 플랜 수 | ||
| </span> | ||
| <span className="text-4xl font-bold text-[#7B44C4]">1</span> | ||
| </div> | ||
|
|
||
| {/* 오른쪽: 아이콘 영역 */} | ||
| <div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#EBDBFC] text-[#7B44C4]"> | ||
| <Calendar size={24} strokeWidth={2.5} /> | ||
| </div> | ||
| </Card> | ||
| <Card className="flex items-center justify-between border-2 border-[#C6F6D7]"> | ||
| {/* 왼쪽: 텍스트 영역 */} | ||
| <div className="flex flex-col gap-1"> | ||
| <span className="text-text-sub text-sm font-medium"> | ||
| 완료된 플랜 수 | ||
| </span> | ||
| <span className="text-4xl font-bold text-[#00841F]">1</span> | ||
| </div> | ||
|
|
||
| {/* 오른쪽: 아이콘 영역 */} | ||
| <div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#C6F6D7] text-[#00841F]"> | ||
| <CheckCircle2 size={24} strokeWidth={2.5} /> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
|
|
||
| {/* 3. 검색 바 */} | ||
| <SearchBar /> | ||
|
|
||
| {/* 4. 메인 플랜 목록 */} | ||
| <section className="space-y-6"> | ||
| <PlanSection | ||
| title="React Hooks 학습" | ||
| description="React Hooks의 기본부터 고급 패턴까지 학습" | ||
| tasks={sampleTasks} | ||
| /> | ||
|
|
||
| {/* 추가적인 PlanSection이 있다면 여기에 배치 */} | ||
| </section> | ||
|
|
||
| {/* 5. 하단 추가 버튼 */} | ||
| <div className="mt-6"> | ||
| <AddPlanButton /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Page; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| interface PageHeaderProps { | ||
| title: string; | ||
| highlight?: string; | ||
| description?: string; | ||
| className?: string; | ||
| } | ||
|
|
||
| const PageHeader = ({ | ||
| title, | ||
| highlight, | ||
| description, | ||
| className = '', | ||
| }: PageHeaderProps) => { | ||
| return ( | ||
| <div className={`mb-8 pt-12 text-3xl ${className}`}> | ||
| <h1 className="text-text text-4xl font-bold tracking-tight"> | ||
| {title} | ||
| {/* highlight가 있을 때만 보라색으로 렌더링 */} | ||
| {highlight && <span className="text-primary ml-2">{highlight}</span>} | ||
| </h1> | ||
|
|
||
| {description && ( | ||
| <p className="text-text-sub mt-2 text-[1.5rem] font-medium"> | ||
| {description} | ||
| </p> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default PageHeader; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // components/dashboard/AddPlanButton.tsx | ||
| import { Plus } from 'lucide-react'; | ||
|
|
||
| export default function AddPlanButton() { | ||
| return ( | ||
| <button className="flex w-full flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-gray-300 py-6 font-medium text-gray-500 transition-all hover:border-gray-400 hover:bg-gray-50"> | ||
| <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-100 text-blue-600"> | ||
| <Plus size={20} /> | ||
| </div> | ||
| 새로운 플랜 추가하기 | ||
| </button> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // components/dashboard/PlanSection.tsx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; // 상태 관리를 위해 클라이언트 컴포넌트 선언 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ChevronDown, ChevronUp, Plus } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Card from '../home/Card'; // 기존에 만든 Card 재사용 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import TaskItem from './TaskItem'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface Task { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isChecked: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface PlanSectionProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tasks: Task[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function PlanSection({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tasks, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: PlanSectionProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isOpen, setIsOpen] = useState(true); // 기본적으로 열려있게 설정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 완료된 할 일 개수 계산 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const completedCount = tasks.filter((t) => t.isChecked).length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalCount = tasks.length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Card className="mb-4 transition-all duration-200"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 헤더 영역 (클릭 시 토글) */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="flex cursor-pointer items-start justify-between" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => setIsOpen(!isOpen)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3 className="text-xl font-bold text-gray-900">{title}</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="mt-1 text-sm text-gray-500">{description}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="text-right"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="block text-xs text-gray-500">진행률</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-lg font-bold text-[#556BD6]"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {completedCount}/{totalCount} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button className="text-gray-400 hover:text-gray-600"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isOpen ? <ChevronUp size={24} /> : <ChevronDown size={24} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토글 헤더가 비접근성(div 클릭)이며 중첩 버튼 구조입니다. 🛠️ 제안 수정- <div
- className="flex cursor-pointer items-start justify-between"
- onClick={() => setIsOpen(!isOpen)}
- >
+ <button
+ type="button"
+ className="flex w-full items-start justify-between text-left"
+ onClick={() => setIsOpen((prev) => !prev)}
+ aria-expanded={isOpen}
+ >
<div>
<h3 className="text-xl font-bold text-gray-900">{title}</h3>
<p className="mt-1 text-sm text-gray-500">{description}</p>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<span className="block text-xs text-gray-500">진행률</span>
<span className="text-lg font-bold text-[`#556BD6`]">
{completedCount}/{totalCount}
</span>
</div>
- <button className="text-gray-400 hover:text-gray-600">
+ <span className="text-gray-400 hover:text-gray-600" aria-hidden="true">
{isOpen ? <ChevronUp size={24} /> : <ChevronDown size={24} />}
- </button>
+ </span>
</div>
- </div>
+ </button>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 펼쳐지는 내용 영역 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isOpen && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="animate-fadeIn mt-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 할 일 목록 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {tasks.map((task) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TaskItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={task.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text={task.text} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date={task.date} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCompleted={task.isChecked} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 하위 항목 추가 버튼 (점선) */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button className="mt-2 flex w-full items-center justify-center gap-1 rounded-xl border-2 border-dashed border-[#556BD6]/30 py-3 text-sm font-medium text-[#556BD6] transition-colors hover:bg-[#556BD6]/5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Plus size={16} /> 새 하위항목 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 버튼에 type 지정이 필요합니다. 🛠️ 제안 수정- <button className="mt-2 flex w-full items-center justify-center gap-1 rounded-xl border-2 border-dashed border-[`#556BD6`]/30 py-3 text-sm font-medium text-[`#556BD6`] transition-colors hover:bg-[`#556BD6`]/5">
+ <button type="button" className="mt-2 flex w-full items-center justify-center gap-1 rounded-xl border-2 border-dashed border-[`#556BD6`]/30 py-3 text-sm font-medium text-[`#556BD6`] transition-colors hover:bg-[`#556BD6`]/5">📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Card> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // components/dashboard/SearchBar.tsx | ||
| import { Search } from 'lucide-react'; | ||
|
|
||
| export default function SearchBar() { | ||
| return ( | ||
| <div className="mb-4 flex justify-end"> | ||
| <div className="relative"> | ||
| <input | ||
| type="text" | ||
| placeholder="검색어를 입력하세요" | ||
| className="w-64 rounded-full border border-gray-200 bg-white py-2 pr-10 pl-4 text-sm shadow-sm focus:border-purple-400 focus:outline-none" | ||
| /> | ||
| <Search className="absolute top-2.5 right-3 text-gray-400" size={18} /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,38 @@ | ||||||
| // components/dashboard/TaskItem.tsx | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일 경로 주석이 실제 위치와 불일치합니다. 🛠️ 제안 수정-// components/dashboard/TaskItem.tsx
+// components/plans/TaskItem.tsx📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| import { Check } from 'lucide-react'; | ||||||
|
|
||||||
| interface TaskItemProps { | ||||||
| text: string; | ||||||
| date: string; | ||||||
| isCompleted: boolean; | ||||||
| } | ||||||
|
|
||||||
| export default function TaskItem({ text, date, isCompleted }: TaskItemProps) { | ||||||
| return ( | ||||||
| <div | ||||||
| className={`mb-2 flex items-center gap-3 rounded-xl p-4 transition-colors ${ | ||||||
| isCompleted ? 'bg-gray-50' : 'bg-gray-100' | ||||||
| }`} | ||||||
| > | ||||||
| {/* 체크박스 커스텀 */} | ||||||
| <div | ||||||
| className={`flex h-6 w-6 cursor-pointer items-center justify-center rounded-md border transition-colors ${ | ||||||
| isCompleted | ||||||
| ? 'border-green-500 bg-green-500 text-white' | ||||||
| : 'border-gray-300 bg-white hover:border-purple-400' | ||||||
| }`} | ||||||
| > | ||||||
| {isCompleted && <Check size={16} strokeWidth={3} />} | ||||||
| </div> | ||||||
|
Comment on lines
+4
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 체크박스가 클릭 가능해 보이지만 동작·접근성이 없습니다. 🛠️ 한 가지 접근: 토글 핸들러 옵션 추가 + 버튼化 interface TaskItemProps {
text: string;
date: string;
isCompleted: boolean;
+ onToggle?: () => void;
}
-export default function TaskItem({ text, date, isCompleted }: TaskItemProps) {
+export default function TaskItem({ text, date, isCompleted, onToggle }: TaskItemProps) {
+ const isInteractive = typeof onToggle === 'function';
return (
<div
className={`mb-2 flex items-center gap-3 rounded-xl p-4 transition-colors ${
isCompleted ? 'bg-gray-50' : 'bg-gray-100'
}`}
>
{/* 체크박스 커스텀 */}
- <div
- className={`flex h-6 w-6 cursor-pointer items-center justify-center rounded-md border transition-colors ${
- isCompleted
- ? 'border-green-500 bg-green-500 text-white'
- : 'border-gray-300 bg-white hover:border-purple-400'
- }`}
- >
+ <button
+ type="button"
+ role="checkbox"
+ aria-checked={isCompleted}
+ onClick={onToggle}
+ disabled={!isInteractive}
+ className={`flex h-6 w-6 items-center justify-center rounded-md border transition-colors disabled:opacity-100 disabled:cursor-default ${
+ isInteractive ? 'cursor-pointer' : 'cursor-default'
+ } ${
+ isCompleted
+ ? 'border-green-500 bg-green-500 text-white'
+ : 'border-gray-300 bg-white'
+ } ${isInteractive ? 'hover:border-purple-400' : ''}`}
+ >
{isCompleted && <Check size={16} strokeWidth={3} />}
- </div>
+ </button>🤖 Prompt for AI Agents |
||||||
|
|
||||||
| <div className="flex flex-col"> | ||||||
| <span | ||||||
| className={`text-sm font-medium ${isCompleted ? 'text-gray-400 line-through' : 'text-gray-700'}`} | ||||||
| > | ||||||
| {text} | ||||||
| </span> | ||||||
| <span className="text-xs text-gray-400">마감: {date}</span> | ||||||
| </div> | ||||||
| </div> | ||||||
| ); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 108
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 1367
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 100
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 241
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 4125
🏁 Script executed:
Repository: DeveloperBlog-Devflow/devflow
Length of output: 97
href경로 수정 필요"/plans/new" 경로가 존재하지 않습니다. 계획 관리 페이지는
/plans경로에만 존재하며, 여기서 AddPlanButton을 통해 새 계획 생성을 처리합니다.href를
/plans로 변경하세요.🤖 Prompt for AI Agents