You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
// frontend/components/calendar/WeekView.tsx — 주 뷰 (실제 날짜 기반 시간 그리드)
|
|
"use client";
|
|
import type { MouseEvent } from "react";
|
|
import { HOURS, dayNum, timeOfPx, topOf } from "@/lib/calendar/time";
|
|
import type { CalEvent, Calendar, FocusBlock as FocusBlockT } from "@/lib/calendar/types";
|
|
import { EvBlock, type EvCommit } from "./EvBlock";
|
|
import { FocusBlock } from "./FocusBlock";
|
|
|
|
function nowTopPx(): number {
|
|
const d = new Date();
|
|
return topOf(`${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`);
|
|
}
|
|
|
|
export function WeekView({
|
|
events,
|
|
focusBlocks,
|
|
calMap,
|
|
weekDates,
|
|
weekdays,
|
|
todayDate,
|
|
onOpen,
|
|
onSlotClick,
|
|
onCommit,
|
|
}: {
|
|
events: CalEvent[];
|
|
focusBlocks: FocusBlockT[];
|
|
calMap: Record<string, Calendar>;
|
|
weekDates: string[]; // ISO 7개(일~토)
|
|
weekdays: string[];
|
|
todayDate: string;
|
|
onOpen: (ev: CalEvent, e: MouseEvent) => void;
|
|
onSlotClick: (dateIso: string, time: string) => void;
|
|
onCommit?: EvCommit;
|
|
}) {
|
|
const nowTop = nowTopPx();
|
|
// 빈 시간 슬롯 클릭 → 생성. 이벤트/집중 블록은 stopPropagation 으로 제외됨.
|
|
const slotClick = (iso: string) => (e: MouseEvent<HTMLDivElement>) => {
|
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
onSlotClick(iso, timeOfPx(e.clientY - rect.top));
|
|
};
|
|
return (
|
|
<div className="cpanel">
|
|
<div className="wk-head">
|
|
<div className="wk-corner" />
|
|
{weekDates.map((iso, i) => (
|
|
<div
|
|
key={iso}
|
|
className={
|
|
"wk-dh" + (iso === todayDate ? " today" : "") + (i === 0 || i === 6 ? " wknd" : "")
|
|
}
|
|
>
|
|
<div className="wd">{weekdays[i]}</div>
|
|
<div className="dn">{dayNum(iso)}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="wk-body" role="region" aria-label="주간 시간표" tabIndex={0}>
|
|
<div className="wk-times">
|
|
{HOURS.map((h) => (
|
|
<div key={h} className="wk-thour">
|
|
<span>{h}:00</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{weekDates.map((iso, i) => {
|
|
const dayEvents = events.filter((e) => e.date === iso);
|
|
const dayAllDay = dayEvents.filter((e) => !e.start);
|
|
const dayTimed = dayEvents.filter((e) => e.start);
|
|
const dayFocus = focusBlocks.filter((b) => b.date === iso);
|
|
return (
|
|
<div
|
|
key={iso}
|
|
data-date={iso}
|
|
className={"wk-col" + (i === 0 || i === 6 ? " wknd" : "")}
|
|
onClick={slotClick(iso)}
|
|
>
|
|
{HOURS.map((h) => (
|
|
<div key={h} className="wk-hr" />
|
|
))}
|
|
{iso === todayDate && <div className="wk-now" style={{ top: nowTop + "px" }} />}
|
|
{dayFocus.map((b) => (
|
|
<FocusBlock key={b.id} b={b} />
|
|
))}
|
|
{dayAllDay.map((ev, ai) => (
|
|
<EvBlock key={ev.id} ev={ev} cal={calMap[ev.cal]} onClick={onOpen} idx={ai} />
|
|
))}
|
|
{dayTimed.map((ev) => (
|
|
<EvBlock
|
|
key={ev.id}
|
|
ev={ev}
|
|
cal={calMap[ev.cal]}
|
|
onClick={onOpen}
|
|
onCommit={onCommit}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|