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