Procházet zdrojové kódy

integration of real api (still wip)

Skylsmoi před 6 roky
rodič
revize
7e0a3fbafb

+ 38 - 16
jsonserver/server.js Zobrazit soubor

55
 server.get('/user/:id/workspace', (req, res) => res.jsonp(jsonDb.workspace_list))
55
 server.get('/user/:id/workspace', (req, res) => res.jsonp(jsonDb.workspace_list))
56
 
56
 
57
 server.get('/workspace/:id', (req, res) => res.jsonp(
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
 
87
 
79
 server.get('/timezone', (req, res) => res.jsonp(timezoneDb.timezone))
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
   switch (req.params.idc) {
91
   switch (req.params.idc) {
83
     case '1': // pageHtml
92
     case '1': // pageHtml
93
+    case '5':
84
       return res.jsonp(jsonDb.content_data_pageHtml)
94
       return res.jsonp(jsonDb.content_data_pageHtml)
85
     case '2':
95
     case '2':
86
       return res.jsonp(jsonDb.content_data_thread)
96
       return res.jsonp(jsonDb.content_data_thread)
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
   switch (req.params.idc) {
103
   switch (req.params.idc) {
94
     case '1': // pageHtml
104
     case '1': // pageHtml
105
+    case '5':
95
       return res.jsonp(jsonDb.timeline)
106
       return res.jsonp(jsonDb.timeline)
96
     case '2':
107
     case '2':
97
       return res.jsonp([])
108
       return res.jsonp([])
104
 server.listen(GLOBAL_PORT, () => {
115
 server.listen(GLOBAL_PORT, () => {
105
   console.log('JSON Server is running on port : ' + GLOBAL_PORT)
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 Zobrazit soubor

105
       {
105
       {
106
         "id": 1,
106
         "id": 1,
107
         "parent_id": null,
107
         "parent_id": null,
108
+        "is_archived": false,
109
+        "is_deleted": false,
108
         "workspace_id": 1,
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
         "id": 2,
133
         "id": 2,

+ 1 - 0
package.json Zobrazit soubor

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

+ 69 - 42
src/action-creator.async.js Zobrazit soubor

6
   updateLangList,
6
   updateLangList,
7
   USER_LOGIN,
7
   USER_LOGIN,
8
   USER_LOGOUT,
8
   USER_LOGOUT,
9
-  USER_DATA,
10
   USER_ROLE,
9
   USER_ROLE,
11
   USER_CONNECTED,
10
   USER_CONNECTED,
12
-  updateUserData,
13
   setUserRole,
11
   setUserRole,
14
   WORKSPACE,
12
   WORKSPACE,
15
   WORKSPACE_LIST,
13
   WORKSPACE_LIST,
16
   FOLDER,
14
   FOLDER,
17
   setFolderData,
15
   setFolderData,
18
-  APP_LIST
16
+  APP_LIST,
17
+  CONTENT_TYPE_LIST
19
 } from './action-creator.sync.js'
18
 } from './action-creator.sync.js'
20
 
19
 
21
 /*
20
 /*
66
 
65
 
67
 export const getLangList = () => async dispatch => {
66
 export const getLangList = () => async dispatch => {
68
   const fetchGetLangList = await fetchWrapper({
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
     actionName: LANG,
73
     actionName: LANG,
72
     dispatch
74
     dispatch
73
   })
75
   })
76
 
78
 
77
 export const getTimezone = () => async dispatch => {
79
 export const getTimezone = () => async dispatch => {
78
   const fetchGetTimezone = await fetchWrapper({
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
     actionName: TIMEZONE,
86
     actionName: TIMEZONE,
82
     dispatch
87
     dispatch
83
   })
88
   })
86
 
91
 
87
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
92
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
88
   return fetchWrapper({
93
   return fetchWrapper({
89
-    url: `${FETCH_CONFIG.mockApiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
94
+    url: `${FETCH_CONFIG.apiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
90
     param: {
95
     param: {
91
       headers: {...FETCH_CONFIG.headers},
96
       headers: {...FETCH_CONFIG.headers},
92
       method: 'POST',
97
       method: 'POST',
103
 
108
 
104
 export const postUserLogout = () => async dispatch => {
109
 export const postUserLogout = () => async dispatch => {
105
   return fetchWrapper({
110
   return fetchWrapper({
106
-    url: `${FETCH_CONFIG.mockApiUrl}/sessions/logout`, // FETCH_CONFIG.apiUrl
111
+    url: `${FETCH_CONFIG.apiUrl}/sessions/logout`, // FETCH_CONFIG.apiUrl
107
     param: {
112
     param: {
108
       headers: {...FETCH_CONFIG.headers},
113
       headers: {...FETCH_CONFIG.headers},
109
       method: 'POST'
114
       method: 'POST'
115
 
120
 
116
 export const getUserIsConnected = () => async dispatch => {
121
 export const getUserIsConnected = () => async dispatch => {
117
   return fetchWrapper({
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
     actionName: USER_CONNECTED,
128
     actionName: USER_CONNECTED,
121
     dispatch
129
     dispatch
122
   })
130
   })
124
 
132
 
125
 export const getUserRole = user => async dispatch => {
133
 export const getUserRole = user => async dispatch => {
126
   const fetchGetUserRole = await fetchWrapper({
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
     actionName: USER_ROLE,
140
     actionName: USER_ROLE,
130
     dispatch
141
     dispatch
131
   })
142
   })
132
   if (fetchGetUserRole.status === 200) dispatch(setUserRole(fetchGetUserRole.json))
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
     dispatch
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
   return fetchWrapper({
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
     dispatch
166
     dispatch
161
   })
167
   })
162
 }
168
 }
163
 
169
 
164
-export const getWorkspaceContent = workspaceId => dispatch => {
170
+export const getWorkspaceContent = (idWorkspace, idContent) => dispatch => {
165
   return fetchWrapper({
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
     actionName: WORKSPACE,
177
     actionName: WORKSPACE,
169
     dispatch
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
   const fetchGetFolderContent = await fetchWrapper({
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
     actionName: `${WORKSPACE}/${FOLDER}`,
189
     actionName: `${WORKSPACE}/${FOLDER}`,
178
     dispatch
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
 export const getAppList = () => dispatch => {
195
 export const getAppList = () => dispatch => {
184
   return fetchWrapper({
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
     actionName: APP_LIST,
202
     actionName: APP_LIST,
188
     dispatch
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 Zobrazit soubor

23
   ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
23
   ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
24
 
24
 
25
 export const WORKSPACE = 'Workspace'
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
 export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
27
 export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
28
 
28
 
29
 export const FOLDER = 'Folder'
29
 export const FOLDER = 'Folder'
36
 export const APP_LIST = 'App/List'
36
 export const APP_LIST = 'App/List'
37
 export const setAppList = appList => ({ type: `Set/${APP_LIST}`, appList })
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
 export const LANG = 'Lang'
42
 export const LANG = 'Lang'
40
 export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
43
 export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
41
 export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })
44
 export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })

+ 1 - 1
src/component/Header/MenuActionListItem/Notification.jsx Zobrazit soubor

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

+ 21 - 88
src/component/Sidebar/WorkspaceListItem.jsx Zobrazit soubor

3
 import { translate } from 'react-i18next'
3
 import { translate } from 'react-i18next'
4
 import PropTypes from 'prop-types'
4
 import PropTypes from 'prop-types'
5
 import AnimateHeight from 'react-animate-height'
5
 import AnimateHeight from 'react-animate-height'
6
+import { Link } from 'react-router-dom'
6
 
7
 
7
 const WorkspaceListItem = props => {
8
 const WorkspaceListItem = props => {
8
   return (
9
   return (
9
     <li className='sidebar__navigation__workspace__item'>
10
     <li className='sidebar__navigation__workspace__item'>
10
       <div className='sidebar__navigation__workspace__item__wrapper' onClick={props.onClickTitle}>
11
       <div className='sidebar__navigation__workspace__item__wrapper' onClick={props.onClickTitle}>
11
         <div className='sidebar__navigation__workspace__item__number'>
12
         <div className='sidebar__navigation__workspace__item__number'>
12
-          {props.name.substring(0, 2).toUpperCase()}
13
+          {props.label.substring(0, 2).toUpperCase()}
13
         </div>
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
         </div>
18
         </div>
18
 
19
 
19
         <div className='sidebar__navigation__workspace__item__icon'>
20
         <div className='sidebar__navigation__workspace__item__icon'>
26
           className='sidebar__navigation__workspace__item__submenu'
27
           className='sidebar__navigation__workspace__item__submenu'
27
           id={`sidebarSubMenu_${props.number}`}
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
             <li
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
                   </div>
48
                   </div>
100
                 </div>
49
                 </div>
101
-              </div>
50
+              </Link>
102
             </li>
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
         </ul>
53
         </ul>
120
       </AnimateHeight>
54
       </AnimateHeight>
121
     </li>
55
     </li>
125
 export default translate()(WorkspaceListItem)
59
 export default translate()(WorkspaceListItem)
126
 
60
 
127
 WorkspaceListItem.propTypes = {
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
   onClickTitle: PropTypes.func,
64
   onClickTitle: PropTypes.func,
132
   onClickAllContent: PropTypes.func,
65
   onClickAllContent: PropTypes.func,
133
   isOpenInSidebar: PropTypes.bool
66
   isOpenInSidebar: PropTypes.bool

+ 16 - 52
src/component/Workspace/ContentItem.jsx Zobrazit soubor

3
 import classnames from 'classnames'
3
 import classnames from 'classnames'
4
 import BtnExtandedAction from './BtnExtandedAction.jsx'
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
   return (
8
   return (
47
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
9
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
48
       <div className='content__type'>
10
       <div className='content__type'>
49
-        <i className={props.icon} />
11
+        <i className={`fa fa-${props.faIcon}`} />
50
       </div>
12
       </div>
51
 
13
 
52
       <div className='content__name'>
14
       <div className='content__name'>
53
         <div className='content__name__text'>
15
         <div className='content__name__text'>
54
-          { props.name }
16
+          { props.label }
55
         </div>
17
         </div>
56
       </div>
18
       </div>
57
 
19
 
59
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
21
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
60
       </div>
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
         <div className='content__status__icon d-block '>
25
         <div className='content__status__icon d-block '>
64
-          <i className={iconStatus} />
26
+          <i className={`fa fa-${status.fa_icon}`} />
65
         </div>
27
         </div>
66
         <div className='content__status__text d-none d-xl-block'>
28
         <div className='content__status__text d-none d-xl-block'>
67
-          {textStatus}
29
+          {status.label}
68
         </div>
30
         </div>
69
       </div>
31
       </div>
70
     </div>
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
   type: PropTypes.string.isRequired,
39
   type: PropTypes.string.isRequired,
78
-  status: PropTypes.string.isRequired,
40
+  statusSlug: PropTypes.string.isRequired,
79
   customClass: PropTypes.string,
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
   customClass: '',
50
   customClass: '',
87
   onClickItem: () => {}
51
   onClickItem: () => {}
88
 }
52
 }

+ 49 - 37
src/component/Workspace/Folder.jsx Zobrazit soubor

20
   }
20
   }
21
 
21
 
22
   handleClickCreateContent = (e, folder, type) => {
22
   handleClickCreateContent = (e, folder, type) => {
23
-    e.preventDefault()
24
     e.stopPropagation() // because we have a link inside a link (togler and newFile)
23
     e.stopPropagation() // because we have a link inside a link (togler and newFile)
25
     this.props.onClickCreateContent(folder, type)
24
     this.props.onClickCreateContent(folder, type)
26
   }
25
   }
49
           </div>
48
           </div>
50
 
49
 
51
           <div className='folder__header__name'>
50
           <div className='folder__header__name'>
52
-            { folderData.title }
51
+            { folderData.label }
53
           </div>
52
           </div>
54
 
53
 
55
           <div className='folder__header__button'>
54
           <div className='folder__header__button'>
56
 
55
 
57
             <div className='folder__header__button__addbtn'>
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
                 {t('Folder.create')} ...
66
                 {t('Folder.create')} ...
60
               </button>
67
               </button>
61
 
68
 
72
                     </div>
79
                     </div>
73
                   </div>
80
                   </div>
74
                 </div>
81
                 </div>
82
+
75
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageHtml')}>
83
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageHtml')}>
76
                   <div className='subdropdown__link__apphtml d-flex align-items-center'>
84
                   <div className='subdropdown__link__apphtml d-flex align-items-center'>
77
                     <div className='subdropdown__link__apphtml__icon mr-3'>
85
                     <div className='subdropdown__link__apphtml__icon mr-3'>
82
                     </div>
90
                     </div>
83
                   </div>
91
                   </div>
84
                 </div>
92
                 </div>
93
+
85
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'File')}>
94
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'File')}>
86
                   <div className='subdropdown__link__appfile d-flex align-items-center'>
95
                   <div className='subdropdown__link__appfile d-flex align-items-center'>
87
                     <div className='subdropdown__link__appfile__icon mr-3'>
96
                     <div className='subdropdown__link__appfile__icon mr-3'>
92
                     </div>
101
                     </div>
93
                   </div>
102
                   </div>
94
                 </div>
103
                 </div>
104
+
95
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageMarkdown')}>
105
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'PageMarkdown')}>
96
                   <div className='subdropdown__link__appmarkdown d-flex align-items-center'>
106
                   <div className='subdropdown__link__appmarkdown d-flex align-items-center'>
97
                     <div className='subdropdown__link__appmarkdown__icon mr-3'>
107
                     <div className='subdropdown__link__appmarkdown__icon mr-3'>
102
                     </div>
112
                     </div>
103
                   </div>
113
                   </div>
104
                 </div>
114
                 </div>
115
+
105
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Thread')}>
116
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Thread')}>
106
                   <div className='subdropdown__link__appthread d-flex align-items-center'>
117
                   <div className='subdropdown__link__appthread d-flex align-items-center'>
107
                     <div className='subdropdown__link__appthread__icon mr-3'>
118
                     <div className='subdropdown__link__appthread__icon mr-3'>
112
                     </div>
123
                     </div>
113
                   </div>
124
                   </div>
114
                 </div>
125
                 </div>
126
+
115
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Task')}>
127
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Task')}>
116
                   <div className='subdropdown__link__apptask d-flex align-items-center'>
128
                   <div className='subdropdown__link__apptask d-flex align-items-center'>
117
                     <div className='subdropdown__link__apptask__icon mr-3'>
129
                     <div className='subdropdown__link__apptask__icon mr-3'>
122
                     </div>
134
                     </div>
123
                   </div>
135
                   </div>
124
                 </div>
136
                 </div>
137
+
125
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Issue')}>
138
                 <div className='subdropdown__link dropdown-item' onClick={e => this.handleClickCreateContent(e, folderData, 'Issue')}>
126
                   <div className='subdropdown__link__appissue d-flex align-items-center'>
139
                   <div className='subdropdown__link__appissue d-flex align-items-center'>
127
                     <div className='subdropdown__link__appissue__icon mr-3'>
140
                     <div className='subdropdown__link__appissue__icon mr-3'>
152
         </div>
165
         </div>
153
 
166
 
154
         <div className='folder__content'>
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
         </div>
199
         </div>
185
       </div>
200
       </div>
186
     )
201
     )
190
 export default translate()(Folder)
205
 export default translate()(Folder)
191
 
206
 
192
 Folder.propTypes = {
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
   onClickItem: PropTypes.func.isRequired,
210
   onClickItem: PropTypes.func.isRequired,
199
   onClickFolder: PropTypes.func.isRequired,
211
   onClickFolder: PropTypes.func.isRequired,
200
   isLast: PropTypes.bool.isRequired
212
   isLast: PropTypes.bool.isRequired

+ 7 - 8
src/container/Sidebar.jsx Zobrazit soubor

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

+ 62 - 28
src/container/WorkspaceContent.jsx Zobrazit soubor

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3
 import appFactory from '../appFactory.js'
4
 import appFactory from '../appFactory.js'
4
 import { PAGE } from '../helper.js'
5
 import { PAGE } from '../helper.js'
5
 import Sidebar from './Sidebar.jsx'
6
 import Sidebar from './Sidebar.jsx'
12
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
13
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
13
 import {
14
 import {
14
   getAppList,
15
   getAppList,
16
+  getContentTypeList,
17
+  getWorkspaceContentList,
15
   getWorkspaceContent,
18
   getWorkspaceContent,
16
   getFolderContent,
19
   getFolderContent,
17
   getWorkspaceList
20
   getWorkspaceList
18
 } from '../action-creator.async.js'
21
 } from '../action-creator.async.js'
19
 import {
22
 import {
20
-  newFlashMessage, setAppList,
21
-  setWorkspaceData,
23
+  newFlashMessage,
24
+  setAppList,
25
+  setContentTypeList,
26
+  setWorkspaceContent,
22
   setWorkspaceListIsOpenInSidebar,
27
   setWorkspaceListIsOpenInSidebar,
23
   updateWorkspaceListData
28
   updateWorkspaceListData
24
 } from '../action-creator.sync.js'
29
 } from '../action-creator.sync.js'
53
 
58
 
54
   async componentDidMount () {
59
   async componentDidMount () {
55
     const { workspaceIdInUrl } = this.state
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
       const fetchGetAppList = await dispatch(getAppList())
66
       const fetchGetAppList = await dispatch(getAppList())
60
       if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
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
     let wsToLoad = null
76
     let wsToLoad = null
64
     if (match.params.idws !== undefined) wsToLoad = match.params.idws
77
     if (match.params.idws !== undefined) wsToLoad = match.params.idws
65
 
78
 
78
 
91
 
79
     if (wsToLoad === null) return // ws already loaded
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
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
97
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
85
   }
98
   }
86
 
99
 
87
   componentDidUpdate (prevProps, prevState) {
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
     if (this.state.workspaceIdInUrl === null) return
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
       renderApp(
122
       renderApp(
102
-        app[contentToOpen.type],
123
+        contentType.find(ct => ct.type === contentToOpen.type),
103
         user,
124
         user,
104
         {...contentToOpen, workspace: workspace}
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
   handleClickEditContentItem = (e, content) => {
135
   handleClickEditContentItem = (e, content) => {
112
     e.stopPropagation()
136
     e.stopPropagation()
142
   }
166
   }
143
 
167
 
144
   render () {
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
     return (
188
     return (
156
       <div className='sidebarpagecontainer'>
189
       <div className='sidebarpagecontainer'>
160
           <PageTitle
193
           <PageTitle
161
             parentClass='workspace__header'
194
             parentClass='workspace__header'
162
             customClass='justify-content-between'
195
             customClass='justify-content-between'
163
-            title={workspace.title}
196
+            title={workspace.label ? workspace.label : ''}
164
           >
197
           >
165
             <DropdownCreateButton parentClass='workspace__header__btnaddworkspace' />
198
             <DropdownCreateButton parentClass='workspace__header__btnaddworkspace' />
166
           </PageTitle>
199
           </PageTitle>
192
                 )
225
                 )
193
                 : (
226
                 : (
194
                   <ContentItem
227
                   <ContentItem
195
-                    name={c.title}
228
+                    label={c.label}
196
                     type={c.type}
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
                     onClickItem={() => this.handleClickContentItem(c)}
233
                     onClickItem={() => this.handleClickContentItem(c)}
200
                     onClickExtendedAction={{
234
                     onClickExtendedAction={{
201
                       edit: e => this.handleClickEditContentItem(e, c),
235
                       edit: e => this.handleClickEditContentItem(e, c),
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 Zobrazit soubor

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

+ 11 - 10
src/helper.js Zobrazit soubor

1
 export const FETCH_CONFIG = {
1
 export const FETCH_CONFIG = {
2
   headers: {
2
   headers: {
3
     'Accept': 'application/json',
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
   apiUrl: 'http://localhost:6543/api/v2',
7
   apiUrl: 'http://localhost:6543/api/v2',
7
   mockApiUrl: 'http://localhost:3001'
8
   mockApiUrl: 'http://localhost:3001'
10
 export const PAGE = {
11
 export const PAGE = {
11
   HOME: '/',
12
   HOME: '/',
12
   WORKSPACE: {
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
   LOGIN: '/login',
24
   LOGIN: '/login',
24
   ACCOUNT: '/account'
25
   ACCOUNT: '/account'

+ 9 - 4
src/reducer/app.js Zobrazit soubor

1
 import { APP_LIST } from '../action-creator.sync.js'
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
   switch (action.type) {
4
   switch (action.type) {
5
     case `Set/${APP_LIST}`:
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
     default:
15
     default:
11
       return state
16
       return state

+ 26 - 0
src/reducer/contentType.js Zobrazit soubor

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 Zobrazit soubor

5
 import workspace from './workspace.js'
5
 import workspace from './workspace.js'
6
 import workspaceList from './workspaceList.js'
6
 import workspaceList from './workspaceList.js'
7
 import app from './app.js'
7
 import app from './app.js'
8
+import contentType from './contentType.js'
8
 import timezone from './timezone.js'
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
 export default rootReducer
13
 export default rootReducer

+ 15 - 19
src/reducer/workspace.js Zobrazit soubor

3
   FOLDER
3
   FOLDER
4
 } from '../action-creator.sync.js'
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
   switch (action.type) {
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
     case `Update/${WORKSPACE}/Filter`:
23
     case `Update/${WORKSPACE}/Filter`:
28
       return {...state, filter: action.filterList}
24
       return {...state, filter: action.filterList}

+ 13 - 10
src/reducer/workspaceList.js Zobrazit soubor

3
   USER_ROLE
3
   USER_ROLE
4
 } from '../action-creator.sync.js'
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
 export function workspaceList (state = [], action) {
6
 export function workspaceList (state = [], action) {
14
   switch (action.type) {
7
   switch (action.type) {
15
     case `Update/${WORKSPACE_LIST}`:
8
     case `Update/${WORKSPACE_LIST}`:
16
       return action.workspaceList.map(ws => ({
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
         isOpenInSidebar: false
21
         isOpenInSidebar: false
19
       }))
22
       }))
20
 
23
 
24
         : ws
27
         : ws
25
       )
28
       )
26
 
29
 
27
-    case `Set/${USER_ROLE}`:
30
+    case `Set/${USER_ROLE}`: // not used yet
28
       return state.map(ws => {
31
       return state.map(ws => {
29
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
32
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
30
         return {
33
         return {
34
         }
37
         }
35
       })
38
       })
36
 
39
 
37
-    case `Update/${USER_ROLE}/SubscriptionNotif`:
40
+    case `Update/${USER_ROLE}/SubscriptionNotif`: // not used yet
38
       return state.map(ws => ws.id === action.workspaceId
41
       return state.map(ws => ws.id === action.workspaceId
39
         ? {...ws, notif: action.subscriptionNotif}
42
         ? {...ws, notif: action.subscriptionNotif}
40
         : ws
43
         : ws