From ac41b301a93fa7b54457478931bba2fd2920985a Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 31 Jul 2020 17:36:10 +0200 Subject: [PATCH 1/7] Refactoring du tableau de bord et ajout du panel pour les DADs --- client/src/components/HomePage/Dashboard.tsx | 21 +-- .../HomePage/DecisionSupportFilePanel.tsx | 44 +++++++ client/src/components/HomePage/ItemPanel.tsx | 121 ++++++++++++++++++ .../components/HomePage/WorkgroupsPanel.tsx | 103 ++++----------- client/src/gql/queries/decisions.tsx | 32 +++++ client/src/gql/queries/helper.ts | 11 ++ client/src/gql/queries/profile.tsx | 10 ++ client/src/gql/queries/workgroups.tsx | 9 ++ client/src/types/decision.tsx | 23 ++++ client/src/types/workgroup.ts | 13 +- 10 files changed, 292 insertions(+), 95 deletions(-) create mode 100644 client/src/components/HomePage/DecisionSupportFilePanel.tsx create mode 100644 client/src/components/HomePage/ItemPanel.tsx create mode 100644 client/src/gql/queries/decisions.tsx create mode 100644 client/src/gql/queries/helper.ts create mode 100644 client/src/types/decision.tsx diff --git a/client/src/components/HomePage/Dashboard.tsx b/client/src/components/HomePage/Dashboard.tsx index 916fba1..967cac2 100644 --- a/client/src/components/HomePage/Dashboard.tsx +++ b/client/src/components/HomePage/Dashboard.tsx @@ -1,26 +1,17 @@ import React from 'react'; import { WorkgroupsPanel } from './WorkgroupsPanel'; +import { DecisionSupportFilePanel } from './DecisionSupportFilePanel'; export function Dashboard() { return (
-
+
+ +
+
-
-
-
-
-

D.A.Ds

-
-
- -
-
-
TODO
-
-
-
+
diff --git a/client/src/components/HomePage/DecisionSupportFilePanel.tsx b/client/src/components/HomePage/DecisionSupportFilePanel.tsx new file mode 100644 index 0000000..d241ff8 --- /dev/null +++ b/client/src/components/HomePage/DecisionSupportFilePanel.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision'; +import { ItemPanel, TabDefinition, Item } from './ItemPanel'; +import { useUserProfile } from '../../gql/queries/profile'; +import { inWorkgroup } from '../../types/workgroup'; +import { useDecisions } from '../../gql/queries/decisions'; + +export function DecisionSupportFilePanel() { + const { user } = useUserProfile(); + const { decisions } = useDecisions(); + + const tabs: TabDefinition[] = [ + { + label: 'Mes dossiers en cours', + itemFilter: (item: Item) => { + const dsf = item as DecisionSupportFile; + return dsf.status === DecisionSupportFileStatus.Opened && inWorkgroup(user, dsf.workgroup); + } + }, + { + label: 'Ouverts', + itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Opened + }, + { + label: 'Clos', + itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Closed + }, + ]; + + + return ( + item.id} + itemLabel={item => item.title} + itemUrl={item => `/decisions/${item.id}`} + /> + ); +} \ No newline at end of file diff --git a/client/src/components/HomePage/ItemPanel.tsx b/client/src/components/HomePage/ItemPanel.tsx new file mode 100644 index 0000000..054e69d --- /dev/null +++ b/client/src/components/HomePage/ItemPanel.tsx @@ -0,0 +1,121 @@ +import React, { FunctionComponent, useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { WithLoader } from "../WithLoader"; + +export interface Item { + id: string + [propName: string]: any; +} + +export interface TabDefinition { + label: string + itemFilter?: (item: Item) => boolean +} + + +export interface ItemPanelProps { + className?: string + itemIconClassName?: string + title?: string + newItemUrl: string + isLoading?: boolean + items: Item[] + tabs?: TabDefinition[], + itemKey: (item: Item, index: number) => string + itemLabel: (item: Item, index: number) => string + itemUrl: (item: Item, index: number) => string +} + +export const ItemPanel: FunctionComponent = (props) => { + const { + title, className, newItemUrl, + itemKey, itemLabel, + itemIconClassName, itemUrl + } = props; + + const [ state, setState ] = useState({ selectedTab: 0, filteredItems: [] }); + + const filterItemsForTab = (tab: TabDefinition, items: Item[]) => { + const itemFilter = tab && typeof tab.itemFilter === 'function' ? tab.itemFilter : () => true; + return items.filter(itemFilter); + }; + + const selectTab = (tabIndex: number) => { + setState(state => { + const { tabs, items } = props; + const newTab = Array.isArray(tabs) && tabs.length > 0 ? tabs[tabIndex] : null; + return { + ...state, + selectedTab: tabIndex, + filteredItems: filterItemsForTab(newTab, items) + }; + }); + }; + + useEffect(() => { + setState(state => { + const { tabs, items } = props; + const newTab = Array.isArray(tabs) && tabs.length > 0 ? tabs[state.selectedTab] : null; + return { + ...state, + filteredItems: filterItemsForTab(newTab, items), + } + }); + }, [props.items, props.tabs]); + + const itemElements = state.filteredItems.map((item: Item, i: number) => { + return ( + + + + + {itemLabel(item, i)} + + ); + }); + + const tabs = props.tabs || []; + + return ( + + ) +}; \ No newline at end of file diff --git a/client/src/components/HomePage/WorkgroupsPanel.tsx b/client/src/components/HomePage/WorkgroupsPanel.tsx index 439b931..c7ea0d1 100644 --- a/client/src/components/HomePage/WorkgroupsPanel.tsx +++ b/client/src/components/HomePage/WorkgroupsPanel.tsx @@ -1,98 +1,45 @@ import React, { useEffect, useState } from 'react'; -import { Workgroup } from '../../types/workgroup'; +import { Workgroup, inWorkgroup } from '../../types/workgroup'; import { User } from '../../types/user'; import { Link } from 'react-router-dom'; -import { useWorkgroupsQuery } from '../../gql/queries/workgroups'; -import { useUserProfileQuery } from '../../gql/queries/profile'; +import { useWorkgroupsQuery, useWorkgroups } from '../../gql/queries/workgroups'; +import { useUserProfileQuery, useUserProfile } from '../../gql/queries/profile'; import { WithLoader } from '../WithLoader'; +import { ItemPanel, Item } from './ItemPanel'; export function WorkgroupsPanel() { - const workgroupsQuery = useWorkgroupsQuery(); - const userProfileQuery = useUserProfileQuery(); - const [ state, setState ] = useState({ selectedTab: 0 }); + const { workgroups } = useWorkgroups(); + const { user } = useUserProfile(); - const isLoading = userProfileQuery.loading || workgroupsQuery.loading; - const { userProfile } = (userProfileQuery.data || {}); - const { workgroups } = (workgroupsQuery.data || {}); - - const filterTabs = [ + const tabs = [ { label: "Mes groupes en cours", - filter: workgroups => workgroups.filter((wg: Workgroup) => { - return wg.closedAt === null && wg.members.some((u: User) => u.id === (userProfile ? userProfile.id : '')); - }) + itemFilter: (item: Item) => { + const wg = item as Workgroup; + return wg.closedAt === null && inWorkgroup(user, wg); + } }, { label: "Ouverts", - filter: workgroups => workgroups.filter((wg: Workgroup) => !wg.closedAt) + itemFilter: (item: Item) => !(item as Workgroup).closedAt }, { label: "Clos", - filter: workgroups => workgroups.filter((wg: Workgroup) => !!wg.closedAt) + itemFilter: (item: Item) => !!(item as Workgroup).closedAt } ]; - - const selectTab = (tabIndex: number) => { - setState(state => ({ ...state, selectedTab: tabIndex })); - }; - let workgroupsItems = []; - - workgroupsItems = filterTabs[state.selectedTab].filter(workgroups || []).map((wg: Workgroup) => { - return ( - - - - - {wg.name} - - ); - }); - return ( - - ) + item.id} + itemLabel={item => item.name} + itemUrl={item => `/workgroups/${item.id}`} + /> + ); } \ No newline at end of file diff --git a/client/src/gql/queries/decisions.tsx b/client/src/gql/queries/decisions.tsx new file mode 100644 index 0000000..f504155 --- /dev/null +++ b/client/src/gql/queries/decisions.tsx @@ -0,0 +1,32 @@ +import { gql, useQuery } from '@apollo/client'; +import { DecisionSupportFile } from '../../types/decision'; +import { useState, useEffect } from 'react'; +import { useGraphQLData } from './helper'; + +const QUERY_DECISIONS = gql` + query decisions($filter: DecisionFilter) { + decisions(filter: $filter) { + id, + title, + sections + createdAt, + closedAt, + votedAt, + workgroup { + id, + name + } + } + } +`; + +export function useDecisionsQuery(options = {}) { + return useQuery(QUERY_DECISIONS, options); +} + +export function useDecisions() { + const { data, loading, error } = useGraphQLData( + QUERY_DECISIONS, 'decicions', [] + ); + return { decisions: data, loading, error }; +} \ No newline at end of file diff --git a/client/src/gql/queries/helper.ts b/client/src/gql/queries/helper.ts new file mode 100644 index 0000000..f159356 --- /dev/null +++ b/client/src/gql/queries/helper.ts @@ -0,0 +1,11 @@ +import { useQuery, DocumentNode } from "@apollo/client"; +import { useState, useEffect } from "react"; + +export function useGraphQLData(q: DocumentNode, key: string, defaultValue: T) { + const query = useQuery(q); + const [ data, setData ] = useState(defaultValue); + useEffect(() => { + setData(query.data ? query.data[key] as T : defaultValue); + }, [query.loading, query.data]); + return { data, loading: query.loading, error: query.error }; +} \ No newline at end of file diff --git a/client/src/gql/queries/profile.tsx b/client/src/gql/queries/profile.tsx index 573fc04..b8f9bae 100644 --- a/client/src/gql/queries/profile.tsx +++ b/client/src/gql/queries/profile.tsx @@ -1,4 +1,7 @@ import { gql, useQuery } from '@apollo/client'; +import { User } from '../../types/user'; +import { useState, useEffect } from 'react'; +import { useGraphQLData } from './helper'; const QUERY_USER_PROFILE = gql` query userProfile { @@ -13,4 +16,11 @@ query userProfile { export function useUserProfileQuery() { return useQuery(QUERY_USER_PROFILE); +} + +export function useUserProfile() { + const { data, loading, error } = useGraphQLData( + QUERY_USER_PROFILE, 'userProfile', {id: '', email: ''} + ); + return { user: data, loading, error }; } \ No newline at end of file diff --git a/client/src/gql/queries/workgroups.tsx b/client/src/gql/queries/workgroups.tsx index 4693edd..261ceeb 100644 --- a/client/src/gql/queries/workgroups.tsx +++ b/client/src/gql/queries/workgroups.tsx @@ -1,4 +1,6 @@ import { gql, useQuery } from '@apollo/client'; +import { Workgroup } from '../../types/workgroup'; +import { useGraphQLData } from './helper'; const QUERY_WORKGROUP = gql` query workgroups($filter: WorkgroupsFilter) { @@ -18,4 +20,11 @@ const QUERY_WORKGROUP = gql` export function useWorkgroupsQuery(options = {}) { return useQuery(QUERY_WORKGROUP, options); +} + +export function useWorkgroups() { + const { data, loading, error } = useGraphQLData( + QUERY_WORKGROUP, 'workgroups', [] + ); + return { workgroups: data, loading, error }; } \ No newline at end of file diff --git a/client/src/types/decision.tsx b/client/src/types/decision.tsx new file mode 100644 index 0000000..d38c619 --- /dev/null +++ b/client/src/types/decision.tsx @@ -0,0 +1,23 @@ +import { Workgroup } from "./workgroup"; + +export enum DecisionSupportFileStatus { + Opened = "opened", + Voted = "voted", + Closed = "closed", +} + +export interface DecisionSupportFileSection { + id: string +} + +// aka Dossier d'aide à la décision +export interface DecisionSupportFile { + id: string + title: string + sections: DecisionSupportFileSection[] + status: DecisionSupportFileStatus + workgroup: Workgroup, + createdAt: Date + votedAt?: Date + closedAt?: Date +} \ No newline at end of file diff --git a/client/src/types/workgroup.ts b/client/src/types/workgroup.ts index 459f29a..9546278 100644 --- a/client/src/types/workgroup.ts +++ b/client/src/types/workgroup.ts @@ -1,9 +1,18 @@ import { User } from "./user"; - export interface Workgroup { id: string name: string createdAt: Date closedAt: Date - members: [User] + members: User[] +} + +export function inWorkgroup(u: User, wg: Workgroup): boolean { + for (let m, i = 0; (m = wg.members[i]); i++) { + if(m.id === u.id) { + return true; + } + } + + return false; } \ No newline at end of file -- 2.17.1 From fc4912882a7bc16e6396739ad43e5823bb29a82f Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 31 Jul 2020 18:04:31 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Base=20de=20la=20page=20de=20cr=C3=A9ation/?= =?UTF-8?q?=C3=A9dition=20d'un=20DAD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/App.tsx | 2 + .../DecisionSupportFilePage/AppendixPanel.tsx | 18 +++++ .../ClarificationSection.tsx | 67 +++++++++++++++++ .../DecisionSupportFilePage.tsx | 74 +++++++++++++++++++ .../DecisionSupportFilePage/MetadataPanel.tsx | 54 ++++++++++++++ .../OptionsSection.tsx | 17 +++++ .../HomePage/DecisionSupportFilePanel.tsx | 2 +- client/src/components/HomePage/ItemPanel.tsx | 2 +- client/src/components/Navbar.tsx | 2 +- client/src/gql/mutations/profile.tsx | 2 +- client/src/gql/mutations/workgroups.tsx | 11 ++- client/src/gql/queries/decisions.tsx | 2 +- client/src/gql/queries/profile.tsx | 2 +- client/src/gql/queries/workgroups.tsx | 2 +- client/src/index.html | 2 +- client/src/sass/_base.scss | 8 +- 16 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 client/src/components/DecisionSupportFilePage/AppendixPanel.tsx create mode 100644 client/src/components/DecisionSupportFilePage/ClarificationSection.tsx create mode 100644 client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx create mode 100644 client/src/components/DecisionSupportFilePage/MetadataPanel.tsx create mode 100644 client/src/components/DecisionSupportFilePage/OptionsSection.tsx diff --git a/client/src/components/App.tsx b/client/src/components/App.tsx index 89e3dca..9324cf2 100644 --- a/client/src/components/App.tsx +++ b/client/src/components/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom"; import { HomePage } from './HomePage/HomePage'; import { ProfilePage } from './ProfilePage/ProfilePage'; import { WorkgroupPage } from './WorkgroupPage/WorkgroupPage'; +import { DecisionSupportFilePage } from './DecisionSupportFilePage/DecisionSupportFilePage'; export class App extends React.Component { render() { @@ -12,6 +13,7 @@ export class App extends React.Component { + } /> diff --git a/client/src/components/DecisionSupportFilePage/AppendixPanel.tsx b/client/src/components/DecisionSupportFilePage/AppendixPanel.tsx new file mode 100644 index 0000000..97603a4 --- /dev/null +++ b/client/src/components/DecisionSupportFilePage/AppendixPanel.tsx @@ -0,0 +1,18 @@ +import React, { FunctionComponent, useState } from 'react'; +import { DecisionSupportFile } from '../../types/decision'; + +export interface AppendixPanelProps { + dsf: DecisionSupportFile, +}; + +export const AppendixPanel: FunctionComponent = ({ dsf }) => { + return ( + + ); +}; \ No newline at end of file diff --git a/client/src/components/DecisionSupportFilePage/ClarificationSection.tsx b/client/src/components/DecisionSupportFilePage/ClarificationSection.tsx new file mode 100644 index 0000000..d2a767c --- /dev/null +++ b/client/src/components/DecisionSupportFilePage/ClarificationSection.tsx @@ -0,0 +1,67 @@ +import React, { FunctionComponent, useState } from 'react'; +import { DecisionSupportFile } from '../../types/decision'; + +export interface ClarificationSectionProps { + dsf: DecisionSupportFile, +}; + +export const ClarificationSection: FunctionComponent = ({ dsf }) => { + return ( +
+
+
+ +
+ +
+
+
+ +
+ +
+

Ne pas essayer de rentrer trop dans les détails ici. Préférer l'utilisation des annexes et y faire référence.

+
+
+ +
+ +
+

Penser à indiquer si des obligations légales pèsent sur cette prise de décision.

+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx b/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx new file mode 100644 index 0000000..02f23da --- /dev/null +++ b/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx @@ -0,0 +1,74 @@ +import React, { FunctionComponent, useState } from 'react'; +import { Page } from '../Page'; +import { ClarificationSection } from './ClarificationSection'; +import { OptionsSection } from './OptionsSection'; +import { MetadataPanel } from './MetadataPanel'; +import { AppendixPanel } from './AppendixPanel'; + +export interface DecisionSupportFilePageProps { + +}; + +export const DecisionSupportFilePage: FunctionComponent = () => { + const [ state, setState ] = useState({ dsf: null }); + const isNew = true; + const isClosed = false; + return ( + +
+
+
+
+ { + isNew ? +
+
+

Nouveau

+

Dossier d'Aide à la Décision

+
+
: +
+
+

{state.dsf.title}

+

Dossier d'Aide à la Décision { isClosed ? '(clos)' : null }

+
+
+ } +
+
+
+ +
+
+ ); +}; \ No newline at end of file diff --git a/client/src/components/DecisionSupportFilePage/MetadataPanel.tsx b/client/src/components/DecisionSupportFilePage/MetadataPanel.tsx new file mode 100644 index 0000000..674edcf --- /dev/null +++ b/client/src/components/DecisionSupportFilePage/MetadataPanel.tsx @@ -0,0 +1,54 @@ +import React, { FunctionComponent, useState } from 'react'; +import { DecisionSupportFile } from '../../types/decision'; + +export interface MetadataPanelProps { + dsf: DecisionSupportFile, +}; + +export const MetadataPanel: FunctionComponent = ({ dsf }) => { + return ( + + ); +}; \ No newline at end of file diff --git a/client/src/components/DecisionSupportFilePage/OptionsSection.tsx b/client/src/components/DecisionSupportFilePage/OptionsSection.tsx new file mode 100644 index 0000000..bd537cd --- /dev/null +++ b/client/src/components/DecisionSupportFilePage/OptionsSection.tsx @@ -0,0 +1,17 @@ +import React, { FunctionComponent, useState } from 'react'; +import { DecisionSupportFile } from '../../types/decision'; + +export interface OptionsSectionProps { + dsf: DecisionSupportFile, +}; + +export const OptionsSection: FunctionComponent = ({ dsf }) => { + return ( +
+

Explorer les options

+
+ +
+
+ ); +}; \ No newline at end of file diff --git a/client/src/components/HomePage/DecisionSupportFilePanel.tsx b/client/src/components/HomePage/DecisionSupportFilePanel.tsx index d241ff8..e6b69b1 100644 --- a/client/src/components/HomePage/DecisionSupportFilePanel.tsx +++ b/client/src/components/HomePage/DecisionSupportFilePanel.tsx @@ -31,7 +31,7 @@ export function DecisionSupportFilePanel() { return ( = (props) => { return (