routerLink="/staff/admin/local/asset/copy_location_order"></eg-link-table-link>
<eg-link-table-link i18n-label label="Shelving Locations Editor"
routerLink="/staff/admin/local/asset/copy_location"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Staff Portal Page"
+ 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"
import {AdminLocalSplashComponent} from './admin-local-splash.component';
import {AddressAlertComponent} from './address-alert.component';
import {AdminCarouselComponent} from './admin-carousel.component';
+import {ClonePortalEntriesDialogComponent} from './staff_portal_page/clone-portal-entries-dialog.component';
+import {AdminStaffPortalPageComponent} from './staff_portal_page/staff-portal-page.component';
import {StandingPenaltyComponent} from './standing-penalty.component';
@NgModule({
AdminLocalSplashComponent,
AddressAlertComponent,
AdminCarouselComponent,
- StandingPenaltyComponent
+ StandingPenaltyComponent,
+ ClonePortalEntriesDialogComponent,
+ AdminStaffPortalPageComponent
],
imports: [
AdminCommonModule,
import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
import {AddressAlertComponent} from './address-alert.component';
import {AdminCarouselComponent} from './admin-carousel.component';
+import {AdminStaffPortalPageComponent} from './staff_portal_page/staff-portal-page.component';
import {StandingPenaltyComponent} from './standing-penalty.component';
import {CourseTermMapComponent} from './course-reserves/course-term-map.component';
path: 'config/standing_penalty',
component: StandingPenaltyComponent
}, {
+ path: 'config/ui_staff_portal_page_entry',
+ component: AdminStaffPortalPageComponent
+}, {
path: 'action/survey',
loadChildren: () =>
import('./survey/survey.module').then(m => m.SurveyModule)
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h3 class="modal-title" i18n>Clone a Library's Portal Page Entries</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form #cloneForm="ngForm" role="form" class="form-validated common-form striped-odd">
+ <div class="form-group row mt-2">
+ <label for="source_library" class="col-sm-6 col-form-label" i18n>Source Library</label>
+ <div class="col-sm-6">
+ <eg-org-select
+ placeholder="Source Library..."
+ domId="source_library"
+ i18n-placeholder
+ [limitPerms]="['STAFF_LOGIN']"
+ (onChange)="result.source_library = $event.id(); cloneForm.form.markAsDirty()">
+ </eg-org-select>
+ </div>
+ </div>
+ <div class="form-group row mt-2">
+ <label for="target_library" class="col-sm-6 col-form-label" i18n>Target Library</label>
+ <div class="col-sm-6">
+ <eg-org-select
+ placeholder="Target Library..."
+ domId="target_library"
+ i18n-placeholder
+ [limitPerms]="['ADMIN_STAFF_PORTAL_PAGE']"
+ (onChange)="result.target_library = $event.id(); cloneForm.form.markAsDirty();">
+ </eg-org-select>
+ </div>
+ </div>
+ <div class="form-group row mt-2">
+ <label for="overwrite_target" class="col-sm-6 col-form-label" i18n>Clear Entries at Target Library?</label>
+ <div class="col-sm-6">
+ <input type="checkbox" id="overwrite_target" name="overwrite_target" [(ngModel)]="result.overwrite_target" />
+ </div>
+ </div>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-info"
+ [disabled]="!result.source_library || !result.target_library || (result.target_library === result.source_library)"
+ (click)="close(result)" i18n>Clone</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Close</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm, NG_VALIDATORS} from '@angular/forms';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'eg-clone-portal-entries-dialog',
+ templateUrl: './clone-portal-entries-dialog.component.html'
+})
+
+export class ClonePortalEntriesDialogComponent
+ extends DialogComponent implements OnInit {
+
+ result = { };
+
+ constructor(
+ private modal: NgbModal
+ ) {
+ super(modal);
+ }
+
+ ngOnInit() {
+ this.onOpen$.subscribe(() => this._initRecord());
+ }
+
+ private _initRecord() {
+ this.result = {
+ source_library: null,
+ target_library: null,
+ overwrite_target: false
+ };
+ }
+
+}
--- /dev/null
+<ng-template #successStrTmpl i18n>{{idlClassDef.label}} Update Succeeded</ng-template>
+<eg-string #successString [template]="successStrTmpl"></eg-string>
+
+<ng-template #updateFailedStrTmpl i18n>Update of {{idlClassDef.label}} failed</ng-template>
+<eg-string #updateFailedString [template]="updateFailedStrTmpl"></eg-string>
+
+<ng-template #deleteFailedStrTmpl i18n>Delete of {{idlClassDef.label}} failed or was not allowed</ng-template>
+<eg-string #deleteFailedString [template]="deleteFailedStrTmpl"></eg-string>
+
+<ng-template #deleteSuccessStrTmpl i18n>{{idlClassDef.label}} Successfully Deleted</ng-template>
+<eg-string #deleteSuccessString [template]="deleteSuccessStrTmpl"></eg-string>
+
+<ng-template #createStrTmpl i18n>{{idlClassDef.label}} Successfully Created</ng-template>
+<eg-string #createString [template]="createStrTmpl"></eg-string>
+
+<ng-template #createErrStrTmpl i18n>Failed to create new {{idlClassDef.label}}</ng-template>
+<eg-string #createErrString [template]="createErrStrTmpl"></eg-string>
+
+<eg-string #cloneSuccessString i18n-text text="Portal Page Entries Cloning Succeeded"></eg-string>
+<eg-string #cloneFailedString i18n-text text="Portal Page Entries Cloning Failed"></eg-string>
+
+<eg-confirm-dialog #delConfirm
+ i18n-dialogTitle i18n-dialogBody
+ dialogTitle="Delete?"
+ dialogBody="Delete staff portal page entry or entries?">
+</eg-confirm-dialog>
+
+<eg-clone-portal-entries-dialog #cloneDialog></eg-clone-portal-entries-dialog>
+
+<ng-container *ngIf="orgField || gridFilters">
+ <div class="row">
+ <div class="col-lg-6">
+ <ng-container *ngIf="orgField">
+ <eg-org-family-select
+ [limitPerms]="viewPerms"
+ [selectedOrgId]="contextOrg.id()"
+ [ancestorSelectorChecked]="true"
+ [(ngModel)]="searchOrgs"
+ (ngModelChange)="grid.reload()">
+ </eg-org-family-select>
+ </ng-container>
+ </div>
+ <div class="col-lg-6 d-flex">
+ <div class="flex-1"></div><!-- push right -->
+ <ng-container *ngIf="gridFilters">
+ <span i18n>Filters Applied: {{gridFilters | json}}</span>
+ <a class="pl-2 font-italic"
+ [attr.href]="clearGridFiltersUrl()" i18n>Clear Filters</a>
+ </ng-container>
+ </div>
+ </div>
+ <hr/>
+</ng-container>
+
+<!-- idlObject and fieldName applied programmatically -->
+<eg-translate #translator></eg-translate>
+
+<ng-container *ngIf="helpTemplate">
+ <ng-container *ngTemplateOutlet="helpTemplate"></ng-container>
+</ng-container>
+
+<ng-template #configFieldLink let-row="row" let-col="col">
+ <a i18n-title title="Link To {{col.label}}"
+ [attr.href]="configFieldLinkUrl(row, col)">{{configLinkLabel(row, col)}}</a>
+</ng-template>
+
+<eg-grid #grid idlClass="{{idlClass}}" [dataSource]="dataSource" hideFields="{{hideGridFields}}"
+ [sortable]="true" persistKey="{{persistKey}}" autoGeneratedColumnOrder="{{fieldOrder}}"
+ [filterable]="true"
+ (onRowActivate)="showEditDialog($event)"
+ [filterable]="true" [stickyHeader]="true">
+ <eg-grid-toolbar-button [disabled]="!canCreate"
+ label="New {{idlClassDef.label}}" i18n-label (onClick)="createNew()">
+ </eg-grid-toolbar-button>
+ <eg-grid-toolbar-button [disabled]="!canCreate"
+ label="Clone a Library's Portal Page Entries" i18n-label (onClick)="cloneEntries()">
+ </eg-grid-toolbar-button>
+ <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>
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
+ </eg-grid-toolbar-action>
+ <ng-container *ngFor="let cf of configFields">
+ <eg-grid-column name="{{cf.name}}" [cellTemplate]="configFieldLink">
+ </eg-grid-column>
+ </ng-container>
+</eg-grid>
+
+<ng-template #textTemplate let-field="field" let-record="record">
+ <textarea rows="3"
+ class="form-control"
+ id="{{idPrefix}}-{{field.name}}" name="{{field.name}}"
+ type="text" pattern="[\s\S]*\S[\s\S]*"
+ placeholder="{{field.label}}..." i18n-placeholder
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"></textarea>
+</ng-template>
+
+<eg-fm-record-editor #editDialog idlClass="{{idlClass}}"
+ [fieldOptions]="fieldOptions"
+ [fieldOrder]="fieldOrder"
+ [defaultNewRecord]="defaultNewRecord"
+ [fieldOptions]="{entry_text:{customTemplate:{template:textTemplate}}}"
+ [preloadLinkedValues]="true"
+ [readonlyFields]="readonlyFields">
+</eg-fm-record-editor>
+
+
--- /dev/null
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import {Location} from '@angular/common';
+import {FormatService} from '@eg/core/format.service';
+import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
+import {ActivatedRoute} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+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 {NetService} from '@eg/core/net.service';
+import {GridCellTextGenerator} from '@eg/share/grid/grid';
+import {StringComponent} from '@eg/share/string/string.component';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {ClonePortalEntriesDialogComponent} from './clone-portal-entries-dialog.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {merge, Observable, empty} from 'rxjs';
+
+@Component({
+ templateUrl: './staff-portal-page.component.html'
+})
+
+export class AdminStaffPortalPageComponent extends AdminPageComponent implements OnInit {
+
+ idlClass = 'cusppe';
+ fieldOrder = 'label,entry_type,target_url,entry_text,image_url,page_col,col_pos,owner,id';
+ classLabel: string;
+
+ refreshSelected: (idlThings: IdlObject[]) => void;
+ createNew: () => void;
+ cellTextGenerator: GridCellTextGenerator;
+
+ @ViewChild('refreshString', { static: true }) refreshString: StringComponent;
+ @ViewChild('refreshErrString', { static: true }) refreshErrString: StringComponent;
+ @ViewChild('cloneSuccessString', { static: true }) cloneSuccessString: StringComponent;
+ @ViewChild('cloneFailedString', { static: true }) cloneFailedString: StringComponent;
+ @ViewChild('cloneDialog', { static: true}) cloneDialog: ClonePortalEntriesDialogComponent;
+ @ViewChild('delConfirm', { static: true }) delConfirm: ConfirmDialogComponent;
+
+ constructor(
+ route: ActivatedRoute,
+ ngLocation: Location,
+ format: FormatService,
+ idl: IdlService,
+ org: OrgService,
+ auth: AuthService,
+ pcrud: PcrudService,
+ perm: PermService,
+ toast: ToastService,
+ private net: NetService
+ ) {
+ super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast);
+ }
+
+ ngOnInit() {
+ super.ngOnInit();
+
+ this.defaultNewRecord = this.idl.create(this.idlClass);
+ this.defaultNewRecord.owner(this.auth.user().ws_ou());
+ }
+
+ cloneEntries() {
+ this.cloneDialog.open().subscribe(
+ result => {
+ this._handleClone(result.source_library, result.target_library, result.overwrite_target);
+ }
+ );
+ }
+
+ deleteSelected(idlThings: IdlObject[]) {
+ this.delConfirm.open().subscribe(confirmed => {
+ if (!confirmed) { return; }
+ super.deleteSelected(idlThings);
+ });
+ }
+
+ _handleClone(src: number, tgt: number, overwrite: Boolean) {
+ const updates: IdlObject[] = [];
+
+ const delObs = (overwrite) ?
+ this.pcrud.search('cusppe', { owner: tgt }, {}, {}) :
+ empty();
+ const newObs = this.pcrud.search('cusppe', { owner: src }, {}, {});
+ merge(delObs, newObs).subscribe(
+ entry => {
+ if (entry.owner() === tgt) {
+ entry.isdeleted(true);
+ } else {
+ entry.owner(tgt);
+ entry.id(null);
+ entry.isnew(true);
+ }
+ updates.push(entry);
+ },
+ err => {},
+ ).add(() => {
+ this.pcrud.autoApply(updates).subscribe(
+ val => {},
+ err => {
+ this.cloneFailedString.current()
+ .then(str => this.toast.danger(str));
+ },
+ () => {
+ this.cloneSuccessString.current()
+ .then(str => this.toast.success(str));
+ this.searchOrgs = {primaryOrgId: tgt}; // change the org filter to the
+ // the one we just cloned into
+ this.grid.reload();
+ }
+ );
+ });
+ }
+}