Interface de gestion des groupes de travail
- Récupération et affichage des groupes existants - Création d'un nouveau groupe - Modification d'un groupe existant - Rejoindre/quitter un groupe de travail
This commit is contained in:
parent
676ddf3bc8
commit
8708e30020
6
client/package-lock.json
generated
6
client/package-lock.json
generated
@ -3235,9 +3235,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bulma": {
|
"bulma": {
|
||||||
"version": "0.7.5",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.0.tgz",
|
||||||
"integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw=="
|
"integrity": "sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ=="
|
||||||
},
|
},
|
||||||
"bulma-switch": {
|
"bulma-switch": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
"apollo-link-http": "^1.5.17",
|
"apollo-link-http": "^1.5.17",
|
||||||
"apollo-link-ws": "^1.0.20",
|
"apollo-link-ws": "^1.0.20",
|
||||||
"apollo-utilities": "^1.3.4",
|
"apollo-utilities": "^1.3.4",
|
||||||
"bulma": "^0.7.2",
|
"bulma": "^0.9.0",
|
||||||
"bulma-switch": "^2.0.0",
|
"bulma-switch": "^2.0.0",
|
||||||
"graphql": "^15.3.0",
|
"graphql": "^15.3.0",
|
||||||
"graphql-request": "^2.0.0",
|
"graphql-request": "^2.0.0",
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { fetchWorkgroups } from '../../store/actions/workgroups';
|
||||||
|
import { RootState } from '../../store/reducers/root';
|
||||||
|
import { WorkgroupsPanel } from './WorkgroupsPanel';
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<div className="box">
|
<WorkgroupsPanel />
|
||||||
<div className="level">
|
|
||||||
<div className="level-left">
|
|
||||||
<h3 className="is-size-3 subtitle level-item">Groupes de travail</h3>
|
|
||||||
</div>
|
|
||||||
<div className="level-right">
|
|
||||||
<button className="button is-primary level-item"><i className="fa fa-plus"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<div className="level">
|
<div className="level">
|
||||||
<div className="level-left">
|
<div className="level-left">
|
||||||
<h3 className="is-size-3 subtitle level-item">D.à.Ds</h3>
|
<h3 className="is-size-3 subtitle level-item">D.A.Ds</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="level-right">
|
<div className="level-right">
|
||||||
<button disabled className="button is-primary level-item"><i className="fa fa-plus"></i></button>
|
<button disabled className="button is-primary level-item"><i className="fa fa-plus"></i></button>
|
||||||
|
96
client/src/components/HomePage/WorkgroupsPanel.tsx
Normal file
96
client/src/components/HomePage/WorkgroupsPanel.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { RootState } from '../../store/reducers/root';
|
||||||
|
import { fetchWorkgroups } from '../../store/actions/workgroups';
|
||||||
|
import { Workgroup } from '../../types/workgroup';
|
||||||
|
import { User } from '../../types/user';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function WorkgroupsPanel() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const workgroups = useSelector<RootState>(state => state.workgroups.workgroupsById);
|
||||||
|
const currentUserId = useSelector<RootState>(state => state.auth.currentUser.id);
|
||||||
|
|
||||||
|
const filterTabs = [
|
||||||
|
{
|
||||||
|
label: "Mes groupes",
|
||||||
|
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => {
|
||||||
|
return wg.members.some((u: User) => u.id === currentUserId);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Ouverts",
|
||||||
|
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => !wg.closedAt)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Clôs",
|
||||||
|
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => !!wg.closedAt)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const [ state, setState ] = useState({ selectedTab: 0 });
|
||||||
|
|
||||||
|
const selectTab = (tabIndex: number) => {
|
||||||
|
setState(state => ({ ...state, selectedTab: tabIndex }));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchWorkgroups());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const workgroupsItems = filterTabs[state.selectedTab].filter(workgroups).map((wg: Workgroup) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/workgroups/${wg.id}`} key={`wg-${wg.id}`} className="panel-block">
|
||||||
|
<span className="panel-icon">
|
||||||
|
<i className="fas fa-users" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
{wg.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="panel is-info">
|
||||||
|
<div className="level panel-heading">
|
||||||
|
<div className="level-left">
|
||||||
|
<p className="level-item">
|
||||||
|
Groupes de travail
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="level-right">
|
||||||
|
<button className="button level-item is-outlined is-info is-inverted">
|
||||||
|
<i className="icon fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="panel-block">
|
||||||
|
<p className="control has-icons-left">
|
||||||
|
<input className="input" type="text" placeholder="Filtrer..." />
|
||||||
|
<span className="icon is-left">
|
||||||
|
<i className="fas fa-search" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div> */}
|
||||||
|
<p className="panel-tabs">
|
||||||
|
{
|
||||||
|
filterTabs.map((tab, i) => {
|
||||||
|
return (
|
||||||
|
<a key={`workgroup-tab-${i}`}
|
||||||
|
onClick={selectTab.bind(null, i)}
|
||||||
|
className={i === state.selectedTab ? 'is-active' : ''}>
|
||||||
|
{tab.label}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
workgroupsItems.length > 0 ?
|
||||||
|
workgroupsItems :
|
||||||
|
<a className="panel-block has-text-centered is-block">
|
||||||
|
<em>Aucun groupe dans cet catégorie pour l'instant.</em>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
19
client/src/store/actions/workgroups.ts
Normal file
19
client/src/store/actions/workgroups.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { Workgroup } from "../../types/workgroup";
|
||||||
|
|
||||||
|
export const FETCH_WORKGROUPS_REQUEST = 'FETCH_WORKGROUPS_REQUEST';
|
||||||
|
export const FETCH_WORKGROUPS_SUCCESS = 'FETCH_WORKGROUPS_SUCCESS';
|
||||||
|
export const FETCH_WORKGROUPS_FAILURE = 'FETCH_WORKGROUPS_FAILURE';
|
||||||
|
|
||||||
|
export interface fetchWorkgroupsRequestAction extends Action {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface fetchWorkgroupsSuccessAction extends Action {
|
||||||
|
workgroups: [Workgroup]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function fetchWorkgroups(): fetchWorkgroupsRequestAction {
|
||||||
|
return { type: FETCH_WORKGROUPS_REQUEST }
|
||||||
|
}
|
@ -31,6 +31,7 @@ function handleSetCurrentUser(state: AuthState, { email }: setCurrentUserAction)
|
|||||||
...state,
|
...state,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
currentUser: {
|
currentUser: {
|
||||||
|
id: '',
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { flagsReducer, FlagsState } from './flags';
|
import { flagsReducer, FlagsState } from './flags';
|
||||||
import { authReducer, AuthState } from './auth';
|
import { authReducer, AuthState } from './auth';
|
||||||
|
import { workgroupsReducer, WorkgroupsState } from './workgroups';
|
||||||
|
|
||||||
export interface RootState {
|
export interface RootState {
|
||||||
auth: AuthState,
|
auth: AuthState,
|
||||||
flags: FlagsState,
|
flags: FlagsState,
|
||||||
|
workgroups: WorkgroupsState,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
flags: flagsReducer,
|
flags: flagsReducer,
|
||||||
auth: authReducer,
|
auth: authReducer,
|
||||||
|
workgroups: workgroupsReducer,
|
||||||
});
|
});
|
38
client/src/store/reducers/workgroups.ts
Normal file
38
client/src/store/reducers/workgroups.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { User } from "../../types/user";
|
||||||
|
import { SET_CURRENT_USER, setCurrentUserAction } from "../actions/auth";
|
||||||
|
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction, updateProfileSuccessAction, UPDATE_PROFILE_SUCCESS, updateProfileRequestAction } from "../actions/profile";
|
||||||
|
import { Workgroup } from "../../types/workgroup";
|
||||||
|
import { FETCH_WORKGROUPS_SUCCESS, fetchWorkgroupsSuccessAction } from "../actions/workgroups";
|
||||||
|
|
||||||
|
export interface WorkgroupsState {
|
||||||
|
workgroupsById: { [id in string]: Workgroup }
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
workgroupsById: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function workgroupsReducer(state = defaultState, action: Action): WorkgroupsState {
|
||||||
|
switch (action.type) {
|
||||||
|
case FETCH_WORKGROUPS_SUCCESS:
|
||||||
|
return handleFetchWorkgroups(state, action as fetchWorkgroupsSuccessAction);
|
||||||
|
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFetchWorkgroups(state: WorkgroupsState, { workgroups }: fetchWorkgroupsSuccessAction): WorkgroupsState {
|
||||||
|
const workgroupsById = {
|
||||||
|
...state.workgroupsById,
|
||||||
|
};
|
||||||
|
|
||||||
|
workgroups.forEach(wg => {
|
||||||
|
workgroupsById[wg.id] = wg;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workgroupsById,
|
||||||
|
};
|
||||||
|
};
|
@ -2,11 +2,13 @@ import { all } from 'redux-saga/effects';
|
|||||||
import { failureRootSaga } from './failure';
|
import { failureRootSaga } from './failure';
|
||||||
import { initRootSaga } from './init';
|
import { initRootSaga } from './init';
|
||||||
import { profileRootSaga } from './profile';
|
import { profileRootSaga } from './profile';
|
||||||
|
import { workgroupsRootSaga } from './workgroups';
|
||||||
|
|
||||||
export function* rootSaga() {
|
export function* rootSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
initRootSaga(),
|
initRootSaga(),
|
||||||
failureRootSaga(),
|
failureRootSaga(),
|
||||||
profileRootSaga(),
|
profileRootSaga(),
|
||||||
|
workgroupsRootSaga(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
25
client/src/store/sagas/workgroups.ts
Normal file
25
client/src/store/sagas/workgroups.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getClient } from "../../util/daddy";
|
||||||
|
import { Config } from "../../config";
|
||||||
|
import { all, takeLatest, put } from "redux-saga/effects";
|
||||||
|
import { FETCH_WORKGROUPS_SUCCESS, FETCH_WORKGROUPS_FAILURE, FETCH_WORKGROUPS_REQUEST } from "../actions/workgroups";
|
||||||
|
import { Workgroup } from "../../types/workgroup";
|
||||||
|
|
||||||
|
export function* workgroupsRootSaga() {
|
||||||
|
yield all([
|
||||||
|
takeLatest(FETCH_WORKGROUPS_REQUEST, fetchWorkgroupsSaga),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* fetchWorkgroupsSaga() {
|
||||||
|
const client = getClient(Config.graphQLEndpoint, Config.subscriptionEndpoint);
|
||||||
|
|
||||||
|
let workgroups: [Workgroup];
|
||||||
|
try {
|
||||||
|
workgroups = yield client.fetchWorkgroups().then(result => result.workgroups);
|
||||||
|
} catch(err) {
|
||||||
|
yield put({ type: FETCH_WORKGROUPS_FAILURE, err });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put({type: FETCH_WORKGROUPS_SUCCESS, workgroups });
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export interface User {
|
export interface User {
|
||||||
|
id: string
|
||||||
email: string
|
email: string
|
||||||
name?: string
|
name?: string
|
||||||
connectedAt?: Date
|
connectedAt?: Date
|
||||||
|
9
client/src/types/workgroup.ts
Normal file
9
client/src/types/workgroup.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { User } from "./user";
|
||||||
|
|
||||||
|
export interface Workgroup {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
createdAt: Date
|
||||||
|
closedAt: Date
|
||||||
|
members: [User]
|
||||||
|
}
|
@ -79,6 +79,7 @@ export class DaddyClient {
|
|||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
userProfile {
|
userProfile {
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
createdAt,
|
createdAt,
|
||||||
@ -89,6 +90,24 @@ export class DaddyClient {
|
|||||||
.then(this.assertAuthorization)
|
.then(this.assertAuthorization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchWorkgroups() {
|
||||||
|
return this.gql.query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
workgroups {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
createdAt,
|
||||||
|
closedAt,
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
.then(this.assertAuthorization)
|
||||||
|
}
|
||||||
|
|
||||||
updateProfile(changes: ProfileChanges) {
|
updateProfile(changes: ProfileChanges) {
|
||||||
return this.gql.mutate({
|
return this.gql.mutate({
|
||||||
variables: {
|
variables: {
|
||||||
|
Loading…
Reference in New Issue
Block a user