(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:
sercan
2021-11-05 10:50:19 +03:00
parent f380d8ca16
commit e4ebe2fd7e
43 changed files with 2 additions and 6205 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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
{
}

View File

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

View File

@@ -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
}
}
];

View File

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

View File

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

View File

@@ -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>

View File

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

View File

@@ -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>

View File

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

View File

@@ -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'
];

View File

@@ -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>

View File

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

View File

@@ -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>

View File

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

View File

@@ -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',

View File

@@ -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',

View File

@@ -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,

View File

@@ -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

View File

@@ -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',

View File

@@ -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
],

View File

@@ -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

View File

@@ -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>

View File

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