diff --git a/projects/admin/src/app/classes/library.ts b/projects/admin/src/app/classes/library.ts index 13580fcec..9ef1d22c0 100644 --- a/projects/admin/src/app/classes/library.ts +++ b/projects/admin/src/app/classes/library.ts @@ -19,9 +19,15 @@ // required as json properties is not lowerCamelCase import { WeekDay } from '@angular/common'; +import { marker } from '@biesbjerg/ngx-translate-extract-marker'; import * as moment from 'moment'; import { WeekDays } from './week-days'; + +export function _(str) { + return marker(str); +} + export interface OpeningHours { day: string; is_open: boolean; @@ -52,6 +58,19 @@ export interface ExceptionDates { repeat?: Repeat; } +export enum NotificationType { + DUE_SOON = _('due_soon'), + RECALL = _('recall'), + OVERDUE = _('overdue'), + AVAILABILITY = _('availability') +} + +export interface NotificationSettings { + type: NotificationType; + email: string; + delay?: number; +} + export class Library { // CLASS ATTRIBUTES ================================================ @@ -63,6 +82,7 @@ export class Library { code: string = null; opening_hours: Array = []; exception_dates?: Array; + notification_settings?: Array; organisation: Organisation; // GETTER & SETTER ================================================ diff --git a/projects/admin/src/app/record/custom-editor/libraries/library-form.service.ts b/projects/admin/src/app/record/custom-editor/libraries/library-form.service.ts index fc2aa1918..6e73ba42a 100644 --- a/projects/admin/src/app/record/custom-editor/libraries/library-form.service.ts +++ b/projects/admin/src/app/record/custom-editor/libraries/library-form.service.ts @@ -16,27 +16,45 @@ */ import { Injectable } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TimeValidator } from '@rero/ng-core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RecordService, TimeValidator } from '@rero/ng-core'; +import { Subject } from 'rxjs'; +import { Library, NotificationSettings, NotificationType } from '../../../classes/library'; import { WeekDays } from '../../../classes/week-days'; -import { Library } from '../../../classes/library'; + @Injectable({ providedIn: 'root' }) export class LibraryFormService { + /** Angular form group */ public form; + + /** RERO ILS notification types */ + private notificationTypes = []; + + /** Observable for build event */ + private buildEvent = new Subject(); + + /** + * Constructor + * + * @param _fb - FormBuilder + * @param _recordService - RecordService + */ constructor( - private fb: FormBuilder - ) { - this.build(); - } + private _fb: FormBuilder, + private _recordService: RecordService + ) { } - build() { - this.form = this.fb.group({ + /** + * Build form + */ + build(): void { + this.form = this._fb.group({ name: ['', [ Validators.required, Validators.minLength(4) @@ -48,11 +66,34 @@ export class LibraryFormService { Validators.required ] }], - opening_hours: this.fb.array([]) + opening_hours: this._fb.array([]), + notification_settings: this._fb.array([]) }); this.initializeOpeningHours(); + this.initializeNotificationSettings(); + } + + /** + * Get build event + */ + getBuildEvent() { + return this.buildEvent.asObservable(); + } + + create() { + this._recordService + .getSchemaForm('notifications') + .subscribe((jsonSchema: any) => { + this.notificationTypes = jsonSchema.schema.properties.notification_type.enum; + this.build(); + this.buildEvent.next(true); + }); } + /** + * Build and set default values for opening hours at form initialization + * @param openingHours - opening hours + */ initializeOpeningHours(openingHours = []) { const days = Object.keys(WeekDays); const hours = this.form.get('opening_hours'); @@ -60,12 +101,16 @@ export class LibraryFormService { hours.push(this.buildOpeningHours( false, days[step], - this.fb.array([]) + this._fb.array([]) )); } this.setOpeningHours(openingHours); } + /** + * Set opening hours from record data + * @param openingHours - opening hours + */ setOpeningHours(openingHours = []) { for (let step = 0; step < 7; step++) { const atimes = this.getTimesByDayIndex(step); @@ -85,8 +130,14 @@ export class LibraryFormService { } } + /** + * Create opening hour form control + * @param isOpen - is open + * @param day - day + * @param times - times array + */ buildOpeningHours(isOpen, day, times): FormGroup { - return this.fb.group({ + return this._fb.group({ is_open: [isOpen], day: [day], times @@ -95,9 +146,14 @@ export class LibraryFormService { }); } + /** + * Create times form control + * @param startTime - start time + * @param endTime - end time + */ buildTimes(startTime = '00:01', endTime = '23:59'): FormGroup { const regex = '^(?!(0:00)|(00:00)$)([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$'; - return this.fb.group({ + return this._fb.group({ start_time: [startTime, { validators: [ Validators.required, @@ -115,10 +171,10 @@ export class LibraryFormService { }); } - reset() { - this.build(); - } - + /** + * Populate the form + * @param library - library + */ populate(library: Library) { this.form.patchValue({ name: library.name, @@ -127,21 +183,74 @@ export class LibraryFormService { code: library.code, }); this.setOpeningHours(library.opening_hours); + this.setNotificationSettings(library.notification_settings); + } + + /** + * Build an set default values for notification settings + * @param notificationSettings - notification settings + */ + initializeNotificationSettings(notificationSettings = []) { + const settings = this.form.get('notification_settings'); + this.notificationTypes.forEach(type => { + settings.push(this.getSettingsByType(type)); + }); + this.setNotificationSettings(notificationSettings); + } + + /** + * Get setting by type + * @param settingType - setting type + */ + getSettingsByType(settingType: NotificationType) { + const model: NotificationSettings = { + type: settingType, + email: '' + }; + switch (settingType) { + case(NotificationType.AVAILABILITY): + model.delay = 0; + break; + } + return this._fb.group(model); + } + + /** + * Set values from record + * @param notificationSettings - notification settings + */ + setNotificationSettings(notificationSettings = []) { + if (notificationSettings.length > 0) { + const formSettings = this.form.get('notification_settings'); + for (let step = 0; step < formSettings.value.length; step++) { + const formSetting = formSettings.get(String(step)); + const currentSetting = notificationSettings.find(element => element.type === formSetting.get('type').value); + if (currentSetting !== undefined) { + formSetting.get('email').setValue(currentSetting.email); + if (currentSetting.delay !== undefined) { + formSetting.get('delay').setValue(currentSetting.delay); + } + } + } + } } setId(id) { this.form.value.id = id; } setLibraryPid(pid) { this.form.value.pid = pid; } setSchema(schema) { this.form.value.$schema = schema; } - get name() { return this.form.get('name'); } - get address() { return this.form.get('address'); } - get email() { return this.form.get('email'); } - get code() { return this.form.get('code'); } - get opening_hours() { + get name(): AbstractControl { return this.form.get('name'); } + get address(): AbstractControl { return this.form.get('address'); } + get email(): AbstractControl { return this.form.get('email'); } + get code(): AbstractControl { return this.form.get('code'); } + get opening_hours(): FormArray { return this.form.get('opening_hours') as FormArray; } + get notification_settings(): FormArray { + return this.form.get('notification_settings') as FormArray; + } - getValues() { return this.form.value; } + getValues(): any { return this.form.value; } addTime(dayIndex): void { this.getTimesByDayIndex(dayIndex).push(this.buildTimes()); diff --git a/projects/admin/src/app/record/custom-editor/libraries/library.component.html b/projects/admin/src/app/record/custom-editor/libraries/library.component.html index af9c15163..15982202f 100644 --- a/projects/admin/src/app/record/custom-editor/libraries/library.component.html +++ b/projects/admin/src/app/record/custom-editor/libraries/library.component.html @@ -14,183 +14,201 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - - -

