mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
97425: Implement feedback
This commit is contained in:
@@ -17,50 +17,89 @@
|
||||
[className]="(formMessage.invalid) && (formMessage.dirty || formMessage.touched) ? 'form-control is-invalid' :'form-control'"
|
||||
formControlName="formMessage">
|
||||
</textarea>
|
||||
<div *ngIf="formMessage.invalid && (formMessage.dirty || formMessage.touched)"
|
||||
class="invalid-feedback show-feedback">
|
||||
<div *ngIf="formMessage.invalid && (formMessage.dirty || formMessage.touched)"
|
||||
class="invalid-feedback show-feedback">
|
||||
<span *ngIf="formMessage.errors">
|
||||
{{ 'system-wide-alert.form.error.message' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label>{{ 'system-wide-alert.form.label.countdownTo' | translate }}</label>
|
||||
<div class="col mb-2 d-flex align-items-end">
|
||||
<ui-switch size="small"
|
||||
[checked]="counterEnabled$ |async"
|
||||
(change)="setCounterEnabled($event)"></ui-switch>
|
||||
<span class="ml-2">{{ 'system-wide-alert.form.label.countdownTo.enable' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="yyyy-mm-dd"
|
||||
name="dp"
|
||||
[(ngModel)]="date"
|
||||
ngbDatepicker
|
||||
#d="ngbDatepicker"
|
||||
/>
|
||||
<button class="btn btn-outline-secondary fas fa-calendar" (click)="d.toggle()" type="button"></button>
|
||||
<div *ngIf="counterEnabled$ |async">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="yyyy-mm-dd"
|
||||
name="dp"
|
||||
[(ngModel)]="date"
|
||||
ngbDatepicker
|
||||
#d="ngbDatepicker"
|
||||
(ngModelChange)="updatePreviewTime()"
|
||||
/>
|
||||
<button class="btn btn-outline-secondary fas fa-calendar" (click)="d.toggle()"
|
||||
type="button"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 d-md-none">
|
||||
<div class="input-group">
|
||||
<ngb-timepicker [(ngModel)]="time"></ngb-timepicker>
|
||||
<div class="col-12 d-md-none">
|
||||
<div class="input-group">
|
||||
<ngb-timepicker [(ngModel)]="time" (ngModelChange)="updatePreviewTime()"></ngb-timepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-block col-md-6 timepicker-margin">
|
||||
<div class="input-group">
|
||||
<ngb-timepicker [(ngModel)]="time"></ngb-timepicker>
|
||||
<div class="d-none d-md-block col-md-6 timepicker-margin">
|
||||
<div class="input-group">
|
||||
<ngb-timepicker [(ngModel)]="time" (ngModelChange)="updatePreviewTime()"></ngb-timepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<span class="text-muted"> {{'system-wide-alert.form.label.countdownTo.hint' | translate}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label>{{ 'system-wide-alert.form.label.preview' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-0 alert alert-warning">
|
||||
<span class="font-weight-bold">
|
||||
<span *ngIf="previewDays > 0 || previewHours > 0 || previewMinutes > 0 ">
|
||||
{{'system-wide-alert-banner.countdown.prefix' | translate }}
|
||||
</span>
|
||||
<span *ngIf="previewDays > 0">
|
||||
{{'system-wide-alert-banner.countdown.days' | translate: {
|
||||
days: previewDays
|
||||
} }}
|
||||
</span>
|
||||
<span *ngIf="previewDays > 0 || previewHours > 0 ">
|
||||
{{'system-wide-alert-banner.countdown.hours' | translate: {
|
||||
hours: previewHours
|
||||
} }}
|
||||
</span>
|
||||
<span *ngIf="previewDays > 0 || previewHours > 0 || previewMinutes > 0 ">
|
||||
{{'system-wide-alert-banner.countdown.minutes' | translate: {
|
||||
minutes: previewMinutes
|
||||
} }}
|
||||
</span>
|
||||
</span>
|
||||
<span>{{formMessage.value}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row float-right space-children-mr mt-2">
|
||||
<button (click)="back()"
|
||||
class="btn btn-outline-secondary"><i
|
||||
|
@@ -1,3 +1,4 @@
|
||||
.timepicker-margin {
|
||||
// Negative margin to offset the time picker arrowsand ensure the date and time are correctly aligned
|
||||
margin-top: -38px;
|
||||
}
|
||||
|
@@ -111,6 +111,44 @@ describe('SystemWideAlertFormComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('setCounterEnabled', () => {
|
||||
it('should set the preview time on enable and update the behaviour subject', () => {
|
||||
spyOn(comp, 'updatePreviewTime');
|
||||
comp.setCounterEnabled(true);
|
||||
|
||||
expect(comp.updatePreviewTime).toHaveBeenCalled();
|
||||
expect(comp.counterEnabled$.value).toBeTrue();
|
||||
});
|
||||
it('should reset the preview time on disable and update the behaviour subject', () => {
|
||||
spyOn(comp, 'updatePreviewTime');
|
||||
comp.setCounterEnabled(false);
|
||||
|
||||
expect(comp.updatePreviewTime).not.toHaveBeenCalled();
|
||||
expect(comp.previewDays).toEqual(0);
|
||||
expect(comp.previewHours).toEqual(0);
|
||||
expect(comp.previewMinutes).toEqual(0);
|
||||
expect(comp.counterEnabled$.value).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePreviewTime', () => {
|
||||
it('should calculate the difference between the current date and the date configured in the form', () => {
|
||||
const countDownDate = new Date();
|
||||
countDownDate.setDate(countDownDate.getDate() + 1);
|
||||
countDownDate.setHours(countDownDate.getHours() + 1);
|
||||
countDownDate.setMinutes(countDownDate.getMinutes() + 1);
|
||||
|
||||
comp.time = {hour: countDownDate.getHours(), minute: countDownDate.getMinutes()};
|
||||
comp.date = {year: countDownDate.getFullYear(), month: countDownDate.getMonth() + 1, day: countDownDate.getDate()};
|
||||
|
||||
comp.updatePreviewTime();
|
||||
|
||||
expect(comp.previewDays).toEqual(1);
|
||||
expect(comp.previewHours).toEqual(1);
|
||||
expect(comp.previewDays).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should update the exising alert with the form values and show a success notification on success', () => {
|
||||
spyOn(comp, 'back');
|
||||
@@ -135,6 +173,29 @@ describe('SystemWideAlertFormComponent', () => {
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts');
|
||||
expect(comp.back).toHaveBeenCalled();
|
||||
});
|
||||
it('should update the exising alert with the form values but add an empty countdown date when disabled and show a success notification on success', () => {
|
||||
spyOn(comp, 'back');
|
||||
comp.currentAlert = systemWideAlert;
|
||||
|
||||
comp.formMessage.patchValue('New message');
|
||||
comp.formActive.patchValue(true);
|
||||
comp.time = {hour: 4, minute: 26};
|
||||
comp.date = {year: 2023, month: 1, day: 25};
|
||||
comp.counterEnabled$.next(false);
|
||||
|
||||
const expectedAlert = new SystemWideAlert();
|
||||
expectedAlert.alertId = systemWideAlert.alertId;
|
||||
expectedAlert.message = 'New message';
|
||||
expectedAlert.active = true;
|
||||
expectedAlert.countdownTo = null;
|
||||
|
||||
comp.save();
|
||||
|
||||
expect(systemWideAlertDataService.put).toHaveBeenCalledWith(expectedAlert);
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts');
|
||||
expect(comp.back).toHaveBeenCalled();
|
||||
});
|
||||
it('should update the exising alert with the form values and show a error notification on error', () => {
|
||||
spyOn(comp, 'back');
|
||||
(systemWideAlertDataService.put as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
|
||||
|
@@ -5,7 +5,7 @@ import { filter, map } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { SystemWideAlert } from '../system-wide-alert.model';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
|
||||
@@ -51,6 +51,26 @@ export class SystemWideAlertFormComponent implements OnInit {
|
||||
*/
|
||||
time;
|
||||
|
||||
/**
|
||||
* Behaviour subject to track whether the counter is enabled
|
||||
*/
|
||||
counterEnabled$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
/**
|
||||
* The amount of minutes to be used in the banner preview
|
||||
*/
|
||||
previewMinutes: number;
|
||||
|
||||
/**
|
||||
* The amount of hours to be used in the banner preview
|
||||
*/
|
||||
previewHours: number;
|
||||
|
||||
/**
|
||||
* The amount of days to be used in the banner preview
|
||||
*/
|
||||
previewDays: number;
|
||||
|
||||
|
||||
constructor(
|
||||
protected systemWideAlertDataService: SystemWideAlertDataService,
|
||||
@@ -99,17 +119,55 @@ export class SystemWideAlertFormComponent implements OnInit {
|
||||
this.formActive.patchValue(alert.active);
|
||||
const countDownTo = zonedTimeToUtc(alert.countdownTo, 'UTC');
|
||||
if (countDownTo.getTime() - new Date().getTime() > 0) {
|
||||
this.counterEnabled$.next(true);
|
||||
this.setDateTime(countDownTo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the countdown timer is enabled or disabled. This will also update the counter in the preview
|
||||
* @param enabled - Whether the countdown timer is enabled or disabled.
|
||||
*/
|
||||
setCounterEnabled(enabled: boolean) {
|
||||
this.counterEnabled$.next(enabled);
|
||||
if (!enabled) {
|
||||
this.previewMinutes = 0;
|
||||
this.previewHours = 0;
|
||||
this.previewDays = 0;
|
||||
} else {
|
||||
this.updatePreviewTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private setDateTime(dateToSet) {
|
||||
this.time = {hour: dateToSet.getHours(), minute: dateToSet.getMinutes()};
|
||||
this.date = {year: dateToSet.getFullYear(), month: dateToSet.getMonth() + 1, day: dateToSet.getDate()};
|
||||
|
||||
this.updatePreviewTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the preview time based on the configured countdown date and the current time
|
||||
*/
|
||||
updatePreviewTime() {
|
||||
const countDownTo = new Date(this.date.year, this.date.month - 1, this.date.day, this.time.hour, this.time.minute);
|
||||
const timeDifference = countDownTo.getTime() - new Date().getTime();
|
||||
this.allocateTimeUnits(timeDifference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to push how many days, hours and minutes are left in the countdown to their respective behaviour subject
|
||||
* @param timeDifference - The time difference to calculate and push the time units for
|
||||
*/
|
||||
private allocateTimeUnits(timeDifference) {
|
||||
this.previewMinutes = Math.floor((timeDifference) / (1000 * 60) % 60);
|
||||
this.previewHours = Math.floor((timeDifference) / (1000 * 60 * 60) % 24);
|
||||
this.previewDays = Math.floor((timeDifference) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
|
||||
get formMessage() {
|
||||
return this.alertForm.get('formMessage');
|
||||
}
|
||||
@@ -127,9 +185,12 @@ export class SystemWideAlertFormComponent implements OnInit {
|
||||
const alert = new SystemWideAlert();
|
||||
alert.message = this.formMessage.value;
|
||||
alert.active = this.formActive.value;
|
||||
const countDownTo = new Date(this.date.year, this.date.month - 1, this.date.day, this.time.hour, this.time.minute);
|
||||
alert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString();
|
||||
|
||||
if (this.counterEnabled$.getValue()) {
|
||||
const countDownTo = new Date(this.date.year, this.date.month - 1, this.date.day, this.time.hour, this.time.minute);
|
||||
alert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString();
|
||||
} else {
|
||||
alert.countdownTo = null;
|
||||
}
|
||||
if (hasValue(this.currentAlert)) {
|
||||
const updatedAlert = Object.assign(new SystemWideAlert(), this.currentAlert, alert);
|
||||
this.handleResponse(this.systemWideAlertDataService.put(updatedAlert), 'system-wide-alert.form.update');
|
||||
|
@@ -4851,9 +4851,9 @@
|
||||
|
||||
|
||||
|
||||
"menu.section.system-wide-alert": "System-wide Alerts",
|
||||
"menu.section.system-wide-alert": "System-wide Alert",
|
||||
|
||||
"system-wide-alert.form.header": "System-wide Alerts",
|
||||
"system-wide-alert.form.header": "System-wide Alert",
|
||||
|
||||
"system-wide-alert.form.cancel": "Cancel",
|
||||
|
||||
@@ -4867,9 +4867,11 @@
|
||||
|
||||
"system-wide-alert.form.label.message": "Alert message",
|
||||
|
||||
"system-wide-alert.form.label.countdownTo": "Count down",
|
||||
"system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer",
|
||||
|
||||
"system-wide-alert.form.label.countdownTo.hint": "Hint: When a date in the future is set, the system wide alert banner will perform a countdown to the set date. Setting the date to the current time or in the past will disable the countdown.",
|
||||
"system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.",
|
||||
|
||||
"system-wide-alert.form.label.preview": "System-wide alert preview",
|
||||
|
||||
"system-wide-alert.form.update.success": "The system-wide alert was successfully updated",
|
||||
|
||||
|
Reference in New Issue
Block a user