Browse Source

integration of real api (still wip)

Skylsmoi 5 years ago
parent
commit
7e0a3fbafb

+ 38 - 16
jsonserver/server.js View File

@@ -55,22 +55,31 @@ server.get('/sessions/whoami', (req, res) =>
55 55
 server.get('/user/:id/workspace', (req, res) => res.jsonp(jsonDb.workspace_list))
56 56
 
57 57
 server.get('/workspace/:id', (req, res) => res.jsonp(
58
-  Object.assign(
59
-    {},
60
-    jsonDb.workspace_detail,
61
-    {content: shuffle(jsonDb.workspace_detail.content.map(
62
-      c => Object.assign({}, c, {workspace_id: req.params.id})
63
-    ))},
64
-    {id: req.params.id}
65
-  )
58
+  {} // this EP should return meta data of the workspace (id, description, label, slug, sidebar_entries (?)
66 59
 ))
67 60
 
68
-server.get('/workspace/:idws/folder/:idf', (req, res) => {
69
-  switch (req.params.idf) {
70
-    case '3':
71
-      return res.jsonp(jsonDb.folder_content_3)
72
-    case '11':
73
-      return res.jsonp(jsonDb.folder_content_11)
61
+server.get('/workspace/:idws/contents/', (req, res) => {
62
+  console.log(req.query)
63
+  if (req.query.parent_id !== undefined) { // get content of a folder
64
+    switch (req.query.parent_id) {
65
+      case '3':
66
+        return res.jsonp(jsonDb.folder_content_3)
67
+      case '11':
68
+        return res.jsonp(jsonDb.folder_content_11)
69
+    }
70
+  } else { // get content of a workspace
71
+    return res.jsonp(
72
+      Object.assign(
73
+        {},
74
+        jsonDb.workspace_detail,
75
+        {
76
+          content: shuffle(jsonDb.workspace_detail.content.map(
77
+            c => Object.assign({}, c, {workspace_id: req.params.idws})
78
+          ))
79
+        },
80
+        {id: req.params.idws}
81
+      )
82
+    )
74 83
   }
75 84
 })
76 85
 
@@ -78,9 +87,10 @@ server.get('/user/:id/roles', (req, res) => res.jsonp(jsonDb.user_role))
78 87
 
79 88
 server.get('/timezone', (req, res) => res.jsonp(timezoneDb.timezone))
80 89
 
81
-server.get('/workspace/:idws/content/:idc', (req, res) => {
90
+server.get('/workspace/:idws/contents/:idc', (req, res) => {
82 91
   switch (req.params.idc) {
83 92
     case '1': // pageHtml
93
+    case '5':
84 94
       return res.jsonp(jsonDb.content_data_pageHtml)
85 95
     case '2':
86 96
       return res.jsonp(jsonDb.content_data_thread)
@@ -89,9 +99,10 @@ server.get('/workspace/:idws/content/:idc', (req, res) => {
89 99
   }
90 100
 })
91 101
 
92
-server.get('/workspace/:idws/content/:idc/timeline', (req, res) => {
102
+server.get('/workspace/:idws/contents/:idc/timeline', (req, res) => {
93 103
   switch (req.params.idc) {
94 104
     case '1': // pageHtml
105
+    case '5':
95 106
       return res.jsonp(jsonDb.timeline)
96 107
     case '2':
97 108
       return res.jsonp([])
@@ -104,3 +115,14 @@ server.use(router)
104 115
 server.listen(GLOBAL_PORT, () => {
105 116
   console.log('JSON Server is running on port : ' + GLOBAL_PORT)
106 117
 })
118
+
119
+
120
+/*
121
+Object.keys(req) :
122
+['_readableState', 'readable', 'domain', '_events', '_eventsCount', '_maxListeners', 'socket', 'connection',
123
+'httpVersionMajor', 'httpVersionMinor', 'httpVersion', 'complete', 'headers', 'rawHeaders', 'trailers', 'rawTrailers',
124
+'upgrade', 'url', 'method', 'statusCode', 'statusMessage', 'client', '_consuming', '_dumped', 'next', 'baseUrl',
125
+'originalUrl', '_parsedUrl', 'params', 'query', 'res', '_parsedOriginalUrl', '_startAt', '_startTime', '_remoteAddress',
126
+'body', 'route' ]
127
+
128
+ */

+ 22 - 3
jsonserver/static_db.json View File

@@ -105,10 +105,29 @@
105 105
       {
106 106
         "id": 1,
107 107
         "parent_id": null,
108
+        "is_archived": false,
109
+        "is_deleted": false,
108 110
         "workspace_id": 1,
109
-        "title": "Modification Design v2",
110
-        "type": "PageHtml",
111
-        "status": "validated"
111
+        "show_in_ui": true,
112
+        "label": "Modification Design v2",
113
+        "slug": "modification-design-v2",
114
+        "content_type_slug": "htmlpage",
115
+        "status_slug": "validated"
116
+      },
117
+      {
118
+        "content_type_slug": "htmlpage",
119
+        "id": 6,
120
+        "is_archived": false,
121
+        "is_deleted": false,
122
+        "label": "Intervention Report 12",
123
+        "parent_id": 34,
124
+        "show_in_ui": true,
125
+        "slug": "intervention-report-12",
126
+        "status_slug": "closed-deprecated",
127
+        "sub_content_type_slug": [
128
+          "string"
129
+        ],
130
+        "workspace_id": 19
112 131
       },
113 132
       {
114 133
         "id": 2,

+ 1 - 0
package.json View File

@@ -58,6 +58,7 @@
58 58
   "standard": {
59 59
     "globals": [
60 60
       "fetch",
61
+      "btoa",
61 62
       "history",
62 63
       "GLOBAL_renderApp",
63 64
       "GLOBAL_renderCreateContentApp",

+ 69 - 42
src/action-creator.async.js View File

@@ -6,16 +6,15 @@ import {
6 6
   updateLangList,
7 7
   USER_LOGIN,
8 8
   USER_LOGOUT,
9
-  USER_DATA,
10 9
   USER_ROLE,
11 10
   USER_CONNECTED,
12
-  updateUserData,
13 11
   setUserRole,
14 12
   WORKSPACE,
15 13
   WORKSPACE_LIST,
16 14
   FOLDER,
17 15
   setFolderData,
18
-  APP_LIST
16
+  APP_LIST,
17
+  CONTENT_TYPE_LIST
19 18
 } from './action-creator.sync.js'
20 19
 
21 20
 /*
@@ -66,8 +65,11 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
66 65
 
67 66
 export const getLangList = () => async dispatch => {
68 67
   const fetchGetLangList = await fetchWrapper({
69
-    url: `${FETCH_CONFIG.mockApiUrl}/lang`,
70
-    param: {...FETCH_CONFIG.header, method: 'GET'},
68
+    url: `${FETCH_CONFIG.apiUrl}/lang`,
69
+    param: {
70
+      headers: {...FETCH_CONFIG.headers},
71
+      method: 'GET'
72
+    },
71 73
     actionName: LANG,
72 74
     dispatch
73 75
   })
@@ -76,8 +78,11 @@ export const getLangList = () => async dispatch => {
76 78
 
77 79
 export const getTimezone = () => async dispatch => {
78 80
   const fetchGetTimezone = await fetchWrapper({
79
-    url: `${FETCH_CONFIG.mockApiUrl}/timezone`,
80
-    param: {...FETCH_CONFIG.header, method: 'GET'},
81
+    url: `${FETCH_CONFIG.apiUrl}/timezone`,
82
+    param: {
83
+      headers: {...FETCH_CONFIG.headers},
84
+      method: 'GET'
85
+    },
81 86
     actionName: TIMEZONE,
82 87
     dispatch
83 88
   })
@@ -86,7 +91,7 @@ export const getTimezone = () => async dispatch => {
86 91
 
87 92
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
88 93
   return fetchWrapper({
89
-    url: `${FETCH_CONFIG.mockApiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
94
+    url: `${FETCH_CONFIG.apiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
90 95
     param: {
91 96
       headers: {...FETCH_CONFIG.headers},
92 97
       method: 'POST',
@@ -103,7 +108,7 @@ export const postUserLogin = (login, password, rememberMe) => async dispatch =>
103 108
 
104 109
 export const postUserLogout = () => async dispatch => {
105 110
   return fetchWrapper({
106
-    url: `${FETCH_CONFIG.mockApiUrl}/sessions/logout`, // FETCH_CONFIG.apiUrl
111
+    url: `${FETCH_CONFIG.apiUrl}/sessions/logout`, // FETCH_CONFIG.apiUrl
107 112
     param: {
108 113
       headers: {...FETCH_CONFIG.headers},
109 114
       method: 'POST'
@@ -115,8 +120,11 @@ export const postUserLogout = () => async dispatch => {
115 120
 
116 121
 export const getUserIsConnected = () => async dispatch => {
117 122
   return fetchWrapper({
118
-    url: `${FETCH_CONFIG.mockApiUrl}/sessions/whoami`, // FETCH_CONFIG.apiUrl
119
-    param: {...FETCH_CONFIG.header, method: 'GET'},
123
+    url: `${FETCH_CONFIG.apiUrl}/sessions/whoami`, // FETCH_CONFIG.apiUrl
124
+    param: {
125
+      headers: {...FETCH_CONFIG.headers},
126
+      method: 'GET'
127
+    },
120 128
     actionName: USER_CONNECTED,
121 129
     dispatch
122 130
   })
@@ -124,67 +132,86 @@ export const getUserIsConnected = () => async dispatch => {
124 132
 
125 133
 export const getUserRole = user => async dispatch => {
126 134
   const fetchGetUserRole = await fetchWrapper({
127
-    url: `${FETCH_CONFIG.mockApiUrl}/user/${user.id}/roles`,
128
-    param: {...FETCH_CONFIG.header, method: 'GET'},
135
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/roles`,
136
+    param: {
137
+      headers: {...FETCH_CONFIG.headers},
138
+      method: 'GET'
139
+    },
129 140
     actionName: USER_ROLE,
130 141
     dispatch
131 142
   })
132 143
   if (fetchGetUserRole.status === 200) dispatch(setUserRole(fetchGetUserRole.json))
133 144
 }
134 145
 
135
-export const updateUserLang = newLang => async dispatch => { // unused
136
-  const fetchUpdateUserLang = await fetchWrapper({
137
-    url: `${FETCH_CONFIG.mockApiUrl}/user`,
138
-    param: {...FETCH_CONFIG.header, method: 'PATCH', body: JSON.stringify({lang: newLang})},
139
-    actionName: USER_DATA,
146
+export const getWorkspaceList = idUser => dispatch => {
147
+  return fetchWrapper({
148
+    url: `${FETCH_CONFIG.apiUrl}/users/${idUser}/workspaces`,
149
+    param: {
150
+      headers: {...FETCH_CONFIG.headers},
151
+      method: 'GET'
152
+    },
153
+    actionName: WORKSPACE_LIST,
140 154
     dispatch
141 155
   })
142
-  if (fetchUpdateUserLang.status === 200) dispatch(updateUserData({lang: fetchUpdateUserLang.json.lang}))
143 156
 }
144 157
 
145
-// export const testResponseNoData = () => async dispatch => {
146
-//   const fetchResponseNoData = await fetchWrapper({
147
-//     url: 'http://localhost:3001/deletenodata',
148
-//     param: {...FETCH_CONFIG.header, method: 'DELETE'},
149
-//     actionName: 'TestNoData',
150
-//     dispatch
151
-//   })
152
-//   console.log('jsonResponseNoData', fetchResponseNoData)
153
-// }
154
-
155
-export const getWorkspaceList = userId => dispatch => {
158
+export const getWorkspaceContentList = idWorkspace => dispatch => {
156 159
   return fetchWrapper({
157
-    url: `${FETCH_CONFIG.mockApiUrl}/user/${userId}/workspace`,
158
-    param: {...FETCH_CONFIG.header, method: 'GET'},
159
-    actionName: WORKSPACE_LIST,
160
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents`,
161
+    param: {
162
+      headers: {...FETCH_CONFIG.headers},
163
+      method: 'GET'
164
+    },
165
+    actionName: WORKSPACE,
160 166
     dispatch
161 167
   })
162 168
 }
163 169
 
164
-export const getWorkspaceContent = workspaceId => dispatch => {
170
+export const getWorkspaceContent = (idWorkspace, idContent) => dispatch => {
165 171
   return fetchWrapper({
166
-    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}`,
167
-    param: {...FETCH_CONFIG.header, method: 'GET'},
172
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/${idContent}`,
173
+    param: {
174
+      headers: {...FETCH_CONFIG.headers},
175
+      method: 'GET'
176
+    },
168 177
     actionName: WORKSPACE,
169 178
     dispatch
170 179
   })
171 180
 }
172 181
 
173
-export const getFolderContent = (workspaceId, folderId) => async dispatch => {
182
+export const getFolderContent = (idWorkspace, idFolder) => async dispatch => {
174 183
   const fetchGetFolderContent = await fetchWrapper({
175
-    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}/folder/${folderId}`,
176
-    param: {...FETCH_CONFIG.header, method: 'GET'},
184
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/?parent_id=${idFolder}`,
185
+    param: {
186
+      headers: {...FETCH_CONFIG.headers},
187
+      method: 'GET'
188
+    },
177 189
     actionName: `${WORKSPACE}/${FOLDER}`,
178 190
     dispatch
179 191
   })
180
-  if (fetchGetFolderContent.status === 200) dispatch(setFolderData(folderId, fetchGetFolderContent.json))
192
+  if (fetchGetFolderContent.status === 200) dispatch(setFolderData(idFolder, fetchGetFolderContent.json))
181 193
 }
182 194
 
183 195
 export const getAppList = () => dispatch => {
184 196
   return fetchWrapper({
185
-    url: `${FETCH_CONFIG.mockApiUrl}/app/config`,
186
-    param: {...FETCH_CONFIG.header, method: 'GET'},
197
+    url: `${FETCH_CONFIG.apiUrl}/system/applications`,
198
+    param: {
199
+      headers: {...FETCH_CONFIG.headers},
200
+      method: 'GET'
201
+    },
187 202
     actionName: APP_LIST,
188 203
     dispatch
189 204
   })
190 205
 }
206
+
207
+export const getContentTypeList = () => dispatch => {
208
+  return fetchWrapper({
209
+    url: `${FETCH_CONFIG.apiUrl}/system/content_types`,
210
+    param: {
211
+      headers: {...FETCH_CONFIG.headers},
212
+      method: 'GET'
213
+    },
214
+    actionName: CONTENT_TYPE_LIST,
215
+    dispatch
216
+  })
217
+}

+ 4 - 1
src/action-creator.sync.js View File

@@ -23,7 +23,7 @@ export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNo
23 23
   ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
24 24
 
25 25
 export const WORKSPACE = 'Workspace'
26
-export const setWorkspaceData = (workspace, filterStr = '') => ({ type: `Set/${WORKSPACE}`, workspace, filterStr })
26
+export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `Set/${WORKSPACE}/Content`, workspaceContent, filterStr })
27 27
 export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
28 28
 
29 29
 export const FOLDER = 'Folder'
@@ -36,6 +36,9 @@ export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) =>
36 36
 export const APP_LIST = 'App/List'
37 37
 export const setAppList = appList => ({ type: `Set/${APP_LIST}`, appList })
38 38
 
39
+export const CONTENT_TYPE_LIST = 'ContentType/List'
40
+export const setContentTypeList = contentTypeList => ({ type: `Set/${CONTENT_TYPE_LIST}`, contentTypeList })
41
+
39 42
 export const LANG = 'Lang'
40 43
 export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
41 44
 export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })

+ 1 - 1
src/component/Header/MenuActionListItem/Notification.jsx View File

@@ -1,5 +1,5 @@
1 1
 import React from 'react'
2
-import PropTypes from 'prop-types'
2
+// import PropTypes from 'prop-types'
3 3
 
4 4
 const Notification = props => {
5 5
   return (

+ 21 - 88
src/component/Sidebar/WorkspaceListItem.jsx View File

@@ -3,17 +3,18 @@ import classnames from 'classnames'
3 3
 import { translate } from 'react-i18next'
4 4
 import PropTypes from 'prop-types'
5 5
 import AnimateHeight from 'react-animate-height'
6
+import { Link } from 'react-router-dom'
6 7
 
7 8
 const WorkspaceListItem = props => {
8 9
   return (
9 10
     <li className='sidebar__navigation__workspace__item'>
10 11
       <div className='sidebar__navigation__workspace__item__wrapper' onClick={props.onClickTitle}>
11 12
         <div className='sidebar__navigation__workspace__item__number'>
12
-          {props.name.substring(0, 2).toUpperCase()}
13
+          {props.label.substring(0, 2).toUpperCase()}
13 14
         </div>
14 15
 
15
-        <div className='sidebar__navigation__workspace__item__name' title={props.name}>
16
-          {props.name}
16
+        <div className='sidebar__navigation__workspace__item__name' title={props.label}>
17
+          {props.label}
17 18
         </div>
18 19
 
19 20
         <div className='sidebar__navigation__workspace__item__icon'>
@@ -26,96 +27,29 @@ const WorkspaceListItem = props => {
26 27
           className='sidebar__navigation__workspace__item__submenu'
27 28
           id={`sidebarSubMenu_${props.number}`}
28 29
         >
29
-          <li
30
-            className='sidebar__navigation__workspace__item__submenu__dropdown'
31
-            onClick={() => props.onClickAllContent(props.idWs)}
32
-          >
33
-            <div className='dropdown__icon'>
34
-              <i className='fa fa-th' />
35
-            </div>
36
-
37
-            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
38
-              <div className='dropdown__title'>
39
-                <div className='dropdown__title__text'>
40
-                  Tous les contenus
41
-                </div>
42
-              </div>
43
-            </div>
44
-
45
-            {/*
46
-            <div className='dropdown__subdropdown dropdown-menu' aria-labelledby='navbarDropdown'>
47
-              <div className='dropdown__subdropdown__item dropdown-item'>
48
-                <div className='dropdown__subdropdown__item__iconfile alignname'>
49
-                  <i className='fa fa-file-text-o' />
50
-                </div>
51
-
52
-                <div className='dropdown__subdropdown__item__textfile alignname'>
53
-                  Documents Archivés
54
-                </div>
55
-              </div>
56
-              <div className='dropdown__subdropdown__item dropdown-item'>
57
-                <div className='dropdown__subdropdown__item__iconfile alignname'>
58
-                  <i className='fa fa-file-text-o' />
59
-                </div>
60
-
61
-                <div className='dropdown__subdropdown__item__textfile alignname'>
62
-                  Documents Supprimés
63
-                </div>
64
-              </div>
65
-            </div>
66
-            */}
67
-
68
-          </li>
69
-
70
-          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
71
-            <div className='dropdown__icon'>
72
-              <i className='fa fa-signal dashboard-color' />
73
-            </div>
74
-
75
-            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
76
-              <div className='dropdown__title'>
77
-                <div className='dropdown__title__text'>
78
-                  Tableau de bord
79
-                </div>
80
-              </div>
81
-            </div>
82
-          </li>
83
-
84
-          { Object.keys(props.app).map(a =>
30
+          { props.allowedApp.map(aa =>
85 31
             <li
86
-              className={classnames('sidebar__navigation__workspace__item__submenu__dropdown', {'activeFilter': props.activeFilterList.includes(a)})}
87
-              onClick={() => props.onClickContentFilter(props.idWs, a)}
88
-              key={a}
32
+              // onClick={() => props.onClickContentFilter(props.idWs, aa.slug)}
33
+              key={aa.slug}
89 34
             >
90
-              <div className='dropdown__icon'>
91
-                <i className={classnames(props.app[a].icon)} style={{backgroudColor: props.app[a].color}} />
92
-              </div>
35
+              <Link to={aa.route}>
36
+                <div className={classnames('sidebar__navigation__workspace__item__submenu__dropdown', {'activeFilter': props.activeFilterList.includes(aa.slug)})}>
37
+                  <div className='dropdown__icon'>
38
+                    <i className={classnames(`fa fa-${aa.faIcon}`)} style={{backgroudColor: aa.hexcolor}} />
39
+                  </div>
93 40
 
94
-              <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
41
+                  <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
95 42
 
96
-                <div className='dropdown__title' id='navbarDropdown'>
97
-                  <div className='dropdown__title__text'>
98
-                    {props.app[a].label[props.lang.id]}
43
+                    <div className='dropdown__title' id='navbarDropdown'>
44
+                      <div className='dropdown__title__text'>
45
+                        {aa.label/* [props.lang.id] */}
46
+                      </div>
47
+                    </div>
99 48
                   </div>
100 49
                 </div>
101
-              </div>
50
+              </Link>
102 51
             </li>
103 52
           )}
104
-
105
-          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
106
-
107
-            <div className='dropdown__icon'>
108
-              <i className='fa fa-calendar calendar-color' />
109
-            </div>
110
-
111
-            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
112
-              <div className='dropdown__title'>
113
-                <div className='dropdown__title__text'>
114
-                  Calendrier
115
-                </div>
116
-              </div>
117
-            </div>
118
-          </li>
119 53
         </ul>
120 54
       </AnimateHeight>
121 55
     </li>
@@ -125,9 +59,8 @@ const WorkspaceListItem = props => {
125 59
 export default translate()(WorkspaceListItem)
126 60
 
127 61
 WorkspaceListItem.propTypes = {
128
-  number: PropTypes.number.isRequired,
129
-  name: PropTypes.string.isRequired,
130
-  app: PropTypes.object,
62
+  label: PropTypes.string.isRequired,
63
+  allowedApp: PropTypes.array,
131 64
   onClickTitle: PropTypes.func,
132 65
   onClickAllContent: PropTypes.func,
133 66
   isOpenInSidebar: PropTypes.bool

+ 16 - 52
src/component/Workspace/ContentItem.jsx View File

@@ -3,55 +3,17 @@ import PropTypes from 'prop-types'
3 3
 import classnames from 'classnames'
4 4
 import BtnExtandedAction from './BtnExtandedAction.jsx'
5 5
 
6
-const FileItem = props => {
7
-  const iconStatus = (() => {
8
-    switch (props.status) {
9
-      case 'current':
10
-        return 'fa fa-fw fa-cogs'
11
-      case 'validated':
12
-        return 'fa fa-fw fa-check'
13
-      case 'canceled':
14
-        return 'fa fa-fw fa-times'
15
-      case 'outdated':
16
-        return 'fa fa-fw fa-ban'
17
-    }
18
-  })()
19
-
20
-  const textStatus = (() => {
21
-    switch (props.status) {
22
-      case 'current':
23
-        return 'En cours'
24
-      case 'validated':
25
-        return 'Validé'
26
-      case 'canceled':
27
-        return 'Annulé'
28
-      case 'outdated':
29
-        return 'Obsolète'
30
-    }
31
-  })()
32
-
33
-  const colorStatus = (() => {
34
-    switch (props.status) {
35
-      case 'current':
36
-        return ' currentColor'
37
-      case 'validated':
38
-        return ' validateColor'
39
-      case 'canceled':
40
-        return ' cancelColor'
41
-      case 'outdated':
42
-        return ' outdateColor'
43
-    }
44
-  })()
45
-
6
+const ContentItem = props => {
7
+  const status = props.contentType.availableStatuses.find(s => s.slug === props.statusSlug)
46 8
   return (
47 9
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
48 10
       <div className='content__type'>
49
-        <i className={props.icon} />
11
+        <i className={`fa fa-${props.faIcon}`} />
50 12
       </div>
51 13
 
52 14
       <div className='content__name'>
53 15
         <div className='content__name__text'>
54
-          { props.name }
16
+          { props.label }
55 17
         </div>
56 18
       </div>
57 19
 
@@ -59,30 +21,32 @@ const FileItem = props => {
59 21
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
60 22
       </div>
61 23
 
62
-      <div className={classnames('content__status d-flex align-items-center justify-content-start') + colorStatus}>
24
+      <div className={classnames('content__status d-flex align-items-center justify-content-start')} style={{color: status.hexcolor}}>
63 25
         <div className='content__status__icon d-block '>
64
-          <i className={iconStatus} />
26
+          <i className={`fa fa-${status.fa_icon}`} />
65 27
         </div>
66 28
         <div className='content__status__text d-none d-xl-block'>
67
-          {textStatus}
29
+          {status.label}
68 30
         </div>
69 31
       </div>
70 32
     </div>
71 33
   )
72 34
 }
73 35
 
74
-export default FileItem
36
+export default ContentItem
75 37
 
76
-FileItem.propTypes = {
38
+ContentItem.propTypes = {
77 39
   type: PropTypes.string.isRequired,
78
-  status: PropTypes.string.isRequired,
40
+  statusSlug: PropTypes.string.isRequired,
79 41
   customClass: PropTypes.string,
80
-  name: PropTypes.string,
81
-  onClickItem: PropTypes.func
42
+  label: PropTypes.string,
43
+  contentType: PropTypes.object,
44
+  onClickItem: PropTypes.func,
45
+  faIcon: PropTypes.string
82 46
 }
83 47
 
84
-FileItem.defaultProps = {
85
-  name: '',
48
+ContentItem.defaultProps = {
49
+  label: '',
86 50
   customClass: '',
87 51
   onClickItem: () => {}
88 52
 }

+ 49 - 37
src/component/Workspace/Folder.jsx View File

@@ -20,7 +20,6 @@ class Folder extends React.Component {
20 20
   }
21 21
 
22 22
   handleClickCreateContent = (e, folder, type) => {
23
-    e.preventDefault()
24 23
     e.stopPropagation() // because we have a link inside a link (togler and newFile)
25 24
     this.props.onClickCreateContent(folder, type)
26 25
   }
@@ -49,13 +48,21 @@ class Folder extends React.Component {
49 48
           </div>
50 49
 
51 50
           <div className='folder__header__name'>
52
-            { folderData.title }
51
+            { folderData.label }
53 52
           </div>
54 53
 
55 54
           <div className='folder__header__button'>
56 55
 
57 56
             <div className='folder__header__button__addbtn'>
58
-              <button className='addbtn__text btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
57
+              <button
58
+                className='addbtn__text btn btn-outline-primary dropdown-toggle'
59
+                type='button'
60
+                id='dropdownMenuButton'
61
+                data-toggle='dropdown'
62
+                aria-haspopup='true'
63
+                aria-expanded='false'
64
+                onClick={e => e.stopPropagation()}
65
+              >
59 66
                 {t('Folder.create')} ...
60 67
               </button>
61 68
 
@@ -72,6 +79,7 @@ class Folder extends React.Component {
72 79
                     </div>
73 80
                   </div>
74 81
                 </div>
82
+
75 83
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageHtml')}>
76 84
                   <div className='subdropdown__link__apphtml d-flex align-items-center'>
77 85
                     <div className='subdropdown__link__apphtml__icon mr-3'>
@@ -82,6 +90,7 @@ class Folder extends React.Component {
82 90
                     </div>
83 91
                   </div>
84 92
                 </div>
93
+
85 94
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'File')}>
86 95
                   <div className='subdropdown__link__appfile d-flex align-items-center'>
87 96
                     <div className='subdropdown__link__appfile__icon mr-3'>
@@ -92,6 +101,7 @@ class Folder extends React.Component {
92 101
                     </div>
93 102
                   </div>
94 103
                 </div>
104
+
95 105
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageMarkdown')}>
96 106
                   <div className='subdropdown__link__appmarkdown d-flex align-items-center'>
97 107
                     <div className='subdropdown__link__appmarkdown__icon mr-3'>
@@ -102,6 +112,7 @@ class Folder extends React.Component {
102 112
                     </div>
103 113
                   </div>
104 114
                 </div>
115
+
105 116
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Thread')}>
106 117
                   <div className='subdropdown__link__appthread d-flex align-items-center'>
107 118
                     <div className='subdropdown__link__appthread__icon mr-3'>
@@ -112,6 +123,7 @@ class Folder extends React.Component {
112 123
                     </div>
113 124
                   </div>
114 125
                 </div>
126
+
115 127
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Task')}>
116 128
                   <div className='subdropdown__link__apptask d-flex align-items-center'>
117 129
                     <div className='subdropdown__link__apptask__icon mr-3'>
@@ -122,6 +134,7 @@ class Folder extends React.Component {
122 134
                     </div>
123 135
                   </div>
124 136
                 </div>
137
+
125 138
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Issue')}>
126 139
                   <div className='subdropdown__link__appissue d-flex align-items-center'>
127 140
                     <div className='subdropdown__link__appissue__icon mr-3'>
@@ -152,35 +165,37 @@ class Folder extends React.Component {
152 165
         </div>
153 166
 
154 167
         <div className='folder__content'>
155
-          { folderData.content.map((c, i) => c.type === 'folder'
156
-            ? <Folder
157
-              app={app}
158
-              folderData={c}
159
-              onClickItem={onClickItem}
160
-              onClickExtendedAction={onClickExtendedAction}
161
-              onClickFolder={onClickFolder}
162
-              isLast={isLast}
163
-              t={t}
164
-              key={c.id}
165
-            />
166
-            : <FileItem
167
-              icon={(app[c.type] || {icon: ''}).icon}
168
-              name={c.title}
169
-              type={c.type}
170
-              status={c.status}
171
-              onClickItem={() => onClickItem(c)}
172
-              onClickExtendedAction={{
173
-                // we have to use the event here because it is the only place where we also have the content (c)
174
-                edit: e => onClickExtendedAction.edit(e, c),
175
-                move: e => onClickExtendedAction.move(e, c),
176
-                download: e => onClickExtendedAction.download(e, c),
177
-                archive: e => onClickExtendedAction.archive(e, c),
178
-                delete: e => onClickExtendedAction.delete(e, c)
179
-              }}
180
-              isLast={isLast && i === folderData.content.length - 1}
181
-              key={c.id}
182
-            />
183
-          )}
168
+          {
169
+          //   folderData.map((c, i) => c.type === 'folder'
170
+          //   ? <Folder
171
+          //     app={app}
172
+          //     folderData={c}
173
+          //     onClickItem={onClickItem}
174
+          //     onClickExtendedAction={onClickExtendedAction}
175
+          //     onClickFolder={onClickFolder}
176
+          //     isLast={isLast}
177
+          //     t={t}
178
+          //     key={c.id}
179
+          //   />
180
+          //   : <FileItem
181
+          //     icon={(app[c.type] || {icon: ''}).icon}
182
+          //     name={c.title}
183
+          //     type={c.type}
184
+          //     status={c.status}
185
+          //     onClickItem={() => onClickItem(c)}
186
+          //     onClickExtendedAction={{
187
+          //       // we have to use the event here because it is the only place where we also have the content (c)
188
+          //       edit: e => onClickExtendedAction.edit(e, c),
189
+          //       move: e => onClickExtendedAction.move(e, c),
190
+          //       download: e => onClickExtendedAction.download(e, c),
191
+          //       archive: e => onClickExtendedAction.archive(e, c),
192
+          //       delete: e => onClickExtendedAction.delete(e, c)
193
+          //     }}
194
+          //     isLast={isLast && i === folderData.content.length - 1}
195
+          //     key={c.id}
196
+          //   />
197
+          // )
198
+          }
184 199
         </div>
185 200
       </div>
186 201
     )
@@ -190,11 +205,8 @@ class Folder extends React.Component {
190 205
 export default translate()(Folder)
191 206
 
192 207
 Folder.propTypes = {
193
-  folderData: PropTypes.shape({
194
-    title: PropTypes.string.isRequired,
195
-    content: PropTypes.array
196
-  }),
197
-  app: PropTypes.object,
208
+  folderData: PropTypes.object,
209
+  app: PropTypes.array,
198 210
   onClickItem: PropTypes.func.isRequired,
199 211
   onClickFolder: PropTypes.func.isRequired,
200 212
   isLast: PropTypes.bool.isRequired

+ 7 - 8
src/container/Sidebar.jsx View File

@@ -10,6 +10,8 @@ import {
10 10
 } from '../action-creator.sync.js'
11 11
 import { PAGE } from '../helper.js'
12 12
 
13
+const qs = require('query-string')
14
+
13 15
 class Sidebar extends React.Component {
14 16
   constructor (props) {
15 17
     super(props)
@@ -35,12 +37,10 @@ class Sidebar extends React.Component {
35 37
   }
36 38
 
37 39
   handleClickContentFilter = (idWs, filter) => {
38
-    const { workspace, history, dispatch } = this.props
40
+    const { workspace, history } = this.props
39 41
 
40 42
     const newFilter = workspace.filter.includes(filter) ? [] : [filter] // use an array to allow multiple filters (NYI)
41 43
 
42
-    dispatch(updateWorkspaceFilter(newFilter))
43
-
44 44
     history.push(`${PAGE.WORKSPACE.CONTENT_LIST(idWs)}?type=${newFilter.join(';')}`) // workspace.filter gets updated on react redraw from match.params
45 45
   }
46 46
 
@@ -48,7 +48,7 @@ class Sidebar extends React.Component {
48 48
 
49 49
   render () {
50 50
     const { sidebarClose, workspaceIdInUrl } = this.state
51
-    const { activeLang, workspace, workspaceList, app, t } = this.props
51
+    const { activeLang, workspaceList, t } = this.props
52 52
 
53 53
     return (
54 54
       <div className={classnames('sidebar', {'sidebarclose': sidebarClose})}>
@@ -61,12 +61,11 @@ class Sidebar extends React.Component {
61 61
             <ul className='sidebar__navigation__workspace'>
62 62
               { workspaceList.map((ws, i) =>
63 63
                 <WorkspaceListItem
64
-                  number={++i}
65 64
                   idWs={ws.id}
66
-                  name={ws.title}
67
-                  app={app}
65
+                  label={ws.label}
66
+                  allowedApp={ws.sidebarEntry}
68 67
                   lang={activeLang}
69
-                  activeFilterList={ws.id === workspaceIdInUrl ? workspace.filter : []}
68
+                  activeFilterList={ws.id === workspaceIdInUrl ? [qs.parse(this.props.location.search).type] : []}
70 69
                   isOpenInSidebar={ws.isOpenInSidebar}
71 70
                   onClickTitle={() => this.handleClickWorkspace(ws.id, !ws.isOpenInSidebar)}
72 71
                   onClickAllContent={this.handleClickAllContent}

+ 62 - 28
src/container/WorkspaceContent.jsx View File

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3 4
 import appFactory from '../appFactory.js'
4 5
 import { PAGE } from '../helper.js'
5 6
 import Sidebar from './Sidebar.jsx'
@@ -12,13 +13,17 @@ import PageContent from '../component/common/layout/PageContent.jsx'
12 13
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
13 14
 import {
14 15
   getAppList,
16
+  getContentTypeList,
17
+  getWorkspaceContentList,
15 18
   getWorkspaceContent,
16 19
   getFolderContent,
17 20
   getWorkspaceList
18 21
 } from '../action-creator.async.js'
19 22
 import {
20
-  newFlashMessage, setAppList,
21
-  setWorkspaceData,
23
+  newFlashMessage,
24
+  setAppList,
25
+  setContentTypeList,
26
+  setWorkspaceContent,
22 27
   setWorkspaceListIsOpenInSidebar,
23 28
   updateWorkspaceListData
24 29
 } from '../action-creator.sync.js'
@@ -53,13 +58,21 @@ class WorkspaceContent extends React.Component {
53 58
 
54 59
   async componentDidMount () {
55 60
     const { workspaceIdInUrl } = this.state
56
-    const { user, workspaceList, app, match, location, dispatch } = this.props
61
+    const { user, workspaceList, app, contentType, match, location, dispatch } = this.props
57 62
 
58
-    if (Object.keys(app).length === 0) {
63
+    console.log('componentDidMount')
64
+
65
+    if (app.length === 0) {
59 66
       const fetchGetAppList = await dispatch(getAppList())
60 67
       if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
61 68
     }
62 69
 
70
+    if (contentType.length === 0) {
71
+      const fetchGetContentTypeList = await dispatch(getContentTypeList())
72
+      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
73
+    }
74
+
75
+
63 76
     let wsToLoad = null
64 77
     if (match.params.idws !== undefined) wsToLoad = match.params.idws
65 78
 
@@ -78,35 +91,46 @@ class WorkspaceContent extends React.Component {
78 91
 
79 92
     if (wsToLoad === null) return // ws already loaded
80 93
 
81
-    const wsContent = await dispatch(getWorkspaceContent(wsToLoad))
94
+    const wsContent = await dispatch(getWorkspaceContentList(wsToLoad))
82 95
 
83
-    if (wsContent.status === 200) dispatch(setWorkspaceData(wsContent.json, qs.parse(location.search).type))
96
+    if (wsContent.status === 200) dispatch(setWorkspaceContent(wsContent.json, qs.parse(location.search).type))
84 97
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
85 98
   }
86 99
 
87 100
   componentDidUpdate (prevProps, prevState) {
88
-    const { app, workspace, user, renderApp, match, dispatch } = this.props
101
+    const { contentType, workspace, user, renderApp, match } = this.props
102
+
103
+    console.log('componentDidUpdate')
89 104
 
90 105
     if (this.state.workspaceIdInUrl === null) return
91 106
 
92
-    const newWorkspaceId = parseInt(match.params.idws)
93
-    if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
107
+    const idWorkspace = parseInt(match.params.idws)
108
+    if (prevState.workspaceIdInUrl !== idWorkspace) this.setState({workspaceIdInUrl: idWorkspace})
109
+
110
+    // if (user.user_id !== -1 && prevProps.user.id !== user.id) dispatch(getWorkspaceList(user.user_id, idWorkspace))
94 111
 
95
-    if (user.id !== -1 && prevProps.user.id !== user.id) dispatch(getWorkspaceList(user.user_id, newWorkspaceId))
112
+    if (match.params.idcts && workspace.id !== -1) { // if a content id is in url, open it
113
+      const idContentToOpen = parseInt(match.params.idcts)
114
+      const contentToOpen = workspace.find(wsc => wsc.id === idContentToOpen) // || await dispatch(getWorkspaceContent(idWorkspace, idContentToOpen))
96 115
 
97
-    if (match.params.idcts) { // if a content id is in url, open it
98
-      const contentToOpen = workspace.content.find(wsc => wsc.id === parseInt(match.params.idcts))
99
-      if (contentToOpen === undefined) return
116
+      // @FIXME : for alpha, we do not handle subfolder. commented code bellow should load a component that is not in the workspace root
117
+      // if (contentToOpen === undefined) { // content is not is ws root
118
+      //   const fetchContent = await dispatch(getWorkspaceContent(idWorkspace, idContentToOpen))
119
+      //   console.log(fetchContent)
120
+      // }
100 121
 
101 122
       renderApp(
102
-        app[contentToOpen.type],
123
+        contentType.find(ct => ct.type === contentToOpen.type),
103 124
         user,
104 125
         {...contentToOpen, workspace: workspace}
105 126
       )
106 127
     }
107 128
   }
108 129
 
109
-  handleClickContentItem = content => this.props.history.push(`${PAGE.WORKSPACE.CONTENT(content.workspace_id, content.id)}${this.props.location.search}`)
130
+  handleClickContentItem = content => {
131
+    console.log('content clicked', content)
132
+    this.props.history.push(`${PAGE.WORKSPACE.CONTENT(content.workspace_id, content.id)}${this.props.location.search}`)
133
+  }
110 134
 
111 135
   handleClickEditContentItem = (e, content) => {
112 136
     e.stopPropagation()
@@ -142,15 +166,24 @@ class WorkspaceContent extends React.Component {
142 166
   }
143 167
 
144 168
   render () {
145
-    const { workspace, app } = this.props
169
+    const { workspace, app, contentType } = this.props
170
+
171
+    const filterWorkspaceContent = (contentList, filter) => {
172
+      console.log(contentList, filter)
173
+      return filter.length === 0
174
+        ? contentList
175
+        : contentList.filter(c => c.type === 'folder' || filter.includes(c.type)) // keep unfiltered files and folders
176
+          // @FIXME we need to filter subfolder too, but right now, we dont handle subfolder
177
+          // .map(c => c.type !== 'folder' ? c : {...c, content: filterWorkspaceContent(c.content, filter)}) // recursively filter folder content
178
+    }
179
+    // .filter(c => c.type !== 'folder' || c.content.length > 0) // remove empty folder => 2018/05/21 - since we load only one lvl of content, don't remove empty
146 180
 
147
-    const filterWorkspaceContent = (contentList, filter) => filter.length === 0
148
-      ? contentList
149
-      : contentList.filter(c => c.type === 'folder' || filter.includes(c.type)) // keep unfiltered files and folders
150
-        .map(c => c.type !== 'folder' ? c : {...c, content: filterWorkspaceContent(c.content, filter)}) // recursively filter folder content
151
-    // .filter(c => c.type !== 'folder' || c.content.length > 0) // remove empty folder => 2018/05/21 - since we load only one lvl of content, don't remove empty folders
181
+    const urlFilter = qs.parse(this.props.location.search).type
152 182
 
153
-    const filteredWorkspaceContent = filterWorkspaceContent(workspace.content, workspace.filter)
183
+    const filteredWorkspaceContent = workspace.length > 0
184
+      ? filterWorkspaceContent(workspace, urlFilter ? [urlFilter] : [])
185
+      : []
186
+    console.log('workspaceContent => filteredWorkspaceContent', filteredWorkspaceContent)
154 187
 
155 188
     return (
156 189
       <div className='sidebarpagecontainer'>
@@ -160,7 +193,7 @@ class WorkspaceContent extends React.Component {
160 193
           <PageTitle
161 194
             parentClass='workspace__header'
162 195
             customClass='justify-content-between'
163
-            title={workspace.title}
196
+            title={workspace.label ? workspace.label : ''}
164 197
           >
165 198
             <DropdownCreateButton parentClass='workspace__header__btnaddworkspace' />
166 199
           </PageTitle>
@@ -192,10 +225,11 @@ class WorkspaceContent extends React.Component {
192 225
                 )
193 226
                 : (
194 227
                   <ContentItem
195
-                    name={c.title}
228
+                    label={c.label}
196 229
                     type={c.type}
197
-                    icon={(app[c.type] || {icon: ''}).icon}
198
-                    status={c.status}
230
+                    faIcon={contentType.length ? contentType.find(a => a.slug === c.type).faIcon : ''}
231
+                    statusSlug={c.statusSlug}
232
+                    contentType={contentType.find(ct => ct.slug === c.type)}
199 233
                     onClickItem={() => this.handleClickContentItem(c)}
200 234
                     onClickExtendedAction={{
201 235
                       edit: e => this.handleClickEditContentItem(e, c),
@@ -222,5 +256,5 @@ class WorkspaceContent extends React.Component {
222 256
   }
223 257
 }
224 258
 
225
-const mapStateToProps = ({ user, workspace, workspaceList, app }) => ({ user, workspace, workspaceList, app })
226
-export default connect(mapStateToProps)(appFactory(WorkspaceContent))
259
+const mapStateToProps = ({ user, workspace, workspaceList, app, contentType }) => ({ user, workspace, workspaceList, app, contentType })
260
+export default withRouter(connect(mapStateToProps)(appFactory(WorkspaceContent)))

+ 6 - 1
src/css/Generic.styl View File

@@ -1,9 +1,14 @@
1
+a
2
+  color fontColor
3
+  &:hover, &:link, &:visited
4
+    color fontColor
5
+    text-decoration none
6
+
1 7
 .tracim
2 8
   height 100%
3 9
   &__content
4 10
     height 100%
5 11
 
6
-
7 12
 .btn-primary
8 13
   background-color thirdColor
9 14
   border-color secondColor

+ 11 - 10
src/helper.js View File

@@ -1,7 +1,8 @@
1 1
 export const FETCH_CONFIG = {
2 2
   headers: {
3 3
     'Accept': 'application/json',
4
-    'Content-Type': 'application/json'
4
+    'Content-Type': 'application/json',
5
+    'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
5 6
   },
6 7
   apiUrl: 'http://localhost:6543/api/v2',
7 8
   mockApiUrl: 'http://localhost:3001'
@@ -10,15 +11,15 @@ export const FETCH_CONFIG = {
10 11
 export const PAGE = {
11 12
   HOME: '/',
12 13
   WORKSPACE: {
13
-    DASHBOARD: (idws = ':idws') => `/workspace/${idws}`,
14
-    NEW: '/workspace/new',
15
-    CALENDAR: (idws = ':idws') => `/workspace/${idws}/apps/calendar`,
16
-    CONTENT_LIST: (idws = ':idws') => `/workspace/${idws}/apps/contents`,
17
-    CONTENT: (idws = ':idws', idcts = ':idcts') => `/workspace/${idws}/apps/contents/${idcts}`,
18
-    CONTENT_NEW: (idws = ':idws', ctstype = ':ctstype') => `/workspace/${idws}/apps/contents/${ctstype}/new`,
19
-    CONTENT_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspace/${idws}/apps/contents/${idcts}/edit`,
20
-    CONTENT_TITLE_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspace/${idws}/apps/contents/${idcts}/title/edit`,
21
-    ADMIN: (idws = ':idws') => `/workspace/${idws}/admin`
14
+    DASHBOARD: (idws = ':idws') => `/workspaces/${idws}`,
15
+    NEW: '/workspaces/new',
16
+    CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
17
+    CONTENT_LIST: (idws = ':idws') => `/workspaces/${idws}/contents`,
18
+    CONTENT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}`,
19
+    CONTENT_NEW: (idws = ':idws', ctstype = ':ctstype') => `/workspaces/${idws}/contents/${ctstype}/new`,
20
+    CONTENT_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/edit`,
21
+    CONTENT_TITLE_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/title/edit`,
22
+    ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
22 23
   },
23 24
   LOGIN: '/login',
24 25
   ACCOUNT: '/account'

+ 9 - 4
src/reducer/app.js View File

@@ -1,11 +1,16 @@
1 1
 import { APP_LIST } from '../action-creator.sync.js'
2 2
 
3
-export default function app (state = {}, action) {
3
+export default function app (state = [], action) {
4 4
   switch (action.type) {
5 5
     case `Set/${APP_LIST}`:
6
-      const rez = {}
7
-      action.appList.forEach(app => (rez[app.name] = app))
8
-      return rez
6
+      return action.appList.map(a => ({
7
+        label: a.label,
8
+        slug: a.slug,
9
+        isActive: a.is_active,
10
+        faIcon: a.fa_icon,
11
+        hexcolor: a.hexcolor,
12
+        config: a.config
13
+      }))
9 14
 
10 15
     default:
11 16
       return state

+ 26 - 0
src/reducer/contentType.js View File

@@ -0,0 +1,26 @@
1
+import { CONTENT_TYPE_LIST } from '../action-creator.sync.js'
2
+
3
+export function contentType (state = [], action) {
4
+  switch (action.type) {
5
+    case `Set/${CONTENT_TYPE_LIST}`:
6
+      return action.contentTypeList.map(ct => ({
7
+        label: ct.label,
8
+        slug: ct.slug,
9
+        faIcon: ct.fa_icon,
10
+        hexcolor: ct.hexcolor,
11
+        creationLabel: ct.creation_label,
12
+        availableStatuses: ct.available_statuses.map(as => ({
13
+          label: as.label,
14
+          slug: as.slug,
15
+          fa_icon: as.fa_icon,
16
+          hexcolor: as.hexcolor,
17
+          global_status: as.global_status
18
+        }))
19
+      }))
20
+
21
+    default:
22
+      return state
23
+  }
24
+}
25
+
26
+export default contentType

+ 2 - 1
src/reducer/root.js View File

@@ -5,8 +5,9 @@ import user from './user.js'
5 5
 import workspace from './workspace.js'
6 6
 import workspaceList from './workspaceList.js'
7 7
 import app from './app.js'
8
+import contentType from './contentType.js'
8 9
 import timezone from './timezone.js'
9 10
 
10
-const rootReducer = combineReducers({ lang, flashMessage, user, workspace, workspaceList, app, timezone })
11
+const rootReducer = combineReducers({ lang, flashMessage, user, workspace, workspaceList, app, contentType, timezone })
11 12
 
12 13
 export default rootReducer

+ 15 - 19
src/reducer/workspace.js View File

@@ -3,26 +3,22 @@ import {
3 3
   FOLDER
4 4
 } from '../action-creator.sync.js'
5 5
 
6
-const serializeWorkspace = data => ({
7
-  id: data.id,
8
-  title: data.title,
9
-  content: data.content,
10
-  ownerId: data.owner_id
11
-})
12
-
13
-export default function user (state = {
14
-  id: -1,
15
-  title: '',
16
-  ownerId: '',
17
-  content: [],
18
-  filter: []
19
-}, action) {
6
+export default function workspace (state = [], action) {
20 7
   switch (action.type) {
21
-    case `Set/${WORKSPACE}`:
22
-      return {
23
-        ...serializeWorkspace(action.workspace),
24
-        filter: action.filterStr ? action.filterStr.split(';') : []
25
-      }
8
+    case `Set/${WORKSPACE}/Content`:
9
+      return action.workspaceContent.map(wsc => ({
10
+        id: wsc.id,
11
+        label: wsc.label,
12
+        slug: wsc.slug,
13
+        type: wsc.content_type_slug,
14
+        workspaceId: wsc.workspace_id,
15
+        isArchived: wsc.is_archived,
16
+        parentId: wsc.parent_id,
17
+        isDeleted: wsc.is_deleted,
18
+        // show_in_ui: wsc.show_in_ui, ???
19
+        statusSlug: wsc.status_slug,
20
+        subContentTypeSlug: wsc.sub_content_type_slug,
21
+      }))
26 22
 
27 23
     case `Update/${WORKSPACE}/Filter`:
28 24
       return {...state, filter: action.filterList}

+ 13 - 10
src/reducer/workspaceList.js View File

@@ -3,18 +3,21 @@ import {
3 3
   USER_ROLE
4 4
 } from '../action-creator.sync.js'
5 5
 
6
-const serializeWorkspaceItem = data => ({
7
-  id: data.id,
8
-  title: data.title,
9
-  role: data.role,
10
-  notif: data.notif
11
-})
12
-
13 6
 export function workspaceList (state = [], action) {
14 7
   switch (action.type) {
15 8
     case `Update/${WORKSPACE_LIST}`:
16 9
       return action.workspaceList.map(ws => ({
17
-        ...serializeWorkspaceItem(ws),
10
+        id: ws.id,
11
+        label: ws.label,
12
+        slug: ws.slug,
13
+        description: ws.description,
14
+        sidebarEntry: ws.sidebar_entries.map(sbe => ({
15
+          slug: sbe.slug,
16
+          route: sbe.route,
17
+          faIcon: sbe.fa_icon,
18
+          hexcolor: sbe.hexcolor,
19
+          label: sbe.label
20
+        })),
18 21
         isOpenInSidebar: false
19 22
       }))
20 23
 
@@ -24,7 +27,7 @@ export function workspaceList (state = [], action) {
24 27
         : ws
25 28
       )
26 29
 
27
-    case `Set/${USER_ROLE}`:
30
+    case `Set/${USER_ROLE}`: // not used yet
28 31
       return state.map(ws => {
29 32
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
30 33
         return {
@@ -34,7 +37,7 @@ export function workspaceList (state = [], action) {
34 37
         }
35 38
       })
36 39
 
37
-    case `Update/${USER_ROLE}/SubscriptionNotif`:
40
+    case `Update/${USER_ROLE}/SubscriptionNotif`: // not used yet
38 41
       return state.map(ws => ws.id === action.workspaceId
39 42
         ? {...ws, notif: action.subscriptionNotif}
40 43
         : ws