Skip to content

Commit

Permalink
feat(dashboard): person list
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Sep 28, 2023
1 parent abcd135 commit 6530f69
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 127 deletions.
Binary file added dashboard/public/icon_1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/icon_512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions dashboard/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="manifest" href="mano.webmanifest" />
<meta name="theme-color" content="#008e7f" />
<link rel="shortcut icon" href="/favicon.png" />
<title>Mano - Admin</title>
Expand Down
21 changes: 21 additions & 0 deletions dashboard/public/mano.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Mano",
"short_name": "Mano",
"theme_color": "#008e7f",
"background_color": "#008e7f",
"display": "fullscreen",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "icon_1024.png",
"type": "image/png",
"sizes": "1024x1024"
},
{
"src": "icon_512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
2 changes: 1 addition & 1 deletion dashboard/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ const RestrictedRoute = ({ component: Component, _isLoggedIn, ...rest }) => {
padding: 0 !important;
overflow: initial;
*/}
<main className="tw-relative tw-flex tw-grow tw-basis-full tw-flex-col tw-overflow-auto tw-overflow-y-scroll tw-px-2 print:!tw-ml-0 print:tw-h-auto print:tw-max-w-full print:tw-overflow-visible print:tw-p-0 sm:tw-px-12 sm:tw-pt-4 sm:tw-pb-12">
<main className="tw-relative tw-flex tw-grow tw-basis-full tw-flex-col tw-overflow-auto tw-overflow-x-hidden tw-overflow-y-scroll tw-px-2 print:!tw-ml-0 print:tw-h-auto print:tw-max-w-full print:tw-overflow-visible print:tw-p-0 sm:tw-px-12 sm:tw-pt-4 sm:tw-pb-12">
<SentryRoute {...rest} render={(props) => (user ? <Component {...props} /> : <Redirect to={{ pathname: '/auth' }} />)} />
</main>
</div>
Expand Down
167 changes: 91 additions & 76 deletions dashboard/src/components/table.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useCallback, useEffect, useRef } from 'react';
import Sortable from 'sortablejs';
import useMinimumWidth from '../services/useMinimumWidth';

