enhance individual gradebook graphql setup
setup enhanced individual gradebook to use graphql to fetch data for the Content Selection components and display in the dropdowns. Add functionality to get correct student/assignment via query params and update the query params when student/assignment changes. Also wire up some of the other components needed for enhanced individual gradebook with placeholder text closes EVAL-3115 flag=individual_gradebook_enhancements test plan: - this change is still a work in progress, but you should be able to see the dropdowns in the enhanced individual gradebook and select a student and assignment. The student and assignment should be reflected in the url query params. The dropdowns should be populated with the correct data from the graphql queries Change-Id: Iefeaaae1c314bd098f0ba9197c8523605cfe78b3 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/318311 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Aaron Shafovaloff <ashafovaloff@instructure.com> QA-Review: Aaron Shafovaloff <ashafovaloff@instructure.com> Product-Review: Aaron Shafovaloff <ashafovaloff@instructure.com>
This commit is contained in:
parent
978d6ec5f1
commit
1f5901032d
|
@ -23,3 +23,4 @@
|
|||
<div>
|
||||
<span data-component="GradebookSelector"></span>
|
||||
</div>
|
||||
<div data-component="EnhancedIndividualGradebook"></div>
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import GradebookMenu from '@canvas/gradebook-menu'
|
||||
import {BrowserRouter, Routes, Route} from 'react-router-dom'
|
||||
import EnhancedIndividualGradebookWrapper from './react/EnhancedIndividualGradebookWrapper'
|
||||
|
||||
ReactDOM.render(
|
||||
<GradebookMenu
|
||||
|
@ -31,3 +33,15 @@ ReactDOM.render(
|
|||
/>,
|
||||
document.querySelector('[data-component="GradebookSelector"]')
|
||||
)
|
||||
|
||||
const matches = window.location.pathname.match(/(.*\/gradebook)/)
|
||||
const baseUrl = (matches && matches[0]) || ''
|
||||
|
||||
ReactDOM.render(
|
||||
<BrowserRouter basename={baseUrl}>
|
||||
<Routes>
|
||||
<Route path="/" element={<EnhancedIndividualGradebookWrapper />} />
|
||||
</Routes>
|
||||
</BrowserRouter>,
|
||||
document.querySelector('[data-component="EnhancedIndividualGradebook"]')
|
||||
)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GRADEBOOK_QUERY = gql`
|
||||
query GradebookQuery($courseId: ID!) {
|
||||
course(id: $courseId) {
|
||||
enrollmentsConnection(filter: {types: StudentEnrollment}) {
|
||||
nodes {
|
||||
user {
|
||||
id: _id
|
||||
name
|
||||
sortableName
|
||||
}
|
||||
}
|
||||
}
|
||||
submissionsConnection {
|
||||
nodes {
|
||||
grade
|
||||
id: _id
|
||||
score
|
||||
assignment {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
}
|
||||
assignmentsConnection {
|
||||
nodes {
|
||||
id: _id
|
||||
name
|
||||
pointsPossible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {AssignmentConnectionResponse, SubmissionConnectionResponse} from '../types'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
type Props = {
|
||||
assignment?: AssignmentConnectionResponse
|
||||
submissions?: SubmissionConnectionResponse[]
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function AssignmentInformation({assignment, submissions}: Props) {
|
||||
return (
|
||||
<View as="div">
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span4">
|
||||
<View as="h2">{I18n.t('Assignment Information')}</View>
|
||||
</View>
|
||||
<View as="div" className="span8 pad-box top-only">
|
||||
<View as="p" className="submission_selection">
|
||||
{I18n.t('Select an assignment to view additional information here.')}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
|
||||
import {AssignmentConnectionResponse, UserConnectionResponse} from '../types'
|
||||
import {View} from '@instructure/ui-view'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
type Props = {
|
||||
assignments: AssignmentConnectionResponse[]
|
||||
students: UserConnectionResponse[]
|
||||
selectedStudentId?: string | null
|
||||
selectedAssignmentId?: string | null
|
||||
onStudentChange: (student?: UserConnectionResponse) => void
|
||||
onAssignmentChange: (assignment?: AssignmentConnectionResponse) => void
|
||||
}
|
||||
|
||||
// TODO: might want a map for quicker lookups
|
||||
type DropDownOption<T> = {
|
||||
id: string
|
||||
name: string
|
||||
data?: T
|
||||
}
|
||||
|
||||
type StudentDropdownOption = DropDownOption<UserConnectionResponse>[]
|
||||
type AssignmentDropdownOption = DropDownOption<AssignmentConnectionResponse>[]
|
||||
|
||||
const DEFAULT_STUDENT_DROPDOWN_TEXT = I18n.t('No Student Selected')
|
||||
const DEFAULT_ASSIGNMENT_DROPDOWN_TEXT = I18n.t('No Assignment Selected')
|
||||
|
||||
const defaultStudentDropdownOptions = {id: '-1', name: DEFAULT_STUDENT_DROPDOWN_TEXT}
|
||||
const defaultAssignmentDropdownOptions = {id: '-1', name: DEFAULT_ASSIGNMENT_DROPDOWN_TEXT}
|
||||
|
||||
export default function ContentSelection({
|
||||
students,
|
||||
assignments,
|
||||
selectedAssignmentId,
|
||||
selectedStudentId,
|
||||
onAssignmentChange,
|
||||
onStudentChange,
|
||||
}: Props) {
|
||||
const [studentDropdownOptions, setStudentDropdownOptions] = useState<StudentDropdownOption>([])
|
||||
const [assignmentDropdownOptions, setAssignmentDropdownOptions] =
|
||||
useState<AssignmentDropdownOption>([])
|
||||
const [selectedStudentIndex, setSelectedStudentIndex] = useState<number>(0)
|
||||
const [selectedAssignmentIndex, setSelectedAssignmentIndex] = useState<number>(0)
|
||||
|
||||
// TOOD: might be ablet to refactor to make simpler
|
||||
useEffect(() => {
|
||||
const studentOptions: StudentDropdownOption = [
|
||||
defaultStudentDropdownOptions,
|
||||
...students.map(student => ({id: student.id, name: student.sortableName, data: student})),
|
||||
]
|
||||
setStudentDropdownOptions(studentOptions)
|
||||
|
||||
if (selectedStudentId) {
|
||||
const studentIndex = studentOptions.findIndex(
|
||||
studentOption => studentOption.id === selectedStudentId
|
||||
)
|
||||
|
||||
if (studentIndex !== -1) {
|
||||
setSelectedStudentIndex(studentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const assignmentOptions: AssignmentDropdownOption = [
|
||||
defaultAssignmentDropdownOptions,
|
||||
...assignments.map(assignment => ({
|
||||
id: assignment.id,
|
||||
name: assignment.name,
|
||||
data: assignment,
|
||||
})),
|
||||
]
|
||||
setAssignmentDropdownOptions(assignmentOptions)
|
||||
|
||||
if (selectedAssignmentId) {
|
||||
const assignmentIndex = assignmentOptions.findIndex(
|
||||
assignmentOption => assignmentOption.id === selectedAssignmentId
|
||||
)
|
||||
|
||||
if (assignmentIndex >= 0) {
|
||||
setSelectedAssignmentIndex(assignmentIndex)
|
||||
}
|
||||
}
|
||||
}, [students, assignments, selectedStudentId, selectedAssignmentId])
|
||||
|
||||
const handleChangeStudent = (event?: React.ChangeEvent<HTMLSelectElement>, newIndex?: number) => {
|
||||
const selectedIndex = (event ? event.target.selectedIndex : newIndex) ?? 0
|
||||
setSelectedStudentIndex(selectedIndex)
|
||||
const selectedStudent = studentDropdownOptions[selectedIndex]?.data
|
||||
onStudentChange(selectedStudent)
|
||||
}
|
||||
|
||||
const handleChangeAssignment = (
|
||||
event?: React.ChangeEvent<HTMLSelectElement>,
|
||||
newIndex?: number
|
||||
) => {
|
||||
const selectedIndex = (event ? event.target.selectedIndex : newIndex) ?? 0
|
||||
setSelectedAssignmentIndex(selectedIndex)
|
||||
const selectedAssignment = assignmentDropdownOptions[selectedIndex]?.data
|
||||
onAssignmentChange(selectedAssignment)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span12">
|
||||
<View as="h2">{I18n.t('Content Selection')}</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View as="div" className="row-fluid pad-box bottom-only">
|
||||
<View as="div" className="span4 text-right-responsive">
|
||||
<label htmlFor="student_select" style={{textAlign: 'right', display: 'block'}}>
|
||||
{I18n.t('Select a student')}
|
||||
</label>
|
||||
</View>
|
||||
<View as="div" className="span8">
|
||||
<select
|
||||
className="student_select"
|
||||
onChange={handleChangeStudent}
|
||||
value={studentDropdownOptions[selectedStudentIndex]?.id}
|
||||
>
|
||||
{studentDropdownOptions.map(option => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<View as="div" className="row-fluid pad-box bottom-only student_navigation">
|
||||
<View as="div" className="span4">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block next_object"
|
||||
disabled={selectedStudentIndex <= 1}
|
||||
onClick={() => handleChangeStudent(undefined, selectedStudentIndex - 1)}
|
||||
>
|
||||
{I18n.t('Previous Student')}
|
||||
</button>
|
||||
</View>
|
||||
<View as="div" className="span4">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block next_object"
|
||||
disabled={selectedStudentIndex >= studentDropdownOptions.length - 1}
|
||||
onClick={() => handleChangeStudent(undefined, selectedStudentIndex + 1)}
|
||||
>
|
||||
{I18n.t('Next Student')}
|
||||
</button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View as="div" className="row-fluid pad-box bottom-only">
|
||||
<View as="div" className="span4 text-right-responsive">
|
||||
<label htmlFor="assignment_select" style={{textAlign: 'right', display: 'block'}}>
|
||||
{I18n.t('Select an assignment')}
|
||||
</label>
|
||||
</View>
|
||||
<View as="div" className="span8">
|
||||
<select
|
||||
className="assignment_select"
|
||||
onChange={handleChangeAssignment}
|
||||
value={assignmentDropdownOptions[selectedAssignmentIndex]?.id}
|
||||
>
|
||||
{assignmentDropdownOptions.map(option => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<View as="div" className="row-fluid pad-box bottom-only assignment_navigation">
|
||||
<View as="div" className="span4">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block next_object"
|
||||
disabled={selectedAssignmentIndex <= 1}
|
||||
onClick={() => handleChangeAssignment(undefined, selectedAssignmentIndex - 1)}
|
||||
>
|
||||
{I18n.t('Previous Assignment')}
|
||||
</button>
|
||||
</View>
|
||||
<View as="div" className="span4">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block next_object"
|
||||
disabled={selectedAssignmentIndex >= assignmentDropdownOptions.length - 1}
|
||||
onClick={() => handleChangeAssignment(undefined, selectedAssignmentIndex + 1)}
|
||||
>
|
||||
{I18n.t('Next Assignment')}
|
||||
</button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {useQuery} from 'react-apollo'
|
||||
import {useSearchParams} from 'react-router-dom'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {View} from '@instructure/ui-view'
|
||||
|
||||
import AssignmentInformation from './AssignmentInformation'
|
||||
import ContentSelection from './ContentSelection'
|
||||
import GlobalSettings from './GlobalSettings'
|
||||
import GradingResults from './GradingResults'
|
||||
import StudentInformation from './StudentInformation'
|
||||
import {
|
||||
AssignmentConnectionResponse,
|
||||
GradebookQueryResponse,
|
||||
SubmissionConnectionResponse,
|
||||
UserConnectionResponse,
|
||||
} from '../types'
|
||||
import {GRADEBOOK_QUERY} from '../queries/Queries'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
const STUDENT_SEARCH_PARAM = 'student'
|
||||
const ASSIGNMENT_SEARCH_PARAM = 'assignment'
|
||||
|
||||
export default function EnhancedIndividualGradebook() {
|
||||
const [submissions, setSubmissions] = useState<SubmissionConnectionResponse[]>([])
|
||||
const [students, setStudents] = useState<UserConnectionResponse[]>([])
|
||||
const [assignments, setAssignments] = useState<AssignmentConnectionResponse[]>([])
|
||||
|
||||
const [selectedStudent, setSelectedStudent] = useState<UserConnectionResponse>()
|
||||
const [selectedAssignment, setSelectedAssignment] = useState<AssignmentConnectionResponse>()
|
||||
const [selectedSubmissions, setSelectedSubmissions] = useState<SubmissionConnectionResponse[]>([])
|
||||
|
||||
const courseId = ENV.GRADEBOOK_OPTIONS?.context_id // TODO: get from somewhere else?
|
||||
const [searchParams, setSearhParams] = useSearchParams()
|
||||
const selectedStudentId = searchParams.get(STUDENT_SEARCH_PARAM)
|
||||
const selectedAssignmentId = searchParams.get(ASSIGNMENT_SEARCH_PARAM)
|
||||
|
||||
const {data, error} = useQuery<GradebookQueryResponse>(GRADEBOOK_QUERY, {
|
||||
variables: {courseId},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
skip: !courseId,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
// TODO: handle error
|
||||
}
|
||||
|
||||
if (data?.course) {
|
||||
const {assignmentsConnection, enrollmentsConnection, submissionsConnection} = data.course
|
||||
|
||||
setAssignments(assignmentsConnection.nodes)
|
||||
setSubmissions(submissionsConnection.nodes)
|
||||
|
||||
const studentEnrollments = enrollmentsConnection.nodes.map(enrollment => enrollment.user)
|
||||
const sortableStudents = studentEnrollments.sort((a, b) => {
|
||||
return a.sortableName.localeCompare(b.sortableName)
|
||||
})
|
||||
setStudents(sortableStudents)
|
||||
}
|
||||
}, [data, error])
|
||||
|
||||
const handleStudentChange = (student?: UserConnectionResponse) => {
|
||||
setSelectedStudent(student)
|
||||
if (student) {
|
||||
searchParams.set(STUDENT_SEARCH_PARAM, student?.id)
|
||||
setSearhParams(searchParams)
|
||||
} else {
|
||||
searchParams.delete(STUDENT_SEARCH_PARAM)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAssignmentChange = (assignment?: AssignmentConnectionResponse) => {
|
||||
setSelectedAssignment(assignment)
|
||||
setSelectedSubmissions(submissions.filter(s => s.assignment.id === assignment?.id))
|
||||
if (assignment) {
|
||||
searchParams.set(ASSIGNMENT_SEARCH_PARAM, assignment?.id)
|
||||
setSearhParams(searchParams)
|
||||
} else {
|
||||
searchParams.delete(ASSIGNMENT_SEARCH_PARAM)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View as="div">
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span12">
|
||||
<View as="h1">{I18n.t('Gradebook: Enhanced Individual View')}</View>
|
||||
{I18n.t(
|
||||
'Note: Grades and notes will be saved automatically after moving out of the field.'
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<GlobalSettings />
|
||||
|
||||
<div className="hr" style={{margin: 10, padding: 10, borderBottom: '1px solid #eee'}} />
|
||||
|
||||
<ContentSelection
|
||||
assignments={assignments}
|
||||
students={students}
|
||||
selectedStudentId={selectedStudentId}
|
||||
selectedAssignmentId={selectedAssignmentId}
|
||||
onStudentChange={handleStudentChange}
|
||||
onAssignmentChange={handleAssignmentChange}
|
||||
/>
|
||||
|
||||
<div className="hr" style={{margin: 10, padding: 10, borderBottom: '1px solid #eee'}} />
|
||||
|
||||
<GradingResults />
|
||||
|
||||
<div className="hr" style={{margin: 10, padding: 10, borderBottom: '1px solid #eee'}} />
|
||||
|
||||
<StudentInformation student={selectedStudent} />
|
||||
|
||||
<div className="hr" style={{margin: 10, padding: 10, borderBottom: '1px solid #eee'}} />
|
||||
|
||||
<AssignmentInformation assignment={selectedAssignment} submissions={selectedSubmissions} />
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import {ApolloProvider, createClient} from '@canvas/apollo'
|
||||
import LoadingIndicator from '@canvas/loading-indicator'
|
||||
import EnhancedIndividualGradebook from './EnhancedIndividualGradebook'
|
||||
|
||||
export default function EnhancedIndividualGradebookWrapper() {
|
||||
const [client, setClient] = useState<any>(null) // TODO: remove <any>
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const setupApolloClient = async () => {
|
||||
// TODO: Properly set up cache
|
||||
// const cache = await createPersistentCache([cache_key])
|
||||
// setClient(createClient({cache}))
|
||||
setClient(createClient())
|
||||
setLoading(false)
|
||||
}
|
||||
setupApolloClient()
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return <LoadingIndicator />
|
||||
}
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<EnhancedIndividualGradebook />
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {View} from '@instructure/ui-view'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
export default function GlobalSettings() {
|
||||
return (
|
||||
<>
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span12">
|
||||
<h2>{I18n.t('Global Settings')}</h2>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span4">
|
||||
<label htmlFor="section_select" style={{textAlign: 'right', display: 'block'}}>
|
||||
{I18n.t('Select a section')}
|
||||
</label>
|
||||
</View>
|
||||
<View as="div" className="span8">
|
||||
{/* TODO: Get Sections */}
|
||||
<select id="section_select" className="section_select">
|
||||
<option value="all">{I18n.t('All Sections')}</option>
|
||||
<option value="1">Section 1</option>
|
||||
</select>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<div className="row-fluid" style={{paddingBottom: 20}}>
|
||||
<View as="div" className="span4">
|
||||
<label htmlFor="sort_select" style={{textAlign: 'right', display: 'block'}}>
|
||||
{I18n.t('Sort Assignments')}
|
||||
</label>
|
||||
</View>
|
||||
<View as="div" className="span8">
|
||||
<select id="sort_select" className="section_select" defaultValue="alpha">
|
||||
<option value="assignment_group">
|
||||
{I18n.t('assignment_order_assignment_groups', 'By Assignment Group and Position')}
|
||||
</option>
|
||||
<option value="alpha">{I18n.t('assignment_order_alpha', 'Alphabetically')}</option>
|
||||
<option value="due_date">{I18n.t('assignment_order_due_date', 'By Due Date')}</option>
|
||||
</select>
|
||||
</View>
|
||||
</div>
|
||||
|
||||
<View as="div" className="row-fluid pad-box bottom-only">
|
||||
<View as="div" className="span4">
|
||||
{/* {{!-- Intentionally left empty so this scales to smaller screens --}} */}
|
||||
</View>
|
||||
<View as="div" className="span7">
|
||||
<div
|
||||
className="checkbox"
|
||||
style={{padding: 12, margin: '10px 0px', background: '#eee', borderRadius: 5}}
|
||||
>
|
||||
<label className="checkbox" htmlFor="ungraded_checkbox">
|
||||
<input type="checkbox" id="ungraded_checkbox" name="ungraded_checkbox" />
|
||||
{I18n.t('View Ungraded as 0')}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="checkbox"
|
||||
style={{padding: 12, margin: '10px 0px', background: '#eee', borderRadius: 5}}
|
||||
>
|
||||
<label className="checkbox" htmlFor="hide_names_checkbox">
|
||||
<input type="checkbox" id="hide_names_checkbox" name="hide_names_checkbox" />
|
||||
{I18n.t('Hide Student Names')}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="checkbox"
|
||||
style={{padding: 12, margin: '10px 0px', background: '#eee', borderRadius: 5}}
|
||||
>
|
||||
<label className="checkbox" htmlFor="concluded_enrollments_checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="concluded_enrollments_checkbox"
|
||||
name="concluded_enrollments_checkbox"
|
||||
/>
|
||||
{I18n.t('Show Concluded Enrollments')}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="checkbox"
|
||||
style={{padding: 12, margin: '10px 0px', background: '#eee', borderRadius: 5}}
|
||||
>
|
||||
<label className="checkbox" htmlFor="show_notes_checkbox">
|
||||
<input type="checkbox" id="show_notes_checkbox" name="show_notes_checkbox" />
|
||||
{I18n.t('Show Notes in Student Info')}
|
||||
</label>
|
||||
</div>
|
||||
{/* {{#if finalGradeOverrideEnabled}}
|
||||
<View as="div" className="checkbox">
|
||||
<label className="checkbox">
|
||||
{{
|
||||
input
|
||||
type="checkbox"
|
||||
id="allow_final_grade_override"
|
||||
name="allow_final_grade_override"
|
||||
checked=allowFinalGradeOverride
|
||||
}}
|
||||
{{#t}}Allow Final Grade Override{{/t}}
|
||||
</label>
|
||||
</View>
|
||||
{{/if}} */}
|
||||
{/* {{#unless gradesAreWeighted}}
|
||||
<View as="div" className="checkbox">
|
||||
<label className="checkbox">
|
||||
{{
|
||||
input
|
||||
type="checkbox"
|
||||
id="show_total_as_points"
|
||||
name="show_total_as_points"
|
||||
checked=showTotalAsPoints
|
||||
}}
|
||||
{{#t "show_total_as_points"}}Show Totals as Points on Student Grade Page{{/t}}
|
||||
</label>
|
||||
</View>
|
||||
{{/unless}} */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span4">
|
||||
{/* {{!-- Intentionally left empty so this scales to smaller screens --}} */}
|
||||
</View>
|
||||
<View as="div" className="span8">
|
||||
<View as="div" className="pad-box bottom-only">
|
||||
<button type="button" className="btn" id="gradebook-export">
|
||||
<i className="icon-download" />
|
||||
{I18n.t('Download Current Scores (.csv)')}
|
||||
</button>
|
||||
{/* {{#if lastGeneratedCsvAttachmentUrl}}
|
||||
<a aria-label="{{unbound lastGeneratedCsvLabel}}" href="{{unbound lastGeneratedCsvAttachmentUrl}}" id="last-exported-gradebook">
|
||||
{{unbound lastGeneratedCsvLabel}}
|
||||
</a>
|
||||
{{/if}} */}
|
||||
</View>
|
||||
|
||||
<View as="div" className="pad-box bottom-only">
|
||||
<a id="upload" className="btn" href="{{unbound uploadCsvUrl}}">
|
||||
<i className="icon-upload" />
|
||||
{I18n.t('Upload Scores (.csv)')}
|
||||
</a>
|
||||
</View>
|
||||
{/* <iframe style="display:none" id="gradebook-export-iframe"></iframe> */}
|
||||
<View as="div" className="pad-box bottom-only">
|
||||
<View as="div">
|
||||
{/* {{#if publishToSisEnabled}}
|
||||
<a href="{{ unbound publishToSisURL }}">
|
||||
{{#t}}Sync grades to SIS{{/t}}
|
||||
</a>
|
||||
{{/if}} */}
|
||||
</View>
|
||||
<View as="div">
|
||||
<a href="{{ unbound gradingHistoryUrl }}">{I18n.t('View Gradebook History')}</a>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
export default function GradingResults() {
|
||||
return (
|
||||
<>
|
||||
<View as="div">
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span4">
|
||||
<View as="h2">{I18n.t('Grading')}</View>
|
||||
</View>
|
||||
<View as="div" className="span8 pad-box top-only">
|
||||
<View as="p" className="submission_selection">
|
||||
{I18n.t('Select a student and an assignment to view and edit grades.')}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
|
||||
import {UserConnectionResponse} from '../types'
|
||||
|
||||
const I18n = useI18nScope('enhanced_individual_gradebook')
|
||||
|
||||
type Props = {
|
||||
student?: UserConnectionResponse
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function StudentInformation({student}: Props) {
|
||||
return (
|
||||
<View as="div">
|
||||
<View as="div" className="row-fluid">
|
||||
<View as="div" className="span4">
|
||||
<View as="h2">{I18n.t('Student Information')}</View>
|
||||
</View>
|
||||
<View as="div" className="span8 pad-box top-only">
|
||||
<View as="p" className="submission_selection">
|
||||
{I18n.t('Select a student to view additional information here.')}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Temporarily generate types for GraphQL queries until we have automated generation setup
|
||||
*/
|
||||
|
||||
export type UserConnectionResponse = {
|
||||
enrollments: {
|
||||
section: {
|
||||
name: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
email: string
|
||||
id: string
|
||||
loginId: string
|
||||
name: string
|
||||
sortableName: string
|
||||
}
|
||||
|
||||
export type AssignmentConnectionResponse = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type SubmissionConnectionResponse = {
|
||||
assignment: {
|
||||
id: string
|
||||
}
|
||||
user: UserConnectionResponse
|
||||
id: string
|
||||
score: number
|
||||
grade: string
|
||||
}
|
||||
|
||||
export type GradebookQueryResponse = {
|
||||
course: {
|
||||
enrollmentsConnection: {
|
||||
nodes: {
|
||||
user: UserConnectionResponse
|
||||
}[]
|
||||
}
|
||||
submissionsConnection: {
|
||||
nodes: SubmissionConnectionResponse[]
|
||||
}
|
||||
assignmentsConnection: {
|
||||
nodes: AssignmentConnectionResponse[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UserSubmissionMap = {
|
||||
[userId: string]: {
|
||||
email: string
|
||||
submissions: {
|
||||
[assignmentId: string]: {
|
||||
score: number
|
||||
grade: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue