diff --git a/src/app/classes/booking/booking.ts b/src/app/classes/booking/booking.ts index 17d2015..e8b1c1e 100644 --- a/src/app/classes/booking/booking.ts +++ b/src/app/classes/booking/booking.ts @@ -15,6 +15,7 @@ export class Booking{ bookingStatusEnum: BookingStatusEnum bookingComment: string cancellationComment: string + isForReview: boolean constructor(bookingId?: number, serviceman?: Serviceman, consultationPurpose?: ConsultationPurpose, consultation?: Consultation, bookingSlot?: BookingSlot, formInstances?: FormInstance[], bookingStatusEnum?: BookingStatusEnum, @@ -28,5 +29,6 @@ export class Booking{ this.bookingStatusEnum = bookingStatusEnum this.bookingComment = bookingComment this.cancellationComment = cancellationComment + this.isForReview = this.isForReview } } \ No newline at end of file diff --git a/src/app/classes/notification/notification.spec.ts b/src/app/classes/notification/notification.spec.ts new file mode 100644 index 0000000..abd8e13 --- /dev/null +++ b/src/app/classes/notification/notification.spec.ts @@ -0,0 +1,7 @@ +import { Notification } from './notification'; + +describe('Notification', () => { + it('should create an instance', () => { + expect(new Notification()).toBeTruthy(); + }); +}); diff --git a/src/app/classes/notification/notification.ts b/src/app/classes/notification/notification.ts new file mode 100644 index 0000000..3575837 --- /dev/null +++ b/src/app/classes/notification/notification.ts @@ -0,0 +1,39 @@ +import { NotificationTypeEnum } from '../notificationtype-enum' +import { Serviceman } from '../serviceman/serviceman' + +export class Notification { + + notificationId: number + notificationDate: Date + title: string + message: string + isRead: boolean + isFetched: boolean + serviceman: Serviceman + dynamicId: number + notificationTypeEnum: NotificationTypeEnum + + constructor( + notificationId?: number, + notificationDate?: Date, + title?: string, + message?: string, + isRead?: boolean, + isFetched?: boolean, + serviceman?: Serviceman, + dynamicId?: number, + notificationTypeEnum?: NotificationTypeEnum + ) { + + this.notificationId = notificationId + this.notificationDate = notificationDate + this.title = title + this.message = message + this.isRead = isRead + this.isFetched = isFetched + this.serviceman = serviceman + this.dynamicId = dynamicId + this.notificationTypeEnum = notificationTypeEnum + } + +} diff --git a/src/app/classes/notificationtype-enum.ts b/src/app/classes/notificationtype-enum.ts new file mode 100644 index 0000000..b2a2109 --- /dev/null +++ b/src/app/classes/notificationtype-enum.ts @@ -0,0 +1,6 @@ +export enum NotificationTypeEnum{ + BOOKING = ('BOOKING'), + CONSULTATION = ("CONSULTATION"), + FORM = ("FORM"), + GENERAL = ("GENERAL") +} \ No newline at end of file diff --git a/src/app/layout/app-main/app-main.component.html b/src/app/layout/app-main/app-main.component.html index 297fa6c..a70101b 100644 --- a/src/app/layout/app-main/app-main.component.html +++ b/src/app/layout/app-main/app-main.component.html @@ -1,4 +1,4 @@ -
- + @@ -38,28 +39,95 @@ -
  • +
  • - - 2 + + {{unreadCounter}} Notifications -
  • + +

    + {{displayNotfication.message}} +

    + +
    + diff --git a/src/app/layout/app-topbar/app-topbar.component.ts b/src/app/layout/app-topbar/app-topbar.component.ts index ce433e4..8c656a4 100644 --- a/src/app/layout/app-topbar/app-topbar.component.ts +++ b/src/app/layout/app-topbar/app-topbar.component.ts @@ -1,11 +1,18 @@ import { Component, OnInit } from '@angular/core'; -import {AppMainComponent} from '../app-main/app-main.component'; +import { AppMainComponent } from '../app-main/app-main.component'; import { Router } from '@angular/router'; import { SessionService } from 'src/app/services/session/session.service'; import { Serviceman } from 'src/app/classes/serviceman/serviceman'; - +import { Message, ConfirmationService, LazyLoadEvent, PrimeNGConfig, MenuItem } from 'primeng/api'; import { AppComponent } from '../../app.component' +import { NotificationService } from 'src/app/services/notification/notification.service'; +import { Notification } from 'src/app/classes/notification/notification'; +import { ToastModule } from 'primeng/toast'; +import { MessageService } from 'primeng/api'; +import { NotificationTypeEnum } from 'src/app/classes/notificationtype-enum'; +import { interval } from 'rxjs'; + @Component({ selector: 'app-topbar', @@ -15,14 +22,155 @@ import { AppComponent } from '../../app.component' export class AppTopbarComponent implements OnInit { serviceman: Serviceman + allNotifications: Notification[] = [] + notiHover: Boolean + msgForDialog: Message[] = [] + selectedNotification: Notification + unreadCounter: number + pollInterval: number + items: MenuItem[]; + display: boolean = false; + displayNotfication: Notification constructor(public app: AppMainComponent, private router: Router, private sessionService: SessionService, - public appForTimer: AppComponent) {} + private notificationService: NotificationService, private messageService: MessageService, private primengConfig: PrimeNGConfig, + public appForTimer: AppComponent) { } ngOnInit() { + this.displayNotfication = new Notification + this.loadHomeContent() + this.pollInterval = 3000 + + interval(this.pollInterval).subscribe(_ => { + this.checkForUnfetchedNotifications() + }) this.serviceman = this.sessionService.getCurrentServiceman() } + checkForUnfetchedNotifications() { + this.notificationService.hasUnfetchedNotifications().subscribe( + response => { + response.hasUnfetchedNotifications === true ? this.loadHomeContent() : null + }, + error => { + console.log(error); + } + ) + } + + showDialog(notification: Notification) { + this.display = true; + this.displayNotfication = notification + } + + numLines(str: String) { + return str.split(/\r\n|\r|\n/).length + } + + getMenuItems(notification: Notification): MenuItem[] { + return [{ + label: 'Update', + icon: 'pi pi-refresh', + command: () => { + this.deleteNotification(notification); + } + }, + { + label: 'Delete', + icon: 'pi pi-times', + command: () => { + + } + } + ]; + } + + loadHomeContent() { + + this.unreadCounter = 0; + this.notiHover = false + this.serviceman = this.sessionService.getCurrentServiceman() + this.notificationService.retrieveAllServicemanNotifications().subscribe( + response => { + this.allNotifications = response.notifications + this.allNotifications.forEach(n => { + n.notificationDate = this.convertUTCStringToSingaporeDate(n.notificationDate) + if (!n.isRead) { + this.unreadCounter = this.unreadCounter + 1; + } + }) + this.allNotifications.sort((x, y) => (y.notificationDate.getTime() - x.notificationDate.getTime())) + }, error => { + console.error(error) + } + ); + + } + + deleteNotification(notification) { + this.notificationService.deleteNotification(notification.notificationId).subscribe( + response => { + this.messageService.add({ severity: 'success', summary: 'Service Message', detail: 'Notification Deleted' }); + }, error => { + console.error(error) + } + ) + } + + redirecting(notification: Notification) { + this.notificationService.readNotification(notification.notificationId).subscribe( + response => { + this.ngOnInit() + }, error => { + console.error(error) + } + ) + let url = "" + if (notification.notificationTypeEnum === NotificationTypeEnum.BOOKING) { + url = url + "/booking-management-screen" + if (notification.dynamicId !== undefined) { + url = url + "/" + notification.dynamicId + } + } + if (notification.notificationTypeEnum === NotificationTypeEnum.CONSULTATION) { + url = url + "/consultation-screen" + if (notification.dynamicId !== undefined) { + url = url + "/" + notification.dynamicId + } + } + if (notification.notificationTypeEnum === NotificationTypeEnum.FORM) { + url = url + "/general-eforms-screen" + if (notification.dynamicId !== undefined) { + url = url + "/" + notification.dynamicId + } + } + this.router.navigate([url]) + } + + readAllNotifications() { + this.notificationService.readAllNotifications().subscribe( + response => { + this.ngOnInit() + }, + error => { + console.log(error); + } + ) + } + + deleteAllNotifications() { + this.notificationService.deleteAllNotifications().subscribe( + response => { + console.log(`deleted all`); + this.ngOnInit(); + }, + error => { + console.log(error); + + } + ) + } + logout() { this.sessionService.setIsLogin(false) this.sessionService.setCurrentServiceman(null) @@ -30,4 +178,18 @@ export class AppTopbarComponent implements OnInit { this.appForTimer.stopTimer() } + convertUTCStringToSingaporeDate(dateCreated) { + if (dateCreated != null) { + let stringUtcTime = dateCreated.toLocaleString().substring(0, 19) + return new Date(Date.UTC( + parseInt(stringUtcTime.substring(0, 4)), + parseInt("" + (+stringUtcTime.substring(5, 7) - 1)), + parseInt(stringUtcTime.substring(8, 10)), + parseInt(stringUtcTime.substring(11, 13)), + parseInt(stringUtcTime.substring(14, 16)), + parseInt(stringUtcTime.substring(17, 19)) + )); + } + } + } diff --git a/src/app/screens/booking-management-screen/booking-management-screen.component.html b/src/app/screens/booking-management-screen/booking-management-screen.component.html index 8a21afe..dbaf7cf 100644 --- a/src/app/screens/booking-management-screen/booking-management-screen.component.html +++ b/src/app/screens/booking-management-screen/booking-management-screen.component.html @@ -23,14 +23,14 @@

    You currently have no booking

    - - Booking Id + Booking Id @@ -38,16 +38,17 @@

    You currently have no booking

    - Booking SlotBooking Slot - Booking Status + Booking Status - Form Quantity + Forms +
    @@ -111,16 +112,22 @@

    Select Date


    +

    + dataKey="slotId" [(ngModel)]="selectedSlot" name="slot">

    + + + + +

    There is no available slot

    @@ -134,7 +141,19 @@

    There is no available slot

    Booking Comments

    - + +

    Consultation type

    + +
    +

    + + + + +

    +
    + - + + + +
    This consultation is also a medical review.
    +
    +
    @@ -184,10 +209,11 @@
    {{selectedBooking.bookingSlot.medicalCentre.name}

    Phone: {{selectedBooking.bookingSlot.medicalCentre.phone}}

    Address: - + {{formatAddress(selectedBooking.bookingSlot.medicalCentre.address.streetName, selectedBooking.bookingSlot.medicalCentre.address.buildingName, selectedBooking.bookingSlot.medicalCentre.address.unitNumber, - selectedBooking.bookingSlot.medicalCentre.address.country, selectedBooking.bookingSlot.medicalCentre.address.postal)}}

    + selectedBooking.bookingSlot.medicalCentre.address.country, selectedBooking.bookingSlot.medicalCentre.address.postal)}} +

    @@ -225,7 +251,8 @@

    Required Forms

    Booking Comment

    {{selectedBooking.bookingComment}}

    -

    Cancellation Reason

    {{selectedBooking.cancellationComment}}

    @@ -235,16 +262,18 @@

    Cancellation Reason

    - +

    Are you sure you would like to cancel the booking? Once cancelled, it is irreversable

    Add Cancellation Reason

    - + - + +
    - \ No newline at end of file + diff --git a/src/app/screens/booking-management-screen/booking-management-screen.component.ts b/src/app/screens/booking-management-screen/booking-management-screen.component.ts index 0d7b0fb..b62219f 100644 --- a/src/app/screens/booking-management-screen/booking-management-screen.component.ts +++ b/src/app/screens/booking-management-screen/booking-management-screen.component.ts @@ -41,6 +41,7 @@ export class BookingManagementScreenComponent implements OnInit { bookingComment: string cancellationComment: string displayCancelDialog: boolean + isForReview: boolean constructor(private breadcrumbService: BreadcrumbService, private consultationService: ConsultationService, private medicalCentreService: MedicalCentreService, private schedulerService: SchedulerService, @@ -137,7 +138,7 @@ export class BookingManagementScreenComponent implements OnInit { createBooking() { console.log(this.bookingComment) this.schedulerService.scheduleBooking(this.sessionService.getCurrentServiceman().servicemanId, - this.selectedConsultationPurpose.consultationPurposeId, this.selectedSlot.slotId, this.bookingComment).subscribe( + this.selectedConsultationPurpose.consultationPurposeId, this.selectedSlot.slotId, this.bookingComment, this.isForReview).subscribe( async response => { this.createdBookingId = response.bookingId diff --git a/src/app/services/notification/notification.service.spec.ts b/src/app/services/notification/notification.service.spec.ts new file mode 100644 index 0000000..c4f2cd6 --- /dev/null +++ b/src/app/services/notification/notification.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { NotificationService } from './notification.service'; + +describe('NotificationService', () => { + let service: NotificationService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(NotificationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/notification/notification.service.ts b/src/app/services/notification/notification.service.ts new file mode 100644 index 0000000..343c3c7 --- /dev/null +++ b/src/app/services/notification/notification.service.ts @@ -0,0 +1,81 @@ +import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +import { SessionService } from '../session/session.service'; + +@Injectable({ + providedIn: 'root' +}) +export class NotificationService { + + baseUrl: string = "/api/Notification" + + constructor(private httpClient: HttpClient, private sessionService: SessionService) {} + + httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) + }; + + retrieveAllServicemanNotifications() { + return this.httpClient.get(this.baseUrl + "/retrieveAllServicemanNotifications?servicemanId=" + this.sessionService.getCurrentServiceman().servicemanId, + this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ); + } + + hasUnfetchedNotifications() { + return this.httpClient.get(this.baseUrl + "/hasUnfetchedNotifications?servicemanId=" + this.sessionService.getCurrentServiceman().servicemanId, this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ); + } + + readNotification(notificationId: number) { + let readNotificationReq = { + "notificationId": notificationId + } + + return this.httpClient.post(this.baseUrl + "/readNotification", readNotificationReq, this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ) + } + + readAllNotifications() { + let readAllNotificationsReq = { + "servicemanId": this.sessionService.getCurrentServiceman().servicemanId + } + + return this.httpClient.post(this.baseUrl + "/readAllNotifications", readAllNotificationsReq, this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ) + } + + deleteNotification(notificationId: number): Observable { + return this.httpClient.delete(this.baseUrl + "/deleteNotification?notificationId=" + notificationId, this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ); + } + + deleteAllNotifications(): Observable { + return this.httpClient.delete(this.baseUrl + "/deleteAllNotification?servicemanId=" + this.sessionService.getCurrentServiceman().servicemanId, this.sessionService.getSecuredHttpOptions()).pipe( + catchError(this.handleError) + ); + } + + private handleError(error: HttpErrorResponse) { + let errorMessage: string = ""; + + if (error.error instanceof ErrorEvent) { + errorMessage = "An unknown error has occurred: " + error.error.message; + } + else { + errorMessage = "A HTTP error has occurred: " + `HTTP ${error.status}: ${error.error.message}`; + } + + console.error(errorMessage); + + return throwError(errorMessage); + } +} diff --git a/src/app/services/scheduler/scheduler.service.ts b/src/app/services/scheduler/scheduler.service.ts index 1ba589a..b4ef1ff 100644 --- a/src/app/services/scheduler/scheduler.service.ts +++ b/src/app/services/scheduler/scheduler.service.ts @@ -34,12 +34,13 @@ export class SchedulerService { ); } - scheduleBooking(servicemanId: number, consultationPurposeId: number, bookingSlotId: number, bookingComment: string): Observable { + scheduleBooking(servicemanId: number, consultationPurposeId: number, bookingSlotId: number, bookingComment: string, isForReview: boolean): Observable { let scheduleBookingreq = { "servicemanId": servicemanId, "consultationPurposeId": consultationPurposeId, "bookingSlotId": bookingSlotId, - "bookingComment": bookingComment + "bookingComment": bookingComment, + "isForReview": isForReview } return this.httpClient.post(this.baseUrl + "/scheduleBooking", scheduleBookingreq, this.sessionService.getSecuredHttpOptions()).pipe( catchError(this.handleError) diff --git a/src/assets/layout/css/layout-moody.css b/src/assets/layout/css/layout-moody.css index 3c46e72..f21a7e4 100644 --- a/src/assets/layout/css/layout-moody.css +++ b/src/assets/layout/css/layout-moody.css @@ -2079,7 +2079,6 @@ a:hover { } .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li > ul a i, .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li > ul a img, .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li > ul a span { vertical-align: middle; - display: inline-block; } .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li > ul a .topbar-submenuitem-badge { background-color: #7CB342; @@ -2111,7 +2110,7 @@ a:hover { content: " "; position: absolute; top: -15px; - left: 232px; + left: 482px; } .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li.active-topmenuitem > ul { display: block; diff --git a/src/assets/layout/images/login/login-bg.jpg b/src/assets/layout/images/login/login-bg.jpg index 42e2a56..aa5ba83 100644 Binary files a/src/assets/layout/images/login/login-bg.jpg and b/src/assets/layout/images/login/login-bg.jpg differ diff --git a/src/styles.css b/src/styles.css index b2cfd16..888ae8d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -11,7 +11,9 @@ .messageOveride2 .p-inline-message { justify-content: left; } - +.triangleOverride .layout-wrapper .layout-main .layout-topbar .layout-topbar-menu-wrapper .topbar-menu > li > ul:before { + left: 382px; +} /* #overrideAccordion .p-accordion-content { background:#ffb0b0 !important; }