@forcecalendar/interface 1.0.10 → 1.0.12
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.
|
@@ -349,11 +349,147 @@ export class ForceCalendar extends BaseComponent {
|
|
|
349
349
|
justify-content: space-between;
|
|
350
350
|
width: 100%;
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
#create-event-btn {
|
|
354
354
|
flex: 1;
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
+
|
|
358
|
+
/* Month View Styles (inline rendering for Locker Service compatibility) */
|
|
359
|
+
.fc-month-view {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
height: 100%;
|
|
363
|
+
background: var(--fc-background);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.fc-month-header {
|
|
367
|
+
display: grid;
|
|
368
|
+
grid-template-columns: repeat(7, 1fr);
|
|
369
|
+
border-bottom: 1px solid var(--fc-border-color);
|
|
370
|
+
background: var(--fc-background-alt);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.fc-month-header-cell {
|
|
374
|
+
padding: 12px 8px;
|
|
375
|
+
text-align: center;
|
|
376
|
+
font-size: 11px;
|
|
377
|
+
font-weight: 600;
|
|
378
|
+
color: var(--fc-text-light);
|
|
379
|
+
text-transform: uppercase;
|
|
380
|
+
letter-spacing: 0.05em;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.fc-month-body {
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
386
|
+
flex: 1;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.fc-month-week {
|
|
390
|
+
display: grid;
|
|
391
|
+
grid-template-columns: repeat(7, 1fr);
|
|
392
|
+
flex: 1;
|
|
393
|
+
min-height: 100px;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.fc-month-day {
|
|
397
|
+
background: var(--fc-background);
|
|
398
|
+
border-right: 1px solid var(--fc-border-color);
|
|
399
|
+
border-bottom: 1px solid var(--fc-border-color);
|
|
400
|
+
padding: 4px;
|
|
401
|
+
min-height: 80px;
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
transition: background-color 0.15s ease;
|
|
404
|
+
display: flex;
|
|
405
|
+
flex-direction: column;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.fc-month-day:hover {
|
|
409
|
+
background: var(--fc-background-hover);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.fc-month-day:last-child {
|
|
413
|
+
border-right: none;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.fc-month-day.other-month {
|
|
417
|
+
background: var(--fc-background-alt);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.fc-month-day.other-month .fc-day-number {
|
|
421
|
+
color: var(--fc-text-light);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.fc-month-day.today {
|
|
425
|
+
background: rgba(37, 99, 235, 0.05);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.fc-month-day.today .fc-day-number {
|
|
429
|
+
background: var(--fc-primary-color);
|
|
430
|
+
color: white;
|
|
431
|
+
border-radius: 50%;
|
|
432
|
+
width: 24px;
|
|
433
|
+
height: 24px;
|
|
434
|
+
display: flex;
|
|
435
|
+
align-items: center;
|
|
436
|
+
justify-content: center;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.fc-day-number {
|
|
440
|
+
font-size: 13px;
|
|
441
|
+
font-weight: 500;
|
|
442
|
+
color: var(--fc-text-color);
|
|
443
|
+
padding: 2px 4px;
|
|
444
|
+
margin-bottom: 4px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.fc-day-events {
|
|
448
|
+
display: flex;
|
|
449
|
+
flex-direction: column;
|
|
450
|
+
gap: 2px;
|
|
451
|
+
flex: 1;
|
|
452
|
+
overflow: hidden;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.fc-event {
|
|
456
|
+
font-size: 11px;
|
|
457
|
+
padding: 2px 6px;
|
|
458
|
+
border-radius: 3px;
|
|
459
|
+
color: white;
|
|
460
|
+
white-space: nowrap;
|
|
461
|
+
overflow: hidden;
|
|
462
|
+
text-overflow: ellipsis;
|
|
463
|
+
cursor: pointer;
|
|
464
|
+
transition: transform 0.1s ease;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.fc-event:hover {
|
|
468
|
+
transform: scale(1.02);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.fc-more-events {
|
|
472
|
+
font-size: 10px;
|
|
473
|
+
color: var(--fc-text-light);
|
|
474
|
+
padding: 2px 4px;
|
|
475
|
+
font-weight: 500;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* Week View Styles (inline rendering for Locker Service compatibility) */
|
|
479
|
+
.fc-week-view {
|
|
480
|
+
display: flex;
|
|
481
|
+
flex-direction: column;
|
|
482
|
+
height: 100%;
|
|
483
|
+
background: var(--fc-background);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Day View Styles (inline rendering for Locker Service compatibility) */
|
|
487
|
+
.fc-day-view {
|
|
488
|
+
display: flex;
|
|
489
|
+
flex-direction: column;
|
|
490
|
+
height: 100%;
|
|
491
|
+
background: var(--fc-background);
|
|
492
|
+
}
|
|
357
493
|
`;
|
|
358
494
|
}
|
|
359
495
|
|
|
@@ -436,30 +572,53 @@ export class ForceCalendar extends BaseComponent {
|
|
|
436
572
|
const container = this.$('#calendar-view-container');
|
|
437
573
|
console.log('[ForceCalendar] afterRender - container:', !!container, 'stateManager:', !!this.stateManager, 'currentView:', this.currentView);
|
|
438
574
|
|
|
575
|
+
// Only create view once per view type change
|
|
439
576
|
if (container && this.stateManager && this.currentView) {
|
|
577
|
+
// Check if container actually has content (render() clears shadow DOM)
|
|
578
|
+
if (this._currentViewInstance && this._currentViewInstance._viewType === this.currentView && container.children.length > 0) {
|
|
579
|
+
console.log('[ForceCalendar] View already exists with content, skipping creation');
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
440
583
|
// Clean up previous view if exists
|
|
441
584
|
if (this._currentViewInstance) {
|
|
442
585
|
if (this._currentViewInstance.cleanup) {
|
|
443
586
|
this._currentViewInstance.cleanup();
|
|
444
587
|
}
|
|
588
|
+
if (this._viewUnsubscribe) {
|
|
589
|
+
this._viewUnsubscribe();
|
|
590
|
+
this._viewUnsubscribe = null;
|
|
591
|
+
}
|
|
445
592
|
}
|
|
446
593
|
|
|
447
594
|
console.log('[ForceCalendar] Creating view for:', this.currentView);
|
|
448
595
|
|
|
449
596
|
// Create a simple view renderer that doesn't use custom elements
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
597
|
+
try {
|
|
598
|
+
const viewRenderer = this._createViewRenderer(this.currentView);
|
|
599
|
+
if (viewRenderer) {
|
|
600
|
+
viewRenderer._viewType = this.currentView;
|
|
601
|
+
this._currentViewInstance = viewRenderer;
|
|
602
|
+
viewRenderer.stateManager = this.stateManager;
|
|
603
|
+
viewRenderer.container = container;
|
|
604
|
+
|
|
605
|
+
console.log('[ForceCalendar] Calling viewRenderer.render()');
|
|
606
|
+
viewRenderer.render();
|
|
607
|
+
console.log('[ForceCalendar] viewRenderer.render() completed');
|
|
608
|
+
|
|
609
|
+
// Subscribe to state changes (store unsubscribe function)
|
|
610
|
+
this._viewUnsubscribe = this.stateManager.subscribe((newState, oldState) => {
|
|
611
|
+
// Only re-render on data changes, not view changes
|
|
612
|
+
if (newState.events !== oldState?.events ||
|
|
613
|
+
newState.currentDate !== oldState?.currentDate) {
|
|
614
|
+
if (viewRenderer && viewRenderer.render) {
|
|
615
|
+
viewRenderer.render();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
} catch (err) {
|
|
621
|
+
console.error('[ForceCalendar] Error creating/rendering view:', err);
|
|
463
622
|
}
|
|
464
623
|
}
|
|
465
624
|
|
|
@@ -510,11 +669,13 @@ export class ForceCalendar extends BaseComponent {
|
|
|
510
669
|
// Create a simple view renderer that bypasses custom elements
|
|
511
670
|
// This is necessary for Salesforce Locker Service compatibility
|
|
512
671
|
const self = this;
|
|
672
|
+
const currentViewName = viewName;
|
|
513
673
|
|
|
514
674
|
return {
|
|
515
675
|
stateManager: null,
|
|
516
676
|
container: null,
|
|
517
677
|
_listeners: [],
|
|
678
|
+
_scrolled: false,
|
|
518
679
|
|
|
519
680
|
cleanup() {
|
|
520
681
|
this._listeners.forEach(({ element, event, handler }) => {
|
|
@@ -532,16 +693,34 @@ export class ForceCalendar extends BaseComponent {
|
|
|
532
693
|
if (!this.container || !this.stateManager) return;
|
|
533
694
|
|
|
534
695
|
const viewData = this.stateManager.getViewData();
|
|
535
|
-
if (!viewData
|
|
536
|
-
this.container.innerHTML = '<div style="padding: 20px; text-align: center;">Loading
|
|
696
|
+
if (!viewData) {
|
|
697
|
+
this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">Loading...</div>';
|
|
537
698
|
return;
|
|
538
699
|
}
|
|
539
700
|
|
|
540
701
|
this.cleanup();
|
|
541
702
|
const config = this.stateManager.getState().config;
|
|
542
|
-
|
|
703
|
+
let html = '';
|
|
704
|
+
|
|
705
|
+
switch (currentViewName) {
|
|
706
|
+
case 'week':
|
|
707
|
+
html = this._renderWeekView(viewData, config);
|
|
708
|
+
break;
|
|
709
|
+
case 'day':
|
|
710
|
+
html = this._renderDayView(viewData, config);
|
|
711
|
+
break;
|
|
712
|
+
case 'month':
|
|
713
|
+
default:
|
|
714
|
+
if (!viewData.weeks) {
|
|
715
|
+
this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No data available for month view.</div>';
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
html = this._renderMonthView(viewData, config);
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
|
|
543
722
|
this.container.innerHTML = html;
|
|
544
|
-
this._attachEventHandlers();
|
|
723
|
+
this._attachEventHandlers(currentViewName);
|
|
545
724
|
},
|
|
546
725
|
|
|
547
726
|
_renderMonthView(viewData, config) {
|
|
@@ -553,54 +732,37 @@ export class ForceCalendar extends BaseComponent {
|
|
|
553
732
|
}
|
|
554
733
|
|
|
555
734
|
let html = `
|
|
556
|
-
<style>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
.fc-month-header-cell { padding: 8px; text-align: left; font-weight: 600; font-size: 10px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
560
|
-
.fc-month-body { flex: 1; display: flex; flex-direction: column; }
|
|
561
|
-
.fc-month-week { flex: 1; display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; }
|
|
562
|
-
.fc-month-week:last-child { border-bottom: none; }
|
|
563
|
-
.fc-month-day { background: #fff; padding: 4px; position: relative; border-right: 1px solid #e5e7eb; min-height: 80px; cursor: pointer; }
|
|
564
|
-
.fc-month-day:last-child { border-right: none; }
|
|
565
|
-
.fc-month-day:hover { background: #f9fafb; }
|
|
566
|
-
.fc-month-day.other-month { background: #f9fafb; }
|
|
567
|
-
.fc-month-day.other-month .fc-day-number { color: #d1d5db; }
|
|
568
|
-
.fc-month-day.today .fc-day-number { background: #ef4444; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
|
|
569
|
-
.fc-day-number { font-size: 12px; font-weight: 500; color: #111827; padding: 4px; }
|
|
570
|
-
.fc-day-events { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
|
|
571
|
-
.fc-event { font-size: 11px; padding: 2px 6px; border-radius: 2px; background: #2563eb; color: white; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
|
|
572
|
-
.fc-event:hover { opacity: 0.9; }
|
|
573
|
-
.fc-more-events { font-size: 10px; color: #6b7280; padding: 2px 4px; cursor: pointer; }
|
|
574
|
-
.fc-more-events:hover { color: #111827; text-decoration: underline; }
|
|
575
|
-
</style>
|
|
576
|
-
<div class="fc-month-view">
|
|
577
|
-
<div class="fc-month-header">
|
|
578
|
-
${dayNames.map(d => `<div class="fc-month-header-cell">${d}</div>`).join('')}
|
|
735
|
+
<div class="fc-month-view" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: #fff; border: 1px solid #e5e7eb;">
|
|
736
|
+
<div class="fc-month-header" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb;">
|
|
737
|
+
${dayNames.map(d => `<div class="fc-month-header-cell" style="padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: #6b7280; text-transform: uppercase;">${d}</div>`).join('')}
|
|
579
738
|
</div>
|
|
580
|
-
<div class="fc-month-body">
|
|
739
|
+
<div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
|
|
581
740
|
`;
|
|
582
741
|
|
|
583
742
|
viewData.weeks.forEach(week => {
|
|
584
|
-
html += '<div class="fc-month-week">';
|
|
743
|
+
html += '<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
|
|
585
744
|
week.days.forEach(day => {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
745
|
+
const isOtherMonth = !day.isCurrentMonth;
|
|
746
|
+
const isToday = day.isToday;
|
|
747
|
+
|
|
748
|
+
const dayBg = isOtherMonth ? '#f3f4f6' : '#fff';
|
|
749
|
+
const dayNumColor = isOtherMonth ? '#9ca3af' : '#111827';
|
|
750
|
+
const todayStyle = isToday ? 'background: #2563eb; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;' : '';
|
|
589
751
|
|
|
590
752
|
const events = day.events || [];
|
|
591
753
|
const visibleEvents = events.slice(0, 3);
|
|
592
754
|
const moreCount = events.length - 3;
|
|
593
755
|
|
|
594
756
|
html += `
|
|
595
|
-
<div class="
|
|
596
|
-
<div class="fc-day-number">${day.dayOfMonth}</div>
|
|
597
|
-
<div class="fc-day-events">
|
|
757
|
+
<div class="fc-month-day" data-date="${day.date}" style="background: ${dayBg}; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; padding: 4px; min-height: 80px; cursor: pointer;">
|
|
758
|
+
<div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${dayNumColor}; padding: 2px 4px; margin-bottom: 4px; ${todayStyle}">${day.dayOfMonth}</div>
|
|
759
|
+
<div class="fc-day-events" style="display: flex; flex-direction: column; gap: 2px;">
|
|
598
760
|
${visibleEvents.map(evt => `
|
|
599
|
-
<div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}">
|
|
761
|
+
<div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}; font-size: 11px; padding: 2px 6px; border-radius: 3px; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;">
|
|
600
762
|
${evt.title}
|
|
601
763
|
</div>
|
|
602
764
|
`).join('')}
|
|
603
|
-
${moreCount > 0 ? `<div class="fc-more-events">+${moreCount} more</div>` : ''}
|
|
765
|
+
${moreCount > 0 ? `<div class="fc-more-events" style="font-size: 10px; color: #6b7280; padding: 2px 4px; font-weight: 500;">+${moreCount} more</div>` : ''}
|
|
604
766
|
</div>
|
|
605
767
|
</div>
|
|
606
768
|
`;
|
|
@@ -612,10 +774,265 @@ export class ForceCalendar extends BaseComponent {
|
|
|
612
774
|
return html;
|
|
613
775
|
},
|
|
614
776
|
|
|
615
|
-
|
|
777
|
+
_renderWeekView(viewData, config) {
|
|
778
|
+
const days = viewData.days || [];
|
|
779
|
+
if (days.length === 0) {
|
|
780
|
+
return '<div style="padding: 20px; text-align: center; color: #666;">No data available for week view.</div>';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const weekStartsOn = config.weekStartsOn || 0;
|
|
784
|
+
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
785
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
786
|
+
|
|
787
|
+
// Process days to add events
|
|
788
|
+
const processedDays = days.map(day => {
|
|
789
|
+
const dayDate = new Date(day.date);
|
|
790
|
+
const events = day.events || [];
|
|
791
|
+
return {
|
|
792
|
+
...day,
|
|
793
|
+
date: dayDate,
|
|
794
|
+
dayName: dayNames[dayDate.getDay()],
|
|
795
|
+
dayOfMonth: dayDate.getDate(),
|
|
796
|
+
isToday: this._isToday(dayDate),
|
|
797
|
+
timedEvents: events.filter(e => !e.allDay),
|
|
798
|
+
allDayEvents: events.filter(e => e.allDay)
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
let html = `
|
|
803
|
+
<div class="fc-week-view" style="display: flex; flex-direction: column; height: 100%; background: #fff; overflow: hidden;">
|
|
804
|
+
<!-- Header -->
|
|
805
|
+
<div style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb; flex-shrink: 0;">
|
|
806
|
+
<div style="border-right: 1px solid #e5e7eb;"></div>
|
|
807
|
+
${processedDays.map(day => `
|
|
808
|
+
<div style="padding: 12px 8px; text-align: center; border-right: 1px solid #e5e7eb;">
|
|
809
|
+
<div style="font-size: 10px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.1em;">${day.dayName}</div>
|
|
810
|
+
<div style="font-size: 16px; font-weight: 500; margin-top: 4px; ${day.isToday ? 'background: #dc2626; color: white; border-radius: 50%; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;' : 'color: #111827;'}">${day.dayOfMonth}</div>
|
|
811
|
+
</div>
|
|
812
|
+
`).join('')}
|
|
813
|
+
</div>
|
|
814
|
+
|
|
815
|
+
<!-- All Day Row -->
|
|
816
|
+
<div style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #fafafa; min-height: 32px; flex-shrink: 0;">
|
|
817
|
+
<div style="font-size: 9px; color: #6b7280; display: flex; align-items: center; justify-content: center; border-right: 1px solid #e5e7eb; text-transform: uppercase; font-weight: 700;">All day</div>
|
|
818
|
+
${processedDays.map(day => `
|
|
819
|
+
<div style="border-right: 1px solid #e5e7eb; padding: 4px; display: flex; flex-direction: column; gap: 2px;">
|
|
820
|
+
${day.allDayEvents.map(evt => `
|
|
821
|
+
<div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}; font-size: 10px; padding: 2px 4px; border-radius: 2px; color: white; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
822
|
+
${evt.title}
|
|
823
|
+
</div>
|
|
824
|
+
`).join('')}
|
|
825
|
+
</div>
|
|
826
|
+
`).join('')}
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<!-- Time Grid Body -->
|
|
830
|
+
<div id="week-scroll-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
|
|
831
|
+
<div style="display: grid; grid-template-columns: 60px repeat(7, 1fr); position: relative; height: 1440px;">
|
|
832
|
+
<!-- Time Gutter -->
|
|
833
|
+
<div style="border-right: 1px solid #e5e7eb; background: #fafafa;">
|
|
834
|
+
${hours.map(h => `
|
|
835
|
+
<div style="height: 60px; font-size: 10px; color: #6b7280; text-align: right; padding-right: 8px; font-weight: 500;">
|
|
836
|
+
${h === 0 ? '' : this._formatHour(h)}
|
|
837
|
+
</div>
|
|
838
|
+
`).join('')}
|
|
839
|
+
</div>
|
|
840
|
+
|
|
841
|
+
<!-- Day Columns -->
|
|
842
|
+
${processedDays.map(day => `
|
|
843
|
+
<div class="fc-week-day-column" data-date="${day.date.toISOString()}" style="border-right: 1px solid #e5e7eb; position: relative; cursor: pointer;">
|
|
844
|
+
<!-- Hour grid lines -->
|
|
845
|
+
${hours.map(() => `<div style="height: 60px; border-bottom: 1px solid #f3f4f6;"></div>`).join('')}
|
|
846
|
+
|
|
847
|
+
<!-- Now indicator for today -->
|
|
848
|
+
${day.isToday ? this._renderNowIndicator() : ''}
|
|
849
|
+
|
|
850
|
+
<!-- Timed events -->
|
|
851
|
+
${day.timedEvents.map(evt => this._renderTimedEvent(evt)).join('')}
|
|
852
|
+
</div>
|
|
853
|
+
`).join('')}
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
`;
|
|
858
|
+
|
|
859
|
+
return html;
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
_renderDayView(viewData, config) {
|
|
863
|
+
// Day view from core has: type, date, dayName, isToday, allDayEvents, hours
|
|
864
|
+
// We need to handle both the core structure and enriched structure
|
|
865
|
+
const currentDate = this.stateManager?.getState()?.currentDate || new Date();
|
|
866
|
+
|
|
867
|
+
let dayDate, dayName, isToday, allDayEvents, timedEvents;
|
|
868
|
+
|
|
869
|
+
if (viewData.type === 'day' && viewData.date) {
|
|
870
|
+
// Core day view structure
|
|
871
|
+
dayDate = new Date(viewData.date);
|
|
872
|
+
dayName = viewData.dayName || ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayDate.getDay()];
|
|
873
|
+
isToday = viewData.isToday !== undefined ? viewData.isToday : this._isToday(dayDate);
|
|
874
|
+
allDayEvents = viewData.allDayEvents || [];
|
|
875
|
+
|
|
876
|
+
// Extract timed events from hours array or get from stateManager
|
|
877
|
+
if (viewData.hours && Array.isArray(viewData.hours)) {
|
|
878
|
+
// Collect unique events from hours (events can span multiple hours)
|
|
879
|
+
const eventMap = new Map();
|
|
880
|
+
viewData.hours.forEach(hour => {
|
|
881
|
+
(hour.events || []).forEach(evt => {
|
|
882
|
+
if (!eventMap.has(evt.id)) {
|
|
883
|
+
eventMap.set(evt.id, evt);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
timedEvents = Array.from(eventMap.values());
|
|
888
|
+
} else {
|
|
889
|
+
timedEvents = [];
|
|
890
|
+
}
|
|
891
|
+
} else if (viewData.days && viewData.days.length > 0) {
|
|
892
|
+
// Enriched structure with days array
|
|
893
|
+
const dayData = viewData.days.find(d => this._isSameDay(new Date(d.date), currentDate)) || viewData.days[0];
|
|
894
|
+
dayDate = new Date(dayData.date);
|
|
895
|
+
dayName = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayDate.getDay()];
|
|
896
|
+
isToday = this._isToday(dayDate);
|
|
897
|
+
const events = dayData.events || [];
|
|
898
|
+
allDayEvents = events.filter(e => e.allDay);
|
|
899
|
+
timedEvents = events.filter(e => !e.allDay);
|
|
900
|
+
} else {
|
|
901
|
+
return '<div style="padding: 20px; text-align: center; color: #666;">No data available for day view.</div>';
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
905
|
+
|
|
906
|
+
let html = `
|
|
907
|
+
<div class="fc-day-view" style="display: flex; flex-direction: column; height: 100%; background: #fff; overflow: hidden;">
|
|
908
|
+
<!-- Header -->
|
|
909
|
+
<div style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid #e5e7eb; background: #f9fafb; flex-shrink: 0;">
|
|
910
|
+
<div style="border-right: 1px solid #e5e7eb;"></div>
|
|
911
|
+
<div style="padding: 16px 24px;">
|
|
912
|
+
<div style="font-size: 12px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.1em;">${dayName}</div>
|
|
913
|
+
<div style="font-size: 24px; font-weight: 600; margin-top: 4px; ${isToday ? 'color: #dc2626;' : 'color: #111827;'}">${dayDate.getDate()}</div>
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
|
|
917
|
+
<!-- All Day Row -->
|
|
918
|
+
<div style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid #e5e7eb; background: #fafafa; min-height: 36px; flex-shrink: 0;">
|
|
919
|
+
<div style="font-size: 9px; color: #6b7280; display: flex; align-items: center; justify-content: center; border-right: 1px solid #e5e7eb; text-transform: uppercase; font-weight: 700;">All day</div>
|
|
920
|
+
<div style="padding: 6px 12px; display: flex; flex-wrap: wrap; gap: 4px;">
|
|
921
|
+
${allDayEvents.map(evt => `
|
|
922
|
+
<div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}; font-size: 12px; padding: 4px 8px; border-radius: 4px; color: white; cursor: pointer; font-weight: 500;">
|
|
923
|
+
${evt.title}
|
|
924
|
+
</div>
|
|
925
|
+
`).join('')}
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
928
|
+
|
|
929
|
+
<!-- Time Grid Body -->
|
|
930
|
+
<div id="day-scroll-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
|
|
931
|
+
<div style="display: grid; grid-template-columns: 60px 1fr; position: relative; height: 1440px;">
|
|
932
|
+
<!-- Time Gutter -->
|
|
933
|
+
<div style="border-right: 1px solid #e5e7eb; background: #fafafa;">
|
|
934
|
+
${hours.map(h => `
|
|
935
|
+
<div style="height: 60px; font-size: 11px; color: #6b7280; text-align: right; padding-right: 12px; font-weight: 500;">
|
|
936
|
+
${h === 0 ? '' : this._formatHour(h)}
|
|
937
|
+
</div>
|
|
938
|
+
`).join('')}
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<!-- Day Column -->
|
|
942
|
+
<div class="fc-day-column" data-date="${dayDate.toISOString()}" style="position: relative; cursor: pointer;">
|
|
943
|
+
<!-- Hour grid lines -->
|
|
944
|
+
${hours.map(() => `<div style="height: 60px; border-bottom: 1px solid #f3f4f6;"></div>`).join('')}
|
|
945
|
+
|
|
946
|
+
<!-- Now indicator for today -->
|
|
947
|
+
${isToday ? this._renderNowIndicator() : ''}
|
|
948
|
+
|
|
949
|
+
<!-- Timed events -->
|
|
950
|
+
${timedEvents.map(evt => this._renderTimedEventDay(evt)).join('')}
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
955
|
+
`;
|
|
956
|
+
|
|
957
|
+
return html;
|
|
958
|
+
},
|
|
959
|
+
|
|
960
|
+
_renderTimedEvent(event) {
|
|
961
|
+
const start = new Date(event.start);
|
|
962
|
+
const end = new Date(event.end);
|
|
963
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
964
|
+
const durationMinutes = Math.max((end - start) / (1000 * 60), 20);
|
|
965
|
+
const color = event.backgroundColor || '#2563eb';
|
|
966
|
+
|
|
967
|
+
return `
|
|
968
|
+
<div class="fc-event" data-event-id="${event.id}"
|
|
969
|
+
style="position: absolute; top: ${startMinutes}px; height: ${durationMinutes}px; left: 2px; right: 2px;
|
|
970
|
+
background-color: ${color}; border-radius: 4px; padding: 4px 8px; font-size: 11px;
|
|
971
|
+
font-weight: 500; color: white; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
972
|
+
cursor: pointer; z-index: 5;">
|
|
973
|
+
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${event.title}</div>
|
|
974
|
+
<div style="font-size: 10px; opacity: 0.9;">${this._formatTime(start)}</div>
|
|
975
|
+
</div>
|
|
976
|
+
`;
|
|
977
|
+
},
|
|
978
|
+
|
|
979
|
+
_renderTimedEventDay(event) {
|
|
980
|
+
const start = new Date(event.start);
|
|
981
|
+
const end = new Date(event.end);
|
|
982
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
983
|
+
const durationMinutes = Math.max((end - start) / (1000 * 60), 30);
|
|
984
|
+
const color = event.backgroundColor || '#2563eb';
|
|
985
|
+
|
|
986
|
+
return `
|
|
987
|
+
<div class="fc-event" data-event-id="${event.id}"
|
|
988
|
+
style="position: absolute; top: ${startMinutes}px; height: ${durationMinutes}px; left: 12px; right: 24px;
|
|
989
|
+
background-color: ${color}; border-radius: 6px; padding: 8px 12px; font-size: 13px;
|
|
990
|
+
font-weight: 500; color: white; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
991
|
+
cursor: pointer; z-index: 5;">
|
|
992
|
+
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${event.title}</div>
|
|
993
|
+
<div style="font-size: 11px; opacity: 0.9;">${this._formatTime(start)} - ${this._formatTime(end)}</div>
|
|
994
|
+
</div>
|
|
995
|
+
`;
|
|
996
|
+
},
|
|
997
|
+
|
|
998
|
+
_renderNowIndicator() {
|
|
999
|
+
const now = new Date();
|
|
1000
|
+
const minutes = now.getHours() * 60 + now.getMinutes();
|
|
1001
|
+
return `<div style="position: absolute; left: 0; right: 0; top: ${minutes}px; height: 2px; background: #dc2626; z-index: 15; pointer-events: none;"></div>`;
|
|
1002
|
+
},
|
|
1003
|
+
|
|
1004
|
+
_formatHour(hour) {
|
|
1005
|
+
const period = hour >= 12 ? 'PM' : 'AM';
|
|
1006
|
+
const displayHour = hour % 12 || 12;
|
|
1007
|
+
return `${displayHour} ${period}`;
|
|
1008
|
+
},
|
|
1009
|
+
|
|
1010
|
+
_formatTime(date) {
|
|
1011
|
+
const hours = date.getHours();
|
|
1012
|
+
const minutes = date.getMinutes();
|
|
1013
|
+
const period = hours >= 12 ? 'PM' : 'AM';
|
|
1014
|
+
const displayHour = hours % 12 || 12;
|
|
1015
|
+
return minutes === 0 ? `${displayHour} ${period}` : `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`;
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
_isToday(date) {
|
|
1019
|
+
const today = new Date();
|
|
1020
|
+
return date.getDate() === today.getDate() &&
|
|
1021
|
+
date.getMonth() === today.getMonth() &&
|
|
1022
|
+
date.getFullYear() === today.getFullYear();
|
|
1023
|
+
},
|
|
1024
|
+
|
|
1025
|
+
_isSameDay(date1, date2) {
|
|
1026
|
+
return date1.getDate() === date2.getDate() &&
|
|
1027
|
+
date1.getMonth() === date2.getMonth() &&
|
|
1028
|
+
date1.getFullYear() === date2.getFullYear();
|
|
1029
|
+
},
|
|
1030
|
+
|
|
1031
|
+
_attachEventHandlers(viewType) {
|
|
616
1032
|
const stateManager = this.stateManager;
|
|
1033
|
+
const self = this;
|
|
617
1034
|
|
|
618
|
-
// Day click handlers
|
|
1035
|
+
// Day click handlers (for month view)
|
|
619
1036
|
this.container.querySelectorAll('.fc-month-day').forEach(dayEl => {
|
|
620
1037
|
this.addListener(dayEl, 'click', (e) => {
|
|
621
1038
|
const date = new Date(dayEl.dataset.date);
|
|
@@ -623,6 +1040,32 @@ export class ForceCalendar extends BaseComponent {
|
|
|
623
1040
|
});
|
|
624
1041
|
});
|
|
625
1042
|
|
|
1043
|
+
// Week view day column click handlers
|
|
1044
|
+
this.container.querySelectorAll('.fc-week-day-column').forEach(dayEl => {
|
|
1045
|
+
this.addListener(dayEl, 'click', (e) => {
|
|
1046
|
+
if (e.target.closest('.fc-event')) return;
|
|
1047
|
+
const date = new Date(dayEl.dataset.date);
|
|
1048
|
+
const rect = dayEl.getBoundingClientRect();
|
|
1049
|
+
const scrollContainer = this.container.querySelector('#week-scroll-container');
|
|
1050
|
+
const y = e.clientY - rect.top + (scrollContainer ? scrollContainer.scrollTop : 0);
|
|
1051
|
+
date.setHours(Math.floor(y / 60), Math.floor(y % 60), 0, 0);
|
|
1052
|
+
stateManager.selectDate(date);
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
// Day view column click handlers
|
|
1057
|
+
this.container.querySelectorAll('.fc-day-column').forEach(dayEl => {
|
|
1058
|
+
this.addListener(dayEl, 'click', (e) => {
|
|
1059
|
+
if (e.target.closest('.fc-event')) return;
|
|
1060
|
+
const date = new Date(dayEl.dataset.date);
|
|
1061
|
+
const rect = dayEl.getBoundingClientRect();
|
|
1062
|
+
const scrollContainer = this.container.querySelector('#day-scroll-container');
|
|
1063
|
+
const y = e.clientY - rect.top + (scrollContainer ? scrollContainer.scrollTop : 0);
|
|
1064
|
+
date.setHours(Math.floor(y / 60), Math.floor(y % 60), 0, 0);
|
|
1065
|
+
stateManager.selectDate(date);
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
|
|
626
1069
|
// Event click handlers
|
|
627
1070
|
this.container.querySelectorAll('.fc-event').forEach(eventEl => {
|
|
628
1071
|
this.addListener(eventEl, 'click', (e) => {
|
|
@@ -634,6 +1077,16 @@ export class ForceCalendar extends BaseComponent {
|
|
|
634
1077
|
}
|
|
635
1078
|
});
|
|
636
1079
|
});
|
|
1080
|
+
|
|
1081
|
+
// Scroll to 8 AM for week and day views
|
|
1082
|
+
if (viewType === 'week' || viewType === 'day') {
|
|
1083
|
+
const scrollContainerId = viewType === 'week' ? '#week-scroll-container' : '#day-scroll-container';
|
|
1084
|
+
const scrollContainer = this.container.querySelector(scrollContainerId);
|
|
1085
|
+
if (scrollContainer && !this._scrolled) {
|
|
1086
|
+
scrollContainer.scrollTop = 8 * 60 - 50;
|
|
1087
|
+
this._scrolled = true;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
637
1090
|
}
|
|
638
1091
|
};
|
|
639
1092
|
}
|