Parcourir la source

Merge branch 'develop' of github.com:tracim/tracim_v2 into develop

AlexiCauvin il y a 6 ans
Parent
révision
bf4879180e

+ 9 - 44
backend/tracim_backend/lib/core/content.py Voir le fichier

@@ -924,41 +924,6 @@ class ContentApi(object):
924 924
 
925 925
         return resultset.all()
926 926
 
927
-    def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> typing.List[Content]:
928
-        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
929
-        assert content_type is not None# DYN_REMOVE
930
-        assert isinstance(content_type, str) # DYN_REMOVE
931
-
932
-        resultset = self._base_query(workspace) \
933
-            .filter(Content.workspace_id == Workspace.workspace_id) \
934
-            .filter(Workspace.is_deleted.is_(False)) \
935
-            .order_by(desc(Content.updated))
936
-
937
-        if content_type!=ContentType.Any:
938
-            resultset = resultset.filter(Content.type==content_type)
939
-
940
-        if parent_id:
941
-            resultset = resultset.filter(Content.parent_id==parent_id)
942
-
943
-        result = []
944
-        for item in resultset:
945
-            new_item = None
946
-            if ContentType.Comment == item.type:
947
-                new_item = item.parent
948
-            else:
949
-                new_item = item
950
-
951
-            # INFO - D.A. - 2015-05-20
952
-            # We do not want to show only one item if the last 10 items are
953
-            # comments about one thread for example
954
-            if new_item not in result:
955
-                result.append(new_item)
956
-
957
-            if len(result) >= limit:
958
-                break
959
-
960
-        return result
961
-
962 927
     def get_last_unread(self, parent_id: int, content_type: str,
963 928
                         workspace: Workspace=None, limit=10) -> typing.List[Content]:
964 929
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
@@ -989,7 +954,7 @@ class ContentApi(object):
989 954
             self,
990 955
             workspace: Workspace=None,
991 956
             limit: typing.Optional[int]=None,
992
-            before_datetime: typing.Optional[datetime.datetime]= None,
957
+            before_content: typing.Optional[Content]= None,
993 958
             content_ids: typing.Optional[typing.List[int]] = None,
994 959
     ) -> typing.List[Content]:
995 960
         """
@@ -997,7 +962,8 @@ class ContentApi(object):
997 962
         (last modification of content itself or one of this comment)
998 963
         :param workspace: Workspace to check
999 964
         :param limit: maximum number of elements to return
1000
-        :param before_datetime: date from where we check older content.
965
+        :param before_content: last_active content are only those updated
966
+         before this content given.
1001 967
         :param content_ids: restrict selection to some content ids and
1002 968
         related Comments
1003 969
         :return: list of content
@@ -1021,6 +987,7 @@ class ContentApi(object):
1021 987
 
1022 988
         active_contents = []
1023 989
         too_recent_content = []
990
+        before_content_find = False
1024 991
         for content in resultset:
1025 992
             related_active_content = None
1026 993
             if ContentType.Comment == content.type:
@@ -1028,18 +995,16 @@ class ContentApi(object):
1028 995
             else:
1029 996
                 related_active_content = content
1030 997
 
1031
-            if not before_datetime:
1032
-                before_datetime = datetime.datetime.now()
1033
-            # INFO - D.A. - 2015-05-20
1034
-            # We do not want to show only one item if the last 10 items are
1035
-            # comments about one thread for example
1036 998
             if related_active_content not in active_contents and related_active_content not in too_recent_content:  # nopep8
1037
-                # we verify that content is old enough
1038
-                if content.updated < before_datetime:
999
+
1000
+                if not before_content or before_content_find:
1039 1001
                     active_contents.append(related_active_content)
1040 1002
                 else:
1041 1003
                     too_recent_content.append(related_active_content)
1042 1004
 
1005
+                if before_content and related_active_content == before_content:
1006
+                    before_content_find = True
1007
+
1043 1008
             if limit and len(active_contents) >= limit:
1044 1009
                 break
1045 1010
 

+ 10 - 2
backend/tracim_backend/lib/core/workspace.py Voir le fichier

@@ -3,9 +3,11 @@ import typing
3 3
 
4 4
 from sqlalchemy.orm import Query
5 5
 from sqlalchemy.orm import Session
6
+from sqlalchemy.orm.exc import NoResultFound
6 7
 
7 8
 from tracim_backend import CFG
8 9
 from tracim_backend.exceptions import EmptyLabelNotAllowed
10
+from tracim_backend.exceptions import WorkspaceNotFound
9 11
 from tracim_backend.lib.utils.translation import fake_translator as _
10 12
 
11 13
 from tracim_backend.lib.core.userworkspace import RoleApi
@@ -132,10 +134,16 @@ class WorkspaceApi(object):
132 134
         return workspace
133 135
 
134 136
     def get_one(self, id):
135
-        return self._base_query().filter(Workspace.workspace_id == id).one()
137
+        try:
138
+            return self._base_query().filter(Workspace.workspace_id == id).one()
139
+        except NoResultFound as exc:
140
+            raise WorkspaceNotFound('workspace {} does not exist or not visible for user'.format(id)) from exc  # nopep8
136 141
 
137 142
     def get_one_by_label(self, label: str) -> Workspace:
138
-        return self._base_query().filter(Workspace.label == label).one()
143
+        try:
144
+            return self._base_query().filter(Workspace.label == label).one()
145
+        except NoResultFound as exc:
146
+            raise WorkspaceNotFound('workspace {} does not exist or not visible for user'.format(id)) from exc  # nopep8
139 147
 
140 148
     """
141 149
     def get_one_for_current_user(self, id):

+ 2 - 2
backend/tracim_backend/models/context_models.py Voir le fichier

@@ -226,10 +226,10 @@ class ActiveContentFilter(object):
226 226
     def __init__(
227 227
             self,
228 228
             limit: int = None,
229
-            before_datetime: datetime = None,
229
+            before_content_id: datetime = None,
230 230
     ):
231 231
         self.limit = limit
232
-        self.before_datetime = before_datetime
232
+        self.before_content_id = before_content_id
233 233
 
234 234
 
235 235
 class ContentIdsQuery(object):

Fichier diff supprimé car celui-ci est trop grand
+ 1806 - 170
backend/tracim_backend/tests/functional/test_user.py


+ 4 - 4
backend/tracim_backend/tests/library/test_content_api.py Voir le fichier

@@ -2247,26 +2247,26 @@ class TestContentApi(DefaultTest):
2247 2247
         secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2248 2248
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2249 2249
 
2250
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=datetime.datetime.now())  # nopep8
2250
+        last_actives = api.get_last_active(workspace=workspace, limit=2)  # nopep8
2251 2251
         assert len(last_actives) == 2
2252 2252
         # comment is newest than page2
2253 2253
         assert last_actives[0] == firstly_created_but_recently_commented
2254 2254
         assert last_actives[1] == secondly_created_but_not_commented
2255 2255
 
2256
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2256
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2257 2257
         assert len(last_actives) == 2
2258 2258
         # last updated content is newer than other one despite creation
2259 2259
         # of the other is more recent
2260 2260
         assert last_actives[0] == firstly_created_but_recently_updated
2261 2261
         assert last_actives[1] == secondly_created_but_not_updated
2262 2262
 
2263
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2263
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2264 2264
         assert len(last_actives) == 2
2265 2265
         # creation order is inverted here as last created is last active
2266 2266
         assert last_actives[0] == secondly_created
2267 2267
         assert last_actives[1] == firstly_created
2268 2268
 
2269
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2269
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2270 2270
         assert len(last_actives) == 1
2271 2271
         # folder subcontent modification does not change folder order
2272 2272
         assert last_actives[0] == main_folder

+ 5 - 3
backend/tracim_backend/views/core_api/schemas.py Voir le fichier

@@ -360,9 +360,11 @@ class ActiveContentFilterQuerySchema(marshmallow.Schema):
360 360
                     'the first limit elem (according to offset)',
361 361
         validate=Range(min=0, error="Value must be positive or 0"),
362 362
     )
