</div>
</ng-template>
+<ng-template #userSettingLabel let-args="args">
+ <div class="col-lg-3 field-label">
+ <label for="cust-{{args.settingName}}-input">
+ {{userSettingTypes[args.settingName].label()}}
+ </label>
+ </div>
+</ng-template>
+
<!-- text / number / email inputs -->
<ng-template #fieldInput let-args="args">
<div class="col-lg-3">
<eg-combobox [entries]="args.entries"
name="{{getClass(args.cls)}}-{{args.field}}-input"
domId="{{getClass(args.cls)}}-{{args.field}}-input"
- [startId]="getFieldValue(args.path, args.field)"
+ [selectedId]="getFieldValue(args.path, args.field)"
(onChange)="
fieldValueChange(args.path, args.field, $event ? $event.id : null);
afterFieldChange(args.path, args.field)"
<!-- like fieldRow below, but for user settings checkboxes -->
<ng-template #userSettingsCheckboxRow let-args="args">
<div class="row pt-1 pb-1 mt-1">
- <div class="col-lg-3 field-label">
- <label for="cust-{{args.settingName}}-input">
- {{userSettingTypes[args.settingName].label()}}
- </label>
- </div>
+ <ng-container *ngTemplateOutlet="userSettingLabel; context: {args: args}">
+ </ng-container>
<div class="col-lg-3">
<input
type="checkbox"
class="form-check-input ml-0"
name="cust-{{args.settingName}}-input"
- id="cust-{{args.setingName}-input"
+ id="cust-{{args.settingName}}-input"
[ngModel]="userSettings[args.settingName]"
(ngModelChange)="userSettingChange(args.settingName, $event)"
[disabled]="args.disabled"
</div>
</ng-template>
+<ng-template #userSettingsInput let-args="args">
+ <div class="col-lg-3">
+ <input
+ type="{{args.type || 'text'}}"
+ class="form-control"
+ name="cust-{{args.settingName}}-input"
+ id="cust-{{args.settingName}}-input"
+ [ngModel]="userSettings[args.settingName]"
+ (ngModelChange)="userSettingChange(args.settingName, $event)"
+ [disabled]="args.disabled"
+ />
+ </div>
+</ng-template>
+
+
+<ng-template #userSettingsInputRow let-args="args">
+ <div class="row pt-1 pb-1 mt-1">
+ <ng-container *ngTemplateOutlet="userSettingLabel; context: {args: args}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="userSettingInput; context: {args: args}">
+ </ng-container>
+ </div>
+</ng-template>
+
+
<!-- One row of label + field.
Used when a field requires no additional toggles. -->
<ng-template #fieldRow let-args="args">
<!-- The List O' Fields -->
-<div class="mt-3 striped-rows-even patron-edit-container" *ngIf="patron">
+<div class="mt-3 striped-rows-even patron-edit-container" *ngIf="patron && !loading">
<ng-container *ngTemplateOutlet="fieldRow; context: {args:
{template: fieldInput, field: 'barcode', cls: 'ac',
path: 'card', disabled: !patron.isnew()}}">
<div class="border rounded p-2" [ngClass]="{
'border-primary': nameTab == 'primary',
'border-success': nameTab == 'preferred'}">
- <b>{{nameTab}}</b>
<div [ngbNavOutlet]="nameNav"></div>
</div>
*ngTemplateOutlet="fieldInput; context: {args: {field: 'email', type: 'email'}}">
</ng-container>
<div class="col-lg-6">
- <button class="btn btn-outline-dark"
- (click)="sendTestMessage('au.email.test')" i18n>
- Send Test Email
- </button>
- <button class="btn btn-outline-dark ml-2"
- (click)="invalidateField('email')" i18n>Invalidate</button>
+ <ng-container *ngIf="patron.email()">
+ <button class="btn btn-outline-dark"
+ (click)="sendTestMessage('au.email.test')" i18n>
+ Send Test Email
+ </button>
+ <ng-container *ngIf="!patron.isnew()">
+ <button class="btn btn-outline-dark ml-2"
+ (click)="invalidateField('email')" i18n>Invalidate</button>
+ </ng-container>
+ </ng-container>
</div>
</div>
*ngTemplateOutlet="fieldInput; context: {args: {field: 'day_phone'}}">
</ng-container>
<div class="col-lg-6">
- <button class="btn btn-outline-dark"
- (click)="invalidateField('day_phone')" i18n>Invalidate</button>
+ <ng-container *ngIf="patron.day_phone() && !patron.isnew()">
+ <button class="btn btn-outline-dark"
+ (click)="invalidateField('day_phone')" i18n>Invalidate</button>
+ </ng-container>
</div>
</div>
</button>
</div>
</div>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldCombobox, field: 'net_access_level', entries: inetLevels}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldCheckbox, field: 'active'}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldCheckbox, field: 'barred'}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldCheckbox, field: 'master_account'}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldInput, field: 'claims_returned_count', type: 'number'}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="fieldRow; context:
+ {args: {template: fieldInput, field: 'claims_never_checked_out_count', type: 'number'}}">
+ </ng-container>
+
+ <div class="row pt-1 pb-1 mt-1" *ngIf="showField('au', 'alert_message')">
+ <ng-container
+ *ngTemplateOutlet="fieldLabel; context: {args: {field: 'alert_message'}}">
+ </ng-container>
+ <div class="col-lg-3">
+ <textarea
+ class="form-control"
+ name="au-alert_message-input"
+ id="au-alert_message-input"
+ [ngModel]="objectFromPath(null)['alert_message']()"
+ (ngModelChange)="fieldValueChange(null, 'alert_message', $event)"
+ (change)="afterFieldChange(null, 'alert_message')"
+ [required]="fieldRequired('au', 'alert_message')"
+ [pattern]="fieldPattern('au', 'alert_message')">
+ </textarea>
+ </div>
+ </div>
+
+ <div class="alert alert-success p-2 m-3" i18n>User Settings</div>
+
+ <ng-container *ngTemplateOutlet="userSettingsInputRow; context:
+ {args: {settingName: 'opac.default_phone'}}">
+ </ng-container>
+
+ <div class="row pt-1 pb-1 mt-1" *ngIf="showField('au', 'home_ou')">
+ <ng-container *ngTemplateOutlet="userSettingLabel;
+ context: {args: {settingName: 'opac.default_pickup_location'}}">
+ </ng-container>
+ <div class="col-lg-3">
+ <eg-org-select
+ domId="cust-opac.default_pickup_location-input"
+ fieldName="cust-opac.default_pickup_location-input"
+ [initialOrgId]="userSettings['opac.default_pickup_location']"
+ [disableOrgs]="cannotHaveVolsOrgs()"
+ (onChange)="userSettingChange(
+ 'opac.default_pickup_location', $event ? $event.id() : null)">
+ </eg-org-select>
+ </div>
+ </div>
+
+ <div class="row pt-1 pb-1 mt-1">
+ <ng-container *ngTemplateOutlet="userSettingLabel;
+ context: {args: {settingName: 'opac.hold_notify'}}">
+ </ng-container>
+ <div class="col-lg-3">
+ <div class="form-check form-check-inline mr-2">
+ <input class="form-check-input" type="radio" name="hold-notify-phone"
+ id="hold-notify-phone" [(ngModel)]="holdNotifyTypes.phone"/>
+ <label class="form-check-label" for="hold-notify-phone" i18n>Phone</label>
+ </div>
+ <div class="form-check form-check-inline mr-2">
+ <input class="form-check-input" type="radio" name="hold-notify-email"
+ id="hold-notify-email" [(ngModel)]="holdNotifyTypes.email"/>
+ <label class="form-check-label" for="hold-notify-email" i18n>Email</label>
+ </div>
+ <div class="form-check form-check-inline mr-2" *ngIf="orgSettings['sms.enable']">
+ <input class="form-check-input" type="radio" name="hold-notify-sms"
+ id="hold-notify-sms" [(ngModel)]="holdNotifyTypes.sms"/>
+ <label class="form-check-label" for="hold-notify-sms" i18n>SMS</label>
+ </div>
+ </div>
+ </div>
+
+ <ng-container *ngIf="orgSettings['sms.enable']">
+
+ <div class="row pt-1 pb-1 mt-1">
+ <ng-container *ngTemplateOutlet="userSettingLabel;
+ context: {args: {settingName: 'opac.default_sms_notify'}}">
+ </ng-container>
+ <ng-container *ngTemplateOutlet="userSettingsInput;
+ context: {args: {settingName: 'opac.default_sms_notify'}}">
+ </ng-container>
+ <div class="col-lg-6">
+ <ng-container *ngIf="userSettings['opac.default_sms_notify'] &&
+ userSettings['opac.default_sms_carrier']">
+ <button class="btn btn-outline-dark"
+ (click)="sendTestMessage('au.sms_text.test')" i18n>
+ Send Test Text
+ </button>
+ </ng-container>
+ </div>
+ </div>
+
+ <div class="row pt-1 pb-1 mt-1">
+ <ng-container *ngTemplateOutlet="userSettingLabel;
+ context: {args: {settingName: 'opac.default_sms_carrier'}}">
+ </ng-container>
+ <div class="col-lg-3">
+ <eg-combobox [entries]="smsCarriers"
+ name="cust-opac.default_sms_carrier-input"
+ domId="cust-opac.default_sms_carrier-input"
+ [selectedId]="userSettings['opac.default_sms_carrier']"
+ (onChange)="userSettingChange(
+ 'opac.default_sms_carrier', $event ? $event.id : null)">
+ </eg-combobox>
+ </div>
+ </div>
+ </ng-container>
+
+
+ <div class="alert alert-success p-2 m-3" i18n>Addresses</div>
+
</div>
import {EventService} from '@eg/core/event.service';
import {PermService} from '@eg/core/perm.service';
import {SecondaryGroupsDialogComponent} from './secondary-groups.component';
+import {ServerStoreService} from '@eg/core/server-store.service';
const COMMON_USER_SETTING_TYPES = [
'circ.holds_behind_desk',
'opac.default_sms_notify'
];
+// Duplicate these settings in resolver.service so they can be
+// fetched/cached with the original batch (fewer net calls).
+const ORG_SETTING_TYPES = [
+ 'sms.enable'
+]
+
const PERMS_NEEDED = [
'EDIT_SELF_IN_CLIENT',
'UPDATE_USER',
nameTab = 'primary';
loading = false;
+ smsCarriers: ComboboxEntry[];
identTypes: ComboboxEntry[];
+ inetLevels: ComboboxEntry[];
+ orgSettings: {[name: string]: any} = {};
userSettings: {[name: string]: any} = {};
userSettingTypes: {[name: string]: IdlObject} = {};
optInSettingTypes: {[name: string]: IdlObject} = {};
// patron we are editing.
hasPerm: {[name: string]: boolean} = {};
+ holdNotifyTypes: {email?: boolean, phone?: boolean, sms?: boolean} = {};
+
constructor(
private org: OrgService,
private net: NetService,
private toast: ToastService,
private perms: PermService,
private evt: EventService,
+ private serverStore: ServerStoreService,
private patronService: PatronService,
public context: PatronContextService
) {}
.then(_ => this.getSecondaryGroups())
.then(_ => this.applyPerms())
.then(_ => this.setIdentTypes())
+ .then(_ => this.setInetLevels())
.then(_ => this.setOptInSettings())
+ .then(_ => this.setOrgSettings())
+ .then(_ => this.setSmsCarriers())
.finally(() => this.loading = false);
}
- getSecondaryGroups(): Promise<any> {
+ setOrgSettings(): Promise<any> {
+ return this.serverStore.getItemBatch(ORG_SETTING_TYPES)
+ .then(settings => this.orgSettings = settings);
+ }
- return this.net.request(
- 'open-ils.actor',
- 'open-ils.actor.user.get_groups',
- this.auth.token(), this.patronId
+ setSmsCarriers(): Promise<any> {
+ if (!this.orgSettings['sms.enable']) {
+ return Promise.resolve();
+ }
- ).pipe(concatMap(maps => this.pcrud.search('pgt',
- {id: maps.map(m => m.grp())}, {}, {atomic: true})
+ return this.patronService.getSmsCarriers().then(carriers => {
+ this.smsCarriers = carriers.map(carrier => {
+ return {
+ id: carrier.id(),
+ label: carrier.name()
+ };
+ });
+ });
+ }
- )).pipe(tap(grps => this.secondaryGroups = grps)).toPromise();
+ getSecondaryGroups(): Promise<any> {
+ return this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.user.get_groups',
+ this.auth.token(), this.patronId
+
+ ).pipe(concatMap(maps => this.pcrud.search('pgt',
+ {id: maps.map(m => m.grp())}, {}, {atomic: true})
+
+ )).pipe(tap(grps => this.secondaryGroups = grps)).toPromise();
}
setIdentTypes(): Promise<any> {
});
}
+ setInetLevels(): Promise<any> {
+ return this.patronService.getInetLevels()
+ .then(levels => {
+ this.inetLevels = levels.map(t => ({id: t.id(), label: t.name()}));
+ });
+ }
+
applyPerms(): Promise<any> {
const promise = this.permOrgs ?
}
});
+ const holdNotify = this.userSettings['opac.hold_notify'];
+ if (holdNotify) {
+ this.holdNotifyTypes.email = holdNotify.match(/email/) !== null;
+ this.holdNotifyTypes.phone = holdNotify.match(/phone/) !== null;
+ this.holdNotifyTypes.sms = holdNotify.match(/sms/) !== null;
+ }
+
+ if (this.userSettings['opac.default_sms_carrier']) {
+ this.userSettings['opac.default_sms_carrier'] =
+ Number(this.userSettings['opac.default_sms_carrier']);
+ }
+
+ if (this.userSettings['opac.default_pickup_location']) {
+ this.userSettings['opac.default_pickup_location'] =
+ Number(this.userSettings['opac.default_pickup_location']);
+ }
+
this.expireDate = new Date(this.patron.expire_date());
}
// so avoid any heavy lifting here. See afterFieldChange();
fieldValueChange(path: string, field: string, value: any) {
if (typeof value === 'boolean') { value = value ? 't' : 'f'; }
+
+ // This can be called in cases where components fire up, even
+ // though the actual value on the patron has not changed.
+ // Exit early in that case so we don't mark the form as dirty.
+ const oldValue = this.getFieldValue(path, field);
+ if (oldValue === value) { return; }
+
this.changeHandlerNeeded = true;
this.objectFromPath(path)[field](value);
}
// TODO: set dirty
-
const obj = path ? this.patron[path]() : this.patron;
const value = obj[field]();
- console.debug(`Modifying field path=${path} field=${field} value=${value}`);
+ console.debug(
+ `Modifying field path=${path || ''} field=${field} value=${value}`);
switch (field) {
// TODO: do many more
.map(org => org.id());
}
+ cannotHaveVolsOrgs(): number[] {
+ return this.org.list()
+ .filter(org => org.ou_type().can_have_vols() === 'f')
+ .map(org => org.id());
+ }
+
setExpireDate() {
const profile = this.profileSelect.profiles[this.patron.profile()];
if (!profile) { return; }