Skip to content

Commit

Permalink
feat(frontend): add confirmation before update docs published status (#…
Browse files Browse the repository at this point in the history
…353)

* feat(frontend): add confirmation before update a document published status

* fix: review

* fix: review

* fix: review

* fix: review

* fix(frontend): handle onsubmit for search filter

* test

* revert previosu
  • Loading branch information
lionelB committed Mar 11, 2021
1 parent b62c7fd commit eb3525f
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 360 deletions.
97 changes: 97 additions & 0 deletions targets/frontend/src/components/documents/Actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import PropTypes from "prop-types";
import { useState } from "react";
import { useSelectionContext } from "src/pages/contenus";
import { Box, Text } from "theme-ui";

import { Button } from "../button";
import { Dialog } from "../dialog";
import { Inline } from "../layout/Inline";
import { Stack } from "../layout/Stack";

export function DocumentsListActions({ onUpdatePublication }) {
const [selectedItems] = useSelectionContext();
const [showPublishDialog, setPublishDialogVisible] = useState(false);
const openPublishDialog = () => setPublishDialogVisible(true);
const closePublishDialog = () => setPublishDialogVisible(false);

function updatePublication() {
const docEntries = Object.entries(selectedItems);
onUpdatePublication(docEntries);
closePublishDialog();
}

return (
<Box>
<Dialog
isOpen={showPublishDialog}
onDismiss={closePublishDialog}
aria-label="Modifier le statut de publication"
>
<Stack>
<Stack>
<Text>
Êtes vous sûr de vouloir modifier la publication des
contenus&nbsp;?
</Text>
<Recap publications={selectedItems} />
</Stack>
<Inline>
<Button onClick={updatePublication} size="small">
Modifier la publication des contenus
</Button>
<Button variant="link" onClick={closePublishDialog} size="small">
Annuler
</Button>
</Inline>
</Stack>
</Dialog>
<Button
type="button"
outline
size="small"
variant="secondary"
disabled={Object.keys(selectedItems).length === 0}
onClick={openPublishDialog}
>
Modifier
</Button>
</Box>
);
}
DocumentsListActions.propTypes = {
onUpdatePublication: PropTypes.func.isRequired,
};
function Recap({ publications }) {
const items = Object.entries(publications).reduce(
(state, [, published]) => {
if (published) {
state.published += 1;
} else {
state.unpublished += 1;
}
return state;
},
{ published: 0, unpublished: 0 }
);
return (
<Box>
<Text sx={{ fontWeight: "heading" }}>Détails</Text>
<ul>
{items.published > 0 && (
<li>
<strong>{items.published}</strong> éléments à publier
</li>
)}
{items.unpublished > 0 && (
<li>
<strong>{items.unpublished}</strong> éléments à dépublier
</li>
)}
</ul>
</Box>
);
}

Recap.propTypes = {
publications: PropTypes.object,
};
164 changes: 164 additions & 0 deletions targets/frontend/src/components/documents/Container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import Link from "next/link";
import { useRouter } from "next/router";
import PropTypes from "prop-types";
import { useCallback, useMemo } from "react";
import { IoMdAdd } from "react-icons/io";
import { useSelectionContext } from "src/pages/contenus";
import { Card, Flex, Message } from "theme-ui";
import { useMutation, useQuery } from "urql";

import { Button } from "../button";
import { Stack } from "../layout/Stack";
import { Pagination } from "../pagination";
import { DocumentsListActions } from "./Actions";
import { DocumentList } from "./List";
import { SearchFilters } from "./SearchFilters";

export function DocumentListContainer({ initialFilterValues }) {
const router = useRouter();
const context = useMemo(() => ({ additionalTypenames: ["documents"] }), []);
const [, setSelectedItems] = useSelectionContext();
const [, updatePublication] = useMutation(updatePublicationMutation);
const updatePublicationStatus = useCallback(
(docEntries) => {
docEntries.forEach(([cdtnId, isPublished]) => {
updatePublication({ cdtnId, isPublished });
});
setSelectedItems({});
},
[updatePublication, setSelectedItems]
);

const updateUrl = useCallback(
(filterValues) => {
// we reset changed published value if search critera change
setSelectedItems({});
const query = { ...filterValues, page: 0 };
router.push({ pathname: router.route, query }, undefined, {
shallow: true,
});
},
[router, setSelectedItems]
);

const [result] = useQuery({
context,
query: searchDocumentQuery,
variables: {
limit: initialFilterValues.itemsPerPage,
offset: initialFilterValues.page * initialFilterValues.itemsPerPage,
published:
initialFilterValues.published === "yes"
? [true]
: initialFilterValues.published === "no"
? [false]
: [true, false],
search: `%${initialFilterValues.q}%`,
source: initialFilterValues.source || null,
},
});

const { fetching, error, data } = result;

if (error) {
return <Message variant="primary">{error.message}</Message>;
}
return (
<Stack>
<Flex sx={{ justifyContent: "flex-end" }}>
<Link href="/contenus/create/" passHref>
<Button as="a" size="small" outline variant="secondary">
<IoMdAdd
sx={{
height: "iconSmall",
mr: "xxsmall",
width: "iconSmall",
}}
/>
Ajouter un contenu
</Button>
</Link>
</Flex>
<Card sx={{ position: "sticky", top: 0 }} bg="white">
<SearchFilters
initialValues={initialFilterValues}
onSearchUpdate={updateUrl}
/>
</Card>
{fetching ? (
<>chargement...</>
) : data.documents.length ? (
<form>
<DocumentList documents={data.documents} />
<DocumentsListActions onUpdatePublication={updatePublicationStatus} />
<Pagination
count={data.documents_aggregate.aggregate.count}
currentPage={initialFilterValues.page}
pageSize={initialFilterValues.itemsPerPage}
/>
</form>
) : (
<p>Pas de résultats.</p>
)}
</Stack>
);
}

