LP#1844169: port Search Filter Groups admin interface to Angular
authorMike Risher <mrisher@catalyte.io>
Tue, 17 Sep 2019 21:33:04 +0000 (21:33 +0000)
committerGalen Charlton <gmc@equinoxOLI.org>
Mon, 12 Jul 2021 15:07:58 +0000 (11:07 -0400)
Port search filter groups admin from DOJO UI to Angular. Each search
filter group has its own edit page, from which you can edit search
filter group entries.

Signed-off-by: Mike Risher <mrisher@catalyte.io>
Signed-off-by: Terran McCanna <tmccanna@georgialibraries.org>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>

Open-ILS/examples/fm_IDL.xml
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/admin/local/search-filter/query-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/query-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.module.ts [new file with mode: 0644]

index 242c357..5c462e4 100644 (file)
@@ -6535,12 +6535,12 @@ SELECT  usr,
 
        <class id="asfg" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::search_filter_group" oils_persist:tablename="actor.search_filter_group" reporter:label="Search Filter Group" oils_persist:field_safe="true">
                <fields oils_persist:primary="id" oils_persist:sequence="actor.search_filter_group_id_seq">
-                       <field name="id" reporter:datatype="id" reporter:selector="label"/>
-                       <field name="owner" reporter:datatype="org_unit"/>
-                       <field name="code" reporter:datatype="text"/>
-                       <field name="label" reporter:datatype="text" oils_persist:i18n="true"/>
-                       <field name="create_date" reporter:datatype="timestamp"/>
-                       <field name="entries" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field name="id" reporter:datatype="id" reporter:selector="label" reporter:label="ID" oils_obj:required="true"/>
+                       <field name="owner" reporter:datatype="org_unit" reporter:label="Owning Org Unit" oils_obj:required="true"/>
+                       <field name="code" reporter:datatype="text" reporter:label="Code" oils_obj:required="true"/>
+                       <field name="label" reporter:datatype="text" oils_persist:i18n="true" reporter:label="Label" oils_obj:required="true"/>
+                       <field name="create_date" reporter:datatype="timestamp" reporter:label="Create Date"/>
+                       <field name="entries" oils_persist:virtual="true" reporter:datatype="link" reporter:label="Group Entries"/>
                </fields>
                <links>
                        <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
@@ -6557,10 +6557,10 @@ SELECT  usr,
        </class>
        <class id="asfge" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::search_filter_group_entry" oils_persist:tablename="actor.search_filter_group_entry" reporter:label="Search Filter Group Entry" oils_persist:field_safe="true">
                <fields oils_persist:primary="id" oils_persist:sequence="actor.search_filter_group_entry_id_seq">
-                       <field name="id" reporter:datatype="id"/>
-                       <field name="grp" reporter:datatype="link"/>
-                       <field name="pos" reporter:datatype="int"/>
-                       <field name="query" reporter:datatype="link"/>
+                       <field name="id" reporter:datatype="id" reporter:label="ID" oils_obj:required="true"/>
+                       <field name="grp" reporter:datatype="link" reporter:label="Search Filter Group" oils_obj:required="true"/>
+                       <field name="pos" reporter:datatype="int" reporter:label="Position" oils_obj:required="true"/>
+                       <field name="query" reporter:datatype="link" reporter:label="Query" oils_obj:required="true"/>
                </fields>
                <links>
                        <link field="grp" reltype="has_a" key="id" map="" class="asfg"/>
index d9e34ea..e460ca4 100644 (file)
@@ -57,7 +57,7 @@
     <eg-link-table-link i18n-label label="Permission Tree Display Entries" 
       url="/eg/staff/admin/local/permission/grp_tree_display_entry"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Search Filter Groups" 
-      url="/eg/staff/admin/local/actor/search_filter_group"></eg-link-table-link>
+      routerLink="/staff/admin/local/actor/search_filter_group"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Shelving Location Groups" 
       routerLink="/staff/admin/local/asset/shelving_location_groups"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Shelving Location Order" 
