feat(schedule,dashboard): remove aside mini-calendar (compact month nav), hero inner scroll wrapper

- schedule: MiniCalendar removed per owner; AsideMonthNav keeps month cursor for hall widgets
- dashboard hero: overflow moved to .kx-timeline-scroll wrapper so the section never shows scrollbars

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zio 2026-07-24 07:29:29 +09:00
parent aab5e8836e
commit a9826df87b
3 changed files with 14 additions and 74 deletions

View File

@ -105,6 +105,7 @@ export function OrganizerDashboardPage() {
{data.milestones.length === 0 ? (
<EmptyState title={t('dashboard.milestoneEmptyTitle')} description={t('dashboard.milestoneEmptyDesc')} />
) : (
<div className="kx-timeline-scroll">
<ol className="kx-timeline">
{data.milestones.map((m, i) => (
<li key={`${m.label}-${i}`} className={`kx-timeline__node is-${milestoneClass(m.state)}`}>
@ -124,6 +125,7 @@ export function OrganizerDashboardPage() {
</li>
))}
</ol>
</div>
)}
</section>

View File

@ -38,14 +38,18 @@
/* 히어로 타임라인 */
.kx-dash__hero {
padding: var(--space-5) var(--space-4);
/* 하단 짤림 방지(소유자 2026-07-24): overflow-x:auto 가로 스크롤바가
마지막 라벨 줄을 덮지 않도록 스크롤바 몫의 하단 여유를 확보. */
padding-bottom: calc(var(--space-5) + 14px);
border: var(--border-card);
border-radius: var(--radius-lg);
background: var(--color-white);
/* 섹션 자체는 스크롤바 금지(소유자 2026-07-24 "상하 스크롤바 안 생기도록")
가로 스크롤은 내부 래퍼(.kx-timeline-scroll) 전담, 섹션 높이는 내용에 맞춰 늘어난다. */
overflow: visible;
}
.kx-timeline-scroll {
overflow-x: auto;
scrollbar-gutter: stable;
overflow-y: hidden;
/* 스크롤바가 생겨도 라벨을 덮지 않도록 래퍼 하단 여유 */
padding-bottom: var(--space-2);
}
.kx-timeline {
display: grid;

View File

@ -17,7 +17,6 @@ import {
CATEGORY_LABEL,
EVENT_YEARS,
STATUS_LABEL,
eventDaysFor,
hallUtilizationFor,
type EventCategory,
type EventStatus,
@ -60,7 +59,6 @@ function isDegradable(err: unknown): boolean {
const MOCK = `${import.meta.env.BASE_URL}mock/organizer_dashboard`;
const THUMBS = [`${MOCK}/img1.jpg`, `${MOCK}/img2.jpg`, `${MOCK}/img3.jpg`];
const TODAY = new Date(2026, 6, 11); // 2026-07-11 (currentDate)
const TODAY_YMD = '2026-07-11';
const KNOWN_CATEGORIES = new Set<EventCategory>(['exhibition', 'culture', 'trade', 'tech']);
@ -315,7 +313,7 @@ export function ExhibitionSchedulePage() {
)}
<aside className="kx-sched__aside">
<MiniCalendar cursor={asideCursor} onCursor={setAsideCursor} holidays={holidays} />
<AsideMonthNav cursor={asideCursor} onCursor={setAsideCursor} />
<HallAvailability cursor={asideCursor} />
<HallUtilization cursor={asideCursor} />
</aside>
@ -324,92 +322,28 @@ export function ExhibitionSchedulePage() {
);
}
const WD = ['일', '월', '화', '수', '목', '금', '토'];
function MiniCalendar({
function AsideMonthNav({
cursor,
onCursor,
holidays,
}: {
cursor: Date;
onCursor: (d: Date) => void;
holidays?: Map<string, string>;
}) {
const { t } = useTranslation();
const year = cursor.getFullYear();
const month = cursor.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
/*
* : `GET /api/events/calendar?year=&month=` event .
* (NETWORK/NOT_FOUND/NOT_IMPLEMENTED) (eventDaysFor) .
* eventDays `${year}-${month+1}-${day}`() .
*/
const query = useQuery<{ days: Set<string>; degraded: boolean }>({
queryKey: ['schedule-calendar', year, month],
queryFn: async () => {
try {
const rows = await scheduleApi.calendar(year, month + 1);
const days = new Set<string>();
for (const r of rows) days.add(`${year}-${month + 1}-${Number(r.date.slice(8, 10))}`);
return { days, degraded: false };
} catch (err) {
if (isDegradable(err)) return { days: eventDaysFor(year, month), degraded: true };
throw err;
}
},
});
const eventDays = query.data?.days ?? new Set<string>();
const degraded = query.data?.degraded ?? false;
const cells: (number | null)[] = [];
for (let i = 0; i < firstDay; i++) cells.push(null);
for (let d = 1; d <= daysInMonth; d++) cells.push(d);
const isToday = (d: number) =>
TODAY.getFullYear() === year && TODAY.getMonth() === month && TODAY.getDate() === d;
return (
<section className="kx-card kx-cal" aria-label={t('schedule.miniCal')}>
{/* 역할 명확화(소유자 2026-07-24): 미니 캘린더 = 아래 홀 위젯의 조회 월 커서 */}
<section className="kx-card kx-cal kx-cal--compact" aria-label={t('schedule.asideMonthPicker')}>
<div className="kx-cal__role">
<strong className="kx-cal__role-title">{t('schedule.asideMonthPicker')}</strong>
<span className="kx-cal__role-hint">{t('schedule.asideMonthPickerHint')}</span>
</div>
<div className="kx-cal__head">
<button className="kx-cal__nav" aria-label={t('schedule.prevMonth')} onClick={() => onCursor(new Date(year, month - 1, 1))}></button>
<strong>{t('schedule.yearMonth', { y: year, m: month + 1 })}{degraded && <span className="kx-bi__degraded"> {t('schedule.offlineSample')}</span>}</strong>
<strong className="tnum">{t('schedule.yearMonth', { y: year, m: month + 1 })}</strong>
<button className="kx-cal__nav" aria-label={t('schedule.nextMonth')} onClick={() => onCursor(new Date(year, month + 1, 1))}></button>
</div>
<div className="kx-cal__grid" role="grid">
{WD.map((w, i) => (
<span key={w} className="kx-cal__wd" role="columnheader">{t(`common.weekday.${i}`)}</span>
))}
{cells.map((d, i) => {
if (d === null) return <span key={`e${i}`} className="kx-cal__cell is-empty" />;
const hasEvent = eventDays.has(`${year}-${month + 1}-${d}`);
const hkey = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
const holidayName = holidays?.get(hkey);
return (
<span
key={d}
className={`kx-cal__cell tnum ${isToday(d) ? 'is-today' : ''} ${hasEvent ? 'has-event' : ''} ${holidayName ? 'is-holiday' : ''}`}
role="gridcell"
title={holidayName || undefined}
aria-label={
holidayName
? `${t('schedule.day', { d })} ${holidayName}`
: hasEvent
? t('schedule.dayHasEvent', { d })
: t('schedule.day', { d })
}
>
{d}
</span>
);
})}
</div>
</section>
);
}