ngx-resource-scheduler 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,655 +0,0 @@
1
- import {
2
- Component,
3
- ChangeDetectionStrategy,
4
- EventEmitter,
5
- Input,
6
- OnChanges,
7
- Output,
8
- SimpleChanges,
9
- TemplateRef,
10
- } from '@angular/core';
11
-
12
- import {
13
- PrimaryAxis,
14
- SchedulerEvent,
15
- SchedulerEventClick,
16
- SchedulerRangeChange,
17
- SchedulerResource,
18
- SchedulerSlotClick,
19
- SchedulerView,
20
- } from './types';
21
-
22
- import { toZonedTime, fromZonedTime } from 'date-fns-tz';
23
- import { PositionedEvent } from './internal/types-internal';
24
-
25
- type PrimaryColumn =
26
- | { kind: 'day'; day: Date; key: string; title: string }
27
- | { kind: 'resource'; resource: SchedulerResource; key: string; title: string };
28
-
29
- type SecondaryColumn =
30
- | { kind: 'day'; day: Date; key: string; title: string }
31
- | { kind: 'resource'; resource: SchedulerResource; key: string; title: string };
32
-
33
- export interface SchedulerEventTemplateContext {
34
- $implicit: PositionedEvent; // allows: let-event
35
- event: PositionedEvent;
36
- startZoned: Date;
37
- endZoned: Date;
38
- resourceId: string;
39
- day: Date;
40
- }
41
-
42
- @Component({
43
- selector: 'ngx-resource-scheduler',
44
- templateUrl: './ngx-resource-scheduler.component.html',
45
- styleUrls: ['./ngx-resource-scheduler.component.scss'],
46
- changeDetection: ChangeDetectionStrategy.OnPush,
47
- standalone: false
48
- })
49
- export class NgxResourceSchedulerComponent implements OnChanges {
50
- // --- REQUIRED ---
51
- @Input() startDate!: Date; // first visible day (00:00 recommended)
52
- @Input() resources: SchedulerResource[] = [];
53
- @Input() events: SchedulerEvent[] = [];
54
-
55
- // --- FLEXIBILITY ---
56
- @Input() nDays: number = 7; // clamp [1..7]
57
- @Input() primaryAxis: PrimaryAxis = 'days';
58
-
59
- // --- TIME WINDOW (vertical) ---
60
- @Input() dayStart: string = '08:00'; // HH:mm
61
- @Input() dayEnd: string = '20:00'; // HH:mm
62
- @Input() slotDuration: string = '00:30'; // HH:mm (grid resolution)
63
- @Input() snapToSlot: boolean = true;
64
-
65
- @Input() showSlotLines: boolean = true;
66
- @Input() slotLineStyle: 'slot' | 'hour' | 'both' = 'slot';
67
-
68
- // NAVIGATION
69
- @Input() showToolbar: boolean = true;
70
- @Input() prevLabel: string = '‹';
71
- @Input() nextLabel: string = '›';
72
- private lastRangeKey: string | null = null;
73
-
74
- // CUSTOM STYLING
75
- @Input() eventTemplate?: TemplateRef<SchedulerEventTemplateContext>;
76
- @Input() eventClass?: (e: PositionedEvent) => string | string[] | Set<string> | { [klass: string]: any };
77
- @Input() eventStyle?: (e: PositionedEvent) => { [k: string]: any };
78
-
79
- // i18n
80
- @Input() showDaysResourcesLabel: boolean = true;
81
- @Input() daysLabel: string = 'days';
82
- @Input() resourcesLabel: string = 'resources';
83
- @Input() todayLabel: string = 'Today';
84
-
85
- // --- MISC ---
86
- @Input() weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 1;
87
- @Input() locale?: string;
88
- @Input() timezone?: 'local' | 'UTC' | string; // IANA Time Zone Identifier
89
- @Input() readonly: boolean = false;
90
-
91
- // --- OUTPUTS ---
92
- @Output() slotClick = new EventEmitter<SchedulerSlotClick>();
93
- @Output() eventClick = new EventEmitter<SchedulerEventClick>();
94
- @Output() eventChange = new EventEmitter<any>();
95
- @Output() rangeChange = new EventEmitter<SchedulerRangeChange>();
96
- @Output() startDateChange = new EventEmitter<Date>();
97
-
98
- // --- INTERNAL LAYOUT CONSTANTS ---
99
- readonly pxPerMinute = 1; // 60px per hour
100
-
101
- // --- COMPUTED ---
102
- visibleDays: Date[] = [];
103
- primaryColumns: PrimaryColumn[] = [];
104
- secondaryColumns: SecondaryColumn[] = [];
105
-
106
- dayStartMinutes = 8 * 60;
107
- dayEndMinutes = 20 * 60;
108
- slotMinutes = 30;
109
-
110
- ngOnChanges(_: SimpleChanges): void {
111
- this.recompute();
112
- }
113
-
114
- // ---------- PUBLIC METHODS ---------
115
-
116
- /** Navigate by one "page" (nDays) backward */
117
- public prev(): void {
118
- this.navigatePrev();
119
- }
120
-
121
- /** Navigate by one "page" (nDays) forward */
122
- public next(): void {
123
- this.navigateNext();
124
- }
125
-
126
- /** Go to today (start of day) */
127
- public today(): void {
128
- this.navigateToday();
129
- }
130
-
131
- /** Programmatically set the visible start date */
132
- public goToDate(d: Date): void {
133
- this.setStartDate(this.startOfDay(d));
134
- }
135
-
136
- // ---------- TEMPLATE HELPERS ----------
137
-
138
- get timelineHeightPx(): number {
139
- return Math.max(0, (this.dayEndMinutes - this.dayStartMinutes) * this.pxPerMinute);
140
- }
141
-
142
- get hourLabels(): number[] {
143
- const startH = Math.floor(this.dayStartMinutes / 60);
144
- const endH = Math.floor(this.dayEndMinutes / 60);
145
-
146
- const hours: number[] = [];
147
- for (let h = startH; h <= endH; h++) hours.push(h);
148
- return hours;
149
- }
150
-
151
- get slotLines(): Array<{ top: number; isHalfHour: boolean }> {
152
- if (!this.showSlotLines) return [];
153
-
154
- const spanMin = this.dayEndMinutes - this.dayStartMinutes;
155
- if (spanMin <= 0 || this.slotMinutes <= 0) return [];
156
-
157
- const lines: Array<{ top: number; isHalfHour: boolean }> = [];
158
- for (let m = 0; m <= spanMin; m += this.slotMinutes) {
159
- const absoluteMin = this.dayStartMinutes + m;
160
-
161
- // true at hh:30 exactly (e.g., 08:30, 09:30, ...)
162
- const isHalfHour = absoluteMin % 60 === 30;
163
-
164
- lines.push({
165
- top: m * this.pxPerMinute,
166
- isHalfHour,
167
- });
168
- }
169
- return lines;
170
- }
171
-
172
- get hourLineOffsetsPx(): number[] {
173
- const spanMin = this.dayEndMinutes - this.dayStartMinutes;
174
- if (spanMin <= 0) return [];
175
-
176
- const offsets: number[] = [];
177
- const firstHourMin = Math.ceil(this.dayStartMinutes / 60) * 60;
178
-
179
- for (let m = firstHourMin; m <= this.dayEndMinutes; m += 60) {
180
- offsets.push((m - this.dayStartMinutes) * this.pxPerMinute);
181
- }
182
- // Also include the top edge at 0 for a clean line
183
- offsets.unshift(0);
184
-
185
- return offsets;
186
- }
187
-
188
- get rangeTitle(): string {
189
- if (!this.visibleDays.length) return '';
190
-
191
- const start = this.visibleDays[0];
192
- const end = this.visibleDays[this.visibleDays.length - 1];
193
-
194
- const sameMonth = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth();
195
- const opts: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
196
-
197
- if (this.visibleDays.length === 1) {
198
- return start.toLocaleDateString(this.locale, { weekday: 'short', ...opts });
199
- }
200
-
201
- if (sameMonth) {
202
- const month = start.toLocaleDateString(this.locale, { month: 'short' });
203
- return `${month} ${start.getDate()}–${end.getDate()}`;
204
- }
205
-
206
- return `${start.toLocaleDateString(this.locale, opts)} – ${end.toLocaleDateString(this.locale, opts)}`;
207
- }
208
-
209
- /**
210
- * Returns events that intersect a given (day, resource) cell.
211
- */
212
- eventsForCell(day: Date, resourceId: string): PositionedEvent[] {
213
- const { startUtc, endUtc } = this.windowBoundsUtc(day);
214
-
215
- const list = this.events
216
- .filter((e) =>
217
- e.resourceId === resourceId &&
218
- e.start < endUtc &&
219
- e.end > startUtc
220
- )
221
- .sort((a, b) => a.start.getTime() - b.start.getTime());
222
-
223
- return this.layoutOverlaps(list);
224
- }
225
-
226
- /**
227
- * CSS positioning for an event within a day cell.
228
- */
229
- styleForEvent(e: PositionedEvent, day: Date): { [k: string]: string } {
230
- const { startUtc: windowStartUtc, endUtc: windowEndUtc } = this.windowBoundsUtc(day);
231
-
232
- // If event doesn't intersect visible window, don't render it
233
- if (e.end <= windowStartUtc || e.start >= windowEndUtc) {
234
- return { display: 'none' };
235
- }
236
-
237
- const start = new Date(Math.max(e.start.getTime(), windowStartUtc.getTime()));
238
- const end = new Date(Math.min(e.end.getTime(), windowEndUtc.getTime()));
239
-
240
- const topMin = (start.getTime() - windowStartUtc.getTime()) / 60000;
241
- const durMin = Math.max(10, (end.getTime() - start.getTime()) / 60000);
242
-
243
- // overlap columns (same as you already have)
244
- const gapPx = 6;
245
- const cols = Math.max(1, e._cols);
246
- const col = Math.max(0, e._col);
247
-
248
- const widthExpr = `calc(${100 / cols}% - ${gapPx}px)`;
249
- const leftExpr = `calc(${(100 * col) / cols}% + ${gapPx / 2}px)`;
250
-
251
- return {
252
- top: `${topMin * this.pxPerMinute}px`,
253
- height: `${durMin * this.pxPerMinute}px`,
254
- width: widthExpr,
255
- left: leftExpr,
256
- right: 'auto',
257
- };
258
- }
259
-
260
- onEventClick(e: SchedulerEvent, mouse: MouseEvent) {
261
- this.eventClick.emit({ event: e, nativeEvent: mouse });
262
- }
263
-
264
- onCellClick(day: Date, resourceId: string, mouse: MouseEvent) {
265
- if (this.readonly) return;
266
-
267
- const clickedDate = this.computeClickedTime(day, mouse);
268
- const primaryKey = this.primaryAxis === 'days' ? this.dayKey(day) : resourceId;
269
- const secondaryKey = this.primaryAxis === 'days' ? resourceId : this.dayKey(day);
270
-
271
- this.slotClick.emit({
272
- date: clickedDate,
273
- day: this.startOfDay(day),
274
- resourceId,
275
- primaryAxis: this.primaryAxis,
276
- primaryKey,
277
- secondaryKey,
278
- });
279
- }
280
-
281
- cellDayKey(p:any,s:any): string {
282
- return this.dayKey(this.resolveCellDay(p,s));
283
- }
284
-
285
- cellResourceId(p:any,s:any): string {
286
- return this.resolveCellResourceId(p,s);
287
- }
288
-
289
- resolveCellResourceId(p: any, s: any): string {
290
- return this.primaryAxis === 'days' ? s.resource.id : p.resource.id;
291
- }
292
-
293
- resolveCellResource(p: any, s: any): SchedulerResource {
294
- return this.primaryAxis === 'days' ? s.resource : p.resource;
295
- }
296
-
297
- cellEvents(p: any, s: any): PositionedEvent[] {
298
- const day = this.resolveCellDay(p, s);
299
- const resourceId = this.resolveCellResourceId(p, s);
300
- return this.eventsForCell(day, resourceId); // now PositionedEvent[]
301
- }
302
-
303
- cellClick(p: any, s: any, ev: MouseEvent) {
304
- const day = this.resolveCellDay(p, s);
305
- const resourceId = this.resolveCellResourceId(p, s);
306
- this.onCellClick(day, resourceId, ev);
307
- }
308
-
309
- getEventLayoutStyle(e: PositionedEvent, p: any, s: any) {
310
- const day = this.resolveCellDay(p, s);
311
-
312
- const layout = this.styleForEvent(e, day); // contains top/height/left/width
313
- const userStyle = this.eventStyle ? this.eventStyle(e) : null;
314
-
315
- // IMPORTANT: layout must win so users can’t break positioning
316
- return userStyle ? { ...userStyle, ...layout } : layout;
317
- }
318
-
319
- // ---------- INTERNAL COMPUTATION ----------
320
-
321
- private windowBoundsUtc(day: Date): { startUtc: Date; endUtc: Date } {
322
- const base = this.startOfDay(day);
323
-
324
- // wall-clock dates for "day at HH:mm" (fields matter)
325
- const wallStart = this.setTime(base, this.dayStartMinutes);
326
- const wallEnd = this.setTime(base, this.dayEndMinutes);
327
-
328
- if (!this.timezone || this.timezone === 'local') {
329
- // interpreted as device local instants
330
- return { startUtc: wallStart, endUtc: wallEnd };
331
- }
332
-
333
- if (this.timezone === 'UTC') {
334
- // build true UTC instants for the wall clock
335
- const y = base.getFullYear();
336
- const m = base.getMonth();
337
- const d = base.getDate();
338
-
339
- const sH = Math.floor(this.dayStartMinutes / 60);
340
- const sM = this.dayStartMinutes % 60;
341
- const eH = Math.floor(this.dayEndMinutes / 60);
342
- const eM = this.dayEndMinutes % 60;
343
-
344
- return {
345
- startUtc: new Date(Date.UTC(y, m, d, sH, sM, 0, 0)),
346
- endUtc: new Date(Date.UTC(y, m, d, eH, eM, 0, 0)),
347
- };
348
- }
349
-
350
- // IANA timezone: wall-clock in zone -> UTC instant
351
- return {
352
- startUtc: fromZonedTime(wallStart, this.timezone),
353
- endUtc: fromZonedTime(wallEnd, this.timezone),
354
- };
355
- }
356
-
357
- private assignColumns(cluster: SchedulerEvent[]): PositionedEvent[] {
358
- // columnsEndTimes[col] = endTime of last event in that col
359
- const columnsEndTimes: number[] = [];
360
- const out: PositionedEvent[] = [];
361
-
362
- for (const e of cluster) {
363
- const s = e.start.getTime();
364
- const en = e.end.getTime();
365
-
366
- // Find first available column
367
- let col = 0;
368
- for (; col < columnsEndTimes.length; col++) {
369
- if (s >= columnsEndTimes[col]) break;
370
- }
371
-
372
- if (col === columnsEndTimes.length) columnsEndTimes.push(en);
373
- else columnsEndTimes[col] = en;
374
-
375
- out.push({ ...e, _col: col, _cols: 0 });
376
- }
377
-
378
- const totalCols = columnsEndTimes.length;
379
- // write total columns for all events in this cluster
380
- for (const pe of out) pe._cols = totalCols;
381
-
382
- return out;
383
- }
384
-
385
- private emitRangeIfChanged(payload: SchedulerRangeChange) {
386
- const key = `${payload.start.toISOString()}|${payload.end.toISOString()}|${payload.primaryAxis}|${payload.days}`;
387
- if (key === this.lastRangeKey) return;
388
- this.lastRangeKey = key;
389
- queueMicrotask(() => this.rangeChange.emit(payload));
390
- }
391
-
392
- private setStartDate(d: Date) {
393
- this.startDate = d;
394
- this.recompute();
395
- this.startDateChange.emit(d);
396
- }
397
-
398
- // Computes clicked time from y offset within a cell (very handy for creating events)
399
- private computeClickedTime(day: Date, mouse: MouseEvent): Date {
400
- const target = mouse.currentTarget as HTMLElement | null;
401
- if (!target || typeof (target as any).getBoundingClientRect !== 'function') {
402
- return this.setTime(this.startOfDay(day), this.dayStartMinutes);
403
- }
404
-
405
- const rect = target.getBoundingClientRect();
406
- const y = mouse.clientY - rect.top;
407
-
408
- const minutesFromStart = Math.max(0, Math.min(this.dayEndMinutes - this.dayStartMinutes, y / this.pxPerMinute));
409
- let snapped = minutesFromStart;
410
-
411
- if (this.snapToSlot && this.slotMinutes > 0) {
412
- snapped = Math.round(minutesFromStart / this.slotMinutes) * this.slotMinutes;
413
- }
414
-
415
- const totalMinutes = this.dayStartMinutes + snapped;
416
- return this.setTime(this.startOfDay(day), totalMinutes);
417
- }
418
-
419
- private layoutOverlaps(events: SchedulerEvent[]): PositionedEvent[] {
420
- // Sweep through events, grouping overlapping "clusters"
421
- const positioned: PositionedEvent[] = [];
422
-
423
- let cluster: SchedulerEvent[] = [];
424
- let clusterEnd = -Infinity;
425
-
426
- const flushCluster = () => {
427
- if (cluster.length === 0) return;
428
- positioned.push(...this.assignColumns(cluster));
429
- cluster = [];
430
- clusterEnd = -Infinity;
431
- };
432
-
433
- for (const e of events) {
434
- const s = e.start.getTime();
435
- const en = e.end.getTime();
436
-
437
- if (cluster.length === 0) {
438
- cluster = [e];
439
- clusterEnd = en;
440
- continue;
441
- }
442
-
443
- // If this event starts after cluster ends, it's a new cluster
444
- if (s >= clusterEnd) {
445
- flushCluster();
446
- cluster = [e];
447
- clusterEnd = en;
448
- continue;
449
- }
450
-
451
- // Still overlapping cluster
452
- cluster.push(e);
453
- if (en > clusterEnd) clusterEnd = en;
454
- }
455
-
456
- flushCluster();
457
- return positioned;
458
- }
459
-
460
- private resolveCellDay(p: any, s: any): Date {
461
- return this.primaryAxis === 'days' ? p.day : s.day;
462
- }
463
-
464
- private recompute() {
465
- // Clamp days
466
- const d = Math.max(1, Math.min(7, Math.floor(this.nDays || 7)));
467
-
468
- // Parse times
469
- this.dayStartMinutes = this.parseHmToMinutes(this.dayStart, 8 * 60);
470
- this.dayEndMinutes = this.parseHmToMinutes(this.dayEnd, 20 * 60);
471
- if (this.dayEndMinutes <= this.dayStartMinutes) {
472
- // fallback to a sane window
473
- this.dayEndMinutes = this.dayStartMinutes + 10 * 60;
474
- }
475
-
476
- this.slotMinutes = this.parseHmToMinutes(this.slotDuration, 30);
477
- if (this.slotMinutes <= 0) this.slotMinutes = 30;
478
-
479
- // Visible days
480
- const start = this.startOfDay(this.startDate ?? new Date());
481
- this.visibleDays = Array.from({ length: d }, (_, i) => this.addDays(start, i));
482
-
483
- // Columns
484
- if (this.primaryAxis === 'days') {
485
- this.primaryColumns = this.visibleDays.map((day) => ({
486
- kind: 'day',
487
- day,
488
- key: this.dayKey(day),
489
- title: this.formatDay(day),
490
- }));
491
-
492
- this.secondaryColumns = this.resources.map((r) => ({
493
- kind: 'resource',
494
- resource: r,
495
- key: r.id,
496
- title: r.title,
497
- }));
498
- } else {
499
- this.primaryColumns = this.resources.map((r) => ({
500
- kind: 'resource',
501
- resource: r,
502
- key: r.id,
503
- title: r.title,
504
- }));
505
-
506
- this.secondaryColumns = this.visibleDays.map((day) => ({
507
- kind: 'day',
508
- day,
509
- key: this.dayKey(day),
510
- title: this.formatDay(day),
511
- }));
512
- }
513
-
514
- // Emit range
515
- const rangeStart = start;
516
- const rangeEnd = this.addDays(start, d);
517
- const view: SchedulerView = 'custom-range';
518
-
519
-
520
- this.emitRangeIfChanged({
521
- start: rangeStart,
522
- end: rangeEnd,
523
- days: d,
524
- primaryAxis: this.primaryAxis,
525
- view,
526
- });
527
- }
528
-
529
- // ------------- NAVIGATION -------------
530
-
531
- navigatePrev() {
532
- const next = this.addDays(this.startOfDay(this.startDate ?? new Date()), -this.normalizedDays());
533
- this.setStartDate(next);
534
- }
535
-
536
- navigateNext() {
537
- const next = this.addDays(this.startOfDay(this.startDate ?? new Date()), this.normalizedDays());
538
- this.setStartDate(next);
539
- }
540
-
541
- navigateToday() {
542
- const today = this.startOfDay(new Date());
543
- this.setStartDate(today);
544
- }
545
-
546
- // ---------- DATE/TIME UTILS ----------
547
-
548
- formatHour(h: number): string {
549
- return `${String(h).padStart(2, '0')}:00`;
550
- }
551
-
552
- hourTopPx(h: number): number {
553
- const minutes = (h * 60) - this.dayStartMinutes;
554
- return minutes * this.pxPerMinute;
555
- }
556
-
557
- isIanaTz(): boolean {
558
- return !!this.timezone && this.timezone !== 'local' && this.timezone !== 'UTC';
559
- }
560
-
561
- toDisplayZone(dUtc: Date): Date {
562
- if (this.timezone === 'UTC') return new Date(dUtc);
563
- if (this.timezone === 'local' || !this.timezone) return new Date(dUtc);
564
-
565
- // Converts a UTC instant to a Date whose wall-clock matches the IANA zone
566
- return toZonedTime(dUtc, this.timezone);
567
- }
568
-
569
- fromDisplayZone(dZoned: Date): Date {
570
- if (this.timezone === 'UTC') return new Date(dZoned);
571
- if (this.timezone === 'local' || !this.timezone) return new Date(dZoned);
572
-
573
- // Converts a wall-clock-in-zone Date back to a UTC instant
574
- return fromZonedTime(dZoned, this.timezone);
575
- }
576
-
577
- private parseHmToMinutes(hm: string, fallback: number): number {
578
- const m = /^(\d{1,2}):(\d{2})$/.exec((hm || '').trim());
579
- if (!m) return fallback;
580
- const hh = Number(m[1]);
581
- const mm = Number(m[2]);
582
- if (!Number.isFinite(hh) || !Number.isFinite(mm)) return fallback;
583
- return Math.max(0, Math.min(24 * 60, hh * 60 + mm));
584
- }
585
-
586
- private startOfDay(d: Date): Date {
587
- const x = new Date(d);
588
- x.setHours(0, 0, 0, 0);
589
- return x;
590
- }
591
-
592
- private addDays(d: Date, n: number): Date {
593
- const x = new Date(d);
594
- x.setDate(x.getDate() + n);
595
- return x;
596
- }
597
-
598
- private setTime(day: Date, minutes: number): Date {
599
- const x = new Date(day);
600
- const hh = Math.floor(minutes / 60);
601
- const mm = minutes % 60;
602
- x.setHours(hh, mm, 0, 0);
603
- return x;
604
- }
605
-
606
- private dayKey(d: Date): string {
607
- // Use YYYY-MM-DD (stable key) instead of full ISO with timezone offsets
608
- const y = d.getFullYear();
609
- const m = String(d.getMonth() + 1).padStart(2, '0');
610
- const day = String(d.getDate()).padStart(2, '0');
611
- return `${y}-${m}-${day}`;
612
- }
613
-
614
- private formatDay(d: Date): string {
615
- return d.toLocaleDateString(this.locale, { weekday: 'short', month: 'short', day: 'numeric' });
616
- }
617
-
618
- private normalizedDays(): number {
619
- const v = Math.floor(this.nDays ?? 7);
620
- return Math.max(1, Math.min(7, v));
621
- }
622
-
623
- // ------------- EVENT TOOLTIP -------------
624
-
625
- eventTooltip(e: SchedulerEvent): string {
626
- const start = this.toDisplayZone(e.start);
627
- const end = this.toDisplayZone(e.end);
628
- const startStr = start.toLocaleTimeString(this.locale, { hour: '2-digit', minute: '2-digit' });
629
- const endStr = end.toLocaleTimeString(this.locale, { hour: '2-digit', minute: '2-digit' });
630
-
631
- // keep it short so the native tooltip looks good
632
- return `${e.title}\n${startStr}–${endStr}`;
633
- }
634
-
635
- // ------------- CUSTOM EVENT STYLING -----------
636
-
637
- eventTemplateCtx(e: PositionedEvent, p: any, s: any): SchedulerEventTemplateContext {
638
- const day = this.resolveCellDay(p, s);
639
- const resourceId = this.resolveCellResourceId(p, s);
640
- return {
641
- $implicit: e,
642
- event: e,
643
- startZoned: this.toDisplayZone(e.start),
644
- endZoned: this.toDisplayZone(e.end),
645
- resourceId,
646
- day,
647
- };
648
- }
649
-
650
- // ------------- TRACK BYS -------------
651
-
652
- trackPrimary = (_: number, c: PrimaryColumn) => c.key;
653
- trackSecondary = (_: number, c: SecondaryColumn) => c.key;
654
- trackEvent = (_: number, e: SchedulerEvent) => e.id;
655
- }
@@ -1,10 +0,0 @@
1
- import { NgModule } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
- import { NgxResourceSchedulerComponent } from './ngx-resource-scheduler.component';
4
-
5
- @NgModule({
6
- imports: [CommonModule],
7
- declarations: [NgxResourceSchedulerComponent],
8
- exports: [NgxResourceSchedulerComponent],
9
- })
10
- export class NgxResourceSchedulerModule {}
@@ -1,9 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
-
3
- @Injectable({
4
- providedIn: 'root'
5
- })
6
- export class NgxResourceSchedulerService {
7
-
8
- constructor() { }
9
- }