Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/api/homeListMockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TabType } from '@/components/molecules/homeList/HomeListTab';

export type ListItem = { title: string; date: string };

export type TabListData = {
todo?: ListItem[];
done?: ListItem[];
};

export const mockData: Record<TabType, TabListData> = {
together: {
todo: [
{ title: '함께 준비할 리스트 1', date: '2025.07.21' },
{ title: '함께 준비할 리스트 2', date: '2025.07.24' },
{ title: '함께 준비할 리스트 3', date: '2025.07.23' },
{ title: '함께 준비할 리스트 4', date: '2025.07.10' },
],
done: [{ title: '함께 완료한 리스트', date: '2025.07.22' }],
},
bride: {
todo: [
{ title: '예신 준비할 리스트 1', date: '2025.07.21' },
{ title: '예신 준비할 리스트 2', date: '2025.07.24' },
{ title: '예신 준비할 리스트 3', date: '2025.07.23' },
],
done: [{ title: '예신 완료한 리스트', date: '2025.07.21' }],
},
groom: {
done: [{ title: '예랑 완료한 리스트', date: '2025.07.23' }],
},
};
72 changes: 72 additions & 0 deletions src/app/home-list/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import { ListItem, mockData } from '@/api/homeListMockData';
import { HomeListItem } from '@/components/molecules/homeList';
import { HomeListType } from '@/components/molecules/homeList/HomeListCard';
import {
HomeListDropdown,
HomeListDropdownType,
} from '@/components/molecules/homeList/HomeListDropdown';
import { TabType } from '@/components/molecules/homeList/HomeListTab';
import { TopBar } from '@/components/molecules/topBar';
import { useSearchParams } from 'next/navigation';
import { useMemo, useState } from 'react';

const labelMap: Record<TabType, string> = {
together: '함께',
bride: '예신',
groom: '예랑',
};

const HomeListPage = () => {
const searchParams = useSearchParams();
const type = (searchParams.get('type') ?? 'todo') as HomeListType;
const role = (searchParams.get('role') ?? 'together') as TabType;

const title =
type === 'done'
? `${labelMap[role]} 완료한 리스트`
: `${labelMap[role]} 할 리스트`;

const [sortedOption, setSortedOption] =
useState<HomeListDropdownType>('날짜순');
const items: ListItem[] = mockData[role]?.[type] ?? [];

const sortedItems = useMemo(() => {
if (sortedOption === '날짜순') {
return [...items].sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
);
}
// TODO: 카테고리순 정렬
return items;
}, [items, sortedOption]);

return (
<>
<TopBar className='w-full' />
<div className='mx-5'>
<h2 className='text-2xl font-semibold text-white'>{title}</h2>
<div className='mt-7 flex items-center justify-between'>
<span className='text-base font-semibold text-primary-500'>{`총 ${items.length}개`}</span>
<HomeListDropdown
selectedOption={sortedOption}
onSelectAction={setSortedOption}
/>
</div>
<div className='mt-4 flex flex-col space-y-3'>
{sortedItems.map(({ title, date }) => (
<HomeListItem
key={title + date}
size='medium'
title={title}
date={date}
/>
))}
</div>
</div>
</>
);
};

