Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -610,14 +610,18 @@ module('Acceptance | Session - Independent Learning', function (hooks) {

assert.ok(page.details.learnersAreVisible);
assert.ok(page.details.instructorsAreVisible);
assert.ok(page.details.overview.ilm.isIlm);

await page.details.overview.ilm.toggleIlm.yesNoToggle.click();
await page.details.overview.ilm.removeIlm();
await page.details.overview.ilm.confirm();

assert.notOk(page.details.overview.ilm.isIlm);
assert.notOk(page.details.learnersAreVisible);
assert.notOk(page.details.instructorsAreVisible);

await page.details.overview.ilm.toggleIlm.yesNoToggle.click();
await page.details.overview.ilm.addIlm();

assert.ok(page.details.overview.ilm.isIlm);
assert.ok(page.details.learnersAreVisible);
assert.ok(page.details.instructorsAreVisible);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ module('Acceptance | Session - Overview', function (hooks) {
assert.strictEqual(currentRouteName(), 'session.index');
assert.ok(page.details.overview.ilm.ilmHours.isVisible);
assert.ok(page.details.overview.ilm.ilmDueDateAndTime.isVisible);
assert.strictEqual(parseInt(page.details.overview.ilm.ilmHours.value, 10), ilmSession.hours);
assert.ok(page.details.overview.ilm.isIlm);
assert.strictEqual(Number(page.details.overview.ilm.ilmHours.value), ilmSession.hours);
assert.strictEqual(
page.details.overview.ilm.ilmDueDateAndTime.value,
this.intl.formatDate(ilmSession.dueDate, {
Expand All @@ -71,8 +72,10 @@ module('Acceptance | Session - Overview', function (hooks) {
}),
);

await page.details.overview.ilm.toggleIlm.yesNoToggle.click();
await page.details.overview.ilm.removeIlm();
await page.details.overview.ilm.confirm();

assert.notOk(page.details.overview.ilm.isIlm);
assert.notOk(page.details.overview.ilm.ilmHours.isVisible);
assert.notOk(page.details.overview.ilm.ilmDueDateAndTime.isVisible);
});
Expand All @@ -94,12 +97,14 @@ module('Acceptance | Session - Overview', function (hooks) {
assert.strictEqual(currentRouteName(), 'session.index');
assert.notOk(page.details.overview.ilm.ilmHours.isVisible);
assert.notOk(page.details.overview.ilm.ilmDueDateAndTime.isVisible);
assert.notOk(page.details.overview.ilm.isIlm);

await page.details.overview.ilm.toggleIlm.yesNoToggle.click();
await page.details.overview.ilm.addIlm();

assert.ok(page.details.overview.ilm.ilmHours.isVisible);
assert.ok(page.details.overview.ilm.ilmDueDateAndTime.isVisible);
assert.strictEqual(parseInt(page.details.overview.ilm.ilmHours.value, 10), 1);
assert.ok(page.details.overview.ilm.isIlm);
assert.strictEqual(Number(page.details.overview.ilm.ilmHours.value), 1);
assert.strictEqual(
page.details.overview.ilm.ilmDueDateAndTime.value,
this.intl.formatDate(DateTime.fromObject({ hour: 17, minute: 0 }).plus({ weeks: 6 }), {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { clickable, create, fillable, hasClass, isVisible, text } from 'ember-cli-page-object';
import yesNoToggle from '../toggle-yesno';
import ilmDueDateAndTime from '../session-overview-ilm-duedate';

const definition = {
Expand All @@ -13,11 +12,18 @@ const definition = {
hasError: isVisible('.validation-error-message'),
},
ilmDueDateAndTime,
toggleIlm: {
scope: '[data-test-ilm-value]',
yesNoToggle,
addIlm: clickable('[data-test-add]'),
removeIlm: clickable('[data-test-remove]'),
canAdd: isVisible('[data-test-add]'),
canRemove: isVisible('[data-test-remove]'),
message: text('[data-test-message]'),
confirmationMessage: {
scope: '[data-test-confirmation-message]',
},
isIlm: hasClass('add', '[data-test-ilm-value] span'),
confirm: clickable('[data-test-confirm]'),
cancel: clickable('[data-test-cancel]'),
undo: clickable('[data-test-undo]'),
isIlm: hasClass('is-ilm', '[data-test-session-ilm]'),
};

export default definition;
Expand Down
257 changes: 184 additions & 73 deletions packages/ilios-common/addon/components/session/ilm.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { guidFor } from '@ember/object/internals';
import { TrackedAsyncData } from 'ember-async-data';
import YupValidations from 'ilios-common/classes/yup-validations';
import { number } from 'yup';
import { task } from 'ember-concurrency';
import { DateTime } from 'luxon';
import { task, timeout } from 'ember-concurrency';
import t from 'ember-intl/helpers/t';
import ToggleYesno from 'ilios-common/components/toggle-yesno';
import perform from 'ember-concurrency/helpers/perform';
import EditableField from 'ilios-common/components/editable-field';
import { on } from '@ember/modifier';
Expand All @@ -17,10 +15,18 @@ import set from 'ember-set-helper/helpers/set';
import YupValidationMessage from 'ilios-common/components/yup-validation-message';
import SessionOverviewIlmDuedate from 'ilios-common/components/session-overview-ilm-duedate';
import focus from 'ilios-common/modifiers/focus';
import FaIcon from '@fortawesome/ember-fontawesome/components/fa-icon';
import { faPlus, faSpinner, faTrash, faUndo } from '@fortawesome/free-solid-svg-icons';
import { DateTime } from 'luxon';
import { and, not } from 'ember-truth-helpers';
import LoadingSpinner from 'ilios-common/components/loading-spinner';

export default class SessionIlmComponent extends Component {
@service store;
@tracked localHours;
@tracked showRemoveConfirmation = false;
@tracked isNew = false;
@tracked isRecentlyAdded = false;

get hours() {
if (this.localHours !== undefined) {
Expand Down Expand Up @@ -51,26 +57,67 @@ export default class SessionIlmComponent extends Component {
return this.ilmSession !== null;
}

saveIndependentLearning = task({ drop: true }, async (value) => {
if (!value) {
const ilmSession = await this.args.session.ilmSession;
this.args.session.set('ilmSession', null);
await ilmSession.destroyRecord();
await this.args.session.save();
} else {
const hours = 1;
const dueDate = DateTime.now().plus({ week: 6 }).set({ hour: 17, minute: 0 }).toJSDate();
this.localHours = hours;
const ilmSession = this.store.createRecord('ilm-session', {
session: this.args.session,
hours,
dueDate,
});
this.args.session.set('ilmSession', await ilmSession.save());
await this.args.session.save();
get showAddNew() {
return !this.isIndependentLearning || this.addIlm.isRunning;
}

get hasLinkedData() {
if (!this.ilmSession) {
return true;
}

return (
this.ilmSession.hasMany('learnerGroups').ids().length ||
this.ilmSession.hasMany('learners').ids().length ||
this.ilmSession.hasMany('instructorGroups').ids().length ||
this.ilmSession.hasMany('instructors').ids().length
);
}

addIlm = task({ drop: true }, async () => {
const hours = 1;
const dueDate = DateTime.now().plus({ week: 6 }).set({ hour: 17, minute: 0 }).toJSDate();
this.localHours = hours;
const ilmSession = this.store.createRecord('ilm-session', {
session: this.args.session,
hours,
dueDate,
});
await ilmSession.save();
this.isNew = true;
this.isRecentlyAdded = true;
await timeout(100);
this.isNew = false;
});

removeIlm = task({ drop: true }, async () => {
const ilmSession = await this.args.session.ilmSession;
this.args.session.set('ilmSession', null);
await ilmSession.destroyRecord();
await this.args.session.save();
this.showRemoveConfirmation = false;
this.isRecentlyAdded = false;
});

get status() {
if (!this.isIndependentLearning && !this.args.editable) {
return 'hidden';
}

if (this.showRemoveConfirmation) {
return 'confirm-removal';
}

let status = this.showAddNew ? 'not-ilm' : 'is-ilm';
if (this.isNew) {
status += ' is-new';
}
if (this.isRecentlyAdded) {
status += ' recently-added';
}
return status;
}

changeIlmHours = task({ restartable: true }, async () => {
this.validations.addErrorDisplayFor('hours');
const isValid = await this.validations.isValid();
Expand All @@ -91,63 +138,127 @@ export default class SessionIlmComponent extends Component {
this.validations.removeErrorDisplayFor('hours');
this.localHours = undefined;
};

clickDelete = () => {
this.isRecentlyAdded = false;
if (this.hasLinkedData) {
this.showRemoveConfirmation = true;
} else {
this.removeIlm.perform();
}
};

<template>
<div class="session-ilm block" data-test-session-ilm>
<label>{{t "general.independentLearning"}}:</label>
<span class="ilm-value" data-test-ilm-value>
{{#if @editable}}
<ToggleYesno
@yes={{this.isIndependentLearning}}
@toggle={{perform this.saveIndependentLearning}}
data-test-ilm-toggle
/>
{{else}}
{{#if this.isIndependentLearning}}
<span class="add">{{t "general.yes"}}</span>
<fieldset class="session-ilm {{this.status}}" data-test-session-ilm>
<legend>
{{t "general.ilm"}}
{{#if (and @editable (not this.showAddNew))}}
{{#if this.showRemoveConfirmation}}
<button
title={{t "general.cancel"}}
type="button"
{{on "click" (set this "showRemoveConfirmation" false)}}
data-test-undo
>
<FaIcon @icon={{faUndo}} />
</button>
{{else}}
<span class="remove">{{t "general.no"}}</span>
{{/if}}
{{/if}}
</span>
</div>
{{#if this.isIndependentLearning}}
<div class="hours block" data-test-ilm-hours>
<label for="hours-{{this.uniqueId}}">{{t "general.hours"}}:</label>
<span>
{{#if @editable}}
<EditableField
@value={{this.hours}}
@save={{perform this.changeIlmHours}}
@close={{this.resetHours}}
as |keyboard isSaving|
<button
title={{t "general.remove"}}
class="remove"
type="button"
{{on "click" this.clickDelete}}
data-test-remove
>
<input
id="hours-{{this.uniqueId}}"
disabled={{isSaving}}
type="text"
value={{this.hours}}
{{this.validations.attach "hours"}}
{{on "input" (pick "target.value" (set this "localHours"))}}
{{keyboard}}
{{focus}}
/>
<YupValidationMessage
@description={{t "general.hours"}}
@validationErrors={{this.validations.errors.hours}}
<FaIcon
@icon={{if this.removeIlm.isRunning faSpinner faTrash}}
@spin={{this.removeIlm.isRunning}}
/>
</EditableField>
{{else}}
{{@session.ilmSession.hours}}
</button>
{{/if}}
</span>
</div>
{{#unless @session.hasPostrequisite}}
<SessionOverviewIlmDuedate
@ilmSession={{this.ilmSession}}
@editable={{@editable}}
class="block"
/>
{{/unless}}
{{/if}}
{{/if}}
</legend>
{{#if this.showRemoveConfirmation}}
<p data-test-confirmation-message>
{{t "general.confirmRemoveIlm"}}
</p>
<div class="confirm-buttons">
<button
type="button"
class="remove text"
{{on "click" this.removeIlm.perform}}
disabled={{this.removeIlm.isRunning}}
data-test-confirm
>
{{#if this.removeIlm.isRunning}}
<LoadingSpinner />
{{else}}
{{t "general.yes"}}
{{/if}}
</button>
<button
type="button"
class="done text"
{{on "click" (set this "showRemoveConfirmation" false)}}
data-test-cancel
>
{{t "general.cancel"}}
</button>
</div>
{{/if}}
{{#if (and @editable (not this.showRemoveConfirmation))}}
{{#if this.showAddNew}}
<button
title={{t "general.addIlm" session=@session.title}}
type="button"
class="add-ilm"
{{on "click" this.addIlm.perform}}
data-test-add
>
<FaIcon
@icon={{if this.addIlm.isRunning faSpinner faPlus}}
@spin={{this.addIlm.isRunning}}
/>
</button>
{{/if}}
{{/if}}
{{#if
(and @session.ilmSession (not this.showRemoveConfirmation) (not this.addIlm.isRunning))
}}
<div class="hours" data-test-ilm-hours>
<label for="hours-{{this.uniqueId}}">{{t "general.hours"}}:</label>
<span>
{{#if @editable}}
<EditableField
@value={{this.hours}}
@save={{perform this.changeIlmHours}}
@close={{this.resetHours}}
as |keyboard isSaving|
>
<input
id="hours-{{this.uniqueId}}"
disabled={{isSaving}}
type="text"
value={{this.hours}}
{{this.validations.attach "hours"}}
{{on "input" (pick "target.value" (set this "localHours"))}}
{{keyboard}}
{{focus}}
/>
<YupValidationMessage
@description={{t "general.hours"}}
@validationErrors={{this.validations.errors.hours}}
/>
</EditableField>
{{else}}
{{@session.ilmSession.hours}}
{{/if}}
</span>
</div>
{{#unless @session.hasPostrequisite}}
<SessionOverviewIlmDuedate @ilmSession={{this.ilmSession}} @editable={{@editable}} />
{{/unless}}
{{/if}}
</fieldset>
</template>
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
@forward "components/course/visualize-objectives-graph";

@forward "components/session/collapsed-objectives";
@forward "components/session/ilm";
@forward "components/session/objectives";
@forward "components/session/manage-objective-descriptors";
@forward "components/session/manage-objective-parents";
Expand Down
Loading
Loading