<li class="list-group-item">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox"
+ id="show-copy_notes-attr"
+ [(ngModel)]="volcopy.defaults.hidden.copy_notes">
+ <label class="form-check-label" for="show-copy_notes-attr" i18n>
+ Add Item Notes
+ </label>
+ </div>
+ </li>
+ <li class="list-group-item">
+ <div class="form-check form-check-inline">
+ <input class="form-check-input" type="checkbox"
id="show-statcat_filter-attr"
[(ngModel)]="volcopy.defaults.hidden.statcat_filter">
<label class="form-check-label" for="show-statcat_filter-attr" i18n>
</div>
</div>
+ <div class="border rounded m-1" *ngIf="displayAttr('copy_notes')">
+ <eg-copy-notes-dialog #copyNotesDialog></eg-copy-notes-dialog>
+ <div class="batch-header font-weight-bold p-2" i18n>Add Item Notes</div>
+ <div class="p-1">
+ <button class="btn btn-outline-dark" (click)="openCopyNotes()" i18n>
+ Item Notes
+ </button>
+ </div>
+ </div>
+
<div class="border rounded m-1" *ngIf="displayAttr('statcat_filter')">
<div class="batch-header font-weight-bold p-2" i18n>Stat Cat Filter</div>
<div class="p-1">
} from '@eg/staff/share/holdings/copy-alerts-dialog.component';
import {CopyTagsDialogComponent
} from '@eg/staff/share/holdings/copy-tags-dialog.component';
+import {CopyNotesDialogComponent
+ } from '@eg/staff/share/holdings/copy-notes-dialog.component';
import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
import {BatchItemAttrComponent, BatchChangeSelection
} from '@eg/staff/share/holdings/batch-item-attr.component';
@ViewChild('copyTagsDialog', {static: false})
private copyTagsDialog: CopyTagsDialogComponent;
+ @ViewChild('copyNotesDialog', {static: false})
+ private copyNotesDialog: CopyNotesDialogComponent;
+
@ViewChild('copyTemplateCbox', {static: false})
copyTemplateCbox: ComboboxComponent;
});
}
+ openCopyNotes() {
+ this.copyNotesDialog.inPlaceMode = true;
+ this.copyNotesDialog.copyIds = this.context.copyList().map(c => c.id());
+
+ this.copyNotesDialog.open({size: 'lg'}).subscribe(newNotes => {
+ if (!newNotes || newNotes.length === 0) { return; }
+
+ console.log(newNotes);
+ newNotes.forEach(note => {
+ this.context.copyList().forEach(copy => {
+ const n = this.idl.clone(note);
+ n.owning_copy(copy.id());
+ copy.notes().push(n);
+ copy.ischanged(true);
+ });
+ });
+ });
+ }
+
applyTemplate() {
const entry = this.copyTemplateCbox.selected;
if (!entry) { return; }
flesh_fields: {
acp: [
'call_number', 'location', 'parts', 'tags',
- 'creator', 'editor', 'stat_cat_entries'
+ 'creator', 'editor', 'stat_cat_entries', 'notes'
],
acptcm: ['tag'],
acpt: ['tag_type']
copy.parts([]);
copy.tags([]);
+ copy.notes([]);
copy.stat_cat_entries([]);
return copy;
.then(_ => {
if (this.summary) {
return this.loadCourseInformation(this.summary.record.id())
- .then(_ => this.summary.getBibCallNumber());
+ .then(__ => this.summary.getBibCallNumber());
} else {
if (this.recordId) {
return this.loadSummary();
--- /dev/null
+<eg-string #successMsg text="Successfully Modified Item Notes" i18n-text></eg-string>
+<eg-string #errorMsg text="Failed To Modify Item Notes" i18n-text></eg-string>
+
+<ng-template #dialogContent>
+ <div class="modal-header">
+ <h4 class="modal-title">
+ <ng-container *ngIf="mode == 'create'">
+ <span i18n>Adding notes for {{copyIds.length}} item(s).</span>
+ </ng-container>
+ <ng-container *ngIf="mode == 'manage'">
+ <span i18n>Managing notes for item {{copy.barcode()}}</span>
+ </ng-container>
+ <span i18n></span>
+ </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 p-4 form-validated">
+
+ <ng-container *ngIf="mode == 'manage' && copy.notes().length">
+ <h4 i18n>Existing Notes</h4>
+ <div class="row mt-2 p-2" *ngFor="let note of copy.notes()">
+ <div class="col-lg-4">{{note.title()}}</div>
+ <div class="col-lg-5">{{note.value()}}</div>
+ <div class="col-lg-3">
+ <button class="btn btn-outline-danger"
+ (click)="removeNote(note)" i18n>Remove</button>
+ </div>
+ </div>
+ <hr/>
+ </ng-container>
+
+ <h4 i18n>New Notes</h4>
+ <div class="row mt-2 p-2" *ngFor="let note of newNotes">
+ <div class="col-lg-4">{{note.title()}}</div>
+ <div class="col-lg-5">{{note.value()}}</div>
+ <div class="col-lg-3">
+ <button class="btn btn-outline-danger" (click)="removeNote(note)" i18n>
+ Remove
+ </button>
+ </div>
+ </div>
+
+ <div class="row mt-2 p-2 rounded border border-success">
+ <div class="col-lg-12">
+ <div class="row">
+ <div class="col-lg-6">
+ <input type="text" class="form-control" [(ngModel)]="curNoteTitle"
+ i18n-placeholder placeholder="Note title..."/>
+ </div>
+ <div class="col-lg-6">
+ <div class="form-check">
+ <input class="form-check-input" type="checkbox"
+ [(ngModel)]="curNotePublic" id="pub-check">
+ <label class="form-check-label" for="pub-check">Public Note</label>
+ </div>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col-lg-9">
+ <textarea class="form-control" [(ngModel)]="curNote"
+ i18n-placeholder placeholder="Enter note value..."></textarea>
+ </div>
+ <div class="col-lg-3">
+ <button class="btn btn-success" (click)="addNew()" i18n>Add Note</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" (click)="close()" i18n>Cancel</button>
+ <button class="btn btn-success mr-2" (click)="applyChanges()" i18n>Apply Changes</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable, throwError, from, empty} from 'rxjs';
+import {tap, map, switchMap} from 'rxjs/operators';
+import {NetService} from '@eg/core/net.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+
+/**
+ * Dialog for managing copy notes.
+ */
+
+@Component({
+ selector: 'eg-copy-notes-dialog',
+ templateUrl: 'copy-notes-dialog.component.html'
+})
+
+export class CopyNotesDialogComponent
+ extends DialogComponent implements OnInit {
+
+ // If there are multiple copyIds, only new notes may be applied.
+ // If there is only one copyId, then notes may be applied or removed.
+ @Input() copyIds: number[] = [];
+
+ mode: string; // create | manage
+
+ // If true, no attempt is made to save the new notes to the
+ // database. It's assumed this takes place in the calling code.
+ // This is useful for creating notes for new copies.
+ @Input() inPlaceMode = false;
+
+ // In 'create' mode, we may be adding notes to multiple copies.
+ copies: IdlObject[] = [];
+
+ // In 'manage' mode we only handle a single copy.
+ copy: IdlObject;
+
+ curNote: string;
+ curNoteTitle: string;
+ curNotePublic = false;
+ newNotes: IdlObject[] = [];
+ delNotes: IdlObject[] = [];
+
+ autoId = -1;
+
+ @ViewChild('successMsg', { static: true }) private successMsg: StringComponent;
+ @ViewChild('errorMsg', { static: true }) private errorMsg: StringComponent;
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private toast: ToastService,
+ private net: NetService,
+ private idl: IdlService,
+ private pcrud: PcrudService,
+ private org: OrgService,
+ private auth: AuthService) {
+ super(modal); // required for subclassing
+ }
+
+ ngOnInit() {
+ }
+
+ /**
+ */
+ open(args: NgbModalOptions): Observable<IdlObject[]> {
+ this.copy = null;
+ this.copies = [];
+ this.newNotes = [];
+
+ if (this.copyIds.length === 0 && !this.inPlaceMode) {
+ return throwError('copy ID required');
+ }
+
+ // In manage mode, we can only manage a single copy.
+ // But in create mode, we can add notes to multiple copies.
+
+ if (this.copyIds.length === 1 && !this.inPlaceMode) {
+ this.mode = 'manage';
+ } else {
+ this.mode = 'create';
+ }
+
+ // Observify data loading
+ const obs = from(this.getCopies());
+
+ // Return open() observable to caller
+ return obs.pipe(switchMap(_ => super.open(args)));
+ }
+
+ getCopies(): Promise<any> {
+ if (this.inPlaceMode) { return Promise.resolve(); }
+
+ return this.pcrud.search('acp', {id: this.copyIds},
+ {flesh: 1, flesh_fields: {acp: ['notes']}},
+ {atomic: true}
+ )
+ .toPromise().then(copies => {
+ this.copies = copies;
+ if (copies.length === 1) {
+ this.copy = copies[0];
+ }
+ });
+ }
+
+ removeNote(note: IdlObject) {
+ this.newNotes = this.newNotes.filter(t => t.id() !== note.id());
+
+ if (note.isnew() || this.mode === 'create') { return; }
+
+ const existing = this.copy.notes().filter(n => n.id() === note.id())[0];
+ if (!existing) { return; }
+
+ existing.isdeleted(true);
+ this.delNotes.push(existing);
+
+ // Remove from copy for dialog display
+ this.copy.notes(this.copy.notes().filter(n => n.id() !== note.id()));
+ }
+
+ addNew() {
+ if (!this.curNoteTitle || !this.curNote) { return; }
+
+ const note = this.idl.create('acpn');
+ note.isnew(true);
+ note.creator(this.auth.user().id());
+ note.pub(this.curNotePublic ? 't' : 'f');
+ note.title(this.curNoteTitle);
+ note.value(this.curNote);
+ note.id(this.autoId--);
+
+ this.newNotes.push(note);
+
+ this.curNote = '';
+ this.curNoteTitle = '';
+ this.curNotePublic = false;
+ }
+
+ applyChanges() {
+
+ if (this.inPlaceMode) {
+ this.close(this.newNotes);
+ return;
+ }
+
+ const notes = [];
+ this.newNotes.forEach(note => {
+ this.copies.forEach(copy => {
+ const n = this.idl.clone(note);
+ n.id(null); // remove temp ID, it will be duped
+ n.owning_copy(copy.id());
+ notes.push(n);
+ });
+ });
+
+ this.pcrud.create(notes).toPromise()
+ .then(_ => {
+ if (this.delNotes.length) {
+ return this.pcrud.remove(this.delNotes).toPromise();
+ }
+ }).then(_ => {
+ this.successMsg.current().then(msg => this.toast.success(msg));
+ this.close(this.newNotes.length > 0 || this.delNotes.length > 0);
+ });
+ }
+}
+
import {MarkMissingDialogComponent} from './mark-missing-dialog.component';
import {CopyAlertsDialogComponent} from './copy-alerts-dialog.component';
import {CopyTagsDialogComponent} from './copy-tags-dialog.component';
+import {CopyNotesDialogComponent} from './copy-notes-dialog.component';
import {ReplaceBarcodeDialogComponent} from './replace-barcode-dialog.component';
import {DeleteHoldingDialogComponent} from './delete-volcopy-dialog.component';
import {ConjoinedItemsDialogComponent} from './conjoined-items-dialog.component';
MarkMissingDialogComponent,
CopyAlertsDialogComponent,
CopyTagsDialogComponent,
+ CopyNotesDialogComponent,
ReplaceBarcodeDialogComponent,
DeleteHoldingDialogComponent,
ConjoinedItemsDialogComponent,
MarkMissingDialogComponent,
CopyAlertsDialogComponent,
CopyTagsDialogComponent,
+ CopyNotesDialogComponent,
ReplaceBarcodeDialogComponent,
DeleteHoldingDialogComponent,
ConjoinedItemsDialogComponent,