Skip to content

Commit

Permalink
Merge pull request #113 from jjh4450/fix/time_table
Browse files Browse the repository at this point in the history
Fix/time table
  • Loading branch information
flareseek authored Sep 23, 2024
2 parents 4510020 + 4de1559 commit a596a71
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 151 deletions.
49 changes: 28 additions & 21 deletions src/pages/Timetable/.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,30 @@ export const section = style({
});

const baseFilterButton = style({
position: "relative",
padding: "10px 15px",
margin: "0 5px 10px 0",
border: "none",
borderRadius: "20px",
cursor: "pointer",
transition: "background-color 0.3s ease",
backgroundColor: "rgba(79, 205, 197, 0.5)",
transition: "all 0.3s ease",
overflow: "hidden",

"::before": {
content: '""',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(255, 255, 255, 0.5)",
transition: "background-color 0.3s ease",
zIndex: -1,
},

"@media": {
"screen and (max-width: 768px)": {
padding: "16px 26px",
padding: "8px 12px",
margin: "0 3px 8px 0",
},
},
Expand All @@ -37,34 +51,26 @@ export const filterButton = styleVariants({
baseFilterButton,
{
color: vars.color.white,
":hover": {

":hover::before": {
backgroundColor: "rgba(255, 255, 255, 0.2)",
},
},
],
active: [
baseFilterButton,
{
backgroundImage: "linear-gradient(to top left, #04D1C3, #009efd, #FFFFFF)",
fontWeight: "bold",
backgroundColor: "rgba(255, 255, 255, 0.3)", // 강조 네모 배경
color: "transparent", // 텍스트 색상 투명
backgroundImage: "linear-gradient(to top left, #04D1C3,#009efd, #FFFFFF)", // 텍스트 그라데이션
WebkitBackgroundClip: "text", // 텍스트에만 그라데이션 적용
WebkitTextFillColor: "transparent", // 텍스트 색상 채우기 제거
position: "relative", // 텍스트와 배경 분리
WebkitBackgroundClip: "text",
backgroundClip: "text",
color: "transparent",

"::before": {
// 배경 유지
content: '""',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(255, 255, 255, 0.3)", // 강조 네모 배경
borderRadius: "20px", // 배경의 모서리 둥글게
zIndex: -1, // 배경이 텍스트 뒤로 가게
backgroundColor: "rgba(255, 255, 255, 0.5)",
},
":hover": {

":hover::before": {
backgroundColor: "rgba(255, 255, 255, 0.4)",
},
},
Expand Down Expand Up @@ -180,6 +186,7 @@ export const artistImage = style({
export const artistName = style({
fontSize: "1.7rem",
fontFamily: vars.font.pyeongChangLight,
wordBreak: "keep-all",
"@media": {
"screen and (max-width: 768px)": {
fontSize: "1.7rem",
Expand Down
157 changes: 43 additions & 114 deletions src/pages/Timetable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,20 @@
import React, { useEffect, useState, useMemo, useCallback, useRef } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { timeTableInfo } from "./timeTableInfo.ts";
import { timeTableFilterProps, timeTableInfoProps } from "../../shared/types/timeTable.ts";
import * as styles from "./.css.ts";
import { Link } from "react-router-dom";
import { artistInfoListProps } from "../../shared/types/mainPage.ts";
import { FilterButton, TimeTableItem } from "./subComponents.tsx";
import { clearTime, END_DATE, START_DATE, TIME_TABLE_FILTER } from "./utils.tsx";

// Constants
const TIME_TABLE_FILTER: timeTableFilterProps[] = [
{ name: "1일차", date: new Date("2024-09-23T00:00:00+09:00") },
{ name: "2일차", date: new Date("2024-09-24T00:00:00+09:00") },
{ name: "3일차", date: new Date("2024-09-25T00:00:00+09:00") },
{ name: "4일차", date: new Date("2024-09-26T00:00:00+09:00") },
];

const START_DATE = new Date("2024-09-23T00:00:00+09:00");
const END_DATE = new Date("2024-09-26T00:00:00+09:00");

const clearTime = (date: Date): Date => {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
};

const formatTime = (date: Date): string => {
return date.toLocaleTimeString("ko-KR", { hour: "2-digit", minute: "2-digit", hour12: false });
};

const getEventStatus = (
event: timeTableInfoProps,
currentTime: Date,
): "past" | "current" | "future" => {
let adjustedStartTime = new Date(event.startTime);

// KNU-ARTIST 항목에 대한 시작 시간 조정
if (event.description.includes("KNU-ARTIST")) {
if (event.description.includes("댄스")) {
adjustedStartTime = new Date(adjustedStartTime.getTime() - 5 * 60000);
} else if (event.description.includes("밴드")) {
adjustedStartTime = new Date(adjustedStartTime.getTime() - 4 * 60000);
}
}

if (currentTime < adjustedStartTime) return "future";
if (currentTime > event.endTime) return "past";
return "current";
};

// Sub-components
const FilterButton: React.FC<{
timeTable: timeTableFilterProps;
onClick: (date: Date) => void;
isActive: boolean;
}> = React.memo(({ timeTable, onClick, isActive }) => (
<button
className={isActive ? styles.filterButton.active : styles.filterButton.default}
onClick={() => onClick(clearTime(timeTable.date))}
>
{timeTable.name}
</button>
));

const ArtistInfo: React.FC<{
artist: artistInfoListProps[];
}> = React.memo(({ artist }) => (
<div className={styles.artistInfoContainer}>
<h3 className={styles.artistInfoTitle}>상세정보</h3>
{artist.map((a: artistInfoListProps, index) => (
<Link to={a.url} key={index} className={styles.artistItem}>
<img src={a.image} alt={a.name} className={styles.artistImage} />
<p className={styles.artistName}>{a.name}</p>
</Link>
))}
</div>
));

const TimeTableItem: React.FC<{
timeTable: timeTableInfoProps;
currentTime: Date;
refCallback: (node: HTMLDivElement | null) => void;
}> = React.memo(({ timeTable, currentTime, refCallback }) => {
const status = getEventStatus(timeTable, currentTime);

return (
<div className={`${styles.timeTableItem}`} ref={status === "current" ? refCallback : null}>
<h2 className={styles.timeTableTitle}>{timeTable.title}</h2>
{timeTable.descriptionShow && (
<p className={styles.timeTableDescription}>
{timeTable.description}
{timeTable.link && (
<a
className={styles.timeTableLink}
href={timeTable.link.url}
target="_blank"
rel="noreferrer"
>
{timeTable.link.text}
</a>
)}
</p>
)}
<p className={styles.timeTableTime}>
{timeTable.startTime.getMonth() + 1}/{timeTable.startTime.getDate()} |{" "}
{formatTime(timeTable.startTime)} ~ {formatTime(timeTable.endTime)}
</p>
{timeTable.artist && <ArtistInfo artist={timeTable.artist} />}
{status === "current" && <div className={styles.currentIndicator}>진행 중</div>}
</div>
);
});

// Main component
/**
* 타임테이블 페이지
*/
export default function Timetable() {
const [viewTime, setViewTime] = useState<Date>(START_DATE);
const [currentTime, setCurrentTime] = useState<Date>(new Date());
const lastCurrentEventRef = useRef<HTMLDivElement | null>(null);

/**
* 현재 날짜에 맞게 기준시간 설정
*/
useEffect(() => {
const today = clearTime(new Date());
const start = clearTime(START_DATE);
Expand All @@ -132,6 +34,11 @@ export default function Timetable() {
return () => clearInterval(timer);
}, []);

/**
* 필터링된 타임테이블 정보
* @see timeTableInfo
* @see useMemo
*/
const filteredTimeTableInfo = useMemo(
() =>
timeTableInfo
Expand All @@ -140,23 +47,43 @@ export default function Timetable() {
[viewTime],
);

/**
* 현재 진행중인 이벤트로 스크롤
*/
useEffect(() => {
// 진행 중인 마지막 요소로 스크롤
if (lastCurrentEventRef.current) {
if ("scrollIntoView" in lastCurrentEventRef.current) {
lastCurrentEventRef.current.scrollIntoView({ behavior: "smooth" });
return;
}
if (
lastCurrentEventRef.current &&
clearTime(currentTime).getTime() === clearTime(viewTime).getTime()
) {
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
const offset = rootFontSize * 3.75;

const elementPosition =
lastCurrentEventRef.current!.getBoundingClientRect().top + window.scrollY;
const offsetPosition = elementPosition - offset;

window.scrollTo({
top: offsetPosition,
behavior: "smooth",
});
} else {
window.scrollTo(0, 0);
}
window.scrollTo(0, 0);
}, [filteredTimeTableInfo]);

/**
* 필터 클릭 이벤트
*/
const handleFilterClick = useCallback((date: Date) => {
setViewTime(clearTime(date));
}, []);

/**
* 렌더링
*/
return (
<section className={styles.section}>
{/*상단 필터링 버튼*/}
<div>
{TIME_TABLE_FILTER.map((timeTable, index) => (
<FilterButton
Expand All @@ -167,6 +94,7 @@ export default function Timetable() {
/>
))}
</div>
{/*필터링된 타임테이블 정보*/}
<div>
{filteredTimeTableInfo.map((timeTable, index) => (
<React.Fragment key={index}>
Expand All @@ -181,6 +109,7 @@ export default function Timetable() {
</React.Fragment>
))}
</div>
{/*하단 필터링 버튼*/}
<div>
{TIME_TABLE_FILTER.map((timeTable, index) => (
<FilterButton
Expand Down
82 changes: 82 additions & 0 deletions src/pages/Timetable/subComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
import { artistInfoListProps } from "../../shared/types/mainPage.ts";
import * as styles from "./.css.ts";
import { Link } from "react-router-dom";
import { timeTableFilterProps, timeTableInfoProps } from "../../shared/types/timeTable.ts";
import { clearTime, formatTime, getEventStatus } from "./utils.tsx";

/**
* 일별 필터 버튼
*/
export const FilterButton: React.FC<{
timeTable: timeTableFilterProps;
onClick: (date: Date) => void;
isActive: boolean;
}> = React.memo(({ timeTable, onClick, isActive }) => (
<button
className={isActive ? styles.filterButton.active : styles.filterButton.default}
onClick={() => onClick(clearTime(timeTable.date))}
>
{timeTable.name}
</button>
));

/**
* 아티스트 정보
* @param artist 아티스트 정보
* @see artistInfoListProps
*/
const ArtistInfo: React.FC<{
artist: artistInfoListProps[];
}> = React.memo(({ artist }) => (
<div className={styles.artistInfoContainer}>
<h3 className={styles.artistInfoTitle}>상세정보</h3>
{artist.map((a: artistInfoListProps, index) => (
<Link to={a.url} key={index} className={styles.artistItem}>
<img src={a.image} alt={a.name} className={styles.artistImage} />
<p className={styles.artistName}>{a.name}</p>
</Link>
))}
</div>
));

/**
* 타임테이블 아이템
* @param timeTable 타임테이블 정보
* @param currentTime 현재 시간
* @param refCallback 현재 진행중인 이벤트를 가리키기 위한 콜백
*/
export const TimeTableItem: React.FC<{
timeTable: timeTableInfoProps;
currentTime: Date;
refCallback: (node: HTMLDivElement | null) => void;
}> = React.memo(({ timeTable, currentTime, refCallback }) => {
const status = getEventStatus(timeTable, currentTime);

return (
<div className={`${styles.timeTableItem}`} ref={status === "current" ? refCallback : null}>
<h2 className={styles.timeTableTitle}>{timeTable.title}</h2>
{timeTable.descriptionShow && (
<p className={styles.timeTableDescription}>
{timeTable.description}
{timeTable.link && (
<a
className={styles.timeTableLink}
href={timeTable.link.url}
target="_blank"
rel="noreferrer"
>
{timeTable.link.text}
</a>
)}
</p>
)}
<p className={styles.timeTableTime}>
{timeTable.startTime.getMonth() + 1}/{timeTable.startTime.getDate()} |{" "}
{formatTime(timeTable.startTime)} ~ {formatTime(timeTable.endTime)}
</p>
{timeTable.artist && <ArtistInfo artist={timeTable.artist} />}
{status === "current" && <div className={styles.currentIndicator}>진행 중</div>}
</div>
);
});
Loading

0 comments on commit a596a71

Please sign in to comment.