From c7f81556d59086ce4bda45a21910cbdb10c727ba Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 23 Feb 2021 11:54:28 -0500 Subject: [PATCH] LP1904036 Edit due date; styling overdues Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- .../src/app/staff/circ/patron/items.component.ts | 2 +- .../eg2/src/app/staff/share/circ/circ.module.ts | 4 +- .../share/circ/due-date-dialog.component.html | 50 ++++++++++++ .../staff/share/circ/due-date-dialog.component.ts | 95 ++++++++++++++++++++++ .../src/app/staff/share/circ/grid.component.html | 18 +++- .../eg2/src/app/staff/share/circ/grid.component.ts | 78 ++++++++++++++++-- Open-ILS/src/eg2/src/styles.css | 9 ++ 7 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts index bc275c73e9..e342b1a93d 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts @@ -135,7 +135,7 @@ export class ItemsComponent implements OnInit, AfterViewInit { this.altList = this.altList.concat(list); } } else { - if (4 & displayCode) return; // bitflag 4 == hide on checkin + if (4 & displayCode) { return; } // bitflag 4 == hide on checkin this.altList = this.altList.concat(list); } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts index 02d0b9a286..af4904f8a0 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts @@ -3,10 +3,12 @@ import {StaffCommonModule} from '@eg/staff/common.module'; import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module'; import {CircService} from './circ.service'; import {CircGridComponent} from './grid.component'; +import {DueDateDialogComponent} from './due-date-dialog.component'; @NgModule({ declarations: [ - CircGridComponent + CircGridComponent, + DueDateDialogComponent ], imports: [ StaffCommonModule, diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.html new file mode 100644 index 0000000000..f47258f6c2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.html @@ -0,0 +1,50 @@ + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.ts new file mode 100644 index 0000000000..60ced722fc --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/due-date-dialog.component.ts @@ -0,0 +1,95 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {EventService} from '@eg/core/event.service'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {AuthService} from '@eg/core/auth.service'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {StringComponent} from '@eg/share/string/string.component'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +/* Dialog for modifying circulation due dates. */ + +@Component({ + selector: 'eg-due-date-dialog', + templateUrl: 'due-date-dialog.component.html' +}) + +export class DueDateDialogComponent + extends DialogComponent implements OnInit { + + @Input() circs: IdlObject[] = []; + @ViewChild('successMsg', { static: true }) private successMsg: StringComponent; + @ViewChild('errorMsg', { static: true }) private errorMsg: StringComponent; + + dueDateIsValid = false; + dueDateIso: string; + numSucceeded: number; + numFailed: number; + nowTime: number; + + constructor( + private modal: NgbModal, // required for passing to parent + private toast: ToastService, + private net: NetService, + private evt: EventService, + private pcrud: PcrudService, + private auth: AuthService) { + super(modal); // required for subclassing + } + + ngOnInit() { + this.onOpen$.subscribe(_ => { + this.numSucceeded = 0; + this.numFailed = 0; + this.dueDateIso = new Date().toISOString(); + this.nowTime = new Date().getTime(); + }); + } + + dueDateChange(iso: string) { + if (iso && Date.parse(iso) > this.nowTime) { + this.dueDateIso = iso; + } else { + this.dueDateIso = null; + } + } + + modifyBatch() { + if (!this.dueDateIso) { return; } + + let promise = Promise.resolve(); + + this.circs.forEach(circ => { + promise = promise.then(_ => this.modifyOne(circ)); + }); + + promise.then(_ => { + this.close(); + this.circs = []; + }); + } + + modifyOne(circ: IdlObject): Promise { + return this.net.request( + 'open-ils.circ', + 'open-ils.circ.circulation.due_date.update', + this.auth.token(), circ.id(), this.dueDateIso + + ).toPromise().then(modCirc => { + + const evt = this.evt.parse(modCirc); + + if (evt) { + this.numFailed++; + console.error(evt); + } else { + this.numSucceeded++; + this.respond(modCirc); + } + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html index 410cbf57d8..1a7ab9873a 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html @@ -1,6 +1,8 @@ + + @@ -8,8 +10,16 @@ {{r.title}} + + + {{r.copy.barcode()}} + + + @@ -23,6 +33,11 @@ (onClick)="openItemAlerts($event, 'create')"> + + + @@ -35,7 +50,8 @@ - + diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts index fd489f3add..da0f97b0be 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts @@ -8,7 +8,8 @@ import {NetService} from '@eg/core/net.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {CheckoutParams, CheckoutResult, CircService} from './circ.service'; import {PromptDialogComponent} from '@eg/share/dialog/prompt.component'; -import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {GridDataSource, GridColumn, GridCellTextGenerator, + GridRowFlairEntry} from '@eg/share/grid/grid'; import {GridComponent} from '@eg/share/grid/grid.component'; import {Pager} from '@eg/share/util/pager'; import {StoreService} from '@eg/core/store.service'; @@ -18,6 +19,8 @@ import {CopyAlertsDialogComponent } from '@eg/staff/share/holdings/copy-alerts-dialog.component'; import {ArrayUtil} from '@eg/share/util/array'; import {PrintService} from '@eg/share/print/print.service'; +import {StringComponent} from '@eg/share/string/string.component'; +import {DueDateDialogComponent} from './due-date-dialog.component'; export interface CircGridEntry { index: string; // class + id -- row index @@ -31,6 +34,10 @@ export interface CircGridEntry { dueDate?: string; copyAlertCount?: number; nonCatCount?: number; + + // useful for reporting precaculated values and avoiding + // repetitive date creation on grid render. + overdue?: boolean; } const CIRC_FLESH_DEPTH = 4; @@ -63,10 +70,16 @@ export class CircGridComponent implements OnInit { entries: CircGridEntry[] = null; gridDataSource: GridDataSource = new GridDataSource(); cellTextGenerator: GridCellTextGenerator; + rowFlair: (row: CircGridEntry) => GridRowFlairEntry; + rowClass: (row: CircGridEntry) => string; + + nowDate: number = new Date().getTime(); + @ViewChild('overdueString') private overdueString: StringComponent; @ViewChild('circGrid') private circGrid: GridComponent; @ViewChild('copyAlertsDialog') private copyAlertsDialog: CopyAlertsDialogComponent; + @ViewChild('dueDateDialog') private dueDateDialog: DueDateDialogComponent; constructor( private org: OrgService, @@ -84,15 +97,24 @@ export class CircGridComponent implements OnInit { // The grid never fetches data directly. // The caller is responsible initiating all data loads. this.gridDataSource.getRows = (pager: Pager, sort: any[]) => { - if (this.entries) { - return from(this.entries); - } else { - return empty(); - } + return this.entries ? from(this.entries) : empty(); }; this.cellTextGenerator = { - title: row => row.title + title: row => row.title, + barcode: row => row.copy ? row.copy.barcode() : '' + }; + + this.rowFlair = (row: CircGridEntry) => { + if (this.circIsOverdue(row)) { + return {icon: 'error_outline', title: this.overdueString.text}; + } + }; + + this.rowClass = (row: CircGridEntry) => { + if (this.circIsOverdue(row)) { + return 'less-intense-alert'; + } }; } @@ -200,6 +222,14 @@ export class CircGridComponent implements OnInit { ); } + getCopies(rows: any): IdlObject[] { + return rows.filter(r => r.copy).map(r => r.copy); + } + + getCircs(rows: any): IdlObject[] { + return rows.filter(r => r.circ).map(r => r.circ); + } + printReceipts(rows: any) { if (rows.length > 0) { this.printer.print({ @@ -209,5 +239,39 @@ export class CircGridComponent implements OnInit { }); } } + + editDueDate(rows: any) { + const circs = this.getCircs(rows); + if (circs.length === 0) { return; } + + let refreshNeeded = false; + this.dueDateDialog.circs = circs; + this.dueDateDialog.open().subscribe( + circ => { + refreshNeeded = true; + const row = rows.filter(r => r.circ.id() === circ.id())[0]; + row.circ.due_date(circ.due_date()); + row.dueDate = circ.due_date(); + delete row.overdue; // it will recalculate + }, + err => console.error(err), + () => { + if (refreshNeeded) { + this.reloadGrid(); + } + } + ); + } + + circIsOverdue(row: CircGridEntry): boolean { + const circ = row.circ; + + if (!circ) { return false; } // noncat + + if (row.overdue === undefined) { + row.overdue = (Date.parse(circ.due_date()) < this.nowDate); + } + return row.overdue; + } } diff --git a/Open-ILS/src/eg2/src/styles.css b/Open-ILS/src/eg2/src/styles.css index 08f6959cf6..bccc56bcbd 100644 --- a/Open-ILS/src/eg2/src/styles.css +++ b/Open-ILS/src/eg2/src/styles.css @@ -323,3 +323,12 @@ input.small { width: 4em; } +/* + * Created initially for styled grid rows where full 'bg-danger' CSS is + * intense and not especially readable, more so when rows are stacked. + * http://web-accessibility.carnegiemuseums.org/design/color/ + */ +.less-intense-alert { + background-color: #f9dede; + color: black; +} -- 2.11.0