index c7ec59f..bfacd2c 100644 (file)
@@ -38,6 +38,10 @@ const routes: Routes = [{
 }, {
     path: 'asset/course_module_term_course_map',
     component: CourseTermMapComponent
+}, {    
+    path: 'actor/search_filter_group',
+    loadChildren: () =>
+      import('./search-filter/search-filter-group.module').then(m => m.SearchFilterGroupModule)
 }, {
     path: 'config/circ_limit_set',
     loadChildren: () =>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/query-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/query-dialog.component.html
new file mode 100644 (file)
index 0000000..9f25211
--- /dev/null
@@ -0,0 +1,45 @@
+<ng-template #dialogContent>
+    <div class="modal-header bg-info">
+        <h4 class="modal-title" i18n>New Query</h4>
+        <button type="button" class="close"
+            i18n-aria-label aria-label="Close"
+            (click)="closeAndReset()">
+            <span aria-hidden="true">&times;</span>
+        </button>
+    </div>
+    <div class="modal-body"> 
+        <div class="row">
+            <div class="col-lg-3 font-weight-bold" i18n>Query Label</div>
+            <div class="col-lg-9">
+                <input type="text" class="form-control" 
+                placeholder="Query Label"
+                i18n-placeholder required
+                [(ngModel)]="newQueryLabel"/>
+            </div>
+        </div>
+        <div class="row mt-3">
+            <div class="col-lg-3 font-weight-bold" i18n>Query Text</div>
+            <div class="col-lg-9">
+                <input type="text" class="form-control" 
+                placeholder="Query Text"
+                i18n-placeholder required
+                [(ngModel)]="newQueryText"/>
+            </div>
+        </div>
+        <div class="row mt-3">
+            <div class="col-lg-3 font-weight-bold" i18n>Position</div>
+            <div class="col-lg-9">
+                <input type="number" class="form-control" 
+                placeholder="Position"
+                i18n-placeholder required
+                [(ngModel)]="newQueryPosition"/>
+            </div>
+        </div>
+    </div>
+    <div class="modal-footer">
+        <button type="button" class="btn btn-warning"
+            (click)="closeAndReset()" i18n>Cancel</button>
+        <button type="button" class="btn btn-success"
+            (click)="save()" i18n >Save</button>
+    </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/query-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/query-dialog.component.ts
new file mode 100644 (file)
index 0000000..f1d1776
--- /dev/null
@@ -0,0 +1,90 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+import {ActivatedRoute} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {AuthService} from '@eg/core/auth.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+  selector: 'eg-query-dialog',
+  templateUrl: './query-dialog.component.html'
+})
+
+export class QueryDialogComponent extends DialogComponent implements OnInit {
+
+    currentId: number;
+    newAsq: IdlObject;
+    newAsfge: IdlObject;
+
+    @Input() mode: 'create' | 'update';
+    @Input() record: IdlObject;
+    @Input() recordId: number;
+    @Input() newQueryLabel: string;
+    @Input() newQueryText: string;
+    @Input() newQueryPosition: string;
+
+    constructor(
+        private modal: NgbModal, // required for passing to parent
+        private route: ActivatedRoute,
+        private idl: IdlService,
+        private net: NetService,
+        private auth: AuthService
+        ) {
+        super(modal); // required for subclassing
+    }
+
+    ngOnInit() {
+        this.currentId = parseInt(this.route.snapshot.paramMap.get('id'), 10);
+        this.newAsfge = this.idl.create('asfge');
+        this.newAsq = this.idl.create('asq');
+    }
+
+    // wipe out all data so next time we start with a clean slate
+    closeAndReset(data) {
+        this.mode = undefined;
+        this.record = undefined;
+        this.recordId = undefined;
+        this.newQueryLabel = undefined;
+        this.newQueryPosition = undefined;
+        this.newQueryText = undefined;
+        this.close(data);
+    }
+
+    save() {
+        if (!this.newQueryLabel || (!this.newQueryPosition && (this.newQueryPosition != 0)) || !this.newQueryText) {
+            this.closeAndReset({notFilledOut: true});
+        }
+        const recToSave = this.prepareRecord();
+        return this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.filter_group_entry.crud',
+            this.auth.token(),
+            recToSave
+        ).toPromise().then(res => {
+            this.closeAndReset(res);
+        });
+    }
+
+    prepareRecord(): IdlObject {
+        let recToSave;
+        let queryToSave;
+        if (this.mode === 'create') {
+            recToSave = this.idl.clone(this.newAsfge);
+            queryToSave = this.idl.clone(this.newAsq);
+            recToSave.isnew(true);
+            recToSave.query(queryToSave);
+        } else if (this.mode === 'update') {
+            recToSave = this.record;
+            queryToSave = this.record.query();
+            recToSave.ischanged(true);
+        } else {
+            console.debug('Error!  No mode defined!');
+        }
+        queryToSave.label(this.newQueryLabel);
+        queryToSave.query_text(this.newQueryText);
+        recToSave.pos(this.newQueryPosition);
+        recToSave.grp(this.currentId);
+        return recToSave;
+    }
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.html
new file mode 100644 (file)
index 0000000..f533942
--- /dev/null
@@ -0,0 +1,37 @@
+<eg-staff-banner bannerText="Search Filter Group" i18n-bannerText></eg-staff-banner>
+
+<div class="col-lg-6 offset-lg-3">
+    <eg-fm-record-editor displayMode="inline" hiddenFieldsList="id,create_date"
+        idlClass="asfg" mode="update" recordId="{{this.currentId}}">
+    </eg-fm-record-editor>
+</div>
+
+<eg-staff-banner bannerText="Search Filter Group Entries" i18n-bannerText></eg-staff-banner>
+
+<eg-grid #grid idlClass="asfge" [dataSource]="dataSource" [sortable]="false"
+    hideFields="grp,query">
+    <eg-grid-column path="query.label" label="Query Label"></eg-grid-column>
+    <eg-grid-column path="query.query_text" label="Query Text"></eg-grid-column>
+    <eg-grid-toolbar-button label="New" i18n-label 
+        (onClick)="createQuery()"></eg-grid-toolbar-button>
+    <eg-grid-toolbar-action label="Edit Selected" i18n-label 
+        (onClick)="editQuery($event)"></eg-grid-toolbar-action>
+    <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+</eg-grid>
+
+<eg-query-dialog #queryDialog ></eg-query-dialog>
+
+<eg-string #createQueryString i18n-text text="New Query Added"></eg-string>
+<eg-string #queryRequiredString i18n-text text="Error! Are all fields filled out?"></eg-string>
+<eg-string #createString i18n-text text="New Search Filter Group Entry Added"></eg-string>
+<eg-string #createErrString i18n-text 
+    text="Creation failed or was not allowed"></eg-string>
+<eg-string #deleteFailedString i18n-text 
+    text="Delete failed or was not allowed"></eg-string>
+<eg-string #deleteSuccessString i18n-text 
+    text="Delete succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text 
+    text="Update failed or was not allowed"></eg-string>
+<eg-string #updateSuccessString i18n-text 
+    text="Update succeeded"></eg-string>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-entries.component.ts
new file mode 100644 (file)
index 0000000..43cc24e
--- /dev/null
@@ -0,0 +1,122 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {QueryDialogComponent} from './query-dialog.component';
+
+@Component({
+    templateUrl: './search-filter-group-entries.component.html'
+})
+
+export class SearchFilterGroupEntriesComponent implements OnInit {
+
+    @ViewChild('editDialog') editDialog: FmRecordEditorComponent;
+    @ViewChild('queryDialog') queryDialog: QueryDialogComponent;
+    @ViewChild('grid', { static: true }) grid: GridComponent;
+
+    @ViewChild('updateSuccessString') updateSuccessString: StringComponent;
+    @ViewChild('updateFailedString') updateFailedString: StringComponent;
+    @ViewChild('createString') createString: StringComponent;
+    @ViewChild('createQueryString') createQueryString: StringComponent;
+    @ViewChild('queryRequiredString') queryRequiredString: StringComponent;
+    @ViewChild('createErrString') createErrString: StringComponent;
+    @ViewChild('deleteFailedString') deleteFailedString: StringComponent;
+    @ViewChild('deleteSuccessString') deleteSuccessString: StringComponent;
+
+    @Input() dataSource: GridDataSource;
+
+    currentId: number;
+
+    constructor(
+        private route: ActivatedRoute,
+        private pcrud: PcrudService,
+        private toast: ToastService
+    ) {
+        this.dataSource = new GridDataSource();
+    }
+
+    ngOnInit() {
+        this.currentId = parseInt(this.route.snapshot.paramMap.get('id'), 10);
+        this.dataSource.getRows = (pager: Pager, sort: any[]) => {
+            const searchOps = {
+                offset: pager.offset,
+                limit: pager.limit,
+                order_by: {asfge: 'pos'},
+                flesh: 1,
+                flesh_fields: {asfge: ['query']}
+            };
+            return this.pcrud.search('asfge', {grp: this.currentId}, searchOps);
+        };
+        this.grid.onRowActivate.subscribe(
+            (idlThing: IdlObject) => this.editQuery([idlThing])
+        );
+    }
+
+    createQuery = () => {
+        this.queryDialog.mode = 'create';
+        this.queryDialog.open({size: 'lg'}).subscribe(
+            result => {
+                if (result.notFilledOut) {
+                    this.queryRequiredString.current()
+                        .then(str => this.toast.danger(str));
+                } else {
+                    this.createQueryString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                }
+            },
+            error => {
+                this.createErrString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    editQuery = (event) => {
+        const firstRecord = event[0];
+        this.queryDialog.record = firstRecord;
+        this.queryDialog.mode = 'update';
+        this.queryDialog.newQueryLabel = firstRecord.query().label();
+        this.queryDialog.newQueryText = firstRecord.query().query_text();
+        this.queryDialog.newQueryPosition = firstRecord.pos();
+        this.queryDialog.recordId = firstRecord.id();
+        this.queryDialog.open({size: 'lg'}).subscribe(
+            result => {
+                if (result.notFilledOut) {
+                    this.queryRequiredString.current()
+                        .then(str => this.toast.danger(str));
+                } else {
+                    this.updateSuccessString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                }
+            },
+            error => {
+                this.updateFailedString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    deleteSelected = (idlThings: IdlObject[]) => {
+        idlThings.forEach(idlThing => idlThing.isdeleted(true));
+        this.pcrud.autoApply(idlThings).subscribe(
+            val => {
+                console.debug('deleted: ' + val);
+                this.deleteSuccessString.current()
+                    .then(str => this.toast.success(str));
+            },
+            err => {
+                this.deleteFailedString.current()
+                    .then(str => this.toast.danger(str));
+            },
+            ()  => this.grid.reload()
+        );
+    }
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group-routing.module.ts
new file mode 100644 (file)
index 0000000..9763794
--- /dev/null
@@ -0,0 +1,19 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {SearchFilterGroupComponent} from './search-filter-group.component';
+import {SearchFilterGroupEntriesComponent} from './search-filter-group-entries.component';
+
+const routes: Routes = [{
+    path: ':id',
+    component: SearchFilterGroupEntriesComponent
+  }, {
+    path: '',
+    component: SearchFilterGroupComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class SearchFilterGroupRoutingModule {}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.html
new file mode 100644 (file)
index 0000000..1defc26
--- /dev/null
@@ -0,0 +1,23 @@
+<eg-staff-banner bannerText="Search Filter Group Configuration" i18n-bannerText>
+    </eg-staff-banner>
+
+<eg-grid #grid idlClass="asfg" [dataSource]="gridDataSource" [sortable]="true">
+<eg-grid-column label="ID" path="id" name="id" datatype="id" [hidden]="true"></eg-grid-column>
+    <eg-grid-toolbar-button label="New Search Filter Group" 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-toolbar-action label="Delete Selected" i18n-label 
+        (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+</eg-grid>
+
+<eg-fm-record-editor #editDialog idlClass="asfg">
+</eg-fm-record-editor>
+
+<eg-string #createString i18n-text text="New Search Filter Group Added"></eg-string>
+<eg-string #createErrString i18n-text text="Failed to Create New Search Filter Group">
+</eg-string>
+<eg-string #deleteFailedString i18n-text 
+    text="Deletion of Search Filter Group failed or was not allowed"></eg-string>
+<eg-string #DeletionSuccessString i18n-text text="Delete of Search Filter Group succeeded">
+</eg-string>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.component.ts
new file mode 100644 (file)
index 0000000..519f54f
--- /dev/null
@@ -0,0 +1,92 @@
+import {Pager} from '@eg/share/util/pager';
+import {Location} from '@angular/common';
+import {FormatService} from '@eg/core/format.service';
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import {Router, ActivatedRoute} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {PermService} from '@eg/core/perm.service';
+import {AuthService} from '@eg/core/auth.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {AdminPageComponent} from '../../../share/admin-page/admin-page.component';
+
+@Component({
+    templateUrl: './search-filter-group.component.html'
+})
+
+export class SearchFilterGroupComponent extends AdminPageComponent implements OnInit {
+
+    @Input() gridDataSource: GridDataSource;
+    @ViewChild('grid', {static: true}) grid: GridComponent;
+    @ViewChild('createString') createString: StringComponent;
+    @ViewChild('createErrString') createErrString: StringComponent;
+    @ViewChild('deleteFailedString') deleteFailedString: StringComponent;
+    @ViewChild('deleteSuccessString') deleteSuccessString: StringComponent;
+
+    constructor(
+        route: ActivatedRoute,
+        ngLocation: Location,
+        format: FormatService,
+        idl: IdlService,
+        org: OrgService,
+        auth: AuthService,
+        pcrud: PcrudService,
+        perm: PermService,
+        toast: ToastService,
+        private router: Router
+    ) {
+        super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast);
+    }
+
+    ngOnInit() {
+        this.gridDataSource = new GridDataSource();
+        this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const searchOps = {
+                offset: pager.offset,
+                limit: pager.limit,
+                order_by: {}
+            };
+            return this.pcrud.retrieveAll('asfg', searchOps);
+        };
+        this.grid.onRowActivate.subscribe(
+            (idlThing: IdlObject) => {
+                const idToEdit = idlThing.id();
+                this.navigateToEditPage(idToEdit);
+            }
+        );
+    }
+
+    createNew = () => {
+        this.editDialog.mode = 'create';
+        this.editDialog.recordId = null;
+        this.editDialog.record = null;
+        this.editDialog.hiddenFieldsList = ['id', 'create_date'];
+        this.editDialog.open({size: 'lg'}).subscribe(
+            ok => {
+                this.createString.current()
+                    .then(str => this.toast.success(str));
+                this.grid.reload();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.createErrString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+
+    editSelected = (sfGroups: IdlObject[]) => {
+        const idToEdit = sfGroups[0].id();
+        this.navigateToEditPage(idToEdit);
+    }
+
+    navigateToEditPage(id: any) {
+        this.router.navigate(['/staff/admin/local/actor/search_filter_group/' + id]);
+    }
+
+ }
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/search-filter/search-filter-group.module.ts
new file mode 100644 (file)
index 0000000..9afcf38
--- /dev/null
@@ -0,0 +1,24 @@
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {SearchFilterGroupComponent} from './search-filter-group.component';
+import {SearchFilterGroupEntriesComponent} from './search-filter-group-entries.component';
+import {SearchFilterGroupRoutingModule} from './search-filter-group-routing.module';
+import {QueryDialogComponent} from './query-dialog.component';
+
+@NgModule({
+  declarations: [
+    SearchFilterGroupComponent,
+    SearchFilterGroupEntriesComponent,
+    QueryDialogComponent
+  ],
+  imports: [
+    AdminCommonModule,
+    SearchFilterGroupRoutingModule,
+  ],
+  exports: [
+  ],
+  providers: [
+  ]
+})
+
+export class SearchFilterGroupModule {}