export default HomeListPage;
2 changes: 1 addition & 1 deletion src/components/atoms/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface DropdownProps {

export const Dropdown = ({ options }: DropdownProps) => {
return (
<div className='absolute right-0 top-4 w-28 rounded-md bg-gray-200 font-pretendard text-white shadow-lg'>
<div className='absolute right-0 top-4 w-28 rounded-md bg-gray-200 font-pretendard text-sm font-medium text-white shadow-lg'>
{options.map((option, index) => (
<div key={option.label}>
{index > 0 && <div className='border-t border-gray-300' />}
Expand Down
19 changes: 13 additions & 6 deletions src/components/molecules/homeList/HomeListDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
import DownArrow from '@/assets/wed_icon/icon_16/downarrow_default_gray 800.svg';
import { Dropdown } from '@/components/atoms/dropdown';
import { useDropdown } from '@/hooks/useDropdown';
import { useState } from 'react';

export const HomeListDropdown = () => {
const [selectedOption, setSelectedOption] = useState('날짜순');
export type HomeListDropdownType = '날짜순' | '카테고리순';
interface HomeListDropdownProps {
selectedOption: HomeListDropdownType;
onSelectAction: (value: HomeListDropdownType) => void;
}

export const HomeListDropdown = ({
selectedOption,
onSelectAction,
}: HomeListDropdownProps) => {
const { isDropdownOpen, handleToggleDropdown, dropdownRef, triggerRef } =
useDropdown();

Expand All @@ -19,15 +26,15 @@ export const HomeListDropdown = () => {
{
label: '날짜순',
onClick: () => {
setSelectedOption('날짜순');
onSelectAction('날짜순');
console.log('날짜순 정렬');
handleToggleDropdown();
},
},
{
label: '카테고리순',
onClick: () => {
setSelectedOption('카테고리순');
onSelectAction('카테고리순');
console.log('카테고리순 정렬');
handleToggleDropdown();
},
Expand All @@ -39,7 +46,7 @@ export const HomeListDropdown = () => {
<div
ref={triggerRef}
onClick={handleClick}
className='flex cursor-pointer items-center gap-1 text-gray-800'
className='flex cursor-pointer items-center gap-1 text-sm font-medium text-gray-600'
>
{selectedOption}
<DownArrow />
Expand Down
34 changes: 34 additions & 0 deletions src/components/molecules/homeList/HomeListTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

export type TabType = 'together' | 'bride' | 'groom';

interface TabSelectorProps {
selectedTab: TabType;
onSelectTab: (tab: TabType) => void;
}

const tabLabels: Record<TabType, string> = {
together: '함께',
bride: '예신',
groom: '예랑',
};

const tabs: TabType[] = ['together', 'bride', 'groom'];

export const TabSelector = ({ selectedTab, onSelectTab }: TabSelectorProps) => {
return (
<div className='mt-8 flex items-center rounded-lg bg-gray-100'>
{tabs.map((tab) => (
<button
key={tab}
onClick={() => onSelectTab(tab)}
className={`h-[53px] flex-1 rounded-lg text-base font-medium ${
tab === selectedTab ? 'bg-gray-200 text-white' : 'text-gray-500'
}`}
>
{tabLabels[tab]}
</button>
))}
</div>
);
};
2 changes: 1 addition & 1 deletion src/components/molecules/homeList/homeListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import RightArrow from '@/assets/wed_icon/icon_16/rightarrow_default_gray 800.svg';
import { HomeListItem } from './HomeListItem';

type HomeListType = 'done' | 'todo';
export type HomeListType = 'done' | 'todo';

const titleMap: Record<HomeListType, string> = {
done: '완료한 리스트',
Expand Down
101 changes: 30 additions & 71 deletions src/components/molecules/progress/TabProgressCard.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,43 @@
'use client';

import { useEffect, useState } from 'react';
import { mockData } from '@/api/homeListMockData';
import { TabType } from '../homeList/HomeListTab';
import { DonutProgress } from './DonutProgress';

const tabs = ['함께', '예신', '예랑'] as const;
type TabType = (typeof tabs)[number];
type ProgressDataMap = Record<TabType, ProgressInfo>;

type ProgressInfo = {
label: string;
total: number;
remaining: number;
};

// mock data
const fetchProgressData = async (): Promise<ProgressDataMap> => {
await new Promise((r) => setTimeout(r, 300));

return {
함께: { label: '함께', total: 20, remaining: 12 },
예신: { label: '예신', total: 5, remaining: 1 },
예랑: { label: '예랑', total: 4, remaining: 4 },
};
};

export const TabProgressCard = () => {
const [selectedTab, setSelectedTab] = useState<TabType>('함께');
const [progressData, setProgressData] = useState<ProgressDataMap>();

useEffect(() => {
// TODO : 서버에서 받아와야함
fetchProgressData().then(setProgressData);
}, []);

if (!progressData) {
return <div className='text-center text-white'>loading...</div>;
}

const { label, total, remaining } = progressData[selectedTab];
interface TabProgressCardProps {
selectedTab: TabType;
}

export const TabProgressCard = ({ selectedTab }: TabProgressCardProps) => {
const tabData = mockData[selectedTab];
const total = (tabData.todo?.length ?? 0) + (tabData.done?.length ?? 0);
const remaining = tabData.todo?.length ?? 0;
const percentage =
total > 0 ? Math.round(((total - remaining) / total) * 100) : 0;

return (
<div className='mt-8 w-full'>
{/* 탭 메뉴 */}
<div className='flex h-[53px] items-center rounded-lg bg-gray-100'>
{tabs.map((tab) => (
<button
key={tab}
onClick={() => setSelectedTab(tab)}
className={`h-[45px] flex-1 rounded-lg text-base font-medium ${
tab === selectedTab ? 'bg-gray-200 text-white' : 'text-gray-500'
}`}
>
{tab}
</button>
))}
</div>
const labelMap: Record<TabType, string> = {
together: '함께',
bride: '예신',
groom: '예랑',
};

{/* 콘텐츠 카드 */}
<div className='mt-4 rounded-lg bg-gray-100 p-5'>
<div className='flex items-start justify-between'>
{/* 텍스트 */}
<div>
<div className='mb-1 text-xl font-semibold text-primary-500'>
{label}
</div>
<div className='text-lg font-medium text-white'>
결혼 준비 진행률
</div>
<div className='mt-3 text-sm font-medium text-gray-600'>
{total}개 중 {remaining}개 남았어요!
</div>
return (
<div className='mt-4 rounded-lg bg-gray-100 p-5'>
<div className='flex items-start justify-between'>
{/* 텍스트 */}
<div>
<div className='mb-1 text-xl font-semibold text-primary-500'>
{labelMap[selectedTab]}
</div>

{/* 도넛 차트 */}
<div className='ml-auto mt-[10px]'>
<DonutProgress percentage={percentage} />
<div className='text-lg font-medium text-white'>결혼 준비 진행률</div>
<div className='mt-3 text-sm font-medium text-gray-600'>
{total}개 중 {remaining}개 남았어요!
</div>
</div>

{/* 도넛 차트 */}
<div className='ml-auto mt-[10px]'>
<DonutProgress percentage={percentage} />
</div>
</div>
</div>
);
Expand Down
42 changes: 28 additions & 14 deletions src/features/home/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
'use client';

import { ListItem, mockData } from '@/api/homeListMockData';
import { BottomNavigation } from '@/components/molecules/bottomNavigation';
import { HomeListCard } from '@/components/molecules/homeList';
import {
TabSelector,
TabType,
} from '@/components/molecules/homeList/HomeListTab';
import MemoLink from '@/components/molecules/memo/MemoLink';
import { Navigation } from '@/components/molecules/navigation';
import { ProgressCard, TabProgressCard } from '@/components/molecules/progress';

// mock data
const homeListItems = [
{ title: '촬영업체 선택1', date: '2025.07.08' },
{ title: '촬영업체 선택2', date: '2025.07.08' },
{ title: '촬영업체 선택3', date: '2025.07.08' },
{ title: '촬영업체 선택4', date: '2025.07.08' },
{ title: '촬영업체 선택5', date: '2025.07.08' },
];
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';

export const Home = () => {
const router = useRouter();
const [selectedTab, setSelectedTab] = useState<TabType>('together');
const [todoItems, setTodoItems] = useState<ListItem[]>([]);
const [doneItems, setDoneItems] = useState<ListItem[]>([]);

useEffect(() => {
const data = mockData[selectedTab];
setTodoItems(data.todo ?? []);
setDoneItems(data.done ?? []);
}, [selectedTab]);

return (
<div className='relative flex h-full w-full flex-col'>
<div className='mx-5 flex flex-1 flex-col overflow-y-auto'>
<Navigation />
<MemoLink />
{/* TODO : percentage 받아오기 */}
<ProgressCard percentage={54} />
<TabProgressCard />
<TabSelector selectedTab={selectedTab} onSelectTab={setSelectedTab} />
<TabProgressCard selectedTab={selectedTab} />
<HomeListCard
type='todo'
items={homeListItems}
onClick={() => console.log('남은 리스트 click')}
items={todoItems}
onClick={() =>
router.push(`/home-list?type=todo&role=${selectedTab}`)
}
/>
<HomeListCard
type='done'
items={homeListItems}
onClick={() => console.log('완료한 리스트 click')}
items={doneItems}
onClick={() =>
router.push(`/home-list?type=done&role=${selectedTab}`)
}
/>
</div>
<BottomNavigation />
Expand Down