diff --git a/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx b/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx new file mode 100644 index 0000000..4fb833a --- /dev/null +++ b/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx @@ -0,0 +1,66 @@ +import { memo } from 'react'; +import { Tag, Link2 } from 'lucide-react'; +import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo } from '@/utils'; + +/** + * 날짜 미정(월만 확정) 일정 카드 (PC) + * date_precision === 'month' 인 일정에 사용. 날짜 대신 "날짜 미정"으로 표시하고 + * 점선 테두리로 확정 일정과 구분한다. + */ +const UndatedScheduleCard = memo(function UndatedScheduleCard({ schedule, onClick, className = '' }) { + const categoryInfo = getCategoryInfo(schedule); + const displayMembers = getDisplayMembers(schedule); + const sourceName = schedule.source?.name; + + // date는 해당 월 1일(YYYY-MM-01)로 저장됨 → 월만 추출 + const month = new Date(schedule.date).getMonth() + 1; + + return ( +
+ {/* 월 영역 (연한 카테고리 색) */} +
+ {month}월 + +
+ + {/* 내용 */} +
+

{decodeHtmlEntities(schedule.title)}

+
+ {categoryInfo.name && ( + + + {categoryInfo.name} + + )} + {sourceName && ( + + + {sourceName} + + )} +
+ {displayMembers.length > 0 && ( +
+ {displayMembers.map((name, i) => ( + + {name} + + ))} +
+ )} +
+
+ ); +}); + +export default UndatedScheduleCard; diff --git a/frontend/src/components/pc/public/schedule/index.js b/frontend/src/components/pc/public/schedule/index.js index ccb3c55..28bbf5d 100644 --- a/frontend/src/components/pc/public/schedule/index.js +++ b/frontend/src/components/pc/public/schedule/index.js @@ -1,5 +1,6 @@ export { default as Calendar } from './Calendar'; export { default as ScheduleCard } from './ScheduleCard'; +export { default as UndatedScheduleCard } from './UndatedScheduleCard'; export { default as BirthdayCard } from './BirthdayCard'; export { default as DebutCard } from './DebutCard'; export { default as CategoryFilter } from './CategoryFilter'; diff --git a/frontend/src/pages/pc/public/schedule/Schedule.jsx b/frontend/src/pages/pc/public/schedule/Schedule.jsx index 36b38dd..79aa224 100644 --- a/frontend/src/pages/pc/public/schedule/Schedule.jsx +++ b/frontend/src/pages/pc/public/schedule/Schedule.jsx @@ -10,6 +10,7 @@ import { Calendar, CategoryFilter, ScheduleCard, + UndatedScheduleCard, BirthdayCard, DebutCard, } from '@/components/pc/public'; @@ -255,12 +256,24 @@ function PCSchedule() { } return schedules.filter((s) => { + if (s.datePrecision === 'month') return false; // 날짜 미정은 별도 처리 const matchesDate = selectedDate ? s.date === selectedDate : s.date?.startsWith(currentYearMonth); const matchesCategory = selectedCategories.length === 0 || selectedCategories.includes(s.category_id); return matchesDate && matchesCategory; }); }, [schedules, selectedDate, currentYearMonth, selectedCategories, isSearchMode, searchTerm, searchResults]); + // 날짜 미정(월만 확정) 일정 — 선택 날짜와 무관하게 해당 월이면 항상 하단에 표시 + const undatedSchedules = useMemo(() => { + if (isSearchMode) return []; + return schedules.filter((s) => { + if (s.datePrecision !== 'month') return false; + const matchesMonth = s.date?.startsWith(currentYearMonth); + const matchesCategory = selectedCategories.length === 0 || selectedCategories.includes(s.category_id); + return matchesMonth && matchesCategory; + }); + }, [schedules, currentYearMonth, selectedCategories, isSearchMode]); + // 달력 점 표시용 (카테고리만 필터링, 날짜는 한 달 전체 유지) const calendarSchedules = useMemo(() => { if (selectedCategories.length === 0) return schedules; @@ -538,7 +551,7 @@ function PCSchedule() { )} - {filteredSchedules.length}개 일정 + {filteredSchedules.length + undatedSchedules.length}개 일정 )} @@ -548,7 +561,7 @@ function PCSchedule() {
{loading ? (
로딩 중...
- ) : filteredSchedules.length > 0 ? ( + ) : filteredSchedules.length > 0 || undatedSchedules.length > 0 ? ( isSearchMode && searchTerm ? ( <>
@@ -593,22 +606,43 @@ function PCSchedule() {
) : ( - filteredSchedules.map((schedule, index) => ( - - {schedule.is_birthday ? ( - handleScheduleClick(schedule)} /> - ) : schedule.is_debut || schedule.is_anniversary ? ( - - ) : ( - handleScheduleClick(schedule)} /> - )} - - )) + <> + {filteredSchedules.map((schedule, index) => ( + + {schedule.is_birthday ? ( + handleScheduleClick(schedule)} /> + ) : schedule.is_debut || schedule.is_anniversary ? ( + + ) : ( + handleScheduleClick(schedule)} /> + )} + + ))} + + {/* 날짜 미정 일정 — 우선순위 낮춰 기존 일정 아래 배치 */} + {undatedSchedules.length > 0 && filteredSchedules.length > 0 && ( +
+
+ 날짜 미정 +
+
+ )} + {undatedSchedules.map((schedule, index) => ( + + handleScheduleClick(schedule)} /> + + ))} + ) ) : isSearchMode && searchTerm ? ( isSearchLoading ? (