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:
wpetit 2020-07-21 18:12:02 +02:00
parent 676ddf3bc8
commit 8708e30020
13 changed files with 224 additions and 16 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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>

View 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>
)
}

View 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 }
}

View File

@ -31,6 +31,7 @@ function handleSetCurrentUser(state: AuthState, { email }: setCurrentUserAction)
...state, ...state,
isAuthenticated: true, isAuthenticated: true,
currentUser: { currentUser: {
id: '',
email email
} }
}; };

View File

@ -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,
}); });

View 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,
};
};

View File

@ -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(),
]); ]);
} }

View 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 });
}

View File

@ -1,4 +1,5 @@
export interface User { export interface User {
id: string
email: string email: string
name?: string name?: string
connectedAt?: Date connectedAt?: Date

View File

@ -0,0 +1,9 @@
import { User } from "./user";
export interface Workgroup {
id: string
name: string
createdAt: Date
closedAt: Date
members: [User]
}

View File

@ -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: {