const Table = ({
columns = [],
data = [],
renderCellSmallDevices,
rowKey,
dataTestId = null,
onRowClick,
Expand Down Expand Up @@ -33,6 +35,8 @@ const Table = ({
}
}, [onListChange, isSortable, data.length]);

const isDesktop = useMinimumWidth('sm');

if (!data.length && noData) {
return (
<table className={[className, 'table-selego'].join(' ')}>
Expand All @@ -51,84 +55,95 @@ const Table = ({
</table>
);
}
return (
<table className={[className, 'table-selego'].join(' ')}>
<thead>
{!!title && (

if (isDesktop || !renderCellSmallDevices) {
return (
<table className={[className, 'table-selego'].join(' ')}>
<thead className="tw-hidden sm:tw-table-header-group">
{!!title && (
<tr>
<td tabIndex={0} aria-label={title} className="title" colSpan={columns.length}>
{title}
</td>
</tr>
)}
<tr>
<td tabIndex={0} aria-label={title} className="title" colSpan={columns.length}>
{title}
</td>
</tr>
)}
<tr>
{columns.map((column) => {
const { onSortBy, onSortOrder, sortBy, sortOrder, sortableKey, dataKey } = column;
const onNameClick = () => {
if (sortBy === sortableKey || sortBy === dataKey) {
onSortOrder(sortOrder === 'ASC' ? 'DESC' : 'ASC');
return;
}
onSortBy(sortableKey || dataKey);
};
return (
<td
className={[column.className || '', !!onSortBy ? 'tw-cursor-pointer' : 'tw-cursor-default'].join(' ')}
style={column.style || {}}
key={String(dataKey) + String(column.title)}>
<button aria-label="Changer l'ordre de tri" type="button" onClick={!!onSortBy ? onNameClick : null}>
{column.title}
</button>
{column.help && <>{column.help}</>}
{!!onSortBy && (sortBy === sortableKey || sortBy === dataKey) && (
<button onClick={!!onSortBy ? onNameClick : null} type="button" aria-label="Changer l'ordre de tri">
{sortOrder === 'ASC' && <span className="tw-mx-4" onClick={() => onSortOrder('DESC')}>{`\u00A0\u2193`}</span>}
{sortOrder === 'DESC' && <span className="tw-mx-4" onClick={() => onSortOrder('ASC')}>{`\u00A0\u2191`}</span>}
{columns.map((column) => {
const { onSortBy, onSortOrder, sortBy, sortOrder, sortableKey, dataKey } = column;
const onNameClick = () => {
if (sortBy === sortableKey || sortBy === dataKey) {
onSortOrder(sortOrder === 'ASC' ? 'DESC' : 'ASC');
return;
}
onSortBy(sortableKey || dataKey);
};
return (
<td
className={[column.className || '', !!onSortBy ? 'tw-cursor-pointer' : 'tw-cursor-default'].join(' ')}
style={column.style || {}}
key={String(dataKey) + String(column.title)}>
<button aria-label="Changer l'ordre de tri" type="button" onClick={!!onSortBy ? onNameClick : null}>
{column.title}
</button>
)}
</td>
);
})}
</tr>
</thead>
<tbody ref={gridRef}>
{data
.filter((e) => e)
.map((item) => {
return (
<tr
onClick={() => (!rowDisabled(item) && onRowClick ? onRowClick(item) : null)}
onKeyUp={(event) => {
if (event.key === 'Enter')
if (!rowDisabled(item) && onRowClick) {
onRowClick(item);
}
}}
key={item[rowKey] || item._id}
data-key={item[rowKey] || item._id}
data-test-id={item[dataTestId] || item[rowKey] || item._id}
tabIndex={0}
className={[
rowDisabled(item)
? 'tw-cursor-not-allowed'
: isSortable
? 'tw-cursor-move'
: Boolean(onRowClick)
? 'tw-cursor-pointer'
: 'tw-cursor-auto',
].join(' ')}
style={item.style || {}}>
{columns.map((column) => {
return (
<td className={([column.className || ''].join(' '), !!column.small ? 'small' : 'not-small')} key={item[rowKey] + column.dataKey}>
{column.render ? column.render(item) : item[column.dataKey] || nullDisplay}
</td>
);
})}
</tr>
);
})}
</tbody>
{column.help && <>{column.help}</>}
{!!onSortBy && (sortBy === sortableKey || sortBy === dataKey) && (
<button onClick={!!onSortBy ? onNameClick : null} type="button" aria-label="Changer l'ordre de tri">
{sortOrder === 'ASC' && <span className="tw-mx-4" onClick={() => onSortOrder('DESC')}>{`\u00A0\u2193`}</span>}
{sortOrder === 'DESC' && <span className="tw-mx-4" onClick={() => onSortOrder('ASC')}>{`\u00A0\u2191`}</span>}
</button>
)}
</td>
);
})}
</tr>
</thead>
<tbody ref={gridRef}>
{data
.filter((e) => e)
.map((item) => {
return (
<tr
onClick={() => (!rowDisabled(item) && onRowClick ? onRowClick(item) : null)}
onKeyUp={(event) => {
if (event.key === 'Enter')
if (!rowDisabled(item) && onRowClick) {
onRowClick(item);
}
}}
key={item[rowKey] || item._id}
data-key={item[rowKey] || item._id}
data-test-id={item[dataTestId] || item[rowKey] || item._id}
tabIndex={0}
className={[
rowDisabled(item)
? 'tw-cursor-not-allowed'
: isSortable
? 'tw-cursor-move'
: Boolean(onRowClick)
? 'tw-cursor-pointer'
: 'tw-cursor-auto',
].join(' ')}
style={item.style || {}}>
{columns.map((column) => {
return (
<td
className={([column.className || ''].join(' '), !!column.small ? 'small' : 'not-small')}
key={item[rowKey] + column.dataKey}>
{column.render ? column.render(item) : item[column.dataKey] || nullDisplay}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
}

return (
<table>
<tbody ref={gridRef}>{data.filter((e) => e).map(renderCellSmallDevices)}</tbody>
</table>
);
};
Expand Down
4 changes: 0 additions & 4 deletions dashboard/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ table.table-selego {
background: #fff;
-fs-table-paginate: paginate;

thead {
display: table-header-group;
}

thead tr {
height: 2.5rem;
}
Expand Down
136 changes: 90 additions & 46 deletions dashboard/src/scenes/person/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,65 +135,109 @@ const List = () => {
</>
}
/>
<div className="tw-flex tw-flex-wrap">
<div className="tw-hidden tw-flex-wrap sm:tw-flex">
<div className="tw-relative tw-w-full tw-max-w-full tw-grow tw-basis-0">
<div className="tw-mb-8 tw-flex tw-w-full tw-justify-end">
<CreatePerson refreshable />
</div>
</div>
</div>
<div className="tw-mb-5 tw-flex tw-flex-wrap">
<div className="tw-mb-5 tw-flex tw-w-full tw-items-start tw-justify-start">
<label htmlFor="search" className="tw-mr-5 tw-shrink-0 tw-basis-40">
Recherche :{' '}
</label>
<div className="tw-flex-grow-1 tw-flex-col tw-items-stretch tw-gap-2">
<Search
placeholder="Par mot clé, présent dans le nom, la description, un commentaire, une action, ..."
value={search}
onChange={(value) => {
if (page) {
setPage(0);
setSearch(value, { sideEffect: ['page', 0] });
} else {
setSearch(value);
}
}}
/>
<div className="tw-flex tw-w-full tw-items-center">
<label htmlFor="viewAllOrganisationData">
<input
type="checkbox"
id="viewAllOrganisationData"
className="tw-mr-2.5"
checked={viewAllOrganisationData}
value={viewAllOrganisationData}
onChange={() => setViewAllOrganisationData(!viewAllOrganisationData)}
/>
Afficher les personnes de toute l'organisation
</label>
</div>
<div className="tw-flex tw-w-full tw-items-center">
<label htmlFor="alertness">
<input
type="checkbox"
className="tw-mr-2.5"
id="alertness"
checked={alertness}
value={alertness}
onChange={() => setFilterAlertness(!alertness)}
/>
N'afficher que les personnes vulnérables où ayant besoin d'une attention particulière
</label>
<details className="[&_summary]:open:tw-opacity-10">
<summary className="tw-my-2 tw-mx-4">Recherche et filtres...</summary>
<div className="tw-mb-5 tw-flex tw-flex-wrap ">
<div className="tw-mb-5 tw-flex tw-w-full tw-items-start tw-justify-start">
<label htmlFor="search" className="tw-mr-5 tw-shrink-0 tw-basis-40">
Recherche :{' '}
</label>
<div className="tw-flex-grow-1 tw-flex-col tw-items-stretch tw-gap-2">
<Search
placeholder="Par mot clé, présent dans le nom, la description, un commentaire, une action, ..."
value={search}
onChange={(value) => {
if (page) {
setPage(0);
setSearch(value, { sideEffect: ['page', 0] });
} else {
setSearch(value);
}
}}
/>
<div className="tw-flex tw-w-full tw-items-center">
<label htmlFor="viewAllOrganisationData">
<input
type="checkbox"
id="viewAllOrganisationData"
className="tw-mr-2.5"
checked={viewAllOrganisationData}
value={viewAllOrganisationData}
onChange={() => setViewAllOrganisationData(!viewAllOrganisationData)}
/>
Afficher les personnes de toute l'organisation
</label>
</div>
<div className="tw-flex tw-w-full tw-items-center">
<label htmlFor="alertness">
<input
type="checkbox"
className="tw-mr-2.5"
id="alertness"
checked={alertness}
value={alertness}
onChange={() => setFilterAlertness(!alertness)}
/>
N'afficher que les personnes vulnérables où ayant besoin d'une attention particulière
</label>
</div>
</div>
</div>
</div>
</div>
<Filters base={filterPersonsWithAllFields} filters={filters} onChange={setFilters} title="Autres filtres : " saveInURLParams />
<Filters base={filterPersonsWithAllFields} filters={filters} onChange={setFilters} title="Autres filtres : " saveInURLParams />
</details>
<Table
data={data}
rowKey={'_id'}
onRowClick={(p) => history.push(`/person/${p._id}`)}
renderCellSmallDevices={(p) => {
return (
<tr className="tw-my-3 tw-block tw-rounded-md tw-bg-[#f4f5f8] tw-p-4 tw-px-2">
<td className="tw-flex tw-flex-col tw-items-start tw-gap-1">
<div className="tw-flex tw-items-center tw-gap-x-2">
{!!p.group && (
<span aria-label="Personne avec des liens familiaux" title="Personne avec des liens familiaux">
👪
</span>
)}
{!!p.alertness && (
<ExclamationMarkButton
aria-label="Personne très vulnérable, ou ayant besoin d'une attention particulière"
title="Personne très vulnérable, ou ayant besoin d'une attention particulière"
/>
)}
{p.outOfActiveList ? (
<div className="tw-max-w-md tw-text-black50">
<div className="tw-flex tw-items-center tw-gap-1 tw-font-bold [overflow-wrap:anywhere]">
{p.name}
{p.otherNames ? <small className="tw-text-main75"> - {p.otherNames}</small> : null}
</div>
<div>Sortie de file active : {p.outOfActiveListReasons?.join(', ')}</div>
</div>
) : (
<div className="tw-flex tw-max-w-md tw-items-center tw-gap-1 tw-font-bold [overflow-wrap:anywhere]">
{p.name}
{p.otherNames ? <small className="tw-text-main75"> - {p.otherNames}</small> : null}
</div>
)}
</div>
<span className="tw-opacity-50">{p.formattedBirthDate}</span>
<div className="tw-flex tw-w-full tw-flex-wrap tw-gap-2">
{p.assignedTeams?.map((teamId) => (
<TagTeam key={teamId} teamId={teamId} />
))}
</div>
</td>
</tr>
);
}}
columns={[
{
title: '',
Expand Down

0 comments on commit 6530f69

Please sign in to comment.