Library

-
-
- -
- -
-
- Name is required. -
-
- Name must be at least 4 characters long. +
+ +

Library

+ +
+ +
+ +
+
+ Name is required. +
+
+ Name must be at least 4 characters long. +
+
-
-
-
- -
- -
-
- Address must be at least 4 characters long. +
+ +
+ +
+
+ Address must be at least 4 characters long. +
+
-
-
-
- -
- -
-
- Email format is not correct. +
+ +
+ +
+
+ Email format is not correct. +
+
-
-
-
- -
- -
-
- Code is already taken. +
+ +
+ +
+
+ Code is already taken. +
+
Validating…
+
-
Validating…
-
-
- - - - {{ 'Opening Hours' | translate }} - -
-
-
-
-
- - -
-
-
-
    -
  • -
    -
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -
    - Start time is required. -
    -
    - Start time format is not correct. -
    -
    -
    -
    - End time is required. -
    -
    - End time format is not correct. + + + + {{ 'Opening Hours' | translate }} + +
    +
    +
    +
    +
    + + +
    +
    +
    +
      +
    • +
      +
      +
      + +
      +
      + +
      +
      + + +
      +
      +
      +
      + Start time is required. +
      +
      + Start time format is not correct. +
      +
      +
      +
      + End time is required. +
      +
      + End time format is not correct. +
      +
      +
      +
      + End time is less than start time. +
      +
      +
    • +
    +
    +
    + The two periods are overlapping.
    -
    -
    - End time is less than start time. -
    +
    +
    +
    +
    +
    + + + + {{ 'Exceptions (holidays, etc.)' | translate }} + + + + + + + {{ 'Notification settings' | translate }} + +
    +
    +
    + {{ setting.controls.type.value}} +
    +
    + +
    + +
    + +
    + The notifications for patrons without e-mail are sent to this address. No notification is sent if + the field is empty.
    -
  • -
