add month calendar

This commit is contained in:
Roland Schneider 2019-09-27 23:03:03 +02:00 committed by Roland Schneider
parent bee08c2092
commit 51788f2194
33 changed files with 698 additions and 25 deletions

View File

@ -5937,6 +5937,11 @@
}
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -23,6 +23,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.19",
"@fortawesome/free-solid-svg-icons": "^5.9.0",
"bootstrap": "^4.3.1",
"moment": "^2.24.0",
"ngx-bootstrap": "^5.0.0",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",

View File

@ -1,18 +1,17 @@
import {Injectable} from "@angular/core";
import {
HttpRequest,
HttpResponse,
HttpHandler,
HTTP_INTERCEPTORS,
HttpEvent,
HttpHandler,
HttpInterceptor,
HTTP_INTERCEPTORS
HttpRequest,
HttpResponse
} from '@angular/common/http';
import {Observable, of, throwError} from 'rxjs';
import {delay, mergeMap, materialize, dematerialize} from 'rxjs/operators';
import {Event, EventType, Trainer} from "../services/event.service";
import {identifierModuleUrl} from "@angular/compiler";
import {delay, dematerialize, materialize, mergeMap} from 'rxjs/operators';
import {DayToDisplay, Event, EventType, Trainer} from "../services/event.service";
import * as moment from "moment";
;
/*
FAKE BACKEND
@ -66,8 +65,7 @@ export class FakeBackendInterceptor implements HttpInterceptor {
name: "weight lift Trainer"
};
let events: Event[] = [
];
let events: Event[] = [];
let id = 1000;
for (let dayIndex = 0; dayIndex < 28; dayIndex++) {
@ -113,7 +111,7 @@ export class FakeBackendInterceptor implements HttpInterceptor {
firstName: testUser.firstName,
lastName: testUser.lastName,
token: '0000-fake-jwt-token-0000'
}
};
return of(new HttpResponse({status: 200, body}))
} else {
@ -161,6 +159,60 @@ export class FakeBackendInterceptor implements HttpInterceptor {
});
}
}
if (request.url.endsWith('/events/available') && request.method === 'GET') {
const data = {
days: [],
events: []
};
moment.locale("hu");
let _moment = moment();
const today = _moment.startOf('day');
const startOfWeek = _moment.clone().startOf("week").startOf('day');
const nextDays :DayToDisplay[] = [];
for (let i = 0; i < 21; i++) {
let active = false;
let nextDay = startOfWeek.clone().add(i, 'd').startOf("day");
if (nextDay.isAfter(today) || nextDay.isSameOrAfter(today)) {
active = true;
}
nextDays.push(
{
date: nextDay.toDate().getTime(),
active: active
} as DayToDisplay
);
}
data.days = nextDays;
nextDays.forEach(value => {
value.events = [];
const startOfDay = value.date
const endOfDay = moment(startOfDay).add(1,'d').toDate().getTime();
events.forEach( event => {
if ( event.start >= value.date && event.start < endOfDay){
value.events.push(event);
}
} )
});
//
// const until = nextDays[nextDays.length - 1].toDate().getTime();
// const availableEvents = events.filter(value => {
// return value.start < until;
// });
//
// nextDays.forEach(value => {
// data.days.push(value.toDate().getTime());
// });
// data.events = availableEvents;
return of(new HttpResponse({status: 200, body: data}));
}
if (request.url.indexOf('/events') >= 0 && request.method === 'GET') {
@ -232,7 +284,6 @@ export class FakeBackendInterceptor implements HttpInterceptor {
}
}
// creating a PROVIDER
export let fakeBackendProvider = {
// use fake backend in place of Http service for backend-less development

View File

@ -1,3 +1,4 @@
export * from './error.interceptor';
export * from './fake-backend';
export * from './jwt.interceptor';
export {DayToDisplay} from "../services/event.service";

View File

@ -5,6 +5,7 @@ import {LoginComponent} from "./pages/login/login.component";
import {ProfileComponent} from "./pages/profile/profile.component";
import {AuthGuard} from "./_guards";
import {EventsComponent} from "./pages/events/events.component";
import {EventDetailsComponent} from "./pages/event-details/event-details.component";
const routes: Routes = [
{ path: 'home', component: HomeComponent },
@ -13,6 +14,7 @@ const routes: Routes = [
{ path: 'logout', redirectTo: '/login' , pathMatch: 'full'},
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] },
{ path: 'events', component: EventsComponent, canActivate: [AuthGuard] },
{ path: 'event-details/:idEvent', component: EventDetailsComponent, canActivate: [AuthGuard] , pathMatch: 'full' },
];
@NgModule({

View File

@ -23,6 +23,11 @@ import {FontAwesomeModule} from "@fortawesome/angular-fontawesome";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faUserPlus , faUserMinus } from '@fortawesome/free-solid-svg-icons';
import { FitWeekdaySelectorComponent } from './components/fit-weekday-selector/fit-weekday-selector.component';
import { MonthCalendarComponent } from './components/month-calendar/month-calendar.component';
import { MonthCalendarDayComponent } from './components/month-calendar-day/month-calendar-day.component';
import { MonthCalendarEventComponent } from './components/month-calendar-event/month-calendar-event.component';
import { EventDetailsComponent } from './pages/event-details/event-details.component';
@ -40,6 +45,11 @@ registerLocaleData(localeHu, 'hu');
ProfileComponent,
EventsComponent,
FitEventTypesComponent,
FitWeekdaySelectorComponent,
MonthCalendarComponent,
MonthCalendarDayComponent,
MonthCalendarEventComponent,
EventDetailsComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,11 @@
<p>fit-weekday-selector works!</p>
<div *ngIf="days" class="row">
<div class="col-md-3" *ngFor="let nextDay of days ">
<p>
{{ formatDayName( nextDay ) }}
</p>
<p>
{{ formatDate( nextDay ) }}
</p>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FitWeekdaySelectorComponent } from './fit-weekday-selector.component';
describe('FitWeekdaySelectorComponent', () => {
let component: FitWeekdaySelectorComponent;
let fixture: ComponentFixture<FitWeekdaySelectorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FitWeekdaySelectorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FitWeekdaySelectorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,29 @@
import {Component, Input, OnInit} from '@angular/core';
import * as moment from 'moment';
@Component({
selector: 'app-fit-weekday-selector',
templateUrl: './fit-weekday-selector.component.html',
styleUrls: ['./fit-weekday-selector.component.scss']
})
export class FitWeekdaySelectorComponent implements OnInit {
@Input()
days: number[] = [];
constructor() { }
ngOnInit() {
moment.locale("hu");
}
formatDayName(dateNum){
return moment(dateNum).format("dddd");
}
formatDate(dateNum){
return moment(dateNum).format("YYYY-MM-DD");
}
}

View File

@ -0,0 +1,15 @@
<h5 class="row align-items-center">
<span class="date col-1">{{getDateOfDay()}}</span>
<small class="col d-sm-none text-center text-muted">{{getDayOfWeekName()}}</small>
<span class="col-1"></span>
</h5>
<p *ngIf="!hasEvents()" class="d-sm-none">No events</p>
<ng-container *ngIf="hasEvents()">
<app-month-calendar-event
*ngFor="let event of day.events"
[event]="event"
(onEvent)="handleEvent($event)"
></app-month-calendar-event>
</ng-container>

View File

@ -0,0 +1,20 @@
@media (max-width:575px) {
.display-4 {
font-size: 1.5rem;
}
.day h5 {
background-color: #f8f9fa;
padding: 3px 5px 5px;
margin: -8px -8px 8px -8px;
}
.date {
padding-left: 16px;
}
}
@media (min-width: 576px) {
.day {
min-height: 14.2857vw;
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MonthCalendarDayComponent } from './month-calendar-day.component';
describe('MonthCalendarDayComponent', () => {
let component: MonthCalendarDayComponent;
let fixture: ComponentFixture<MonthCalendarDayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MonthCalendarDayComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MonthCalendarDayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,54 @@
import {Component, EventEmitter, HostBinding, Input, OnInit, Output} from '@angular/core';
import {dateToMoment, Day, MonthCalendarEvent} from "../month-calendar/month-calendar.component";
import {DayToDisplay} from "../../_helpers";
@Component({
selector: '[app-month-calendar-day]',
templateUrl: './month-calendar-day.component.html',
styleUrls: ['./month-calendar-day.component.scss']
})
export class MonthCalendarDayComponent implements OnInit {
@Input()
day: DayToDisplay;
@Output()
onEvent: EventEmitter<MonthCalendarEvent> = new EventEmitter<MonthCalendarEvent>();
oMoment: any;
constructor() { }
ngOnInit() {
if ( this.day ){
console.info(this.day);
this.oMoment = dateToMoment(this.day.date);
}
}
getDayOfWeekName(){
return this.oMoment.format('dddd');
}
getDateOfDay(){
return this.oMoment.format('DD');
}
hasEvents(){
return this.day.active && this.day.events && this.day.events.length;
}
@HostBinding('class') get styleClass(){
let styleClass = "day col-sm p-2 border border-left-0 border-top-0 text-truncate";
if ( !this.day.active ){
styleClass += " d-none d-sm-inline-block bg-light text-muted";
}
return styleClass;
}
handleEvent($event){
this.onEvent.emit($event);
}
}

View File

@ -0,0 +1,9 @@
<a
(click)="selectEvent()"
class="event d-block p-1 pl-2 pr-2 mb-1 rounded text-truncate small bg-success text-white" title="Test Event 2">
{{formatTime()}}:{{event.eventType.name}}
<ng-container *ngIf="hasTrainer()">
<br>
{{formatTrainer()}}
</ng-container>
</a>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MonthCalendarEventComponent } from './month-calendar-event.component';
describe('MonthCalendarEventComponent', () => {
let component: MonthCalendarEventComponent;
let fixture: ComponentFixture<MonthCalendarEventComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MonthCalendarEventComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MonthCalendarEventComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,44 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Event} from "../../services/event.service";
import * as moment from "moment";
import {MonthCalendarEvent} from "../month-calendar/month-calendar.component";
@Component({
selector: 'app-month-calendar-event',
templateUrl: './month-calendar-event.component.html',
styleUrls: ['./month-calendar-event.component.scss']
})
export class MonthCalendarEventComponent implements OnInit {
@Input()
event: Event;
@Output()
onEvent = new EventEmitter<MonthCalendarEvent>();
constructor() {
}
ngOnInit() {
}
formatTime() {
return moment(this.event.start).format('HH:mm');
}
hasTrainer() {
return !!this.event.trainer;
}
formatTrainer() {
const trainer = this.event.trainer;
return trainer.name;
}
selectEvent() {
this.onEvent.emit({
type: 'SELECT_EVENT',
event: this.event
})
}
}

View File

@ -0,0 +1,15 @@
<div class="container-fluid">
<header>
<h4 class="display-4 mb-4 text-center">Naptár</h4>
<div class="row d-none d-sm-flex p-1 bg-dark text-white">
<h5 *ngFor="let dayOfWeek of daysOfWeek" class="col-sm p-1 text-center name-of-day-of-week">{{dayOfWeek.name}}</h5>
</div>
</header>
<div *ngIf="eventsAvailableResponse" class="row border border-right-0 border-bottom-0">
<ng-container *ngFor="let day of eventsAvailableResponse.days; let i = index">
<div app-month-calendar-day [day]="day" (onEvent)="handleEvent($event)" ></div>
<div *ngIf="i > 0 && ( ((i+1) % 7) == 0)" class="w-100"></div>
</ng-container>
</div>
</div>

View File

@ -0,0 +1,22 @@
@media (max-width:575px) {
.display-4 {
font-size: 1.5rem;
}
.day h5 {
background-color: #f8f9fa;
padding: 3px 5px 5px;
margin: -8px -8px 8px -8px;
}
.date {
padding-left: 4px;
}
}
@media (min-width: 576px) {
.day {
min-height: 14.2857vw;
}
}
.name-of-day-of-week{
text-transform: capitalize;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MonthCalendarComponent } from './month-calendar.component';
describe('MonthCalendarComponent', () => {
let component: MonthCalendarComponent;
let fixture: ComponentFixture<MonthCalendarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MonthCalendarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MonthCalendarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,91 @@
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
import {Event, EventsAvailableResponse} from "../../services/event.service";
import * as moment from "moment";
@Component({
selector: 'app-month-calendar',
templateUrl: './month-calendar.component.html',
styleUrls: ['./month-calendar.component.scss']
})
export class MonthCalendarComponent implements OnInit, OnChanges {
minDate: number;
maxDate: number;
daysOfWeek: DayOfWeek[];
days: Day[];
@Input()
eventsAvailableResponse: EventsAvailableResponse;
@Output()
onEvent: EventEmitter<MonthCalendarEvent> = new EventEmitter<MonthCalendarEvent>();
constructor( ) { }
ngOnInit() {
moment.locale('hu');
//
this.fillDaysOfWeek();
//
// let firstDay = moment().startOf('week');
//
// this.days = [];
// if ( this.eventsAvailableResponse ){
//
// for ( let i = 0; i < this.eventsAvailableResponse.days.length; i++){
// this.days.push({
// date: this.eventsAvailableResponse.days[i],
// events: [],
// active: true
// })
// }
// }
}
fillDaysOfWeek(){
this.daysOfWeek = [];
let startOfWeek = moment().startOf("week");
for ( let i = 0; i < 7 ; i++ ){
let momWeekDay = startOfWeek.clone().add(i,'d');
const dayOfWeek = {
name: momWeekDay.format("dddd"),
};
this.daysOfWeek.push(dayOfWeek);
}
}
ngOnChanges(changes: SimpleChanges): void {
console.info(changes);
if ( changes.hasOwnProperty('eventsAvailableResponse')){
}
}
handleEvent($event){
this.onEvent.emit($event);
}
}
export interface Day {
date: number;
events: Event[];
active: boolean;
}
export interface DayOfWeek {
name: string;
}
export function dateToMoment(date: number) {
return moment(date);
}
export interface MonthCalendarEvent {
type: string; // type of the month calendar event
event: Event; // the fitness event
}

View File

@ -0,0 +1,36 @@
<div class="container" *ngIf="event">
<div class="row">
<div class="col-lg-3 col-sm-12"><span class="title">Edzés típusa</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event.eventType.name}}</span></div>
</div>
<div class="row" *ngIf="event.trainer">
<div class="col-lg-3 col-sm-12"><span class="title">Edző</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event.trainer.name}}</span></div>
</div>
<div class="row">
<div class="col-lg-3 col-sm-12"><span class="title">Edzés kezdési időpontja</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event.start | date:'yyyy.MM.dd HH:mm'}}</span></div>
</div>
<div class="row">
<div class="col-lg-3 col-sm-12"><span class="title">Edzés vége</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event.end | date:'yyyy.MM.dd HH:mm'}}</span></div>
</div>
<div class="row">
<div class="col-lg-3 col-sm-12"><span class="title">Férőhelyek száma</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event.seatCount }}</span></div>
</div>
<!-- <div class="row">-->
<!-- <div class="col-lg-3 col-sm-12"><span class="title">Szabad helyek száma</span></div>-->
<!-- <div class="col-lg-9 col-sm-12"><span>{{event.seatCount - event.reservationCount }}</span></div>-->
<!-- </div>-->
<div class="row">
<div class="col-lg-9 col-sm-12 pt-2">
<a *ngIf="mayRegister(event)" class="btn btn-primary " (click)="register(event)" >Foglalás</a>
<a *ngIf="mayCancel(event)" class="btn btn-primary" (click)="cancel(event)" >Lemondás</a>
<span *ngIf="!mayCancel(event) && noFreeSeat(event)">Már nincs szabad hely</span>
</div>
<div class="col-lg-3 text-lg-right col-sm-12 pt-2">
<a class="btn btn-primary " (click)="goBack(event)" >Vissza</a>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EventDetailsComponent } from './event-details.component';
describe('EventDetailsComponent', () => {
let component: EventDetailsComponent;
let fixture: ComponentFixture<EventDetailsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EventDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EventDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import {Event, EventService} from "../../services/event.service";
import {ActivatedRoute, RouterState} from "@angular/router";
import {NavigationService} from "../../services/navigation.service";
@Component({
selector: 'app-event-details',
templateUrl: './event-details.component.html',
styleUrls: ['./event-details.component.scss']
})
export class EventDetailsComponent implements OnInit {
event: Event;
constructor(
private eventService: EventService,
private route: ActivatedRoute,
private navigationService: NavigationService
) { }
ngOnInit() {
let idEvent = +this.route.snapshot.paramMap.get('idEvent');
this.eventService.findEvent(idEvent).subscribe(
value => this.event = value
);
}
mayRegister(event: Event) {
return event.reservedAt == null && event.reservationCount < event.seatCount;
}
mayCancel(event: Event) {
return event.reservedAt;
}
noFreeSeat(event: Event) {
return event.reservedAt == null && event.reservationCount >= event.seatCount;
}
goBack(event: Event) {
this.navigationService.navigateToHome();
}
register(event: Event) {
}
cancel(event: Event) {
}
}

View File

@ -1 +1,9 @@
<p>home works!</p>
<div class="container">
<!-- <app-fit-weekday-selector [days]="(availableEvents | async)?.days" ></app-fit-weekday-selector>-->
<ng-container *ngIf="availableEvents | async">
<app-month-calendar
(onEvent)="handleEvent($event)"
[eventsAvailableResponse]="availableEvents | async "></app-month-calendar>
</ng-container>
</div>

View File

@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core';
import {EventsAvailableResponse, EventService} from "../../services/event.service";
import {Observable} from "rxjs";
import {MonthCalendarEvent} from "../../components/month-calendar/month-calendar.component";
import {NavigationService} from "../../services/navigation.service";
@Component({
selector: 'app-home',
@ -7,9 +11,20 @@ import { Component, OnInit } from '@angular/core';
})
export class HomeComponent implements OnInit {
constructor() { }
availableEvents: Observable<EventsAvailableResponse>;
constructor(private eventService: EventService,
private navigationService: NavigationService) { }
ngOnInit() {
}
this.availableEvents = this.eventService.findEventsAvailable();
}
handleEvent($event: MonthCalendarEvent) {
console.info($event);
this.navigationService.navigateToEventDetails($event.event.id);
}
}

View File

@ -29,4 +29,9 @@ export class Endpoints {
return `${this.baseUrl}/event-type`;
}
public static GET_EVENTS_AVAILABLE(){
return `${this.baseUrl}/events/available`;
}
}

View File

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Endpoints} from "./endpoints";
import {Observable} from "rxjs";
import {DayToDisplay} from "../_helpers";
@Injectable({
providedIn: 'root'
@ -31,6 +32,10 @@ export class EventService {
return this.http.post(Endpoints.POST_EVENT_CANCEL( idEvent ),{}) as Observable<Event>;
}
findEventsAvailable(): Observable<EventsAvailableResponse>{
return this.http.get(Endpoints.GET_EVENTS_AVAILABLE()) as Observable<EventsAvailableResponse>;
}
}
export interface Trainer {
@ -55,4 +60,14 @@ export interface EventType {
name: string;
}
export interface DayToDisplay {
date: number;
active: true;
events: Event[];
}
export interface EventsAvailableResponse {
days: DayToDisplay[];
events: Event[];
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { NavigationService } from './navigation.service';
describe('NavigationService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: NavigationService = TestBed.get(NavigationService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import {NavigationExtras, Router} from "@angular/router";
@Injectable({
providedIn: 'root'
})
export class NavigationService {
constructor(private navigation: Router) { }
public navigate(commands: any[], extras?: NavigationExtras){
this.navigation.navigate( commands,extras );
}
public navigateToEventDetails(idEvent: number){
this.navigate(['/event-details/'+idEvent ])
}
public navigateToHome(){
this.navigate(['/home' ])
}
}

View File

@ -36,7 +36,7 @@ body{
background: #e5ce48;
border: 1px solid #e5ce48;
color: #000; }
.btn.btn-primary:hover {
.btn.btn-primary:hover,.btn.btn-primary:active, .btn-primary:not(:disabled):not(.disabled):active {
border: 1px solid #e5ce48;
background: transparent;
color: #e5ce48; }