Gestion des liens profonds sur les tabs dans la page DAD #30

Manually merged
wpetit merged 1 commits from feature/dsf-tab-deep-linking into develop 2020-10-12 15:58:58 +02:00
6 changed files with 192 additions and 140 deletions
Showing only changes of commit 5649cd2aad - Show all commits

View File

@ -45,7 +45,7 @@ export const App: FunctionComponent<AppProps> = () => {
<Route path="/unauthorized" exact component={UnauthorizedPage} />
<PrivateRoute path="/profile" exact component={ProfilePage} />
<PrivateRoute path="/workgroups/:id" exact component={WorkgroupPage} />
<PrivateRoute path="/decisions/:id" exact component={DecisionSupportFilePage} />
<PrivateRoute path="/decisions/:id" component={DecisionSupportFilePage} />
<PrivateRoute path="/dashboard" exact component={DashboardPage} />
<PrivateRoute path="/logout" exact component={LogoutPage} />
<Route component={() => <Redirect to="/" />} />

View File

@ -49,7 +49,6 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
return (
<section>
<div className="box">
<div className="field">
<label className="label is-medium">Intitulé du dossier</label>
<div className="control">
@ -136,7 +135,6 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
</div>
</div>
</div>
</div>
</section>
);
};

View File

@ -32,7 +32,6 @@ export const DecisionReportSection: FunctionComponent<DecisionReportSectionProps
return (
<section>
<div className="box">
<div className="field">
<label className="label is-medium">Compte rendu du vote</label>
<div className="control">
@ -45,7 +44,6 @@ export const DecisionReportSection: FunctionComponent<DecisionReportSectionProps
</div>
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer le résultat du vote et les éléments de contexte liés à la prise de décision.</p>
</div>
</div>
</section>
);
};

View File

@ -11,6 +11,7 @@ import { OptionsSection } from './OptionsSection';
import { useIsAuthorized } from '../../gql/queries/authorization';
import { TimelinePanel } from './TimelinePanel';
import { DecisionReportSection } from './DecisionReportSection';
import { RoutedTabs } from '../RoutedTabs';
export interface DecisionSupportFilePageProps {
@ -30,10 +31,8 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
const [ state, setState ] = useState({
dsf: newDecisionSupportFile(),
saved: true,
selectedTabIndex: 0,
});
const { isAuthorized } = useIsAuthorized({
variables: {
action: 'update',
@ -48,9 +47,26 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
setState(state => ({ ...state, dsf: { ...state.dsf, ...dsf }}))
}, [ decisionSupportFiles ]);
const selectTab = (tabIndex: number) => {
setState(state => ({ ...state, selectedTabIndex: tabIndex }));
};
const tabs = [
{
name: "Clarifier la proposition",
icon: "fas fa-pen",
route: '/info',
render: () => (<ClarificationSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
},
{
name: "Explorer les options",
icon: "fas fa-search",
route: '/options',
render: () => (<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
},
{
name: "Prendre la décision",
icon: "fas fa-person-booth",
route: '/vote',
render: () => (<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
}
];
const updateDSF = (dsf: DecisionSupportFile) => {
setState(state => {
@ -130,46 +146,9 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
</section>
<div className="columns mt-3">
<div className="column is-8">
<div className="tabs is-medium is-toggle">
<ul>
<li className={`has-background-white ${state.selectedTabIndex === 0 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 0)}>
<a>
<span className="icon is-small"><i className="fas fa-pen" aria-hidden="true"></i></span>
<span>Clarifier la proposition</span>
</a>
</li>
<li className={`has-background-white ${state.selectedTabIndex === 1 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 1)}>
<a>
<span className="icon is-small"><i className="fas fa-search" aria-hidden="true"></i></span>
<span>Explorer les options</span>
</a>
</li>
<li className={`has-background-white ${state.selectedTabIndex === 2 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 2)}>
<a>
<span className="icon is-small"><i className="fas fa-person-booth" aria-hidden="true"></i></span>
<span>Prendre la décision</span>
</a>
</li>
</ul>
<div className="box">
<RoutedTabs baseRoute={`/decisions/${id}`} tabs={tabs} />
</div>
{
state.selectedTabIndex === 0 ?
<ClarificationSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
{
state.selectedTabIndex === 1 ?
<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
{
state.selectedTabIndex === 2 ?
<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
</div>
<div className="column is-4">
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />

View File

@ -75,8 +75,7 @@ export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf, up
return (
<section>
<h4 id="options-section" className="is-size-4 title is-spaced"><a href="#options-section">Explorer les options</a></h4>
<div className="box">
<h4 id="options-section" className="is-size-4 title is-spaced">Explorer les options</h4>
<div className="table-container">
<table className={`table is-bordered is-striped is-hoverable is-fullwidth`}>
<thead>
@ -154,7 +153,6 @@ export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf, up
</tfoot>
</table>
</div>
</div>
</section>
);
};

View File

@ -0,0 +1,79 @@
import React, { FunctionComponent, ReactNode, useEffect, useState } from 'react';
import { useHistory, useLocation, useRouteMatch } from 'react-router';
import { Link } from 'react-router-dom';
export interface Tab {
route: string
name: string
icon ?: string
render: (tab: Tab) => ReactNode
}
export interface RoutedTabsProps {
tabs: Tab[]
baseRoute?: string
defaultTabIndex?: number
}
export const RoutedTabs: FunctionComponent<RoutedTabsProps> = ({ tabs, baseRoute, defaultTabIndex }) => {
const history = useHistory();
const location = useLocation();
const tabRoute = (route: string): string => {
return `${baseRoute}${route}`;
};
const [ selectedTabIndex, setSelectedTabIndex ] = useState(defaultTabIndex || 0);
const expectedTab = tabs[selectedTabIndex];
const expectedTabRoute = tabRoute(expectedTab.route);
let matchExpectedTabRoute = useRouteMatch(expectedTabRoute);
useEffect(() => {
if (matchExpectedTabRoute) return;
const newTabIndex = tabs.findIndex(t => location.pathname === tabRoute(t.route));
if (newTabIndex !== -1) {
selectTab(newTabIndex);
return;
}
history.push(expectedTabRoute);
}, [matchExpectedTabRoute]);
const selectTab = (tabIndex: number) => {
setSelectedTabIndex(tabIndex);
const newTab = tabs[tabIndex];
history.push(tabRoute(newTab.route));
};
return (
<React.Fragment>
<div className="tabs is-medium is-boxed">
<ul>
{
tabs.map((t: Tab, i: number) => {
return (
<li key={`tab-${i}`} className={`has-background-white ${selectedTabIndex === i ? 'is-active': ''}`}
onClick={selectTab.bind(null, i)}>
<a>
{
t.icon ?
<span className="icon is-small"><i className={t.icon} aria-hidden="true"></i></span> :
null
}
<span>{t.name}</span>
</a>
</li>
);
})
}
</ul>
</div>
{ expectedTab.render(expectedTab) }
</React.Fragment>
);
}