From aa3dbaee4b8c34aec79e752a6d94534a14394175 Mon Sep 17 00:00:00 2001 From: Bill Erickson <berickxx@gmail.com> Date: Mon, 4 Jan 2021 16:12:50 -0500 Subject: [PATCH] LP1910145 Angular Hold Detail Notes & Notifications The hold detail view now displays hold notes and hold notification records. Notes and Notifications may be created by staff. Notes may be deleted by staff. Signed-off-by: Bill Erickson <berickxx@gmail.com> Signed-off-by: Garry Collum <gcollum@gmail.com> Signed-off-by: Galen Charlton <gmc@equinoxOLI.org> --- .../app/staff/share/holds/detail.component.html | 69 +++++++++++++++++-- .../src/app/staff/share/holds/detail.component.ts | 80 +++++++++++++++++++--- .../eg2/src/app/staff/share/holds/holds.module.ts | 6 +- .../staff/share/holds/note-dialog.component.html | 37 ++++++++++ .../app/staff/share/holds/note-dialog.component.ts | 48 +++++++++++++ .../staff/share/holds/notify-dialog.component.html | 25 +++++++ .../staff/share/holds/notify-dialog.component.ts | 45 ++++++++++++ Open-ILS/src/eg2/src/styles.css | 8 +++ 8 files changed, 302 insertions(+), 16 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.html b/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.html index bc7e4f6627..e80d1ff524 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.html @@ -1,11 +1,11 @@ - -<eg-staff-banner bannerText="Hold Details (#{{hold.id}})" i18n-bannerText> -</eg-staff-banner> +<eg-hold-note-dialog #noteDialog [holdId]="holdId"></eg-hold-note-dialog> +<eg-hold-notify-dialog #notifyDialog [holdId]="holdId"></eg-hold-notify-dialog> <div class="row"> - <div class="col-lg-3"> + <div class="col-lg-2"> <button (click)="showListView()" class="btn btn-info" i18n>List View</button> </div> + <div class="col-lg-3 font-weight-bold" i18n><h4>Hold #{{holdId}}</h4></div> </div> <div class="well-table"> @@ -96,3 +96,64 @@ </div> </div> +<div class="row mt-2"> + <div class="col-lg-12"> + <ul ngbNav #detailNav="ngbNav" class="nav-tabs" [activeId]="detailTab"> + <li ngbNavItem="notes"> + <a ngbNavLink i18n>Notes</a> + <ng-template ngbNavContent> + <button class="btn btn-outline-dark mt-3" (click)="newNote()" i18n>New Note</button> + <div class="mt-3" *ngFor="let note of notes"> + <div class="d-flex"> + <div class="font-weight-bold">{{note.title()}}</div> + <div class="flex-1"></div> + <div> + <span *ngIf="note.slip() == 't'" + class="ml-2 badge badge-info p-1">Print on Slip</span> + <span *ngIf="note.pub() == 't'" + class="ml-2 badge badge-warning p-1">Patron Visible</span> + <span *ngIf="note.staff() == 't'" + class="ml-2 badge badge-info p-1">Staff Create</span> + </div> + </div> + <div class="well-table"> + <div class="well-row"> + <div class="well-value">{{note.body()}}</div> + <div class="well-label-no-flex"> + <button class="btn btn-warning" + (click)="deleteNote(note)" i18n>Delete</button> + </div> + </div> + </div> + </div> + </ng-template> + </li> + <li ngbNavItem="notifications"> + <a ngbNavLink i18n>Staff Notifications</a> + <ng-template ngbNavContent> + <button class="btn btn-outline-dark mt-3" + (click)="newNotify()" i18n>Add Record of Notification</button> + <div class="mt-3" *ngFor="let notify of notifies"> + <div class="d-flex"> + <div class="font-weight-bold">{{notify.method()}}</div> + <div class="flex-1"></div> + <div> + <span>{{notify.notify_time() | date:'short'}}</span> + <span class="ml-2" i18n> + Created by {{notify.notify_staff().usrname()}}</span> + </div> + <div> + </div> + </div> + <div class="well-table"> + <div class="well-row"> + <div class="well-value">{{notify.note()}}</div> + </div> + </div> + </div> + </ng-template> + </li> + </ul> + <div [ngbNavOutlet]="detailNav"></div> + </div> +</div> diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.ts index 67b3801e0d..2f28c24bd9 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/detail.component.ts @@ -1,8 +1,13 @@ import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core'; import {Observable, Observer, of} from 'rxjs'; +import {tap} from 'rxjs/operators'; +import {IdlObject} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; import {OrgService} from '@eg/core/org.service'; import {AuthService} from '@eg/core/auth.service'; +import {HoldNoteDialogComponent} from './note-dialog.component'; +import {HoldNotifyDialogComponent} from './notify-dialog.component'; /** Hold details read-only view */ @@ -11,25 +16,42 @@ import {AuthService} from '@eg/core/auth.service'; templateUrl: 'detail.component.html' }) export class HoldDetailComponent implements OnInit { + detailTab = 'notes'; + notes: IdlObject[] = []; + notifies: IdlObject[] = []; - _holdId: number; + private _holdId: number; @Input() set holdId(id: number) { - this._holdId = id; - if (this.initDone) { - this.fetchHold(); + if (this._holdId !== id) { + this._holdId = id; + if (this.initDone) { + this.fetchHold(); + } } } + get holdId(): number { + return this._holdId; + } + hold: any; // wide hold reference @Input() set wideHold(wh: any) { this.hold = wh; } + get wideHold(): any { + return this.hold; + } + initDone: boolean; @Output() onShowList: EventEmitter<any>; + @ViewChild('noteDialog') noteDialog: HoldNoteDialogComponent; + @ViewChild('notifyDialog') notifyDialog: HoldNotifyDialogComponent; + constructor( private net: NetService, + private pcrud: PcrudService, private org: OrgService, private auth: AuthService, ) { @@ -42,15 +64,38 @@ export class HoldDetailComponent implements OnInit { } fetchHold() { - if (!this._holdId) { return; } + if (!this.holdId && !this.hold) { return; } - this.net.request( - 'open-ils.circ', - 'open-ils.circ.hold.wide_hash.stream', - this.auth.token(), {id: this._holdId} - ).subscribe(wideHold => { + const promise = this.hold ? Promise.resolve(this.hold) : + this.net.request( + 'open-ils.circ', + 'open-ils.circ.hold.wide_hash.stream', + this.auth.token(), {id: this.holdId} + ).toPromise(); + + return promise.then(wideHold => { this.hold = wideHold; - }); + // avoid this.holdId = since it re-fires this fetch. + this._holdId = wideHold.id; + }) + .then(_ => this.getNotes()) + .then(_ => this.getNotifies()); + } + + getNotes(): Promise<any> { + this.notes = []; + return this.pcrud.search('ahrn', {hold: this.holdId}) + .pipe(tap(note => this.notes.push(note))).toPromise(); + } + + getNotifies(): Promise<any> { + this.notifies = []; + + return this.pcrud.search('ahn', {hold: this.holdId}, { + flesh: 1, + flesh_fields: {ahn: ['notify_staff']}, + order_by: {ahn: 'notify_time DESC'} + }).pipe(tap(notify => this.notifies.push(notify))).toPromise(); } getOrgName(id: number) { @@ -62,6 +107,19 @@ export class HoldDetailComponent implements OnInit { showListView() { this.onShowList.emit(); } + + deleteNote(note: IdlObject) { + this.pcrud.remove(note).toPromise() + .then(ok => { if (ok) { this.getNotes(); } }); + } + + newNote() { + this.noteDialog.open().subscribe(note => this.notes.unshift(note)); + } + + newNotify() { + this.notifyDialog.open().subscribe(notify => this.getNotifies()); // fleshing + } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/holds.module.ts b/Open-ILS/src/eg2/src/app/staff/share/holds/holds.module.ts index 5bcb68aeaf..b5b4bb25b7 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holds/holds.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/holds.module.ts @@ -9,6 +9,8 @@ import {HoldRetargetDialogComponent} from './retarget-dialog.component'; import {HoldTransferDialogComponent} from './transfer-dialog.component'; import {HoldCancelDialogComponent} from './cancel-dialog.component'; import {HoldManageDialogComponent} from './manage-dialog.component'; +import {HoldNoteDialogComponent} from './note-dialog.component'; +import {HoldNotifyDialogComponent} from './notify-dialog.component'; @NgModule({ declarations: [ @@ -18,7 +20,9 @@ import {HoldManageDialogComponent} from './manage-dialog.component'; HoldRetargetDialogComponent, HoldTransferDialogComponent, HoldCancelDialogComponent, - HoldManageDialogComponent + HoldManageDialogComponent, + HoldNoteDialogComponent, + HoldNotifyDialogComponent ], imports: [ StaffCommonModule, diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.html new file mode 100644 index 0000000000..fa3ed9e8c6 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.html @@ -0,0 +1,37 @@ +<ng-template #dialogContent> + <div class="modal-header bg-info"> + <h4 class="modal-title">Add Hold Note for Hold #{{holdId}}</h4> + <button type="button" class="close" + i18n-aria-label aria-label="Close" (click)="close()"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body form-validated"> + <div class="form-group form-check"> + <input type="checkbox" class="form-check-input" + id="patron-visible-cbox" [(ngModel)]="pub"> + <label class="form-check-label" + for="patron-visible-cbox" i18n>Patron Visible?</label> + </div> + <div class="form-group form-check"> + <input type="checkbox" class="form-check-input" + id="hold-slip-cbox" [(ngModel)]="slip"> + <label class="form-check-label" + for="hold-slip-cbox" i18n>Print on Slip?</label> + </div> + <div class="form-group"> + <label for="title-input" i18n>Note Title</label> + <input type="text" class="form-control" id="title-input" required [(ngModel)]="title"/> + </div> + <div class="form-group"> + <label for="body-input" i18n>Note Body</label> + <textarea class="form-control" id="body-input" required [(ngModel)]="body"> + </textarea> + </div> + <div class="w-100"> + <button class="btn btn-success ml-auto" (click)="createNote()" + [disabled]="!title || !body" i18n>Create Note</button> + <button class="btn btn-warning ml-2" (click)="close()" i18n>Cancel</button> + </div> + </div> +</ng-template> diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.ts new file mode 100644 index 0000000000..935dd5dc51 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/note-dialog.component.ts @@ -0,0 +1,48 @@ +import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core'; +import {Observable, Observer, of} from 'rxjs'; +import {tap} from 'rxjs/operators'; +import {IdlObject, IdlService} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {OrgService} from '@eg/core/org.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; + +/** New hold note dialog */ + +@Component({ + selector: 'eg-hold-note-dialog', + templateUrl: 'note-dialog.component.html' +}) +export class HoldNoteDialogComponent extends DialogComponent { + pub = false; + slip = false; + title: string; + body: string; + + @Input() holdId: number; + + constructor( + private modal: NgbModal, + private idl: IdlService, + private pcrud: PcrudService + ) { super(modal); } + + createNote() { + const note = this.idl.create('ahrn'); + note.staff('t'); + note.hold(this.holdId); + note.title(this.title); + note.body(this.body); + note.slip(this.slip ? 't' : 'f'); + note.pub(this.pub ? 't' : 'f'); + + this.pcrud.create(note).toPromise().then( + resp => this.close(resp), // new note object + err => console.error('Could not create note', err) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.html new file mode 100644 index 0000000000..5d8e0b0118 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.html @@ -0,0 +1,25 @@ +<ng-template #dialogContent> + <div class="modal-header bg-info"> + <h4 class="modal-title">Create Record of Hold Notification</h4> + <button type="button" class="close" + i18n-aria-label aria-label="Close" (click)="close()"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body form-validated"> + <div class="form-group"> + <label for="method-input" i18n>Notification Method</label> + <input type="text" class="form-control" id="method-input" required [(ngModel)]="method"/> + </div> + <div class="form-group"> + <label for="note-input" i18n>Note</label> + <textarea class="form-control" id="note-input" required [(ngModel)]="note"> + </textarea> + </div> + <div class="w-100"> + <button class="btn btn-success ml-auto" (click)="createNotify()" + [disabled]="!method || !note" i18n>Create</button> + <button class="btn btn-warning ml-2" (click)="close()" i18n>Cancel</button> + </div> + </div> +</ng-template> diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.ts new file mode 100644 index 0000000000..06baa34756 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/notify-dialog.component.ts @@ -0,0 +1,45 @@ +import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core'; +import {Observable, Observer, of} from 'rxjs'; +import {tap} from 'rxjs/operators'; +import {IdlObject, IdlService} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {OrgService} from '@eg/core/org.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; + +/** New hold notify dialog */ + +@Component({ + selector: 'eg-hold-notify-dialog', + templateUrl: 'notify-dialog.component.html' +}) +export class HoldNotifyDialogComponent extends DialogComponent { + method: string; + note: string; + + @Input() holdId: number; + + constructor( + private modal: NgbModal, + private idl: IdlService, + private auth: AuthService, + private pcrud: PcrudService + ) { super(modal); } + + createNotify() { + const notify = this.idl.create('ahn'); + notify.hold(this.holdId); + notify.notify_staff(this.auth.user().id()); + notify.method(this.method); + notify.note(this.note); + + this.pcrud.create(notify).toPromise().then( + resp => this.close(resp), // new notify object + err => console.error('Could not create notify', err) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/styles.css b/Open-ILS/src/eg2/src/styles.css index 9ec9f67262..5ff572a608 100644 --- a/Open-ILS/src/eg2/src/styles.css +++ b/Open-ILS/src/eg2/src/styles.css @@ -63,6 +63,14 @@ h5 {font-size: .95rem} min-height: 40px; } +.well-table .well-label-no-flex { + display: flex; + align-items: center; + margin: 4px; + padding: 4px; + min-height: 40px; +} + .well-table .well-value { flex: 1; display: flex; -- 2.11.0