DocumentListContainer.propTypes = {
initialFilterValues: PropTypes.shape({
itemsPerPage: PropTypes.number,
page: PropTypes.number,
published: PropTypes.oneOf(["all", "yes", "no"]),
q: PropTypes.string,
source: PropTypes.string,
}),
};

const searchDocumentQuery = `
query documents($source: String, $search: String!, $published: [Boolean!]!, $offset: Int = 0, $limit: Int = 50) {
documents(where: {
_not: {
document: {_has_key: "split"}
}
_and: {
source: {_eq: $source, _neq: "code_du_travail"}
title: {_ilike: $search}
is_published: {_in: $published}
}
},
offset: $offset, limit: $limit, order_by: [{source: asc}, {slug: asc}]) {
id: initial_id
cdtnId: cdtn_id
title
source
isPublished: is_published
}
documents_aggregate(where: {
_not: {
document: {_has_key: "split"}
}
_and: {
source: {_eq: $source, _neq: "code_du_travail"}
title: {_ilike: $search},
is_published: {_in: $published}
}
}) {
aggregate {
count
}
}
}
`;

const updatePublicationMutation = `
mutation publication($cdtnId:String!, $isPublished:Boolean!) {
document: update_documents_by_pk(
_set: {is_published: $isPublished}
pk_columns: { cdtn_id: $cdtnId }
) {
cdtnId:cdtn_id, isPublished:is_published
}
}
`;
121 changes: 121 additions & 0 deletions targets/frontend/src/components/documents/List.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/** @jsxImportSource theme-ui */

import { SOURCES } from "@socialgouv/cdtn-sources";
import Link from "next/link";
import PropTypes from "prop-types";
import { IoIosCheckmark, IoIosClose } from "react-icons/io";
import { useSelectionContext } from "src/pages/contenus";
import { theme } from "src/theme";
import { Box, NavLink } from "theme-ui";

export function DocumentList({ documents }) {
return (
<table>
<thead>
<tr>
<th />
<th sx={{ textAlign: "left" }}>Document</th>
<th sx={{ textAlign: "left" }}>Publié</th>
</tr>
</thead>
<tbody>
{documents.map((doc) => (
<DocumentRow key={doc.cdtnId} document={doc} />
))}
</tbody>
</table>
);
}
DocumentList.propTypes = {
documents: PropTypes.arrayOf(
PropTypes.shape({
cdtnId: PropTypes.string.isRequired,
isPublished: PropTypes.bool.isRequired,
source: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
})
).isRequired,
};

const DocumentRow = function DocumentRow({
document: { cdtnId, source, title, isPublished },
}) {
const [selectedItems, setSelectedItems] = useSelectionContext();
const updatePublishedRef = () => {
// eslint-disable-next-line no-prototype-builtins
if (selectedItems.hasOwnProperty(cdtnId)) {
delete selectedItems[cdtnId];
setSelectedItems({ ...selectedItems });
} else {
setSelectedItems({ ...selectedItems, [cdtnId]: !isPublished });
}
};

return (
<tr>
<td>
<input
name={cdtnId}
onChange={updatePublishedRef}
defaultChecked={
// eslint-disable-next-line no-prototype-builtins
selectedItems.hasOwnProperty(cdtnId) ? !isPublished : isPublished
}
sx={checkboxStyles}
type="checkbox"
/>
</td>
<td>
<Link href={sourceToRoute({ cdtnId, source })} passHref shallow>
<NavLink>
<span
sx={{
color: isPublished ? theme.colors.link : theme.colors.muted,
}}
>
{source}{title}
</span>
</NavLink>
</Link>
</td>
<td sx={{ textAlign: "center" }}>
{isPublished ? (
<Box sx={{ color: "muted" }}>
<IoIosCheckmark />
</Box>
) : (
<Box sx={{ color: "critical" }}>
<IoIosClose />
</Box>
)}
</td>
</tr>
);
};

DocumentRow.propTypes = {
document: PropTypes.shape({
cdtnId: PropTypes.string.isRequired,
isPublished: PropTypes.bool.isRequired,
source: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}).isRequired,
};

const sourceToRoute = ({ cdtnId, source }) => {
switch (source) {
case SOURCES.EDITORIAL_CONTENT:
case SOURCES.HIGHLIGHTS:
case SOURCES.PREQUALIFIED:
return `/contenus/edit/${cdtnId}`;
default:
return `/contenus/${cdtnId}`;
}
};

const checkboxStyles = {
cursor: "pointer",
display: "block",
m: "0 0 0 small",
padding: 0,
};
Loading

0 comments on commit eb3525f

Please sign in to comment.