/**
* Create an IDL-friendly display version of a human-readable date
*/
- idlFormatDate(original: string): string {
- return Moment(original, 'MMM DD YYYY', 'America/Los_Angeles').format('YYYY-MM-DD');
- }
+ idlFormatDate(date: string): string { return this.momentizeDateString(date).format('YYYY-MM-DD'); }
+
/**
* Create an IDL-friendly display version of a human-readable datetime
*/
- idlFormatDatetime(original: string): string {
- return Moment(original, 'MMM DD YYYY HH:mm', 'America/Los_Angeles').toISOString()
+ idlFormatDatetime(datetime: string): string { return this.momentizeDateTimeString(datetime).toISOString(); }
+
+ momentizeDateString(date: string): Moment {
+ const parseableFormat = this.makeFormatParseable(this.dateFormat);
+ if (parseableFormat.length) {return Moment(date, parseableFormat, 'Asia/Tokyo')};
+ return Moment(date, 'Asia/Tokyo');
+ }
+
+ momentizeDateTimeString(datetime: string): Moment {
+ const parseableFormat = this.makeFormatParseable(this.dateTimeFormat);
+ if (parseableFormat.length) {return Moment(datetime, parseableFormat, 'Asia/Tokyo')};
+ return Moment(datetime, 'Asia/Tokyo');
+ }
+
+ /**
+ * Takes a dateFormate or dateTimeFormat string (which uses Angular syntax) and transforms
+ * it into a format string that MomentJs can use to parse input human-readable strings
+ * (https://momentjs.com/docs/#/parsing/string-format/)
+ *
+ * Returns a blank string if it can't do this transformation.
+ */
+ private makeFormatParseable(original: string): string {
+ const specialFormats = ['short', 'medium', 'long', 'full',
+ 'shortDate', 'mediumDate', 'longDate', 'fullDate',
+ 'shortTime', 'mediumTime', 'longTime', 'fullTime'];
+ if (!original || specialFormats.includes(original)) { return ''; };
+ return original
+ .replace(/a+/, 'a') // MomentJs can handle all sorts of meridian strings
+ .replace('d', 'D') // MomentJs capitalizes day of month
+ .replace('EEEEEE', '') // MomentJs does not handle short day of week
+ .replace('EEEEE', '') // MomentJs does not handle narrow day of week
+ .replace('EEEE', 'dddd') // MomentJs has different syntax for long day of week
+ .replace(/E{1,3}/, 'ddd') // MomentJs has different syntax for abbreviated day of week
+ .replace('L', 'M') // MomentJs does not differentiate between month and month standalone
+ .replace('W', '') // MomentJs uses W for something else
+ .replace('y', 'Y'); // MomentJs capitalizes year
}
}
-<!-- TODO:
-* Have this accept ISO input (i.e. from the database)
-* Have this accept @Input params of timezone (default to OU), disable
-* turn this into a popover that pops over the input: https://ng-bootstrap.github.io/#/components/popover/examples
-* have this input and output the five and noble stuff in the correct format (currently you can't just type ssomething into the box and have it show up on the calendar, but it does show up in the model - cool)
-
--->
<div class="input-group">
<input type="datetime"
[attr.id]="domId.length ? domId : null"
name="{{fieldName}}"
class="form-control"
[placeholder]="initialIso"
- [(ngModel)]="dateTime"
+ [(ngModel)]="stringVersion"
(blur)="blurred($event)"
#dtPicker="ngbPopover"
[ngbPopover]="dt"
<ng-template #dt>
<ngb-datepicker
[(ngModel)]="dateModel"
- (ngModelChange)="modelChanged($event)"
+ (ngModelChange)="modelChanged()"
[footerTemplate]="time">
</ngb-datepicker>
</ng-template>
@Output() onChangeAsIso = new EventEmitter();
- dateTime: any; // Used internally on internal input
+ stringVersion: any; // Used internally on internal input
timeModel: NgbTimeStruct;
dateModel: NgbDateStruct;
}
modelChanged(event) {
- let newDate: any;
+ let newDate: Moment;
- if (event) {
- newDate = Moment(new Date(this.dateTime));
+ if (event) { // if the change came from the input field
+ newDate = this.format.momentizeDateTimeString(this.stringVersion);
} else {
- newDate = new Date(this.dateModel.year, this.dateModel.month, this.dateModel.day,
- this.timeModel.hour, this.timeModel.minute, this.timeModel.second);
+ newDate = Moment(new Date(this.dateModel.year, (this.dateModel.month - 1), this.dateModel.day,
+ this.timeModel.hour, this.timeModel.minute, this.timeModel.second));
}
if (newDate && !isNaN(newDate)) {
console.log('newDate');
// Set component view value
- this.dateTime = this.format.transform({value: newDate, datatype: 'string', datePlusTime: true});
+ this.stringVersion = this.format.transform({value: newDate, datatype: 'timestamp', datePlusTime: true});
// Update form passed in view value
- this.onChangeAsIso.emit(Moment(newDate).toISOString);
+ this.onChangeAsIso.emit(newDate.toISOString());
}
}
<ng-container *ngSwitchCase="'timestamp-timepicker'">
<eg-datetime-select
+ [showTZ]="timezone"
+ [timezone]="timezone"
domId="{{idPrefix}}-{{field.name}}"
(onChangeAsIso)="record[field.name]($event)"
initialIso="{{record[field.name]()}}">
mode: 'create' | 'update' | 'view' = 'create';
recId: any;
+ // Show datetime fields in this particular timezone
+ timezone: string;
+
// IDL record we are editing
// TODO: allow this to be update in real time by the caller?
record: IdlObject;
@Input() requiredFieldsList: string[] = [];
@Input() requiredFields: string; // comma-separated string version
- // list of timezone fields that should display with a timepicker
+ // list of timestamp fields that should display with a timepicker
@Input() datetimeFieldsList: string[] = [];
@Input() datetimeFields: string; // comma-separated string version
}
}
-
<eg-grid-toolbar-action label="View Reservations for This Resource" i18n-label [action]="viewByResource" [disableOnRows]="notOneResourceSelected"></eg-grid-toolbar-action>
<eg-grid-column name="id" [hidden]="true" [index]="true" i18n-label label="ID" path="id"></eg-grid-column>
- <eg-grid-column label="Patron username" i18n-label path="usr.usrname"></eg-grid-column>
- <eg-grid-column label="Patron barcode" i18n-label path="usr.card.barcode"></eg-grid-column>
+ <eg-grid-column label="Patron username" i18n-label path="usr.usrname" [sortable]="false"></eg-grid-column>
+ <eg-grid-column label="Patron barcode" i18n-label path="usr.card.barcode" [sortable]="false"></eg-grid-column>
<eg-grid-column label="Patron first name" i18n-label path="usr.first_given_name"></eg-grid-column>
<eg-grid-column label="Patron middle name" i18n-label [hidden]="true" path="usr.second_given_name"></eg-grid-column>
<eg-grid-column label="Patron family name" i18n-label path="usr.family_name"></eg-grid-column>
<eg-grid-column name="pickup_time" label="Pickup Time" [datePlusTime]="true" i18n-label path="pickup_time" datatype="timestamp"></eg-grid-column>
<eg-grid-column label="Email notify" i18n-label [hidden]="true" path="email_notify" datatype="bool"></eg-grid-column>
<eg-grid-column i18n-label [hidden]="true" path="unrecovered" datatype="bool"></eg-grid-column>
- <eg-grid-column label="Billing total" i18n-label path="billing_total" datatype="money"></eg-grid-column>
- <eg-grid-column label="Payment total" i18n-label path="payment_total" datatype="money"></eg-grid-column>
+ <eg-grid-column label="Billing total" i18n-label path="billing_total" datatype="money" [sortable]="false"></eg-grid-column>
+ <eg-grid-column label="Payment total" i18n-label path="payment_total" datatype="money" [sortable]="false"></eg-grid-column>
<eg-grid-column label="Booking interval" i18n-label [hidden]="true" path="booking_interval" [hidden]="true"></eg-grid-column>
<eg-grid-column label="Fine interval" i18n-label [hidden]="true" path="fine_interval" [hidden]="true"></eg-grid-column>
<eg-grid-column label="Fine amount" i18n-label [hidden]="true" path="fine_amount" datatype="money"></eg-grid-column>
<eg-grid-column label="Maximum fine" i18n-label [hidden]="true" path="max_fine" datatype="money"></eg-grid-column>
<eg-grid-column i18n-label label="Resource Barcode" path="current_resource.barcode"></eg-grid-column>
<eg-grid-column i18n-label label="Note" path="note"></eg-grid-column>
- <eg-grid-column i18n-label label="Resource Type" path="target_resource_type.name"></eg-grid-column>
+ <eg-grid-column i18n-label label="Resource Type" path="target_resource_type.name" [sortable]="false"></eg-grid-column>
<eg-grid-column label="Request library" i18n-label [hidden]="true" path="request_lib.name"></eg-grid-column>
<eg-grid-column label="Pickup library" i18n-label [hidden]="true" path="pickup_lib.name"></eg-grid-column>
+ <eg-grid-column label="Pickup library timezone" i18n-label path="timezone" [sortable]="false"></eg-grid-column>
</eg-grid>
import {Router, ActivatedRoute, ParamMap} from '@angular/router';
import {NetService} from '@eg/core/net.service';
import {NoTimezoneSetComponent} from './no-timezone-set.component';
+import {OrgService} from '@eg/core/org.service';
import {PatronService} from '@eg/staff/share/patron.service';
import * as Moment from 'moment-timezone';
returnNotAppropriate: (rows: IdlObject[]) => boolean;
constructor(
+ private auth: AuthService,
+ private format: FormatService,
+ private pcrud: PcrudService,
private route: ActivatedRoute,
private router: Router,
private toast: ToastService,
- private pcrud: PcrudService,
- private auth: AuthService,
- private format: FormatService,
private net: NetService,
+ private org: OrgService,
private patronService: PatronService
) {
flesh_fields: {'bresv' : [
'usr', 'capture_staff', 'target_resource', 'target_resource_type', 'current_resource', 'request_lib', 'pickup_lib'
], 'au': ['card'] }
- });
+ }).pipe(tap((row) => {
+ this.org.settings('lib.timezone', row['pickup_lib']()).then((tz) => {row['timezone'] = tz['lib.timezone']});
+ }));
};
this.editDialog.mode = 'update';
showEditDialog(idlThing: IdlObject) {
this.editDialog.recId = idlThing.id();
+ this.editDialog.timezone = idlThing['timezone'];
return this.editDialog.open({size: 'lg'}).then(
ok => {
this.toast.success('Reservation successfully updated'); // TODO: needs i18n, pluralization