363
-    before_datetime = marshmallow.fields.DateTime(
364
-        format=DATETIME_FORMAT,
365
-        description='return only content lastly updated before this date',
363
+    before_content_id = marshmallow.fields.Int(
364
+        example=41,
365
+        default=None,
366
+        allow_none=True,
367
+        description='return only content updated before this content',
366 368
     )
367 369
     @post_load
368 370
     def make_content_filter(self, data):

+ 10 - 2
backend/tracim_backend/views/core_api/user_controller.py Voir le fichier

@@ -11,6 +11,7 @@ from tracim_backend.lib.core.group import GroupApi
11 11
 from tracim_backend.lib.core.user import UserApi
12 12
 from tracim_backend.lib.core.workspace import WorkspaceApi
13 13
 from tracim_backend.lib.core.content import ContentApi
14
+from tracim_backend.models.contents import ContentTypeLegacy as ContentType
14 15
 from tracim_backend.views.controllers import Controller
15 16
 from tracim_backend.lib.utils.authorization import require_same_user_or_profile
16 17
 from tracim_backend.lib.utils.authorization import require_profile
@@ -265,10 +266,17 @@ class UserController(Controller):
265 266
         workspace = None
266 267
         if hapic_data.path.workspace_id:
267 268
             workspace = wapi.get_one(hapic_data.path.workspace_id)
269
+        before_content = None
270
+        if content_filter.before_content_id:
271
+            before_content = api.get_one(
272
+                content_id=content_filter.before_content_id,
273
+                workspace=workspace,
274
+                content_type=ContentType.Any
275
+            )
268 276
         last_actives = api.get_last_active(
269 277
             workspace=workspace,
270 278
             limit=content_filter.limit or None,
271
-            before_datetime=content_filter.before_datetime or None,
279
+            before_content=before_content,
272 280
         )
273 281
         return [
274 282
             api.get_content_in_context(content)
@@ -302,7 +310,7 @@ class UserController(Controller):
302 310
         last_actives = api.get_last_active(
303 311
             workspace=workspace,
304 312
             limit=None,
305
-            before_datetime=None,
313
+            before_content=None,
306 314
             content_ids=hapic_data.query.contents_ids or None
307 315
         )
308 316
         return [

+ 1 - 0
frontend/package.json Voir le fichier

@@ -32,6 +32,7 @@
32 32
     "js-cookie": "^2.2.0",
33 33
     "prop-types": "^15.6.0",
34 34
     "query-string": "^6.1.0",
35
+    "radium": "^0.24.1",
35 36
     "react": "^16.0.0",
36 37
     "react-animate-height": "^0.10.10",
37 38
     "react-dom": "^16.0.0",

+ 40 - 8
frontend/src/action-creator.async.js Voir le fichier

@@ -11,6 +11,8 @@ import {
11 11
   setUserRole,
12 12
   WORKSPACE,
13 13
   WORKSPACE_LIST,
14
+  WORKSPACE_DETAIL,
15
+  WORKSPACE_MEMBER_LIST,
14 16
   FOLDER,
15 17
   setFolderData,
16 18
   APP_LIST,
@@ -57,8 +59,20 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
57 59
   })()
58 60
   if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
59 61
 
60
-  if ([200, 204, 304].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
61
-  else if ([400, 404, 500].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
62
+  // if ([200, 204, 304].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
63
+  // else if ([400, 404, 500].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
64
+  switch (fetchResult.status) {
65
+    case 200:
66
+    case 204:
67
+    case 304:
68
+      dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
69
+      break
70
+    case 400:
71
+    case 404:
72
+    case 500:
73
+      dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
74
+      break
75
+  }
62 76
 
63 77
   return fetchResult
64 78
 }
@@ -161,9 +175,9 @@ export const getWorkspaceList = user => dispatch => {
161 175
   })
162 176
 }
163 177
 
164
-export const getWorkspaceContentList = (user, idWorkspace, idParent) => dispatch => {
178
+export const getWorkspaceDetail = (user, idWorkspace) => dispatch => {
165 179
   return fetchWrapper({
166
-    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents?parent_id=${idParent}`,
180
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}`,
167 181
     param: {
168 182
       headers: {
169 183
         ...FETCH_CONFIG.headers,
@@ -171,16 +185,34 @@ export const getWorkspaceContentList = (user, idWorkspace, idParent) => dispatch
171 185
       },
172 186
       method: 'GET'
173 187
     },
174
-    actionName: WORKSPACE,
188
+    actionName: WORKSPACE_DETAIL,
175 189
     dispatch
176 190
   })
177 191
 }
178 192
 
179
-export const getWorkspaceContent = (idWorkspace, idContent) => dispatch => {
193
+export const getWorkspaceMemberList = (user, idWorkspace) => dispatch => {
180 194
   return fetchWrapper({
181
-    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/${idContent}`,
195
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/members`,
182 196
     param: {
183
-      headers: {...FETCH_CONFIG.headers},
197
+      headers: {
198
+        ...FETCH_CONFIG.headers,
199
+        'Authorization': 'Basic ' + user.auth
200
+      },
201
+      method: 'GET'
202
+    },
203
+    actionName: WORKSPACE_MEMBER_LIST,
204
+    dispatch
205
+  })
206
+}
207
+
208
+export const getWorkspaceContentList = (user, idWorkspace, idParent) => dispatch => {
209
+  return fetchWrapper({
210
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents?parent_id=${idParent}`,
211
+    param: {
212
+      headers: {
213
+        ...FETCH_CONFIG.headers,
214
+        'Authorization': 'Basic ' + user.auth
215
+      },
184 216
       method: 'GET'
185 217
     },
186 218
     actionName: WORKSPACE,

+ 24 - 14
frontend/src/action-creator.sync.js Voir le fichier

@@ -15,13 +15,13 @@ export const addFlashMessage = msg => ({ type: `${ADD}/${FLASH_MESSAGE}`, msg })
15 15
 export const removeFlashMessage = msg => ({ type: `${REMOVE}/${FLASH_MESSAGE}`, msg })
16 16
 
17 17
 export const USER = 'User'
18
-export const USER_LOGIN = 'User/Login'
19
-export const USER_LOGOUT = 'User/Logout'
20
-export const USER_DATA = 'User/Data'
21
-export const USER_ROLE = 'User/Role'
22
-export const USER_CONNECTED = 'User/Connected'
23
-export const USER_DISCONNECTED = 'User/Disconnected'
24
-export const USER_LANG = 'User/Lang'
18
+export const USER_LOGIN = `${USER}/Login`
19
+export const USER_LOGOUT = `${USER}/Logout`
20
+export const USER_DATA = `${USER}/Data`
21
+export const USER_ROLE = `${USER}/Role`
22
+export const USER_CONNECTED = `${USER}/Connected`
23
+export const USER_DISCONNECTED = `${USER}/Disconnected`
24
+export const USER_LANG = `${USER}/Lang`
25 25
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
26 26
 export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
27 27
 export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
@@ -31,20 +31,30 @@ export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNo
31 31
   ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
32 32
 
33 33
 export const WORKSPACE = 'Workspace'
34
-export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `${SET}/${WORKSPACE}/Content`, workspaceContent, filterStr })
34
+export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
35
+export const setWorkspaceContentList = (workspaceContentList, filterStr = '') => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList, filterStr })
35 36
 export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
36 37
 
37
-export const FOLDER = 'Folder'
38
-export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
39
-
40
-export const WORKSPACE_LIST = 'WorkspaceList'
38
+export const WORKSPACE_LIST = `${WORKSPACE}/List`
41 39
 export const updateWorkspaceListData = workspaceList => ({ type: `${UPDATE}/${WORKSPACE_LIST}`, workspaceList })
42 40
 export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
43 41
 
44
-export const APP_LIST = 'App/List'
42
+export const WORKSPACE_DETAIL = `${WORKSPACE}/Detail`
43
+export const setWorkspaceDetail = workspaceDetail => ({ type: `${SET}/${WORKSPACE_DETAIL}`, workspaceDetail })
44
+
45
+export const WORKSPACE_MEMBER = `${WORKSPACE}/Member`
46
+export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
47
+export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
48
+
49
+export const FOLDER = 'Folder'
50
+export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
51
+
52
+export const APP = 'App'
53
+export const APP_LIST = `${APP}/List`
45 54
 export const setAppList = appList => ({ type: `${SET}/${APP_LIST}`, appList })
46 55
 
47
-export const CONTENT_TYPE_LIST = 'ContentType/List'
56
+export const CONTENT_TYPE = 'ContentType'
57
+export const CONTENT_TYPE_LIST = `${CONTENT_TYPE}/List`
48 58
 export const setContentTypeList = contentTypeList => ({ type: `${SET}/${CONTENT_TYPE_LIST}`, contentTypeList })
49 59
 
50 60
 export const LANG = 'Lang'

+ 2 - 0
frontend/src/component/Workspace/ContentItem.jsx Voir le fichier

@@ -4,6 +4,8 @@ import classnames from 'classnames'
4 4
 import BtnExtandedAction from './BtnExtandedAction.jsx'
5 5
 
6 6
 const ContentItem = props => {
7
+  if (props.contentType === null) return null // this means the endpoint system/content_type hasn't responded yet
8
+
7 9
   const status = props.contentType.availableStatuses.find(s => s.slug === props.statusSlug)
8 10
   return (
9 11
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>

+ 3 - 3
frontend/src/component/Workspace/OpenContentApp.jsx Voir le fichier

@@ -6,11 +6,11 @@ import appFactory from '../../appFactory.js'
6 6
 // @FIXME Côme - 2018/07/31 - should this be in a component like AppFeatureManager ?
7 7
 export class OpenContentApp extends React.Component {
8 8
   openContentApp = () => {
9
-    const { idWorkspace, appOpenedType, user, workspaceContent, contentType, renderAppFeature, dispatchCustomEvent, match } = this.props
9
+    const { idWorkspace, appOpenedType, user, workspaceContentList, contentType, renderAppFeature, dispatchCustomEvent, match } = this.props
10 10
 
11 11
     if (isNaN(idWorkspace) || idWorkspace === -1) return
12 12
 
13
-    if (['type', 'idcts'].every(p => p in match.params) && match.params.type !== 'contents' && workspaceContent.length) {
13
+    if (['type', 'idcts'].every(p => p in match.params) && match.params.type !== 'contents' && workspaceContentList.length) {
14 14
       if (isNaN(match.params.idcts) || !contentType.map(c => c.slug).includes(match.params.type)) return
15 15
 
16 16
       const contentToOpen = {
@@ -57,5 +57,5 @@ export class OpenContentApp extends React.Component {
57 57
   }
58 58
 }
59 59
 
60
-const mapStateToProps = ({ user, workspaceContent, contentType }) => ({ user, workspaceContent, contentType })
60
+const mapStateToProps = ({ user, workspaceContentList, contentType }) => ({ user, workspaceContentList, contentType })
61 61
 export default withRouter(connect(mapStateToProps)(appFactory(OpenContentApp)))

+ 1 - 1
frontend/src/component/Workspace/OpenCreateContentApp.jsx Voir le fichier

@@ -39,5 +39,5 @@ export class OpenCreateContentApp extends React.Component {
39 39
   }
40 40
 }
41 41
 
42
-const mapStateToProps = ({ user, workspaceContent, contentType }) => ({ user, workspaceContent, contentType })
42
+const mapStateToProps = ({ user, contentType }) => ({ user, contentType })
43 43
 export default withRouter(connect(mapStateToProps)(appFactory(OpenCreateContentApp)))

+ 3 - 3
frontend/src/container/AppFullscreenManager.jsx Voir le fichier

@@ -17,7 +17,7 @@ class AppFullscreenManager extends React.Component {
17 17
   componentDidMount = () => this.setState({AmIMounted: true})
18 18
 
19 19
   render () {
20
-    const { user, renderAppFullscreen } = this.props
20
+    const { props } = this
21 21
 
22 22
     return (
23 23
       <div className='sidebarpagecontainer'>
@@ -28,12 +28,12 @@ class AppFullscreenManager extends React.Component {
28 28
         {this.state.AmIMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
29 29
           <div className='emptyDiForRoute'>
30 30
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
31
-              renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, user, {})
31
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})
32 32
               return null
33 33
             }} />
34 34
 
35 35
             <Route path={PAGE.ADMIN.USER} render={() => {
36
-              renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'user'}, user, {})
36
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'user'}, props.user, {})
37 37
               return null
38 38
             }} />
39 39
           </div>

+ 178 - 377
frontend/src/container/Dashboard.jsx Voir le fichier

@@ -2,15 +2,23 @@ import React from 'react'
2 2
 import { connect } from 'react-redux'
3 3
 import Sidebar from './Sidebar.jsx'
4 4
 import imgProfil from '../img/imgProfil.png'
5
+import { translate } from 'react-i18next'
6
+import Radium from 'radium'
7
+import color from 'color'
8
+import {
9
+  PageWrapper,
10
+  PageTitle,
11
+  PageContent
12
+} from 'tracim_frontend_lib'
5 13
 import {
6
-  getAppList,
7
-  getContentTypeList, getWorkspaceList
14
+  getWorkspaceDetail,
15
+  getWorkspaceMemberList
8 16
 } from '../action-creator.async.js'
9 17
 import {
10
-  setAppList,
11
-  setContentTypeList, setWorkspaceListIsOpenInSidebar, updateWorkspaceListData
18
+  addFlashMessage,
19
+  setWorkspaceDetail,
20
+  setWorkspaceMemberList
12 21
 } from '../action-creator.sync.js'
13
-import { translate } from 'react-i18next'
14 22
 
15 23
 class Dashboard extends React.Component {
16 24
   constructor (props) {
@@ -25,28 +33,28 @@ class Dashboard extends React.Component {
25 33
   }
26 34
 
27 35
   async componentDidMount () {
28
-    const { workspaceIdInUrl } = this.state
29
-    const { user, workspaceList, app, contentType, dispatch } = this.props
30
-
31
-    console.log('<Dashboard> componentDidMount')
32
-
33
-    if (app.length === 0) {
34
-      const fetchGetAppList = await dispatch(getAppList(user))
35
-      if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
36
-    }
37
-
38
-    if (contentType.length === 0) {
39
-      const fetchGetContentTypeList = await dispatch(getContentTypeList(user))
40
-      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
36
+    const { props, state } = this
37
+
38
+    const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
39
+    switch (fetchWorkspaceDetail.status) {
40
+      case 200:
41
+        props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json))
42
+        break
43
+      case 400:
44
+      case 500:
45
+        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
46
+        break
41 47
     }
42 48
 
43
-    if (user.user_id !== -1 && workspaceList.length === 0) {
44
-      const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
45
-
46
-      if (fetchGetWorkspaceList.status === 200) {
47
-        dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
48
-        dispatch(setWorkspaceListIsOpenInSidebar(workspaceIdInUrl || fetchGetWorkspaceList.json[0].workspace_id, true))
49
-      }
49
+    const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
50
+    switch (fetchWorkspaceMemberList.status) {
51
+      case 200:
52
+        props.dispatch(setWorkspaceMemberList(fetchWorkspaceMemberList.json))
53
+        break
54
+      case 400:
55
+      case 500:
56
+        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
57
+        break
50 58
     }
51 59
   }
52 60
 
@@ -67,152 +75,118 @@ class Dashboard extends React.Component {
67 75
   }))
68 76
 
69 77
   render () {
78
+    const { props, state } = this
79
+
70 80
     return (
71 81
       <div className='sidebarpagecontainer'>
72 82
         <Sidebar />
73 83
 
74
-        <div className='dashboard'>
75
-          <div className='container-fluid nopadding'>
76
-            <div className='dashboard__header mb-5'>
77
-              <div className='pageTitleGeneric dashboard__header__title d-flex align-items-center'>
78
-                <div className='pageTitleGeneric__title dashboard__header__title__text mr-3'>
79
-                  {this.props.t('Dashboard')}
80
-                </div>
81
-                <div className='dashboard__header__acces' />
82
-              </div>
83
-              <div className='dashboard__header__advancedmode mr-3'>
84
-                <button type='button' className='btn btn-primary'>
85
-                  {this.props.t('Active advanced Dashboard')}
86
-                </button>
87
-              </div>
84
+        <PageWrapper customeClass='dashboard'>
85
+          <PageTitle
86
+            parentClass='dashboard__header'
87
+            title={props.t('Dashboard')}
88
+            subtitle={''}
89
+          >
90
+            <div className='dashboard__header__advancedmode mr-3'>
91
+              <button type='button' className='btn btn-primary'>
92
+                {props.t('Active advanced Dashboard')}
93
+              </button>
88 94
             </div>
95
+          </PageTitle>
89 96
 
90
-            <div className='dashboard__wkswrapper'>
97
+          <PageContent>
98
+            <div className='dashboard__workspace-wrapper'>
91 99
               <div className='dashboard__workspace'>
92 100
                 <div className='dashboard__workspace__title'>
93
-                  Développement tracim
101
+                  {props.curWs.label}
94 102
                 </div>
95 103
 
96 104
                 <div className='dashboard__workspace__detail'>
97
-                  Ligne directive pour le prochain design de Tracim et des futurs fonctionnalités.
105
+                  {props.curWs.description}
98 106
                 </div>
99 107
               </div>
100
-              <div className='dashboard__userstatut'>
101 108
 
109
+              <div className='dashboard__userstatut'>
102 110
                 <div className='dashboard__userstatut__role'>
103
-                  <div className='dashboard__userstatut__role__text'>
104
-                    Hi ! Alexi, vous êtes actuellement
111
+                  <div className='dashboard__userstatut__role__msg'>
112
+                    {props.t(`Hi ! ${props.user.public_name}, vous êtes actuellement`)}
105 113
                   </div>
106
-                  <div className='dashboard__userstatut__role__rank'>
107
-                    <div className='dashboard__userstatut__role__rank__icon'>
114
+
115
+                  <div className='dashboard__userstatut__role__definition'>
116
+                    <div className='dashboard__userstatut__role__definition__icon'>
108 117
                       <i className='fa fa-graduation-cap' />
109 118
                     </div>
110
-                    <div className='dashboard__userstatut__role__rank__rolename'>
111
-                      Gestionnaire de projet
119
+
120
+                    <div className='dashboard__userstatut__role__definition__text'>
121
+                      {(member => member ? member.role : '')(props.curWs.member.find(m => m.id === props.user.user_id))}
112 122
                     </div>
113 123
                   </div>
114 124
                 </div>
115 125
 
116 126
                 <div className='dashboard__userstatut__notification'>
117 127
                   <div className='dashboard__userstatut__notification__text'>
118
-                    Vous êtes abonné(e) aux notifications de ce workspace
128
+                    {props.t("You have subscribed to this workspace's notifications")} (nyi)
119 129
                   </div>
120
-                  {this.state.displayNotifBtn === false &&
121
-                    <div
122
-                      className='dashboard__userstatut__notification__btn btn btn-outline-primary'
123
-                      onClick={this.handleToggleNotifBtn}
124
-                    >
125
-                      {this.props.t('Change your status')}
126
-                    </div>
127
-                  }
128 130
 
129
-                  {this.state.displayNotifBtn === true &&
130
-                    <div className='dashboard__userstatut__notification__subscribe dropdown'>
131
-                      <button className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
132
-                        Abonné(e)
133
-                      </button>
134
-                      <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
135
-                        <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
136
-                          {this.props.t('subscriber')}
137
-                        </div>
138
-                        <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
139
-                          {this.props.t('unsubscribed')}
131
+                  {state.displayNotifBtn
132
+                    ? (
133
+                      <div className='dashboard__userstatut__notification__subscribe dropdown'>
134
+                        <button
135
+                          className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle'
136
+                          type='button'
137
+                          id='dropdownMenuButton'
138
+                          data-toggle='dropdown'
139
+                          aria-haspopup='true'
140
+                          aria-expanded='false'
141
+                        >
142
+                          Abonné(e)
143
+                        </button>
144
+
145
+                        <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
146
+                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
147
+                            {props.t('subscriber')}
148
+                          </div>
149
+                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
150
+                            {props.t('unsubscribed')}
151
+                          </div>
140 152
                         </div>
141 153
                       </div>
142
-                    </div>
154
+                    )
155
+                    : (
156
+                      <div
157
+                        className='dashboard__userstatut__notification__btn btn btn-outline-primary'
158
+                        onClick={this.handleToggleNotifBtn}
159
+                      >
160
+                        {props.t('Change your status')}
161
+                      </div>
162
+                    )
143 163
                   }
144 164
                 </div>
145 165
               </div>
146 166
             </div>
147 167
 
148 168
             <div className='dashboard__calltoaction justify-content-xl-center'>
149
-              <div className='dashboard__calltoaction__button btnaction btnthread'>
150
-                <div className='dashboard__calltoaction__button__text'>
151
-                  <div className='dashboard__calltoaction__button__text__icon'>
152
-                    <i className='fa fa-comments-o' />
153
-                  </div>
154
-                  <div className='dashboard__calltoaction__button__text__title'>
155
-                    {this.props.t('Start a new Thread')}
156
-                  </div>
157
-                </div>
158
-              </div>
159
-
160
-              <div className='dashboard__calltoaction__button btnaction writefile'>
161
-                <div className='dashboard__calltoaction__button__text'>
162
-                  <div className='dashboard__calltoaction__button__text__icon'>
163
-                    <i className='fa fa-file-text-o' />
164
-                  </div>
165
-                  <div className='dashboard__calltoaction__button__text__title'>
166
-                    {this.props.t('Writing a document')}
167
-                  </div>
168
-                </div>
169
-              </div>
170
-
171
-              <div className='dashboard__calltoaction__button btnaction importfile'>
172
-                <div className='dashboard__calltoaction__button__text'>
173
-                  <div className='dashboard__calltoaction__button__text__icon'>
174
-                    <i className='fa fa-paperclip' />
175
-                  </div>
176
-                  <div className='dashboard__calltoaction__button__text__title'>
177
-                    {this.props.t('Upload a file')}
178
-                  </div>
179
-                </div>
180
-              </div>
181
-
182
-              {/*
183
-                <div className='dashboard__calltoaction__button btnaction visioconf'>
184
-                  <div className='dashboard__calltoaction__button__text'>
185
-                    <div className='dashboard__calltoaction__button__text__icon'>
186
-                      <i className='fa fa-video-camera' />
187
-                    </div>
188
-                    <div className='dashboard__calltoaction__button__text__title'>
189
-                      {this.props.t('Start a videoconference')}
190
-                    </div>
191
-                  </div>
192
-                </div>
193
-
194
-                <div className='dashboard__calltoaction__button btnaction calendar'>
169
+              {props.contentType.map(ct =>
170
+                <div
171
+                  className='dashboard__calltoaction__button btnaction'
172
+                  style={{
173
+                    backgroundColor: ct.hexcolor,
174
+                    ':hover': {
175
+                      backgroundColor: color(ct.hexcolor).darken(0.15).hexString()
176
+                    }
177
+                  }}
178
+                  key={ct.label}
179
+                >
195 180
                   <div className='dashboard__calltoaction__button__text'>
196 181
                     <div className='dashboard__calltoaction__button__text__icon'>
197
-                      <i className='fa fa-calendar' />
182
+                      <i className={`fa fa-${ct.faIcon}`} />
198 183
                     </div>
199 184
                     <div className='dashboard__calltoaction__button__text__title'>
200
-                      {this.props.t('View the Calendar')}
185
+                      {ct.creationLabel}
201 186
                     </div>
202 187
                   </div>
203 188
                 </div>
204
-              */ }
205
-
206
-              <div className='dashboard__calltoaction__button btnaction explore'>
207
-                <div className='dashboard__calltoaction__button__text'>
208
-                  <div className='dashboard__calltoaction__button__text__icon'>
209
-                    <i className='fa fa-folder-open-o' />
210
-                  </div>
211
-                  <div className='dashboard__calltoaction__button__text__title'>
212
-                    {this.props.t('Explore the workspace')}
213
-                  </div>
214
-                </div>
215
-              </div>
189
+              )}
216 190
             </div>
217 191
 
218 192
             <div className='dashboard__wksinfo'>
@@ -227,57 +201,13 @@ class Dashboard extends React.Component {
227 201
                   </div>
228 202
                 </div>
229 203
                 <div className='dashboard__activity__wrapper'>
230
-                  <div className='dashboard__activity__workspace'>
231
-                    <div className='dashboard__activity__workspace__icon'>
232
-                      <i className='fa fa-comments-o' />
233
-                    </div>
234
-                    <div className='dashboard__activity__workspace__name'>
235
-                      <span>Développement Tracim</span>
236
-                    </div>
237
-                  </div>
238
-
239
-                  <div className='dashboard__activity__workspace'>
240
-                    <div className='dashboard__activity__workspace__icon'>
241
-                      <i className='fa fa-list-ul' />
242
-                    </div>
243
-                    <div className='dashboard__activity__workspace__name'>
244
-                      Mission externe
245
-                    </div>
246
-                  </div>
247
-
248
-                  <div className='dashboard__activity__workspace'>
249
-                    <div className='dashboard__activity__workspace__icon'>
250
-                      <i className='fa fa-list-ul' />
251
-                    </div>
252
-                    <div className='dashboard__activity__workspace__name'>
253
-                      Recherche et developpement
254
-                    </div>
255
-                  </div>
256
-
257
-                  <div className='dashboard__activity__workspace'>
258
-                    <div className='dashboard__activity__workspace__icon'>
259
-                      <i className='fa fa-file-text-o' />
260
-                    </div>
261
-                    <div className='dashboard__activity__workspace__name'>
262
-                      <span>Marketing</span>
263
-                    </div>
264
-                  </div>
265 204
 
266 205
                   <div className='dashboard__activity__workspace'>
267 206
                     <div className='dashboard__activity__workspace__icon'>
268 207
                       <i className='fa fa-comments-o' />
269 208
                     </div>
270 209
                     <div className='dashboard__activity__workspace__name'>
271
-                      <span>Évolution</span>
272
-                    </div>
273
-                  </div>
274
-
275
-                  <div className='dashboard__activity__workspace'>
276
-                    <div className='dashboard__activity__workspace__icon'>
277
-                      <i className='fa fa-file-text-o' />
278
-                    </div>
279
-                    <div className='dashboard__activity__workspace__name'>
280
-                      Commercialisation
210
+                      <span>Développement Tracim</span>
281 211
                     </div>
282 212
                   </div>
283 213
 
@@ -299,6 +229,7 @@ class Dashboard extends React.Component {
299 229
                   {this.state.displayNewMemberDashboard === false &&
300 230
                     <div>
301 231
                       <ul className='dashboard__memberlist__list'>
232
+
302 233
                         <li className='dashboard__memberlist__list__item'>
303 234
                           <div className='dashboard__memberlist__list__item__avatar'>
304 235
                             <img src={imgProfil} alt='avatar' />
@@ -316,107 +247,6 @@ class Dashboard extends React.Component {
316 247
                           </div>
317 248
                         </li>
318 249
 
319
-                        <li className='dashboard__memberlist__list__item'>
320
-                          <div className='dashboard__memberlist__list__item__avatar'>
321
-                            <img src={imgProfil} alt='avatar' />
322
-                          </div>
323
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
324
-                            <div className='dashboard__memberlist__list__item__info__name'>
325
-                              Aldwin Vinel
326
-                            </div>
327
-                            <div className='dashboard__memberlist__list__item__info__role'>
328
-                              Lecteur
329
-                            </div>
330
-                          </div>
331
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
332
-                            <i className='fa fa-trash-o' />
333
-                          </div>
334
-                        </li>
335
-
336
-                        <li className='dashboard__memberlist__list__item'>
337
-                          <div className='dashboard__memberlist__list__item__avatar'>
338
-                            <img src={imgProfil} alt='avatar' />
339
-                          </div>
340
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
341
-                            <div className='dashboard__memberlist__list__item__info__name'>
342
-                              William Himme
343
-                            </div>
344
-                            <div className='dashboard__memberlist__list__item__info__role'>
345
-                              Contributeur
346
-                            </div>
347
-                          </div>
348
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
349
-                            <i className='fa fa-trash-o' />
350
-                          </div>
351
-                        </li>
352
-
353
-                        <li className='dashboard__memberlist__list__item'>
354
-                          <div className='dashboard__memberlist__list__item__avatar'>
355
-                            <img src={imgProfil} alt='avatar' />
356
-                          </div>
357
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
358
-                            <div className='dashboard__memberlist__list__item__info__name'>
359
-                              Yacine Lite
360
-                            </div>
361
-                            <div className='dashboard__memberlist__list__item__info__role'>
362
-                              Contributeur
363
-                            </div>
364
-                          </div>
365
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
366
-                            <i className='fa fa-trash-o' />
367
-                          </div>
368
-                        </li>
369
-
370
-                        <li className='dashboard__memberlist__list__item'>
371
-                          <div className='dashboard__memberlist__list__item__avatar'>
372
-                            <img src={imgProfil} alt='avatar' />
373
-                          </div>
374
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
375
-                            <div className='dashboard__memberlist__list__item__info__name'>
376
-                              Alexi Falcin
377
-                            </div>
378
-                            <div className='dashboard__memberlist__list__item__info__role'>
379
-                              Gestionnaire
380
-                            </div>
381
-                          </div>
382
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
383
-                            <i className='fa fa-trash-o' />
384
-                          </div>
385
-                        </li>
386
-
387
-                        <li className='dashboard__memberlist__list__item'>
388
-                          <div className='dashboard__memberlist__list__item__avatar'>
389
-                            <img src={imgProfil} alt='avatar' />
390
-                          </div>
391
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
392
-                            <div className='dashboard__memberlist__list__item__info__name'>
393
-                              Mickaël Fonati
394
-                            </div>
395
-                            <div className='dashboard__memberlist__list__item__info__role'>
396
-                              Gestionnaire
397
-                            </div>
398
-                          </div>
399
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
400
-                            <i className='fa fa-trash-o' />
401
-                          </div>
402
-                        </li>
403
-
404
-                        <li className='dashboard__memberlist__list__item'>
405
-                          <div className='dashboard__memberlist__list__item__avatar'>
406
-                            <img src={imgProfil} alt='avatar' />
407
-                          </div>
408
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
409
-                            <div className='dashboard__memberlist__list__item__info__name'>
410
-                              Eva Lonbard
411
-                            </div>
412
-                            <div className='dashboard__memberlist__list__item__info__role'>
413
-                              Gestionnaire
414
-                            </div>
415
-                          </div>
416
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
417
-                            <i className='fa fa-trash-o' />
418
-                          </div>
419
-                        </li>
420 250
                       </ul>
421 251
 
422 252
                       <div
@@ -440,93 +270,64 @@ class Dashboard extends React.Component {
440 270
                   }
441 271
 
442 272
                   {this.state.displayNewMemberDashboard === true &&
443
-                    <form className='dashboard__memberlist__form'>
444
-                      <div
445
-                        className='dashboard__memberlist__form__close d-flex justify-content-end'
446
-                      >
447
-                        <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
273
+                  <form className='dashboard__memberlist__form'>
274
+                    <div
275
+                      className='dashboard__memberlist__form__close d-flex justify-content-end'
276
+                    >
277
+                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
278
+                    </div>
279
+
280
+                    <div className='dashboard__memberlist__form__member'>
281
+                      <div className='dashboard__memberlist__form__member__name'>
282
+                        <label className='name__label' htmlFor='addmember'>
283
+                          {this.props.t('Enter the name or email of the member')}
284
+                        </label>
285
+                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
448 286
                       </div>
449
-                      <div className='dashboard__memberlist__form__member'>
450
-                        <div className='dashboard__memberlist__form__member__name'>
451
-                          <label className='name__label' htmlFor='addmember'>
452
-                            {this.props.t('Enter the name or email of the member')}
453
-                          </label>
454
-                          <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
287
+
288
+                      <div className='dashboard__memberlist__form__member__create'>
289
+                        <div className='create__radiobtn mr-3'>
290
+                          <input type='radio' />
455 291
                         </div>
456
-                        <div className='dashboard__memberlist__form__member__create'>
457
-                          <div className='create__radiobtn mr-3'>
458
-                            <input type='radio' />
459
-                          </div>
460
-                          <div className='create__text'>
461
-                            {this.props.t('Create an account')}
462
-                          </div>
292
+
293
+                        <div className='create__text'>
294
+                          {this.props.t('Create an account')}
463 295
                         </div>
464 296
                       </div>
465
-                      <div className='dashboard__memberlist__form__role'>
466
-                        <div className='dashboard__memberlist__form__role__text'>
467
-                          {this.props.t('Choose the role of the member')}
468
-                        </div>
469
-                        <ul className='dashboard__memberlist__form__role__list'>
470
-                          <li className='dashboard__memberlist__form__role__list__item'>
471
-                            <div className='item__radiobtn mr-3'>
472
-                              <input type='radio' name='role' value='responsable' />
473
-                            </div>
474
-                            <div className='item__text'>
475
-                              <div className='item_text_icon mr-2'>
476
-                                <i className='fa fa-gavel' />
477
-                              </div>
478
-                              <div className='item__text__name'>
479
-                                {this.props.t('Supervisor')}
480
-                              </div>
481
-                            </div>
482
-                          </li>
483
-                          <li className='dashboard__memberlist__form__role__list__item'>
484
-                            <div className='item__radiobtn mr-3'>
485
-                              <input type='radio' name='role' value='gestionnaire' />
486
-                            </div>
487
-                            <div className='item__text'>
488
-                              <div className='item_text_icon mr-2'>
489
-                                <i className='fa fa-graduation-cap' />
490
-                              </div>
491
-                              <div className='item__text__name'>
492
-                                {this.props.t('Content Manager')}
493
-                              </div>
494
-                            </div>
495
-                          </li>
496
-                          <li className='dashboard__memberlist__form__role__list__item'>
497
-                            <div className='item__radiobtn mr-3'>
498
-                              <input type='radio' name='role' value='contributeur' />
499
-                            </div>
500
-                            <div className='item__text'>
501
-                              <div className='item_text_icon mr-2'>
502
-                                <i className='fa fa-pencil' />
503
-                              </div>
504
-                              <div className='item__text__name'>
505
-                                {this.props.t('Contributor')}
506
-                              </div>
507
-                            </div>
508
-                          </li>
509
-                          <li className='dashboard__memberlist__form__role__list__item'>
510
-                            <div className='item__radiobtn mr-3'>
511
-                              <input type='radio' name='role' value='lecteur' />
297
+                    </div>
298
+
299
+                    <div className='dashboard__memberlist__form__role'>
300
+                      <div className='dashboard__memberlist__form__role__text'>
301
+                        {this.props.t('Choose the role of the member')}
302
+                      </div>
303
+
304
+                      <ul className='dashboard__memberlist__form__role__list'>
305
+
306
+                        <li className='dashboard__memberlist__form__role__list__item'>
307
+                          <div className='item__radiobtn mr-3'>
308
+                            <input type='radio' name='role' value='responsable' />
309
+                          </div>
310
+
311
+                          <div className='item__text'>
312
+                            <div className='item_text_icon mr-2'>
313
+                              <i className='fa fa-gavel' />
512 314
                             </div>
513
-                            <div className='item__text'>
514
-                              <div className='item_text_icon mr-2'>
515
-                                <i className='fa fa-eye' />
516
-                              </div>
517
-                              <div className='item__text__name'>
518
-                                {this.props.t('Reader')}
519
-                              </div>
315
+
316
+                            <div className='item__text__name'>
317
+                              {this.props.t('Supervisor')}
520 318
                             </div>
521
-                          </li>
522
-                        </ul>
523
-                      </div>
524
-                      <div className='dashboard__memberlist__form__submitbtn'>
525
-                        <button className='btn btn-outline-primary'>
526
-                          {this.props.t('Validate')}
527
-                        </button>
528
-                      </div>
529
-                    </form>
319
+                          </div>
320
+                        </li>
321
+
322
+                      </ul>
323
+                    </div>
324
+
325
+                    <div className='dashboard__memberlist__form__submitbtn'>
326
+                      <button className='btn btn-outline-primary'>
327
+                        {this.props.t('Validate')}
328
+                      </button>
329
+                    </div>
330
+                  </form>
530 331
                   }
531 332
                 </div>
532 333
               </div>
@@ -547,17 +348,17 @@ class Dashboard extends React.Component {
547 348
                   </div>
548 349
                 </div>
549 350
                 {this.state.displayWebdavBtn === true &&
550
-                  <div>
551
-                    <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
552
-                      <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
553
-                        {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
554
-                      </div>
351
+                <div>
352
+                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
353
+                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
354
+                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
355
+                    </div>
555 356
 
556
-                      <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
557
-                        http://algoo.trac.im/webdav/
558
-                      </div>
357
+                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
358
+                      http://algoo.trac.im/webdav/
559 359
                     </div>
560 360
                   </div>
361
+                </div>
561 362
                 }
562 363
               </div>
563 364
               <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
@@ -577,27 +378,27 @@ class Dashboard extends React.Component {
577 378
                 </div>
578 379
                 <div className='dashboard__moreinfo__calendar__wrapperText'>
579 380
                   {this.state.displayCalendarBtn === true &&
580
-                    <div>
581
-                      <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
582
-                        <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
583
-                          {this.props.t('Each workspace has its own calendar.')}
584
-                        </div>
381
+                  <div>
382
+                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
383
+                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
384
+                        {this.props.t('Each workspace has its own calendar.')}
385
+                      </div>
585 386
 
586
-                        <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
587
-                          http://algoo.trac.im/calendar/
588
-                        </div>
387
+                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
388
+                        http://algoo.trac.im/calendar/
589 389
                       </div>
590 390
                     </div>
391
+                  </div>
591 392
                   }
592 393
                 </div>
593 394
               </div>
594 395
             </div>
595
-          </div>
596
-        </div>
396
+          </PageContent>
397
+        </PageWrapper>
597 398
       </div>
598 399
     )
599 400
   }
600 401
 }
601 402
 
602
-const mapStateToProps = ({ user, app, contentType, workspaceList }) => ({ user, app, contentType, workspaceList })
603
-export default connect(mapStateToProps)(translate()(Dashboard))
403
+const mapStateToProps = ({ user, contentType, currentWorkspace }) => ({ user, contentType, curWs: currentWorkspace })
404
+export default connect(mapStateToProps)(translate()(Radium(Dashboard)))

+ 571 - 0
frontend/src/container/Dashboard_old.jsx Voir le fichier

@@ -0,0 +1,571 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import Sidebar from './Sidebar.jsx'
4
+import imgProfil from '../img/imgProfil.png'
5
+import { translate } from 'react-i18next'
6
+
7
+class Dashboard extends React.Component {
8
+  constructor (props) {
9
+    super(props)
10
+    this.state = {
11
+      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
12
+      displayNewMemberDashboard: false,
13
+      displayNotifBtn: false,
14
+      displayWebdavBtn: false,
15
+      displayCalendarBtn: false
16
+    }
17
+  }
18
+
19
+  handleToggleNewMemberDashboard = () => this.setState(prevState => ({
20
+    displayNewMemberDashboard: !prevState.displayNewMemberDashboard
21
+  }))
22
+
23
+  handleToggleNotifBtn = () => this.setState(prevState => ({
24
+    displayNotifBtn: !prevState.displayNotifBtn
25
+  }))
26
+
27
+  handleToggleWebdavBtn = () => this.setState(prevState => ({
28
+    displayWebdavBtn: !prevState.displayWebdavBtn
29
+  }))
30
+
31
+  handleToggleCalendarBtn = () => this.setState(prevState => ({
32
+    displayCalendarBtn: !prevState.displayCalendarBtn
33
+  }))
34
+
35
+  render () {
36
+    return (
37
+      <div className='sidebarpagecontainer'>
38
+        <Sidebar />
39
+
40
+        <div className='dashboard'>
41
+          <div className='container-fluid nopadding'>
42
+            <div className='dashboard__header mb-5'>
43
+              <div className='pageTitleGeneric dashboard__header__title d-flex align-items-center'>
44
+                <div className='pageTitleGeneric__title dashboard__header__title__text mr-3'>
45
+                  {this.props.t('Dashboard')}
46
+                </div>
47
+                <div className='dashboard__header__acces' />
48
+              </div>
49
+
50
+              <div className='dashboard__header__advancedmode mr-3'>
51
+                <button type='button' className='btn btn-primary'>
52
+                  {this.props.t('Active advanced Dashboard')}
53
+                </button>
54
+              </div>
55
+            </div>
56
+
57
+            <div className='dashboard__workspace-wrapper'>
58
+              <div className='dashboard__workspace'>
59
+                <div className='dashboard__workspace__title'>
60
+                  Développement tracim
61
+                </div>
62
+
63
+                <div className='dashboard__workspace__detail'>
64
+                  Ligne directive pour le prochain design de Tracim et des futurs fonctionnalités.
65
+                </div>
66
+              </div>
67
+
68
+              <div className='dashboard__userstatut'>
69
+
70
+                <div className='dashboard__userstatut__role'>
71
+                  <div className='dashboard__userstatut__role__text'>
72
+                    Hi ! Alexi, vous êtes actuellement
73
+                  </div>
74
+                  <div className='dashboard__userstatut__role__rank'>
75
+                    <div className='dashboard__userstatut__role__rank__icon'>
76
+                      <i className='fa fa-graduation-cap' />
77
+                    </div>
78
+                    <div className='dashboard__userstatut__role__rank__rolename'>
79
+                      Gestionnaire de projet
80
+                    </div>
81
+                  </div>
82
+                </div>
83
+
84
+                <div className='dashboard__userstatut__notification'>
85
+                  <div className='dashboard__userstatut__notification__text'>
86
+                    Vous êtes abonné(e) aux notifications de ce workspace
87
+                  </div>
88
+                  {this.state.displayNotifBtn === false &&
89
+                  <div
90
+                    className='dashboard__userstatut__notification__btn btn btn-outline-primary'
91
+                    onClick={this.handleToggleNotifBtn}
92
+                  >
93
+                    {this.props.t('Change your status')}
94
+                  </div>
95
+                  }
96
+
97
+                  {this.state.displayNotifBtn === true &&
98
+                  <div className='dashboard__userstatut__notification__subscribe dropdown'>
99
+                    <button className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
100
+                      Abonné(e)
101
+                    </button>
102
+                    <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
103
+                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
104
+                        {this.props.t('subscriber')}
105
+                      </div>
106
+                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
107
+                        {this.props.t('unsubscribed')}
108
+                      </div>
109
+                    </div>
110
+                  </div>
111
+                  }
112
+                </div>
113
+              </div>
114
+            </div>
115
+
116
+            <div className='dashboard__calltoaction justify-content-xl-center'>
117
+              <div className='dashboard__calltoaction__button btnaction btnthread'>
118
+                <div className='dashboard__calltoaction__button__text'>
119
+                  <div className='dashboard__calltoaction__button__text__icon'>
120
+                    <i className='fa fa-comments-o' />
121
+                  </div>
122
+                  <div className='dashboard__calltoaction__button__text__title'>
123
+                    {this.props.t('Start a new Thread')}
124
+                  </div>
125
+                </div>
126
+              </div>
127
+
128
+              <div className='dashboard__calltoaction__button btnaction writefile'>
129
+                <div className='dashboard__calltoaction__button__text'>
130
+                  <div className='dashboard__calltoaction__button__text__icon'>
131
+                    <i className='fa fa-file-text-o' />
132
+                  </div>
133
+                  <div className='dashboard__calltoaction__button__text__title'>
134
+                    {this.props.t('Writing a document')}
135
+                  </div>
136
+                </div>
137
+              </div>
138
+
139
+              <div className='dashboard__calltoaction__button btnaction importfile'>
140
+                <div className='dashboard__calltoaction__button__text'>
141
+                  <div className='dashboard__calltoaction__button__text__icon'>
142
+                    <i className='fa fa-paperclip' />
143
+                  </div>
144
+                  <div className='dashboard__calltoaction__button__text__title'>
145
+                    {this.props.t('Upload a file')}
146
+                  </div>
147
+                </div>
148
+              </div>
149
+
150
+              {/*
151
+                <div className='dashboard__calltoaction__button btnaction visioconf'>
152
+                  <div className='dashboard__calltoaction__button__text'>
153
+                    <div className='dashboard__calltoaction__button__text__icon'>
154
+                      <i className='fa fa-video-camera' />
155
+                    </div>
156
+                    <div className='dashboard__calltoaction__button__text__title'>
157
+                      {this.props.t('Start a videoconference')}
158
+                    </div>
159
+                  </div>
160
+                </div>
161
+
162
+                <div className='dashboard__calltoaction__button btnaction calendar'>
163
+                  <div className='dashboard__calltoaction__button__text'>
164
+                    <div className='dashboard__calltoaction__button__text__icon'>
165
+                      <i className='fa fa-calendar' />
166
+                    </div>
167
+                    <div className='dashboard__calltoaction__button__text__title'>
168
+                      {this.props.t('View the Calendar')}
169
+                    </div>
170
+                  </div>
171
+                </div>
172
+              */ }
173
+
174
+              <div className='dashboard__calltoaction__button btnaction explore'>
175
+                <div className='dashboard__calltoaction__button__text'>
176
+                  <div className='dashboard__calltoaction__button__text__icon'>
177
+                    <i className='fa fa-folder-open-o' />
178
+                  </div>
179
+                  <div className='dashboard__calltoaction__button__text__title'>
180
+                    {this.props.t('Explore the workspace')}
181
+                  </div>
182
+                </div>
183
+              </div>
184
+            </div>
185
+
186
+            <div className='dashboard__wksinfo'>
187
+              <div className='dashboard__activity'>
188
+                <div className='dashboard__activity__header'>
189
+                  <div className='dashboard__activity__header__title subTitle'>
190
+                    {this.props.t('Recent activity')}
191
+                  </div>
192
+
193
+                  <div className='dashboard__activity__header__allread btn btn-outline-primary'>
194
+                    {this.props.t('Mark everything as read')}
195
+                  </div>
196
+                </div>
197
+                <div className='dashboard__activity__wrapper'>
198
+                  <div className='dashboard__activity__workspace'>
199
+                    <div className='dashboard__activity__workspace__icon'>
200
+                      <i className='fa fa-comments-o' />
201
+                    </div>
202
+                    <div className='dashboard__activity__workspace__name'>
203
+                      <span>Développement Tracim</span>
204
+                    </div>
205
+                  </div>
206
+
207
+                  <div className='dashboard__activity__workspace'>
208
+                    <div className='dashboard__activity__workspace__icon'>
209
+                      <i className='fa fa-list-ul' />
210
+                    </div>
211
+                    <div className='dashboard__activity__workspace__name'>
212
+                      Mission externe
213
+                    </div>
214
+                  </div>
215
+
216
+                  <div className='dashboard__activity__workspace'>
217
+                    <div className='dashboard__activity__workspace__icon'>
218
+                      <i className='fa fa-list-ul' />
219
+                    </div>
220
+                    <div className='dashboard__activity__workspace__name'>
221
+                      Recherche et developpement
222
+                    </div>
223
+                  </div>
224
+
225
+                  <div className='dashboard__activity__workspace'>
226
+                    <div className='dashboard__activity__workspace__icon'>
227
+                      <i className='fa fa-file-text-o' />
228
+                    </div>
229
+                    <div className='dashboard__activity__workspace__name'>
230
+                      <span>Marketing</span>
231
+                    </div>
232
+                  </div>
233
+
234
+                  <div className='dashboard__activity__workspace'>
235
+                    <div className='dashboard__activity__workspace__icon'>
236
+                      <i className='fa fa-comments-o' />
237
+                    </div>
238
+                    <div className='dashboard__activity__workspace__name'>
239
+                      <span>Évolution</span>
240
+                    </div>
241
+                  </div>
242
+
243
+                  <div className='dashboard__activity__workspace'>
244
+                    <div className='dashboard__activity__workspace__icon'>
245
+                      <i className='fa fa-file-text-o' />
246
+                    </div>
247
+                    <div className='dashboard__activity__workspace__name'>
248
+                      Commercialisation
249
+                    </div>
250
+                  </div>
251
+
252
+                  <div className='dashboard__activity__more d-flex flex-row-reverse'>
253
+                    <div className='dashboard__activity__more__btn btn btn-outline-primary'>
254
+                      {this.props.t('See more')}
255
+                    </div>
256
+                  </div>
257
+                </div>
258
+              </div>
259
+
260
+              <div className='dashboard__memberlist'>
261
+
262
+                <div className='dashboard__memberlist__title subTitle'>
263
+                  {this.props.t('Member List')}
264
+                </div>
265
+
266
+                <div className='dashboard__memberlist__wrapper'>
267
+                  {this.state.displayNewMemberDashboard === false &&
268
+                  <div>
269
+                    <ul className='dashboard__memberlist__list'>
270
+                      <li className='dashboard__memberlist__list__item'>
271
+                        <div className='dashboard__memberlist__list__item__avatar'>
272
+                          <img src={imgProfil} alt='avatar' />
273
+                        </div>
274
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
275
+                          <div className='dashboard__memberlist__list__item__info__name'>
276
+                            Jean Dupont
277
+                          </div>
278
+                          <div className='dashboard__memberlist__list__item__info__role'>
279
+                            Responsable
280
+                          </div>
281
+                        </div>
282
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
283
+                          <i className='fa fa-trash-o' />
284
+                        </div>
285
+                      </li>
286
+
287
+                      <li className='dashboard__memberlist__list__item'>
288
+                        <div className='dashboard__memberlist__list__item__avatar'>
289
+                          <img src={imgProfil} alt='avatar' />
290
+                        </div>
291
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
292
+                          <div className='dashboard__memberlist__list__item__info__name'>
293
+                            Aldwin Vinel
294
+                          </div>
295
+                          <div className='dashboard__memberlist__list__item__info__role'>
296
+                            Lecteur
297
+                          </div>
298
+                        </div>
299
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
300
+                          <i className='fa fa-trash-o' />
301
+                        </div>
302
+                      </li>
303
+
304
+                      <li className='dashboard__memberlist__list__item'>
305
+                        <div className='dashboard__memberlist__list__item__avatar'>
306
+                          <img src={imgProfil} alt='avatar' />
307
+                        </div>
308
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
309
+                          <div className='dashboard__memberlist__list__item__info__name'>
310
+                            William Himme
311
+                          </div>
312
+                          <div className='dashboard__memberlist__list__item__info__role'>
313
+                            Contributeur
314
+                          </div>
315
+                        </div>
316
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
317
+                          <i className='fa fa-trash-o' />
318
+                        </div>
319
+                      </li>
320
+
321
+                      <li className='dashboard__memberlist__list__item'>
322
+                        <div className='dashboard__memberlist__list__item__avatar'>
323
+                          <img src={imgProfil} alt='avatar' />
324
+                        </div>
325
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
326
+                          <div className='dashboard__memberlist__list__item__info__name'>
327
+                            Yacine Lite
328
+                          </div>
329
+                          <div className='dashboard__memberlist__list__item__info__role'>
330
+                            Contributeur
331
+                          </div>
332
+                        </div>
333
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
334
+                          <i className='fa fa-trash-o' />
335
+                        </div>
336
+                      </li>
337
+
338
+                      <li className='dashboard__memberlist__list__item'>
339
+                        <div className='dashboard__memberlist__list__item__avatar'>
340
+                          <img src={imgProfil} alt='avatar' />
341
+                        </div>
342
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
343
+                          <div className='dashboard__memberlist__list__item__info__name'>
344
+                            Alexi Falcin
345
+                          </div>
346
+                          <div className='dashboard__memberlist__list__item__info__role'>
347
+                            Gestionnaire
348
+                          </div>
349
+                        </div>
350
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
351
+                          <i className='fa fa-trash-o' />
352
+                        </div>
353
+                      </li>
354
+
355
+                      <li className='dashboard__memberlist__list__item'>
356
+                        <div className='dashboard__memberlist__list__item__avatar'>
357
+                          <img src={imgProfil} alt='avatar' />
358
+                        </div>
359
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
360
+                          <div className='dashboard__memberlist__list__item__info__name'>
361
+                            Mickaël Fonati
362
+                          </div>
363
+                          <div className='dashboard__memberlist__list__item__info__role'>
364
+                            Gestionnaire
365
+                          </div>
366
+                        </div>
367
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
368
+                          <i className='fa fa-trash-o' />
369
+                        </div>
370
+                      </li>
371
+
372
+                      <li className='dashboard__memberlist__list__item'>
373
+                        <div className='dashboard__memberlist__list__item__avatar'>
374
+                          <img src={imgProfil} alt='avatar' />
375
+                        </div>
376
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
377
+                          <div className='dashboard__memberlist__list__item__info__name'>
378
+                            Eva Lonbard
379
+                          </div>
380
+                          <div className='dashboard__memberlist__list__item__info__role'>
381
+                            Gestionnaire
382
+                          </div>
383
+                        </div>
384
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
385
+                          <i className='fa fa-trash-o' />
386
+                        </div>
387
+                      </li>
388
+                    </ul>
389
+
390
+                    <div
391
+                      className='dashboard__memberlist__btnadd'
392
+                      onClick={this.handleToggleNewMemberDashboard}
393
+                    >
394
+                      <div className='dashboard__memberlist__btnadd__button'>
395
+                        <div className='dashboard__memberlist__btnadd__button__avatar'>
396
+                          <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
397
+                            <i className='fa fa-plus' />
398
+                          </div>
399
+                        </div>
400
+                        <div
401
+                          className='dashboard__memberlist__btnadd__button__text'
402
+                        >
403
+                          {this.props.t('Add a member')}
404
+                        </div>
405
+                      </div>
406
+                    </div>
407
+                  </div>
408
+                  }
409
+
410
+                  {this.state.displayNewMemberDashboard === true &&
411
+                  <form className='dashboard__memberlist__form'>
412
+                    <div
413
+                      className='dashboard__memberlist__form__close d-flex justify-content-end'
414
+                    >
415
+                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
416
+                    </div>
417
+                    <div className='dashboard__memberlist__form__member'>
418
+                      <div className='dashboard__memberlist__form__member__name'>
419
+                        <label className='name__label' htmlFor='addmember'>
420
+                          {this.props.t('Enter the name or email of the member')}
421
+                        </label>
422
+                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
423
+                      </div>
424
+                      <div className='dashboard__memberlist__form__member__create'>
425
+                        <div className='create__radiobtn mr-3'>
426
+                          <input type='radio' />
427
+                        </div>
428
+                        <div className='create__text'>
429
+                          {this.props.t('Create an account')}
430
+                        </div>
431
+                      </div>
432
+                    </div>
433
+                    <div className='dashboard__memberlist__form__role'>
434
+                      <div className='dashboard__memberlist__form__role__text'>
435
+                        {this.props.t('Choose the role of the member')}
436
+                      </div>
437
+                      <ul className='dashboard__memberlist__form__role__list'>
438
+                        <li className='dashboard__memberlist__form__role__list__item'>
439
+                          <div className='item__radiobtn mr-3'>
440
+                            <input type='radio' name='role' value='responsable' />
441
+                          </div>
442
+                          <div className='item__text'>
443
+                            <div className='item_text_icon mr-2'>
444
+                              <i className='fa fa-gavel' />
445
+                            </div>
446
+                            <div className='item__text__name'>
447
+                              {this.props.t('Supervisor')}
448
+                            </div>
449
+                          </div>
450
+                        </li>
451
+                        <li className='dashboard__memberlist__form__role__list__item'>
452
+                          <div className='item__radiobtn mr-3'>
453
+                            <input type='radio' name='role' value='gestionnaire' />
454
+                          </div>
455
+                          <div className='item__text'>
456
+                            <div className='item_text_icon mr-2'>
457
+                              <i className='fa fa-graduation-cap' />
458
+                            </div>
459
+                            <div className='item__text__name'>
460
+                              {this.props.t('Content Manager')}
461
+                            </div>
462
+                          </div>
463
+                        </li>
464
+                        <li className='dashboard__memberlist__form__role__list__item'>
465
+                          <div className='item__radiobtn mr-3'>
466
+                            <input type='radio' name='role' value='contributeur' />
467
+                          </div>
468
+                          <div className='item__text'>
469
+                            <div className='item_text_icon mr-2'>
470
+                              <i className='fa fa-pencil' />
471
+                            </div>
472
+                            <div className='item__text__name'>
473
+                              {this.props.t('Contributor')}
474
+                            </div>
475
+                          </div>
476
+                        </li>
477
+                        <li className='dashboard__memberlist__form__role__list__item'>
478
+                          <div className='item__radiobtn mr-3'>
479
+                            <input type='radio' name='role' value='lecteur' />
480
+                          </div>
481
+                          <div className='item__text'>
482
+                            <div className='item_text_icon mr-2'>
483
+                              <i className='fa fa-eye' />
484
+                            </div>
485
+                            <div className='item__text__name'>
486
+                              {this.props.t('Reader')}
487
+                            </div>
488
+                          </div>
489
+                        </li>
490
+                      </ul>
491
+                    </div>
492
+                    <div className='dashboard__memberlist__form__submitbtn'>
493
+                      <button className='btn btn-outline-primary'>
494
+                        {this.props.t('Validate')}
495
+                      </button>
496
+                    </div>
497
+                  </form>
498
+                  }
499
+                </div>
500
+              </div>
501
+            </div>
502
+
503
+            <div className='dashboard__moreinfo'>
504
+              <div className='dashboard__moreinfo__webdav genericBtnInfoDashboard'>
505
+                <div
506
+                  className='dashboard__moreinfo__webdav__btn genericBtnInfoDashboard__btn'
507
+                  onClick={this.handleToggleWebdavBtn}
508
+                >
509
+                  <div className='dashboard__moreinfo__webdav__btn__icon genericBtnInfoDashboard__btn__icon'>
510
+                    <i className='fa fa-windows' />
511
+                  </div>
512
+
513
+                  <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
514
+                    {this.props.t('Implement Tracim in your explorer')}
515
+                  </div>
516
+                </div>
517
+                {this.state.displayWebdavBtn === true &&
518
+                <div>
519
+                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
520
+                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
521
+                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
522
+                    </div>
523
+
524
+                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
525
+                      http://algoo.trac.im/webdav/
526
+                    </div>
527
+                  </div>
528
+                </div>
529
+                }
530
+              </div>
531
+              <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
532
+                <div className='dashboard__moreinfo__calendar__wrapperBtn'>
533
+                  <div
534
+                    className='dashboard__moreinfo__calendar__btn genericBtnInfoDashboard__btn'
535
+                    onClick={this.handleToggleCalendarBtn}
536
+                  >
537
+                    <div className='dashboard__moreinfo__calendar__btn__icon genericBtnInfoDashboard__btn__icon'>
538
+                      <i className='fa fa-calendar' />
539
+                    </div>
540
+
541
+                    <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
542
+                      {this.props.t('Workspace Calendar')}
543
+                    </div>
544
+                  </div>
545
+                </div>
546
+                <div className='dashboard__moreinfo__calendar__wrapperText'>
547
+                  {this.state.displayCalendarBtn === true &&
548
+                  <div>
549
+                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
550
+                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
551
+                        {this.props.t('Each workspace has its own calendar.')}
552
+                      </div>
553
+
554
+                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
555
+                        http://algoo.trac.im/calendar/
556
+                      </div>
557
+                    </div>
558
+                  </div>
559
+                  }
560
+                </div>
561
+              </div>
562
+            </div>
563
+          </div>
564
+        </div>
565
+      </div>
566
+    )
567
+  }
568
+}
569
+
570
+const mapStateToProps = ({ user, app, contentType, workspaceList }) => ({ user, app, contentType, workspaceList })
571
+export default connect(mapStateToProps)(translate()(Dashboard))

+ 3 - 4
frontend/src/container/Sidebar.jsx Voir le fichier

@@ -63,7 +63,7 @@ class Sidebar extends React.Component {
63 63
 
64 64
     history.push(`${PAGE.WORKSPACE.CONTENT_LIST(idWs)}?type=${newFilter.join(';')}`) // workspace.filter gets updated on react redraw from match.params
65 65
 
66
-    // obviously, it's ugly to use custom event to tell WorkspaceContent to refresh, but since WorkspaceContent
66
+    // obviously, it's ugly to use custom event to tell WorkspaceContentList to refresh, but since WorkspaceContentList
67 67
     // will end up being an App, it'll have to be that way. So it's fine
68 68
     GLOBAL_dispatchEvent({ type: 'refreshContentList', data: {} })
69 69
   }
@@ -124,11 +124,10 @@ class Sidebar extends React.Component {
124 124
   }
125 125
 }
126 126
 
127
-const mapStateToProps = ({ lang, user, workspace, workspaceList, app }) => ({
127
+const mapStateToProps = ({ lang, user, workspace, workspaceList }) => ({
128 128
   activeLang: lang.find(l => l.active) || {id: 'en'},
129 129
   user,
130 130
   workspace,
131
-  workspaceList,
132
-  app
131
+  workspaceList
133 132
 })
134 133
 export default withRouter(connect(mapStateToProps)(translate()(Sidebar)))

+ 7 - 2
frontend/src/container/Tracim.jsx Voir le fichier

@@ -16,12 +16,14 @@ import PrivateRoute from './PrivateRoute.jsx'
16 16
 import { COOKIE, PAGE } from '../helper.js'
17 17
 import {
18 18
   getAppList,
19
-  getUserIsConnected
19
+  getUserIsConnected,
20
+  getContentTypeList
20 21
 } from '../action-creator.async.js'
21 22
 import {
22 23
   removeFlashMessage,
23 24
   setAppList,
24
-  setUserConnected
25
+  setUserConnected,
26
+  setContentTypeList
25 27
 } from '../action-creator.sync.js'
26 28
 import Cookies from 'js-cookie'
27 29
 
@@ -47,6 +49,9 @@ class Tracim extends React.Component {
47 49
 
48 50
         const fetchGetAppList = await dispatch(getAppList(userLogged))
49 51
         if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
52
+
53
+        const fetchGetContentTypeList = await dispatch(getContentTypeList(userLogged))
54
+        if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
50 55
         break
51 56
 
52 57
       case 401:

+ 12 - 19
frontend/src/container/WorkspaceContent.jsx Voir le fichier

@@ -16,14 +16,12 @@ import {
16 16
   PageContent
17 17
 } from 'tracim_frontend_lib'
18 18
 import {
19
-  getContentTypeList,
20 19
   getWorkspaceContentList,
21 20
   getFolderContent
22 21
 } from '../action-creator.async.js'
23 22
 import {
24 23
   newFlashMessage,
25
-  setContentTypeList,
26
-  setWorkspaceContent
24
+  setWorkspaceContentList
27 25
 } from '../action-creator.sync.js'
28 26
 
29 27
 const qs = require('query-string')
@@ -61,15 +59,10 @@ class WorkspaceContent extends React.Component {
61 59
   }
62 60
 
63 61
   async componentDidMount () {
64
-    const { user, workspaceList, contentType, match, dispatch } = this.props
62
+    const { workspaceList, match } = this.props
65 63
 
66 64
     console.log('%c<WorkspaceContent> componentDidMount', 'color: #c17838')
67 65
 
68
-    if (contentType.length === 0) {
69
-      const fetchGetContentTypeList = await dispatch(getContentTypeList(user))
70
-      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
71
-    }
72
-
73 66
     let wsToLoad = null
74 67
 
75 68
     if (match.params.idws === undefined) {
@@ -109,7 +102,7 @@ class WorkspaceContent extends React.Component {
109 102
 
110 103
     const wsContent = await dispatch(getWorkspaceContentList(user, idWorkspace, 0))
111 104
 
112
-    if (wsContent.status === 200) dispatch(setWorkspaceContent(wsContent.json, qs.parse(location.search).type))
105
+    if (wsContent.status === 200) dispatch(setWorkspaceContentList(wsContent.json, qs.parse(location.search).type))
113 106
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
114 107
   }
115 108
 
@@ -155,7 +148,7 @@ class WorkspaceContent extends React.Component {
155 148
   handleUpdateAppOpenedType = openedAppType => this.setState({appOpenedType: openedAppType})
156 149
 
157 150
   render () {
158
-    const { workspaceContent, contentType } = this.props
151
+    const { workspaceContentList, contentType } = this.props
159 152
 
160 153
     const filterWorkspaceContent = (contentList, filter) => {
161 154
       return filter.length === 0
@@ -168,8 +161,8 @@ class WorkspaceContent extends React.Component {
168 161
 
169 162
     const urlFilter = qs.parse(this.props.location.search).type
170 163
 
171
-    const filteredWorkspaceContent = workspaceContent.length > 0
172
-      ? filterWorkspaceContent(workspaceContent, urlFilter ? [urlFilter] : [])
164
+    const filteredWorkspaceContentList = workspaceContentList.length > 0
165
+      ? filterWorkspaceContent(workspaceContentList, urlFilter ? [urlFilter] : [])
173 166
       : []
174 167
 
175 168
     return (
@@ -196,7 +189,7 @@ class WorkspaceContent extends React.Component {
196 189
             parentClass='workspace__header'
197 190
             customClass='justify-content-between'
198 191
             title='Liste des Contenus'
199
-            subtitle={workspaceContent.label ? workspaceContent.label : ''}
192
+            subtitle={workspaceContentList.label ? workspaceContentList.label : ''}
200 193
           >
201 194
             <DropdownCreateButton
202 195
               parentClass='workspace__header__btnaddworkspace'
@@ -212,7 +205,7 @@ class WorkspaceContent extends React.Component {
212 205
             <div className='workspace__content__fileandfolder folder__content active'>
213 206
               <ContentItemHeader />
214 207
 
215
-              { filteredWorkspaceContent.map((c, i) => c.type === 'folder'
208
+              { filteredWorkspaceContentList.map((c, i) => c.type === 'folder'
216 209
                 ? (
217 210
                   <Folder
218 211
                     availableApp={contentType}
@@ -227,7 +220,7 @@ class WorkspaceContent extends React.Component {
227 220
                     }}
228 221
                     onClickFolder={this.handleClickFolder}
229 222
                     onClickCreateContent={this.handleClickCreateContent}
230
-                    isLast={i === filteredWorkspaceContent.length - 1}
223
+                    isLast={i === filteredWorkspaceContentList.length - 1}
231 224
                     key={c.id}
232 225
                   />
233 226
                 )
@@ -237,7 +230,7 @@ class WorkspaceContent extends React.Component {
237 230
                     type={c.type}
238 231
                     faIcon={contentType.length ? contentType.find(a => a.slug === c.type).faIcon : ''}
239 232
                     statusSlug={c.statusSlug}
240
-                    contentType={contentType.find(ct => ct.slug === c.type)}
233
+                    contentType={contentType.length ? contentType.find(ct => ct.slug === c.type) : null}
241 234
                     onClickItem={() => this.handleClickContentItem(c)}
242 235
                     onClickExtendedAction={{
243 236
                       edit: e => this.handleClickEditContentItem(e, c),
@@ -247,7 +240,7 @@ class WorkspaceContent extends React.Component {
247 240
                       delete: e => this.handleClickDeleteContentItem(e, c)
248 241
                     }}
249 242
                     onClickCreateContent={this.handleClickCreateContent}
250
-                    isLast={i === filteredWorkspaceContent.length - 1}
243
+                    isLast={i === filteredWorkspaceContentList.length - 1}
251 244
                     key={c.id}
252 245
                   />
253 246
                 )
@@ -268,5 +261,5 @@ class WorkspaceContent extends React.Component {
268 261
   }
269 262
 }
270 263
 
271
-const mapStateToProps = ({ user, workspaceContent, workspaceList, app, contentType }) => ({ user, workspaceContent, workspaceList, app, contentType })
264
+const mapStateToProps = ({ user, workspaceContentList, workspaceList, contentType }) => ({ user, workspaceContentList, workspaceList, contentType })
272 265
 export default withRouter(connect(mapStateToProps)(appFactory(WorkspaceContent)))

+ 4 - 18
frontend/src/css/Dashboard.styl Voir le fichier

@@ -43,7 +43,7 @@ coloricon()
43 43
         margin 0
44 44
     &__advancedmode
45 45
       cursor pointer
46
-  &__wkswrapper
46
+  &__workspace-wrapper
47 47
     flexwrap()
48 48
   &__workspace
49 49
     margin-right 20px
@@ -62,9 +62,9 @@ coloricon()
62 62
       flexwrap()
63 63
       margin 20px 0
64 64
       font-size 18px
65
-      &__text
65
+      &__msg
66 66
         margin-right 15px
67
-      &__rank
67
+      &__definition
68 68
         display flex
69 69
         &__icon
70 70
           margin-right 15px
@@ -88,25 +88,11 @@ coloricon()
88 88
       &:active
89 89
         box-shadow inset 0px 0px 5px 2px #656565
90 90
       &__text
91
+        color white
91 92
         &__icon
92 93
           font-size 30px
93
-          .fa-comments-o, .fa-paperclip, .fa-calendar, .fa-file-text-o, .fa-folder-open-o, .fa-video-camera
94
-              color white
95 94
         &__title
96 95
           font-size 18px
97
-          color white
98
-    .btnthread
99
-      background-color threadColor
100
-    .writefile
101
-      background-color writefile
102
-    .importfile
103
-      background-color importfile
104
-    .visioconf
105
-      background-color grey-hover
106
-    .calendar
107
-      background-color calendar
108
-    .explore
109
-      background-color explore
110 96
   &__wksinfo
111 97
     flexwrap()
112 98
     margin-top 150px

+ 5 - 1
frontend/src/css/Generic.styl Voir le fichier

@@ -202,7 +202,7 @@ a
202 202
   display flex
203 203
   flex-direction column
204 204
   justify-content center
205
-  margin 20px 30px 20px 0
205
+  margin 0 15px
206 206
   border-radius 10px
207 207
   padding 15px
208 208
   width 230px
@@ -210,6 +210,10 @@ a
210 210
   box-shadow shadow-all
211 211
   text-align center
212 212
   cursor pointer
213
+  &:nth-child(1)
214
+    margin-left 0
215
+  &:nth-last-child
216
+    margin-right 0
213 217
 
214 218
 .genericBtnInfoDashboard
215 219
   &__btn

+ 2 - 0
frontend/src/helper.js Voir le fichier

@@ -55,3 +55,5 @@ export const ROLE = [{
55 55
   icon: 'fa-gavel',
56 56
   translationKey: 'role.manager'
57 57
 }]
58
+
59
+export const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route

frontend/src/reducer/app.js → frontend/src/reducer/appList.js Voir le fichier


+ 46 - 0
frontend/src/reducer/currentWorkspace.js Voir le fichier

@@ -0,0 +1,46 @@
1
+import {SET, WORKSPACE_DETAIL, WORKSPACE_MEMBER_LIST} from '../action-creator.sync.js'
2
+import { handleRouteFromApi } from '../helper.js'
3
+
4
+const defaultWorkspace = {
5
+  id: 0,
6
+  slug: '',
7
+  label: '',
8
+  description: '',
9
+  sidebarEntries: [],
10
+  member: []
11
+}
12
+
13
+export default function currentWorkspace (state = defaultWorkspace, action) {
14
+  switch (action.type) {
15
+    case `${SET}/${WORKSPACE_DETAIL}`:
16
+      return {
17
+        ...state,
18
+        id: action.workspaceDetail.workspace_id,
19
+        slug: action.workspaceDetail.slug,
20
+        label: action.workspaceDetail.label,
21
+        description: action.workspaceDetail.description,
22
+        sidebarEntries: action.workspaceDetail.sidebar_entries.map(sbe => ({
23
+          slug: sbe.slug,
24
+          route: handleRouteFromApi(sbe.route),
25
+          faIcon: sbe.fa_icon,
26
+          hexcolor: sbe.hexcolor,
27
+          label: sbe.label
28
+        }))
29
+      }
30
+
31
+    case `${SET}/${WORKSPACE_MEMBER_LIST}`:
32
+      return {
33
+        ...state,
34
+        member: action.workspaceMemberList.map(m => ({
35
+          id: m.user_id,
36
+          publicName: m.user.public_name,
37
+          avatarUrl: m.user.avatar_url,
38
+          role: m.role,
39
+          isActive: m.is_active,
40
+        }))
41
+      }
42
+
43
+    default:
44
+      return state
45
+  }
46
+}

+ 4 - 3
frontend/src/reducer/root.js Voir le fichier

@@ -2,12 +2,13 @@ import { combineReducers } from 'redux'
2 2
 import lang from './lang.js'
3 3
 import flashMessage from './flashMessage.js'
4 4
 import user from './user.js'
5
-import workspaceContent from './workspaceContent.js'
5
+import currentWorkspace from './currentWorkspace.js'
6
+import workspaceContentList from './workspaceContentList.js'
6 7
 import workspaceList from './workspaceList.js'
7
-import app from './app.js'
8
+import appList from './appList.js'
8 9
 import contentType from './contentType.js'
9 10
 import timezone from './timezone.js'
10 11
 
11
-const rootReducer = combineReducers({ lang, flashMessage, user, workspaceContent, workspaceList, app, contentType, timezone })
12
+const rootReducer = combineReducers({ lang, flashMessage, user, currentWorkspace, workspaceContentList, workspaceList, appList, contentType, timezone })
12 13
 
13 14
 export default rootReducer

frontend/src/reducer/workspaceContent.js → frontend/src/reducer/workspaceContentList.js Voir le fichier

@@ -2,13 +2,14 @@ import {
2 2
   SET,
3 3
   UPDATE,
4 4
   WORKSPACE,
5
+  WORKSPACE_CONTENT,
5 6
   FOLDER
6 7
 } from '../action-creator.sync.js'
7 8
 
8
-export default function workspace (state = [], action) {
9
+export default function workspaceContentList (state = [], action) {
9 10
   switch (action.type) {
10
-    case `${SET}/${WORKSPACE}/Content`:
11
-      return action.workspaceContent.map(wsc => ({
11
+    case `${SET}/${WORKSPACE_CONTENT}`:
12
+      return action.workspaceContentList.map(wsc => ({
12 13
         id: wsc.content_id,
13 14
         label: wsc.label,
14 15
         slug: wsc.slug,

+ 2 - 3
frontend/src/reducer/workspaceList.js Voir le fichier

@@ -4,8 +4,7 @@ import {
4 4
   WORKSPACE_LIST,
5 5
   USER_ROLE
6 6
 } from '../action-creator.sync.js'
7
-
8
-const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route
7
+import { handleRouteFromApi } from '../helper.js'
9 8
 
10 9
 export function workspaceList (state = [], action) {
11 10
   switch (action.type) {
@@ -14,7 +13,7 @@ export function workspaceList (state = [], action) {
14 13
         id: ws.workspace_id,
15 14
         label: ws.label,
16 15
         slug: ws.slug,
17
-        description: ws.description,
16
+        // description: ws.description, // not returned by /api/v2/users/:idUser/workspaces
18 17
         sidebarEntry: ws.sidebar_entries.map(sbe => ({
19 18
           slug: sbe.slug,
20 19
           route: handleRouteFromApi(sbe.route),