lp1857911 follow-up tweaks
authorJason Etheridge <jason@EquinoxOLI.org>
Wed, 12 Apr 2023 12:09:42 +0000 (08:09 -0400)
committerJane Sandberg <js7389@princeton.edu>
Tue, 2 May 2023 21:12:26 +0000 (14:12 -0700)
* AdminPage component  -> Return button if page was invoked with gridFilters
* AdminPage component  -> options to hide delete and edit actions
* Stat cat admin pages -> disable edit and delete for stat cat entries to match                                                          behavior of legacy interfaces, until we discuss
                          something better
* AdminPage component  -> stock delete confirmation for AdminPage component
* Stat cat admin pages -> custom delete confirmation prompts for stat cats
* AdminPage component  -> sticky org selector options for org fields in fmEditor
* AdminPage component  -> option for new record org fields to follow context org
* Stat cat admin pages -> enable org field follows context org featuer
* orgFamilySelect      -> persistKey support
* Stat cat admin pages -> use a persistKey for main org selector

Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/share/org-family-select/org-family-select.component.ts
Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.stat-cat-admin.sql [new file with mode: 0644]
docs/RELEASE_NOTES_NEXT/miscellaneous.adoc

index 1ab47ed..c0ea3e4 100644 (file)
@@ -91,6 +91,7 @@
                 placeholder="{{field.label}}..."
                 i18n-placeholder
                 domId="{{idPrefix}}-{{field.name}}"
+                persistKey="{{field.persistKey}}"
                 [limitPerms]="modePerms[mode]"
                 [readOnly]="field.readOnly"
                 [applyDefault]="field.orgDefaultAllowed"
