ResourcesSchedule
ResourcesSchedule
리소스 일별·주별·월별 뷰를 하나로 결합한 Schedule 래퍼 컴포넌트예요. @mantine/schedule 패키지의 컴포넌트예요.
출처: 문서
본문
사용법 (Usage)
ResourcesSchedule은 ResourcesDayView, ResourcesWeekView, ResourcesMonthView를 뷰 전환과 함께 하나의 컴포넌트로 결합한 래퍼예요. 현재 뷰와 날짜 상태를 관리하며 공통 props를 세 뷰 모두에 자동으로 전달해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
/>
);
}
제어된 상태 (Controlled state)
date/onDateChange와 view/onViewChange props로 현재 날짜와 뷰 레벨을 제어할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ResourcesScheduleViewLevel } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [view, setView] = useState('day');
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
view={view as ResourcesScheduleViewLevel}
onViewChange={setView as (v: ResourcesScheduleViewLevel) => void}
events={events}
resources={resources}
/>
);
}
드래그 앤 드롭 (Drag and drop)
withEventsDragAndDrop prop으로 모든 뷰에서 드래그 앤 드롭을 활성화할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState(initialEvents);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
withEventsDragAndDrop
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}
주 뷰 (Week view)
defaultView="week"로 일정을 기본적으로 주 뷰에서 열 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
defaultView="week"
/>
);
}
뷰별 props (View-specific props)
dayViewProps, weekViewProps, monthViewProps를 사용해 각 뷰에 특정한 props를 전달할 수 있어요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
dayViewProps={{ intervalMinutes: 30 }}
/>
);
}
이벤트 폼 (Event form)
타임 슬롯, 날짜 셀, 또는 기존 이벤트를 클릭하면 이벤트 폼이 열려요. 폼은 자동 뷰 전환과 함께 일별·주별·월별 뷰에서 동작해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { Select } from '@mantine/core';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { EventData, EventForm } from './EventForm';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState(initialEvents);
const [formOpened, setFormOpened] = useState(false);
const [selectedEventData, setSelectedEventData] = useState(null);
const [selectedResourceId, setSelectedResourceId] = useState(null);
const handleTimeSlotClick = ({
slotStart,
slotEnd,
resourceId,
}: {
slotStart: string;
slotEnd: string;
nativeEvent: React.MouseEvent;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: new Date(slotStart),
end: new Date(slotEnd),
color: 'blue',
});
setFormOpened(true);
};
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 (
<>
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
onTimeSlotClick={handleTimeSlotClick}
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>
</>
);
}
외부 드래그 앤 드롭 (External drag and drop)
onExternalEventDrop prop으로 외부 항목을 일정에 끌어올 수 있어요. 콜백은 대상 리소스의 resourceId를 받아요.
import { useRef, useState } from 'react';
import dayjs from 'dayjs';
import { Box, Grid, Text } from '@mantine/core';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'london', label: 'Meeting room: London' },
];
const sidebarItems = [
{ title: 'Quick Sync', duration: 30, color: 'teal' },
{ title: 'Workshop', duration: 120, color: 'orange' },
{ title: 'One-on-One', duration: 60, color: 'violet' },
];
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState([]);
const nextId = useRef(1);
const handleExternalDrop = ({
dataTransfer,
dropDateTime,
resourceId,
}: {
dataTransfer: DataTransfer;
dropDateTime: string;
resourceId?: string | number;
}) => {
const raw = dataTransfer.getData('text/plain');
if (!raw) {
return;
}
const data = JSON.parse(raw);
const start = dayjs(dropDateTime);
const end = start.add(data.duration, 'minutes');
setEvents((prev) => [
...prev,
{
id: nextId.current++,
title: data.title,
start: start.format('YYYY-MM-DD HH:mm:ss'),
end: end.format('YYYY-MM-DD HH:mm:ss'),
color: data.color,
resourceId,
},
]);
};
const handleEventDrop = ({
eventId,
newStart,
newEnd,
resourceId,
}: {
eventId: string | number;
newStart: string;
newEnd: string;
event: ScheduleEventData;
resourceId?: string | number;
}) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId ? { ...event, start: newStart, end: newEnd, resourceId } : event
)
);
};
return (
<Grid>
<Grid.Col span={4}>
<Text>Drag to schedule</Text>
{sidebarItems.map((item) => (
<div
draggable
onDragStart={(e) => {
e.dataTransfer.setData('text/plain', JSON.stringify(item));
e.dataTransfer.effectAllowed = 'copy';
}}
style={{ padding: '8px 12px', marginBottom: 8, cursor: 'grab' }}
>
{item.title}
</div>
))}
</Grid.Col>
<Grid.Col span={8}>
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
onExternalEventDrop={handleExternalDrop}
onEventDrop={handleEventDrop}
/>
</Grid.Col>
</Grid>
);
}
이벤트 리사이즈 (Event resize)
withEventResize prop으로 이벤트 리사이즈를 활성화해요. 세 뷰 모두에서 이벤트의 왼쪽이나 오른쪽 가장자리를 끌어 리사이즈할 수 있어요. 주 뷰에서 리사이즈는 이벤트 자신의 날짜에 제한돼요. 월 뷰에서 리사이즈는 하루 단위로 스냅되며 이벤트의 원래 시간을 보존해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState(initialEvents);
const handleEventUpdate = ({ eventId, newStart, newEnd }: { eventId: string | number; newStart: string; newEnd: string; event: ScheduleEventData }) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId ? { ...event, start: newStart, end: newEnd } : event
)
);
};
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
withEventResize
onEventResize={handleEventUpdate}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}
정적 모드 (Static mode)
mode="static"으로 설정하면 세 뷰 모두에서 모든 상호작용을 비활성화해요.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
events={events}
resources={resources}
mode="static"
/>
);
}