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 ? (