-
-
- The two periods are overlapping.
+ + +
+ +
+ +
Sending how many minutes after the item is available
+
+
+
+ + +
+
+ +
- - - - {{ 'Exceptions (holidays, etc.)' | translate }} - - - - -
-
- - -
-
- - + + +
diff --git a/projects/admin/src/app/record/custom-editor/libraries/library.component.ts b/projects/admin/src/app/record/custom-editor/libraries/library.component.ts index f04b52e4e..74c44a56f 100644 --- a/projects/admin/src/app/record/custom-editor/libraries/library.component.ts +++ b/projects/admin/src/app/record/custom-editor/libraries/library.component.ts @@ -16,7 +16,7 @@ */ import { Location } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormArray, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; @@ -25,18 +25,39 @@ import { ToastrService } from 'ngx-toastr'; import { UserService } from '@rero/shared'; import { Library } from '../../../classes/library'; import { LibraryFormService } from './library-form.service'; +import { Subscription } from 'rxjs'; @Component({ selector: 'admin-libraries-library', templateUrl: './library.component.html', styleUrls: ['./library.component.scss'] }) -export class LibraryComponent implements OnInit { +export class LibraryComponent implements OnInit, OnDestroy { + /** The current library. */ public library: Library; + + /** The angular form to edit the library. */ public libForm: FormGroup; - public organisationPid; + /** The current organisation pid. */ + public organisationPid: string; + + /** Form build event subscription to release the memory. */ + private eventForm: Subscription; + + /** + * + * @param recordService - ng-core eventForm + * @param libraryForm - LibraryFormService + * @param route - angular ActivatedRoute + * @param router - angular Router + * @param userService - ng-core UserService + * @param apiService - ng-core ApiService + * @param toastService - ToastrService + * @param translateService - ngx-translate TranslateService + * @param location - angular Location + */ constructor( private recordService: RecordService, private libraryForm: LibraryFormService, @@ -49,28 +70,39 @@ export class LibraryComponent implements OnInit { private location: Location ) { } + /** + * Component initialization. + */ ngOnInit() { this.route.params.subscribe( (params) => { const loggedUser = this.userService.user; if (loggedUser) { this.organisationPid = loggedUser.currentOrganisation; } - if (params && params.pid) { - this.recordService.getRecord('libraries', params.pid, 1).subscribe(record => { - this.library = new Library(record.metadata); - this.libraryForm.populate(record.metadata); + this.libraryForm.create(); + this.eventForm = this.libraryForm.getBuildEvent().subscribe((buildEvent: any) => { + if (params && params.pid) { + this.recordService.getRecord('libraries', params.pid, 1).subscribe(record => { + this.library = new Library(record.metadata); + this.libraryForm.populate(record.metadata); + this.libForm = this.libraryForm.form; + this.setAsyncValidator(); + }); + } else { + this.library = new Library({}); this.libForm = this.libraryForm.form; this.setAsyncValidator(); - }); - } else { - this.libraryForm.reset(); - this.library = new Library({}); - this.libForm = this.libraryForm.form; - this.setAsyncValidator(); - } + } + }); }); } + /** Component destruction. */ + ngOnDestroy() { + this.eventForm.unsubscribe(); + } + + /** Create the form async validators. */ setAsyncValidator() { this.libForm.controls.code.setAsyncValidators([ UniqueValidator.createValidator( @@ -82,12 +114,26 @@ export class LibraryComponent implements OnInit { ]); } + /** Name of the library. */ get name() { return this.libraryForm.name; } + + /** Address of the library. */ get address() { return this.libraryForm.address; } + + /** Contact email of the library. */ get email() { return this.libraryForm.email; } + + /** Code of the library. */ get code() { return this.libraryForm.code; } + + /** Hours when the library is open. */ get openingHours() { return this.libraryForm.opening_hours as FormArray; } + /** Notificaition settings. */ + get notificationSettings() { + return this.libraryForm.notification_settings as FormArray; } + + /** Form submission. */ onSubmit() { this._cleanFormValues(this.libraryForm.getValues()); this.library.update(this.libraryForm.getValues()); @@ -112,7 +158,7 @@ export class LibraryComponent implements OnInit { this.router.navigate(['../detail', record.metadata.pid], {relativeTo: this.route, replaceUrl: true}); }); } - this.libraryForm.reset(); + this.libraryForm.build(); } /** @@ -126,17 +172,22 @@ export class LibraryComponent implements OnInit { day.times = []; } }); + formValues.notification_settings = formValues.notification_settings.filter(element => element.email !== ''); } + /** Cancel the edition. */ onCancel(event) { event.preventDefault(); this.location.back(); - this.libraryForm.reset(); + this.libraryForm.build(); } + + /** Add new opening hours. */ addTime(dayIndex): void { this.libraryForm.addTime(dayIndex); } + /** Delete existing opening hours. */ deleteTime(dayIndex, timeIndex): void { this.libraryForm.deleteTime(dayIndex, timeIndex); } diff --git a/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.html b/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.html index 4e5729dab..8ca819b5b 100644 --- a/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.html +++ b/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.html @@ -16,79 +16,104 @@ -->

{{ record.metadata.name }}

-
+
-
- {{ 'Address' | translate }}: -
-
- {{ record.metadata.address }} -
+
+ Address +
+
+ {{ record.metadata.address }} +
-
- {{ 'Code' | translate }}: +
+ Code
{{ record.metadata.code }}
-
- {{ 'Email' | translate }}: +
+ Email
{{ record.metadata.email }}
-
+ - -
-
- Opening Hours -
-
-
    -
  • - -
  • -
-
-
+ + + + + {{ 'Opening Hours' | translate }} + +
+
    +
  • + +
  • +
+
+
- -
-
- Exceptions (holidays, etc.) -
-
-
    -
  • - -
  • -
-
-
+ + + + {{ 'Exceptions (holidays, etc.)' | translate }} + +
+
    +
  • + +
  • +
+
+
+ + + {{ 'Notification settings' | translate }} + + +
+
+
{{ setting.type }}
+
+ +
{{ 'Email' | translate }}:
+
{{ setting.email }}
+ +
{{ 'Delay' | translate }}
+
{{ setting.delay }}
+
+
+
+
+
+ + +

no location

+
diff --git a/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.ts b/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.ts index c4c5fa4c7..caf1bb8aa 100644 --- a/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.ts +++ b/projects/admin/src/app/record/detail-view/library-detail-view/library-detail-view.component.ts @@ -24,7 +24,7 @@ import { UserService } from '@rero/shared'; @Component({ selector: 'admin-library-detail-view', templateUrl: './library-detail-view.component.html', - styles: [] + styles: ['tab {margin: 1rem;}'] }) export class LibraryDetailViewComponent implements DetailRecord, OnInit, OnDestroy { diff --git a/proxy.conf.json b/proxy.conf.json index 96ee69a9f..10072c74d 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -26,6 +26,15 @@ "^/api": "https://localhost:5000/api" } }, + "/notifications/*": { + "target": "https://localhost:5000", + "secure": false, + "logLevel": "debug", + "changeOrigin": true, + "pathRewrite": { + "^/notifications": "https://localhost:5000/notifications" + } + }, "/lang/*": { "target": "https://localhost:5000", "secure": false,