mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2024-10-30 01:08:47 +00:00
(Breaking Change) Removed Calendar app due to FullCalendar's Angular component not being properly developed in the new version
(Breaking Change) Removed Fuse DateRange component (alternative: https://github.com/fetrarij/ngx-daterangepicker-material)
This commit is contained in:
parent
f380d8ca16
commit
e4ebe2fd7e
72
package-lock.json
generated
72
package-lock.json
generated
|
@ -4171,53 +4171,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@fullcalendar/angular": {
|
||||
"version": "4.4.5-beta",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/angular/-/angular-4.4.5-beta.tgz",
|
||||
"integrity": "sha512-L144YrgrgFr75/LGNcDDX9xKy465AZR/EqWPxkdNFgBSeeblH+kf8OMy8K6YcuJDlv4nXw4RucBqbMrrQKvbQw==",
|
||||
"requires": {
|
||||
"@fullcalendar/core": "~4.4.0",
|
||||
"fast-deep-equal": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"@fullcalendar/core": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-4.4.2.tgz",
|
||||
"integrity": "sha512-vq7KQGuAJ1ieFG5tUqwxwUwmXYtblFOTjHaLAVHo6iEPB52mS7DS45VJfkhaQmX4+5/+BHRpg82G1qkuAINwtg=="
|
||||
},
|
||||
"@fullcalendar/daygrid": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-4.4.2.tgz",
|
||||
"integrity": "sha512-axjfMhxEXHShV3r2TZjf+2niJ1C6LdAxkHKmg7mVq4jXtUQHOldU5XsjV0v2lUAt1urJBFi2zajfK8798ukL3Q=="
|
||||
},
|
||||
"@fullcalendar/interaction": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-4.4.2.tgz",
|
||||
"integrity": "sha512-3ItpGFnxcYQT4NClqhq93QTQwOI8x3mlMf5M4DgK5avVaSzpv9g8p+opqeotK2yzpFeINps06cuQyB1h7vcv1Q=="
|
||||
},
|
||||
"@fullcalendar/list": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-4.4.2.tgz",
|
||||
"integrity": "sha512-buhfd0w0PavH3EVZ6DR6kvjb+wPDe16XEpNcPkTpvIxnAziwGBvcUeHUBd9KvtEhOcvs9sAKoYKbU4xwHFK0Wg=="
|
||||
},
|
||||
"@fullcalendar/moment": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/moment/-/moment-4.4.2.tgz",
|
||||
"integrity": "sha512-PBrjxxDEG3RO+8SOA3a1YA7yoGI3bgnltiGY3ehOtJwFIMsUQDSSr5aMoWyRpz7MXgp2YOQY5rzMEIp2A8eK9w=="
|
||||
},
|
||||
"@fullcalendar/rrule": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/rrule/-/rrule-4.4.2.tgz",
|
||||
"integrity": "sha512-pUKHFp62SZbW9X3vvxc8IMnoWpQ6Nt2IBwwPFPAWmebCnUhyDfMf3tpKaV9slUYvW0Cch4Y58tv0EySP27Q2jg=="
|
||||
},
|
||||
"@fullcalendar/timegrid": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-4.4.2.tgz",
|
||||
"integrity": "sha512-M5an7qii8OUmI4ogY47k5pn2j/qUbLp6sa6Vo0gO182HR5pb9YtrEZnoQhnScok+I0BkDkLFzMQoiAMTjBm2PQ==",
|
||||
"requires": {
|
||||
"@fullcalendar/daygrid": "~4.4.0"
|
||||
}
|
||||
},
|
||||
"@gar/promisify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
|
||||
|
@ -7901,7 +7854,8 @@
|
|||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.1.2",
|
||||
|
@ -9773,12 +9727,6 @@
|
|||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"luxon": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
|
||||
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
|
||||
"optional": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
|
@ -12683,22 +12631,6 @@
|
|||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.8.tgz",
|
||||
"integrity": "sha512-cUaXuUPrz9d1wdyzHsBfT1hptKlGgABeCINFXFvulEPqh9Np9BnF3C3lrv9uO54IIr8VDb58tsSF3LhsW+4VRw==",
|
||||
"requires": {
|
||||
"luxon": "^1.21.3",
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
|
@ -25,14 +25,6 @@
|
|||
"@angular/platform-browser": "13.0.0",
|
||||
"@angular/platform-browser-dynamic": "13.0.0",
|
||||
"@angular/router": "13.0.0",
|
||||
"@fullcalendar/angular": "4.4.5-beta",
|
||||
"@fullcalendar/core": "4.4.2",
|
||||
"@fullcalendar/daygrid": "4.4.2",
|
||||
"@fullcalendar/interaction": "4.4.2",
|
||||
"@fullcalendar/list": "4.4.2",
|
||||
"@fullcalendar/moment": "4.4.2",
|
||||
"@fullcalendar/rrule": "4.4.2",
|
||||
"@fullcalendar/timegrid": "4.4.2",
|
||||
"@ngneat/transloco": "3.0.7",
|
||||
"apexcharts": "3.29.0",
|
||||
"crypto-js": "3.3.0",
|
||||
|
@ -44,7 +36,6 @@
|
|||
"ngx-quill": "14.3.0",
|
||||
"perfect-scrollbar": "1.5.3",
|
||||
"quill": "1.3.7",
|
||||
"rrule": "2.6.8",
|
||||
"rxjs": "6.6.7",
|
||||
"tslib": "2.3.1",
|
||||
"zone.js": "0.11.4"
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
<div
|
||||
class="range"
|
||||
(click)="openPickerPanel()"
|
||||
#pickerPanelOrigin>
|
||||
|
||||
<div class="start">
|
||||
<div class="date">{{range.startDate}}</div>
|
||||
<div
|
||||
class="time"
|
||||
*ngIf="range.startTime">{{range.startTime}}</div>
|
||||
</div>
|
||||
|
||||
<div class="separator">-</div>
|
||||
|
||||
<div class="end">
|
||||
<div class="date">{{range.endDate}}</div>
|
||||
<div
|
||||
class="time"
|
||||
*ngIf="range.endTime">{{range.endTime}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #pickerPanel>
|
||||
|
||||
<!-- Start -->
|
||||
<div class="start">
|
||||
|
||||
<div class="month">
|
||||
<div class="month-header">
|
||||
<button
|
||||
class="previous-button"
|
||||
mat-icon-button
|
||||
(click)="prev()"
|
||||
tabindex="1">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:chevron-left'"></mat-icon>
|
||||
</button>
|
||||
<div class="month-label">{{getMonthLabel(1)}}</div>
|
||||
</div>
|
||||
<mat-month-view
|
||||
[(activeDate)]="activeDates.month1"
|
||||
[dateFilter]="dateFilter()"
|
||||
[dateClass]="dateClass()"
|
||||
(click)="$event.stopImmediatePropagation()"
|
||||
(selectedChange)="onSelectedDateChange($event)"
|
||||
#matMonthView1>
|
||||
</mat-month-view>
|
||||
</div>
|
||||
|
||||
<mat-form-field
|
||||
class="fuse-mat-no-subscript time start-time"
|
||||
*ngIf="timeRange">
|
||||
<input
|
||||
matInput
|
||||
[autocomplete]="'off'"
|
||||
[formControl]="startTimeFormControl"
|
||||
(blur)="updateStartTime($event)"
|
||||
tabindex="3">
|
||||
<mat-label>Start time</mat-label>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- End -->
|
||||
<div class="end">
|
||||
|
||||
<div class="month">
|
||||
<div class="month-header">
|
||||
<div class="month-label">{{getMonthLabel(2)}}</div>
|
||||
<button
|
||||
class="next-button"
|
||||
mat-icon-button
|
||||
(click)="next()"
|
||||
tabindex="2">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:chevron-right'"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-month-view
|
||||
[(activeDate)]="activeDates.month2"
|
||||
[dateFilter]="dateFilter()"
|
||||
[dateClass]="dateClass()"
|
||||
(click)="$event.stopImmediatePropagation()"
|
||||
(selectedChange)="onSelectedDateChange($event)"
|
||||
#matMonthView2>
|
||||
</mat-month-view>
|
||||
</div>
|
||||
|
||||
<mat-form-field
|
||||
class="fuse-mat-no-subscript time end-time"
|
||||
*ngIf="timeRange">
|
||||
<input
|
||||
matInput
|
||||
[formControl]="endTimeFormControl"
|
||||
(blur)="updateEndTime($event)"
|
||||
tabindex="4">
|
||||
<mat-label>End time</mat-label>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-template>
|
|
@ -1,292 +0,0 @@
|
|||
/* Variables */
|
||||
$body-cell-padding: 2px;
|
||||
|
||||
fuse-date-range {
|
||||
display: flex;
|
||||
|
||||
.range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
max-height: 48px;
|
||||
cursor: pointer;
|
||||
|
||||
.start,
|
||||
.end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
border-radius: 6px;
|
||||
border-width: 1px;
|
||||
line-height: 1;
|
||||
@apply shadow-sm border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500;
|
||||
|
||||
.date {
|
||||
white-space: nowrap;
|
||||
|
||||
+ .time {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 2px;
|
||||
|
||||
@screen sm {
|
||||
margin: 0 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-date-range-panel {
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
@apply shadow-2xl bg-card;
|
||||
|
||||
.start,
|
||||
.end {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.month {
|
||||
max-width: 196px;
|
||||
min-width: 196px;
|
||||
width: 196px;
|
||||
|
||||
.month-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.previous-button,
|
||||
.next-button {
|
||||
position: absolute;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
min-height: 24px !important;
|
||||
max-height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
|
||||
.mat-icon {
|
||||
@apply icon-size-5;
|
||||
}
|
||||
}
|
||||
|
||||
.previous-button {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.next-button {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.month-label {
|
||||
font-weight: 500;
|
||||
@apply text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
mat-month-view {
|
||||
display: flex;
|
||||
min-height: 188px;
|
||||
|
||||
.mat-calendar-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
tbody {
|
||||
|
||||
tr {
|
||||
|
||||
&[aria-hidden=true] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
||||
td:first-child {
|
||||
|
||||
&[aria-hidden=true] {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td,
|
||||
td:hover {
|
||||
|
||||
&.fuse-date-range {
|
||||
|
||||
&:before {
|
||||
@apply bg-primary-200;
|
||||
}
|
||||
|
||||
.mat-calendar-body-cell-content {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.fuse-date-range-start,
|
||||
&.fuse-date-range-end {
|
||||
|
||||
.mat-calendar-body-cell-content {
|
||||
@apply bg-primary text-on-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-calendar-body-today {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td.mat-calendar-body-cell {
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
padding: $body-cell-padding !important;
|
||||
|
||||
&.fuse-date-range {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: $body-cell-padding;
|
||||
right: 0;
|
||||
bottom: $body-cell-padding;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.fuse-date-range-start {
|
||||
|
||||
&:before {
|
||||
left: $body-cell-padding;
|
||||
border-radius: 999px 0 0 999px;
|
||||
}
|
||||
|
||||
&.fuse-date-range-end,
|
||||
&:last-child {
|
||||
|
||||
&:before {
|
||||
right: $body-cell-padding;
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fuse-date-range-end {
|
||||
|
||||
&:before {
|
||||
right: $body-cell-padding;
|
||||
border-radius: 0 999px 999px 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
||||
&:before {
|
||||
left: $body-cell-padding;
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
||||
&:before {
|
||||
border-radius: 999px 0 0 999px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
||||
&:before {
|
||||
border-radius: 0 999px 999px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-calendar-body-cell-content {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
td.mat-calendar-body-label {
|
||||
|
||||
+ td.mat-calendar-body-cell {
|
||||
|
||||
&.fuse-date-range {
|
||||
|
||||
&:before {
|
||||
border-radius: 999px 0 0 999px;
|
||||
}
|
||||
|
||||
&.fuse-date-range-start {
|
||||
|
||||
&.fuse-date-range-end {
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fuse-date-range-end {
|
||||
|
||||
&:before {
|
||||
left: $body-cell-padding;
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
width: 100%;
|
||||
max-width: 196px;
|
||||
}
|
||||
}
|
||||
|
||||
.start {
|
||||
align-items: flex-start;
|
||||
margin-right: 20px;
|
||||
|
||||
.month {
|
||||
|
||||
.month-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.end {
|
||||
align-items: flex-end;
|
||||
margin-left: 20px;
|
||||
|
||||
.month {
|
||||
|
||||
.month-label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,682 +0,0 @@
|
|||
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, HostBinding, Input, OnDestroy, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import { Overlay } from '@angular/cdk/overlay';
|
||||
import { TemplatePortal } from '@angular/cdk/portal';
|
||||
import { MatCalendarCellCssClasses, MatMonthView } from '@angular/material/datepicker';
|
||||
import { Subject } from 'rxjs';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-date-range',
|
||||
templateUrl : './date-range.component.html',
|
||||
styleUrls : ['./date-range.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs : 'fuseDateRange',
|
||||
providers : [
|
||||
{
|
||||
provide : NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => FuseDateRangeComponent),
|
||||
multi : true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class FuseDateRangeComponent implements ControlValueAccessor, OnInit, OnDestroy
|
||||
{
|
||||
@Output() readonly rangeChanged: EventEmitter<{ start: string; end: string }> = new EventEmitter<{ start: string; end: string }>();
|
||||
@ViewChild('matMonthView1') private _matMonthView1: MatMonthView<any>;
|
||||
@ViewChild('matMonthView2') private _matMonthView2: MatMonthView<any>;
|
||||
@ViewChild('pickerPanelOrigin', {read: ElementRef}) private _pickerPanelOrigin: ElementRef;
|
||||
@ViewChild('pickerPanel') private _pickerPanel: TemplateRef<any>;
|
||||
@HostBinding('class.fuse-date-range') private _defaultClassNames = true;
|
||||
|
||||
activeDates: { month1: Moment | null; month2: Moment | null } = {
|
||||
month1: null,
|
||||
month2: null
|
||||
};
|
||||
setWhichDate: 'start' | 'end' = 'start';
|
||||
startTimeFormControl: FormControl;
|
||||
endTimeFormControl: FormControl;
|
||||
private _dateFormat: string;
|
||||
private _onChange: (value: any) => void;
|
||||
private _onTouched: (value: any) => void;
|
||||
private _programmaticChange!: boolean;
|
||||
private _range: { start: Moment | null; end: Moment | null } = {
|
||||
start: null,
|
||||
end : null
|
||||
};
|
||||
private _timeFormat: string;
|
||||
private _timeRange: boolean;
|
||||
private readonly _timeRegExp: RegExp = new RegExp('^(0[0-9]|1[0-9]|2[0-4]|[0-9]):([0-5][0-9])(A|(?:AM)|P|(?:PM))?$', 'i');
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef,
|
||||
private _overlay: Overlay,
|
||||
private _renderer2: Renderer2,
|
||||
private _viewContainerRef: ViewContainerRef
|
||||
)
|
||||
{
|
||||
this._onChange = (): void => {
|
||||
};
|
||||
this._onTouched = (): void => {
|
||||
};
|
||||
this.dateFormat = 'DD/MM/YYYY';
|
||||
this.timeFormat = '12';
|
||||
|
||||
// Initialize the component
|
||||
this._init();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Setter & getter for dateFormat input
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Input()
|
||||
set dateFormat(value: string)
|
||||
{
|
||||
// Return if the values are the same
|
||||
if ( this._dateFormat === value )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the value
|
||||
this._dateFormat = value;
|
||||
}
|
||||
|
||||
get dateFormat(): string
|
||||
{
|
||||
return this._dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter & getter for timeFormat input
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Input()
|
||||
set timeFormat(value: string)
|
||||
{
|
||||
// Return if the values are the same
|
||||
if ( this._timeFormat === value )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set format based on the time format input
|
||||
this._timeFormat = value === '12' ? 'hh:mmA' : 'HH:mm';
|
||||
}
|
||||
|
||||
get timeFormat(): string
|
||||
{
|
||||
return this._timeFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter & getter for timeRange input
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Input()
|
||||
set timeRange(value: boolean)
|
||||
{
|
||||
// Return if the values are the same
|
||||
if ( this._timeRange === value )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the value
|
||||
this._timeRange = value;
|
||||
|
||||
// If the time range turned off...
|
||||
if ( !value )
|
||||
{
|
||||
this.range = {
|
||||
start: this._range.start.clone().startOf('day'),
|
||||
end : this._range.end.clone().endOf('day')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get timeRange(): boolean
|
||||
{
|
||||
return this._timeRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter & getter for range input
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Input()
|
||||
set range(value)
|
||||
{
|
||||
if ( !value )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the value is an object and has 'start' and 'end' values
|
||||
if ( !value.start || !value.end )
|
||||
{
|
||||
console.error('Range input must have "start" and "end" properties!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are setting an individual date or both of them
|
||||
const whichDate = value.whichDate || null;
|
||||
|
||||
// Get the start and end dates as moment
|
||||
const start = moment(value.start);
|
||||
const end = moment(value.end);
|
||||
|
||||
// If we are only setting the start date...
|
||||
if ( whichDate === 'start' )
|
||||
{
|
||||
// Set the start date
|
||||
this._range.start = start.clone();
|
||||
|
||||
// If the selected start date is after the end date...
|
||||
if ( this._range.start.isAfter(this._range.end) )
|
||||
{
|
||||
// Set the end date to the start date but keep the end date's time
|
||||
const endDate = start.clone().hours(this._range.end.hours()).minutes(this._range.end.minutes()).seconds(this._range.end.seconds());
|
||||
|
||||
// Test this new end date to see if it's ahead of the start date
|
||||
if ( this._range.start.isBefore(endDate) )
|
||||
{
|
||||
// If it's, set the new end date
|
||||
this._range.end = endDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, set the end date same as the start date
|
||||
this._range.end = start.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are only setting the end date...
|
||||
if ( whichDate === 'end' )
|
||||
{
|
||||
// Set the end date
|
||||
this._range.end = end.clone();
|
||||
|
||||
// If the selected end date is before the start date...
|
||||
if ( this._range.start.isAfter(this._range.end) )
|
||||
{
|
||||
// Set the start date to the end date but keep the start date's time
|
||||
const startDate = end.clone().hours(this._range.start.hours()).minutes(this._range.start.minutes()).seconds(this._range.start.seconds());
|
||||
|
||||
// Test this new end date to see if it's ahead of the start date
|
||||
if ( this._range.end.isAfter(startDate) )
|
||||
{
|
||||
// If it's, set the new start date
|
||||
this._range.start = startDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, set the start date same as the end date
|
||||
this._range.start = end.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are setting both dates...
|
||||
if ( !whichDate )
|
||||
{
|
||||
// Set the start date
|
||||
this._range.start = start.clone();
|
||||
|
||||
// If the start date is before the end date, set the end date as normal.
|
||||
// If the start date is after the end date, set the end date same as the start date.
|
||||
this._range.end = start.isBefore(end) ? end.clone() : start.clone();
|
||||
}
|
||||
|
||||
// Prepare another range object that holds the ISO formatted range dates
|
||||
const range = {
|
||||
start: this._range.start.clone().toISOString(),
|
||||
end : this._range.end.clone().toISOString()
|
||||
};
|
||||
|
||||
// Emit the range changed event with the range
|
||||
this.rangeChanged.emit(range);
|
||||
|
||||
// Update the model with the range if the change was not a programmatic change
|
||||
// Because programmatic changes trigger writeValue which triggers onChange and onTouched
|
||||
// internally causing them to trigger twice which breaks the form's pristine and touched
|
||||
// statuses.
|
||||
if ( !this._programmaticChange )
|
||||
{
|
||||
this._onTouched(range);
|
||||
this._onChange(range);
|
||||
}
|
||||
|
||||
// Set the active dates
|
||||
this.activeDates = {
|
||||
month1: this._range.start.clone(),
|
||||
month2: this._range.start.clone().add(1, 'month')
|
||||
};
|
||||
|
||||
// Set the time form controls
|
||||
this.startTimeFormControl.setValue(this._range.start.clone().format(this._timeFormat).toString());
|
||||
this.endTimeFormControl.setValue(this._range.end.clone().format(this._timeFormat).toString());
|
||||
|
||||
// Run ngAfterContentInit on month views to trigger
|
||||
// re-render on month views if they are available
|
||||
if ( this._matMonthView1 && this._matMonthView2 )
|
||||
{
|
||||
this._matMonthView1.ngAfterContentInit();
|
||||
this._matMonthView2.ngAfterContentInit();
|
||||
}
|
||||
|
||||
// Reset the programmatic change status
|
||||
this._programmaticChange = false;
|
||||
}
|
||||
|
||||
get range(): any
|
||||
{
|
||||
// Clone the range start and end
|
||||
const start = this._range.start.clone();
|
||||
const end = this._range.end.clone();
|
||||
|
||||
// Build and return the range object
|
||||
return {
|
||||
startDate: start.clone().format(this.dateFormat),
|
||||
startTime: this.timeRange ? start.clone().format(this.timeFormat) : null,
|
||||
endDate : end.clone().format(this.dateFormat),
|
||||
endTime : this.timeRange ? end.clone().format(this.timeFormat) : null
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Control Value Accessor
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update the form model on change
|
||||
*
|
||||
* @param fn
|
||||
*/
|
||||
registerOnChange(fn: any): void
|
||||
{
|
||||
this._onChange = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the form model on blur
|
||||
*
|
||||
* @param fn
|
||||
*/
|
||||
registerOnTouched(fn: any): void
|
||||
{
|
||||
this._onTouched = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to view from model when the form model changes programmatically
|
||||
*
|
||||
* @param range
|
||||
*/
|
||||
writeValue(range: { start: string; end: string }): void
|
||||
{
|
||||
// Set this change as a programmatic one
|
||||
this._programmaticChange = true;
|
||||
|
||||
// Set the range
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void
|
||||
{
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next();
|
||||
this._unsubscribeAll.complete();
|
||||
|
||||
// @ TODO: Workaround until "angular/issues/20007" resolved
|
||||
this.writeValue = (): void => {
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open the picker panel
|
||||
*/
|
||||
openPickerPanel(): void
|
||||
{
|
||||
// Create the overlay
|
||||
const overlayRef = this._overlay.create({
|
||||
panelClass : 'fuse-date-range-panel',
|
||||
backdropClass : '',
|
||||
hasBackdrop : true,
|
||||
scrollStrategy : this._overlay.scrollStrategies.reposition(),
|
||||
positionStrategy: this._overlay.position()
|
||||
.flexibleConnectedTo(this._pickerPanelOrigin)
|
||||
.withPositions([
|
||||
{
|
||||
originX : 'start',
|
||||
originY : 'bottom',
|
||||
overlayX: 'start',
|
||||
overlayY: 'top',
|
||||
offsetY : 8
|
||||
},
|
||||
{
|
||||
originX : 'start',
|
||||
originY : 'top',
|
||||
overlayX: 'start',
|
||||
overlayY: 'bottom',
|
||||
offsetY : -8
|
||||
}
|
||||
])
|
||||
});
|
||||
|
||||
// Create a portal from the template
|
||||
const templatePortal = new TemplatePortal(this._pickerPanel, this._viewContainerRef);
|
||||
|
||||
// On backdrop click
|
||||
overlayRef.backdropClick().subscribe(() => {
|
||||
|
||||
// If template portal exists and attached...
|
||||
if ( templatePortal && templatePortal.isAttached )
|
||||
{
|
||||
// Detach it
|
||||
templatePortal.detach();
|
||||
}
|
||||
|
||||
// If overlay exists and attached...
|
||||
if ( overlayRef && overlayRef.hasAttached() )
|
||||
{
|
||||
// Detach it
|
||||
overlayRef.detach();
|
||||
overlayRef.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Attach the portal to the overlay
|
||||
overlayRef.attach(templatePortal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month label
|
||||
*
|
||||
* @param month
|
||||
*/
|
||||
getMonthLabel(month: number): string
|
||||
{
|
||||
if ( month === 1 )
|
||||
{
|
||||
return this.activeDates.month1.clone().format('MMMM Y');
|
||||
}
|
||||
|
||||
return this.activeDates.month2.clone().format('MMMM Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Date class function to add/remove class names to calendar days
|
||||
*/
|
||||
dateClass(): any
|
||||
{
|
||||
return (date: Moment): MatCalendarCellCssClasses => {
|
||||
|
||||
// If the date is both start and end date...
|
||||
if ( date.isSame(this._range.start, 'day') && date.isSame(this._range.end, 'day') )
|
||||
{
|
||||
return ['fuse-date-range', 'fuse-date-range-start', 'fuse-date-range-end'];
|
||||
}
|
||||
|
||||
// If the date is the start date...
|
||||
if ( date.isSame(this._range.start, 'day') )
|
||||
{
|
||||
return ['fuse-date-range', 'fuse-date-range-start'];
|
||||
}
|
||||
|
||||
// If the date is the end date...
|
||||
if ( date.isSame(this._range.end, 'day') )
|
||||
{
|
||||
return ['fuse-date-range', 'fuse-date-range-end'];
|
||||
}
|
||||
|
||||
// If the date is in between start and end dates...
|
||||
if ( date.isBetween(this._range.start, this._range.end, 'day') )
|
||||
{
|
||||
return ['fuse-date-range', 'fuse-date-range-mid'];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Date filter to enable/disable calendar days
|
||||
*/
|
||||
dateFilter(): any
|
||||
{
|
||||
// If we are selecting the end date, disable all the dates that comes before the start date
|
||||
return (date: Moment): boolean => !(this.setWhichDate === 'end' && date.isBefore(this._range.start, 'day'));
|
||||
}
|
||||
|
||||
/**
|
||||
* On selected date change
|
||||
*
|
||||
* @param date
|
||||
*/
|
||||
onSelectedDateChange(date: Moment): void
|
||||
{
|
||||
// Create a new range object
|
||||
const newRange = {
|
||||
start : this._range.start.clone().toISOString(),
|
||||
end : this._range.end.clone().toISOString(),
|
||||
whichDate: null
|
||||
};
|
||||
|
||||
// Replace either the start or the end date with the new one
|
||||
// depending on which date we are setting
|
||||
if ( this.setWhichDate === 'start' )
|
||||
{
|
||||
newRange.start = moment(newRange.start).year(date.year()).month(date.month()).date(date.date()).toISOString();
|
||||
}
|
||||
else
|
||||
{
|
||||
newRange.end = moment(newRange.end).year(date.year()).month(date.month()).date(date.date()).toISOString();
|
||||
}
|
||||
|
||||
// Append the which date to the new range object
|
||||
newRange.whichDate = this.setWhichDate;
|
||||
|
||||
// Switch which date to set on the next run
|
||||
this.setWhichDate = this.setWhichDate === 'start' ? 'end' : 'start';
|
||||
|
||||
// Set the range
|
||||
this.range = newRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous month on both views
|
||||
*/
|
||||
prev(): void
|
||||
{
|
||||
this.activeDates.month1 = moment(this.activeDates.month1).subtract(1, 'month');
|
||||
this.activeDates.month2 = moment(this.activeDates.month2).subtract(1, 'month');
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to next month on both views
|
||||
*/
|
||||
next(): void
|
||||
{
|
||||
this.activeDates.month1 = moment(this.activeDates.month1).add(1, 'month');
|
||||
this.activeDates.month2 = moment(this.activeDates.month2).add(1, 'month');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the start time
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
updateStartTime(event): void
|
||||
{
|
||||
// Parse the time
|
||||
const parsedTime = this._parseTime(event.target.value);
|
||||
|
||||
// Go back to the previous value if the form control is not valid
|
||||
if ( this.startTimeFormControl.invalid )
|
||||
{
|
||||
// Override the time
|
||||
const time = this._range.start.clone().format(this._timeFormat);
|
||||
|
||||
// Set the time
|
||||
this.startTimeFormControl.setValue(time);
|
||||
|
||||
// Do not update the range
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the new time to the start date
|
||||
const startDate = this._range.start.clone().hours(parsedTime.hours()).minutes(parsedTime.minutes());
|
||||
|
||||
// If the new start date is after the current end date,
|
||||
// use the end date's time and set the start date again
|
||||
if ( startDate.isAfter(this._range.end) )
|
||||
{
|
||||
const endDateHours = this._range.end.hours();
|
||||
const endDateMinutes = this._range.end.minutes();
|
||||
|
||||
// Set the start date
|
||||
startDate.hours(endDateHours).minutes(endDateMinutes);
|
||||
}
|
||||
|
||||
// If everything is okay, set the new date
|
||||
this.range = {
|
||||
start : startDate.toISOString(),
|
||||
end : this._range.end.clone().toISOString(),
|
||||
whichDate: 'start'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the end time
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
updateEndTime(event): void
|
||||
{
|
||||
// Parse the time
|
||||
const parsedTime = this._parseTime(event.target.value);
|
||||
|
||||
// Go back to the previous value if the form control is not valid
|
||||
if ( this.endTimeFormControl.invalid )
|
||||
{
|
||||
// Override the time
|
||||
const time = this._range.end.clone().format(this._timeFormat);
|
||||
|
||||
// Set the time
|
||||
this.endTimeFormControl.setValue(time);
|
||||
|
||||
// Do not update the range
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the new time to the end date
|
||||
const endDate = this._range.end.clone().hours(parsedTime.hours()).minutes(parsedTime.minutes());
|
||||
|
||||
// If the new end date is before the current start date,
|
||||
// use the start date's time and set the end date again
|
||||
if ( endDate.isBefore(this._range.start) )
|
||||
{
|
||||
const startDateHours = this._range.start.hours();
|
||||
const startDateMinutes = this._range.start.minutes();
|
||||
|
||||
// Set the end date
|
||||
endDate.hours(startDateHours).minutes(startDateMinutes);
|
||||
}
|
||||
|
||||
// If everything is okay, set the new date
|
||||
this.range = {
|
||||
start : this._range.start.clone().toISOString(),
|
||||
end : endDate.toISOString(),
|
||||
whichDate: 'end'
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _init(): void
|
||||
{
|
||||
// Start and end time form controls
|
||||
this.startTimeFormControl = new FormControl('', [Validators.pattern(this._timeRegExp)]);
|
||||
this.endTimeFormControl = new FormControl('', [Validators.pattern(this._timeRegExp)]);
|
||||
|
||||
// Set the default range
|
||||
this._programmaticChange = true;
|
||||
this.range = {
|
||||
start: moment().startOf('day').toISOString(),
|
||||
end : moment().add(1, 'day').endOf('day').toISOString()
|
||||
};
|
||||
|
||||
// Set the default time range
|
||||
this._programmaticChange = true;
|
||||
this.timeRange = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the time from the inputs
|
||||
*
|
||||
* @param value
|
||||
* @private
|
||||
*/
|
||||
private _parseTime(value: string): Moment
|
||||
{
|
||||
// Parse the time using the time regexp
|
||||
const timeArr = value.split(this._timeRegExp).filter(part => part !== '');
|
||||
|
||||
// Get the meridiem
|
||||
const meridiem = timeArr[2] || null;
|
||||
|
||||
// If meridiem exists...
|
||||
if ( meridiem )
|
||||
{
|
||||
// Create a moment using 12-hours format and return it
|
||||
return moment(value, 'hh:mmA').seconds(0);
|
||||
}
|
||||
|
||||
// If meridiem doesn't exist, create a moment using 24-hours format and return in
|
||||
return moment(value, 'HH:mm').seconds(0);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMomentDateModule } from '@angular/material-moment-adapter';
|
||||
import { FuseDateRangeComponent } from '@fuse/components/date-range/date-range.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseDateRangeComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatDatepickerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatMomentDateModule
|
||||
],
|
||||
exports : [
|
||||
FuseDateRangeComponent
|
||||
]
|
||||
})
|
||||
export class FuseDateRangeModule
|
||||
{
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from '@fuse/components/date-range/public-api';
|
|
@ -1,2 +0,0 @@
|
|||
export * from '@fuse/components/date-range/date-range.component';
|
||||
export * from '@fuse/components/date-range/date-range.module';
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
/* 3. Overrides */
|
||||
@import 'overrides/angular-material';
|
||||
@import 'overrides/fullcalendar';
|
||||
@import 'overrides/highlightjs';
|
||||
@import 'overrides/perfect-scrollbar';
|
||||
@import 'overrides/quill';
|
||||
|
|
|
@ -1,680 +0,0 @@
|
|||
/* ----------------------------------------------------------------------------------------------------- */
|
||||
/* @ FullCalendar overrides
|
||||
/* ----------------------------------------------------------------------------------------------------- */
|
||||
.fc {
|
||||
|
||||
.fc-view-container {
|
||||
|
||||
/* Day Grid - Month view */
|
||||
.fc-view.fc-dayGridMonth-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-day-header {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
@apply text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-week {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-day {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-day-top {
|
||||
text-align: center;
|
||||
|
||||
&.fc-other-month {
|
||||
opacity: 1;
|
||||
|
||||
.fc-day-number {
|
||||
@apply text-hint;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-today {
|
||||
|
||||
.fc-day-number {
|
||||
@apply bg-primary text-on-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-day-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 21px;
|
||||
margin: 4px 0;
|
||||
font-size: 12px;
|
||||
border-radius: 50%;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 4px 6px;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
@screen sm {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-more {
|
||||
padding: 0 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
@apply text-secondary;
|
||||
|
||||
@screen sm {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-highlight-skeleton {
|
||||
|
||||
.fc-highlight {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-popover {
|
||||
@apply bg-card;
|
||||
|
||||
&.fc-more-popover {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
@apply shadow-2xl;
|
||||
|
||||
.fc-header {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
padding: 0 8px;
|
||||
@apply bg-hover;
|
||||
|
||||
.fc-title {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
max-height: 160px;
|
||||
overflow: hidden auto;
|
||||
|
||||
.fc-event-container {
|
||||
padding: 8px;
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 0 6px 0;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Grid - Week view */
|
||||
.fc-view.fc-timeGridWeek-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
@apply text-secondary;
|
||||
|
||||
&.fc-weekday {
|
||||
padding-top: 16px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.fc-date {
|
||||
padding-bottom: 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-row {
|
||||
min-height: 0;
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-day {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
padding-bottom: 0;
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
border: none;
|
||||
background: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border: none;
|
||||
width: 48px !important;
|
||||
|
||||
+ .fc-day {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-day {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-time {
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-widget-content {
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
margin: 0 12px 0 0;
|
||||
|
||||
.fc-time-grid-event {
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
.fc-time,
|
||||
.fc-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Grid - Day view */
|
||||
.fc-view.fc-timeGridDay-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
@apply text-secondary;
|
||||
|
||||
&.fc-weekday {
|
||||
padding-top: 16px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.fc-date {
|
||||
padding-bottom: 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-day {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-row {
|
||||
min-height: 0;
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
padding-bottom: 0;
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
border: none;
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-day {
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-axis {
|
||||
border: none;
|
||||
width: 48px !important;
|
||||
|
||||
+ .fc-day {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-time {
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
|
||||
.fc-widget-content {
|
||||
border-color: var(--fuse-divider);
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
margin: 0 12px 0 0;
|
||||
|
||||
.fc-time-grid-event {
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
.fc-time,
|
||||
.fc-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List - Year view */
|
||||
.fc-view.fc-listYear-view {
|
||||
border: none;
|
||||
|
||||
.fc-list-table {
|
||||
|
||||
.fc-list-heading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fc-list-item {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
|
||||
td {
|
||||
@apply bg-hover;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
padding: 0 8px;
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: var(--fuse-divider);
|
||||
|
||||
&.fc-list-item-date {
|
||||
order: 1;
|
||||
padding-left: 16px;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
|
||||
@screen sm {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
span {
|
||||
|
||||
&:first-child {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: 2px;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
max-width: 32px;
|
||||
font-size: 18px;
|
||||
|
||||
@screen sm {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
+ span {
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
@apply text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-time {
|
||||
flex: 0 0 auto;
|
||||
order: 3;
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
max-width: 120px;
|
||||
|
||||
@screen sm {
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-marker {
|
||||
flex: 0 0 auto;
|
||||
order: 2;
|
||||
|
||||
.fc-event-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-title {
|
||||
flex: 1 1 auto;
|
||||
order: 4;
|
||||
padding-right: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Day grid event - Dragging */
|
||||
.fc-day-grid-event {
|
||||
|
||||
&.fc-dragging,
|
||||
&.fc-resizing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 4px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -86,7 +86,6 @@ export const appRoutes: Route[] = [
|
|||
// Apps
|
||||
{path: 'apps', children: [
|
||||
{path: 'academy', loadChildren: () => import('app/modules/admin/apps/academy/academy.module').then(m => m.AcademyModule)},
|
||||
{path: 'calendar', loadChildren: () => import('app/modules/admin/apps/calendar/calendar.module').then(m => m.CalendarModule)},
|
||||
{path: 'chat', loadChildren: () => import('app/modules/admin/apps/chat/chat.module').then(m => m.ChatModule)},
|
||||
{path: 'contacts', loadChildren: () => import('app/modules/admin/apps/contacts/contacts.module').then(m => m.ContactsModule)},
|
||||
{path: 'ecommerce', loadChildren: () => import('app/modules/admin/apps/ecommerce/ecommerce.module').then(m => m.ECommerceModule)},
|
||||
|
|
|
@ -1,624 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { assign, cloneDeep, omit } from 'lodash-es';
|
||||
import * as moment from 'moment';
|
||||
import RRule, { RRuleSet, rrulestr } from 'rrule';
|
||||
import { FuseMockApiService, FuseMockApiUtils } from '@fuse/lib/mock-api';
|
||||
import { calendars as calendarsData, events as eventsData, exceptions as exceptionsData, settings as settingsData, weekdays as weekdaysData } from 'app/mock-api/apps/calendar/data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CalendarMockApi
|
||||
{
|
||||
private _calendars: any[] = calendarsData;
|
||||
private _events: any[] = eventsData;
|
||||
private _exceptions: any[] = exceptionsData;
|
||||
private _settings: any = settingsData;
|
||||
private _weekdays: any = weekdaysData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseMockApiService: FuseMockApiService)
|
||||
{
|
||||
// Register Mock API handlers
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register Mock API handlers
|
||||
*/
|
||||
registerHandlers(): void
|
||||
{
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Calendars - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/apps/calendar/calendars')
|
||||
.reply(() => {
|
||||
|
||||
// Clone the calendars
|
||||
const calendars = cloneDeep(this._calendars);
|
||||
|
||||
// Return the response
|
||||
return [200, calendars];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Calendars - POST
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPost('api/apps/calendar/calendars')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the calendar as the new calendar
|
||||
const newCalendar = cloneDeep(request.body.calendar);
|
||||
|
||||
// Add an id to the new calendar
|
||||
newCalendar.id = FuseMockApiUtils.guid();
|
||||
|
||||
// Push the new calendar
|
||||
this._calendars.push(newCalendar);
|
||||
|
||||
// Return the response
|
||||
return [200, newCalendar];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Calendars - PATCH
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPatch('api/apps/calendar/calendars')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the id and calendar
|
||||
const id = request.body.id;
|
||||
const calendar = cloneDeep(request.body.calendar);
|
||||
|
||||
// Prepare the updated calendar
|
||||
let updatedCalendar = null;
|
||||
|
||||
// Find the calendar and update it
|
||||
this._calendars.forEach((item, index, calendars) => {
|
||||
|
||||
if ( item.id === id )
|
||||
{
|
||||
// Update the calendar
|
||||
calendars[index] = assign({}, calendars[index], calendar);
|
||||
|
||||
// Store the updated calendar
|
||||
updatedCalendar = calendars[index];
|
||||
}
|
||||
});
|
||||
|
||||
// Return the response
|
||||
return [200, updatedCalendar];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Calendars - DELETE
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onDelete('api/apps/calendar/calendars')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the id
|
||||
const id = request.params.get('id');
|
||||
|
||||
// Find the calendar and delete it
|
||||
const index = this._calendars.findIndex(calendar => calendar.id === id);
|
||||
this._calendars.splice(index, 1);
|
||||
|
||||
// Find the events that belong to the calendar and remove them as well
|
||||
this._events = this._events.filter(event => event.calendarId !== id);
|
||||
|
||||
// Return the response
|
||||
return [200, true];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Events - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/apps/calendar/events')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the start and end dates as moment
|
||||
const viewStart = moment(request.params.get('start')).startOf('day');
|
||||
const viewEnd = moment(request.params.get('end')).endOf('day');
|
||||
|
||||
// Clone the events
|
||||
const events = cloneDeep(this._events);
|
||||
|
||||
// Prepare the results
|
||||
const results: any[] = [];
|
||||
|
||||
// Go through the events...
|
||||
events.forEach((event) => {
|
||||
|
||||
// Get the event's start and end dates as moment
|
||||
const eventStart = moment(event.start);
|
||||
const eventEnd = moment(event.end);
|
||||
|
||||
// If it's a normal event...
|
||||
if ( !event.recurrence )
|
||||
{
|
||||
// Only grab the event if it's within the range
|
||||
if ( eventStart.isSameOrAfter(viewStart, 'day') && eventEnd.isSameOrBefore(viewEnd, 'day') )
|
||||
{
|
||||
// Push the event into the results array
|
||||
results.push(event);
|
||||
}
|
||||
}
|
||||
// If it's a recurring event...
|
||||
else
|
||||
{
|
||||
// Skip if the event does not recur within the view range
|
||||
if ( eventStart.isAfter(viewEnd, 'day') || eventEnd.isBefore(viewStart, 'day') )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the DTSTART and UNTIL for RRule
|
||||
const dtStart = eventStart.clone();
|
||||
const until = viewEnd.isBefore(eventEnd) ? viewEnd.clone().utc() : eventEnd.clone().utc();
|
||||
|
||||
// Create an RRuleSet
|
||||
const rruleset = this._generateRuleset(event, dtStart, until);
|
||||
|
||||
// Generate the recurring dates and loop through them
|
||||
rruleset.all().forEach((date) => {
|
||||
|
||||
// Get the rule date as a moment
|
||||
const ruleDate = moment(date);
|
||||
|
||||
// Subtract the UTC Offset from the rule date as we use local time for DTSTART.
|
||||
// The reason for this is simple; if we use UTC dates for DTSTART, RRule generated
|
||||
// dates can shift. Since we have to store the dates as UTC, we can figure out the
|
||||
// UTC value by simply subtracting the UTC Offset (minutes) from the rule date rather
|
||||
// than using UTC dates in the first place. This will ensure there will be no time/day
|
||||
// shift on generated rules since they will be generated based on the local time.
|
||||
ruleDate.subtract(ruleDate.utcOffset(), 'minutes');
|
||||
|
||||
// Skip the date if it's not in between the view start and view end
|
||||
// to prevent generating unnecessary amount of instances and to
|
||||
// prevent instance duplication
|
||||
if ( !ruleDate.isBetween(viewStart, viewEnd, 'day', '[]') )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the event instance
|
||||
const eventInstance = {
|
||||
|
||||
// Generate an instance id using the event id and rule date
|
||||
id: event.id + '_' + ruleDate.clone().utc().format('YYYYMMDD[T]HHmmss[Z]'),
|
||||
|
||||
// Set the recurringEventId on the event so we don't lose the event's origin
|
||||
recurringEventId: event.id,
|
||||
|
||||
// Whether this is the first instance of the recurring event or not
|
||||
isFirstInstance: event.start === ruleDate.clone().toISOString(),
|
||||
|
||||
// Get the rest of the mock-api
|
||||
calendarId : event.calendarId,
|
||||
title : event.title,
|
||||
description: event.description,
|
||||
start : ruleDate.toISOString(),
|
||||
end : ruleDate.add(event.duration, 'minutes').toISOString(),
|
||||
duration : event.duration,
|
||||
allDay : event.allDay,
|
||||
recurrence : event.recurrence
|
||||
};
|
||||
|
||||
// Push the event instance to the results array
|
||||
results.push(eventInstance);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Return the response
|
||||
return [200, results];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Event - POST
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPost('api/apps/calendar/event')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the event as the new event
|
||||
const newEvent = cloneDeep(request.body.event);
|
||||
|
||||
// Add an id to the new event
|
||||
newEvent.id = FuseMockApiUtils.guid();
|
||||
|
||||
// Unshift the new event
|
||||
this._events.unshift(newEvent);
|
||||
|
||||
// Return the response
|
||||
return [200, newEvent];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Event - PATCH
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPatch('api/apps/calendar/event')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the id and event
|
||||
const id = request.body.id;
|
||||
const event = cloneDeep(request.body.event);
|
||||
|
||||
// Prepare the updated event
|
||||
let updatedEvent = null;
|
||||
|
||||
// Find the event and update it
|
||||
this._events.forEach((item, index, events) => {
|
||||
|
||||
if ( item.id === id )
|
||||
{
|
||||
// Update the event
|
||||
events[index] = assign({}, events[index], event);
|
||||
|
||||
// Store the updated event
|
||||
updatedEvent = events[index];
|
||||
}
|
||||
});
|
||||
|
||||
// Return the response
|
||||
return [200, updatedEvent];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Event - DELETE
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onDelete('api/apps/calendar/event')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the id and event
|
||||
const id = request.params.get('id');
|
||||
|
||||
// Find the event and delete it
|
||||
const index = this._events.findIndex(item => item.id === id);
|
||||
this._events.splice(index, 1);
|
||||
|
||||
// Return the response
|
||||
return [200, true];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Recurring Event - PATCH
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPatch('api/apps/calendar/recurring-event')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the event, original event and mode
|
||||
const event = cloneDeep(request.body.event);
|
||||
const originalEvent = cloneDeep(request.body.originalEvent);
|
||||
const mode = request.body.mode;
|
||||
|
||||
// Find the original recurring event from db
|
||||
const recurringEvent = this._events.find(item => item.id === event.recurringEventId);
|
||||
|
||||
// Single
|
||||
if ( mode === 'single' )
|
||||
{
|
||||
// Create a new event from the event while ignoring the range and recurringEventId
|
||||
const {
|
||||
range,
|
||||
recurringEventId,
|
||||
...newEvent
|
||||
} = event;
|
||||
|
||||
// Generate a unique id for the event
|
||||
newEvent.id = FuseMockApiUtils.guid();
|
||||
|
||||
// Calculate the end date using the start date and the duration
|
||||
newEvent.end = moment(newEvent.start).add(newEvent.duration, 'minutes');
|
||||
|
||||
// Null-ify the recurrence and duration
|
||||
newEvent.duration = null;
|
||||
newEvent.recurrence = null;
|
||||
|
||||
// Push the new event to the events array
|
||||
this._events.push(newEvent);
|
||||
|
||||
// If this is the first instance of the recurring event...
|
||||
if ( originalEvent.start === recurringEvent.start )
|
||||
{
|
||||
// Generate the rruleset
|
||||
const rruleset = this._generateRuleset(recurringEvent, moment(recurringEvent.start), moment(recurringEvent.end).utc());
|
||||
|
||||
// Generate the dates using rruleset and get the 2nd date from start
|
||||
const ruleDate = moment(rruleset.all((date, i) => i < 2)[1]);
|
||||
|
||||
// Subtract the UTC Offset from the rule date as we use local time for DTSTART.
|
||||
// The reason for this is simple; if we use UTC dates for DTSTART, RRule generated
|
||||
// dates can shift. Since we have to store the dates as UTC, we can figure out the
|
||||
// UTC value by simply subtracting the UTC Offset (minutes) from the rule date rather
|
||||
// than using UTC dates in the first place. This will ensure there will be no time/day
|
||||
// shift on generated rules since they will be generated based on the local time.
|
||||
ruleDate.subtract(ruleDate.utcOffset(), 'minutes');
|
||||
|
||||
// Update the recurring event's start date
|
||||
recurringEvent.start = ruleDate.toISOString();
|
||||
}
|
||||
// Otherwise...
|
||||
else
|
||||
{
|
||||
// Add a new exception for the recurring event that ignores this single event's start date
|
||||
this._exceptions.push({
|
||||
id : FuseMockApiUtils.guid(),
|
||||
eventId: originalEvent.recurringEventId,
|
||||
exdate : moment(originalEvent.start).toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Future
|
||||
if ( mode === 'future' )
|
||||
{
|
||||
// Update the end date
|
||||
recurringEvent.end = moment(originalEvent.start).subtract(1, 'day').endOf('day').toISOString();
|
||||
|
||||
// Parse the recurrence rules from the original event
|
||||
const parsedRules: any = {};
|
||||
originalEvent.recurrence.split(';').forEach((rule: string) => {
|
||||
const parsedRule = rule.split('=');
|
||||
parsedRules[parsedRule[0]] = parsedRule[1];
|
||||
});
|
||||
|
||||
// Add/Update the UNTIL rule
|
||||
parsedRules['UNTIL'] = moment(recurringEvent.end).utc().format('YYYYMMDD[T]HHmmss[Z]');
|
||||
|
||||
// Generate the rule string from the parsed rules
|
||||
const rules: string[] = [];
|
||||
Object.keys(parsedRules).forEach((key) => {
|
||||
rules.push(key + '=' + parsedRules[key]);
|
||||
});
|
||||
|
||||
// Update the recurrence on the original recurring event
|
||||
recurringEvent.recurrence = rules.join(';');
|
||||
|
||||
// Create a new event from the event while ignoring the recurringEventId
|
||||
const {
|
||||
recurringEventId,
|
||||
...newEvent
|
||||
} = event;
|
||||
|
||||
// Generate a unique id for the event
|
||||
newEvent.id = FuseMockApiUtils.guid();
|
||||
|
||||
// Push the new event to the events array
|
||||
this._events.push(newEvent);
|
||||
}
|
||||
|
||||
// All
|
||||
if ( mode === 'all' )
|
||||
{
|
||||
// Find the event index
|
||||
const eventIndex = this._events.findIndex(item => item.id === event.recurringEventId);
|
||||
|
||||
// Update the recurring event
|
||||
this._events[eventIndex] = assign({}, this._events[eventIndex], omit(event, ['id', 'recurringEventId', 'range']));
|
||||
}
|
||||
|
||||
// Return the response
|
||||
return [200, true];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Recurring Event - DELETE
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onDelete('api/apps/calendar/recurring-event')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the event and mode
|
||||
const event = JSON.parse(request.params.get('event') ?? '');
|
||||
const mode = request.params.get('mode');
|
||||
|
||||
// Find the recurring event
|
||||
const recurringEvent = this._events.find(item => item.id === event.recurringEventId);
|
||||
|
||||
// Single
|
||||
if ( mode === 'single' )
|
||||
{
|
||||
// If this is the first instance of the recurring event...
|
||||
if ( event.start === recurringEvent.start )
|
||||
{
|
||||
// Generate the rruleset
|
||||
const rruleset = this._generateRuleset(recurringEvent, moment(recurringEvent.start), moment(recurringEvent.end).utc());
|
||||
|
||||
// Generate the dates using rruleset and get the 2nd date from start
|
||||
const ruleDate = moment(rruleset.all((date, i) => i < 2)[1]);
|
||||
|
||||
// Subtract the UTC Offset from the rule date as we use local time for DTSTART.
|
||||
// The reason for this is simple; if we use UTC dates for DTSTART, RRule generated
|
||||
// dates can shift. Since we have to store the dates as UTC, we can figure out the
|
||||
// UTC value by simply subtracting the UTC Offset (minutes) from the rule date rather
|
||||
// than using UTC dates in the first place. This will ensure there will be no time/day
|
||||
// shift on generated rules since they will be generated based on the local time.
|
||||
ruleDate.subtract(ruleDate.utcOffset(), 'minutes');
|
||||
|
||||
// Update the recurring event's start date
|
||||
recurringEvent.start = ruleDate.toISOString();
|
||||
}
|
||||
// Otherwise...
|
||||
else
|
||||
{
|
||||
// Add a new exception for the recurring event that ignores this single event's start date
|
||||
this._exceptions.push({
|
||||
id : FuseMockApiUtils.guid(),
|
||||
eventId: event.recurringEventId,
|
||||
exdate : moment(event.start).toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Future
|
||||
if ( mode === 'future' )
|
||||
{
|
||||
// Update the end date of the event
|
||||
recurringEvent.end = moment(event.start).subtract(1, 'day').endOf('day').toISOString();
|
||||
|
||||
// Parse the recurrence rules of the event
|
||||
const parsedRules: any = {};
|
||||
recurringEvent.recurrence.split(';').forEach((rule: string) => {
|
||||
const parsedRule = rule.split('=');
|
||||
parsedRules[parsedRule[0]] = parsedRule[1];
|
||||
});
|
||||
|
||||
// Add/Update the UNTIL rule
|
||||
parsedRules['UNTIL'] = moment(event.end).utc().format('YYYYMMDD[T]HHmmss[Z]');
|
||||
|
||||
// Generate the rule string from the parsed rules
|
||||
const rules: string[] = [];
|
||||
Object.keys(parsedRules).forEach((key) => {
|
||||
rules.push(key + '=' + parsedRules[key]);
|
||||
});
|
||||
|
||||
// Update the recurrence of the event
|
||||
recurringEvent.recurrence = rules.join(';');
|
||||
}
|
||||
|
||||
// All
|
||||
if ( mode === 'all' )
|
||||
{
|
||||
// Find the event and delete it
|
||||
const index = this._events.findIndex(item => item.id === event.recurringEventId);
|
||||
this._events.splice(index, 1);
|
||||
}
|
||||
|
||||
// Return the response
|
||||
return [200, true];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Settings - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/apps/calendar/settings')
|
||||
.reply(() => [200, cloneDeep(this._settings)]);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Settings - PATCH
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onPatch('api/apps/calendar/settings')
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get the settings
|
||||
const settings = cloneDeep(request.body.settings);
|
||||
|
||||
// Store the updated settings
|
||||
this._settings = settings;
|
||||
|
||||
// Return the response
|
||||
return [200, settings];
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Weekdays - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/apps/calendar/weekdays')
|
||||
.reply(() => {
|
||||
|
||||
// Clone the weekdays
|
||||
const weekdays = cloneDeep(this._weekdays);
|
||||
|
||||
// If the startWeekOn setting is set to Sunday...
|
||||
if ( this._settings.startWeekOn === 0 )
|
||||
{
|
||||
// Move the Sunday to the beginning
|
||||
weekdays.unshift(weekdays.pop());
|
||||
}
|
||||
|
||||
// If the startWeekOn is set to Saturday...
|
||||
if ( this._settings.startWeekOn === 6 )
|
||||
{
|
||||
// Move the Sunday to the beginning
|
||||
weekdays.unshift(weekdays.pop());
|
||||
|
||||
// Then move the Saturday to the beginning
|
||||
weekdays.unshift(weekdays.pop());
|
||||
}
|
||||
|
||||
// Return the response
|
||||
return [200, weekdays];
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generates an RRuleSet from given event
|
||||
*
|
||||
* @param event
|
||||
* @param dtStart
|
||||
* @param until
|
||||
* @private
|
||||
*/
|
||||
private _generateRuleset(event: any, dtStart: any, until: any): RRuleSet | RRule
|
||||
{
|
||||
// Parse the recurrence rules
|
||||
const parsedRules: any = {};
|
||||
event.recurrence.split(';').forEach((rule: string) => {
|
||||
|
||||
// Split the rule
|
||||
const parsedRule = rule.split('=');
|
||||
|
||||
// Omit UNTIL or COUNT from the parsed rules since we only
|
||||
// need them for calculating the event's end date. We will
|
||||
// add an UNTIL later based on the above calculations.
|
||||
if ( parsedRule[0] === 'UNTIL' || parsedRule[0] === 'COUNT' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the rule to the parsed rules
|
||||
parsedRules[parsedRule[0]] = parsedRule[1];
|
||||
});
|
||||
|
||||
// Generate the rule array from the parsed rules
|
||||
const rules: string[] = [];
|
||||
Object.keys(parsedRules).forEach((key) => {
|
||||
rules.push(key + '=' + parsedRules[key]);
|
||||
});
|
||||
|
||||
// Prepare the ruleSet
|
||||
const ruleSet = [];
|
||||
|
||||
// Add DTSTART
|
||||
ruleSet.push('DTSTART:' + dtStart.format('YYYYMMDD[T]HHmmss[Z]'));
|
||||
|
||||
// Add RRULE
|
||||
ruleSet.push('RRULE:' + rules.join(';') + ';UNTIL=' + until.format('YYYYMMDD[T]HHmmss[Z]'));
|
||||
|
||||
// Find and add any available exceptions to the rule
|
||||
this._exceptions.forEach((item) => {
|
||||
|
||||
// If the item is an exception to this event...
|
||||
if ( item.eventId === event.id )
|
||||
{
|
||||
// Add it as an EXDATE to the rrule
|
||||
ruleSet.push('EXDATE:' + moment(item.exdate).format('YYYYMMDD[T]HHmmss[Z]'));
|
||||
}
|
||||
});
|
||||
|
||||
// Create an RRuleSet from the ruleSet array
|
||||
return rrulestr(ruleSet.join('\n'), {forceset: true});
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import * as moment from 'moment';
|
||||
|
||||
export const calendars = [
|
||||
{
|
||||
id : '1a470c8e-40ed-4c2d-b590-a4f1f6ead6cc',
|
||||
title : 'Personal',
|
||||
color : 'bg-teal-500',
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
id : '5dab5f7b-757a-4467-ace1-305fe07b11fe',
|
||||
title : 'Work',
|
||||
color : 'bg-indigo-500',
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
id : '09887870-f85a-40eb-8171-1b13d7a7f529',
|
||||
title : 'Appointments',
|
||||
color : 'bg-pink-500',
|
||||
visible: true
|
||||
}
|
||||
];
|
||||
export const events = [
|
||||
// Personal
|
||||
{
|
||||
id : '3be50686-e3a1-4f4b-aa4d-5cb8517ba4e4',
|
||||
calendarId : '1a470c8e-40ed-4c2d-b590-a4f1f6ead6cc',
|
||||
title : 'Portfolio Design',
|
||||
description: '',
|
||||
start : moment().hour(9).minute(0).second(0).millisecond(0).toISOString(), // Today 09:00
|
||||
end : moment().add(1, 'day').hour(14).minute(0).second(0).millisecond(0).toISOString(), // Tomorrow 14:00
|
||||
duration : null,
|
||||
allDay : false,
|
||||
recurrence : null
|
||||
},
|
||||
{
|
||||
id : '660f0dcd-48f8-4266-a89a-8ee0789c074a',
|
||||
calendarId : '1a470c8e-40ed-4c2d-b590-a4f1f6ead6cc',
|
||||
title : 'Dinner with Mom',
|
||||
description: 'Do not forget to buy her lilacs!',
|
||||
start : moment().date(10).hour(18).minute(0).second(0).millisecond(0).toISOString(), // 10th of the current month at 18:00
|
||||
end : moment().date(10).hour(20).minute(0).second(0).millisecond(0).toISOString(), // 10th of the current month at 20:00
|
||||
duration : null,
|
||||
allDay : false,
|
||||
recurrence : null
|
||||
},
|
||||
{
|
||||
id : '7471b840-5efb-45da-9092-a0f04ee5617b',
|
||||
calendarId : '1a470c8e-40ed-4c2d-b590-a4f1f6ead6cc',
|
||||
title : 'Lunch with Becky',
|
||||
description: '',
|
||||
start : moment().date(21).hour(12).minute(0).second(0).millisecond(0).toISOString(), // 21st of the current month at noon
|
||||
end : moment().date(21).hour(14).minute(0).second(0).millisecond(0).toISOString(), // 21st of the current month at 14:00
|
||||
duration : null,
|
||||
allDay : false,
|
||||
recurrence : null
|
||||
},
|
||||
{
|
||||
id : 'c3e6c110-9b67-4e6b-a2ab-3046abf1b074',
|
||||
calendarId : '1a470c8e-40ed-4c2d-b590-a4f1f6ead6cc',
|
||||
title : 'Mom\'s Birthday',
|
||||
description: '',
|
||||
start : moment().date(8).startOf('day').toISOString(), // 8th of the current month at start of the day
|
||||
end : moment().year(9999).endOf('year').toISOString(), // End of the times
|
||||
duration : 0,
|
||||
allDay : true,
|
||||
recurrence : 'FREQ=YEARLY;INTERVAL=1'
|
||||
},
|
||||
// Appointments
|
||||
{
|
||||
id : 'd2220429-9214-4c4b-9da6-f8da2fbfd507',
|
||||
calendarId : '09887870-f85a-40eb-8171-1b13d7a7f529',
|
||||
title : 'Doctor\'s Visit',
|
||||
description: 'Measure blood pressure before leaving home',
|
||||
start : moment().date(1).hour(10).minute(0).second(0).millisecond(0).add((9 - moment().date(1).day()) % 7, 'day').toISOString(), // First Tuesday of the current month at 10:00
|
||||
end : moment().year(9999).endOf('year').toISOString(), // End of the times
|
||||
duration : 90, // Minutes
|
||||
allDay : false,
|
||||
recurrence : 'FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU'
|
||||
},
|
||||
{
|
||||
id : '4d88418c-cbdf-4f03-89e1-e3dca14a9e92',
|
||||
calendarId : '09887870-f85a-40eb-8171-1b13d7a7f529',
|
||||
title : 'Therapy Session',
|
||||
description: '',
|
||||
start : moment().date(1).hour(13).minute(0).second(0).millisecond(0).add((6 - moment().date(1).day()) % 7, 'day').add(1, 'week').toISOString(), // Second Saturday of the current month at 13:00
|
||||
end : moment().year(9999).endOf('year').toISOString(), // End of the times
|
||||
duration : 120, // Minutes
|
||||
allDay : false,
|
||||
recurrence : 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SA'
|
||||
},
|
||||
// Work
|
||||
{
|
||||
id : '0e848e4a-0333-42e3-b223-0209c4b58a3b',
|
||||
calendarId : '5dab5f7b-757a-4467-ace1-305fe07b11fe',
|
||||
title : 'Design Review',
|
||||
description: '',
|
||||
start : moment().date(19).hour(15).minute(0).second(0).millisecond(0).toISOString(), // 19th of the current month at 15:00
|
||||
end : moment().date(19).hour(17).minute(30).second(0).millisecond(0).toISOString(), // 19th of the current month at 17:30
|
||||
duration : null,
|
||||
allDay : true,
|
||||
recurrence : null
|
||||
},
|
||||
{
|
||||
id : 'f619eb76-c21b-4bb0-aeff-41e765cae290',
|
||||
calendarId : '5dab5f7b-757a-4467-ace1-305fe07b11fe',
|
||||
title : 'Consulting',
|
||||
description: 'Sarah and Jessica will be joining the call',
|
||||
start : moment().date(8).hour(11).minute(30).second(0).millisecond(0).toISOString(), // 8th of the current month at 11:30
|
||||
end : moment().date(8).hour(12).minute(45).second(0).millisecond(0).toISOString(), // 8th of the current month at 12:45
|
||||
duration : null,
|
||||
allDay : false,
|
||||
recurrence : null
|
||||
},
|
||||
{
|
||||
id : 'd97ea3ca-0e5a-4b86-a4fa-5b80a261081e',
|
||||
calendarId : '5dab5f7b-757a-4467-ace1-305fe07b11fe',
|
||||
title : 'Meeting',
|
||||
description: '',
|
||||
start : moment().date(1).hour(9).minute(0).second(0).millisecond(0).add((5 - moment().date(1).day()) % 7, 'day').add(1, 'week').toISOString(), // Second Friday of the current month at 09:00
|
||||
end : moment().year(9999).endOf('year').toISOString(), // End of the times
|
||||
duration : 150, // Minutes
|
||||
allDay : false,
|
||||
recurrence : 'FREQ=WEEKLY;INTERVAL=2;BYDAY=FR'
|
||||
}
|
||||
];
|
||||
export const exceptions = [];
|
||||
export const settings = {
|
||||
dateFormat : 'll', // Aug 20, 2019
|
||||
timeFormat : '24', // 24-hour format
|
||||
startWeekOn: 1 // Monday
|
||||
};
|
||||
export const weekdays = [
|
||||
{
|
||||
abbr : 'M',
|
||||
label: 'Monday',
|
||||
value: 'MO'
|
||||
},
|
||||
{
|
||||
abbr : 'T',
|
||||
label: 'Tuesday',
|
||||
value: 'TU'
|
||||
},
|
||||
{
|
||||
abbr : 'W',
|
||||
label: 'Wednesday',
|
||||
value: 'WE'
|
||||
},
|
||||
{
|
||||
abbr : 'T',
|
||||
label: 'Thursday',
|
||||
value: 'TH'
|
||||
},
|
||||
{
|
||||
abbr : 'F',
|
||||
label: 'Friday',
|
||||
value: 'FR'
|
||||
},
|
||||
{
|
||||
abbr : 'S',
|
||||
label: 'Saturday',
|
||||
value: 'SA'
|
||||
},
|
||||
{
|
||||
abbr : 'S',
|
||||
label: 'Sunday',
|
||||
value: 'SU'
|
||||
}
|
||||
];
|
|
@ -53,14 +53,6 @@ export const defaultNavigation: FuseNavigationItem[] = [
|
|||
icon : 'heroicons_outline:academic-cap',
|
||||
link : '/apps/academy'
|
||||
},
|
||||
{
|
||||
id : 'apps.calendar',
|
||||
title : 'Calendar',
|
||||
subtitle: '3 upcoming events',
|
||||
type : 'basic',
|
||||
icon : 'heroicons_outline:calendar',
|
||||
link : '/apps/calendar'
|
||||
},
|
||||
{
|
||||
id : 'apps.chat',
|
||||
title: 'Chat',
|
||||
|
|
|
@ -32,14 +32,6 @@ export const shortcuts = [
|
|||
link : '/dashboards/analytics',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : '34fb28db-4ec8-4570-8584-2414d6de796b',
|
||||
label : 'Calendar',
|
||||
description: 'Latest appointments',
|
||||
icon : 'heroicons_outline:calendar',
|
||||
link : '/apps/calendar',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : '2daac375-a2f7-4393-b4d7-ce6061628b66',
|
||||
label : 'Mailbox',
|
||||
|
|
|
@ -2,7 +2,6 @@ import { AcademyMockApi } from 'app/mock-api/apps/academy/api';
|
|||
import { ActivitiesMockApi } from 'app/mock-api/pages/activities/api';
|
||||
import { AnalyticsMockApi } from 'app/mock-api/dashboards/analytics/api';
|
||||
import { AuthMockApi } from 'app/mock-api/common/auth/api';
|
||||
import { CalendarMockApi } from 'app/mock-api/apps/calendar/api';
|
||||
import { ChatMockApi } from 'app/mock-api/apps/chat/api';
|
||||
import { ContactsMockApi } from 'app/mock-api/apps/contacts/api';
|
||||
import { CryptoMockApi } from 'app/mock-api/dashboards/crypto/api';
|
||||
|
@ -28,7 +27,6 @@ export const mockApiServices = [
|
|||
ActivitiesMockApi,
|
||||
AnalyticsMockApi,
|
||||
AuthMockApi,
|
||||
CalendarMockApi,
|
||||
ChatMockApi,
|
||||
ContactsMockApi,
|
||||
CryptoMockApi,
|
||||
|
|
|
@ -1,386 +0,0 @@
|
|||
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden dark:bg-gray-900">
|
||||
|
||||
<mat-drawer-container class="flex-auto h-full bg-transparent">
|
||||
|
||||
<!-- Drawer -->
|
||||
<mat-drawer
|
||||
class="w-60 dark:bg-gray-900"
|
||||
[autoFocus]="false"
|
||||
[mode]="drawerMode"
|
||||
[opened]="drawerOpened"
|
||||
#drawer>
|
||||
<calendar-sidebar (calendarUpdated)="onCalendarUpdated($event)"></calendar-sidebar>
|
||||
</mat-drawer>
|
||||
|
||||
<mat-drawer-content class="flex">
|
||||
|
||||
<!-- Main -->
|
||||
<div class="flex flex-col flex-auto">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-0 flex-wrap items-center p-4 border-b bg-card">
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="toggleDrawer()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="ml-4 text-2xl font-semibold tracking-tight whitespace-nowrap">
|
||||
{{viewTitle}}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="ml-5"
|
||||
mat-icon-button
|
||||
(click)="previous()">
|
||||
<mat-icon
|
||||
class="icon-size-5"
|
||||
[svgIcon]="'heroicons_solid:chevron-left'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="next()">
|
||||
<mat-icon
|
||||
class="icon-size-5"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="hidden md:inline-flex"
|
||||
mat-icon-button
|
||||
(click)="today()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="hidden md:block ml-auto">
|
||||
<mat-form-field class="fuse-mat-dense fuse-mat-no-subscript w-30 ml-2">
|
||||
<mat-select
|
||||
(selectionChange)="changeView(viewChanger.value)"
|
||||
[value]="view"
|
||||
#viewChanger="matSelect">
|
||||
<mat-option [value]="'dayGridMonth'">Month</mat-option>
|
||||
<mat-option [value]="'timeGridWeek'">Week</mat-option>
|
||||
<mat-option [value]="'timeGridDay'">Day</mat-option>
|
||||
<mat-option [value]="'listYear'">Schedule</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div class="md:hidden ml-auto">
|
||||
<button
|
||||
class=""
|
||||
[matMenuTriggerFor]="actionsMenu"
|
||||
mat-icon-button>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
|
||||
|
||||
<mat-menu #actionsMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="today()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<span>Go to today</span>
|
||||
</button>
|
||||
<button
|
||||
[matMenuTriggerFor]="actionsViewsMenu"
|
||||
mat-menu-item>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:view-grid'"></mat-icon>
|
||||
<span>View</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #actionsViewsMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'dayGridMonth'"
|
||||
(click)="changeView('dayGridMonth')">
|
||||
<span>Month</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'timeGridWeek'"
|
||||
(click)="changeView('timeGridWeek')">
|
||||
<span>Week</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'timeGridDay'"
|
||||
(click)="changeView('timeGridDay')">
|
||||
<span>Day</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'listYear'"
|
||||
(click)="changeView('listYear')">
|
||||
<span>Schedule</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FullCalendar -->
|
||||
<div class="flex flex-col flex-auto">
|
||||
<full-calendar
|
||||
[defaultView]="view"
|
||||
[events]="events"
|
||||
[firstDay]="settings.startWeekOn"
|
||||
[handleWindowResize]="false"
|
||||
[header]="false"
|
||||
[height]="'parent'"
|
||||
[plugins]="calendarPlugins"
|
||||
[views]="views"
|
||||
(dateClick)="onDateClick($event)"
|
||||
(eventClick)="onEventClick($event)"
|
||||
(eventRender)="onEventRender($event)"
|
||||
#fullCalendar></full-calendar>
|
||||
</div>
|
||||
|
||||
<!-- Event panel -->
|
||||
<ng-template #eventPanel>
|
||||
|
||||
<!-- Preview mode -->
|
||||
<ng-container *ngIf="panelMode === 'view'">
|
||||
<div class="flex-auto p-8">
|
||||
<!-- Info -->
|
||||
<div class="flex">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:information-circle'"></mat-icon>
|
||||
<div class="flex flex-auto justify-between ml-6">
|
||||
<!-- Info -->
|
||||
<div>
|
||||
<div class="text-3xl font-semibold tracking-tight leading-none">{{event.title || '(No title)'}}</div>
|
||||
<div class="mt-0.5 text-secondary">{{event.start | date:'EEEE, MMMM d'}}</div>
|
||||
<div class="text-secondary">{{recurrenceStatus}}</div>
|
||||
</div>
|
||||
<!-- Actions -->
|
||||
<div class="flex -mt-2 -mr-2 ml-10">
|
||||
|
||||
<!-- Non-recurring event -->
|
||||
<ng-container *ngIf="!event.recurrence">
|
||||
<!-- Edit -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="changeEventPanelMode('edit', 'single')">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
</button>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="deleteEvent(event)">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:trash'"></mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Recurring event -->
|
||||
<ng-container *ngIf="event.recurrence">
|
||||
<!-- Edit -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="editMenu">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #editMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeEventPanelMode('edit', 'single')">
|
||||
This event
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="!event.isFirstInstance"
|
||||
(click)="changeEventPanelMode('edit', 'future')">
|
||||
This and following events
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeEventPanelMode('edit', 'all')">
|
||||
All events
|
||||
</button>
|
||||
</mat-menu>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="deleteMenu">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:trash'"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #deleteMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="deleteEvent(event, 'single')">
|
||||
This event
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="!event.isFirstInstance"
|
||||
(click)="deleteEvent(event, 'future')">
|
||||
This and following events
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="deleteEvent(event, 'all')">
|
||||
All events
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div
|
||||
class="flex mt-6"
|
||||
*ngIf="event.description">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu-alt-2'"></mat-icon>
|
||||
<div class="flex-auto ml-6">{{event.description}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar -->
|
||||
<div class="flex mt-6">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<div class="flex flex-auto items-center ml-6">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
[ngClass]="getCalendar(event.calendarId).color"></div>
|
||||
<div class="ml-3 leading-none">{{getCalendar(event.calendarId).title}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Add / Edit mode -->
|
||||
<ng-container *ngIf="panelMode === 'add' || panelMode === 'edit'">
|
||||
<form
|
||||
class="flex flex-col w-full p-6 pt-8 sm:pt-10 sm:pr-8"
|
||||
[formGroup]="eventForm">
|
||||
|
||||
<!-- Title -->
|
||||
<div class="flex items-center">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6"
|
||||
[svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-no-subscript flex-auto">
|
||||
<input
|
||||
matInput
|
||||
[formControlName]="'title'"
|
||||
[placeholder]="'Event title'">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Dates -->
|
||||
<div class="flex items-start mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mt-3 mr-6"
|
||||
[svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<div class="flex-auto">
|
||||
<fuse-date-range
|
||||
[formControlName]="'range'"
|
||||
[dateFormat]="settings.dateFormat"
|
||||
[timeRange]="!eventForm.get('allDay').value"
|
||||
[timeFormat]="settings.timeFormat"></fuse-date-range>
|
||||
<mat-checkbox
|
||||
class="mt-4"
|
||||
[color]="'primary'"
|
||||
[formControlName]="'allDay'">
|
||||
All day
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recurrence -->
|
||||
<div
|
||||
class="flex items-center mt-6"
|
||||
*ngIf="!event.recurrence || eventEditMode !== 'single'">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6 transform -scale-x-1"
|
||||
[svgIcon]="'heroicons_outline:refresh'"></mat-icon>
|
||||
<div
|
||||
class="flex flex-auto items-center h-12 px-4 rounded-md border cursor-pointer shadow-sm border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500"
|
||||
(click)="openRecurrenceDialog()">
|
||||
<div class="flex-auto">
|
||||
{{recurrenceStatus || 'Does not repeat'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar -->
|
||||
<div class="flex items-center mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6"
|
||||
[svgIcon]="'heroicons_outline:tag'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-no-subscript flex-auto">
|
||||
<mat-select
|
||||
[formControlName]="'calendarId'"
|
||||
(change)="$event.stopImmediatePropagation()">
|
||||
<mat-select-trigger class="inline-flex items-center leading-none">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
[ngClass]="getCalendar(eventForm.get('calendarId').value)?.color"></span>
|
||||
<span class="ml-3">{{getCalendar(eventForm.get('calendarId').value)?.title}}</span>
|
||||
</mat-select-trigger>
|
||||
<ng-container *ngFor="let calendar of calendars">
|
||||
<mat-option [value]="calendar.id">
|
||||
<div class="inline-flex items-center">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
[ngClass]="calendar.color"></span>
|
||||
<span class="ml-3">{{calendar.title}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="flex items-start mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6 mt-3"
|
||||
[svgIcon]="'heroicons_outline:menu-alt-2'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-textarea fuse-mat-no-subscript flex-auto">
|
||||
<textarea
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
[cdkAutosizeMinRows]="1"
|
||||
[formControlName]="'description'"
|
||||
[placeholder]="'Event description'">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="ml-auto mt-6">
|
||||
<button
|
||||
class="add"
|
||||
*ngIf="panelMode === 'add'"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[color]="'primary'"
|
||||
(click)="addEvent()">
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
class="save"
|
||||
*ngIf="panelMode === 'edit'"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[color]="'primary'"
|
||||
(click)="updateEvent()">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
||||
</mat-drawer-content>
|
||||
|
||||
</mat-drawer-container>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
calendar {
|
||||
|
||||
/* Tweak: FullCalendar CSS only height to improve resize performance */
|
||||
/* With this tweak, we can disable "handleWindowResize" option of FullCalendar */
|
||||
/* which disables the height calculations on window resize and increases the */
|
||||
/* overall performance. */
|
||||
/* This tweak only affects the Calendar app's FullCalendar. */
|
||||
full-calendar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-view-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-view {
|
||||
|
||||
/* Day grid - Month view */
|
||||
/* Time grid - Week view */
|
||||
/* Time grid - Day view */
|
||||
&.fc-dayGridMonth-view,
|
||||
&.fc-timeGridWeek-view,
|
||||
&.fc-timeGridDay-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
height: 100%;
|
||||
|
||||
> thead {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
> tbody {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
> tr {
|
||||
display: flex;
|
||||
|
||||
> td {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.fc-scroller {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden scroll !important;
|
||||
height: auto !important;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Day grid - Month view */
|
||||
&.fc-dayGridMonth-view {
|
||||
|
||||
> table {
|
||||
|
||||
> tbody {
|
||||
|
||||
> tr {
|
||||
|
||||
> td {
|
||||
|
||||
.fc-scroller {
|
||||
display: flex;
|
||||
|
||||
> .fc-day-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 580px;
|
||||
|
||||
> .fc-row {
|
||||
flex: 1 0 0;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List - Year view */
|
||||
&.fc-listYear-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-scroller {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Event panel */
|
||||
.calendar-event-panel {
|
||||
border-radius: 8px;
|
||||
@apply shadow-2xl bg-card;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,75 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
import { MAT_DATE_FORMATS } from '@angular/material/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatMomentDateModule } from '@angular/material-moment-adapter';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { FullCalendarModule } from '@fullcalendar/angular';
|
||||
import { FuseDateRangeModule } from '@fuse/components/date-range';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
import { CalendarComponent } from 'app/modules/admin/apps/calendar/calendar.component';
|
||||
import { CalendarRecurrenceComponent } from 'app/modules/admin/apps/calendar/recurrence/recurrence.component';
|
||||
import { CalendarSettingsComponent } from 'app/modules/admin/apps/calendar/settings/settings.component';
|
||||
import { CalendarSidebarComponent } from 'app/modules/admin/apps/calendar/sidebar/sidebar.component';
|
||||
import { calendarRoutes } from 'app/modules/admin/apps/calendar/calendar.routing';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CalendarComponent,
|
||||
CalendarRecurrenceComponent,
|
||||
CalendarSettingsComponent,
|
||||
CalendarSidebarComponent
|
||||
],
|
||||
imports : [
|
||||
RouterModule.forChild(calendarRoutes),
|
||||
ScrollingModule,
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatMomentDateModule,
|
||||
MatRadioModule,
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
MatTooltipModule,
|
||||
FullCalendarModule,
|
||||
FuseDateRangeModule,
|
||||
SharedModule
|
||||
],
|
||||
providers : [
|
||||
{
|
||||
provide : MAT_DATE_FORMATS,
|
||||
useValue: {
|
||||
parse : {
|
||||
dateInput: 'DD.MM.YYYY'
|
||||
},
|
||||
display: {
|
||||
dateInput : 'DD.MM.YYYY',
|
||||
monthYearLabel : 'MMM YYYY',
|
||||
dateA11yLabel : 'DD.MM.YYYY',
|
||||
monthYearA11yLabel: 'MMMM YYYY'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export class CalendarModule
|
||||
{
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CalendarService } from 'app/modules/admin/apps/calendar/calendar.service';
|
||||
import { Calendar, CalendarSettings, CalendarWeekday } from 'app/modules/admin/apps/calendar/calendar.types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CalendarCalendarsResolver implements Resolve<any>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _calendarService: CalendarService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolver
|
||||
*
|
||||
* @param route
|
||||
* @param state
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Calendar[]>
|
||||
{
|
||||
return this._calendarService.getCalendars();
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CalendarSettingsResolver implements Resolve<any>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _calendarService: CalendarService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolver
|
||||
*
|
||||
* @param route
|
||||
* @param state
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<CalendarSettings>
|
||||
{
|
||||
return this._calendarService.getSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CalendarWeekdaysResolver implements Resolve<any>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _calendarService: CalendarService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolver
|
||||
*
|
||||
* @param route
|
||||
* @param state
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<CalendarWeekday[]>
|
||||
{
|
||||
return this._calendarService.getWeekdays();
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { Route } from '@angular/router';
|
||||
import { CalendarComponent } from 'app/modules/admin/apps/calendar/calendar.component';
|
||||
import { CalendarSettingsComponent } from 'app/modules/admin/apps/calendar/settings/settings.component';
|
||||
import { CalendarCalendarsResolver, CalendarSettingsResolver, CalendarWeekdaysResolver } from 'app/modules/admin/apps/calendar/calendar.resolvers';
|
||||
|
||||
export const calendarRoutes: Route[] = [
|
||||
{
|
||||
path : '',
|
||||
component: CalendarComponent,
|
||||
resolve : {
|
||||
calendars: CalendarCalendarsResolver,
|
||||
settings : CalendarSettingsResolver,
|
||||
weekdays : CalendarWeekdaysResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path : 'settings',
|
||||
component: CalendarSettingsComponent,
|
||||
resolve : {
|
||||
settings: CalendarSettingsResolver
|
||||
}
|
||||
}
|
||||
];
|
|
@ -1,475 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { Moment } from 'moment';
|
||||
import { Calendar, CalendarEvent, CalendarEventEditMode, CalendarSettings, CalendarWeekday } from 'app/modules/admin/apps/calendar/calendar.types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CalendarService
|
||||
{
|
||||
// Private
|
||||
private _calendars: BehaviorSubject<Calendar[] | null> = new BehaviorSubject(null);
|
||||
private _events: BehaviorSubject<CalendarEvent[] | null> = new BehaviorSubject(null);
|
||||
private _loadedEventsRange: { start: Moment | null; end: Moment | null } = {
|
||||
start: null,
|
||||
end : null
|
||||
};
|
||||
private readonly _numberOfDaysToPrefetch = 60;
|
||||
private _settings: BehaviorSubject<CalendarSettings | null> = new BehaviorSubject(null);
|
||||
private _weekdays: BehaviorSubject<CalendarWeekday[] | null> = new BehaviorSubject(null);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _httpClient: HttpClient)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Getter for calendars
|
||||
*/
|
||||
get calendars$(): Observable<Calendar[]>
|
||||
{
|
||||
return this._calendars.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for events
|
||||
*/
|
||||
get events$(): Observable<CalendarEvent[]>
|
||||
{
|
||||
return this._events.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for settings
|
||||
*/
|
||||
get settings$(): Observable<CalendarSettings>
|
||||
{
|
||||
return this._settings.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for weekdays
|
||||
*/
|
||||
get weekdays$(): Observable<CalendarWeekday[]>
|
||||
{
|
||||
return this._weekdays.asObservable();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get calendars
|
||||
*/
|
||||
getCalendars(): Observable<Calendar[]>
|
||||
{
|
||||
return this._httpClient.get<Calendar[]>('api/apps/calendar/calendars').pipe(
|
||||
tap((response) => {
|
||||
this._calendars.next(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add calendar
|
||||
*
|
||||
* @param calendar
|
||||
*/
|
||||
addCalendar(calendar: Calendar): Observable<Calendar>
|
||||
{
|
||||
return this.calendars$.pipe(
|
||||
take(1),
|
||||
switchMap(calendars => this._httpClient.post<Calendar>('api/apps/calendar/calendars', {
|
||||
calendar
|
||||
}).pipe(
|
||||
map((addedCalendar) => {
|
||||
|
||||
// Add the calendar
|
||||
calendars.push(addedCalendar);
|
||||
|
||||
// Update the calendars
|
||||
this._calendars.next(calendars);
|
||||
|
||||
// Return the added calendar
|
||||
return addedCalendar;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update calendar
|
||||
*
|
||||
* @param id
|
||||
* @param calendar
|
||||
*/
|
||||
updateCalendar(id: string, calendar: Calendar): Observable<Calendar>
|
||||
{
|
||||
return this.calendars$.pipe(
|
||||
take(1),
|
||||
switchMap(calendars => this._httpClient.patch<Calendar>('api/apps/calendar/calendars', {
|
||||
id,
|
||||
calendar
|
||||
}).pipe(
|
||||
map((updatedCalendar) => {
|
||||
|
||||
// Find the index of the updated calendar
|
||||
const index = calendars.findIndex(item => item.id === id);
|
||||
|
||||
// Update the calendar
|
||||
calendars[index] = updatedCalendar;
|
||||
|
||||
// Update the calendars
|
||||
this._calendars.next(calendars);
|
||||
|
||||
// Return the updated calendar
|
||||
return updatedCalendar;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete calendar
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
deleteCalendar(id: string): Observable<any>
|
||||
{
|
||||
return this.calendars$.pipe(
|
||||
take(1),
|
||||
switchMap(calendars => this._httpClient.delete<Calendar>('api/apps/calendar/calendars', {
|
||||
params: {id}
|
||||
}).pipe(
|
||||
map((isDeleted) => {
|
||||
|
||||
// Find the index of the deleted calendar
|
||||
const index = calendars.findIndex(item => item.id === id);
|
||||
|
||||
// Delete the calendar
|
||||
calendars.splice(index, 1);
|
||||
|
||||
// Update the calendars
|
||||
this._calendars.next(calendars);
|
||||
|
||||
// Remove the events belong to deleted calendar
|
||||
const events = this._events.value.filter(event => event.calendarId !== id);
|
||||
|
||||
// Update the events
|
||||
this._events.next(events);
|
||||
|
||||
// Return the deleted status
|
||||
return isDeleted;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events
|
||||
*
|
||||
* @param start
|
||||
* @param end
|
||||
* @param replace
|
||||
*/
|
||||
getEvents(start: Moment, end: Moment, replace = false): Observable<CalendarEvent[]>
|
||||
{
|
||||
// Set the new start date for loaded events
|
||||
if ( replace || !this._loadedEventsRange.start || start.isBefore(this._loadedEventsRange.start) )
|
||||
{
|
||||
this._loadedEventsRange.start = start;
|
||||
}
|
||||
|
||||
// Set the new end date for loaded events
|
||||
if ( replace || !this._loadedEventsRange.end || end.isAfter(this._loadedEventsRange.end) )
|
||||
{
|
||||
this._loadedEventsRange.end = end;
|
||||
}
|
||||
|
||||
// Get the events
|
||||
return this._httpClient.get<CalendarEvent[]>('api/apps/calendar/events', {
|
||||
params: {
|
||||
start: start.toISOString(true),
|
||||
end : end.toISOString(true)
|
||||
}
|
||||
}).pipe(
|
||||
switchMap(response => this._events.pipe(
|
||||
take(1),
|
||||
map((events) => {
|
||||
|
||||
// If replace...
|
||||
if ( replace )
|
||||
{
|
||||
// Execute the observable with the response replacing the events object
|
||||
this._events.next(response);
|
||||
}
|
||||
// Otherwise...
|
||||
else
|
||||
{
|
||||
// If events is null, replace it with an empty array
|
||||
events = events || [];
|
||||
|
||||
// Execute the observable by appending the response to the current events
|
||||
this._events.next([...events, ...response]);
|
||||
}
|
||||
|
||||
// Return the response
|
||||
return response;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload events using the loaded events range
|
||||
*/
|
||||
reloadEvents(): Observable<CalendarEvent[]>
|
||||
{
|
||||
// Get the events
|
||||
return this._httpClient.get<CalendarEvent[]>('api/apps/calendar/events', {
|
||||
params: {
|
||||
start: this._loadedEventsRange.start.toISOString(),
|
||||
end : this._loadedEventsRange.end.toISOString()
|
||||
}
|
||||
}).pipe(
|
||||
map((response) => {
|
||||
|
||||
// Execute the observable with the response replacing the events object
|
||||
this._events.next(response);
|
||||
|
||||
// Return the response
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch future events
|
||||
*
|
||||
* @param end
|
||||
*/
|
||||
prefetchFutureEvents(end: Moment): Observable<CalendarEvent[]>
|
||||
{
|
||||
// Calculate the remaining prefetched days
|
||||
const remainingDays = this._loadedEventsRange.end.diff(end, 'days');
|
||||
|
||||
// Return if remaining days is bigger than the number
|
||||
// of days to prefetch. This means we were already been
|
||||
// there and fetched the events data so no need for doing
|
||||
// it again.
|
||||
if ( remainingDays >= this._numberOfDaysToPrefetch )
|
||||
{
|
||||
return of([]);
|
||||
}
|
||||
|
||||
// Figure out the start and end dates
|
||||
const start = this._loadedEventsRange.end.clone().add(1, 'day');
|
||||
end = this._loadedEventsRange.end.clone().add(this._numberOfDaysToPrefetch - remainingDays, 'days');
|
||||
|
||||
// Prefetch the events
|
||||
return this.getEvents(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch past events
|
||||
*
|
||||
* @param start
|
||||
*/
|
||||
prefetchPastEvents(start: Moment): Observable<CalendarEvent[]>
|
||||
{
|
||||
// Calculate the remaining prefetched days
|
||||
const remainingDays = start.diff(this._loadedEventsRange.start, 'days');
|
||||
|
||||
// Return if remaining days is bigger than the number
|
||||
// of days to prefetch. This means we were already been
|
||||
// there and fetched the events data so no need for doing
|
||||
// it again.
|
||||
if ( remainingDays >= this._numberOfDaysToPrefetch )
|
||||
{
|
||||
return of([]);
|
||||
}
|
||||
|
||||
// Figure out the start and end dates
|
||||
start = this._loadedEventsRange.start.clone().subtract(this._numberOfDaysToPrefetch - remainingDays, 'days');
|
||||
const end = this._loadedEventsRange.start.clone().subtract(1, 'day');
|
||||
|
||||
// Prefetch the events
|
||||
return this.getEvents(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
addEvent(event): Observable<CalendarEvent>
|
||||
{
|
||||
return this.events$.pipe(
|
||||
take(1),
|
||||
switchMap(events => this._httpClient.post<CalendarEvent>('api/apps/calendar/event', {
|
||||
event
|
||||
}).pipe(
|
||||
map((addedEvent) => {
|
||||
|
||||
// Update the events
|
||||
this._events.next(events);
|
||||
|
||||
// Return the added event
|
||||
return addedEvent;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event
|
||||
*
|
||||
* @param id
|
||||
* @param event
|
||||
*/
|
||||
updateEvent(id: string, event): Observable<CalendarEvent>
|
||||
{
|
||||
return this.events$.pipe(
|
||||
take(1),
|
||||
switchMap(events => this._httpClient.patch<CalendarEvent>('api/apps/calendar/event', {
|
||||
id,
|
||||
event
|
||||
}).pipe(
|
||||
map((updatedEvent) => {
|
||||
|
||||
// Find the index of the updated event
|
||||
const index = events.findIndex(item => item.id === id);
|
||||
|
||||
// Update the event
|
||||
events[index] = updatedEvent;
|
||||
|
||||
// Update the events
|
||||
this._events.next(events);
|
||||
|
||||
// Return the updated event
|
||||
return updatedEvent;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update recurring event
|
||||
*
|
||||
* @param event
|
||||
* @param originalEvent
|
||||
* @param mode
|
||||
*/
|
||||
updateRecurringEvent(event, originalEvent, mode: CalendarEventEditMode): Observable<boolean>
|
||||
{
|
||||
return this._httpClient.patch<boolean>('api/apps/calendar/recurring-event', {
|
||||
event,
|
||||
originalEvent,
|
||||
mode
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete event
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
deleteEvent(id: string): Observable<CalendarEvent>
|
||||
{
|
||||
return this.events$.pipe(
|
||||
take(1),
|
||||
switchMap(events => this._httpClient.delete<CalendarEvent>('api/apps/calendar/event', {params: {id}}).pipe(
|
||||
map((isDeleted) => {
|
||||
|
||||
// Find the index of the deleted event
|
||||
const index = events.findIndex(item => item.id === id);
|
||||
|
||||
// Delete the event
|
||||
events.splice(index, 1);
|
||||
|
||||
// Update the events
|
||||
this._events.next(events);
|
||||
|
||||
// Return the deleted status
|
||||
return isDeleted;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete recurring event
|
||||
*
|
||||
* @param event
|
||||
* @param mode
|
||||
*/
|
||||
deleteRecurringEvent(event, mode: CalendarEventEditMode): Observable<boolean>
|
||||
{
|
||||
return this._httpClient.delete<boolean>('api/apps/calendar/recurring-event', {
|
||||
params: {
|
||||
event: JSON.stringify(event),
|
||||
mode
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings
|
||||
*/
|
||||
getSettings(): Observable<CalendarSettings>
|
||||
{
|
||||
return this._httpClient.get<CalendarSettings>('api/apps/calendar/settings').pipe(
|
||||
tap((response) => {
|
||||
this._settings.next(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings
|
||||
*/
|
||||
updateSettings(settings: CalendarSettings): Observable<CalendarSettings>
|
||||
{
|
||||
return this.events$.pipe(
|
||||
take(1),
|
||||
switchMap(events => this._httpClient.patch<CalendarSettings>('api/apps/calendar/settings', {
|
||||
settings
|
||||
}).pipe(
|
||||
map((updatedSettings) => {
|
||||
|
||||
// Update the settings
|
||||
this._settings.next(settings);
|
||||
|
||||
// Get weekdays again to get them in correct order
|
||||
// in case the startWeekOn setting changes
|
||||
this.getWeekdays().subscribe();
|
||||
|
||||
// Return the updated settings
|
||||
return updatedSettings;
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get weekdays
|
||||
*/
|
||||
getWeekdays(): Observable<CalendarWeekday[]>
|
||||
{
|
||||
return this._httpClient.get<CalendarWeekday[]>('api/apps/calendar/weekdays').pipe(
|
||||
tap((response) => {
|
||||
this._weekdays.next(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
export interface Calendar
|
||||
{
|
||||
id: string;
|
||||
title: string;
|
||||
color: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export type CalendarDrawerMode = 'over' | 'side';
|
||||
|
||||
export interface CalendarEvent
|
||||
{
|
||||
id: string;
|
||||
calendarId: string;
|
||||
recurringEventId: string | null;
|
||||
isFirstInstance: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
start: string | null;
|
||||
end: string | null;
|
||||
allDay: boolean;
|
||||
recurrence: string;
|
||||
}
|
||||
|
||||
export interface CalendarEventException
|
||||
{
|
||||
id: string;
|
||||
eventId: string;
|
||||
exdate: string;
|
||||
}
|
||||
|
||||
export type CalendarEventPanelMode = 'view' | 'add' | 'edit';
|
||||
export type CalendarEventEditMode = 'single' | 'future' | 'all';
|
||||
|
||||
export interface CalendarSettings
|
||||
{
|
||||
dateFormat: 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY-MM-DD' | 'll';
|
||||
timeFormat: '12' | '24';
|
||||
startWeekOn: 6 | 0 | 1;
|
||||
}
|
||||
|
||||
export interface CalendarWeekday
|
||||
{
|
||||
abbr: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
<form
|
||||
class="flex flex-col w-full"
|
||||
[formGroup]="recurrenceForm">
|
||||
|
||||
<div class="text-2xl font-semibold tracking-tight">Recurrence rules</div>
|
||||
|
||||
<!-- Interval and frequency -->
|
||||
<div class="flex mt-12">
|
||||
<mat-form-field class="fuse-mat-no-subscript w-24 -mt-6">
|
||||
<mat-label>Repeat every</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
[autocomplete]="'off'"
|
||||
[formControlName]="'interval'"
|
||||
[min]="1">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="fuse-mat-no-subscript w-40 ml-4">
|
||||
<mat-select [formControlName]="'freq'">
|
||||
<mat-option [value]="'DAILY'">day(s)</mat-option>
|
||||
<mat-option [value]="'WEEKLY'">week(s)</mat-option>
|
||||
<mat-option [value]="'MONTHLY'">month(s)</mat-option>
|
||||
<mat-option [value]="'YEARLY'">year(s)</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Weekly repeat options -->
|
||||
<div
|
||||
class="flex flex-col mt-6"
|
||||
[formGroupName]="'weekly'"
|
||||
*ngIf="recurrenceForm.get('freq').value === 'WEEKLY'">
|
||||
<div class="font-medium">Repeat on</div>
|
||||
<mat-button-toggle-group
|
||||
class="mt-1.5 border-0 space-x-1"
|
||||
[formControlName]="'byDay'"
|
||||
[multiple]="true">
|
||||
<ng-container *ngFor="let weekday of weekdays">
|
||||
<mat-button-toggle
|
||||
class="w-10 h-10 border-0 rounded-full"
|
||||
[disableRipple]="true"
|
||||
[value]="weekday.value"
|
||||
[matTooltip]="weekday.label">
|
||||
{{weekday.abbr}}
|
||||
</mat-button-toggle>
|
||||
</ng-container>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<!-- Monthly repeat options -->
|
||||
<div
|
||||
class="flex mt-6"
|
||||
[formGroupName]="'monthly'"
|
||||
*ngIf="recurrenceForm.get('freq').value === 'MONTHLY'">
|
||||
<mat-form-field class="fuse-mat-no-subscript w-full">
|
||||
<mat-label>Repeat on</mat-label>
|
||||
<mat-select [formControlName]="'repeatOn'">
|
||||
<mat-option [value]="'date'">Monthly on day {{recurrenceForm.get('monthly.date').value}}</mat-option>
|
||||
<mat-option [value]="'nthWeekday'">Monthly on the {{nthWeekdayText}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Ends -->
|
||||
<div
|
||||
class="flex flex-col mt-12"
|
||||
[formGroupName]="'end'">
|
||||
<div class="flex items-center">
|
||||
<mat-form-field class="fuse-mat-no-subscript w-24 -mt-6">
|
||||
<mat-label>Ends</mat-label>
|
||||
<mat-select [formControlName]="'type'">
|
||||
<mat-option [value]="'never'">Never</mat-option>
|
||||
<mat-option [value]="'until'">On</mat-option>
|
||||
<mat-option [value]="'count'">After</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field
|
||||
class="fuse-mat-no-subscript w-40 ml-4"
|
||||
*ngIf="recurrenceForm.get('end.type').value === 'until'">
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="untilDatePicker"
|
||||
[formControlName]="'until'">
|
||||
<mat-datepicker-toggle
|
||||
matSuffix
|
||||
[for]="untilDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #untilDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field
|
||||
class="fuse-mat-no-subscript w-40 ml-4"
|
||||
*ngIf="recurrenceForm.get('end.type').value === 'count'">
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
[autocomplete]="'off'"
|
||||
[formControlName]="'count'"
|
||||
[min]="1">
|
||||
<span matSuffix>occurrence(s)</span>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="ml-auto mt-8">
|
||||
<button
|
||||
class="clear"
|
||||
mat-button
|
||||
[color]="'primary'"
|
||||
(click)="clear()">
|
||||
Clear
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
[disabled]="recurrenceForm.invalid"
|
||||
[color]="'primary'"
|
||||
(click)="done()">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
|
@ -1,341 +0,0 @@
|
|||
import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import * as moment from 'moment';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { CalendarService } from 'app/modules/admin/apps/calendar/calendar.service';
|
||||
import { CalendarWeekday } from 'app/modules/admin/apps/calendar/calendar.types';
|
||||
|
||||
@Component({
|
||||
selector : 'calendar-recurrence',
|
||||
templateUrl : './recurrence.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class CalendarRecurrenceComponent implements OnInit, OnDestroy
|
||||
{
|
||||
nthWeekdayText: string;
|
||||
recurrenceForm: FormGroup;
|
||||
recurrenceFormValues: any;
|
||||
weekdays: CalendarWeekday[];
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
public matDialogRef: MatDialogRef<CalendarRecurrenceComponent>,
|
||||
private _calendarService: CalendarService,
|
||||
private _formBuilder: FormBuilder
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Get weekdays
|
||||
this._calendarService.weekdays$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((weekdays) => {
|
||||
|
||||
// Store the weekdays
|
||||
this.weekdays = weekdays;
|
||||
});
|
||||
|
||||
// Initialize
|
||||
this._init();
|
||||
|
||||
// Create the recurrence form
|
||||
this.recurrenceForm = this._formBuilder.group({
|
||||
freq : [null],
|
||||
interval: [null, Validators.required],
|
||||
weekly : this._formBuilder.group({
|
||||
byDay: [[]]
|
||||
}),
|
||||
monthly : this._formBuilder.group({
|
||||
repeatOn : [null], // date | nthWeekday
|
||||
date : [null],
|
||||
nthWeekday: [null]
|
||||
}),
|
||||
end : this._formBuilder.group({
|
||||
type : [null], // never | until | count
|
||||
until: [null],
|
||||
count: [null]
|
||||
})
|
||||
});
|
||||
|
||||
// Subscribe to 'freq' field value changes
|
||||
this.recurrenceForm.get('freq').valueChanges.subscribe((value) => {
|
||||
|
||||
// Set the end values
|
||||
this._setEndValues(value);
|
||||
});
|
||||
|
||||
// Subscribe to 'weekly.byDay' field value changes
|
||||
this.recurrenceForm.get('weekly.byDay').valueChanges.subscribe((value) => {
|
||||
|
||||
// Get the event's start date
|
||||
const startDate = moment(this.data.event.start);
|
||||
|
||||
// If nothing is selected, select the original value from
|
||||
// the event form to prevent an empty value on the field
|
||||
if ( !value || !value.length )
|
||||
{
|
||||
// Get the day of event start date
|
||||
const eventStartDay = startDate.format('dd').toUpperCase();
|
||||
|
||||
// Set the original value back without emitting a
|
||||
// change event to prevent an infinite loop
|
||||
this.recurrenceForm.get('weekly.byDay').setValue([eventStartDay], {emitEvent: false});
|
||||
}
|
||||
});
|
||||
|
||||
// Patch the form with the values
|
||||
this.recurrenceForm.patchValue(this.recurrenceFormValues);
|
||||
|
||||
// Set end values for the first time
|
||||
this._setEndValues(this.recurrenceForm.get('freq').value);
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void
|
||||
{
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next();
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Clear
|
||||
*/
|
||||
clear(): void
|
||||
{
|
||||
// Close the dialog
|
||||
this.matDialogRef.close({recurrence: 'cleared'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Done
|
||||
*/
|
||||
done(): void
|
||||
{
|
||||
// Get the recurrence form values
|
||||
const recurrenceForm = this.recurrenceForm.value;
|
||||
|
||||
// Prepare the rule array and add the base rules
|
||||
const ruleArr = ['FREQ=' + recurrenceForm.freq, 'INTERVAL=' + recurrenceForm.interval];
|
||||
|
||||
// If monthly on certain days...
|
||||
if ( recurrenceForm.freq === 'MONTHLY' && recurrenceForm.monthly.repeatOn === 'nthWeekday' )
|
||||
{
|
||||
ruleArr.push('BYDAY=' + recurrenceForm.monthly.nthWeekday);
|
||||
}
|
||||
|
||||
// If weekly...
|
||||
if ( recurrenceForm.freq === 'WEEKLY' )
|
||||
{
|
||||
// If byDay is an array...
|
||||
if ( Array.isArray(recurrenceForm.weekly.byDay) )
|
||||
{
|
||||
ruleArr.push('BYDAY=' + recurrenceForm.weekly.byDay.join(','));
|
||||
}
|
||||
// Otherwise
|
||||
else
|
||||
{
|
||||
ruleArr.push('BYDAY=' + recurrenceForm.weekly.byDay);
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the end options is selected...
|
||||
if ( recurrenceForm.end.type === 'until' )
|
||||
{
|
||||
ruleArr.push('UNTIL=' + moment(recurrenceForm.end.until).endOf('day').utc().format('YYYYMMDD[T]HHmmss[Z]'));
|
||||
}
|
||||
|
||||
if ( recurrenceForm.end.type === 'count' )
|
||||
{
|
||||
ruleArr.push('COUNT=' + recurrenceForm.end.count);
|
||||
}
|
||||
|
||||
// Generate rule text
|
||||
const ruleText = ruleArr.join(';');
|
||||
|
||||
// Close the dialog
|
||||
this.matDialogRef.close({recurrence: ruleText});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _init(): void
|
||||
{
|
||||
// Get the event's start date
|
||||
const startDate = moment(this.data.event.start);
|
||||
|
||||
// Calculate the weekday
|
||||
const weekday = moment(this.data.event.start).format('dd').toUpperCase();
|
||||
|
||||
// Calculate the nthWeekday
|
||||
let nthWeekdayNo = 1;
|
||||
while ( startDate.clone().isSame(startDate.clone().subtract(nthWeekdayNo, 'week'), 'month') )
|
||||
{
|
||||
nthWeekdayNo++;
|
||||
}
|
||||
const nthWeekday = nthWeekdayNo + weekday;
|
||||
|
||||
// Calculate the nthWeekday as text
|
||||
const ordinalNumberSuffixes = {
|
||||
1: 'st',
|
||||
2: 'nd',
|
||||
3: 'rd',
|
||||
4: 'th',
|
||||
5: 'th'
|
||||
};
|
||||
this.nthWeekdayText = nthWeekday.slice(0, 1) + ordinalNumberSuffixes[nthWeekday.slice(0, 1)] + ' ' +
|
||||
this.weekdays.find(item => item.value === nthWeekday.slice(-2)).label;
|
||||
|
||||
// Set the defaults on recurrence form values
|
||||
this.recurrenceFormValues = {
|
||||
freq : 'DAILY',
|
||||
interval: 1,
|
||||
weekly : {
|
||||
byDay: weekday
|
||||
},
|
||||
monthly : {
|
||||
repeatOn : 'date',
|
||||
date : moment(this.data.event.start).date(),
|
||||
nthWeekday: nthWeekday
|
||||
},
|
||||
end : {
|
||||
type : 'never',
|
||||
until: null,
|
||||
count: null
|
||||
}
|
||||
};
|
||||
|
||||
// If recurrence rule string is available on the
|
||||
// event meaning that the is a recurring one...
|
||||
if ( this.data.event.recurrence )
|
||||
{
|
||||
// Parse the rules
|
||||
const parsedRules: any = {};
|
||||
this.data.event.recurrence.split(';').forEach((rule) => {
|
||||
parsedRules[rule.split('=')[0]] = rule.split('=')[1];
|
||||
});
|
||||
|
||||
// Overwrite the recurrence form values
|
||||
this.recurrenceFormValues.freq = parsedRules.FREQ;
|
||||
this.recurrenceFormValues.interval = parsedRules.INTERVAL;
|
||||
|
||||
if ( parsedRules.FREQ === 'WEEKLY' )
|
||||
{
|
||||
this.recurrenceFormValues.weekly.byDay = parsedRules.BYDAY.split(',');
|
||||
}
|
||||
|
||||
if ( parsedRules.FREQ === 'MONTHLY' )
|
||||
{
|
||||
this.recurrenceFormValues.monthly.repeatOn = parsedRules.BYDAY ? 'nthWeekday' : 'date';
|
||||
}
|
||||
|
||||
this.recurrenceFormValues.end.type = parsedRules.UNTIL ? 'until' : (parsedRules.COUNT ? 'count' : 'never');
|
||||
this.recurrenceFormValues.end.until = parsedRules.UNTIL || null;
|
||||
this.recurrenceFormValues.end.count = parsedRules.COUNT || null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the end value based on frequency
|
||||
*
|
||||
* @param freq
|
||||
* @private
|
||||
*/
|
||||
private _setEndValues(freq: string): void
|
||||
{
|
||||
// Return if freq is not available
|
||||
if ( !freq )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the event's start date
|
||||
const startDate = moment(this.data.event.startDate);
|
||||
|
||||
// Get the end type
|
||||
const endType = this.recurrenceForm.get('end.type').value;
|
||||
|
||||
// If until is not selected
|
||||
if ( endType !== 'until' )
|
||||
{
|
||||
let until;
|
||||
|
||||
// Change the until's default value based on the frequency
|
||||
if ( freq === 'DAILY' )
|
||||
{
|
||||
until = startDate.clone().add(1, 'month').toISOString();
|
||||
}
|
||||
|
||||
if ( freq === 'WEEKLY' )
|
||||
{
|
||||
until = startDate.clone().add(12, 'weeks').toISOString();
|
||||
}
|
||||
|
||||
if ( freq === 'MONTHLY' )
|
||||
{
|
||||
until = startDate.clone().add(12, 'months').toISOString();
|
||||
}
|
||||
|
||||
if ( freq === 'YEARLY' )
|
||||
{
|
||||
until = startDate.clone().add(5, 'years').toISOString();
|
||||
}
|
||||
|
||||
// Set the until
|
||||
this.recurrenceForm.get('end.until').setValue(until);
|
||||
}
|
||||
|
||||
// If count is not selected...
|
||||
if ( endType !== 'count' )
|
||||
{
|
||||
let count;
|
||||
|
||||
// Change the count's default value based on the frequency
|
||||
if ( freq === 'DAILY' )
|
||||
{
|
||||
count = 30;
|
||||
}
|
||||
|
||||
if ( freq === 'WEEKLY' || freq === 'MONTHLY' )
|
||||
{
|
||||
count = 12;
|
||||
}
|
||||
|
||||
if ( freq === 'YEARLY' )
|
||||
{
|
||||
count = 5;
|
||||
}
|
||||
|
||||
// Set the count
|
||||
this.recurrenceForm.get('end.count').setValue(count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<div
|
||||
class="absolute inset-0 flex flex-col min-w-0 overflow-y-auto"
|
||||
cdkScrollable>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="flex flex-col flex-auto">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center h-16 px-4 sm:px-6 py-2 border-b">
|
||||
<a
|
||||
[routerLink]="['..']"
|
||||
mat-icon-button>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
||||
</a>
|
||||
<div class="ml-1 text-lg font-medium">Settings</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-auto p-6 sm:p-8">
|
||||
<form
|
||||
class="flex flex-col w-full max-w-xs"
|
||||
[formGroup]="settingsForm">
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Date format</mat-label>
|
||||
<mat-select [formControlName]="'dateFormat'">
|
||||
<mat-option [value]="'ll'">Aug 20, {{year}}</mat-option>
|
||||
<mat-option [value]="'MM/DD/YYYY'">12/31/{{year}}</mat-option>
|
||||
<mat-option [value]="'DD/MM/YYYY'">31/12/{{year}}</mat-option>
|
||||
<mat-option [value]="'YYYY-MM-DD'">{{year}}-12-31</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Time format</mat-label>
|
||||
<mat-select [formControlName]="'timeFormat'">
|
||||
<mat-option [value]="'12'">1:00pm</mat-option>
|
||||
<mat-option [value]="'24'">13:30</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Start week on</mat-label>
|
||||
<mat-select [formControlName]="'startWeekOn'">
|
||||
<mat-option [value]="6">Saturday</mat-option>
|
||||
<mat-option [value]="0">Sunday</mat-option>
|
||||
<mat-option [value]="1">Monday</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
class="mt-4"
|
||||
mat-flat-button
|
||||
[color]="'primary'"
|
||||
[disabled]="settingsForm.invalid || settingsForm.pristine"
|
||||
(click)="updateSettings()">
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,96 +0,0 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { CalendarService } from 'app/modules/admin/apps/calendar/calendar.service';
|
||||
|
||||
@Component({
|
||||
selector : 'calendar-settings',
|
||||
templateUrl : './settings.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation : ViewEncapsulation.None
|
||||
})
|
||||
export class CalendarSettingsComponent implements OnInit, OnDestroy
|
||||
{
|
||||
settingsForm: FormGroup;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _calendarService: CalendarService,
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _formBuilder: FormBuilder
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Getter for current year
|
||||
*/
|
||||
get year(): string
|
||||
{
|
||||
return new Date().getFullYear().toString();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Create the event form
|
||||
this.settingsForm = this._formBuilder.group({
|
||||
dateFormat : [''],
|
||||
timeFormat : [''],
|
||||
startWeekOn: ['']
|
||||
});
|
||||
|
||||
// Get settings
|
||||
this._calendarService.settings$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((settings) => {
|
||||
|
||||
// Fill the settings form
|
||||
this.settingsForm.patchValue(settings);
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void
|
||||
{
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next();
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
updateSettings(): void
|
||||
{
|
||||
// Get the settings
|
||||
const settings = this.settingsForm.value;
|
||||
|
||||
// Update the settings on the server
|
||||
this._calendarService.updateSettings(settings).subscribe((updatedSettings) => {
|
||||
|
||||
// Reset the form with the updated settings
|
||||
this.settingsForm.reset(updatedSettings);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
export const calendarColors = [
|
||||
'bg-gray-500',
|
||||
'bg-red-500',
|
||||
'bg-orange-500',
|
||||
'bg-yellow-500',
|
||||
'bg-green-500',
|
||||
'bg-teal-500',
|
||||
'bg-blue-500',
|
||||
'bg-indigo-500',
|
||||
'bg-purple-500',
|
||||
'bg-pink-500'
|
||||
];
|
|
@ -1,117 +0,0 @@
|
|||
<div class="flex flex-col flex-auto min-h-full p-8">
|
||||
<div class="pb-6 text-4xl font-extrabold tracking-tight">Calendar</div>
|
||||
|
||||
<!-- Calendars -->
|
||||
<div class="group flex items-center justify-between mb-3">
|
||||
<span class="text-lg font-medium">Calendars</span>
|
||||
<mat-icon
|
||||
class="hidden group-hover:inline-flex icon-size-5 cursor-pointer"
|
||||
[svgIcon]="'heroicons_solid:plus-circle'"
|
||||
(click)="addCalendar()"></mat-icon>
|
||||
</div>
|
||||
<ng-container *ngFor="let calendar of calendars">
|
||||
<div class="group flex items-center justify-between mt-2">
|
||||
<div
|
||||
class="flex items-center"
|
||||
(click)="toggleCalendarVisibility(calendar)">
|
||||
<mat-icon
|
||||
class="cursor-pointer"
|
||||
[svgIcon]="calendar.visible ? 'check_box' : 'check_box_outline_blank'"></mat-icon>
|
||||
<span
|
||||
class="w-3 h-3 ml-2 rounded-full"
|
||||
[ngClass]="calendar.color"></span>
|
||||
<span class="ml-2 leading-none">{{calendar.title}}</span>
|
||||
</div>
|
||||
<mat-icon
|
||||
class="hidden group-hover:inline-flex icon-size-5 cursor-pointer"
|
||||
[svgIcon]="'heroicons_solid:pencil-alt'"
|
||||
(click)="openEditPanel(calendar)"></mat-icon>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="-mx-4 mt-auto">
|
||||
<a
|
||||
class="flex items-center w-full py-3 px-4 rounded-full hover:bg-hover"
|
||||
[routerLink]="['settings']">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:cog'"></mat-icon>
|
||||
<span class="ml-2 font-medium leading-none">Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Edit panel -->
|
||||
<ng-template #editPanel>
|
||||
<div class="flex flex-col w-80 p-8 shadow-2xl rounded-lg bg-card">
|
||||
<div class="text-2xl font-semibold tracking-tight">
|
||||
<ng-container *ngIf="!calendar.id">Add calendar</ng-container>
|
||||
<ng-container *ngIf="calendar.id">Edit calendar</ng-container>
|
||||
</div>
|
||||
<div class="flex items-center mt-8">
|
||||
<mat-form-field class="fuse-mat-no-subscript w-full">
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="calendar.title"
|
||||
[placeholder]="'Title'"
|
||||
required>
|
||||
<mat-select
|
||||
[(value)]="calendar.color"
|
||||
[disableOptionCentering]="true"
|
||||
matPrefix>
|
||||
<mat-select-trigger class="h-6">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:color-swatch'"></mat-icon>
|
||||
</mat-select-trigger>
|
||||
<div class="px-4 pt-5 text-xl font-semibold">Calendar color</div>
|
||||
<div class="flex flex-wrap w-48 my-4 mx-3 -mr-5">
|
||||
<ng-container *ngFor="let color of calendarColors">
|
||||
<mat-option
|
||||
class="relative flex w-12 h-12 p-0 cursor-pointer rounded-full bg-transparent"
|
||||
[value]="color"
|
||||
#matOption="matOption">
|
||||
<mat-icon
|
||||
class="absolute m-3 text-white"
|
||||
*ngIf="matOption.selected"
|
||||
[svgIcon]="'heroicons_outline:check'"></mat-icon>
|
||||
<span
|
||||
class="flex w-10 h-10 m-1 rounded-full"
|
||||
[ngClass]="color"></span>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</div>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="ml-auto mt-8 space-x-2">
|
||||
<button
|
||||
mat-button
|
||||
*ngIf="calendar.id"
|
||||
(click)="deleteCalendar(calendar)">
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
*ngIf="calendar.id"
|
||||
[color]="'primary'"
|
||||
[disabled]="!calendar.title"
|
||||
(click)="saveCalendar(calendar)">
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
*ngIf="!calendar.id"
|
||||
(click)="closeEditPanel()">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
*ngIf="!calendar.id"
|
||||
[color]="'primary'"
|
||||
[disabled]="!calendar.title"
|
||||
(click)="saveCalendar(calendar)">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
|
@ -1,218 +0,0 @@
|
|||
import { Component, EventEmitter, OnDestroy, OnInit, Output, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
|
||||
import { TemplatePortal } from '@angular/cdk/portal';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Calendar } from 'app/modules/admin/apps/calendar/calendar.types';
|
||||
import { CalendarService } from 'app/modules/admin/apps/calendar/calendar.service';
|
||||
import { calendarColors } from 'app/modules/admin/apps/calendar/sidebar/calendar-colors';
|
||||
|
||||
@Component({
|
||||
selector : 'calendar-sidebar',
|
||||
templateUrl : './sidebar.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class CalendarSidebarComponent implements OnInit, OnDestroy
|
||||
{
|
||||
@Output() readonly calendarUpdated: EventEmitter<any> = new EventEmitter<any>();
|
||||
@ViewChild('editPanel') private _editPanel: TemplateRef<any>;
|
||||
|
||||
calendar: Calendar | null;
|
||||
calendarColors: any = calendarColors;
|
||||
calendars: Calendar[];
|
||||
private _editPanelOverlayRef: OverlayRef;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _calendarService: CalendarService,
|
||||
private _overlay: Overlay,
|
||||
private _viewContainerRef: ViewContainerRef
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Get calendars
|
||||
this._calendarService.calendars$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((calendars) => {
|
||||
|
||||
// Store the calendars
|
||||
this.calendars = calendars;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void
|
||||
{
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next();
|
||||
this._unsubscribeAll.complete();
|
||||
|
||||
// Dispose the overlay
|
||||
if ( this._editPanelOverlayRef )
|
||||
{
|
||||
this._editPanelOverlayRef.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open edit panel
|
||||
*/
|
||||
openEditPanel(calendar: Calendar): void
|
||||
{
|
||||
// Set the calendar
|
||||
this.calendar = cloneDeep(calendar);
|
||||
|
||||
// Create the overlay if it doesn't exist
|
||||
if ( !this._editPanelOverlayRef )
|
||||
{
|
||||
this._createEditPanelOverlay();
|
||||
}
|
||||
|
||||
// Attach the portal to the overlay
|
||||
this._editPanelOverlayRef.attach(new TemplatePortal(this._editPanel, this._viewContainerRef));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the edit panel
|
||||
*/
|
||||
closeEditPanel(): void
|
||||
{
|
||||
// Detach the overlay from the portal
|
||||
if ( this._editPanelOverlayRef )
|
||||
{
|
||||
this._editPanelOverlayRef.detach();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the calendar visibility
|
||||
*
|
||||
* @param calendar
|
||||
*/
|
||||
toggleCalendarVisibility(calendar: Calendar): void
|
||||
{
|
||||
// Toggle the visibility
|
||||
calendar.visible = !calendar.visible;
|
||||
|
||||
// Update the calendar
|
||||
this.saveCalendar(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add calendar
|
||||
*/
|
||||
addCalendar(): void
|
||||
{
|
||||
// Create a new calendar with default values
|
||||
const calendar = {
|
||||
id : null,
|
||||
title : '',
|
||||
color : 'bg-blue-500',
|
||||
visible: true
|
||||
};
|
||||
|
||||
// Open the edit panel
|
||||
this.openEditPanel(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the calendar
|
||||
*
|
||||
* @param calendar
|
||||
*/
|
||||
saveCalendar(calendar: Calendar): void
|
||||
{
|
||||
// If there is no id on the calendar...
|
||||
if ( !calendar.id )
|
||||
{
|
||||
// Add calendar to the server
|
||||
this._calendarService.addCalendar(calendar).subscribe(() => {
|
||||
|
||||
// Close the edit panel
|
||||
this.closeEditPanel();
|
||||
|
||||
// Emit the calendarUpdated event
|
||||
this.calendarUpdated.emit();
|
||||
});
|
||||
}
|
||||
// Otherwise...
|
||||
else
|
||||
{
|
||||
// Update the calendar on the server
|
||||
this._calendarService.updateCalendar(calendar.id, calendar).subscribe(() => {
|
||||
|
||||
// Close the edit panel
|
||||
this.closeEditPanel();
|
||||
|
||||
// Emit the calendarUpdated event
|
||||
this.calendarUpdated.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the calendar
|
||||
*
|
||||
* @param calendar
|
||||
*/
|
||||
deleteCalendar(calendar: Calendar): void
|
||||
{
|
||||
// Delete the calendar on the server
|
||||
this._calendarService.deleteCalendar(calendar.id).subscribe(() => {
|
||||
|
||||
// Close the edit panel
|
||||
this.closeEditPanel();
|
||||
|
||||
// Emit the calendarUpdated event
|
||||
this.calendarUpdated.emit();
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create the edit panel overlay
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _createEditPanelOverlay(): void
|
||||
{
|
||||
// Create the overlay
|
||||
this._editPanelOverlayRef = this._overlay.create({
|
||||
hasBackdrop : true,
|
||||
scrollStrategy : this._overlay.scrollStrategies.reposition(),
|
||||
positionStrategy: this._overlay.position()
|
||||
.global()
|
||||
.centerHorizontally()
|
||||
.centerVertically()
|
||||
});
|
||||
|
||||
// Detach the overlay from the portal on backdrop click
|
||||
this._editPanelOverlayRef.backdropClick().subscribe(() => {
|
||||
this.closeEditPanel();
|
||||
this.calendar = null;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
<div class="flex flex-col flex-auto min-w-0">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between p-6 sm:py-8 sm:px-10 border-b bg-card dark:bg-transparent">
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="flex flex-wrap items-center font-medium">
|
||||
<div>
|
||||
<a class="whitespace-nowrap text-primary-500">Documentation</a>
|
||||
</div>
|
||||
<div class="flex items-center ml-1 whitespace-nowrap">
|
||||
<mat-icon
|
||||
class="icon-size-5 text-secondary"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
<a class="ml-1 text-primary-500">Fuse Components</a>
|
||||
</div>
|
||||
<div class="flex items-center ml-1 whitespace-nowrap">
|
||||
<mat-icon
|
||||
class="icon-size-5 text-secondary"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
<span class="ml-1 text-secondary">Components</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Title -->
|
||||
<div class="mt-2">
|
||||
<h2 class="text-3xl md:text-4xl font-extrabold tracking-tight leading-7 sm:leading-10 truncate">
|
||||
Date Range
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="-ml-3 sm:ml-0 mb-2 sm:mb-0 order-first sm:order-last"
|
||||
mat-icon-button
|
||||
(click)="toggleDrawer()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-auto max-w-3xl p-6 sm:p-10 prose prose-sm">
|
||||
|
||||
<p>
|
||||
<strong>fuse-date-range</strong> is a date-time range selector component. It can be programmed to provide date or date-time ranges. It has full
|
||||
<strong>ngModel</strong> and <strong>reactive form</strong> support and built to works with <strong>moment.js</strong>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Exported as: </strong><code>fuseDateRange</code>
|
||||
</p>
|
||||
|
||||
<h2>Module</h2>
|
||||
<!-- @formatter:off -->
|
||||
<textarea
|
||||
fuse-highlight
|
||||
lang="typescript">
|
||||
import { FuseDateRangeModule } from '@fuse/components/date-range';
|
||||
</textarea>
|
||||
<!-- @formatter:on -->
|
||||
|
||||
<h2>Usage</h2>
|
||||
<p>
|
||||
Here's the basic usage of the <code>fuse-date-range</code>:
|
||||
</p>
|
||||
<!-- @formatter:off -->
|
||||
<textarea fuse-highlight
|
||||
lang="html">
|
||||
<fuse-date-range [formControlName]="'range'"
|
||||
[dateFormat]="settings.dateFormat"
|
||||
[timeRange]="!eventForm.get('allDay').value"
|
||||
[timeFormat]="settings.timeFormat"></fuse-date-range>
|
||||
</textarea>
|
||||
<!-- @formatter:on -->
|
||||
|
||||
<h2>Properties</h2>
|
||||
<div class="bg-card py-3 px-6 rounded shadow">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono text-md text-secondary">
|
||||
<div>@Input()</div>
|
||||
<div>dateFormat: string</div>
|
||||
</td>
|
||||
<td>
|
||||
Moment.js date format string to format output date.
|
||||
</td>
|
||||
<td>
|
||||
<code class="whitespace-nowrap">DD/MM/YYYY</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono text-md text-secondary">
|
||||
<div>@Input()</div>
|
||||
<div>timeFormat: string</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong>12</strong> for 12-hour, <strong>24</strong> for 24-hour format.
|
||||
</td>
|
||||
<td>
|
||||
<code class="whitespace-nowrap">12</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono text-md text-secondary">
|
||||
<div>@Input()</div>
|
||||
<div>timeRange: boolean</div>
|
||||
</td>
|
||||
<td>
|
||||
Whether to enable time range.
|
||||
</td>
|
||||
<td>
|
||||
<code class="whitespace-nowrap">true</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono text-md text-secondary">
|
||||
<div>@Input()</div>
|
||||
<div>range: any</div>
|
||||
</td>
|
||||
<td>
|
||||
Date range input <code>{{'{'}} start: string, end: string {{'}'}}</code>. If you are using <strong>ngModel</strong> or <strong>Reactive
|
||||
forms</strong>, you shouldn't use this input!
|
||||
</td>
|
||||
<td>
|
||||
<code class="whitespace-nowrap">true</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Range</h2>
|
||||
<p>
|
||||
The input of the range must be in the following format. The <strong>start</strong> and <strong>end</strong> date strings must be moment compatible strings as they
|
||||
will be immediately parsed with MomentJS.
|
||||
|
||||
</p>
|
||||
<!-- @formatter:off -->
|
||||
<textarea fuse-highlight
|
||||
lang="typescript">
|
||||
// Input format
|
||||
{
|
||||
start: string,
|
||||
end : string
|
||||
}
|
||||
</textarea>
|
||||
<!-- @formatter:on -->
|
||||
<p>
|
||||
The outputted range object will be in the following format. The <strong>date</strong> and <strong>time</strong> fields will be formatted based on the
|
||||
<em>dateFormat</em> and <em>timeFormat</em> inputs.
|
||||
</p>
|
||||
<!-- @formatter:off -->
|
||||
<textarea fuse-highlight
|
||||
lang="typescript">
|
||||
// Output format
|
||||
{
|
||||
startDate: string,
|
||||
startTime: null | string,
|
||||
endDate : string,
|
||||
endTime : null | string
|
||||
}
|
||||
</textarea>
|
||||
<!-- @formatter:on -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,29 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { FuseComponentsComponent } from 'app/modules/admin/ui/fuse-components/fuse-components.component';
|
||||
|
||||
@Component({
|
||||
selector : 'date-range',
|
||||
templateUrl: './date-range.component.html'
|
||||
})
|
||||
export class DateRangeComponent
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseComponentsComponent: FuseComponentsComponent)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Toggle the drawer
|
||||
*/
|
||||
toggleDrawer(): void
|
||||
{
|
||||
// Toggle the drawer
|
||||
this._fuseComponentsComponent.matDrawer.toggle();
|
||||
}
|
||||
}
|
|
@ -128,13 +128,6 @@ export class NavigationComponent
|
|||
icon : 'insert_chart',
|
||||
link : '/supported-components/apex-charts'
|
||||
},
|
||||
{
|
||||
id : 'supported-components.full-calendar',
|
||||
title: 'FullCalendar',
|
||||
type : 'basic',
|
||||
icon : 'today',
|
||||
link : '/supported-components/full-calendar'
|
||||
},
|
||||
{
|
||||
id : 'supported-components.google-maps',
|
||||
title: 'Google Maps',
|
||||
|
|
|
@ -57,12 +57,6 @@ export class FuseComponentsComponent implements OnInit, OnDestroy
|
|||
type : 'basic',
|
||||
link : '/ui/fuse-components/components/card'
|
||||
},
|
||||
{
|
||||
id : 'fuse-components.components.date-range',
|
||||
title: 'DateRange',
|
||||
type : 'basic',
|
||||
link : '/ui/fuse-components/components/date-range'
|
||||
},
|
||||
{
|
||||
id : 'fuse-components.components.drawer',
|
||||
title: 'Drawer',
|
||||
|
|
|
@ -12,7 +12,6 @@ import { MatTabsModule } from '@angular/material/tabs';
|
|||
import { MatTreeModule } from '@angular/material/tree';
|
||||
import { FuseAlertModule } from '@fuse/components/alert';
|
||||
import { FuseCardModule } from '@fuse/components/card';
|
||||
import { FuseDateRangeModule } from '@fuse/components/date-range';
|
||||
import { FuseDrawerModule } from '@fuse/components/drawer';
|
||||
import { FuseHighlightModule } from '@fuse/components/highlight';
|
||||
import { FuseLoadingBarModule } from '@fuse/components/loading-bar';
|
||||
|
@ -24,7 +23,6 @@ import { FuseComponentsComponent } from 'app/modules/admin/ui/fuse-components/fu
|
|||
import { MockApiComponent } from 'app/modules/admin/ui/fuse-components/libraries/mock-api/mock-api.component';
|
||||
import { AlertComponent } from 'app/modules/admin/ui/fuse-components/components/alert/alert.component';
|
||||
import { CardComponent } from 'app/modules/admin/ui/fuse-components/components/card/card.component';
|
||||
import { DateRangeComponent } from 'app/modules/admin/ui/fuse-components/components/date-range/date-range.component';
|
||||
import { DrawerComponent } from 'app/modules/admin/ui/fuse-components/components/drawer/drawer.component';
|
||||
import { FullscreenComponent } from 'app/modules/admin/ui/fuse-components/components/fullscreen/fullscreen.component';
|
||||
import { HighlightComponent } from 'app/modules/admin/ui/fuse-components/components/highlight/highlight.component';
|
||||
|
@ -47,7 +45,6 @@ import { fuseComponentsRoutes } from 'app/modules/admin/ui/fuse-components/fuse-
|
|||
MockApiComponent,
|
||||
AlertComponent,
|
||||
CardComponent,
|
||||
DateRangeComponent,
|
||||
DrawerComponent,
|
||||
FullscreenComponent,
|
||||
HighlightComponent,
|
||||
|
@ -77,7 +74,6 @@ import { fuseComponentsRoutes } from 'app/modules/admin/ui/fuse-components/fuse-
|
|||
MatTreeModule,
|
||||
FuseAlertModule,
|
||||
FuseCardModule,
|
||||
FuseDateRangeModule,
|
||||
FuseDrawerModule,
|
||||
FuseHighlightModule,
|
||||
FuseLoadingBarModule,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { FuseComponentsComponent } from 'app/modules/admin/ui/fuse-components/fu
|
|||
import { MockApiComponent } from 'app/modules/admin/ui/fuse-components/libraries/mock-api/mock-api.component';
|
||||
import { AlertComponent } from 'app/modules/admin/ui/fuse-components/components/alert/alert.component';
|
||||
import { CardComponent } from 'app/modules/admin/ui/fuse-components/components/card/card.component';
|
||||
import { DateRangeComponent } from 'app/modules/admin/ui/fuse-components/components/date-range/date-range.component';
|
||||
import { DrawerComponent } from 'app/modules/admin/ui/fuse-components/components/drawer/drawer.component';
|
||||
import { FullscreenComponent } from 'app/modules/admin/ui/fuse-components/components/fullscreen/fullscreen.component';
|
||||
import { HighlightComponent } from 'app/modules/admin/ui/fuse-components/components/highlight/highlight.component';
|
||||
|
@ -54,10 +53,6 @@ export const fuseComponentsRoutes: Route[] = [
|
|||
path : 'card',
|
||||
component: CardComponent
|
||||
},
|
||||
{
|
||||
path : 'date-range',
|
||||
component: DateRangeComponent
|
||||
},
|
||||
{
|
||||
path : 'drawer',
|
||||
component: DrawerComponent
|
||||
|
|
|
@ -101,12 +101,6 @@ export class OtherComponentsComponent implements OnInit, OnDestroy
|
|||
type : 'basic',
|
||||
link : '/ui/other-components/third-party/apex-charts'
|
||||
},
|
||||
{
|
||||
id : 'other-components.third-party.full-calendar',
|
||||
title: 'FullCalendar',
|
||||
type : 'basic',
|
||||
link : '/ui/other-components/third-party/full-calendar'
|
||||
},
|
||||
{
|
||||
id : 'other-components.third-party.ngx-markdown',
|
||||
title: 'ngx-markdown',
|
||||
|
|
|
@ -18,7 +18,6 @@ import { SearchComponent } from 'app/modules/admin/ui/other-components/common/se
|
|||
import { ShortcutsComponent } from 'app/modules/admin/ui/other-components/common/shortcuts/shortcuts.component';
|
||||
import { UserComponent } from 'app/modules/admin/ui/other-components/common/user/user.component';
|
||||
import { ApexChartsComponent } from 'app/modules/admin/ui/other-components/third-party/apex-charts/apex-charts.component';
|
||||
import { FullCalendarComponent } from 'app/modules/admin/ui/other-components/third-party/full-calendar/full-calendar.component';
|
||||
import { NgxMarkdownComponent } from 'app/modules/admin/ui/other-components/third-party/ngx-markdown/ngx-markdown.component';
|
||||
import { QuillEditorComponent } from 'app/modules/admin/ui/other-components/third-party/quill-editor/quill-editor.component';
|
||||
import { otherComponentsRoutes } from 'app/modules/admin/ui/other-components/other-components.routing';
|
||||
|
@ -35,7 +34,6 @@ import { otherComponentsRoutes } from 'app/modules/admin/ui/other-components/oth
|
|||
ShortcutsComponent,
|
||||
UserComponent,
|
||||
ApexChartsComponent,
|
||||
FullCalendarComponent,
|
||||
NgxMarkdownComponent,
|
||||
QuillEditorComponent
|
||||
],
|
||||
|
|
|
@ -9,7 +9,6 @@ import { SearchComponent } from 'app/modules/admin/ui/other-components/common/se
|
|||
import { ShortcutsComponent } from 'app/modules/admin/ui/other-components/common/shortcuts/shortcuts.component';
|
||||
import { UserComponent } from 'app/modules/admin/ui/other-components/common/user/user.component';
|
||||
import { ApexChartsComponent } from 'app/modules/admin/ui/other-components/third-party/apex-charts/apex-charts.component';
|
||||
import { FullCalendarComponent } from 'app/modules/admin/ui/other-components/third-party/full-calendar/full-calendar.component';
|
||||
import { NgxMarkdownComponent } from 'app/modules/admin/ui/other-components/third-party/ngx-markdown/ngx-markdown.component';
|
||||
import { QuillEditorComponent } from 'app/modules/admin/ui/other-components/third-party/quill-editor/quill-editor.component';
|
||||
|
||||
|
@ -77,10 +76,6 @@ export const otherComponentsRoutes: Route[] = [
|
|||
path : 'apex-charts',
|
||||
component: ApexChartsComponent
|
||||
},
|
||||
{
|
||||
path : 'full-calendar',
|
||||
component: FullCalendarComponent
|
||||
},
|
||||
{
|
||||
path : 'ngx-markdown',
|
||||
component: NgxMarkdownComponent
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
<div class="flex flex-col flex-auto min-w-0">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between p-6 sm:py-8 sm:px-10 border-b bg-card dark:bg-transparent">
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="flex flex-wrap items-center font-medium">
|
||||
<div>
|
||||
<a class="whitespace-nowrap text-primary-500">Documentation</a>
|
||||
</div>
|
||||
<div class="flex items-center ml-1 whitespace-nowrap">
|
||||
<mat-icon
|
||||
class="icon-size-5 text-secondary"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
<a class="ml-1 text-primary-500">Other Components</a>
|
||||
</div>
|
||||
<div class="flex items-center ml-1 whitespace-nowrap">
|
||||
<mat-icon
|
||||
class="icon-size-5 text-secondary"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
<span class="ml-1 text-secondary">Third Party</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Title -->
|
||||
<div class="mt-2">
|
||||
<h2 class="text-3xl md:text-4xl font-extrabold tracking-tight leading-7 sm:leading-10 truncate">
|
||||
FullCalendar
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="-ml-3 sm:ml-0 mb-2 sm:mb-0 order-first sm:order-last"
|
||||
mat-icon-button
|
||||
(click)="toggleDrawer()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-auto max-w-3xl p-6 sm:p-10 prose prose-sm">
|
||||
|
||||
<p>
|
||||
<a
|
||||
href="https://fullcalendar.io/"
|
||||
rel="noreferrer"
|
||||
target="_blank">FullCalendar
|
||||
</a>
|
||||
is the most popular full-sized Javascript calendar library. Fuse supports FullCalendar through official
|
||||
<a
|
||||
href="https://github.com/fullcalendar/fullcalendar-angular"
|
||||
rel="noreferrer"
|
||||
target="_blank">fullcalendar-angular
|
||||
</a>
|
||||
component.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>Calendar</strong> demo application is built using <em>FullCalendar</em>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,29 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { OtherComponentsComponent } from 'app/modules/admin/ui/other-components/other-components.component';
|
||||
|
||||
@Component({
|
||||
selector : 'full-calendar',
|
||||
templateUrl: './full-calendar.component.html'
|
||||
})
|
||||
export class FullCalendarComponent
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _otherComponentsComponent: OtherComponentsComponent)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Toggle the drawer
|
||||
*/
|
||||
toggleDrawer(): void
|
||||
{
|
||||
// Toggle the drawer
|
||||
this._otherComponentsComponent.matDrawer.toggle();
|
||||
}
|
||||
}
|
|
@ -2,12 +2,6 @@
|
|||
/* @ Import third party library styles here.
|
||||
/* ----------------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* FullCalendar */
|
||||
@import '~@fullcalendar/core/main.css';
|
||||
@import '~@fullcalendar/daygrid/main.css';
|
||||
@import '~@fullcalendar/timegrid/main.css';
|
||||
@import '~@fullcalendar/list/main.css';
|
||||
|
||||
/* Perfect scrollbar */
|
||||
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user