-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
68da31e
commit a55b8b6
Showing
23 changed files
with
404 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// src/components/AnimatedRefreshButton.tsx | ||
import { motion, useAnimation } from 'framer-motion' | ||
import { useState } from 'react' | ||
|
||
type AnimatedRefreshButtonProps = { | ||
onClick: () => void | ||
} | ||
|
||
export function AnimatedRefreshButton({ onClick }: AnimatedRefreshButtonProps) { | ||
const controls = useAnimation() | ||
const [isAnimating, setIsAnimating] = useState(false) | ||
|
||
const handleClick = async () => { | ||
if (!isAnimating) { | ||
setIsAnimating(true) | ||
onClick() | ||
await controls.start({ | ||
rotate: 360, | ||
transition: { duration: 1, ease: 'linear' }, | ||
}) | ||
await controls.set({ rotate: 0 }) | ||
setIsAnimating(false) | ||
} | ||
} | ||
|
||
return ( | ||
<motion.button onClick={handleClick} className="d-btn d-btn-circle d-btn-ghost d-btn-sm" whileTap={{ scale: 0.9 }}> | ||
<motion.svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="h-20px w-20px" | ||
viewBox="0 0 20 20" | ||
fill="currentColor" | ||
animate={controls} | ||
> | ||
<path | ||
fillRule="evenodd" | ||
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" | ||
clipRule="evenodd" | ||
/> | ||
</motion.svg> | ||
</motion.button> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { useState } from 'react' | ||
|
||
import { Navigation } from './Navigation' | ||
import { SettingsContent } from './SettingsContent' | ||
import { TaskContent } from './TaskContent' | ||
|
||
export function Content() { | ||
const [activeTab, setActiveTab] = useState<'tasks' | 'settings'>('tasks') | ||
|
||
return ( | ||
<div className="flex h-full flex-col"> | ||
{activeTab === 'tasks' ? <TaskContent /> : <SettingsContent />} | ||
<Navigation activeTab={activeTab} setActiveTab={setActiveTab} /> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { motion } from 'framer-motion' | ||
type LoadingSkeletonProps = { | ||
progress: number | ||
} | ||
|
||
export function LoadingSkeleton({ progress }: LoadingSkeletonProps) { | ||
return ( | ||
<div className="space-y-16px"> | ||
<div className="mb-16px h-4px w-full rounded-full bg-gray-200"> | ||
<motion.div | ||
className="h-4px rounded-full bg-blue-600" | ||
initial={{ width: 0 }} | ||
animate={{ width: `${progress}%` }} | ||
/> | ||
</div> | ||
{[...Array(5)].map((_, index) => ( | ||
<div key={index} className="d-skeleton h-80px w-full"></div> | ||
))} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { cn } from '@/utils/cn' | ||
|
||
type NavigationProps = { | ||
activeTab: 'tasks' | 'settings' | ||
setActiveTab: (tab: 'tasks' | 'settings') => void | ||
} | ||
|
||
export function Navigation({ activeTab, setActiveTab }: NavigationProps) { | ||
return ( | ||
<div className="flex justify-around border-t border-gray-200 bg-white bg-opacity-80"> | ||
<button | ||
className={cn('flex flex-1 flex-col items-center justify-center px-16px py-12px', { | ||
'text-blue-500': activeTab === 'tasks', | ||
'text-gray-500': activeTab !== 'tasks', | ||
})} | ||
onClick={() => setActiveTab('tasks')} | ||
> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="mb-4px h-24px w-24px" | ||
viewBox="0 0 20 20" | ||
fill="currentColor" | ||
> | ||
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" /> | ||
<path | ||
fillRule="evenodd" | ||
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" | ||
clipRule="evenodd" | ||
/> | ||
</svg> | ||
<span className="text-12px">과제</span> | ||
</button> | ||
<button | ||
className={cn('flex flex-1 flex-col items-center justify-center px-16px py-12px', { | ||
'text-blue-500': activeTab === 'settings', | ||
'text-gray-500': activeTab !== 'settings', | ||
})} | ||
onClick={() => setActiveTab('settings')} | ||
> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="mb-4px h-24px w-24px" | ||
viewBox="0 0 20 20" | ||
fill="currentColor" | ||
> | ||
<path | ||
fillRule="evenodd" | ||
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" | ||
clipRule="evenodd" | ||
/> | ||
</svg> | ||
<span className="text-12px">설정</span> | ||
</button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
type SettingItemProps = { | ||
title: string | ||
description: string | ||
children: React.ReactNode | ||
} | ||
|
||
export function SettingItem({ title, description, children }: SettingItemProps) { | ||
return ( | ||
<div className="flex items-center justify-between rounded-12px bg-white p-10px"> | ||
<div> | ||
<h3 className="text-14px font-semibold text-gray-800">{title}</h3> | ||
<p className="text-11px text-gray-600">{description}</p> | ||
</div> | ||
{children} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { SettingItem } from './SettingItem' | ||
|
||
export function SettingsContent() { | ||
return ( | ||
<div className="flex-1 space-y-12px overflow-y-auto px-10px py-20px"> | ||
<SettingItem title="알림" description="과제 마감 알림 받기"> | ||
<input type="checkbox" className="d-toggle d-toggle-primary" /> | ||
</SettingItem> | ||
<SettingItem title="자동 새로고침" description="10분마다 과제 목록 갱신"> | ||
<input type="checkbox" className="d-toggle d-toggle-primary" /> | ||
</SettingItem> | ||
<SettingItem title="언어" description="앱 표시 언어 선택"> | ||
<select className="d-select d-select-bordered d-select-sm"> | ||
<option>한국어</option> | ||
<option>English</option> | ||
<option>日本語</option> | ||
</select> | ||
</SettingItem> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { cn } from '@/utils/cn' | ||
|
||
type TabNavigationProps = { | ||
activeTab: 'ongoing' | 'all' | ||
setActiveTab: (tab: 'ongoing' | 'all') => void | ||
} | ||
|
||
export function TabNavigation({ activeTab, setActiveTab }: TabNavigationProps) { | ||
return ( | ||
<div className="flex space-x-8px"> | ||
<button | ||
className={cn('rounded-full px-10px py-4px text-12px transition-colors', { | ||
'bg-blue-500 text-white': activeTab === 'ongoing', | ||
'bg-gray-200 text-gray-700': activeTab !== 'ongoing', | ||
})} | ||
onClick={() => setActiveTab('ongoing')} | ||
> | ||
진행중 | ||
</button> | ||
<button | ||
className={cn('rounded-full px-10px py-4px text-12px transition-colors', { | ||
'bg-blue-500 text-white': activeTab === 'all', | ||
'bg-gray-200 text-gray-700': activeTab !== 'all', | ||
})} | ||
onClick={() => setActiveTab('all')} | ||
> | ||
전체 | ||
</button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { ActivityType } from '@/types' | ||
import { cn } from '@/utils/cn' | ||
import { calculateDday } from '@/utils/dateUtils' | ||
|
||
type TaskCardProps = { | ||
task: ActivityType | ||
} | ||
|
||
export function TaskCard({ task }: TaskCardProps) { | ||
const dday = calculateDday(task.endAt, task.startAt) | ||
|
||
return ( | ||
<div className="rounded-12px bg-white p-12px shadow-sm"> | ||
<div className="mb-8px flex items-center gap-8px"> | ||
<span | ||
className={cn('rounded-8px px-6px py-4px text-11px', { | ||
'bg-blue-100 text-blue-800': task.type === 'video', | ||
'bg-green-100 text-green-800': task.type === 'assignment', | ||
})} | ||
> | ||
{task.type === 'video' ? '영상' : '과제'} | ||
</span> | ||
<h3 className="flex-1 text-14px font-semibold text-gray-800">{task.title}</h3> | ||
</div> | ||
<p className="mb-4px text-12px text-gray-600">{task.courseTitle}</p> | ||
<div className="flex items-center justify-between"> | ||
<span className="text-11px font-bold text-gray-500">{dday}</span> | ||
<span | ||
className={cn('rounded-8px px-6px py-4px text-11px', { | ||
'bg-green-100 text-green-800': task.hasSubmitted, | ||
'bg-yellow-100 text-yellow-800': !task.hasSubmitted && !task.startAt, | ||
'bg-red-100 text-red-800': !task.hasSubmitted && task.startAt, | ||
})} | ||
> | ||
{task.hasSubmitted ? '제출완료' : task.startAt !== '' ? '미제출' : '예정됨'} | ||
</span> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { formatDistanceToNow } from 'date-fns' | ||
import { ko } from 'date-fns/locale' | ||
import { useState, useMemo } from 'react' | ||
|
||
import { AnimatedRefreshButton } from './AnimatedRefreshButton' | ||
import { LoadingSkeleton } from './LoadingSkeleton' | ||
import { TabNavigation } from './TabNavigation' | ||
import { TaskList } from './TaskList' | ||
import { contentsData } from '@/data/dummyData' | ||
import useGetContents from '@/hooks/useGetContents' | ||
|
||
const isDevelopment = process.env.NODE_ENV === 'development' | ||
|
||
export function TaskContent() { | ||
const [taskTab, setTaskTab] = useState<'ongoing' | 'all'>('ongoing') | ||
const { data, pos, isLoading, refetch } = useGetContents({ enabled: !isDevelopment }) | ||
|
||
const contentData = isDevelopment ? contentsData : data | ||
|
||
const filteredTasks = useMemo(() => { | ||
return contentData.activityList.filter(task => (taskTab === 'ongoing' ? !task.hasSubmitted : true)) | ||
}, [taskTab, contentData.activityList]) | ||
|
||
const formattedUpdateTime = formatDistanceToNow(new Date(contentData.updateAt), { addSuffix: true, locale: ko }) | ||
|
||
return ( | ||
<> | ||
<div className="bg-white bg-opacity-50 px-16px py-12px"> | ||
<div className="mb-12px flex items-center justify-between"> | ||
<h2 className="text-16px font-bold">과제 목록</h2> | ||
<div className="group relative"> | ||
<AnimatedRefreshButton onClick={refetch} /> | ||
<div className="absolute right-0 mt-4px whitespace-nowrap rounded-2px bg-gray-800 px-6px py-2px text-10px text-white opacity-0 shadow-lg transition-opacity duration-300 group-hover:opacity-100"> | ||
{formattedUpdateTime} 갱신됨 | ||
</div> | ||
</div> | ||
</div> | ||
<TabNavigation activeTab={taskTab} setActiveTab={setTaskTab} /> | ||
</div> | ||
<div className="relative flex-1 overflow-hidden"> | ||
<div className="absolute inset-x-0 top-0 z-10 h-16px bg-gradient-to-b from-slate-100 to-transparent"></div> | ||
<div className="absolute inset-x-0 bottom-0 z-10 h-16px bg-gradient-to-t from-slate-100 to-transparent"></div> | ||
<div className="h-full overflow-y-auto px-16px py-20px"> | ||
{isLoading ? <LoadingSkeleton progress={pos} /> : <TaskList tasks={filteredTasks} />} | ||
</div> | ||
</div> | ||
</> | ||
) | ||
} |
Oops, something went wrong.