index 4c6f801..77273ef 100644 (file)
@@ -84,6 +84,9 @@ export interface FmFieldOptions {
     // from the default set of form inputs.
     customTemplate?: CustomFieldTemplate;
 
+    // Use this persistKey if the field is an org field
+    persistKey?: StringComponent;
+
     // help text to display via a popover
     helpText?: StringComponent;
 
@@ -569,6 +572,9 @@ export class FmRecordEditorComponent
         } else if (field.datatype === 'org_unit') {
             field.orgDefaultAllowed =
                 this.orgDefaultAllowedList.includes(field.name);
+            if (fieldOptions.persistKey) {
+                field.persistKey = fieldOptions.persistKey;
+            }
         }
 
         if (fieldOptions.helpText) {
index f829265..64188da 100644 (file)
@@ -4,6 +4,7 @@ import {AuthService} from '@eg/core/auth.service';
 import {IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
+import {ServerStoreService} from '@eg/core/server-store.service';
 
 export interface OrgFamily {
   primaryOrgId: number;
@@ -52,6 +53,8 @@ export class OrgFamilySelectComponent implements ControlValueAccessor, OnInit {
 
     @Input() domId: string;
 
+    @Input() persistKey: string;
+
     @Output() onChange = new EventEmitter<any>();
 
     @ViewChildren(OrgSelectComponent)  orgSelects: QueryList<OrgSelectComponent>;
@@ -70,7 +73,8 @@ export class OrgFamilySelectComponent implements ControlValueAccessor, OnInit {
 
     constructor(
         private auth: AuthService,
-        private org: OrgService
+        private org: OrgService,
+        private serverStore: ServerStoreService
     ) {
     }
 
@@ -130,7 +134,26 @@ export class OrgFamilySelectComponent implements ControlValueAccessor, OnInit {
 
             this.propagateChange(this.options);
             this.onChange.emit(this.options);
+
+            if (this.persistKey) {
+                const key = `eg.orgfamilyselect.${this.persistKey}`;
+                this.serverStore.setItem(key, this.options);
+            }
         };
+
+        this.loadPersistedValues();
+    }
+
+    private loadPersistedValues() {
+        if (!this.persistKey) return;
+
+        const key = `eg.orgfamilyselect.${this.persistKey}`;
+
+        this.serverStore.getItem(key).then(persistedOptions => {
+            if (persistedOptions) {
+                this.writeValue(persistedOptions);
+            }
+        });
     }
 
     writeValue(value: OrgFamily) {
index 7332f6a..3363b9f 100644 (file)
@@ -18,12 +18,18 @@ import {tap, switchMap} from 'rxjs/operators';
       <eg-admin-page persistKeyPfx="{{persistKeyPfx}}" idlClass="{{idlClass}}"
         configLinkBasePath="{{configLinkBasePath}}"
         fieldOrder="{{fieldOrder}}"
+        [fieldOptions]="fieldOptions"
         readonlyFields="{{readonlyFields}}"
         recordLabel="{{recordLabel}}"
         orgDefaultAllowed="{{orgDefaultAllowed}}"
+        orgFieldsDefaultingToContextOrg="{{orgFieldsDefaultingToContextOrg}}"
+        contextOrgSelectorPersistKey="{{contextOrgSelectorPersistKey}}"
         [hideClearFilters]="hideClearFilters"
         [defaultNewRecord]="defaultNewRecordIdl"
         [enableUndelete]="enableUndelete"
+        [disableDelete]="disableDelete"
+        [deleteConfirmation]="deleteConfirmation"
+        [disableEdit]="disableEdit"
         [disableOrgFilter]="disableOrgFilter"></eg-admin-page>
       </ng-container>
     `
@@ -35,9 +41,12 @@ export class BasicAdminPageComponent implements OnInit {
     classLabel: string;
     persistKeyPfx: string;
     fieldOrder = '';
+    fieldOptions = {};
+    contextOrgSelectorPersistKey = '';
     readonlyFields = '';
     recordLabel = '';
     orgDefaultAllowed = '';
+    orgFieldsDefaultingToContextOrg = '';
     hideClearFilters: boolean;
     defaultNewRecordIdl: IdlObject;
     configLinkBasePath = '/staff/admin';
@@ -46,6 +55,9 @@ export class BasicAdminPageComponent implements OnInit {
     disableOrgFilter: boolean;
 
     enableUndelete: boolean;
+    disableDelete: boolean;
+    deleteConfirmation: string;
+    disableEdit: boolean;
 
     private getParams$: Observable<ParamMap>;
     private getRouteData$: Observable<any>;
@@ -83,10 +95,16 @@ export class BasicAdminPageComponent implements OnInit {
                     }
                     this.disableOrgFilter = data['disableOrgFilter'];
                     this.enableUndelete = data['enableUndelete'];
+                    this.disableDelete = data['disableDelete'];
+                    this.deleteConfirmation = data['deleteConfirmation'];
+                    this.disableEdit = data['disableEdit'];
                     this.fieldOrder = data['fieldOrder'];
+                    this.fieldOptions = data['fieldOptions'];
+                    this.contextOrgSelectorPersistKey = data['contextOrgSelectorPersistKey'];
                     this.readonlyFields = data['readonlyFields'];
                     this.recordLabel = data['recordLabel'];
                     this.orgDefaultAllowed = data['orgDefaultAllowed'];
+                    this.orgFieldsDefaultingToContextOrg = data['orgFieldsDefaultingToContextOrg'];
                     this.hideClearFilters = data['hideClearFilters'];
                     this.defaultNewRecord = data['defaultNewRecord'];
                 }
index b5eb873..13be7f8 100644 (file)
@@ -70,8 +70,6 @@
       routerLink="/staff/admin/local/config/ui_staff_portal_page_entry"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Standing Penalties" 
       routerLink="/staff/admin/local/config/standing_penalty"></eg-link-table-link>
-    <eg-link-table-link i18n-label label="Statistical Categories Editor" 
-      url="/eg/staff/admin/local/asset/stat_cat_editor"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Item Statistical Categories Editor" 
       routerLink="/staff/admin/local/asset/stat_cat"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Patron Statistical Categories Editor" 
index e0fc95d..ac64960 100644 (file)
@@ -107,7 +107,11 @@ const routes: Routes = [{
         table: 'stat_cat',
         readonlyFields: 'id',
         orgDefaultAllowed: 'owner',
+        orgFieldsDefaultingToContextOrg: 'owner',
+        fieldOptions: {owner: {persistKey: 'admin.stat_cat.owner' } },
+        contextOrgSelectorPersistKey: 'admin.item_stat_cat.main_org_selector',
         recordLabel: $localize `Item Statistical Category`,
+        deleteConfirmation: $localize `Are you sure you wish to delete the selected statistical categories?  This will also remove the affected stat cats from any item records using them.`,
         fieldOrder: 'name,owner,required,opac_visible,checkout_archive,sip_field,sip_format'}]
 }, {
     path: 'asset/stat_cat_entry',
@@ -117,6 +121,11 @@ const routes: Routes = [{
         table: 'stat_cat_entry',
         readonlyFields: 'id,stat_cat',
         orgDefaultAllowed: 'owner',
+        orgFieldsDefaultingToContextOrg: 'owner',
+        fieldOptions: {owner: {persistKey: 'admin.stat_cat.owner' } },
+        contextOrgSelectorPersistKey: 'admin.item_stat_cat.main_org_selector',
+        disableEdit: true,
+        disableDelete: true,
         recordLabel: $localize `Item Statistical Category Entry`,
         hideClearFilters: true,
         fieldOrder: 'stat_cat,value,owner'}]
@@ -128,7 +137,11 @@ const routes: Routes = [{
         table: 'stat_cat',
         readonlyFields: 'id',
         orgDefaultAllowed: 'owner',
+        orgFieldsDefaultingToContextOrg: 'owner',
+        fieldOptions: {owner: {persistKey: 'admin.stat_cat.owner' } },
+        contextOrgSelectorPersistKey: 'admin.patron_stat_cat.main_org_selector',
         recordLabel: $localize `Patron Statistical Category`,
+        deleteConfirmation: $localize `Are you sure you wish to delete the selected statistical categories?  This will also remove the affected stat cats from any patron records using them.`,
         fieldOrder: 'name,owner,required,opac_visible,usr_summary,allow_freetext,checkout_archive,sip_field,sip_format'}]
 }, {
     path: 'actor/stat_cat_entry',
@@ -138,6 +151,11 @@ const routes: Routes = [{
         table: 'stat_cat_entry',
         readonlyFields: 'id,stat_cat',
         orgDefaultAllowed: 'owner',
+        orgFieldsDefaultingToContextOrg: 'owner',
+        fieldOptions: {owner: {persistKey: 'admin.stat_cat.owner' } },
+        contextOrgSelectorPersistKey: 'admin.patron_stat_cat.main_org_selector',
+        disableEdit: true,
+        disableDelete: true,
         recordLabel: $localize `Patron Statistical Category Entry`,
         hideClearFilters: true,
         fieldOrder: 'stat_cat,value,owner'}]
index 877dc94..d12a2a4 100644 (file)
 <ng-template #createErrStrTmpl i18n>Failed to create new {{recordLabel || idlClassDef.label}}</ng-template>
 <eg-string #createErrString [template]="createErrStrTmpl"></eg-string>
 
+<eg-confirm-dialog #deleteConfirmDialog
+  i18n-dialogTitle
+  dialogTitle="Delete Selected Rows"
+  [dialogBodyTemplate]="deleteConfirmMsg">
+</eg-confirm-dialog>
+<ng-template #deleteConfirmMsg>
+  <span *ngIf="!deleteConfirmation" i18n>Are you sure you want to delete the selected rows? This will also delete any data depending on the rows deleted.</span>
+  <span *ngIf="deleteConfirmation">{{deleteConfirmation}}</span>
+</ng-template>
+
 <ng-container *ngIf="orgField || gridFilters">
   <div class="row">
     <div class="col-lg-6">
       <ng-container *ngIf="orgField">
         <eg-org-family-select
+          [persistKey]="contextOrgSelectorPersistKey"
           [limitPerms]="viewPerms" 
           [selectedOrgId]="contextOrg.id()"
           [(ngModel)]="searchOrgs"
-          (ngModelChange)="grid.reload()">
+          (ngModelChange)="contextOrgChanged($event)">
         </eg-org-family-select>
       </ng-container>
     </div>
         <span i18n>Filters Applied: {{gridFilters | json}}</span>
         <a *ngIf="!hideClearFilters" class="ps-2 fst-italic"
           [attr.href]="clearGridFiltersUrl()" i18n>Clear Filters</a>
+        &nbsp;
+        <button class="btn btn-info label-with-material-icon"
+          (click)="goBack()" [disabled]="hasNoHistory()">
+          <span class="material-icons">keyboard_backspace</span>
+          <span i18n>Return</span>
+        </button>
       </ng-container>
     </div>
   </div>
   <eg-grid-toolbar-button [disabled]="translatableFields.length === 0"
     label="Apply Translations" i18n-label (onClick)="translate()">
   </eg-grid-toolbar-button>
-  <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
+  <eg-grid-toolbar-action label="Edit Selected" i18n-label
+    (onClick)="editSelected($event)" *ngIf="!disableEdit">
   </eg-grid-toolbar-action>
   <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)"
-    [disableOnRows]="shouldDisableDelete">
+    [disableOnRows]="shouldDisableDelete" *ngIf="!disableDelete">
   </eg-grid-toolbar-action>
   <eg-grid-toolbar-action label="Undelete Selected" i18n-label (onClick)="undeleteSelected($event)"
     [disableOnRows]="shouldDisableUndelete" *ngIf="enableUndelete">
index cf24c43..ce63793 100644 (file)
@@ -7,6 +7,7 @@ import {GridDataSource, GridColumn} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {TranslateComponent} from '@eg/share/translate/translate.component';
 import {ToastService} from '@eg/share/toast/toast.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 import {Pager} from '@eg/share/util/pager';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {OrgService} from '@eg/core/org.service';
@@ -63,6 +64,15 @@ export class AdminPageComponent implements OnInit {
     // Give the grid an option to undelete any deleted rows
     @Input() enableUndelete: boolean;
 
+    // Remove the ability to delete rows
+    @Input() disableDelete: boolean;
+
+    // Optional: Replace the default deletion confirmation text with this
+    @Input() deleteConfirmation: string;
+
+    // Remove the ability to edit rows
+    @Input() disableEdit: boolean;
+
     // Include objects linking to org units which are ancestors
     // of the selected org unit.
     @Input() includeOrgAncestors: boolean;
@@ -74,6 +84,9 @@ export class AdminPageComponent implements OnInit {
     // following eg.grid.
     @Input() persistKey: string;
 
+    // If present, will be applied to the org selector for the grid
+    @Input() contextOrgSelectorPersistKey: string;
+
     // Optional path component to add to the generated grid persist key,
     // formatted as (for example):
     // 'eg.grid.admin.${persistKeyPfx}.config.billing_type'
@@ -91,6 +104,9 @@ export class AdminPageComponent implements OnInit {
     // optional list of org fields which are allowed a default if unset
     @Input() orgDefaultAllowed: string;
 
+    // list of org fields to receive the context org as their default for new records
+    @Input() orgFieldsDefaultingToContextOrg: string;
+
     // Optional template containing help/about text which will
     // be added to the page, above the grid.
     @Input() helpTemplate: TemplateRef<any>;
@@ -121,6 +137,8 @@ export class AdminPageComponent implements OnInit {
     @ViewChild('undeleteFailedString', { static: true }) undeleteFailedString: StringComponent;
     @ViewChild('undeleteSuccessString', { static: true }) undeleteSuccessString: StringComponent;
     @ViewChild('translator', { static: true }) translator: TranslateComponent;
+    @ViewChild('deleteConfirmDialog', { static: true })
+    private deleteConfirmDialog: ConfirmDialogComponent;
 
     idlClassDef: any;
     pkeyField: string;
@@ -181,6 +199,11 @@ export class AdminPageComponent implements OnInit {
         }
     }
 
+    contextOrgChanged(orgEvent: any) {
+        this.grid.reload();
+        this.setDefaultNewRecordOrgFieldDefaults( orgEvent['primaryOrgId'] );
+    }
+
     ngOnInit() {
 
         this.idlClassDef = this.idl.classes[this.idlClass];
@@ -241,10 +264,31 @@ export class AdminPageComponent implements OnInit {
         this.checkCreatePerms();
         this.applyOrgValues(Number(contextOrg));
 
+        this.setDefaultNewRecordOrgFieldDefaults( Number(contextOrg) );
+
         // If the caller provides not data source, create a generic one.
         if (!this.dataSource) {
             this.initDataSource();
         }
+
+        console.log('admin this',this);
+    }
+
+    setDefaultNewRecordOrgFieldDefaults(contextOrg: number) {
+        // however we get a defaultNewRecord, we may want to default some org fields to the context org
+        if (this.orgFieldsDefaultingToContextOrg) {
+            if (!this.defaultNewRecord) {
+                this.defaultNewRecord = this.idl.create(this.idlClass);
+            }
+            this.orgFieldsDefaultingToContextOrg.split(/,/).forEach( field => {
+                if (this.defaultNewRecord[field] && this.pkeyField !== field) {
+                    if (contextOrg) {
+                        // since this can change often, we'll just blow away anything that might have come in a different way
+                        this.defaultNewRecord[field]( contextOrg );
+                    }
+                }
+            });
+        }
     }
 
     checkCreatePerms() {
@@ -322,6 +366,9 @@ export class AdminPageComponent implements OnInit {
     }
 
     showEditDialog(idlThing: IdlObject): Promise<any> {
+        if (this.disableEdit) {
+            return;
+        }
         this.editDialog.mode = 'update';
         this.editDialog.recordId = idlThing[this.pkeyField]();
         return new Promise((resolve, reject) => {
@@ -370,18 +417,22 @@ export class AdminPageComponent implements OnInit {
     }
 
     deleteSelected(idlThings: IdlObject[]) {
-        idlThings.forEach(idlThing => idlThing.isdeleted(true));
-        this.pcrud.autoApply(idlThings).subscribe(
-            val => {
-                this.deleteSuccessString.current()
-                    .then(str => this.toast.success(str));
-            },
-            err => {
-                this.deleteFailedString.current()
-                    .then(str => this.toast.danger(str));
-            },
-            ()  => this.grid.reload()
-        );
+        this.deleteConfirmDialog.open().subscribe(confirmed => {
+            if ( confirmed ) {
+                idlThings.forEach(idlThing => idlThing.isdeleted(true));
+                this.pcrud.autoApply(idlThings).subscribe(
+                    val => {
+                        this.deleteSuccessString.current()
+                            .then(str => this.toast.success(str));
+                    },
+                    err => {
+                        this.deleteFailedString.current()
+                            .then(str => this.toast.danger(str));
+                    },
+                    ()  => this.grid.reload()
+                );
+            }
+        });
     }
 
     shouldDisableDelete(rows: IdlObject[]): boolean {
@@ -559,6 +610,15 @@ export class AdminPageComponent implements OnInit {
         const url = this.configLinkBasePath + '/' + parts[0] + '/' + parts[1];
         return this.ngLocation.prepareExternalUrl(url);
     }
+
+    hasNoHistory(): boolean {
+        return history.length === 0;
+    }
+
+    goBack() {
+        history.back();
+    }
+
 }
 
 export interface TemplateField {
index 1e0c6f6..4cdff6c 100644 (file)
@@ -20032,6 +20032,27 @@ VALUES (
     'Grid Config: admin.config.idl_field_doc',
     'cwst', 'label'
   )
+), (
+  'eg.orgselect.admin.stat_cat.owner', 'gui', 'integer',
+  oils_i18n_gettext(
+      'eg.orgselect.admin.stat_cat.owner',
+      'Default org unit for stat cat and stat cat entry editors',
+      'cwst', 'label'
+  )
+), (
+  'eg.orgfamilyselect.admin.item_stat_cat.main_org_selector', 'gui', 'integer',
+  oils_i18n_gettext(
+      'eg.orgfamilyselect.admin.item_stat_cat.main_org_selector',
+      'Default org unit for the main org select in the item stat cat and stat cat entry admin interfaces.',
+      'cwst', 'label'
+  )
+), (
+  'eg.orgfamilyselect.admin.patron_stat_cat.main_org_selector', 'gui', 'integer',
+  oils_i18n_gettext(
+      'eg.orgfamilyselect.admin.patron_stat_cat.main_org_selector',
+      'Default org unit for the main org select in the patron stat cat and stat cat entry admin interfaces.',
+      'cwst', 'label'
+  )
 );
 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.stat-cat-admin.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.stat-cat-admin.sql
new file mode 100644 (file)
index 0000000..8b020ef
--- /dev/null
@@ -0,0 +1,31 @@
+
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES
+    (
+        'eg.orgselect.admin.stat_cat.owner', 'gui', 'integer',
+        oils_i18n_gettext(
+            'eg.orgselect.admin.stat_cat.owner',
+            'Default org unit for stat cat and stat cat entry editors',
+            'cwst', 'label'
+        )
+    ), (
+        'eg.orgfamilyselect.admin.item_stat_cat.main_org_selector', 'gui', 'integer',
+        oils_i18n_gettext(
+            'eg.orgfamilyselect.admin.item_stat_cat.main_org_selector',
+            'Default org unit for the main org select in the item stat cat and stat cat entry admin interfaces.',
+            'cwst', 'label'
+        )
+    ), (
+        'eg.orgfamilyselect.admin.patron_stat_cat.main_org_selector', 'gui', 'integer',
+        oils_i18n_gettext(
+            'eg.orgfamilyselect.admin.patron_stat_cat.main_org_selector',
+            'Default org unit for the main org select in the patron stat cat and stat cat entry admin interfaces.',
+            'cwst', 'label'
+        )
+    );
+
+COMMIT;
index c7a9dcd..a17df9a 100644 (file)
@@ -31,3 +31,4 @@
 * Patron and staff login forms now include a button to reveal the password input. (LP#1977554)
 * Adds new Local Administration entries for Item Statistical Categories Editor and Patron Statistical Categories Editor, which are angularized interfaces.
 * Tweaks eg-grids to underline hyperlinks within cells.  This potentially affects multiple interfaces.
+* eg-org-family-select now supports persistKey