Skip to content
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

Feat(client): 캘린더 컴포넌트 구현 #118

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ab1aaed
feat: 캘린더 컴포넌트 생성
gwagjiug Jan 17, 2025
560050f
Merge branch 'develop' of https://github.com/SOPT-all/35-APPJAM-WEB-C…
gwagjiug Jan 17, 2025
c4ea1d7
delete: gitkeep 삭제제
gwagjiug Jan 17, 2025
52d5c9f
feat: 캘린더 컴포넌트 및 스타일 추가, 정보 버튼 클릭 기능 구현
gwagjiug Jan 17, 2025
9deae98
style: info 버튼 스타일 개선 및 테두리 두께 조정
gwagjiug Jan 17, 2025
aef1d60
delete: .gitkeep 삭제제
gwagjiug Jan 18, 2025
63b6067
fix: mock 데이터 축제 날짜 수정
gwagjiug Jan 18, 2025
0214c6f
feat: 캘린더 날짜 섹션 패딩 수정
gwagjiug Jan 18, 2025
adca901
feat: 축제 선택 훅 추가 및 타임테이블 컴포넌트 통합
gwagjiug Jan 18, 2025
0af590c
fix: 축제 날짜 수정
gwagjiug Jan 18, 2025
0ebcaa3
feat: 캘린더 컴포넌트에 날짜 선택 기능 추가 및 스타일 개선
gwagjiug Jan 18, 2025
efd2e04
feat: 축제 데이터에 새로운 날짜 추가
gwagjiug Jan 18, 2025
4252fa5
feat: 캘린더 컴포넌트의 날짜 선택 기능 개선 및 스타일 최적화
gwagjiug Jan 18, 2025
f652f95
Merge branch 'develop' of https://github.com/SOPT-all/35-APPJAM-WEB-C…
gwagjiug Jan 18, 2025
dbaaf8c
feat: use-button-selection.ts 로 이름 변경
gwagjiug Jan 18, 2025
86e53a1
feat: 캘린더 날짜 선택 시 transition 추가
gwagjiug Jan 18, 2025
fa8f1ef
feat: 캘린더 스타일에서 false 상태 제거
gwagjiug Jan 18, 2025
a1cbe4f
feat: WEEKDAYS 상수 추가
gwagjiug Jan 18, 2025
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
5 changes: 3 additions & 2 deletions apps/client/src/pages/confeti/components/summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BtnHeartDefault24,
BtnHeartFilled24,
} from 'node_modules/@confeti/design-system/src/icons/src';
import { WEEKDAYS } from '@shared/constants/day';

