diff --git a/packages/frontend/app/components/reports/subject-header.js b/packages/frontend/app/components/reports/subject-header.js index 16dd02d76c..56929ad60c 100644 --- a/packages/frontend/app/components/reports/subject-header.js +++ b/packages/frontend/app/components/reports/subject-header.js @@ -4,7 +4,7 @@ import { action } from '@ember/object'; import { service } from '@ember/service'; import PapaParse from 'papaparse'; import { dropTask, timeout, waitForProperty } from 'ember-concurrency'; -import createDownloadFile from 'frontend/utils/create-download-file'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; import { validatable, Length } from 'ilios-common/decorators/validation'; import { TrackedAsyncData } from 'ember-async-data'; diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs index ab8c132d82..2ec5355603 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs @@ -26,6 +26,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -78,4 +88,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js index 17098949a4..78ac1743a9 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js @@ -1,12 +1,14 @@ import Component from '@glimmer/component'; import { filter, map } from 'rsvp'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { TrackedAsyncData } from 'ember-async-data'; -import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers'; import { action } from '@ember/object'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; +import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeInstructorSessionTypeGraph extends Component { @service router; @@ -135,4 +137,21 @@ export default class CourseVisualizeInstructorSessionTypeGraph extends Component ); this.tooltipContent = htmlSafe(uniqueValues(mapBy(meta.sessions, 'title')).sort().join(', ')); }); + + downloadData = dropTask(async () => { + const data = await this.getData(this.args.course, this.args.user); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.sessionType')}`] = obj.meta.sessionType.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile( + `ilios-course-${this.args.course.id}-instructor-${this.args.user.id}-session-types.csv`, + csv, + 'text/csv', + ); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs index b9bad31545..4e542b8aa5 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs @@ -26,6 +26,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -78,4 +88,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js index 7bf9e2486f..0571bd08ac 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js @@ -2,12 +2,14 @@ import Component from '@glimmer/component'; import { filter, map } from 'rsvp'; import { isEmpty } from '@ember/utils'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { TrackedAsyncData } from 'ember-async-data'; -import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; import { action } from '@ember/object'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; +import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeInstructorTermGraph extends Component { @service router; @@ -156,4 +158,21 @@ export default class CourseVisualizeInstructorTermGraph extends Component { ); this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', ')); }); + + downloadData = dropTask(async () => { + const data = await this.getData(this.args.course, this.args.user); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.term')}`] = obj.meta.term.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile( + `ilios-course-${this.args.course.id}-instructor-${this.args.user.id}-vocabulary-terms.csv`, + csv, + 'text/csv', + ); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs index c7ea34878a..5cd8f7c389 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs @@ -27,6 +27,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -83,4 +93,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-instructors-graph.js b/packages/ilios-common/addon/components/course/visualize-instructors-graph.js index e85ef88966..85fde461b9 100644 --- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.js @@ -1,13 +1,15 @@ import Component from '@glimmer/component'; import { isEmpty } from '@ember/utils'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { map } from 'rsvp'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { cleanQuery } from 'ilios-common/utils/query-utils'; import { TrackedAsyncData } from 'ember-async-data'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeInstructorsGraph extends Component { @@ -155,4 +157,17 @@ export default class CourseVisualizeInstructorsGraph extends Component { this.router.transitionTo('course-visualize-instructor', this.args.course.id, obj.meta.user.id); } + + downloadData = dropTask(async () => { + const data = await this.getData(this.args.course); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.instructor')}`] = obj.meta.user.fullName; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile(`ilios-course-${this.args.course.id}-instructors.csv`, csv, 'text/csv'); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs b/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs index 10795f70f8..b62480d64d 100644 --- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs @@ -53,6 +53,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -126,4 +136,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-objectives-graph.js b/packages/ilios-common/addon/components/course/visualize-objectives-graph.js index 2136bc8ef5..8a7b6d7690 100644 --- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.js @@ -4,9 +4,11 @@ import { action } from '@ember/object'; import { service } from '@ember/service'; import { htmlSafe } from '@ember/template'; import { filter, map } from 'rsvp'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { TrackedAsyncData } from 'ember-async-data'; +import PapaParse from 'papaparse'; import { mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; export default class CourseVisualizeObjectivesGraph extends Component { @service router; @@ -38,6 +40,7 @@ export default class CourseVisualizeObjectivesGraph extends Component { get hasData() { return this.data.length; } + get sortedAscending() { return this.sortBy.search(/desc/) === -1; } @@ -191,4 +194,23 @@ export default class CourseVisualizeObjectivesGraph extends Component { ); this.tooltipContent = htmlSafe(mapBy(meta.sessionObjectives, 'sessionTitle').sort().join(', ')); }); + + downloadData = dropTask(async () => { + const sessions = await this.args.course.sessions; + const data = await this.getDataObjects(sessions); + const output = data.map((obj) => { + const rhett = {}; + rhett[this.intl.t('general.percentage')] = obj.percentage; + rhett[this.intl.t('general.courseObjective')] = obj.meta.courseObjective.title; + rhett[this.intl.t('general.competencies')] = obj.meta.competencies; + rhett[this.intl.t('general.sessions')] = mapBy( + sortBy(mapBy(obj.meta.sessionObjectives, 'session'), 'title'), + 'title', + ).join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile(`ilios-course-${this.args.course.id}-objectives.csv`, csv, 'text/csv'); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs b/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs index 5b1610c25a..3a6b81e0d7 100644 --- a/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs @@ -26,6 +26,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -78,4 +88,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-session-type-graph.js b/packages/ilios-common/addon/components/course/visualize-session-type-graph.js index 5d952ef73c..69c7d132c8 100644 --- a/packages/ilios-common/addon/components/course/visualize-session-type-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-session-type-graph.js @@ -2,12 +2,14 @@ import Component from '@glimmer/component'; import { map } from 'rsvp'; import { isEmpty } from '@ember/utils'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { TrackedAsyncData } from 'ember-async-data'; -import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; import { action } from '@ember/object'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; +import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeSessionTypeGraph extends Component { @service router; @@ -149,4 +151,22 @@ export default class CourseVisualizeSessionTypeGraph extends Component { this.tooltipTitle = title; this.tooltipContent = content; }); + + downloadData = dropTask(async () => { + const data = await this.getDataObjects(this.args.course, this.args.sessionType); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.vocabulary')} - ${this.intl.t('general.term')}`] = + `${obj.meta.vocabulary.title} - ${obj.meta.term.title}`; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile( + `ilios-course-${this.args.course.id}-session-type-${this.args.sessionType.id}-vocabulary-terms.csv`, + csv, + 'text/csv', + ); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs b/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs index 5b23278aaf..b47d7f4a94 100644 --- a/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs @@ -27,6 +27,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -83,4 +93,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-session-types-graph.js b/packages/ilios-common/addon/components/course/visualize-session-types-graph.js index aaa30cc606..5d43453270 100644 --- a/packages/ilios-common/addon/components/course/visualize-session-types-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-session-types-graph.js @@ -1,13 +1,15 @@ import Component from '@glimmer/component'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { cleanQuery } from 'ilios-common/utils/query-utils'; import { map } from 'rsvp'; import { TrackedAsyncData } from 'ember-async-data'; +import PapaParse from 'papaparse'; +import { cleanQuery } from 'ilios-common/utils/query-utils'; import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; export default class CourseVisualizeSessionTypesGraph extends Component { @service router; @@ -156,4 +158,17 @@ export default class CourseVisualizeSessionTypesGraph extends Component { obj.meta.sessionType.id, ); } + + downloadData = dropTask(async () => { + const data = await this.getData(this.args.course); + const output = data.map((obj) => { + const rhett = {}; + rhett[this.intl.t('general.sessionType')] = obj.meta.sessionType.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile(`ilios-course-${this.args.course.id}-session-types.csv`, csv, 'text/csv'); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-term-graph.hbs b/packages/ilios-common/addon/components/course/visualize-term-graph.hbs index 51fd37cd4c..437164ea2e 100644 --- a/packages/ilios-common/addon/components/course/visualize-term-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-term-graph.hbs @@ -26,6 +26,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -78,4 +88,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-term-graph.js b/packages/ilios-common/addon/components/course/visualize-term-graph.js index 9e63e4169f..d245d17660 100644 --- a/packages/ilios-common/addon/components/course/visualize-term-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-term-graph.js @@ -1,12 +1,14 @@ import Component from '@glimmer/component'; import { map } from 'rsvp'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { TrackedAsyncData } from 'ember-async-data'; -import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; import { action } from '@ember/object'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; +import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeTermGraph extends Component { @service router; @@ -121,4 +123,21 @@ export default class CourseVisualizeTermGraph extends Component { ); this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', ')); }); + + downloadData = dropTask(async () => { + const data = await this.getDataObjects(this.args.course, this.args.term); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.sessionType')}`] = obj.meta.sessionType.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile( + `ilios-course-${this.args.course.id}-vocabulary-term-${this.args.term.id}-session-types`, + csv, + 'text/csv', + ); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs index 166414ed68..d58d9a001f 100644 --- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs @@ -27,6 +27,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -83,4 +93,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js index 677f69ffdc..e84cb3fb28 100644 --- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js @@ -1,11 +1,13 @@ import Component from '@glimmer/component'; import { all, map } from 'rsvp'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { TrackedAsyncData } from 'ember-async-data'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; import { findById, mapBy, sortBy, uniqueById } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeVocabulariesGraph extends Component { @@ -44,7 +46,6 @@ export default class CourseVisualizeVocabulariesGraph extends Component { rhett.vocabulary = obj.meta.vocabulary; rhett.vocabularyTitle = obj.meta.vocabulary.title; rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', '); - rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', '); return rhett; }); } @@ -150,4 +151,17 @@ export default class CourseVisualizeVocabulariesGraph extends Component { obj.meta.vocabulary.id, ); } + + downloadData = dropTask(async () => { + const data = await this.getDataObjects(this.args.course); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.vocabulary')}`] = obj.meta.vocabulary.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile(`ilios-course-${this.args.course.id}-vocabularies.csv`, csv, 'text/csv'); + }); } diff --git a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs index 828daf18cd..f10ea7d568 100644 --- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs +++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs @@ -27,6 +27,16 @@ {{/if}} {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+ +
@@ -83,4 +93,4 @@ {{else}} {{/if}} - + \ No newline at end of file diff --git a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js index 68719c65d4..e9e42bfc0d 100644 --- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js +++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js @@ -1,11 +1,13 @@ import Component from '@glimmer/component'; import { filter, map } from 'rsvp'; import { htmlSafe } from '@ember/template'; -import { restartableTask, timeout } from 'ember-concurrency'; +import { dropTask, restartableTask, timeout } from 'ember-concurrency'; import { service } from '@ember/service'; import { cached, tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { TrackedAsyncData } from 'ember-async-data'; +import PapaParse from 'papaparse'; +import createDownloadFile from 'ilios-common/utils/create-download-file'; import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers'; export default class CourseVisualizeVocabularyGraph extends Component { @@ -148,4 +150,21 @@ export default class CourseVisualizeVocabularyGraph extends Component { } this.router.transitionTo('course-visualize-term', this.args.course.id, obj.meta.term.id); } + + downloadData = dropTask(async () => { + const data = await this.getDataObjects(this.args.course, this.args.vocabulary.id); + const output = data.map((obj) => { + const rhett = {}; + rhett[`${this.intl.t('general.term')}`] = obj.meta.term.title; + rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', '); + rhett[this.intl.t('general.minutes')] = obj.data; + return rhett; + }); + const csv = PapaParse.unparse(output); + createDownloadFile( + `ilios-course-${this.args.course.id}-vocabulary-${this.args.vocabulary.id}`, + csv, + 'text/csv', + ); + }); } diff --git a/packages/frontend/app/utils/create-download-file.js b/packages/ilios-common/addon/utils/create-download-file.js similarity index 100% rename from packages/frontend/app/utils/create-download-file.js rename to packages/ilios-common/addon/utils/create-download-file.js diff --git a/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss b/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss index f3150802d2..6a7fef6e93 100644 --- a/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss +++ b/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss @@ -34,5 +34,6 @@ grid-template-columns: 1fr; grid-gap: 10px; margin-left: 0.8rem; + margin-right: 0.8rem; } } diff --git a/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss b/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss index a42d1f9f17..263a68d972 100644 --- a/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss +++ b/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss @@ -10,6 +10,10 @@ grid-column: -1/1; padding-top: 2rem; + .table-actions { + text-align: right; + } + table { @include t.ilios-table-structure; @include t.ilios-table-colors; diff --git a/packages/ilios-common/app/utils/create-download-file.js b/packages/ilios-common/app/utils/create-download-file.js new file mode 100644 index 0000000000..c7bc7687db --- /dev/null +++ b/packages/ilios-common/app/utils/create-download-file.js @@ -0,0 +1 @@ +export { default } from 'ilios-common/utils/create-download-file'; diff --git a/packages/ilios-common/package.json b/packages/ilios-common/package.json index 0fac429227..09a8de844d 100644 --- a/packages/ilios-common/package.json +++ b/packages/ilios-common/package.json @@ -63,6 +63,7 @@ "luxon": ">= 3.5.0", "mockdate": ">= 3.0.0", "normalize.css": "^8.0.1", + "papaparse": "^5.4.1", "query-string": ">= 9.1.0", "scroll-into-view": ">= 1.16.0", "striptags": ">= 3.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81f605fcb7..4e02f5a4ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,7 +149,7 @@ importers: version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19) ember-cli-dependency-checker: specifier: ^3.3.2 - version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) + version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) ember-cli-dependency-lint: specifier: 2.0.1 version: 2.0.1 @@ -212,7 +212,7 @@ importers: version: 9.0.0(ember-qunit@8.1.1(@ember/test-helpers@3.3.1(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(webpack@5.96.1))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(qunit@2.22.0))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(qunit@2.22.0)(webpack@5.96.1) ember-focus-trap: specifier: ^1.1.0 - version: 1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)) + version: 1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)) ember-load-initializers: specifier: ^2.1.2 version: 2.1.2(@babel/core@7.26.0) @@ -434,7 +434,7 @@ importers: version: 9.1.0(@ember/test-helpers@4.0.4(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)))(@glimmer/component@1.1.2(@babel/core@7.26.0))(@glimmer/tracking@1.1.2)(ember-modifier@4.2.0(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)))(miragejs@0.1.48)(tracked-built-ins@3.3.0)(webpack@5.96.1) ember-focus-trap: specifier: ^1.0.0 - version: 1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)) + version: 1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)) ember-in-element-polyfill: specifier: ^1.0.0 version: 1.0.1 @@ -489,6 +489,9 @@ importers: normalize.css: specifier: ^8.0.1 version: 8.0.1 + papaparse: + specifier: ^5.4.1 + version: 5.4.1 query-string: specifier: '>= 9.1.0' version: 9.1.1 @@ -570,7 +573,7 @@ importers: version: 3.0.0 ember-cli-dependency-checker: specifier: ^3.3.2 - version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) + version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) ember-cli-inject-live-reload: specifier: ^2.1.0 version: 2.1.0 @@ -759,7 +762,7 @@ importers: version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19) ember-cli-dependency-checker: specifier: ^3.3.2 - version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) + version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) ember-cli-dependency-lint: specifier: 2.0.1 version: 2.0.1 @@ -990,7 +993,7 @@ importers: version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19) ember-cli-dependency-checker: specifier: ^3.3.2 - version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) + version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) ember-cli-dependency-lint: specifier: 2.0.1 version: 2.0.1 @@ -1215,7 +1218,7 @@ importers: version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19) ember-cli-dependency-checker: specifier: ^3.3.2 - version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) + version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)) ember-cli-htmlbars: specifier: ^6.3.0 version: 6.3.0 @@ -5095,8 +5098,8 @@ packages: '@embroider/core': optional: true - ember-cli-dependency-checker@3.3.3: - resolution: {integrity: sha512-mvp+HrE0M5Zhc2oW8cqs8wdhtqq0CfQXAYzaIstOzHJJn/U01NZEGu3hz7J7zl/+jxZkyygylzcS57QqmPXMuQ==} + ember-cli-dependency-checker@3.3.2: + resolution: {integrity: sha512-PwkrW5oYsdPWwt+0Tojufmv/hxVETTjkrEdK7ANQB2VSnqpA5UcYubwpQM9ONuR2J8wyNDMwEHlqIrk/FYtBsQ==} engines: {node: '>= 6'} peerDependencies: ember-cli: ^3.2.0 || >=4.0.0 @@ -5379,11 +5382,11 @@ packages: miragejs: optional: true - ember-focus-trap@1.1.1: - resolution: {integrity: sha512-5tOWu6eV1UoNZE+P9Gl9lJXNrENZVCoOXi52ePb7JOrOZ3ckOk1OkPsFwR4Jym9VJ7vZ6S3Z3D8BrkFa2aCpYw==} + ember-focus-trap@1.1.0: + resolution: {integrity: sha512-KxbCKpAJaBVZm+bW4tHPoBJAZThmxa6pI+WQusL+bj0RtAnGUNkWsVy6UBMZ5QqTQzf4EvGHkCVACVp5lbAWMQ==} engines: {node: 12.* || >= 14} peerDependencies: - ember-source: '>= 4.0.0' + ember-source: ^4.0.0 || ^5.0.0 ember-functions-as-helper-polyfill@2.1.2: resolution: {integrity: sha512-yvW6xykvZEIYzzwlrC/g9yu6LtLkkj5F+ho6U+BDxN1uREMgoMOZnji7sSILn5ITVpaJ055DPcO+utEFD7IZOA==} @@ -6081,6 +6084,9 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} + find-yarn-workspace-root@1.2.1: + resolution: {integrity: sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==} + find-yarn-workspace-root@2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} @@ -9005,8 +9011,8 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - streamx@2.20.2: - resolution: {integrity: sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==} + streamx@2.20.1: + resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} string-template@0.2.1: resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==} @@ -9321,11 +9327,11 @@ packages: tiny-lr@2.0.0: resolution: {integrity: sha512-f6nh0VMRvhGx4KCeK1lQ/jaL0Zdb5WdR+Jk8q9OSUQnaSDxAEGH1fgqLZ+cMl5EW3F2MGnCsalBO1IsnnogW1Q==} - tldts-core@6.1.61: - resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==} + tldts-core@6.1.60: + resolution: {integrity: sha512-XHjoxak8SFQnHnmYHb3PcnW5TZ+9ErLZemZei3azuIRhQLw4IExsVbL3VZJdHcLeNaXq6NqawgpDPpjBOg4B5g==} - tldts@6.1.61: - resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==} + tldts@6.1.60: + resolution: {integrity: sha512-TYVHm7G9NCnhgqOsFalbX6MG1Po5F4efF+tLfoeiOGQq48Oqgwcgz8upY2R1BHWa4aDrj28RYx0dkYJ63qCFMg==} hasBin: true tmp@0.0.28: @@ -13724,7 +13730,7 @@ snapshots: bare-stream@2.3.2: dependencies: - streamx: 2.20.2 + streamx: 2.20.1 optional: true base64-js@1.5.1: {} @@ -15491,14 +15497,16 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-dependency-checker@3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)): + ember-cli-dependency-checker@3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)): dependencies: chalk: 2.4.2 ember-cli: 5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7) - find-yarn-workspace-root: 2.0.0 + find-yarn-workspace-root: 1.2.1 is-git-url: 1.0.0 resolve: 1.22.8 semver: 5.7.2 + transitivePeerDependencies: + - supports-color ember-cli-dependency-lint@2.0.1: dependencies: @@ -16247,7 +16255,7 @@ snapshots: - supports-color - webpack - ember-focus-trap@1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)): + ember-focus-trap@1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)): dependencies: '@embroider/addon-shim': 1.9.0 ember-source: 5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1) @@ -17416,6 +17424,13 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 + find-yarn-workspace-root@1.2.1: + dependencies: + fs-extra: 4.0.3 + micromatch: 3.1.10 + transitivePeerDependencies: + - supports-color + find-yarn-workspace-root@2.0.0: dependencies: micromatch: 4.0.8 @@ -20635,7 +20650,7 @@ snapshots: stream-shift@1.0.3: {} - streamx@2.20.2: + streamx@2.20.1: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 @@ -20933,7 +20948,7 @@ snapshots: dependencies: b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.20.2 + streamx: 2.20.1 tar@6.2.1: dependencies: @@ -21188,11 +21203,11 @@ snapshots: transitivePeerDependencies: - supports-color - tldts-core@6.1.61: {} + tldts-core@6.1.60: {} - tldts@6.1.61: + tldts@6.1.60: dependencies: - tldts-core: 6.1.61 + tldts-core: 6.1.60 tmp@0.0.28: dependencies: @@ -21247,7 +21262,7 @@ snapshots: tough-cookie@5.0.0: dependencies: - tldts: 6.1.61 + tldts: 6.1.60 tr46@0.0.3: {}