Bu kılavuzda toplantı odası rezervasyonu için bir takvim oluşturacağız: her oda için bir satırı olan bir zaman çizelgesi, sürükleyerek yerleştirdiğiniz rezervasyonlar, bir odanın iki kez rezerve edilmesini önleyen bir kural ve bir rezervasyonu herhangi bir takvime ekleyen bir dosya. Takvim, @needless-ui/angular paketindeki planlayıcıyı kullanır. Planlayıcı, tüm Needless UI bileşenleri gibi MIT lisanslıdır; bu yüzden kaynak zaman çizelgesi her uygulama için ücretsizdir.

Odalar ve rezervasyonlar

Odalar birer kaynaktır: bir id, bir title ve geri kalan her şey için data. Rezervasyonlar ise 2026-10-23T09:00 gibi ISO biçiminde düz zamanlar taşıyan ve odalarının id’sini resource içinde tutan etkinliklerdir. Bir rezervasyon, birden çok odayı ya da kişiyi bir liste olarak tutabilir.

Her oda için bir satır

İkisini de view="timeline" ile nui-scheduler bileşenine verin. dayStart ve dayEnd günü açılış ve kapanış saatleri arasında tutar, snapMinutes rezervasyonları çeyrek saatlik adımlarla taşır, businessHours ise odaların kapalı olduğu zamanı gölgeler. nuiSchedulerResource her satırın başlığını çizer.

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>

Rezerve edin ve taşıyın

Planlayıcı verilerinizi hiçbir zaman kendisi değiştirmez; yalnızca kullanıcıların ne yaptığını bildirir. Bir odanın satırında sürüklediğinizde zaman seçilir ve slotSelect bu zamanı odayla birlikte bildirir. Bir rezervasyonu ya da bitişini sürüklediğinizde eventChange yeni zamanlarını, rezervasyon başka bir odaya taşındıysa yeni odasını da bildirir. Signal’i güncellediğinizde zaman çizelgesi de buna göre değişir.

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');
}
}

Çift rezervasyona izin yok

Bir rezervasyon yapılmadan ya da taşınmadan önce planlayıcı allow fonksiyonuna sorar. Fonksiyon false döndürürse sürükleme kırmızı görünür ve bırakıldığında hiçbir şey olmaz; klavye kullanıldığında ise planlayıcı o zamanın müsait olmadığını duyurur. Örnek, odanın diğer rezervasyonlarını denetler; son söz sunucunuzda olduğu için orada da yeniden denetleyin.

Ekrandaki günleri yükleyin

rangeChange, gösterilen ilk ve son günü bir kez başlangıçta, sonra da kullanıcılar her sayfa değiştirdiğinde bildirir; böylece yalnızca o günlerin rezervasyonlarını httpResource ile ya da istediğiniz herhangi bir istekle getirebilirsiniz.

Takvime ekleyin

nuiSchedulerToIcs(), rezervasyonları bir iCalendar dosyası olarak yazar; Google Calendar, Outlook ve Apple Calendar bu dosyayı içe aktarır. nuiSchedulerFromIcs() böyle bir dosyayı geri okur ve başka saat dilimlerindeki zamanları kullanıcının saat dilimine çevirir.

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);
}

Klavye ve ekran okuyucular

Bunların hepsi fare olmadan da çalışır. Zaman çizelgesi tek bir sekme durağıdır: ok tuşları saatler ve odalar arasında gezinir, Shift ile birlikte oklar zaman seçer, Enter da seçilen zamanı rezerve eder. Odak bir rezervasyondayken Alt ile birlikte oklar onu başka bir saate ya da odaya taşır. Her rezervasyon, başlığı, odası, günü ve saatiyle adlandırılan bir düğmedir.

Gün görünümünde yan yana duran odalar, yinelenen rezervasyonlar ve diğer tüm seçenekler için planlayıcının sayfasına ve API’sine bakın.