diff --git a/packages/quill-cdn/assets/images/icons/xs/arrow-down.svg b/packages/quill-cdn/assets/images/icons/xs/arrow-down.svg new file mode 100644 index 00000000000..1ff9dae0340 --- /dev/null +++ b/packages/quill-cdn/assets/images/icons/xs/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/quill-cdn/assets/images/icons/xs/pencil.svg b/packages/quill-cdn/assets/images/icons/xs/pencil.svg new file mode 100644 index 00000000000..ec1a469a707 --- /dev/null +++ b/packages/quill-cdn/assets/images/icons/xs/pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/quill-cdn/assets/images/icons/xs/tool-connect.svg b/packages/quill-cdn/assets/images/icons/xs/tool-connect.svg new file mode 100644 index 00000000000..bad7ca41da0 --- /dev/null +++ b/packages/quill-cdn/assets/images/icons/xs/tool-connect.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/quill-cdn/assets/images/icons/xs/tool-lessons.svg b/packages/quill-cdn/assets/images/icons/xs/tool-lessons.svg new file mode 100644 index 00000000000..daeded04bb9 --- /dev/null +++ b/packages/quill-cdn/assets/images/icons/xs/tool-lessons.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/QuillLMS/app/assets/stylesheets/pages/my_lessons.scss b/services/QuillLMS/app/assets/stylesheets/pages/my_lessons.scss index 3f3e626bc75..07beb972709 100644 --- a/services/QuillLMS/app/assets/stylesheets/pages/my_lessons.scss +++ b/services/QuillLMS/app/assets/stylesheets/pages/my_lessons.scss @@ -2,58 +2,30 @@ section:last-of-type { padding-bottom: 5px; } - .my-lessons-header { - margin-top: 30px; - margin-bottom: 30px; - h1 { - font-weight: 600; - font-size: 30px; - margin-bottom: 10px; - } - p { - margin: 7px 0px; - font-size: 16px; - } - span { - font-weight: 600; + .top-section { + display: flex; + margin: 32px 0px 64px 0px; + .my-lessons-header { + h1 { + @include display-xl; + margin-bottom: 10px; + } + p { + margin: 7px 0px; + @include text-s; + } + span { + font-weight: 600; + } + a { + color: $quill-green-dark; + text-decoration: underline; + } } - a { - color: #027360; - font-weight: 600; - text-decoration: underline; + .dropdown-container { + min-width: 190px; } } - #launch-lesson { - height: 40px; - color: white; - font-size: 14px; - font-weight: bold; - width: 145px; - border-radius: 3px; - background-color: #00c2a2; - display: flex; - align-items: center; - justify-content: center; - } - .lesson-completed { - margin-right: 8px; - color: #82bf3c; - font-weight: 600; - width: 160px; - } - .resume-lesson { - width: 145px; - height: 40px; - border-radius: 3px; - background-color: $quill-white; - border: solid 1px #00c2a2; - font-weight: bold; - color: #00c2a2 !important; - margin-right: 30px; - display: flex; - justify-content: center; - align-items: center; - } .customized-editions-tag { width: 90px; height: 23px; @@ -71,6 +43,130 @@ display: none; } } + .lesson-unit-container { + border-radius: 8px; + border: 1px solid $quill-grey-15 !important; + padding: 32px !important; + margin-bottom: 32px; + .unit-header-container { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + .unit-name { + @include display-l; + } + .assigned-student-info { + display: flex; + color: $quill-grey-90; + @include text-s; + .locked-info { + display: flex; + border-right: 1px solid $quill-grey-15; + padding-right: 16px; + img { + margin-right: 4px; + } + } + .assigned-to { + display: flex; + @include text-s; + color: $quill-grey-90; + span:first-of-type { + margin: 0px 2px; + } + span:last-of-type { + margin-left: 2px; + } + span { + font-weight: 700; + } + } + } + .assigned-student-info:has(.locked-info) { + .assigned-to { + padding-left: 16px; + } + } + } + .table { + &.assigned-activities { + margin-bottom: 0px; + } + } + .lesson-activities-table { + .data-table-headers .data-table-header { + &.lesson-activity-header span { + margin-left: 8px; + } + &.launch-lesson-header span { + margin-left: 18px; + } + } + .data-table-body .data-table-row .data-table-row-section { + .customize-link, .download-link { + margin-left: 22px; + } + &.launch-lesson, &.lesson-activity { + a, .activity-cell-container { + margin-left: 8px; + } + } + } + .activity-cell-container { + display: flex; + justify-content: space-between; + width: 100%; + .data-table-chip { + max-width: 480px; + width: max-content; + span { + text-overflow: unset; + white-space: break-spaces; + text-align: left; + } + } + .lesson-complete { + display: flex; + align-items: center; + p { + color: $quill-green-vibrant; + font-size: 13px; + font-weight: 700; + margin-right: 8px; + } + } + } + a { + color: $quill-white; + } + } + } + .empty-lessons-content { + display: flex; + align-items: center; + flex-direction: column; + padding: 128px 0px; + + h2 { + font-size: 20px; + font-weight: 700; + line-height: normal; + } + + p { + @include text-s; + margin-top: 8px; + line-height: normal; + color: $quill-grey-50; + } + .buttons { + margin-top: 24px; + .quill-button:first-of-type { + margin-right: 8px; + } + } + } } .empty-lessons { diff --git a/services/QuillLMS/app/assets/stylesheets/shared/feedback-note.scss b/services/QuillLMS/app/assets/stylesheets/shared/feedback-note.scss index 84e25a1d859..715f459b0cb 100644 --- a/services/QuillLMS/app/assets/stylesheets/shared/feedback-note.scss +++ b/services/QuillLMS/app/assets/stylesheets/shared/feedback-note.scss @@ -1,9 +1,7 @@ .feedback-note { - border-top: solid 1px #c0c0c0; - padding-top: 20px; + color: $quill-grey-50; a { color: #027360; - font-weight: 600; text-decoration: underline; } } diff --git a/services/QuillLMS/app/assets/stylesheets/shared/table/react-table.scss b/services/QuillLMS/app/assets/stylesheets/shared/table/react-table.scss index ca2dd174538..97efcf4e745 100644 --- a/services/QuillLMS/app/assets/stylesheets/shared/table/react-table.scss +++ b/services/QuillLMS/app/assets/stylesheets/shared/table/react-table.scss @@ -26,11 +26,11 @@ &.grey { background-color: $quill-grey-1; border: 1px solid $quill-grey-15; - p { + span { color: $quill-grey-90; } } - p { + span { @include display-xs; color: $quill-green; margin-left: 4px; diff --git a/services/QuillLMS/client/app/bundles/Shared/components/shared/__tests__/__snapshots__/dataTableChip.test.tsx.snap b/services/QuillLMS/client/app/bundles/Shared/components/shared/__tests__/__snapshots__/dataTableChip.test.tsx.snap index 05124a565b9..9be58d46c3a 100644 --- a/services/QuillLMS/client/app/bundles/Shared/components/shared/__tests__/__snapshots__/dataTableChip.test.tsx.snap +++ b/services/QuillLMS/client/app/bundles/Shared/components/shared/__tests__/__snapshots__/dataTableChip.test.tsx.snap @@ -5,9 +5,9 @@ exports[`DataTableChip it should render 1`] = `
-

+ test label -

+
`; diff --git a/services/QuillLMS/client/app/bundles/Shared/components/shared/dataTableChip.tsx b/services/QuillLMS/client/app/bundles/Shared/components/shared/dataTableChip.tsx index 9bc61f49b06..4e6f72738f8 100644 --- a/services/QuillLMS/client/app/bundles/Shared/components/shared/dataTableChip.tsx +++ b/services/QuillLMS/client/app/bundles/Shared/components/shared/dataTableChip.tsx @@ -7,22 +7,31 @@ interface DataTableChipProps { src: string }, label: string, - link?: string + link?: string, + onClick?: (e: any) => void } -export const DataTableChip = ({ color, icon, label, link }: DataTableChipProps) => { +export const DataTableChip = ({ color, icon, label, link, onClick }: DataTableChipProps) => { if(link) { return( {icon && {icon.alt}} -

{label}

+ {label}
) } + if (onClick) { + return ( + + ) + } return (
{icon && {icon.alt}} -

{label}

+ {label}
) } diff --git a/services/QuillLMS/client/app/bundles/Shared/styles/data_table.scss b/services/QuillLMS/client/app/bundles/Shared/styles/data_table.scss index 76751b1545c..4024bfdfe3c 100644 --- a/services/QuillLMS/client/app/bundles/Shared/styles/data_table.scss +++ b/services/QuillLMS/client/app/bundles/Shared/styles/data_table.scss @@ -162,12 +162,12 @@ table.data-table { background-color: $quill-grey-1; border: 1px solid $quill-grey-15; - p { + span { color: $quill-grey-90; } } - p { + span { @include display-xs; color: $quill-green; margin-left: 4px; diff --git a/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/classroom_lessons.jsx b/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/classroom_lessons.jsx index 77e8833344c..5f72ad5d24c 100644 --- a/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/classroom_lessons.jsx +++ b/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/classroom_lessons.jsx @@ -187,20 +187,26 @@ export default class ClassroomLessons extends React.Component { } renderEmptyState() { - const assignLessonsLink = Assign Lessons // eslint-disable-line react/jsx-no-target-blank - const learnMoreLink = Learn More // eslint-disable-line react/jsx-no-target-blank + const assignLessonsLink = Assign Lessons + const learnMoreLink = Learn More return ( -
-
-

You have no lessons assigned!

-

In order to launch a lesson, you need to assign a lesson to one of your classes.

-

With Quill Lessons, teachers can use Quill to lead whole-class lessons and to see and display student responses in real-time.

-
- {assignLessonsLink} - {learnMoreLink} +
+
+
+
+

Launch Lessons

+
+
+
+

You have no lessons assigned

+

In order to launch a lesson, you need to assign a lesson to one of your classes.

+

With Quill Lessons, teachers can use Quill to lead whole-class lessons and to see and display student responses in real-time.

+
+ {assignLessonsLink} + {learnMoreLink} +
- cartoon of a teacher gesturing at a projector screen showing Quill Lessons content
); } @@ -235,12 +241,15 @@ export default class ClassroomLessons extends React.Component { return (
- {this.renderHeader()} - classy.id === selectedClassroomId)} - /> +
+ {this.renderHeader()} + classy.id === selectedClassroomId)} + /> +
{ + it('renders the unit name and assigned information', () => { + render(); + const unitNameSpan = screen.getByText('Sample Unit') + + expect(screen.getByText('Sample Unit')).toBeInTheDocument(); + expect(screen.getByText('Assigned to')).toBeInTheDocument(); + expect(screen.getByText('20 Students')).toBeInTheDocument(); + expect(screen.getByText('2 classes (Class 1, Class 2)')).toBeInTheDocument(); + }); + + it('renders classroom activities as rows in the DataTable', () => { + render(); + + expect(screen.getByText('Activity 1')).toBeInTheDocument(); + expect(screen.getByText('Activity 2')).toBeInTheDocument(); + }); + + it('renders launch lesson options based on activity state', () => { + render(); + + const launchButtons = screen.getAllByText('Launch Lesson'); + expect(launchButtons.length).toBeGreaterThan(0); + + const viewReportButtons = screen.getAllByText('View Report'); + expect(viewReportButtons.length).toBeGreaterThan(0); + }); + + it('renders the customize and download links', () => { + render(); + + const customizeIcons = screen.getAllByLabelText('Customize lesson'); + expect(customizeIcons[0]).toBeInTheDocument(); + expect(customizeIcons[1]).toBeInTheDocument(); + + const downloadIcons = screen.getAllByLabelText('Download lesson'); + expect(downloadIcons[0]).toBeInTheDocument(); + expect(downloadIcons[1]).toBeInTheDocument(); + }); +}); diff --git a/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.jsx b/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.jsx deleted file mode 100644 index 312aca9c7f0..00000000000 --- a/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.jsx +++ /dev/null @@ -1,210 +0,0 @@ -import Pluralize from 'pluralize'; -import React from 'react'; - -import { requestPut, } from '../../../../../modules/request/index'; -import AddClassroomActivityRow from './add_classroom_activity_row.jsx'; -import ClassroomActivity from './classroom_activity'; - -export default class Unit extends React.Component { - constructor(props) { - super(props) - - this.state = { - edit: false, - unitName: (this.props.data.unitName || this.props.data.unit.name), - savedUnitName: (this.props.data.unitName || this.props.data.unit.name), - error: false, - }; - - } - - hideUnit = () => { - const x = confirm('Are you sure you want to delete this Activity Pack? \n \nIt will delete all assignments given to students associated with this pack, even if those assignments have already been completed.'); - if (x) { - this.props.hideUnit(this.getUnitId()); - } - }; - - assignedToText = () => { - const dclassy = this.props.data.classrooms; - // ensure classrooms is always an array as sometimes it is passed a set - // and we need to do a number of things with it that are better with an array - const classrooms = Array.isArray(dclassy) ? dclassy : Array.from(dclassy); - const studentCount = this.props.data.num_students_assigned || this.props.data.studentCount; - return ( -
{`Assigned to ${studentCount} ${Pluralize('Student', studentCount)} in - ${classrooms.length} ${Pluralize('class', classrooms.length)} (${classrooms.join(', ')}).`}
- ); - }; - - editUnit = () => { - this.props.editUnit(this.getUnitId()); - }; - - deleteOrLockedInfo = () => { - const firstCa = this.props.data.classroomActivities.values().next().value; - const ownedByCurrentUser = firstCa.ownedByCurrentUser; - if (!this.props.report && !this.props.lesson && ownedByCurrentUser) { - return Delete; - } else if (!ownedByCurrentUser) { - return Created By {firstCa.ownerName}; - } - }; - - editName = () => { - if (this.props.data.classroomActivities.values().next().value.ownedByCurrentUser) { - let text, - classy, - inlineStyle; - if (this.state.errors) { - text = `${this.state.errors}. Click here to try again.`; - classy = 'errors h-pointer'; - inlineStyle = { paddingTop: '4px', }; - } else { - classy = 'edit-unit'; - text = 'Edit Name'; - } - return {text}; - } - }; - - submitName = () => { - return Submit; - }; - - dueDate = () => { - if (!this.props.report && !this.props.lesson) { - return Due Date; - } - }; - - changeToEdit = () => { - this.setState({ edit: true, }); - }; - - handleNameChange = (e) => { - this.setState({ unitName: e.target.value, }); - }; - - editUnitName = () => { - return ; - }; - - editStudentsLink = () => { - return this.props.report || this.props.lesson ? null : Edit Classes & Students; - }; - - handleSubmit = () => { - requestPut( - `${process.env.DEFAULT_URL}/teachers/units/${this.props.data.unitId}`, - { unit: { name: this.state.unitName, }, }, - (body) => { - this.setState({ - edit: false, - errors: undefined, - savedUnitName: this.state.unitName, - }); - }, - (body) => { - this.setState({ - errors: body.errors, - edit: false, - unitName: this.state.savedUnitName, - }); - } - ) - - }; - - showUnitName = () => { - return {this.state.unitName}; - }; - - showOrEditName = () => { - return this.state.edit ? this.editUnitName() : this.showUnitName(); - }; - - nameActionLink = () => { - if (this.state.edit) { - return this.submitName(); - } else if (this.props.report || this.props.lesson) { - return null; - } - return this.editName(); - - // return this.state.edit ? this.submitName() : this.editName() - }; - - getUnitId = () => { - return this.props.data.unitId || this.props.data.unit.id; - }; - - addClassroomActivityRow = () => { - if (this.props.data.classroomActivities.values().next().value.ownedByCurrentUser) { - return this.props.report || this.props.lesson ? null : ; - } - }; - - renderClassroomActivities = () => { - const classroomActivitiesArr = []; - if (this.props.data.classroom_activities) { - this.props.data.classroom_activities.forEach((ca) => { - classroomActivitiesArr.push( - - ); - }); - } else if (this.props.data.classroomActivities) { - this.props.data.classroomActivities.forEach((ca, key) => { - classroomActivitiesArr.push( - - ); - }) - } - return classroomActivitiesArr; - }; - - render() { - if (this.props.data.classroomActivities.size === 0) { - return null; - } - - return ( -
-
- - {this.showOrEditName()} - - {this.nameActionLink()} - {this.deleteOrLockedInfo()} -
-
- {this.assignedToText()} - {this.editStudentsLink()} - {this.dueDate()} -
-
- {this.renderClassroomActivities()} - {this.addClassroomActivityRow()} -
-
- ); - } -} diff --git a/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.tsx b/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.tsx new file mode 100644 index 00000000000..869042f03c7 --- /dev/null +++ b/services/QuillLMS/client/app/bundles/Teacher/components/assignment_flow/manage_units/unit.tsx @@ -0,0 +1,230 @@ +import Pluralize from 'pluralize'; +import React from 'react'; + +import PreviewOrLaunchModal from '../../shared/preview_or_launch_modal'; +import { DataTable, DataTableChip, EXTRA_SMALL_ICON_BASE_SRC } from '../../../../Shared'; + +const headers = [ + { + name: 'Tool', + attribute: 'tool', + width: '42px', + isSortable: false, + noTooltip: true + }, + { + name: 'Activity', + attribute: 'activity', + width: '646px', + isSortable: false, + noTooltip: true, + rowSectionClassName: 'lesson-activity', + headerClassName: 'lesson-activity-header' + }, + { + name: 'Launch Lesson', + attribute: 'launchLesson', + width: '150px', + isSortable: false, + noTooltip: true, + rowSectionClassName: 'launch-lesson', + headerClassName: 'launch-lesson-header' + }, + { + name: 'Customize', + attribute: 'customize', + width: '92px', + isSortable: false, + noTooltip: true + }, + { + name: 'Download', + attribute: 'download', + width: '46px', + isSortable: false, + noTooltip: true + }, +] + +const sharedButtonClassName = "quill-button contained extra-small green focus-on-light" + +export const Unit = ({ data }) => { + const { + classroomActivities, + classroom_activities, + classrooms, + name, + num_students_assigned, + studentCount, + unit, + unitId, + unitName + } = data + const [lessonActivityId, setLessonActivityId] = React.useState(null) + const [showModal, setShowModal] = React.useState(false) + + function assignedToText () { + // ensure classrooms is always an array as sometimes it is passed a set + // and we need to do a number of things with it that are better with an array + const classroomsArray = Array.isArray(classrooms) ? classrooms : Array.from(classrooms); + const studentCountData = num_students_assigned || studentCount; + return ( +
+

Assigned to

+ {`${studentCountData} ${Pluralize('Student', studentCountData)}`} +

in

+ {`${classroomsArray.length} ${Pluralize('class', classroomsArray.length)} (${classroomsArray.join(', ')})`} +
+ ) + }; + + function lockedInfo () { + const firstClassroomActivity = classroomActivities.values().next().value; + const ownedByCurrentUser = firstClassroomActivity?.ownedByCurrentUser; + if(ownedByCurrentUser) { return } + return( + + + Created By {firstClassroomActivity.ownerName} + + ); + }; + + function handleDataTableChipClick(e, id) { + setLessonActivityId(id) + handleToggleModal() + } + + function handleToggleModal() { + setShowModal(!showModal) + } + + function getClassroomActivities() { + if (Object.keys(classroomActivities)) { + return classroomActivities + } + if (Object.keys(classroom_activities)) { + return classroom_activities + } + } + + function getActivityId(lessonActivity) { + const { activityId, activityUid, activity } = lessonActivity + return activityId || activityUid || activity?.uid + } + + function getClassroomUnitId(lessonActivity) { + const { cuId, classroom_unit_id } = lessonActivity + return cuId || classroom_unit_id + } + + function renderModal() { + const activities = getClassroomActivities() + const lessonActivityData = activities.get(lessonActivityId) + + if (!showModal || !(Object.keys(lessonActivityData).length)) { return } + + return ( + + ); + } + + function renderActivityCell(lessonActivity, id) { + const { name, completed, started } = lessonActivity + const href = `/teachers/classroom_units/${getClassroomUnitId(lessonActivity)}/mark_lesson_as_completed/${id}`; + return( +
+ handleDataTableChipClick(e, id)} /> + {completed &&

Lesson Complete

} + {started && Mark As Complete} +
+ ) + } + + function handleLaunchLessonWithNoStudents(e, activityId, classroomUnitId) { + if (window.confirm("You have no students in this class. Quill Lessons is a collaborative tool for teachers and students to work together. If you'd like to launch this lesson anyway, click OK below. Otherwise, click Cancel.")) { + window.location.href = `${process.env.DEFAULT_URL}/teachers/classroom_units/${classroomUnitId}/launch_lesson/${activityId}`; + } + } + + function renderLaunchLessonCell(lessonActivity) { + const { completed, studentCount, started } = lessonActivity + const activityId = getActivityId(lessonActivity) + const classroomUnitId = getClassroomUnitId(lessonActivity) + if (completed) { + return View Report; + } + if (studentCount === 0) { + return handleLaunchLessonWithNoStudents(e, activityId, classroomUnitId)}>{started ? 'Resume Lesson' : 'Launch Lesson'}; + } + if (started) { + return Resume Lesson; + } + return Launch Lesson; + } + + function renderCustomizeCell(id) { + return( + + extra small pencil icon + + ) + } + + function renderDownloadCell(id) { + return( + + extra small arrow down icon + + ) + } + + function renderClassroomActivities() { + const lessonsIcon = lessons icon + // this data is passed as a Map type so we need to use forEach to iterate through it and push the relevant data into an empty array to pass as the DataTable rows prop + const rows = []; + const activities = getClassroomActivities() + activities.forEach((lessonActivity) => { + const id = getActivityId(lessonActivity) + rows.push({ + id, + tool: lessonsIcon, + activity: renderActivityCell(lessonActivity, id), + launchLesson: renderLaunchLessonCell(lessonActivity), + customize: renderCustomizeCell(id), + download: renderDownloadCell(id) + }) + }) + return ( + + ) + }; + return ( +
+ {renderModal()} +
+ + {unitName || name} + +
+ {lockedInfo()} + {assignedToText()} +
+
+
+ {renderClassroomActivities()} +
+
+ ); +} + +export default Unit