이 가이드에서는 회의실을 예약하는 캘린더를 만듭니다. 회의실마다 한 행씩 있는 타임라인, 드래그해서 원하는 자리에 놓는 예약, 한 회의실이 두 번 예약되지 않게 막는 규칙, 그리고 예약을 어느 캘린더에나 넣을 수 있는 파일로 이루어집니다. @needless-ui/angular의 스케줄러를 사용합니다. Needless UI의 모든 것과 마찬가지로 MIT 라이선스이므로, 리소스 타임라인을 어떤 앱에서든 무료로 쓸 수 있습니다.

회의실과 예약

회의실은 리소스입니다. id와 title이 있고, 그 밖의 정보는 data에 넣습니다. 예약은 일정이며, 2026-10-23T09:00 같은 단순한 ISO 시각과 resource에 넣은 회의실 id를 가집니다. 예약 하나에 여러 회의실이나 사람을 목록으로 지정할 수도 있습니다.

회의실마다 한 행

둘 다 view="timeline"과 함께 nui-scheduler에 전달합니다. dayStart와 dayEnd는 하루를 회의실 운영 시간으로 한정하고, snapMinutes는 예약을 15분 단위로 옮기며, businessHours는 회의실이 문을 닫는 시간을 음영으로 표시합니다. nuiSchedulerResource는 각 행의 헤더를 그립니다.

booking.html
<nui-scheduler
[events]="bookings()"
[resources]="rooms"
view="timeline"
dayStart="8"
dayEnd="19"
snapMinutes="15"
[businessHours]="hours"
editable
selectable
[allow]="free"
(slotSelect)="book($event)"
(eventChange)="move($event)"
(rangeChange)="range.set($event)"
>
<ng-template nuiSchedulerResource let-room>{{ room.title }}</ng-template>
</nui-scheduler>
<button nuiButton type="button" (click)="download()">Add to calendar</button>

예약하고 옮기기

스케줄러는 데이터를 직접 바꾸지 않고, 사용자가 한 일을 알려 줍니다. 회의실 행을 가로질러 드래그하면 시간이 선택되고, slotSelect가 그 시간을 회의실과 함께 알려 줍니다. 예약이나 그 끝을 드래그하면 eventChange가 새 시간을 알려 주고, 다른 회의실로 옮겼다면 새 회의실도 알려 줍니다. 시그널을 갱신하면 타임라인도 따라 바뀝니다.

booking.ts
import { Component, signal } from '@angular/core';
import { NuiButton } from '@needless-ui/angular/button';
import {
NuiScheduler,
type NuiSchedulerChange,
type NuiSchedulerEvent,
NuiSchedulerResourceTemplate,
type NuiSchedulerSlot,
} from '@needless-ui/angular/scheduler';
import { downloadIcs } from './booking-ics';

@Component({
selector: 'app-bookings',
imports: [NuiButton, NuiScheduler, NuiSchedulerResourceTemplate],
templateUrl: './booking.html',
})
export class Bookings {
/** Rooms are resources; bookings are events, with their room in `resource`. */
protected readonly rooms = [
{ id: 'atlas', title: 'Atlas' },
{ id: 'boreal', title: 'Boreal' },
];
protected readonly bookings = signal<NuiSchedulerEvent[]>([]);
/** Open on weekdays from 8:00 to 19:00: other time is shaded. */
protected readonly hours = [{ days: [1, 2, 3, 4, 5], start: '08:00', end: '19:00' }];
/** The days on screen: load their bookings when it changes. */
protected readonly range = signal<{ start: string; end: string } | null>(null);

protected book({ start, end, resource }: NuiSchedulerSlot): void {
const booking = { id: crypto.randomUUID(), title: 'New booking', start, end, resource };
this.bookings.update((all) => [...all, booking]);
}

protected move({ event, start, end, resource }: NuiSchedulerChange): void {
this.bookings.update((all) =>
all.map((b) =>
b.id === event.id ? { ...b, start, end, resource: resource ?? b.resource } : b,
),
);
}

/** A room holds one booking at a time. */
protected readonly free = (time: NuiSchedulerChange | NuiSchedulerSlot): boolean => {
const id = 'event' in time ? time.event.id : null;
const room = 'event' in time ? (time.resource ?? time.event.resource) : time.resource;
return !this.bookings().some(
(b) => b.id !== id && b.resource === room && b.start < time.end && b.end > time.start,
);
};

protected download(): void {
downloadIcs(this.bookings(), 'bookings');
}
}

이중 예약 방지

스케줄러는 예약을 만들거나 옮기기 전에 먼저 allow에 묻습니다. false를 반환하면 드래그가 빨간색으로 표시되고 놓아도 아무 일도 일어나지 않으며, 키보드로 조작할 때는 그 시간을 쓸 수 없다고 안내합니다. 이 예제는 같은 회의실의 다른 예약을 확인합니다. 최종 판단은 서버가 내리므로, 서버에서도 다시 확인해야 합니다.

화면에 보이는 날짜 불러오기

rangeChange는 표시된 첫날과 마지막 날을 처음에 한 번, 그리고 사용자가 페이지를 넘길 때마다 다시 알려 줍니다. 그래서 httpResource나 원하는 어떤 요청으로든 그 기간의 예약만 가져올 수 있습니다.

캘린더에 추가

nuiSchedulerToIcs()는 예약을 iCalendar 파일로 쓰며, 이 파일은 Google Calendar, Outlook, Apple Calendar에서 가져올 수 있습니다. nuiSchedulerFromIcs()는 반대로 이런 파일을 읽고, 다른 시간대의 시간을 사용자의 시간대로 맞춥니다.

booking-ics.ts
import { type NuiSchedulerEvent, nuiSchedulerToIcs } from '@needless-ui/angular/scheduler';

/** Saves events as an .ics file, which Google Calendar, Outlook and Apple Calendar import. */
export function downloadIcs(events: readonly NuiSchedulerEvent[], name: string): void {
const file = new Blob([nuiSchedulerToIcs(events, { name })], { type: 'text/calendar' });
const link = document.createElement('a');
link.href = URL.createObjectURL(file);
link.download = `${name}.ics`;
link.click();
URL.revokeObjectURL(link.href);
}

키보드와 스크린 리더

모든 기능을 마우스 없이 쓸 수 있습니다. 타임라인은 탭 순서에서 한 번만 멈추며, 화살표 키로 시간과 회의실 사이를 이동하고, Shift와 화살표 키로 시간을 선택한 뒤 Enter로 예약합니다. 예약에서 Alt와 화살표 키를 누르면 예약을 다른 시간이나 회의실로 옮깁니다. 모든 예약은 제목, 회의실, 날짜, 시간으로 이름이 지정된 버튼입니다.

회의실을 나란히 놓은 일 보기, 반복 예약, 그 밖의 모든 옵션은 스케줄러 페이지와 API를 참고하시기 바랍니다.