From 6ce83d0fb7c73608d75148dbdcba2df179aab1fe Mon Sep 17 00:00:00 2001
From: Rebecca Tinchon <58392030+WolfyWin@users.noreply.github.com>
Date: Wed, 4 Sep 2024 10:39:15 +0200
Subject: [PATCH] [Training] Improved cursus (#2834)
* New editor cursus
* archive and restore actions
* Creation types for trainings
* hidden archived session and event
* PR review
* pr review 2
---
.../cursus/Controller/CourseController.php | 93 ++++++++++-
src/plugin/cursus/Entity/Course.php | 2 +
src/plugin/cursus/Finder/EventFinder.php | 1 +
src/plugin/cursus/Finder/SessionFinder.php | 1 +
.../Migrations/Version20240821130015.php | 30 ++++
.../modules/actions/course/archive.js | 53 +++++++
.../modules/actions/course/restore.js | 34 ++++
.../modules/course/components/about.jsx | 7 +
.../modules/course/components/edit.jsx | 50 ------
.../modules/course/components/empty.jsx | 26 ++--
.../modules/course/components/list.jsx | 11 +-
.../modules/course/components/type.jsx | 131 ++++++++++++++++
.../course/editor/components/actions.jsx | 45 ++++++
.../course/editor/components/appearance.jsx | 67 ++++++++
.../course/editor/components/history.jsx | 14 ++
.../modules/course/editor/components/main.jsx | 73 +++++++++
.../course/editor/components/overview.jsx | 75 +++++++++
.../course/editor/components/permissions.jsx | 44 ++++++
.../course/editor/components/registration.jsx | 145 ++++++++++++++++++
.../course/editor/components/workspaces.jsx | 95 ++++++++++++
.../modules/course/editor/containers/main.jsx | 28 ++++
.../modals/creation/components/modal.jsx | 39 +++++
.../modals/creation/containers/modal.jsx | 29 ++++
.../modules/course/modals/creation/index.js | 13 ++
.../course/modals/creation/store/index.js | 7 +
.../course/modals/creation/store/reducer.js | 10 ++
.../course/modals/creation/store/selectors.js | 5 +
.../Resources/modules/course/store/actions.js | 32 ++--
src/plugin/cursus/Resources/modules/plugin.js | 4 +-
.../modules/session/components/list.jsx | 2 +-
.../modules/tools/events/components/tool.jsx | 20 ++-
.../modules/tools/events/containers/tool.jsx | 1 +
.../trainings/catalog/components/list.jsx | 23 ++-
.../trainings/catalog/components/main.jsx | 13 +-
.../trainings/catalog/containers/main.jsx | 5 +-
.../trainings/catalog/store/selectors.js | 1 +
.../tools/trainings/components/tool.jsx | 8 +-
.../tools/trainings/containers/menu.jsx | 20 ---
.../trainings/editor/components/list.jsx | 23 +++
.../trainings/editor/components/main.jsx | 31 ++++
.../trainings/editor/containers/main.jsx | 18 +++
.../tools/trainings/editor/store/index.js | 7 +
.../tools/trainings/editor/store/reducer.js | 10 ++
.../tools/trainings/editor/store/selectors.js | 5 +
.../Resources/translations/actions.en.json | 13 +-
.../Resources/translations/actions.fr.json | 13 +-
.../Resources/translations/cursus.en.json | 36 ++++-
.../Resources/translations/cursus.fr.json | 37 ++++-
.../cursus/Serializer/CourseSerializer.php | 4 +-
49 files changed, 1316 insertions(+), 138 deletions(-)
create mode 100644 src/plugin/cursus/Installation/Migrations/Version20240821130015.php
create mode 100644 src/plugin/cursus/Resources/modules/actions/course/archive.js
create mode 100644 src/plugin/cursus/Resources/modules/actions/course/restore.js
delete mode 100644 src/plugin/cursus/Resources/modules/course/components/edit.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/components/type.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/actions.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/appearance.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/history.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/main.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/overview.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/permissions.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/registration.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/components/workspaces.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/editor/containers/main.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/components/modal.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/containers/modal.jsx
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/index.js
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/store/index.js
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/store/reducer.js
create mode 100644 src/plugin/cursus/Resources/modules/course/modals/creation/store/selectors.js
delete mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/containers/menu.jsx
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/components/list.jsx
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/components/main.jsx
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/containers/main.jsx
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/store/index.js
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/store/reducer.js
create mode 100644 src/plugin/cursus/Resources/modules/tools/trainings/editor/store/selectors.js
diff --git a/src/plugin/cursus/Controller/CourseController.php b/src/plugin/cursus/Controller/CourseController.php
index f793e62f6a6..5a3d7bb69b2 100644
--- a/src/plugin/cursus/Controller/CourseController.php
+++ b/src/plugin/cursus/Controller/CourseController.php
@@ -55,7 +55,7 @@ public function __construct(
RoutingHelper $routing,
ToolManager $toolManager,
CourseManager $manager,
- PdfManager $pdfManager
+ PdfManager $pdfManager,
) {
$this->authorization = $authorization;
$this->tokenStorage = $tokenStorage;
@@ -106,6 +106,8 @@ protected function getDefaultHiddenFilters(): array
}
}
+ $filters['archived'] = false;
+
return $filters;
}
@@ -114,14 +116,89 @@ protected function getDefaultHiddenFilters(): array
*/
public function listPublicAction(Request $request): JsonResponse
{
- $params = $request->query->all();
- $params['hiddenFilters'] = [
- 'public' => true,
- ];
+ return new JsonResponse($this->crud->list(
+ Course::class,
+ array_merge($request->query->all(), ['hiddenFilters' => array_merge($this->getDefaultHiddenFilters(), [
+ 'public' => true,
+ ])]),
+ $this->getOptions()['list']
+ ));
+ }
- return new JsonResponse(
- $this->crud->list(Course::class, $params)
- );
+ /**
+ * @Route("/list/archived", name="apiv2_cursus_course_list_archived", methods={"GET"})
+ */
+ public function listArchivedAction(Request $request): JsonResponse
+ {
+ $this->checkPermission('IS_AUTHENTICATED_FULLY', null, [], true);
+
+ return new JsonResponse($this->crud->list(
+ Course::class,
+ array_merge($request->query->all(), ['hiddenFilters' => array_merge($this->getDefaultHiddenFilters(), [
+ 'archived' => true,
+ ])]),
+ $this->getOptions()['list']
+ ));
+ }
+
+ /**
+ * @Route("/archive", name="apiv2_cursus_course_archive", methods={"POST"})
+ */
+ public function archiveAction(Request $request): JsonResponse
+ {
+ $processed = [];
+
+ $this->om->startFlushSuite();
+
+ $data = json_decode($request->getContent(), true);
+
+ /** @var Course[] $courses */
+ $courses = $this->om->getRepository(Course::class)->findBy([
+ 'uuid' => $data['ids'],
+ ]);
+
+ foreach ($courses as $course) {
+ if ($this->authorization->isGranted('ADMINISTRATE', $course) && !$course->isArchived()) {
+ $course->setArchived(true);
+ $processed[] = $course;
+ }
+ }
+
+ $this->om->endFlushSuite();
+
+ return new JsonResponse(array_map(function (Course $course) {
+ return $this->serializer->serialize($course);
+ }, $processed));
+ }
+
+ /**
+ * @Route("/restore", name="apiv2_cursus_course_restore", methods={"POST"})
+ */
+ public function restoreAction(Request $request): JsonResponse
+ {
+ $processed = [];
+
+ $this->om->startFlushSuite();
+
+ $data = json_decode($request->getContent(), true);
+
+ /** @var Course[] $courses */
+ $courses = $this->om->getRepository(Course::class)->findBy([
+ 'uuid' => $data['ids'],
+ ]);
+
+ foreach ($courses as $course) {
+ if ($this->authorization->isGranted('ADMINISTRATE', $course) && $course->isArchived()) {
+ $course->setArchived(false);
+ $processed[] = $course;
+ }
+ }
+
+ $this->om->endFlushSuite();
+
+ return new JsonResponse(array_map(function (Course $course) {
+ return $this->serializer->serialize($course);
+ }, $processed));
}
/**
diff --git a/src/plugin/cursus/Entity/Course.php b/src/plugin/cursus/Entity/Course.php
index 05dbcec7aa3..eb508ff8100 100644
--- a/src/plugin/cursus/Entity/Course.php
+++ b/src/plugin/cursus/Entity/Course.php
@@ -11,6 +11,7 @@
namespace Claroline\CursusBundle\Entity;
+use Claroline\AppBundle\Entity\Meta\Archived;
use Claroline\AppBundle\Entity\Meta\IsPublic;
use Claroline\CommunityBundle\Model\HasOrganizations;
use Claroline\CoreBundle\Entity\Facet\PanelFacet;
@@ -31,6 +32,7 @@ class Course extends AbstractTraining
{
use HasOrganizations;
use IsPublic;
+ use Archived;
/**
* @Gedmo\Slug(fields={"name"})
diff --git a/src/plugin/cursus/Finder/EventFinder.php b/src/plugin/cursus/Finder/EventFinder.php
index 15c71007d7a..23d35423d9a 100644
--- a/src/plugin/cursus/Finder/EventFinder.php
+++ b/src/plugin/cursus/Finder/EventFinder.php
@@ -27,6 +27,7 @@ public function configureQueryBuilder(QueryBuilder $qb, array $searches = [], ar
$qb->join('obj.plannedObject', 'po');
$qb->join('obj.session', 's');
$qb->join('s.course', 'c');
+ $qb->andWhere('c.archived = 0');
foreach ($searches as $filterName => $filterValue) {
switch ($filterName) {
diff --git a/src/plugin/cursus/Finder/SessionFinder.php b/src/plugin/cursus/Finder/SessionFinder.php
index 9450f918c36..20eba1a3eb5 100644
--- a/src/plugin/cursus/Finder/SessionFinder.php
+++ b/src/plugin/cursus/Finder/SessionFinder.php
@@ -27,6 +27,7 @@ public static function getClass(): string
public function configureQueryBuilder(QueryBuilder $qb, array $searches = [], array $sortBy = null, ?int $page = 0, ?int $limit = -1): QueryBuilder
{
$qb->join('obj.course', 'c');
+ $qb->andWhere('c.archived = 0');
foreach ($searches as $filterName => $filterValue) {
switch ($filterName) {
diff --git a/src/plugin/cursus/Installation/Migrations/Version20240821130015.php b/src/plugin/cursus/Installation/Migrations/Version20240821130015.php
new file mode 100644
index 00000000000..ffab3c787db
--- /dev/null
+++ b/src/plugin/cursus/Installation/Migrations/Version20240821130015.php
@@ -0,0 +1,30 @@
+addSql('
+ ALTER TABLE claro_cursusbundle_course
+ ADD archived TINYINT(1) NOT NULL
+ ');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('
+ ALTER TABLE claro_cursusbundle_course
+ DROP archived
+ ');
+ }
+}
diff --git a/src/plugin/cursus/Resources/modules/actions/course/archive.js b/src/plugin/cursus/Resources/modules/actions/course/archive.js
new file mode 100644
index 00000000000..893a50d844b
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/actions/course/archive.js
@@ -0,0 +1,53 @@
+import {createElement} from 'react'
+import get from 'lodash/get'
+
+import {url} from '#/main/app/api'
+import {ASYNC_BUTTON} from '#/main/app/buttons'
+import {hasPermission} from '#/main/app/security'
+import {trans, transChoice} from '#/main/app/intl/translation'
+
+import {CourseCard} from '#/plugin/cursus/course/components/card'
+
+/**
+ * Archive action.
+ */
+export default (courses, refresher) => {
+ const processable = courses.filter(course => hasPermission('administrate', course) && !get(course, 'meta.archived'))
+
+ return {
+ name: 'archive',
+ type: ASYNC_BUTTON,
+ icon: 'fa fa-fw fa-box',
+ label: trans('archive', {}, 'actions'),
+ displayed: 0 !== processable.length,
+ dangerous: true,
+ confirm: {
+ title: transChoice('archive_training_confirm_title', processable.length, {}, 'actions'),
+ message: transChoice('archive_training_confirm_message', processable.length, {count: processable.length}, 'actions'),
+ additional: [
+ createElement('div', {
+ key: 'additional',
+ className: 'modal-body'
+ }, processable.map(course => createElement(CourseCard, {
+ key: course.id,
+ orientation: 'row',
+ className: 'mb-2',
+ size: 'xs',
+ data: course
+ })))
+ ]
+ },
+ request: {
+ url: url(['apiv2_cursus_course_archive']),
+ request: {
+ method: 'POST',
+ body: JSON.stringify({
+ ids: processable.map(course => course.id)
+ })
+ },
+ success: (response) => refresher.update(response)
+ },
+ group: trans('management'),
+ scope: ['object', 'collection']
+ }
+}
diff --git a/src/plugin/cursus/Resources/modules/actions/course/restore.js b/src/plugin/cursus/Resources/modules/actions/course/restore.js
new file mode 100644
index 00000000000..1952600fcda
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/actions/course/restore.js
@@ -0,0 +1,34 @@
+import get from 'lodash/get'
+
+import {url} from '#/main/app/api'
+import {ASYNC_BUTTON} from '#/main/app/buttons'
+import {trans} from '#/main/app/intl/translation'
+import {hasPermission} from '#/main/app/security'
+
+/**
+ * Restore action.
+ */
+export default (courses, refresher) => {
+ const processable = courses.filter(course => hasPermission('administrate', course) && get(course, 'meta.archived'))
+
+ return {
+ name: 'restore',
+ type: ASYNC_BUTTON,
+ icon: 'fa fa-fw fa-box-open',
+ label: trans('restore', {}, 'actions'),
+ displayed: 0 !== processable.length,
+ request: {
+ url: url(['apiv2_cursus_course_restore']),
+ request: {
+ method: 'POST',
+ body: JSON.stringify({
+ ids: processable.map(course => course.id)
+ })
+ },
+ success: (response) => refresher.update(response)
+ },
+ group: trans('management'),
+ scope: ['object', 'collection'],
+ dangerous: true
+ }
+}
diff --git a/src/plugin/cursus/Resources/modules/course/components/about.jsx b/src/plugin/cursus/Resources/modules/course/components/about.jsx
index f7ade47f25b..ce2087bd088 100644
--- a/src/plugin/cursus/Resources/modules/course/components/about.jsx
+++ b/src/plugin/cursus/Resources/modules/course/components/about.jsx
@@ -178,6 +178,13 @@ const CourseAbout = (props) => {
+
+ {get(props.course, 'meta.archived') === true &&
+
+ {trans('course_archived_info_help', {}, 'cursus')}
+
+ }
+
{!isEmpty(props.activeSession) &&
diff --git a/src/plugin/cursus/Resources/modules/course/components/edit.jsx b/src/plugin/cursus/Resources/modules/course/components/edit.jsx
deleted file mode 100644
index b12b7720397..00000000000
--- a/src/plugin/cursus/Resources/modules/course/components/edit.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react'
-import {connect} from 'react-redux'
-import {PropTypes as T} from 'prop-types'
-
-import {trans} from '#/main/app/intl/translation'
-import {LINK_BUTTON} from '#/main/app/buttons'
-import {Course as CourseTypes} from '#/plugin/cursus/prop-types'
-import {CoursePage} from '#/plugin/cursus/course/components/page'
-import {CourseForm} from '#/plugin/cursus/course/containers/form'
-
-import {selectors} from '#/plugin/cursus/course/store'
-import {selectors as toolSelectors} from '#/main/core/tool/store'
-import {selectors as formSelectors} from '#/main/app/content/form/store'
-
-const CourseEditComponent = (props) =>
-
-
-
-
-CourseEditComponent.propTypes = {
- path: T.string.isRequired,
- contextType: T.string.isRequired,
- course: T.shape(
- CourseTypes.propTypes
- )
-}
-
-const CourseEdit = connect(
- (state) => ({
- path: toolSelectors.path(state),
- course: formSelectors.data(formSelectors.form(state, selectors.FORM_NAME)),
- contextType: toolSelectors.contextType(state)
- })
-)(CourseEditComponent)
-
-export {
- CourseEdit
-}
diff --git a/src/plugin/cursus/Resources/modules/course/components/empty.jsx b/src/plugin/cursus/Resources/modules/course/components/empty.jsx
index 1a364c7810b..1ca58315aa3 100644
--- a/src/plugin/cursus/Resources/modules/course/components/empty.jsx
+++ b/src/plugin/cursus/Resources/modules/course/components/empty.jsx
@@ -1,12 +1,14 @@
import React from 'react'
import {PropTypes as T} from 'prop-types'
-import {trans} from '#/main/app/intl/translation'
-import {LINK_BUTTON} from '#/main/app/buttons'
import {ToolPage} from '#/main/core/tool'
-import {Button} from '#/main/app/action/components/button'
+import {MODAL_BUTTON} from '#/main/app/buttons'
+import {trans} from '#/main/app/intl/translation'
+
import {ContentSizing} from '#/main/app/content/components/sizing'
+import {CreationType} from '#/plugin/cursus/course/components/type'
import {ContentPlaceholder} from '#/main/app/content/components/placeholder'
+import {MODAL_COURSE_TYPE_CREATION} from '#/plugin/cursus/course/modals/creation'
const EmptyCourse = (props) =>
actions={[
{
name: 'add',
- type: LINK_BUTTON,
+ type: MODAL_BUTTON,
icon: 'fa fa-fw fa-plus',
label: trans('add_course', {}, 'cursus'),
- target: `${props.path}/new`,
+ modal: [MODAL_COURSE_TYPE_CREATION, {
+ path: props.path
+ }],
group: trans('management'),
displayed: props.canEdit,
primary: true
@@ -30,14 +34,12 @@ const EmptyCourse = (props) =>
title={trans('no_course', {}, 'cursus')}
help={trans('no_course_help', {}, 'cursus')}
/>
-
+
+
+
+
+
EmptyCourse.propTypes = {
diff --git a/src/plugin/cursus/Resources/modules/course/components/list.jsx b/src/plugin/cursus/Resources/modules/course/components/list.jsx
index 19ce0cf0cc2..fbd73b8a7e9 100644
--- a/src/plugin/cursus/Resources/modules/course/components/list.jsx
+++ b/src/plugin/cursus/Resources/modules/course/components/list.jsx
@@ -13,6 +13,8 @@ import {actions as listActions} from '#/main/app/content/list/store'
import {CourseCard} from '#/plugin/cursus/course/components/card'
import {getActions, getDefaultAction} from '#/plugin/cursus/course/utils'
+import {ContentSizing} from '#/main/app/content/components/sizing'
+
const Courses = (props) => {
const refresher = merge({
add: () => props.invalidate(props.name),
@@ -79,7 +81,11 @@ const Courses = (props) => {
display={{
current: listConst.DISPLAY_LIST
}}
- />
+ >
+
+ {props.children}
+
+
)
}
@@ -89,7 +95,8 @@ Courses.propTypes = {
url: T.oneOfType([T.string, T.array]),
currentUser: T.object,
refresher: T.object,
- invalidate: T.func.isRequired
+ invalidate: T.func.isRequired,
+ children: T.node
}
Courses.defaultProps = {
diff --git a/src/plugin/cursus/Resources/modules/course/components/type.jsx b/src/plugin/cursus/Resources/modules/course/components/type.jsx
new file mode 100644
index 00000000000..6ebd5232703
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/components/type.jsx
@@ -0,0 +1,131 @@
+import React from 'react'
+import {PropTypes as T} from 'prop-types'
+import {useHistory} from 'react-router-dom'
+import {trans} from '#/main/app/intl'
+
+import {MODAL_WORKSPACES} from '#/main/core/modals/workspaces'
+import {ContentMenu} from '#/main/app/content/components/menu'
+import {CALLBACK_BUTTON, MODAL_BUTTON} from '#/main/app/buttons'
+import {Course as CourseTypes} from '#/plugin/cursus/prop-types'
+
+const CreationType = (props) => {
+ const history = useHistory()
+
+ return (
+
(
+ {
+ type: CALLBACK_BUTTON,
+ callback: () => {
+ if (props.modal) {
+ props.fadeModal()
+ }
+ history.push(props.path + '/new')
+ props.openForm(null, CourseTypes.defaultProps, selected[0])
+ }
+ }
+ )
+ }]
+ }
+ }, {
+ id: 'create-with-model',
+ icon: 'stamp',
+ label: trans('create_mode_model', {}, 'cursus'),
+ description: trans('create_mode_model_desc', {}, 'cursus'),
+ displayed: props.contextType === 'desktop',
+ action: {
+ type: MODAL_BUTTON,
+ modal: [MODAL_WORKSPACES, {
+ url: ['apiv2_workspace_list_model'],
+ multiple: false,
+ selectAction: (selected) => (
+ {
+ type: CALLBACK_BUTTON,
+ callback: () => {
+ if (props.modal) {
+ props.fadeModal()
+ }
+ history.push(props.path + '/new')
+ props.openForm(null, CourseTypes.defaultProps, selected[0])
+ }
+ }
+ )
+ }]
+ }
+ }, {
+ id: 'create-empty',
+ icon: 'plus-circle',
+ label: trans('create_mode_empty', {}, 'cursus'),
+ description: trans('create_mode_empty_desc', {}, 'cursus'),
+ action: {
+ type: CALLBACK_BUTTON,
+ callback: () => {
+ if (props.modal) {
+ props.fadeModal()
+ }
+ history.push(props.path + '/new')
+ props.openForm(null, CourseTypes.defaultProps)
+ }
+ }
+ }, {
+ id: 'create-from-copy',
+ icon: 'clone',
+ label: trans('create_mode_copy', {}, 'cursus'),
+ description: trans('create_mode_copy_desc', {}, 'cursus'),
+ action: {
+ type: MODAL_BUTTON,
+ modal: []
+ },
+ group: trans('create_mode_group_existing', {}, 'cursus')
+ }, {
+ id: 'create-from-organization',
+ icon: 'building',
+ label: trans('create_mode_organization', {}, 'cursus'),
+ description: trans('create_mode_organization_desc', {}, 'cursus'),
+ action: {
+ type: CALLBACK_BUTTON,
+ callback: () => true
+ },
+ group: trans('create_mode_group_existing', {}, 'cursus')
+ }, {
+ id: 'create-from-existing',
+ icon: 'graduation-cap',
+ label: trans('create_mode_existing', {}, 'cursus'),
+ description: trans('create_mode_existing_desc', {}, 'cursus'),
+ displayed: props.contextType === 'workspace',
+ action: {
+ type: CALLBACK_BUTTON,
+ callback: () => true
+ },
+ group: trans('create_mode_group_existing', {}, 'cursus')
+ }
+ ]}
+ />
+ )
+}
+
+CreationType.propTypes = {
+ path: T.string.isRequired,
+ openForm: T.func.isRequired,
+ reset: T.func,
+ contextType: T.string,
+ modal: T.bool,
+ fadeModal: T.func
+}
+
+export {
+ CreationType
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/actions.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/actions.jsx
new file mode 100644
index 00000000000..e916cc6ee38
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/actions.jsx
@@ -0,0 +1,45 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {CALLBACK_BUTTON} from '#/main/app/buttons'
+
+import {EditorActions} from '#/main/app/editor'
+
+const CourseEditorActions = () =>
+ false
+ },
+ managerOnly: true
+ }, {
+ title: trans('restore_training', {}, 'actions'),
+ help: trans('restore_training_help', {}, 'actions'),
+ action: {
+ label: trans('restore', {}, 'actions'),
+ type: CALLBACK_BUTTON,
+ callback: () => false
+ },
+ managerOnly: true
+ }, {
+ title: trans('delete_training', {}, 'actions'),
+ help: trans('delete_training_help', {}, 'actions'),
+ action: {
+ label: trans('delete', {}, 'actions'),
+ type: CALLBACK_BUTTON,
+ callback: () => false
+ },
+ dangerous: true,
+ managerOnly: true
+ }
+ ]}
+ />
+
+export {
+ CourseEditorActions
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/appearance.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/appearance.jsx
new file mode 100644
index 00000000000..7de9b58f84e
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/appearance.jsx
@@ -0,0 +1,67 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+const CourseEditorAppearance = (props) =>
+
+ {props.children}
+
+
+export {
+ CourseEditorAppearance
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/history.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/history.jsx
new file mode 100644
index 00000000000..1357012877b
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/history.jsx
@@ -0,0 +1,14 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+const CourseEditorHistory = () =>
+
+
+export {
+ CourseEditorHistory
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/main.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/main.jsx
new file mode 100644
index 00000000000..49879b105d7
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/main.jsx
@@ -0,0 +1,73 @@
+import React from 'react'
+import get from 'lodash/get'
+import {PropTypes as T} from 'prop-types'
+
+import {trans} from '#/main/app/intl'
+import {route} from '#/plugin/cursus/routing'
+import {hasPermission} from '#/main/app/security'
+import {Editor} from '#/main/app/editor/components/main'
+import {Course as CourseTypes} from '#/plugin/cursus/prop-types'
+
+import {CourseEditorHistory} from '#/plugin/cursus/course/editor/components/history'
+import {CourseEditorActions} from '#/plugin/cursus/course/editor/components/actions'
+import {CourseEditorOverview} from '#/plugin/cursus/course/editor/components/overview'
+import {CourseEditorAppearance} from '#/plugin/cursus/course/editor/components/appearance'
+import {CourseEditorWorkspaces} from '#/plugin/cursus/course/editor/components/workspaces'
+import {CourseEditorPermissions} from '#/plugin/cursus/course/editor/components/permissions'
+import {CourseEditorRegistration} from '#/plugin/cursus/course/editor/components/registration'
+
+import {selectors} from '#/plugin/cursus/course/store'
+
+const CourseEditor = (props) =>
+ (
+
+ )
+ }, {
+ name:'registration',
+ title: trans('registration'),
+ render: () => (
+
+ )
+ }
+ ].concat(props.pages || [])}
+ />
+
+CourseEditor.propTypes = {
+ path: T.string.isRequired,
+ course: T.shape(
+ CourseTypes.propTypes
+ ),
+ update: T.func.isRequired,
+ contextType: T.string,
+ pages: T.array
+}
+
+export {
+ CourseEditor
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/overview.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/overview.jsx
new file mode 100644
index 00000000000..d39eb310626
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/overview.jsx
@@ -0,0 +1,75 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+const CourseEditorOverview = () =>
+
+
+export {
+ CourseEditorOverview
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/permissions.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/permissions.jsx
new file mode 100644
index 00000000000..e6a855bf884
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/permissions.jsx
@@ -0,0 +1,44 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+const CourseEditorPermissions = () =>
+
+
+export {
+ CourseEditorPermissions
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/registration.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/registration.jsx
new file mode 100644
index 00000000000..fe5bde18f3c
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/registration.jsx
@@ -0,0 +1,145 @@
+import React from 'react'
+import get from 'lodash/get'
+import isEmpty from 'lodash/isEmpty'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+import {FormParameters} from '#/main/app/content/form/parameters/containers/main'
+
+const CourseEditorRegistration = (props) =>
+ {
+ if (!checked) {
+ props.update(props.name, 'registration.autoRegistration', false)
+ props.update(props.name, 'registration.validation', false)
+ props.update(props.name, 'registration.pendingRegistrations', false)
+ }
+ },
+ linked: [
+ {
+ name: 'registration._selfRegistrationMode',
+ type: 'choice',
+ label: trans('mode'),
+ displayed: (course) => get(course, 'registration.selfRegistration'),
+ calculated: (course) => {
+ if (get(course, 'registration.autoRegistration')) {
+ return 'auto'
+ } else if (get(course, 'registration.validation')) {
+ return 'validation'
+ }
+
+ return 'simple'
+ },
+ required: true,
+ options: {
+ condensed: false,
+ choices: {
+ simple: trans('simple_registration', {}, 'cursus'),
+ validation: trans('validate_registration', {}, 'cursus'),
+ auto: trans('auto_registration', {}, 'cursus')
+ }
+ },
+ onChange: (registrationMode) => {
+ switch (registrationMode) {
+ case 'simple':
+ props.update(props.name, 'registration.autoRegistration', false)
+ props.update(props.name, 'registration.validation', false)
+ break
+
+ case 'auto':
+ props.update(props.name, 'registration.autoRegistration', true)
+
+ // reset incompatible options
+ props.update(props.name, 'restrictions._restrictUsers', false)
+ props.update(props.name, 'restrictions.users', null)
+ props.update(props.name, 'registration.mail', false)
+ props.update(props.name, 'registration.validation', false)
+ props.update(props.name, 'registration.userValidation', false)
+ props.update(props.name, 'registration.selfUnregistration', false)
+ props.update(props.name, 'registration.pendingRegistrations', false)
+ props.update(props.name, 'registration._enableCustomForm', false)
+ props.update(props.name, 'registration.form', [])
+ break
+
+ case 'validation':
+ props.update(props.name, 'registration.validation', true)
+
+ // reset incompatible options
+ props.update(props.name, 'registration.autoRegistration', false)
+ break
+ }
+ }
+ }, {
+ name: 'registration.pendingRegistrations',
+ type: 'boolean',
+ label: trans('enable_course_pending_list', {}, 'cursus'),
+ displayed: (course) => get(course, 'registration.selfRegistration') && !get(course, 'registration.autoRegistration')
+ }
+ ]
+ }, {
+ name: 'registration.mail',
+ type: 'boolean',
+ label: trans('registration_send_mail', {}, 'cursus'),
+ displayed: (course) => !get(course, 'registration.autoRegistration'),
+ onChange: (checked) => {
+ if (!checked) {
+ props.update(props.name, 'registration.userValidation', false)
+ }
+ },
+ linked: [
+ {
+ name: 'registration.userValidation',
+ type: 'boolean',
+ label: trans('registration_user_validation', {}, 'cursus'),
+ help: trans('registration_user_validation_help', {}, 'cursus'),
+ displayed: (course) => get(course, 'registration.mail')
+ }
+ ]
+ }, {
+ name: 'registration.selfUnregistration',
+ type: 'boolean',
+ label: trans('activate_self_unregistration'),
+ help: trans('self_unregistration_training_help', {}, 'cursus'),
+ displayed: (course) => !get(course, 'registration.autoRegistration')
+ }, {
+ name: 'registration._enableCustomForm',
+ type: 'boolean',
+ label: trans('enable_custom_registration_form', {}, 'cursus'),
+ displayed: (data) => !get(data, 'registration.autoRegistration', false),
+ calculated: (data) => get(data, 'registration._enableCustomForm') || !isEmpty(get(data, 'registration.form')),
+ onChange: (enabled) => {
+ if (!enabled) {
+ props.update(props.name, 'registration.form', [])
+ }
+ },
+ help: [trans('custom_registration_form_help', {}, 'cursus'), trans('custom_registration_form_group_help', {}, 'cursus')]
+ }
+ ]
+ }
+ ]}
+ >
+ {(get(props.course, 'registration._enableCustomForm') || !isEmpty(get(props.course, 'registration.form'))) &&
+
+ }
+
+
+export {
+ CourseEditorRegistration
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/components/workspaces.jsx b/src/plugin/cursus/Resources/modules/course/editor/components/workspaces.jsx
new file mode 100644
index 00000000000..a103ab9706e
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/components/workspaces.jsx
@@ -0,0 +1,95 @@
+import React from 'react'
+import get from 'lodash/get'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+const CourseEditorWorkspaces = (props) =>
+ {
+ if (get(course, '_workspaceType')) {
+ return get(course, '_workspaceType')
+ }
+
+ if (get(course, 'workspace', null)) {
+ if (get(props.course, 'workspace.meta.model', false)) {
+ return 'model'
+ }
+ return 'workspace'
+ }
+ return 'none'
+ },
+ onChange: () => {
+ props.update(props.name, 'workspace', null)
+ props.update(props.name, 'registration.tutorRole', null)
+ props.update(props.name, 'registration.learnerRole', null)
+ },
+ linked: [
+ {
+ name: 'workspace',
+ type: 'workspace',
+ label: get(props.course, 'workspace.meta.model', false) || 'model' === get(props.course, '_workspaceType') ? trans('workspace_model') : trans('workspace'),
+ required: true,
+ options: {
+ picker: {
+ model: get(props.course, 'workspace.meta.model', false) || 'model' === get(props.course, '_workspaceType'),
+ title: get(props.course, 'workspace.meta.model', false) || 'model' === get(props.course, '_workspaceType') ? trans('workspace_models', {}, 'workspace') : trans('workspaces')
+ }
+ },
+ displayed: (course) => get(course, 'workspace', null) || ['workspace', 'model'].includes(get(course, '_workspaceType'))
+ }
+ ]
+ }, {
+ name: 'registration.tutorRole',
+ type: 'role',
+ label: trans('tutor_role', {}, 'cursus'),
+ displayed: (course) => get(course, 'workspace', null),
+ options: {
+ picker: {
+ url: ['apiv2_workspace_list_roles', {id: get(props.course, 'workspace.id', null)}],
+ filters: []
+ }
+ },
+ help: trans('tutor_role_help', {}, 'cursus')
+ }, {
+ name: 'registration.learnerRole',
+ type: 'role',
+ label: trans('learner_role', {}, 'cursus'),
+ displayed: (course) => get(course, 'workspace', null),
+ options: {
+ picker: {
+ url: ['apiv2_workspace_list_roles', {id: get(props.course, 'workspace.id', null)}],
+ filters: []
+ }
+ },
+ help: trans('learner_role_help', {}, 'cursus')
+ }
+ ]
+ }
+ ]}
+ />
+
+export {
+ CourseEditorWorkspaces
+}
diff --git a/src/plugin/cursus/Resources/modules/course/editor/containers/main.jsx b/src/plugin/cursus/Resources/modules/course/editor/containers/main.jsx
new file mode 100644
index 00000000000..b02cabcbf47
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/editor/containers/main.jsx
@@ -0,0 +1,28 @@
+import {connect} from 'react-redux'
+
+import {withReducer} from '#/main/app/store/components/withReducer'
+
+import {selectors as toolSelectors} from '#/main/core/tool'
+import {reducer, selectors} from '#/plugin/cursus/course/store'
+import {actions as formActions, selectors as formSelectors} from '#/main/app/content/form/store'
+
+import {CourseEditor as CourseEditorComponent} from '#/plugin/cursus/course/editor/components/main'
+
+const CourseEditor = withReducer(selectors.STORE_NAME, reducer)(
+ connect(
+ (state) => ({
+ path: toolSelectors.path(state),
+ contextType: toolSelectors.contextType(state),
+ course: formSelectors.data(formSelectors.form(state, selectors.FORM_NAME))
+ }),
+ (dispatch) => ({
+ update(name, prop, value) {
+ dispatch(formActions.updateProp(name, prop, value))
+ }
+ })
+ )(CourseEditorComponent)
+)
+
+export {
+ CourseEditor
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/components/modal.jsx b/src/plugin/cursus/Resources/modules/course/modals/creation/components/modal.jsx
new file mode 100644
index 00000000000..705e084d5bd
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/components/modal.jsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import {PropTypes as T} from 'prop-types'
+import omit from 'lodash/omit'
+
+import {trans} from '#/main/app/intl'
+
+import {Modal} from '#/main/app/overlays/modal/components/modal'
+import {CreationType} from '#/plugin/cursus/course/components/type'
+
+const CreationModal = (props) =>
+
+
+
+
+
+
+CreationModal.propTypes = {
+ path: T.string.isRequired,
+ contextType: T.string.isRequired,
+ openForm: T.func.isRequired,
+ reset: T.func.isRequired,
+ fadeModal: T.func.isRequired
+}
+
+export {
+ CreationModal
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/containers/modal.jsx b/src/plugin/cursus/Resources/modules/course/modals/creation/containers/modal.jsx
new file mode 100644
index 00000000000..85483578c38
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/containers/modal.jsx
@@ -0,0 +1,29 @@
+import {connect} from 'react-redux'
+
+import {withReducer} from '#/main/app/store/reducer'
+
+import {actions as formActions} from '#/main/app/content/form'
+import {selectors as toolSelectors} from '#/main/core/tool/store'
+import {actions as courseActions} from '#/plugin/cursus/course/store'
+import {reducer, selectors} from '#/plugin/cursus/course/modals/creation/store'
+import {CreationModal as BaseCreationModal} from '#/plugin/cursus/course/modals/creation/components/modal'
+
+const CreationModal = withReducer(selectors.STORE_NAME, reducer)(
+ connect(
+ (state) => ({
+ contextType: toolSelectors.contextType(state)
+ }),
+ (dispatch) => ({
+ openForm(slug, defaultProps, workspace = null) {
+ dispatch(courseActions.openForm(slug, defaultProps, workspace))
+ },
+ reset() {
+ dispatch(formActions.reset(selectors.STORE_NAME, {}, true))
+ }
+ })
+ )(BaseCreationModal)
+)
+
+export {
+ CreationModal
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/index.js b/src/plugin/cursus/Resources/modules/course/modals/creation/index.js
new file mode 100644
index 00000000000..8809a0f20f2
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/index.js
@@ -0,0 +1,13 @@
+import {registry} from '#/main/app/modals/registry'
+
+// gets the modal component
+import {CreationModal} from '#/plugin/cursus/course/modals/creation/containers/modal'
+
+const MODAL_COURSE_TYPE_CREATION = 'MODAL_COURSE_TYPE_CREATION'
+
+// make the modal available for use
+registry.add(MODAL_COURSE_TYPE_CREATION, CreationModal)
+
+export {
+ MODAL_COURSE_TYPE_CREATION
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/store/index.js b/src/plugin/cursus/Resources/modules/course/modals/creation/store/index.js
new file mode 100644
index 00000000000..7fb8ecc8817
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/store/index.js
@@ -0,0 +1,7 @@
+import {reducer} from '#/plugin/cursus/course/modals/creation/store/reducer'
+import {selectors} from '#/plugin/cursus/course/modals/creation/store/selectors'
+
+export {
+ reducer,
+ selectors
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/store/reducer.js b/src/plugin/cursus/Resources/modules/course/modals/creation/store/reducer.js
new file mode 100644
index 00000000000..c00c8657518
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/store/reducer.js
@@ -0,0 +1,10 @@
+import {makeFormReducer} from '#/main/app/content/form/store'
+import {selectors} from '#/plugin/cursus/course/modals/creation/store/selectors'
+
+const reducer = makeFormReducer(selectors.STORE_NAME, {
+ new: true
+})
+
+export {
+ reducer
+}
diff --git a/src/plugin/cursus/Resources/modules/course/modals/creation/store/selectors.js b/src/plugin/cursus/Resources/modules/course/modals/creation/store/selectors.js
new file mode 100644
index 00000000000..2bac4df9406
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/course/modals/creation/store/selectors.js
@@ -0,0 +1,5 @@
+const STORE_NAME = 'courseTypeCreation'
+
+export const selectors = {
+ STORE_NAME
+}
diff --git a/src/plugin/cursus/Resources/modules/course/store/actions.js b/src/plugin/cursus/Resources/modules/course/store/actions.js
index 3881ab22a1b..0b392860bb2 100644
--- a/src/plugin/cursus/Resources/modules/course/store/actions.js
+++ b/src/plugin/cursus/Resources/modules/course/store/actions.js
@@ -37,25 +37,27 @@ actions.open = (courseSlug, force = false) => (dispatch, getState) => {
}
actions.openForm = (courseSlug = null, defaultProps = {}, workspace = null) => (dispatch) => {
- if (!courseSlug) {
- if(workspace) {
- defaultProps = {
- ...defaultProps,
- _workspaceType: 'workspace',
- workspace: workspace
- }
+ if(workspace) {
+ defaultProps = {
+ ...defaultProps,
+ _workspaceType: workspace.meta.model ? 'model' : 'workspace',
+ workspace: workspace
}
return dispatch(formActions.resetForm(selectors.FORM_NAME, defaultProps, true))
}
- return dispatch({
- [API_REQUEST]: {
- url: ['apiv2_cursus_course_get', {field: 'slug', id: courseSlug}],
- silent: true,
- before: () => dispatch(formActions.resetForm(selectors.FORM_NAME, null, false)),
- success: (data) => dispatch(formActions.resetForm(selectors.FORM_NAME, data))
- }
- })
+ if (courseSlug) {
+ return dispatch({
+ [API_REQUEST]: {
+ url: ['apiv2_cursus_course_get', {field: 'slug', id: courseSlug}],
+ silent: true,
+ before: () => dispatch(formActions.resetForm(selectors.FORM_NAME, null, true)),
+ success: (data) => dispatch(formActions.resetForm(selectors.FORM_NAME, data))
+ }
+ })
+ } else {
+ return dispatch(formActions.resetForm(selectors.FORM_NAME, defaultProps, true))
+ }
}
actions.openSession = (sessionId = null, force = false) => (dispatch, getState) => {
diff --git a/src/plugin/cursus/Resources/modules/plugin.js b/src/plugin/cursus/Resources/modules/plugin.js
index 631a1b00fc6..b5dcc770068 100644
--- a/src/plugin/cursus/Resources/modules/plugin.js
+++ b/src/plugin/cursus/Resources/modules/plugin.js
@@ -42,7 +42,9 @@ registry.add('ClarolineCursusBundle', {
'open' : () => { return import(/* webpackChunkName: "training-action-course-open" */ '#/plugin/cursus/actions/course/open') },
'edit' : () => { return import(/* webpackChunkName: "training-action-course-edit" */ '#/plugin/cursus/actions/course/edit') },
'export-pdf': () => { return import(/* webpackChunkName: "training-action-course-export-pdf" */ '#/plugin/cursus/actions/course/export-pdf') },
- 'delete' : () => { return import(/* webpackChunkName: "training-action-course-delete" */ '#/plugin/cursus/actions/course/delete') }
+ 'delete' : () => { return import(/* webpackChunkName: "training-action-course-delete" */ '#/plugin/cursus/actions/course/delete') },
+ 'archive' : () => { return import(/* webpackChunkName: "training-action-course-archive" */ '#/plugin/cursus/actions/course/archive') },
+ 'restore' : () => { return import(/* webpackChunkName: "training-action-course-restore" */ '#/plugin/cursus/actions/course/restore') }
},
training_presence: {
'export-pdf' : () => { return import(/* webpackChunkName: "training-action-presence-export-pdf" */ '#/plugin/cursus/actions/presence/export-pdf') },
diff --git a/src/plugin/cursus/Resources/modules/session/components/list.jsx b/src/plugin/cursus/Resources/modules/session/components/list.jsx
index b578f0df244..0e3a13d99e9 100644
--- a/src/plugin/cursus/Resources/modules/session/components/list.jsx
+++ b/src/plugin/cursus/Resources/modules/session/components/list.jsx
@@ -23,7 +23,7 @@ const SessionList = (props) =>
}}
primaryAction={(row) => ({
type: LINK_BUTTON,
- target: route(props.course, row, props.path),
+ target: route(row.course, row, props.path),
label: trans('open', {}, 'actions')
})}
delete={props.delete}
diff --git a/src/plugin/cursus/Resources/modules/tools/events/components/tool.jsx b/src/plugin/cursus/Resources/modules/tools/events/components/tool.jsx
index fad91975485..4c918682b97 100644
--- a/src/plugin/cursus/Resources/modules/tools/events/components/tool.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/events/components/tool.jsx
@@ -2,12 +2,13 @@ import React from 'react'
import get from 'lodash/get'
import {PropTypes as T} from 'prop-types'
+import {trans} from '#/main/app/intl'
+import {Tool} from '#/main/core/tool'
+import {LINK_BUTTON} from '#/main/app/buttons'
import {Course} from '#/plugin/cursus/course/containers/main'
import {Course as CourseTypes} from '#/plugin/cursus/prop-types'
import {CourseCreation} from '#/plugin/cursus/course/components/creation'
-import {CourseEdit} from '#/plugin/cursus/course/components/edit'
-
-import {Tool} from '#/main/core/tool'
+import {CourseEditor} from '#/plugin/cursus/course/editor/containers/main'
import {EmptyCourse} from '#/plugin/cursus/course/components/empty'
import {EventsAll} from '#/plugin/cursus/tools/events/components/all'
@@ -15,8 +16,6 @@ import {EventsPublic} from '#/plugin/cursus/tools/events/components/public'
import {EventsDetails} from '#/plugin/cursus/tools/events/containers/details'
import {EventsPresences} from '#/plugin/cursus/tools/events/containers/presences'
import {EventsRegistered} from '#/plugin/cursus/tools/events/components/registered'
-import {LINK_BUTTON} from '#/main/app/buttons'
-import {trans} from '#/main/app/intl'
const EventsTool = (props) =>
}, {
path: '/course/:courseSlug/edit',
onEnter: () => props.openForm(props.course.slug),
- component: CourseEdit
+ component: CourseEditor
}, {
path: '/course',
onEnter: () => {
@@ -79,7 +78,13 @@ const EventsTool = (props) =>
history={params.history}
/>)
} else {
- return ()
+ return (
+
+ )
}
}
}, {
@@ -128,6 +133,7 @@ EventsTool.propTypes = {
type: T.string,
data: T.object
}).isRequired,
+ contextType: T.string,
canEdit: T.bool.isRequired,
canRegister: T.bool.isRequired,
invalidateList: T.func.isRequired,
diff --git a/src/plugin/cursus/Resources/modules/tools/events/containers/tool.jsx b/src/plugin/cursus/Resources/modules/tools/events/containers/tool.jsx
index 8e6416073c8..7d9fe09175d 100644
--- a/src/plugin/cursus/Resources/modules/tools/events/containers/tool.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/events/containers/tool.jsx
@@ -18,6 +18,7 @@ const EventsTool = withReducer(selectors.STORE_NAME, reducer)(
path: toolSelectors.path(state),
course: selectors.course(state),
currentContext: toolSelectors.context(state),
+ contextType: toolSelectors.contextType(state),
canEdit: hasPermission('edit', toolSelectors.toolData(state)),
canRegister: hasPermission('register', toolSelectors.toolData(state))
}),
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/list.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/list.jsx
index fbf3f90c2c0..a10bd11f83c 100644
--- a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/list.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/list.jsx
@@ -2,11 +2,13 @@ import React from 'react'
import {PropTypes as T} from 'prop-types'
import {trans} from '#/main/app/intl/translation'
-import {LINK_BUTTON} from '#/main/app/buttons'
+import {LINK_BUTTON, MODAL_BUTTON} from '#/main/app/buttons'
import {ToolPage} from '#/main/core/tool'
import {CourseList} from '#/plugin/cursus/course/components/list'
+import {CreationType} from '#/plugin/cursus/course/components/type'
import {selectors} from '#/plugin/cursus/tools/trainings/catalog/store'
+import {MODAL_COURSE_TYPE_CREATION} from '#/plugin/cursus/course/modals/creation'
const CatalogList = (props) =>
actions={[
{
name: 'add',
- type: LINK_BUTTON,
+ type: MODAL_BUTTON,
icon: 'fa fa-fw fa-plus',
label: trans('add_course', {}, 'cursus'),
- target: `${props.path}/new`,
+ modal: [MODAL_COURSE_TYPE_CREATION, {
+ path: props.path + '/course'
+ }],
group: trans('management'),
displayed: props.canEdit,
primary: true
@@ -34,12 +38,21 @@ const CatalogList = (props) =>
path={props.path}
name={selectors.LIST_NAME}
url={['apiv2_cursus_course_list']}
- />
+ >
+
+
+
CatalogList.propTypes = {
path: T.string.isRequired,
- canEdit: T.bool.isRequired
+ canEdit: T.bool.isRequired,
+ contextType: T.string,
+ openForm: T.func.isRequired
}
export {
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/main.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/main.jsx
index 7d192aa18d6..7ea1cbae79f 100644
--- a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/main.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/components/main.jsx
@@ -5,8 +5,7 @@ import {Routes} from '#/main/app/router'
import {route} from '#/plugin/cursus/routing'
import {Course} from '#/plugin/cursus/course/containers/main'
-import {Course as CourseTypes} from '#/plugin/cursus/prop-types'
-import {CourseEdit} from '#/plugin/cursus/course/components/edit'
+import {CourseEditor} from '#/plugin/cursus/course/editor/containers/main'
import {CatalogList} from '#/plugin/cursus/tools/trainings/catalog/components/list'
import {CourseCreation} from '#/plugin/cursus/course/components/creation'
@@ -18,17 +17,21 @@ const CatalogMain = (props) =>
path: '/',
exact: true,
render: () => (
-
+
)
}, {
path: '/new',
- onEnter: () => props.openForm(null, CourseTypes.defaultProps),
disabled: !props.canEdit,
component: CourseCreation
}, {
path: '/:slug/edit',
onEnter: (params = {}) => props.openForm(params.slug),
- component: CourseEdit
+ component: CourseEditor
}, {
path: '/:slug',
onEnter: (params = {}) => props.open(params.slug),
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/containers/main.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/containers/main.jsx
index 7b6172de5c7..9b16e206bdf 100644
--- a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/containers/main.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/containers/main.jsx
@@ -14,14 +14,15 @@ const CatalogMain = withReducer(courseSelectors.STORE_NAME, courseReducer)(
(state) => ({
path: toolSelectors.path(state),
course: selectors.course(state),
+ contextType: toolSelectors.contextType(state),
canEdit: hasPermission('edit', toolSelectors.toolData(state))
}),
(dispatch) => ({
open(slug) {
dispatch(courseActions.open(slug))
},
- openForm(slug, defaultProps) {
- dispatch(courseActions.openForm(slug, defaultProps))
+ openForm(slug, defaultProps, workspace = null) {
+ dispatch(courseActions.openForm(slug, defaultProps, workspace))
}
})
)(CatalogMainComponent)
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/store/selectors.js b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/store/selectors.js
index a769eae3f1e..d1dd69aa95b 100644
--- a/src/plugin/cursus/Resources/modules/tools/trainings/catalog/store/selectors.js
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/catalog/store/selectors.js
@@ -10,6 +10,7 @@ const course = createSelector(
(catalog) => catalog.course
)
+
export const selectors = {
STORE_NAME,
LIST_NAME,
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/components/tool.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/components/tool.jsx
index 14cd2fe286d..114dad69777 100644
--- a/src/plugin/cursus/Resources/modules/tools/trainings/components/tool.jsx
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/components/tool.jsx
@@ -1,13 +1,14 @@
import React from 'react'
import {PropTypes as T} from 'prop-types'
+import {trans} from '#/main/app/intl'
import {Tool} from '#/main/core/tool'
+import {LINK_BUTTON} from '#/main/app/buttons'
+import {EventMain} from '#/plugin/cursus/tools/trainings/event/containers/main'
import {CatalogMain} from '#/plugin/cursus/tools/trainings/catalog/containers/main'
import {SessionMain} from '#/plugin/cursus/tools/trainings/session/containers/main'
-import {EventMain} from '#/plugin/cursus/tools/trainings/event/containers/main'
-import {LINK_BUTTON} from '#/main/app/buttons'
-import {trans} from '#/main/app/intl'
+import {TrainingsEditor} from '#/plugin/cursus/tools/trainings/editor/containers/main'
const TrainingsTool = (props) =>
component: EventMain
}
]}
+ editor={TrainingsEditor}
/>
TrainingsTool.propTypes = {
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/containers/menu.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/containers/menu.jsx
deleted file mode 100644
index c22a763b1c6..00000000000
--- a/src/plugin/cursus/Resources/modules/tools/trainings/containers/menu.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import {connect} from 'react-redux'
-
-import {hasPermission} from '#/main/app/security'
-
-import {selectors as toolSelectors} from '#/main/core/tool/store'
-import {selectors as securitySelectors} from '#/main/app/security/store'
-
-import {TrainingsMenu as TrainingsMenuComponent} from '#/plugin/cursus/tools/trainings/components/menu'
-
-const TrainingsMenu = connect(
- (state) => ({
- authenticated: securitySelectors.isAuthenticated(state),
- canEdit: hasPermission('edit', toolSelectors.toolData(state)),
- canRegister: hasPermission('register', toolSelectors.toolData(state))
- })
-)(TrainingsMenuComponent)
-
-export {
- TrainingsMenu
-}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/list.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/list.jsx
new file mode 100644
index 00000000000..46be1bafbd6
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/list.jsx
@@ -0,0 +1,23 @@
+import React from 'react'
+
+import {trans} from '#/main/app/intl'
+import {EditorPage} from '#/main/app/editor'
+
+import {CourseList} from '#/plugin/cursus/course/components/list'
+import {selectors} from '#/plugin/cursus/tools/trainings/editor/store'
+
+const TrainingsEditorArchive = (props) =>
+
+
+
+
+export {
+ TrainingsEditorArchive
+}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/main.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/main.jsx
new file mode 100644
index 00000000000..6321467ad91
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/components/main.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import {PropTypes as T} from 'prop-types'
+
+import {trans} from '#/main/app/intl'
+import {ToolEditor} from '#/main/core/tool/editor/containers/main'
+
+import {TrainingsEditorArchive} from '#/plugin/cursus/tools/trainings/editor/components/list'
+
+const TrainingsEditor = (props) =>
+ (
+
+ )
+ }
+ ]}
+ />
+
+TrainingsEditor.propTypes = {
+ path: T.string.isRequired
+}
+
+export {
+ TrainingsEditor
+}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/containers/main.jsx b/src/plugin/cursus/Resources/modules/tools/trainings/editor/containers/main.jsx
new file mode 100644
index 00000000000..5a78de37f33
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/containers/main.jsx
@@ -0,0 +1,18 @@
+import {connect} from 'react-redux'
+
+import {selectors as toolSelectors} from '#/main/core/tool'
+import {withReducer} from '#/main/app/store/components/withReducer'
+import {reducer, selectors} from '#/plugin/cursus/tools/trainings/editor/store'
+import {TrainingsEditor as TrainingsEditorComponent} from '#/plugin/cursus/tools/trainings/editor/components/main'
+
+const TrainingsEditor = withReducer(selectors.STORE_NAME, reducer)(
+ connect(
+ (state) => ({
+ path: toolSelectors.path(state)
+ })
+ )(TrainingsEditorComponent)
+)
+
+export {
+ TrainingsEditor
+}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/index.js b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/index.js
new file mode 100644
index 00000000000..fd57f63b99f
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/index.js
@@ -0,0 +1,7 @@
+import {reducer} from '#/plugin/cursus/tools/trainings/editor/store/reducer'
+import {selectors} from '#/plugin/cursus/tools/trainings/editor/store/selectors'
+
+export {
+ reducer,
+ selectors
+}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/reducer.js b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/reducer.js
new file mode 100644
index 00000000000..ec2ce163dbe
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/reducer.js
@@ -0,0 +1,10 @@
+import {makeListReducer} from '#/main/app/content/list/store'
+import {selectors} from '#/plugin/cursus/tools/trainings/editor/store/selectors'
+
+const reducer = makeListReducer(selectors.STORE_NAME, {
+ sortBy: {property: 'name', direction: 1}
+})
+
+export {
+ reducer
+}
diff --git a/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/selectors.js b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/selectors.js
new file mode 100644
index 00000000000..62ae67b1795
--- /dev/null
+++ b/src/plugin/cursus/Resources/modules/tools/trainings/editor/store/selectors.js
@@ -0,0 +1,5 @@
+const STORE_NAME = 'trainingsArchived'
+
+export const selectors = {
+ STORE_NAME
+}
diff --git a/src/plugin/cursus/Resources/translations/actions.en.json b/src/plugin/cursus/Resources/translations/actions.en.json
index 19946fc2f60..a85bd4f71e6 100644
--- a/src/plugin/cursus/Resources/translations/actions.en.json
+++ b/src/plugin/cursus/Resources/translations/actions.en.json
@@ -7,5 +7,16 @@
"validate_registration": "Validate registration",
"send_invitation": "Send an invitation",
"open-training": "Open training",
- "move-pending": "Move on pending list"
+ "move-pending": "Move on pending list",
+
+ "archive_training": "Archive training",
+ "archive_training_help": "Hide the training from the list of active trainings.",
+ "archive_training_confirm_title": "{1} Archive a training | [2,Inf[ Archive multiple trainings",
+ "archive_training_confirm_message": "{1} Are you sure you want to archive this training? | [2,Inf[ Are you sure you want to archive these %count% trainings?",
+
+ "restore_training": "Restore training",
+ "restore_training_help": "Restore the training in the list of active trainings.",
+
+ "delete_training": "Delete training",
+ "delete_training_help": "Permanently delete the training."
}
diff --git a/src/plugin/cursus/Resources/translations/actions.fr.json b/src/plugin/cursus/Resources/translations/actions.fr.json
index 2f723276e47..905b0c30a99 100644
--- a/src/plugin/cursus/Resources/translations/actions.fr.json
+++ b/src/plugin/cursus/Resources/translations/actions.fr.json
@@ -7,5 +7,16 @@
"validate_registration": "Valider l'inscription",
"send_invitation": "Envoyer une invitation",
"open-training": "Ouvrir la formation",
- "move-pending": "Déplacer en liste d'attente"
+ "move-pending": "Déplacer en liste d'attente",
+
+ "archive_training": "Archiver la formation",
+ "archive_training_help": "Permet de masquer la formation de la liste des formations actives.",
+ "archive_training_confirm_title": "{1} Archivage d'une formation | [2,Inf[ Archivage de plusieurs formations",
+ "archive_training_confirm_message": "{1} Êtes-vous sûr de vouloir archiver cette formation ? | [2,Inf[ Êtes-vous sûr de vouloir archiver ces %count% formations ?",
+
+ "restore_training": "Restaurer la formation",
+ "restore_training_help": "Permet de restaurer la formation dans la liste des formations actives.",
+
+ "delete_training": "Supprimer la formation",
+ "delete_training_help": "Permet de supprimer définitivement la formation."
}
diff --git a/src/plugin/cursus/Resources/translations/cursus.en.json b/src/plugin/cursus/Resources/translations/cursus.en.json
index dbc840d1b52..e07704bf0ca 100644
--- a/src/plugin/cursus/Resources/translations/cursus.en.json
+++ b/src/plugin/cursus/Resources/translations/cursus.en.json
@@ -7,13 +7,45 @@
"add_pending": "Add to pending list",
"new_session": "New training session",
"new_event": "New event",
+ "public_course": "Public training",
+ "make_course_public": "Make the training public",
+ "make_course_public_help": "Public trainings are visible to anonymous.",
+
+ "create_mode_workspace": "Create a training with a workspace",
+ "create_mode_workspace_desc": "All training sessions take place in the same workspace.",
+ "create_mode_model": "Create a training with a workspace template",
+ "create_mode_model_desc": "A new workspace will be generated from the selected template for each new session. ",
+ "create_mode_empty": "Create an empty training",
+ "create_mode_empty_desc": "Create an empty training so that you can configure it as you wish.",
+ "create_mode_copy": "Copy an existing training",
+ "create_mode_copy_desc": "Duplicate a training and all its content.",
+ "create_mode_organization": "Add from an organization",
+ "create_mode_organization_desc": "Select an existing training from another organization.",
+ "create_mode_existing": "Select an existing training",
+ "create_mode_existing_desc": "Select an existing training.",
+ "create_mode_group_existing": "From existing content",
+
"catalog": "Training catalog",
"course": "Training",
"courses": "Trainings",
- "make_course_public": "Make the training public",
- "make_course_public_help": "Public trainings are visible to anonymous.",
+ "course_appearance_help": "Customize your training's advanced display settings.",
"course_delete_confirm_title": "{1} Training deletion | [2,Inf[ Trainings deletion",
"course_delete_confirm_message": "{1} Are you sure you want to delete this training? | [2,Inf[ Are you sure you want to delete these %count% trainings?",
+ "course_history_help": "Find all the changes made to your training courses.",
+ "course_long_desc_help": "Describe in detail a description of the content of your training (the detailed description is displayed in the \"About\" view instead of the short description).",
+ "course_permissions_help": "Manage your training rights and modifications.",
+ "course_registration_help": "Manage registration and unregistration parameters related to your training.",
+ "course_short_desc_help": "Describe briefly your training (the short description is displayed in the lists and in the \"About\" view).",
+ "course_type_model": "Use an workspace template to generate a new space for each training session.",
+ "course_type_workspace": "Use the same workspace for all training sessions.",
+ "course_workspaces_help": "Manage workspaces parameters related to your training.",
+
+ "course_archived_info": "Archived training",
+ "course_archived_info_help": "This training is archived.",
+
+ "archived_trainings": "Archived trainings",
+ "archived_trainings_help": "Manage archived trainings.",
+
"my_courses": "My trainings",
"new_course": "New training",
"create_session": "Create training session",
diff --git a/src/plugin/cursus/Resources/translations/cursus.fr.json b/src/plugin/cursus/Resources/translations/cursus.fr.json
index 3bb459a0a33..49243ee9a14 100644
--- a/src/plugin/cursus/Resources/translations/cursus.fr.json
+++ b/src/plugin/cursus/Resources/translations/cursus.fr.json
@@ -7,13 +7,46 @@
"add_pending": "Ajouter en liste d'attente",
"new_session": "Nouvelle session de formation",
"new_event": "Nouvelle séance",
+ "public_course": "Formation publique",
+ "make_course_public": "Rendre la formation publique",
+ "make_course_public_help": "Les formations publiques sont visibles par les anonymes.",
+
+ "create_mode_workspace": "Créer une formation avec espace d'activités",
+ "create_mode_workspace_desc": "Toutes les sessions de la formation se déroule dans le même espace d'activités.",
+ "create_mode_model": "Créer une formation avec modèle d'espace d'activités",
+ "create_mode_model_desc": "Un nouvel espace d'activités sera généré à partir du modèle sélectionné pour chaque nouvelle session.",
+ "create_mode_empty": "Créer une formation vide",
+ "create_mode_empty_desc": "Créez une formation vide pour pouvoir la configurer comme vous le souhaitez.",
+ "create_mode_copy": "Copier une formation existante",
+ "create_mode_copy_desc": "Dupliquez une formation ainsi que tous ses contenus.",
+ "create_mode_organization": "Ajouter depuis une organisation",
+ "create_mode_organization_desc": "Sélectionnez une formation existante dans une autre organisation.",
+ "create_mode_existing": "Sélectionner une formation existante",
+ "create_mode_existing_desc": "Sélectionnez une formation existante.",
+ "create_mode_group_existing": "A partir d'un contenu existant",
+
"catalog": "Catalogue de formations",
"course": "Formation",
"courses": "Formations",
- "make_course_public": "Rendre la formation publique",
- "make_course_public_help": "Les formations publiques sont visibles par les anonymes.",
+ "course_appearance_help": "Personnalisez les paramètres d'affichage avancés de votre formation.",
"course_delete_confirm_title": "{1} Suppression d'une formation | [2,Inf[ Suppression de plusieurs formations",
"course_delete_confirm_message": "{1} Êtes-vous sûr de vouloir supprimer ce formation ? | [2,Inf[ Êtes-vous sûr de vouloir supprimer ces %count% formations ?",
+ "course_history_help": "Retrouvez toutes les modifications effectuées sur vos formations.",
+ "course_long_desc_help": "Décrivez de manière détaillée le contenu de votre formation (La description détaillée est affichée sur la vue \"À propos\" à la place de la description courte).",
+ "course_permissions_help": "Gérez les différents droits et de modifications de votre formation.",
+ "course_registration_help": "Gérer les paramètres d'inscription et de désinscription liés à votre formation.",
+ "course_short_desc_help": "Décrivez succintement votre formation (La description courte est affichée dans les listes et sur la vue \"À propos\").",
+ "course_type_model": "Utiliser un modèle d'espace d'activités pour générer un nouvel espace pour chaque session de la formation.",
+ "course_type_workspace": "Utiliser le même espace d'activités pour toutes les sessions de la formation.",
+ "course_workspaces_help": "Gérer les paramètres d'espaces d'activités liés à votre formation.",
+
+ "course_archived_info": "Formation archivée",
+ "course_archived_info_help": "Cette formation est archivée.",
+
+ "archived_trainings": "Formations archivées",
+ "archived_trainings_help": "Gérez les formations archivées.",
+
+
"my_courses": "Mes formations",
"new_course": "Nouvelle formation",
"create_session": "Créer une session de formation",
diff --git a/src/plugin/cursus/Serializer/CourseSerializer.php b/src/plugin/cursus/Serializer/CourseSerializer.php
index 2b3457a4e6e..fda906aad3a 100644
--- a/src/plugin/cursus/Serializer/CourseSerializer.php
+++ b/src/plugin/cursus/Serializer/CourseSerializer.php
@@ -57,7 +57,7 @@ public function __construct(
RoleSerializer $roleSerializer,
OrganizationSerializer $orgaSerializer,
WorkspaceSerializer $workspaceSerializer,
- PanelFacetSerializer $panelFacetSerializer
+ PanelFacetSerializer $panelFacetSerializer,
) {
$this->authorization = $authorization;
$this->eventDispatcher = $eventDispatcher;
@@ -110,6 +110,7 @@ public function serialize(Course $course, array $options = []): array
'updated' => DateNormalizer::normalize($course->getUpdatedAt()),
'duration' => $course->getDefaultSessionDuration(),
'public' => $course->isPublic(),
+ 'archived' => $course->isArchived(),
],
'opening' => [
'session' => $course->getSessionOpening(),
@@ -132,6 +133,7 @@ public function serialize(Course $course, array $options = []): array
'edit' => $this->authorization->isGranted('EDIT', $course),
'delete' => $this->authorization->isGranted('DELETE', $course),
'register' => $this->authorization->isGranted('REGISTER', $course),
+ 'administrate' => $this->authorization->isGranted('ADMINISTRATE', $course),
];
}