diff --git a/.eslintrc.json b/.eslintrc.json index 16aa149049..f38ca3d320 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,6 +35,7 @@ "no-unused-vars": [ 0 ], - "no-console": "off" + "no-console": "off", + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"] } } diff --git a/package.json b/package.json index ce13f9cc2d..70f3c13722 100644 --- a/package.json +++ b/package.json @@ -17,51 +17,59 @@ "author": "", "license": "GPL", "dependencies": { - "babel-core": "^6.2.1", - "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.1.18", - "babel-preset-react": "^6.1.18", + "axios": "^0.15.3", + "babel-core": "^6.21.0", + "babel-loader": "^6.2.10", + "babel-preset-es2015": "^6.18.0", + "babel-preset-react": "^6.16.0", "crypto": "0.0.3", "express": "^4.14.0", "express-urlrewrite": "^1.2.0", - "feathers-authentication": "^0.7.11", - "feathers-authentication-client": "^0.1.1", - "feathers-client": "^1.7.2", - "feathers-hooks": "^1.6.1", - "feathers-subscriptions-manager": "^1.0.1", + "feathers-authentication": "^1.0.2", + "feathers-authentication-client": "^0.1.6", + "feathers-client": "^1.9.0", + "feathers-hooks": "^1.7.1", "feathers-reactive": "^0.4.1", - "oauth-1.0a": "^2.0.0", - "jquery": "^3.1.1", - "react": "^0.14.8", - "react-dom": "^0.14.8", - "react-komposer": "^1.13.1", - "react-player": "^0.12.0", - "react-router": "^3.0.0", - "react-s-alert": "^1.2.1", + "feathers-subscriptions-manager": "^1.0.1", + "formsy-react": "^0.19.0", + "formsy-react-components": "^0.9.0", + "jquery": "^3.1.1", + "oauth-1.0a": "^2.0.0", + "rc-pagination": "^1.6.5", + "rc-select": "^6.7.1", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-dropzone": "^3.9.0", + "react-komposer": "^2.0.0", + "react-player": "^0.14.1", + "react-router": "^3.0.1", + "react-router-scroll": "^0.4.1", + "react-s-alert": "^1.2.2", + "react-select": "^1.0.0-rc.2", "react-tabs": "^0.8.2", - "rxjs": "^5.0.0-rc.4", - "serve-favicon": "^2.3.0", - "socket.io-client": "^1.5.1" + "rxjs": "^5.0.3", + "serve-favicon": "^2.3.2", + "socket.io-client": "^1.7.2" }, "devDependencies": { - "babel-polyfill": "^6.16.0", - "css-loader": "^0.25.0", - "eslint": "^3.8.1", - "eslint-plugin-react": "^6.4.1", + "babel-polyfill": "^6.20.0", + "css-loader": "^0.26.1", + "eslint": "^3.13.1", + "eslint-plugin-react": "^6.9.0", "exports-loader": "^0.6.3", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.9.0", - "imports-loader": "^0.6.5", + "imports-loader": "^0.7.0", "json-loader": "^0.5.4", - "mocha": "^3.1.2", - "node-sass": "^3.10.1", - "postcss-loader": "^1.0.0", - "request": "^2.76.0", - "resolve-url-loader": "^1.6.0", - "sass-loader": "^4.0.2", + "mocha": "^3.2.0", + "node-sass": "^4.3.0", + "postcss-loader": "^1.2.2", + "request": "^2.79.0", + "resolve-url-loader": "^1.6.1", + "sass-loader": "^4.1.1", "style-loader": "^0.13.1", - "tether": "^1.3.7", + "tether": "^1.4.0", "url-loader": "^0.5.7", - "webpack": "^1.13.2" + "webpack": "^1.14.0" } } diff --git a/src/app.js b/src/app.js index 41c2cdaf86..807152464f 100644 --- a/src/app.js +++ b/src/app.js @@ -16,7 +16,7 @@ const _init = () => { }; // Wait for server to tell us whether logged in (then) or not (catch) -Server.authenticate().then(() => { +Server.authenticateUser().then(() => { _init(); }).catch(() => { _init(); diff --git a/src/modules/active-students/components/active-students.jsx b/src/modules/active-students/components/active-students.jsx index 10034c94eb..b48b33239f 100644 --- a/src/modules/active-students/components/active-students.jsx +++ b/src/modules/active-students/components/active-students.jsx @@ -1,5 +1,5 @@ -import LayoutBackend from '../../backend/containers/layout'; -import SectionTitle from '../../backend/components/title'; /* only for backend */ +import LayoutBase from '../../base/containers/layout'; +import SectionTitle from '../../base/components/title'; /* only for base */ import List from './list'; @@ -21,10 +21,10 @@ class ActiveStudents extends React.Component { render() { return ( - + - + ); } diff --git a/src/modules/administration/actions/administration.js b/src/modules/administration/actions/administration.js index 3c20aad73a..e43b61c06a 100644 --- a/src/modules/administration/actions/administration.js +++ b/src/modules/administration/actions/administration.js @@ -1,56 +1,69 @@ import { Permissions, Server } from '../../core/helpers/'; -const classService = Server.service('/classes'); -const courseService = Server.service('/courses'); -const schoolService = Server.service('/schools'); const userService = Server.service('/users'); +const classService = Server.service('/classes'); -export default { - updateSchool: (schoolId, data) => { - schoolService.patch(schoolId, data); - }, - - - updateCourse: (data) => { - if(data._id) return courseService.update(data._id, data); - - return courseService.create(data); - }, - - removeCourse: (data) => { - return courseService.remove(data._id); - }, - +const indexArrayByKey = (array, key) => { + const result = {}; + array.forEach((obj) => { + result[obj[key]] = obj; + }); + return result; +}; - updateClass: (data) => { - if(data._id) return classService.update(data._id, data); +export default { - return classService.create(data); + loadContent: (serviceName, query) => { + const service = Server.service(serviceName); + return service.find({query}) + .then(result => { + return Promise.resolve({ + records: indexArrayByKey(result.data, '_id'), + pagination: {total: result.total, skip: result.skip} + }); + }); }, - removeClass: (data) => { - return classService.remove(data._id); + updateRecord: (serviceName, data) => { + const service = Server.service(serviceName); + if(data._id) return service.patch(data._id, data); + return service.create(data); }, - - updateStudent: (data) => { - if(data._id) return userService.update(data._id, data); - - return userService.create(data); + removeRecord: (serviceName, data) => { + const id = data._id; + if(!id) throw new Error("_id not set!"); + const service = Server.service(serviceName); + return service.remove(id); }, - removeStudent: (data) => { - return userService.remove(data._id); + populateFields: (serviceName, _id, fields) => { + const service = Server.service(serviceName); + return service.find({query: { + _id, + $populate: fields + }}) + .then(result => Promise.resolve(result.data[0])); }, - - updateTeacher: (data) => { - if(data._id) return userService.update(data._id, data); - - return userService.create(data); + _loadTeachers: (schoolId) => { + return userService.find({ + query: { + schoolId, + roles: ['teacher'], + $limit: 1000 + } + }) + .then(result => Promise.resolve(result.data)); }, - removeTeacher: (data) => { - return userService.remove(data._id); + _loadClasses: (schoolId) => { + return classService.find({ + query: { + schoolId, + $limit: 1000 + } + }) + .then(result => Promise.resolve(result.data)); } }; diff --git a/src/modules/administration/components/admin-section.jsx b/src/modules/administration/components/admin-section.jsx index 9ddc297674..08db9ab113 100644 --- a/src/modules/administration/components/admin-section.jsx +++ b/src/modules/administration/components/admin-section.jsx @@ -1,4 +1,8 @@ import ReactDOM from 'react-dom'; +import Pagination from 'rc-pagination'; +import Select from 'rc-select'; +import '../styles/rc-pagination.scss'; +import '../styles/rc-select.scss'; import ModalForm from './modal-form'; import Table from './table'; @@ -12,50 +16,42 @@ class AdminSection extends React.Component { title: '', addLabel: '', editLabel: '', - submitCallback: () => {} - } + }; this.state = { - record: {} + record: {}, + records: [], + numberOfPages: 1, + itemsPerPage: 10, }; this.defaultRecord = {}; + this.loadContentFromServer = null; + this.serviceName = null; } - - handleRecordChange(e) { - let el = e.target; - let name = el.name; - let type = el.type; - let record = this.state.record; - - if (type == 'select-multiple') { - let selectedOptions = []; - for (let i = 0, l = el.options.length; i < l; i++) { - if (el.options[i].selected) { - selectedOptions.push(el.options[i].value); - } - } - record[name] = selectedOptions; - } else { - record[name] = el.value; - } - - this.setState({record}); + componentDidMount() { + this.loadContent(1, this.state.itemsPerPage); } + // return the query passed to actions.loadContent along with pagination options, e.g. {schoolId: 123456} + contentQuery() { + throw new TypeError("contentQuery() has to be implemented by AdminSection subclasses."); + } modalFormUI(record) { - return; + throw new Error("modalFormUI() has to be implemented by AdminSection."); } modalUI() { const title = this.state.record.name != '' ? this.options.editLabel : this.options.addLabel; return ( ); } @@ -68,23 +64,25 @@ class AdminSection extends React.Component { } getTableHead() { - return []; + throw new Error("getTableHead() has to be implemented by AdminSection."); } getTableBody() { - return []; + throw new Error("getTableBody() has to be implemented by AdminSection."); } getTableActions(actions, record) { return ( -
+
{actions.map((action, index) => { return ( - + ); })} @@ -92,6 +90,77 @@ class AdminSection extends React.Component { ); } + onPageSizeChange(currentPage, itemsPerPage) { + this.setState({itemsPerPage}); + this.loadContent(1, itemsPerPage); + } + + loadContent(page, itemsPerPage) { + const paginationOptions = {$skip: (page - 1) * itemsPerPage, $limit: itemsPerPage}; + const query = Object.assign({}, paginationOptions, this.contentQuery()); + this.loadContentFromServer(query) + .then((result) => { + const numberOfPages = Math.ceil(result.pagination.total / this.state.itemsPerPage); + Object.assign(result, {numberOfPages}); + this.setState(result); + }); + } + + loadTeachers() { + this.props.actions.loadTeachers() + .then(teachers => this.setState({teachers})); + } + + loadClasses() { + this.props.actions.loadClasses() + .then(classes => this.setState({classes})); + } + + onPageChange(page) { + this.loadContent(page, this.state.itemsPerPage); + } + + updateRecord(data) { + console.info(`Replacing \n${JSON.stringify(this.state.records[data._id])} with \n${JSON.stringify(data)}`); + this.props.actions.updateRecord(this.serviceName, data) + .then(this.customizeRecordBeforeInserting.bind(this)) + .then(savedData => { + let records = this.state.records; + records[data._id] = savedData; + this.setState({records}); + }); + } + + // override point to customize records before they are inserted into the table, + // e.g. to populate fields (resolve ids) + customizeRecordBeforeInserting(data) { + return Promise.resolve(data); + } + + removeRecord(data) { + this.props.actions.removeRecord(this.serviceName, data) + .then(_ => { + let records = this.state.records; + delete records[data._id]; + this.setState({records}); + }); + } + + getPaginationControl() { + //if (this.state.numberOfPages < 2) return null; + return (); + } + render() { return (
@@ -101,13 +170,13 @@ class AdminSection extends React.Component {
{this.options.title}
+ {this.getPaginationControl()} - {this.modalUI()} ); diff --git a/src/modules/administration/components/administration.jsx b/src/modules/administration/components/administration.jsx index 2faad24abe..2ad41eaf1f 100755 --- a/src/modules/administration/components/administration.jsx +++ b/src/modules/administration/components/administration.jsx @@ -1,12 +1,12 @@ -import LayoutBackend from '../../backend/containers/layout'; -import SectionTitle from '../../backend/components/title'; /* only for backend */ +import LayoutBase from '../../base/containers/layout'; +import SectionTitle from '../../base/components/title'; /* only for base */ import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; -import SectionSchool from './school'; -import SectionCourses from './courses'; -import SectionClasses from './classes'; -import SectionTeachers from './teachers'; -import SectionStudents from './students'; +import SectionSchool from '../containers/school'; +import SectionCourses from '../containers/courses'; +import SectionClasses from '../containers/classes'; +import SectionTeachers from '../containers/teachers'; +import SectionStudents from '../containers/students'; require('../styles/administration.scss'); @@ -19,7 +19,7 @@ class Administration extends React.Component { render() { return ( - + @@ -37,7 +37,7 @@ class Administration extends React.Component { - + ); } diff --git a/src/modules/administration/components/classes.jsx b/src/modules/administration/components/classes.jsx index 747795cb05..23f8b4007f 100644 --- a/src/modules/administration/components/classes.jsx +++ b/src/modules/administration/components/classes.jsx @@ -1,25 +1,21 @@ +import { + Input, + ReactSelect +} from '../../core/helpers/form'; + import AdminSection from './admin-section'; -import ModalForm from './modal-form'; -import Table from './table'; class SectionClasses extends AdminSection { constructor(props) { super(props); - this.options = { + const options = { title: 'Klassen', addLabel: 'Klasse hinzufügen', - editLabel: 'Klasse bearbeiten', - submitCallback: (data) => { - this.props.actions.updateClass(data); - } - }; - - this.defaultRecord = { - name: '', - schoolId: this.props.school._id + editLabel: 'Klasse bearbeiten' }; + Object.assign(this.options, options); this.actions = [ { @@ -27,69 +23,108 @@ class SectionClasses extends AdminSection { icon: 'edit' }, { - action: this.removeRecord.bind(this), + action: this.removeRecord, icon: 'trash-o' } - ] + ]; + + Object.assign(this.state, { + teachers: [], + classes: [] + }); + + this.loadContentFromServer = this.props.actions.loadContent.bind(this, '/classes'); + this.serviceName = '/classes'; } - modalFormUI() { - const record = this.state.record; - return ( -
-
- - -
- -
- - -
-
- ); + componentDidMount() { + super.componentDidMount(); + this.loadTeachers(); } - removeRecord(record) { - this.props.actions.removeClass(record); + contentQuery() { + const schoolId = this.props.schoolId; + return { + schoolId, + $populate: ['teacherIds'] + }; } getTableHead() { return [ - 'ID', 'Bezeichnung', - 'Erstellt am', + 'Lehrer', '' ]; } + customizeRecordBeforeInserting(data) { + return this.props.actions.populateFields('/classes', data._id, ['teacherIds']); + } + getTableBody() { - return this.props.classes.map((c) => { + return Object.keys(this.state.records).map((id) => { + const c = this.state.records[id]; return [ - c._id, c.name, - c.createdAt, + c.teacherIds.map(teacher => teacher.lastName).join(', '), this.getTableActions(this.actions, c) ]; }); } + + getTeacherOptions() { + return this.state.teachers.map((r) => { + return { + label: `${r.firstName || r._id} ${r.lastName || ""}`, + value: r._id + }; + }); + } + + modalFormUI() { + const record = this.state.record; + + return ( +
+ + + + + + + +
+ ); + } } export default SectionClasses; diff --git a/src/modules/administration/components/courses.jsx b/src/modules/administration/components/courses.jsx index b6ec16e477..dbb03f25b6 100644 --- a/src/modules/administration/components/courses.jsx +++ b/src/modules/administration/components/courses.jsx @@ -1,26 +1,21 @@ +import { + Input, + ReactSelect +} from '../../core/helpers/form'; + import AdminSection from './admin-section'; -import ModalForm from './modal-form'; -import Table from './table'; class SectionCourses extends AdminSection { constructor(props) { super(props); - this.options = { + const options = { title: 'Kurse', addLabel: 'Kurs hinzufügen', editLabel: 'Kurs bearbeiten', - submitCallback: (data) => { - this.props.actions.updateCourse(data); - } - }; - - this.defaultRecord = { - name: '', - schoolId: this.props.school._id, - classId: '58407f3f8fd94f15f984ab03' // TODO: no _id }; + Object.assign(this.options, options); this.actions = [ { @@ -28,98 +23,128 @@ class SectionCourses extends AdminSection { icon: 'edit' }, { - action: this.removeRecord.bind(this), + action: this.removeRecord, icon: 'trash-o' } - ] + ]; + + Object.assign(this.state, {teachers: [], classes: []}); + + this.loadContentFromServer = this.props.actions.loadContent.bind(this, '/courses'); + this.serviceName = '/courses'; } - modalFormUI(courseId) { - const record = this.state.record; - return ( -
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
- ); + componentDidMount() { + super.componentDidMount(); + this.loadTeachers(); + this.loadClasses(); } - removeRecord(record) { - this.props.actions.removeCourse(record); + contentQuery() { + const schoolId = this.props.schoolId; + return { + schoolId, + $populate: ['classIds', 'teacherIds'] + }; + } + + customizeRecordBeforeInserting(data) { + return this.props.actions.populateFields('/courses', data._id, ['classIds', 'teacherIds']); } getTableHead() { return [ - 'ID', 'Name', 'Klasse(n)', - 'Erstellt am', + 'Lehrer', '' ]; } getTableBody() { - return this.props.courses.map((c) => { + return Object.keys(this.state.records).map((id) => { + const c = this.state.records[id]; return [ - c._id, c.name, - c.classId, - c.createdAt, + (c.classIds || []).map(cl => cl.name).join(', '), + c.teacherIds.map(teacher => teacher.lastName).join(', '), this.getTableActions(this.actions, c) ]; }); } + + getTeacherOptions() { + return this.state.teachers.map((r) => { + return { + label: `${r.firstName || r._id} ${r.lastName || ""}`, + value: r._id + }; + }); + } + + getClassOptions() { + return this.state.classes.map((r) => { + return { + label: r.name || r._id, + value: r._id + }; + }); + } + + modalFormUI(courseId) { + const record = this.state.record; + return ( +
+ + + + + + + + + +
+ ); + } } export default SectionCourses; diff --git a/src/modules/administration/components/modal-form.jsx b/src/modules/administration/components/modal-form.jsx index 23bb6c4f20..ea3a8be4f7 100644 --- a/src/modules/administration/components/modal-form.jsx +++ b/src/modules/administration/components/modal-form.jsx @@ -1,4 +1,5 @@ import ReactDOM from 'react-dom'; +import {Form} from '../../core/helpers/form'; class ModalForm extends React.Component { @@ -6,49 +7,67 @@ class ModalForm extends React.Component { super(props); } + componentDidMount() { + this.modal = $(ReactDOM.findDOMNode(this)); + } + + onSubmit(data) { + this.props.submitCallback(data); + this.modal.modal('hide'); + } + + onClose() { + this.props.closeCallback(); + this.modal.modal('hide'); + } + render() { - let closeIcon = ''; - let closeButton = ''; + let closeIcon; + let closeButton; + if(this.props.closable) { closeIcon = ( ); closeButton = ( - + ); } return ( - + + + + + ); + } + + render() { + return ( +
+
Schüler
+
{student.firstName}{student.lastName}
+ + + + + + + + + { + this.students.map(student => { + return this.getStudentUI(student); + }) + } + +
ProfilbildVornameNachname
+
    +
+
+ ); + } + +} + +export default SectionStudents; diff --git a/src/modules/courses/components/teachers.jsx b/src/modules/courses/components/teachers.jsx new file mode 100644 index 0000000000..6810c1cd09 --- /dev/null +++ b/src/modules/courses/components/teachers.jsx @@ -0,0 +1,20 @@ +require('../styles/course.scss'); + +class SectionTeachers extends React.Component { + + constructor(props) { + super(props); + this.teachers = this.props.course.teacherIds; + } + + render() { + return ( +
+ Lehrer: { this.teachers.map(teacher => teacher.lastName).join(", ") } +
+ ); + } + +} + +export default SectionTeachers; diff --git a/src/modules/courses/components/tools.jsx b/src/modules/courses/components/tools.jsx new file mode 100644 index 0000000000..bfbbf514b9 --- /dev/null +++ b/src/modules/courses/components/tools.jsx @@ -0,0 +1,21 @@ +require('../styles/course.scss'); +import Tools from '../../tools/containers/tools'; + + +class SectionTools extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+ +
+ ); + } + +} + +export default SectionTools; diff --git a/src/modules/courses/containers/course.js b/src/modules/courses/containers/course.js new file mode 100644 index 0000000000..f83f945764 --- /dev/null +++ b/src/modules/courses/containers/course.js @@ -0,0 +1,43 @@ +import {render} from 'react-dom'; +import {compose} from 'react-komposer'; +import {browserHistory} from 'react-router'; + +import component from '../components/course'; +import { Server, Notification } from '../../core/helpers/'; +import actions from '../actions/course'; + +const coursesService = Server.service('/courses'); + +const containsId = (array, value) => { + return array.filter(entry => entry._id == value).length > 0; +}; + +const composer = (props, onData) => { + + let currentUser = Server.get("user"); + + coursesService.find({query: { + _id: props.params.id, + $populate: ['ltiToolIds', 'userIds', 'teacherIds', 'classId'] + }}).then(res => { + + let course = res.data[0]; + + if (!containsId(course.userIds, currentUser._id) && !containsId(course.teacherIds, currentUser._id)) { + onData(new Error('You don\'t have the permission to see this page.')); + return; + } + + let componentData = { + actions, + course: course + }; + + onData(null, componentData); + }).catch(err => { + Notification.showError("Kurs wurde nicht gefunden"); + browserHistory.push("/courses/"); + }); +}; + +export default compose(composer)(component); diff --git a/src/modules/courses/containers/courses.js b/src/modules/courses/containers/courses.js new file mode 100644 index 0000000000..68b2ffb153 --- /dev/null +++ b/src/modules/courses/containers/courses.js @@ -0,0 +1,29 @@ +import {render} from 'react-dom'; +import {compose} from 'react-komposer'; + +import component from '../components/courses'; +import { Server, Notification } from '../../core/helpers/'; +import actions from '../actions/courses'; + +const coursesService = Server.service('/courses'); + +const composer = (props, onData) => { + + const currentUser = Server.get('user'); + coursesService.find({ + query: { + $or: [{teacherIds: currentUser._id}, {userIds: currentUser._id}] + } + }).then(res => { + let componentData = { + actions, + courses: res.data + }; + + onData(null, componentData); + }).catch(err => { + Notification.showError(err.message); + }); +}; + +export default compose(composer)(component); diff --git a/src/modules/error-page/index.js b/src/modules/courses/index.js similarity index 100% rename from src/modules/error-page/index.js rename to src/modules/courses/index.js diff --git a/src/modules/courses/routes.jsx b/src/modules/courses/routes.jsx new file mode 100644 index 0000000000..dbbf104f87 --- /dev/null +++ b/src/modules/courses/routes.jsx @@ -0,0 +1,15 @@ +import Courses from './containers/courses'; +import Course from './containers/course'; + +export default [ + { + path: '/courses/', + name: 'courses', + component: Courses + }, + { + path: '/courses/:id', + name: 'course', + component: Course + } +]; diff --git a/src/modules/courses/styles/course.scss b/src/modules/courses/styles/course.scss new file mode 100644 index 0000000000..d8b930857c --- /dev/null +++ b/src/modules/courses/styles/course.scss @@ -0,0 +1,47 @@ +@import '../../core/styles/colors.scss'; + +.route-course { + .course-section { + margin: 10px; + width: 100%; + float: left; + padding-bottom: 20px; + } + + .course-subsection { + padding: 20px !important; + + &.left { + border-right: 2px $colorGrey solid; + } + + &.teachers { + .tag { + margin-right: 2px; + } + } + } + + table { + width: 100%; + border-collapse: separate; + border-spacing: 0px 2px; + + th { + text-align: center; + } + + td { + padding-top: 10px; + text-align: center; + color: grey; + background-color: whitesmoke; + } + .picture { + border-radius: 50%; + height: 32px; + width: 32px; + margin-right: 10px; + } + } +} diff --git a/src/modules/courses/styles/courseCard.scss b/src/modules/courses/styles/courseCard.scss new file mode 100644 index 0000000000..985ba2ab03 --- /dev/null +++ b/src/modules/courses/styles/courseCard.scss @@ -0,0 +1,10 @@ +@import '../../core/styles/colors.scss'; + +.course-card { + cursor: pointer; + margin-top: 15px; + .card-img-top { + max-width:100%; + height:auto; + } +} diff --git a/src/modules/courses/styles/courses.scss b/src/modules/courses/styles/courses.scss new file mode 100644 index 0000000000..20cd590f69 --- /dev/null +++ b/src/modules/courses/styles/courses.scss @@ -0,0 +1,12 @@ +@import '../../core/styles/colors.scss'; + +.courses { + .courses-section { + width: 100%; + float: left; + padding-bottom: 20px; + } + .btn-courses { + margin-bottom: 10px; + } +} diff --git a/src/modules/dashboard/components/dashboard.jsx b/src/modules/dashboard/components/dashboard.jsx index 3c183223c2..f623846de4 100755 --- a/src/modules/dashboard/components/dashboard.jsx +++ b/src/modules/dashboard/components/dashboard.jsx @@ -1,5 +1,5 @@ -import LayoutBackend from '../../backend/containers/layout'; -import SectionTitle from '../../backend/components/title'; +import LayoutBase from '../../base/containers/layout'; +import SectionTitle from '../../base/components/title'; import SectionTimetable from '../../timetable/components/table'; import SectionControls from '../../timetable/components/controls'; import SectionTools from './tools'; @@ -54,7 +54,7 @@ class Dashboard extends React.Component { }]; return ( - + @@ -62,7 +62,7 @@ class Dashboard extends React.Component { - + ); } diff --git a/src/modules/dashboard/containers/dashboard.js b/src/modules/dashboard/containers/dashboard.js index 83fcd2f849..6a8f4a247a 100644 --- a/src/modules/dashboard/containers/dashboard.js +++ b/src/modules/dashboard/containers/dashboard.js @@ -16,6 +16,13 @@ const composer = (props, onData) => { return; } + if(!(currentUser.preferences || {}).finishedSignup + && Permissions.userHasPermission(currentUser, permissions.ADMIN_VIEW) + ) { + browserHistory.push('/signup/school/'); + return; + } + let componentData = { actions }; diff --git a/src/modules/dashboard/styles/messages.scss b/src/modules/dashboard/styles/messages.scss index 12be9ab35f..46f1867171 100644 --- a/src/modules/dashboard/styles/messages.scss +++ b/src/modules/dashboard/styles/messages.scss @@ -3,9 +3,7 @@ .section-messages { width:100%; float:left; - border-top:2px solid #696969; - border-bottom:2px solid #696969; - padding: 15px 0px 50px; + padding: 50px 0px 30px; line-height: 1.75rem; h5 { diff --git a/src/modules/dashboard/styles/tasks.scss b/src/modules/dashboard/styles/tasks.scss index 15680960ac..f083341684 100644 --- a/src/modules/dashboard/styles/tasks.scss +++ b/src/modules/dashboard/styles/tasks.scss @@ -3,7 +3,6 @@ .section-tasks { width:100%; float:left; - border-bottom:2px solid #696969; padding: 15px 0px 50px; line-height: 1.75rem; diff --git a/src/modules/dashboard/styles/tools.scss b/src/modules/dashboard/styles/tools.scss index 4561854bdd..2c8d1b3db3 100644 --- a/src/modules/dashboard/styles/tools.scss +++ b/src/modules/dashboard/styles/tools.scss @@ -3,7 +3,6 @@ .section-tools { width:100%; float:left; - border-bottom:2px solid #696969; padding: 15px 0px 50px; line-height: 1.75rem; diff --git a/src/modules/error-page/components/error-page.jsx b/src/modules/error-page/components/error-page.jsx deleted file mode 100644 index 0d898963a2..0000000000 --- a/src/modules/error-page/components/error-page.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import LayoutStatic from '../../static/components/layout'; -import Forbidden from './forbidden'; -import NotFound from './notfound'; - -require('../styles/error-page.scss'); - -class ErrorPage extends React.Component { - - constructor(props) { - super(props); - } - - - - - render() { - return ( - - -
- -
- ); - } - -} - -export default ErrorPage; diff --git a/src/modules/error-page/components/forbidden.jsx b/src/modules/error-page/components/forbidden.jsx deleted file mode 100644 index b146c5b7ec..0000000000 --- a/src/modules/error-page/components/forbidden.jsx +++ /dev/null @@ -1,32 +0,0 @@ -require('../styles/errormsg.scss'); - -class Forbidden extends React.Component { - - constructor(props) { - super(props); - this.state = {}; - } - - render() { - return ( -
-
- 403 -
- -
- FORBIDDEN :( -
- -
- Ups! Da ist etwas schief gelaufen.
- Du verfügst nicht über die Berechtigungen,
- um auf diese Seite zuzugreifen. -
-
- ); - } - -} - -export default Forbidden; diff --git a/src/modules/error-page/components/notfound.jsx b/src/modules/error-page/components/notfound.jsx deleted file mode 100644 index 6c18b2af67..0000000000 --- a/src/modules/error-page/components/notfound.jsx +++ /dev/null @@ -1,32 +0,0 @@ -require('../styles/errormsg.scss'); - -class NotFound extends React.Component { - - constructor(props) { - super(props); - this.state = {}; - } - - render() { - return ( -
-
- 404 -
- -
- NOT FOUND :( -
- -
- Ups! Da ist etwas schief gelaufen.
- Diese Seite existiert (noch) nicht,
- bitte versuche es auf einem andern Weg. -
-
- ); - } - -} - -export default NotFound; diff --git a/src/modules/error-page/containers/error-page.js b/src/modules/error-page/containers/error-page.js deleted file mode 100644 index 86ab6421fb..0000000000 --- a/src/modules/error-page/containers/error-page.js +++ /dev/null @@ -1,17 +0,0 @@ - -import {render} from 'react-dom'; -import {compose} from 'react-komposer'; - -import component from '../components/error-page'; -import actions from '../actions/error-page'; - -const composer = (props, onData) => { - let componentData = { - actions - }; - - onData(null, componentData); -}; - -export default compose(composer)(component); - diff --git a/src/modules/error-page/routes.jsx b/src/modules/error-page/routes.jsx deleted file mode 100644 index 789a24aea3..0000000000 --- a/src/modules/error-page/routes.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import ErrorPage from './containers/error-page'; - -export default [ - { - path: '/error/', - name: 'error-page', - component: ErrorPage - } -]; diff --git a/src/modules/error-page/styles/error-page.scss b/src/modules/error-page/styles/error-page.scss deleted file mode 100644 index 8369e76a82..0000000000 --- a/src/modules/error-page/styles/error-page.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../core/styles/colors.scss'; - -.error-page { - background:$colorBeige; - position: absolute; - width: 100%; - height: 100%; - - h3 { - color: $colorHPIRed; - float:left; - width:100%; - text-align: center; - font-weight:bold; - margin:100px 0px; - } - - .btn { - float:right; - } - - .form-group { - z-index:10; - position: relative; - } - - select, input { - margin-bottom:10px; - border: 1px solid rgba(0, 0, 0, 0.15); - } - - .bg-graphic { - position:absolute; - background:no-repeat center center; - background-size:contain; - z-index:0; - width:600px; - height:600px; - - &.bg-graphic-right { - right:5%; - top:5%; - background-image:url(../../../static/images/login-right.png); - } - } - -} diff --git a/src/modules/error-page/styles/errormsg.scss b/src/modules/error-page/styles/errormsg.scss deleted file mode 100644 index 41c066414c..0000000000 --- a/src/modules/error-page/styles/errormsg.scss +++ /dev/null @@ -1,26 +0,0 @@ - -@import '../../core/styles/colors.scss'; - -.details{ - position:absolute; - margin-left: 70px; - margin-top: 100px; - z-index:10; - - .errnumber{ - font-size: 800%; - font-weight: bold; - } - - .underline{ - font-weight: bold; - font-size: 500%; - } - - .description{ - font-size: 200%; - } - -} - - diff --git a/src/modules/file-explorer/actions/file-explorer.js b/src/modules/file-explorer/actions/file-explorer.js index ff8b4c5632..f67d5b5bce 100644 --- a/src/modules/file-explorer/actions/file-explorer.js +++ b/src/modules/file-explorer/actions/file-explorer.js @@ -1 +1,60 @@ -export default {}; +import axios from 'axios'; +import {FileService} from '../../core/helpers'; +import {Permissions, Server, Notification} from '../../core/helpers/'; + +const saveFile = (url, fileName) => { + var options = { + responseType: 'blob' + }; + + axios.get(url, options).then(res => { + var a = document.createElement('a'); + a.href = window.URL.createObjectURL(res.data); + a.download = fileName; + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + }); +}; + +export default { + upload: (files) => { + const currentUser = Server.get('user'); + Promise.all(files.map((file) => { + return FileService.getUrl(file.name, file.type, `users/${currentUser._id}`, 'putObject') + .then((signedUrl) => { + var options = { + headers: signedUrl.header + }; + return axios.put(signedUrl.url, file, options); + }); + })).then(res => { + window.location.reload(); + }); + }, + + download: (file) => { + const currentUser = Server.get('user'); + return FileService.getUrl(file.name, null, `users/${currentUser._id}`, 'getObject') + .then((signedUrl) => { + if (!signedUrl.url) { + Notification.showError("Beim Downloaden der Datei ist etwas schief gelaufen!"); + return; + } + + saveFile(signedUrl.url, file.name); + }).catch(err => { + Notification.showError(err.message); + }); + }, + + delete: (file) => { + const currentUser = Server.get('user'); + return FileService.deleteFile(`users/${currentUser._id}`, file.name, null) + .then((res) => { + window.location.reload(); + }).catch(err => { + Notification.showError(err.message); + }); + } +}; diff --git a/src/modules/file-explorer/components/file-explorer.jsx b/src/modules/file-explorer/components/file-explorer.jsx index 90bdb3e5b8..0d8de221a7 100755 --- a/src/modules/file-explorer/components/file-explorer.jsx +++ b/src/modules/file-explorer/components/file-explorer.jsx @@ -1,5 +1,5 @@ -import LayoutBackend from '../../backend/containers/layout'; -import SectionTitle from '../../backend/components/title'; /* only for backend */ +import LayoutBase from '../../base/containers/layout'; +import SectionTitle from '../../base/components/title'; /* only for base */ import Directories from './directories'; import Upload from './upload'; @@ -23,12 +23,11 @@ class FileExplorer extends React.Component { render() { return ( - + - - - - + + + ); } diff --git a/src/modules/file-explorer/components/files.jsx b/src/modules/file-explorer/components/files.jsx index 9f1b7a5b98..95dcf7f472 100644 --- a/src/modules/file-explorer/components/files.jsx +++ b/src/modules/file-explorer/components/files.jsx @@ -4,50 +4,71 @@ class Files extends React.Component { constructor(props) { super(props); + } - this.state = {}; + handleOnDownloadClick(file) { + this.props.actions.download(file); } - /* Mock data */ - getData() { - return [{ - name: 'Aufgabe1.docx', - type: 'Word-Dokument', - thumbnail: 'https://vebu.de/wp-content/uploads/2015/12/beitragsbild_690x460_bienen_industriell-690x460.jpg' - }, - { - name: 'Aufgabe2.docx', - type: 'Word-Dokument', - thumbnail: 'https://www.hauenstein-rafz.ch/de-wAssets/img/pflanzenwelt/sammelsurium/bienenweidepflanzen/Biene_12.jpg' - }, - { - name: 'Bienen.jpg', - type: 'Bildressource', - thumbnail: 'http://www.br-online.de/kinder/fragen-verstehen/wissen/2003/00278/bienenwabe_dpa482.jpg' - }]; + handleOnDeleteClick(file) { + this.props.actions.delete(file); } - getFilesUI() { + getFileDeleteModalUI(file) { + return ( + + ); + } + + getFileUI(file) { return ( -
- {this.getData.bind(this)().map((file) => { - return ( -
-
-
-
+
+
+
+
-
- {file.name} -
+ {file.name} +
+
+