lp1857910 Field Documentation Port
authorKyle Huckins <khuckins@catalyte.io>
Wed, 8 Jan 2020 18:04:11 +0000 (18:04 +0000)
committerJane Sandberg <js7389@princeton.edu>
Fri, 20 May 2022 00:16:46 +0000 (18:16 -0600)
- Port grid UI allowing the creation and editing of Field
Documentation from DOJO to Angular

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>

 Changes to be committed:
modified:   Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.html
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.ts
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.module.ts
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/routing.module.ts
modified:   Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>

Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts

index 41f19dc..9188bde 100644 (file)
@@ -30,8 +30,8 @@
     <eg-link-table-link i18n-label label="Event Definition Groups"
       routerLink="/staff/admin/local/action_trigger/event_def_group"></eg-link-table-link>
     <!-- do-able with a list of IDL classes to add to the edit dialog -->
-    <eg-link-table-link i18n-label label="Field Documentation" 
-      url="/eg/staff/admin/local/config/idl_field_doc"></eg-link-table-link>
+    <eg-link-table-link i18n-label label="Field Documentation"
+      routerLink="/staff/admin/local/config/idl_field_doc"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Group Penalty Thresholds" 
       routerLink="/staff/admin/local/permission/grp_penalty_threshold"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Hold Policies" 
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.html
new file mode 100644 (file)
index 0000000..481a681
--- /dev/null
@@ -0,0 +1,52 @@
+<eg-title i18n-prefix prefix="Field Documentation"></eg-title>
+<eg-staff-banner bannerText="Field Documentation" i18n-bannerText></eg-staff-banner>
+
+<div class="row mt-3">
+  <div class="col-md-3">
+    <div class="input-group">
+      <div class="input-group-prepend">
+        <div class="input-group-text" i18n>Class</div>
+        <!-- IDL Selector -->
+        <eg-combobox [allowFreeText]="true"
+          [entries]="idlEntries" [(ngModel)]="selectedClass"
+          (ngModelChange)="setGrid()">
+        </eg-combobox>
+      </div>
+    </div>
+  </div>
+  
+</div>
+
+<div class='w-11 mt-3'>
+  <eg-grid #fieldDocGrid [dataSource]="gridDataSource"
+    persistKey="admin.config.idl_field_doc" idlClass="fdoc">
+    <eg-grid-toolbar-button 
+      label="New Field Documentation" i18n-label (onClick)="createNew()">
+    </eg-grid-toolbar-button>
+    <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
+    </eg-grid-toolbar-action>
+  </eg-grid>
+</div>
+
+<eg-fm-record-editor #editDialog hiddenFields="id" idlClass="fdoc"
+  requiredFields="fm_class,field,owner,string" [(fieldOptions)]="fieldOptions">
+</eg-fm-record-editor>
+
+<ng-template #fieldClassSelector let-fieldentries="fieldentries" let-selected="selectedEntry">
+  <eg-combobox [allowFreeText]="true" [(ngModel)]="selected" required="true"
+    [entries]="fieldentries" (onChange)="setClass($event)">
+  </eg-combobox>
+</ng-template>
+
+<ng-template #fieldSelector
+  let-entries="fields" let-selected="selectedEntry">
+  <eg-combobox [allowFreeText]="true" required="true"
+    [entries]="fields" [(ngModel)]="selected"
+    (ngModelChange)="setField($event)">
+  </eg-combobox>
+</ng-template>
+
+<eg-string #updateSuccessString text="Updated succeeded!" i18n-text></eg-string>
+<eg-string #updateFailedString text="Updated failed." i18n-text></eg-string>
+<eg-string #createSuccessString text="New Field Documentation Created!" i18n-text></eg-string>
+<eg-string #createFailedString text="Creation of new Field Documentation failed." i18n-text></eg-string>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.component.ts
new file mode 100644 (file)
index 0000000..a8ff1ec
--- /dev/null
@@ -0,0 +1,183 @@
+import {Component, OnInit, Input, ViewChild, ViewEncapsulation} from'@angular/core';
+import {Router} from '@angular/router';
+import {Observable, Observer, of} from 'rxjs';
+import {map} from 'rxjs/operators';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
+import {AuthService} from '@eg/core/auth.service';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+
+@Component({
+    templateUrl: './field-documentation.component.html'
+})
+
+export class FieldDocumentationComponent implements OnInit {
+
+    idlEntries: any[] = [];
+    fieldOptions: any = {};
+    @Input() selectedClass: any;
+    @Input() fields: [] = [];
+    gridDataSource: GridDataSource;
+    @ViewChild('fieldClassSelector', {static: true}) fieldClassSelector: any;
+    @ViewChild('fieldSelector', {static: true}) fieldSelector: any;
+    @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+    @ViewChild('fieldDocGrid', { static: true }) fieldDocGrid: GridComponent;
+    @ViewChild('updateSuccessString', { static: true }) updateSuccessString: StringComponent;
+    @ViewChild('createSuccessString', { static: false }) createSuccessString: StringComponent;
+    @ViewChild('createFailedString', { static: false }) createFailedString: StringComponent;
+    @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent;
+
+    constructor(
+    private auth: AuthService,
+        private idl: IdlService,
+        private pcrud: PcrudService,
+        private toast: ToastService
+    ) {}
+
+    ngOnInit() {
+        this.gridDataSource = new GridDataSource();
+        Object.values(this.idl.classes).forEach(idlClass => {
+            let fields = [];
+            Object.values(idlClass['field_map']).forEach(field => {
+                // We can safely ignore virtual fields...
+                if (!field['virtual']) {
+                    fields.push({
+                        id: field['name'],
+                        label: field['label'] ? field['label'] : field['name']
+                    });
+                }
+            });
+            if (idlClass['label']) {
+                this.idlEntries.push({
+                    label: idlClass['label'],
+                    id: idlClass['name'],
+                    fields: fields
+                });
+            }
+        });
+        this.idlEntries.sort((a, b) => {
+            let textA = a.label.toUpperCase();
+            let textB = b.label.toUpperCase();
+            return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
+        });
+        if (this.selectedClass) this.setGrid();
+        this.fieldDocGrid.onRowActivate.subscribe((fieldDoc: IdlObject) => {
+            this.showEditDialog(fieldDoc);
+        });
+
+        this.fieldOptions = {
+            fm_class: {
+                customTemplate: {
+                    template:this.fieldClassSelector,
+                    context: {
+                        fieldentries: this.idlEntries,
+                        selectedEntry: this.selectedClass
+                    }
+                }
+            },
+            field: {
+                customTemplate: {
+                    template: this.fieldSelector,
+                    context: {
+                        selectedEntry: null
+                    }
+                }
+            }
+        }
+    }
+
+    setClass(idlClass, entry?) {
+        if (this.editDialog.record) this.editDialog.record.fm_class(idlClass.id);
+        this.fieldOptions.fm_class.customTemplate.context.selectedEntry = idlClass;
+        this.fields = idlClass.fields;
+        
+        if (entry && entry.field()) this.setField(
+            idlClass.fields.find(o => o.id == entry.field()),
+        );
+    }
+
+    setField(entry) {
+        if (this.editDialog.record) this.editDialog.record.field(entry.id);
+        this.fieldOptions.field.customTemplate.context.selectedEntry = entry;
+    }
+
+    setGrid() {
+        this.gridDataSource.data = [];
+        this.setCurrentFieldDoc();
+    }
+
+    setCurrentFieldDoc() {
+        if (this.selectedClass) {
+            this.fields = this.selectedClass.fields;
+            this.pcrud.search('fdoc',
+                {fm_class: this.selectedClass.id}
+            ).subscribe(fdocs => {
+                this.gridDataSource.data.push(fdocs);
+            });
+        }
+    }
+
+    setFieldOptions(field) {
+        field.owner(this.auth.user().ws_ou());
+        this.fieldOptions.fm_class.customTemplate.context.selectedEntry = this.selectedClass;
+        this.fieldOptions.field.customTemplate.context.fields = this.selectedClass ? this.selectedClass.fields : [];
+        this.fieldOptions.field.customTemplate.context.record = field;
+        if (this.selectedClass) {
+            this.setClass(this.selectedClass, field);
+        }
+    }
+
+    showEditDialog(field: IdlObject): Promise<any> {
+        this.editDialog.mode = 'update';
+        this.editDialog.recordId = field.id();
+        this.setFieldOptions(field);
+        return new Promise((resolve, reject) => {
+            this.editDialog.open({size: 'lg'}).subscribe(result => {
+                this.updateSuccessString.current()
+                    .then(str => this.toast.success(str));
+                this.setGrid();
+                resolve(result);
+            }, error => {
+                this.updateFailedString.current()
+                    .then(str => this.toast.danger(str));
+            });
+        });
+    }
+
+    editSelected(fields: IdlObject[]) {
+        const editOneFieldDoc = (fieldDoc: IdlObject) => {
+            if (!fieldDoc) return;
+            
+            this.showEditDialog(fieldDoc).then(
+                () => editOneFieldDoc(fields.shift())
+            );
+        }
+
+        editOneFieldDoc(fields.shift());
+    }
+
+    createNew() {
+        this.editDialog.mode = 'create';
+        this.editDialog.recordId = null;
+        this.editDialog.record = this.idl.create('fdoc');
+        this.setFieldOptions(this.editDialog.record);
+        this.editDialog.open({size: 'lg'}).subscribe(
+            ok => {
+                this.createSuccessString.current()
+                    .then(str => this.toast.success(str));
+                this.setGrid();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.createFailedString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+}
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/field-documentation.module.ts
new file mode 100644 (file)
index 0000000..0fc1266
--- /dev/null
@@ -0,0 +1,18 @@
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {FieldDocumentationComponent} from './field-documentation.component';
+import {FieldDocumentationRoutingModule} from './routing.module';
+
+@NgModule({
+    declarations: [
+        FieldDocumentationComponent
+    ],
+    imports: [
+        AdminCommonModule,
+        FieldDocumentationRoutingModule
+    ],
+    exports: [],
+    providers: []
+})
+
+export class FieldDocumentationModule {}
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/field-documentation/routing.module.ts
new file mode 100644 (file)
index 0000000..e9e2f27
--- /dev/null
@@ -0,0 +1,14 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {FieldDocumentationComponent} from './field-documentation.component';
+const routes: Routes = [{
+    path: '',
+    component: FieldDocumentationComponent
+}];
+
+@NgModule({
+    imports: [RouterModule.forChild(routes)],
+    exports: [RouterModule]
+})
+
+export class FieldDocumentationRoutingModule {}
\ No newline at end of file
index c87c517..f3ff4e8 100644 (file)
@@ -66,6 +66,9 @@ const routes: Routes = [{
     loadChildren: () =>
       import('./triggers/triggers.module').then(m => m.TriggersModule)
 }, {
+    path: 'config/idl_field_doc',
+    loadChildren: '@eg/staff/admin/local/field-documentation/field-documentation.module#FieldDocumentationModule'
+}, {
     path: ':schema/:table',
     component: BasicAdminPageComponent
 }];