ResourcesMonthView
ResourcesMonthView
리소스 간 일정 관리를 위해 리소스 레인(lane)을 사용하는 월별 뷰 컴포넌트예요. @mantine/schedule 패키지의 컴포넌트예요. 각 날짜 셀에 리소스 레인이 포함된 월 그리드를 표시해요.
출처: 문서
본문
사용법 (Usage)
ResourcesMonthView는 각 날짜 셀에 리소스 레인이 포함된 월 그리드를 표시해요. 이벤트는 리소스의 레인 안에 표시되어 한 달 동안의 리소스 활용도를 쉽게 파악할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
/>
);
}
정적 모드 (Static mode)
mode="static"으로 설정하면 모든 상호작용을 비활성화해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
드래그 앤 드롭 (Drag and drop)
withEventsDragAndDrop prop으로 드래그 앤 드롭을 활성화해요. onEventDrop 콜백은 대상 리소스의 resourceId를 받아 이벤트의 리소스 할당을 갱신할 수 있게 해줘요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState(initialEvents);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
withEventsDragAndDrop
startScrollDate={dayjs().format('YYYY-MM-DD')}
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
/>
);
}
이벤트 리사이즈 (Event resize)
withEventResize prop으로 이벤트 리사이즈를 활성화해요. 이벤트는 시작 또는 종료 가장자리를 끌어 리사이즈할 수 있으며, onEventResize 콜백이 갱신된 시작·종료 날짜로 호출돼요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState(initialEvents);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
withEventResize
startScrollDate={dayjs().format('YYYY-MM-DD')}
onEventResize={({ eventId, newStart, newEnd }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd }
: event
)
);
}}
/>
);
}
리사이즈는 하루 단위로 스냅되며 이벤트의 원래 시간을 보존해요. 예를 들어 17:00에 끝나는 이벤트의 종료 가장자리를 다른 날로 끌어도 17:00 종료 시간이 유지돼요. 정확히 자정에 끝나는 이벤트는 이전 날에 끝나는 것으로 취급되므로 리사이즈 후에도 종료 시간은 배타적(exclusive)으로 유지돼요.
리사이즈 핸들은 이벤트가 실제로 시작하고 끝나는 가장자리에만 렌더링돼요. 표시된 달 밖으로 계속되거나 여러 행에 걸쳐 분할된 이벤트는 해당 가장자리에 핸들을 렌더링하지 않아요. "+N more" 표시기 뒤에 숨겨진 이벤트는 리사이즈할 수 없어요.
리사이즈는 이벤트의 리소스를 변경하지 않아요. 다른 리소스로 이벤트를 옮기려면 이벤트 자체를 드래그해요.
canResizeEvent로 리사이즈할 수 있는 이벤트를 제어할 수 있어요.
이벤트 폼 (Event form)
날짜 셀을 클릭하면 새 이벤트를 만들고, 기존 이벤트를 클릭하면 편집할 수 있어요. withDragSlotSelect와 onSlotDragEnd를 사용하면 날짜 셀을 가로질러 드래그해 생성할 수 있어요. onSlotDragEnd 콜백은 드래그가 시작된 리소스의 resourceId를 포함해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { Select } from '@mantine/core';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { EventData, EventForm } from './EventForm';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState(initialEvents);
const [formOpened, setFormOpened] = useState(false);
const [selectedEventData, setSelectedEventData] = useState(null);
const [selectedResourceId, setSelectedResourceId] = useState(null);
const handleDayClick = ({
date: clickedDate,
resourceId,
}: {
date: string;
nativeEvent: React.MouseEvent;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: dayjs(clickedDate).startOf('day').toDate(),
end: dayjs(clickedDate).endOf('day').toDate(),
color: 'blue',
});
setFormOpened(true);
};
const handleEventClick = (event: ScheduleEventData) => {
setSelectedResourceId(event.resourceId ? String(event.resourceId) : null);
setSelectedEventData({
id: event.id,
title: event.title,
start: new Date(event.start),
end: new Date(event.end),
color: event.color || 'blue',
});
setFormOpened(true);
};
const handleSlotDragEnd = ({
rangeStart,
rangeEnd,
resourceId,
}: {
rangeStart: string;
rangeEnd: string;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: new Date(rangeStart),
end: new Date(rangeEnd),
color: 'blue',
});
setFormOpened(true);
};
const handleSubmit = (values: EventData) => {
if (values.id) {
setEvents((prev) =>
prev.map((event) =>
event.id === values.id
? {
...event,
title: values.title,
start: dayjs(values.start).toISOString(),
end: dayjs(values.end).toISOString(),
color: values.color || 'blue',
resourceId: selectedResourceId || event.resourceId,
}
: event
)
);
} else {
setEvents((prev) => [
...prev,
{
id: Math.random().toString(36).substring(2, 11),
title: values.title,
start: dayjs(values.start).toISOString(),
end: dayjs(values.end).toISOString(),
color: values.color || 'blue',
resourceId: selectedResourceId || undefined,
},
]);
}
};
const handleDeleteEvent = () => {
if (selectedEventData?.id) {
setEvents((prev) => prev.filter((e) => e.id !== selectedEventData.id));
}
};
return (
<>
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
onDayClick={handleDayClick}
onEventClick={handleEventClick}
onSlotDragEnd={handleSlotDragEnd}
withDragSlotSelect
/>
<EventForm
setFormOpened={setFormOpened}
onExitTransitionEnd={() => setSelectedEventData(null)}
values={selectedEventData}
onSubmit={handleSubmit}
onDelete={selectedEventData?.id ? handleDeleteEvent : undefined}
>
<Select
data={resources.map((r) => ({ value: String(r.id), label: r.label }))}
value={selectedResourceId}
onChange={setSelectedResourceId}
/>
</EventForm>
</>
);
}
커스텀 이벤트 렌더링 (Custom event rendering)
renderEvent prop으로 이벤트 렌더링 방식을 커스터마이즈할 수 있어요. 아래 예시는 호버 시 이벤트 세부 정보를 보여주는 HoverCard를 사용해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { HoverCard, UnstyledButton } from '@mantine/core';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { EventDetails } from './EventDetails';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState(initialEvents);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
renderEvent={(event, props) => (
<HoverCard>
<HoverCard.Target>
<UnstyledButton {...props}>{event.title}</UnstyledButton>
</HoverCard.Target>
<HoverCard.Dropdown>
<EventDetails event={event} />
</HoverCard.Dropdown>
</HoverCard>
)}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}
커스텀 리소스 라벨 (Custom resource label)
renderResourceLabel prop으로 행 헤더의 리소스 라벨 렌더링을 커스터마이즈할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { Stack, Text } from '@mantine/core';
import { ResourcesMonthView, ScheduleResourceData } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
renderResourceLabel={(resource: ScheduleResourceData) => (
<Stack gap={0}>
<Text fw={500}>{resource.label}</Text>
<Text size="xs">Office</Text>
</Stack>
)}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}
리소스 그룹 (Resource groups)
groups prop으로 리소스를 라벨이 있는 헤더 아래에 그룹화할 수 있어요. 그룹 라벨은 리소스 라벨 왼쪽에 열로 표시되며 리소스에 걸쳐 세로로 펼쳐져요. renderGroupLabel로 그룹 라벨 렌더링을, groupLabelWidth로 그룹 열 너비를 커스터마이즈해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleResourceGroup } from '@mantine/schedule';
import { events } from './data';
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'overflow', label: 'Overflow room' },
];
const groups: ScheduleResourceGroup[] = [
{ label: 'Floor 1', resourceIds: ['tokyo', 'paris'] },
{ label: 'Floor 2', resourceIds: ['new-york'] },
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
groups={groups}
/>
);
}
지역화 (Localization)
locale prop으로 언어를 변경할 수 있어요. labels prop으로 내장 라벨을 재정의할 수 있어요.
import 'dayjs/locale/es';
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
labels={{
moreEvents: (hiddenEventsCount) => `+${hiddenEventsCount} más`,
}}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}
반복 이벤트 (Recurring events)
ResourcesMonthView는 표시된 달에 대해 반복 이벤트를 자동으로 확장해요. 전체 문서는 반복 이벤트 가이드를 참고해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events = [
{
id: 'daily-sync-series',
title: 'Daily sync (series)',
start: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:00:00`,
end: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:30:00`,
color: 'blue',
resourceId: 'tokyo',
recurrence: {
rrule: 'FREQ=DAILY;COUNT=10',
exdate: [`${today} 09:00:00`],
},
},
{
id: 'daily-sync-override',
title: 'Daily sync (moved today)',
start: `${today} 14:00:00`,
end: `${today} 14:30:00`,
color: 'grape',
resourceId: 'tokyo',
recurringEventId: 'daily-sync-series',
recurrenceId: `${today} 09:00:00`,
},
];
function Demo() {
const [date, setDate] = useState(today);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
/>
);
}
셀당 최대 이벤트 수 (Max events per cell)
maxEventsPerTimeSlot prop으로 각 셀에 표시할 이벤트 수를 제한할 수 있어요. 제한보다 이벤트가 많으면 "+more" 표시기가 표시돼요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
// ... many events per resource
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
maxEventsPerTimeSlot={3}
/>
);
}
날짜 너비와 행 높이 (Day width and row height)
dayWidth와 rowHeight props로 날짜 열과 리소스 행의 크기를 커스터마이즈할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
시작 스크롤 날짜 (Start scroll date)
startScrollDate prop으로 초기 렌더링 시 특정 날짜로 스크롤할 수 있어요. 달에 날짜가 많고 오늘과 같은 특정 날짜에서 시작하고 싶을 때 유용해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
주말 없이 (Without weekend days)
withWeekendDays={false}로 월 그리드에서 주말 열을 숨길 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
더 많은 이벤트 표시기 (More events indicator)
날짜 셀에 maxEventsPerTimeSlot보다 많은 이벤트가 있으면 "+X more" 표시기가 표시돼요. 표시기를 클릭하면 그 날의 모든 이벤트를 볼 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
// ... many events on the same day
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
/>
);
}
더 많은 이벤트 props (More events props)
moreEventsProps로 더 많은 이벤트 드롭다운을 커스터마이즈할 수 있어요. 예를 들어 팝오버 대신 모달을 사용할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
// ... many events on the same day
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
moreEventsProps={{ transitionProps: { duration: 0 } }}
/>
);
}
커스텀 헤더 (Custom header)
withHeader={false}로 기본 헤더를 숨기고 ScheduleHeader를 사용해 내비게이션 컨트롤이 있는 커스텀 헤더를 만들 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { DateStringValue, ResourcesMonthView, ScheduleHeader } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(
dayjs().format('YYYY-MM-DD')
);
return (
<>
<ScheduleHeader>
<ScheduleHeader.Control
onClick={() =>
setDate(dayjs(date).subtract(1, 'month').startOf('month').format('YYYY-MM-DD') as DateStringValue)
}
>
‹
</ScheduleHeader.Control>
<ScheduleHeader.Select
onYearChange={(year) =>
setDate(dayjs(date).year(year).startOf('month').format('YYYY-MM-DD') as DateStringValue)
}
onMonthChange={(month) =>
setDate(dayjs(date).month(month).startOf('month').format('YYYY-MM-DD') as DateStringValue)
}
/>
<ScheduleHeader.Control
onClick={() =>
setDate(dayjs(date).add(1, 'month').startOf('month').format('YYYY-MM-DD') as DateStringValue)
}
>
›
</ScheduleHeader.Control>
<ScheduleHeader.Today
onClick={() => setDate(dayjs().format('YYYY-MM-DD') as DateStringValue)}
/>
</ScheduleHeader>
<ResourcesMonthView
date={date}
onDateChange={setDate}
events={events}
resources={resources}
withHeader={false}
/>
</>
);
}
반경 (Radius)
radius prop으로 이벤트의 테두리 반경을 커스터마이즈할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
스크롤 영역 props (Scroll area props)
scrollAreaProps로 스크롤 영역 동작을 커스터마이즈할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';