interface SummaryProps {
title: string;
Expand All @@ -20,13 +21,13 @@ interface SummaryProps {
const formatReserveDate = (reserveAt: string): string => {
const date = new Date(reserveAt);

const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
const weekData = WEEKDAYS;

const hours = date.getHours();
const period = hours >= 12 ? '오후' : '오전';
const hour12 = hours % 12 === 0 ? 12 : hours % 12;

const formattedDate = `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일 (${weekdays[date.getDay()]}) ${period}${hour12}시`;
const formattedDate = `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일 (${weekData[date.getDay()]}) ${period}${hour12}시`;

return formattedDate;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
import { themeVars } from '@confeti/design-system/styles';

export const container = style({
padding: '2rem',
});

export const yearSection = style({
...themeVars.fontStyles.title5_b_15,
color: themeVars.color.black,
});

export const dateSection = style({
display: 'grid',
gridTemplateColumns: 'repeat(7,1fr)',
padding: '2.1rem 0rem',
gap: '2rem',
});

export const dateItems = style({
...themeVars.display.flexColumnCenter,
width: '3rem',
height: '5.5rem',
gap: '0.6rem',
});

export const dayNum = recipe({
base: {
...themeVars.display.flexCenter,
width: '3rem',
height: '3rem',
borderRadius: '1.5rem',
cursor: 'pointer',
background: 'transparent',
},
variants: {
isSelected: {
true: {
background: themeVars.color.confeti_lime,
transition: 'background 0.3s ease',
},
},
hasFestivalDate: {
true: {
...themeVars.fontStyles.title4_b_16,
color: themeVars.color.black,
},
false: {
...themeVars.fontStyles.body1_r_16,
color: themeVars.color.gray500,
cursor: 'default',
},
},
},
});

export const dayKo = recipe({
base: {
...themeVars.display.flexCenter,
background: 'transparent',
},
variants: {
hasFestivalDate: {
true: {
...themeVars.fontStyles.title4_b_16,
color: themeVars.color.black,
},
false: {
...themeVars.fontStyles.body1_r_16,
color: themeVars.color.gray500,
},
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as styles from './calender.css';
import { cn } from '@confeti/design-system/utils';
import {
useFormattedYear,
useFormattedWeek,
useDayNumSelection,
createFestivalDateMap,
checkFestivalDateStatus,
} from '@pages/time-table/hooks/use-data-formatted';

interface CalenderProps {
festivalDates: { festivalDateId: number; festivalAt: string }[];
}

const Calender = ({ festivalDates }: CalenderProps) => {
const firstDate = festivalDates[0]?.festivalAt;
const { weekDays } = useFormattedWeek(firstDate);
const { selectedDayNumeId, handleDayNumClick } = useDayNumSelection();
const festivalDateMap = createFestivalDateMap(festivalDates);

const dateItems = weekDays.map((day, id) => ({
...day,
...checkFestivalDateStatus(festivalDateMap, id, selectedDayNumeId),
}));
Comment on lines +21 to +24
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateItems 배열은 기존의 날짜 정보(date, dayKo)에 **선택 여부(isSelected)**와 축제일 여부(hasFestivalDate) 같은 추가적인 정보를 포함하는 객체인데

네이밍 뭐가 적절할까요..?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateDetails이 적절해보여요


return (
<section className={styles.container}>
<div className={styles.yearSection}>
<span>{useFormattedYear(firstDate)}</span>
</div>
<div className={styles.dateSection}>
{dateItems.map(
({ date, dayKo, festivalDateId, isSelected, hasFestivalDate }) => (
<div className={styles.dateItems} key={festivalDateId}>
<p
className={cn(styles.dayNum({ isSelected, hasFestivalDate }))}
onClick={() =>
festivalDateId && handleDayNumClick(festivalDateId)
}
>
{date}
</p>
<p
className={cn(styles.dayKo({ hasFestivalDate }))}
onClick={() =>
festivalDateId && handleDayNumClick(festivalDateId)
}
>
{dayKo}
</p>
</div>
),
)}
</div>
</section>
);
};

export default Calender;
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ export const ImageVariants = recipe({
},
lg: {},
},
isClicked: {
true: {
border: '2.5px solid transparent',
backgroundImage: `
radial-gradient(circle at bottom, #ffffff 100%, rgba(255, 255, 255, 0) 100%),
linear-gradient(to top,rgb(234, 255, 175) 30%,rgb(174, 225, 32) 100%)
`,
backgroundOrigin: 'border-box',
backgroundClip: 'content-box, border-box',
transition: 'background-image 0.4s ease, border-color 0.4s ease',
},
Comment on lines +74 to +84
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InfoButton의 border 그라데이션 스타일을 위한 코드입니다 border 토큰화 해야할까요??

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선은 지금 상태로 유지하면 될 것 같아요!

},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ interface InfoItemsProps {
alt: string;
text: string;
size?: SizeType;
isClicked?: boolean;
onClick(): void;
}

interface ItemImagesProps {
size?: SizeType;
src: string;
alt: string;
isClicked?: boolean;
}

interface ItemTextProps {
Expand Down Expand Up @@ -70,17 +73,24 @@ const FixedButton = ({ size = 'md' }: FixedButtonProps) => (
</>
);

const InfoItems = ({ size = 'md', src, alt, text }: InfoItemsProps) => (
const InfoItems = ({
size = 'md',
src,
alt,
text,
onClick,
isClicked,
}: InfoItemsProps) => (
<>
<div className={cn(ItemsVariants({ size }))}>
<InfoButton.ImageField src={src} alt={alt} />
<div className={cn(ItemsVariants({ size }))} onClick={onClick}>
<InfoButton.ImageField src={src} alt={alt} isClicked={isClicked} />
<InfoButton.TextField text={text} color="black" />
</div>
</>
);

const ItemImage = ({ size = 'md', src, alt }: ItemImagesProps) => (
<img className={cn(ImageVariants({ size }))} src={src} alt={alt} />
const ItemImage = ({ size = 'md', src, alt, isClicked }: ItemImagesProps) => (
<img className={cn(ImageVariants({ size, isClicked }))} src={src} alt={alt} />
);

const ItemText = ({ size = 'md', text, color }: ItemTextProps) => (
Expand Down
Empty file.
50 changes: 50 additions & 0 deletions apps/client/src/pages/time-table/hooks/use-button-selection.ts
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 채은이랑 마음 잘맞음 이슈 때문에 기존 파일 새롭게 그냥 추가해서 올린겁니다~ 기존 develop 에도 존재하는 파일을 그냥 복붙해서 올린거에요~

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState, useMemo } from 'react';

interface Festival {
festivalId: number;
title: string;
logoUrl: string;
festivalDates: Array<{
festivalDateId: number;
festivalAt: string;
}>;
}

const useButtonSelection = (festivals: Festival[]) => {
// 초기 선택된 축제 ID 설정
const [clickedFestivalId, setClickedFestivalId] = useState<number | null>(
festivals.length > 0 ? festivals[0].festivalId : null,
);

// festival ID와 dates를 매핑하는 Map 생성
const festivalDatesMap = useMemo(
() =>
new Map(
festivals.map((festival) => [
festival.festivalId,
festival.festivalDates,
]),
),
[festivals],
);

// 축제 선택/해제 핸들러
const handleFestivalClick = (festivalId: number) => {
setClickedFestivalId((prevId) =>
prevId === festivalId ? null : festivalId,
);
};

// 선택된 축제의 날짜들 가져오기
const selectedFestivalDates = clickedFestivalId
? (festivalDatesMap.get(clickedFestivalId) ?? [])
: [];

return {
clickedFestivalId,
selectedFestivalDates,
handleFestivalClick,
};
};

export default useButtonSelection;
87 changes: 87 additions & 0 deletions apps/client/src/pages/time-table/hooks/use-data-formatted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useState, useMemo } from 'react';
import { WEEKDAYS } from '@shared/constants/day';

const YEAR_MESSAGE = {
ERR_MESSAGE: '',
};

const weekData = WEEKDAYS;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 @bongtta 랑 완전히 겹치는 상수 데이터를 쓰고있어서 shared 로 옮기고, 채은님이 사용하시던 페이지에서도 제가 수정완료 했습니다!


export const useFormattedYear = (date: string | null) => {
if (!date) {
return ``;
}

const [year, month] = date.split('.') || [];
if (!year || !month) {
return `${YEAR_MESSAGE.ERR_MESSAGE}`;
}
Comment on lines +16 to +18
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 메시지 필요할까요?? 궁금합니다. 인포버튼 클릭했을 때 밑에 텍스트가 렌더링 되는 부분에서 혹시 몰라서 우선 대체로 에러 메시지를 띄울 수 있도록 설정해두어야 하나? 하는데 굳이 필요없을 것 같기도해요

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 메세지에 대한 기준이 아직 명확하지 않으니 우선은 ' '로 구현해주신대로 유지합시다!
앱잼기간동안 에러는 ErrorBoundary로 공통으로 처리해요 !


return `${year}${parseInt(month, 10)}월`;
};

/**
* 특정 날짜 기준으로 일주일 동안의 날짜(num) 값을 계산하고 반환함
*/
export const useFormattedWeek = (date: string | null) => {
return useMemo(() => {
if (!date) return { weekDays: [] };

const [year, month, day] = date
.split('.')
.map((part) => parseInt(part, 10));
if (isNaN(year) || isNaN(month) || isNaN(day)) return { weekDays: [] };

const baseDate = new Date(year, month - 1, day);
const weekDays = Array.from({ length: 7 }, (_, i) => {
const currentDate = new Date(baseDate);
currentDate.setDate(baseDate.getDate() + i);
return {
date: currentDate.getDate(),
dayKo: weekData[currentDate.getDay()],
};
});

return { weekDays };
}, [date]);
};

export const useDayNumSelection = () => {
const [selectedDayNumeId, setSelectedDateId] = useState<number | null>(null);

const handleDayNumClick = (festivalDateId: number) => {
setSelectedDateId((prev) =>
prev === festivalDateId ? null : festivalDateId,
);
};

return {
selectedDayNumeId,
handleDayNumClick,
};
};

// festivalDateMap 생성 함수
export const createFestivalDateMap = (
festivalDates: { festivalDateId: number }[],
) => {
return new Map(
festivalDates.map((festival, index) => [
index + 1,
festival.festivalDateId,
]),
);
};
// isSelected 처리 함수
export const checkFestivalDateStatus = (
Comment on lines +74 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
};
// isSelected 처리 함수
export const checkFestivalDateStatus = (
};
// isSelected 처리 함수
export const checkFestivalDateStatus = (

festivalDateMap: Map<number, number>,
id: number,
selectedDateId: number | null,
) => {
const festivalDateId = festivalDateMap.get(id + 1);
const isSelected: boolean | undefined =
festivalDateId && selectedDateId === festivalDateId ? true : undefined;
const hasFestivalDate = festivalDateId !== undefined;

return { festivalDateId, isSelected, hasFestivalDate };
};
Loading
Loading