diff --git a/src/actions/posts.js b/src/actions/posts.js index 2c0cd2d521f8bc5bd1b299b50461e533e3d69d47..c3233771302b52b769290377a681e96566c1d454 100644 --- a/src/actions/posts.js +++ b/src/actions/posts.js @@ -208,9 +208,10 @@ export const archive = createAsyncAction( * @param {CF2021.ProposalPost} proposal * @param {CF2021.ProposalPostState} state */ -const updateProposalState = async (proposal, state) => { +const updateProposalState = async (proposal, state, additionalPayload) => { const body = JSON.stringify({ state: postsStateMappingRev[state], + ...(additionalPayload || {}), }); await fetch(`/posts/${proposal.id}`, { method: "PUT", @@ -277,16 +278,16 @@ export const rejectProposal = createAsyncAction( /** * @param {CF2021.ProposalPost} proposal */ - (proposal) => { - return updateProposalState(proposal, "rejected"); + ({ proposal, archive }) => { + return updateProposalState(proposal, "rejected", { is_archived: archive }); }, { shortCircuitHook: ({ args }) => { - if (args.type !== "procedure-proposal") { + if (args.proposal.type !== "procedure-proposal") { return errorResult(); } - if (args.state !== "announced") { + if (args.proposal.state !== "announced") { return errorResult(); } @@ -302,16 +303,18 @@ export const rejectProposalByChairman = createAsyncAction( /** * @param {CF2021.ProposalPost} proposal */ - (proposal) => { - return updateProposalState(proposal, "rejected-by-chairman"); + ({ proposal, archive }) => { + return updateProposalState(proposal, "rejected-by-chairman", { + is_archived: archive, + }); }, { shortCircuitHook: ({ args }) => { - if (args.type !== "procedure-proposal") { + if (args.proposal.type !== "procedure-proposal") { return errorResult(); } - if (!["pending", "announced"].includes(args.state)) { + if (!["pending", "announced"].includes(args.proposal.state)) { return errorResult(); } diff --git a/src/components/modals/ModalConfirm.jsx b/src/components/modals/ModalConfirm.jsx index cfe8d26e1ae800e43bdc94a27122247d2cda665e..8b4d876067a5c9642a891a19c8b87073ce17d03a 100644 --- a/src/components/modals/ModalConfirm.jsx +++ b/src/components/modals/ModalConfirm.jsx @@ -1,16 +1,8 @@ import React from "react"; import Button from "components/Button"; -import { - Card, - CardActions, - CardBody, - CardBodyText, - CardHeadline, -} from "components/cards"; -import ErrorMessage from "components/ErrorMessage"; -import Modal from "./Modal"; +import ModalWithActions from "./ModalWithActions"; const ModalConfirm = ({ title, @@ -23,44 +15,39 @@ const ModalConfirm = ({ error, ...props }) => { + const actions = ( + <> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={onConfirm} + loading={confirming} + > + {yesActionLabel} + </Button> + <Button + hoverActive + color="grey-125" + className="text-sm" + onClick={onCancel} + > + {cancelActionLabel} + </Button> + </> + ); + return ( - <Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}> - <Card> - <CardBody> - <div className="flex items-center justify-between mb-4"> - <CardHeadline>{title}</CardHeadline> - <button onClick={onCancel} type="button"> - <i className="ico--cross"></i> - </button> - </div> - <CardBodyText>{children}</CardBodyText> - {error && ( - <ErrorMessage className="mt-2"> - PĹ™i provádÄ›nĂ akce došlo k problĂ©mu: {error} - </ErrorMessage> - )} - </CardBody> - <CardActions right className="space-x-1"> - <Button - hoverActive - color="blue-300" - className="text-sm" - onClick={onConfirm} - loading={confirming} - > - {yesActionLabel} - </Button> - <Button - hoverActive - color="red-600" - className="text-sm" - onClick={onCancel} - > - {cancelActionLabel} - </Button> - </CardActions> - </Card> - </Modal> + <ModalWithActions + onClose={onCancel} + title={title} + error={error} + actions={actions} + containerClassName="max-w-md" + {...props} + > + {children} + </ModalWithActions> ); }; diff --git a/src/components/modals/ModalWithActions.jsx b/src/components/modals/ModalWithActions.jsx new file mode 100644 index 0000000000000000000000000000000000000000..242fd676a5bec498d1ea215aee4cd3df11ee1cc8 --- /dev/null +++ b/src/components/modals/ModalWithActions.jsx @@ -0,0 +1,47 @@ +import React from "react"; + +import { + Card, + CardActions, + CardBody, + CardBodyText, + CardHeadline, +} from "components/cards"; +import ErrorMessage from "components/ErrorMessage"; + +import Modal from "./Modal"; + +const ModalConfirm = ({ + title, + children, + actions, + error, + onClose, + ...props +}) => { + return ( + <Modal onRequestClose={onClose} {...props}> + <Card> + <CardBody> + <div className="flex items-center justify-between mb-4"> + <CardHeadline>{title}</CardHeadline> + <button onClick={onClose} type="button"> + <i className="ico--cross"></i> + </button> + </div> + <CardBodyText>{children}</CardBodyText> + {error && ( + <ErrorMessage className="mt-2"> + PĹ™i provádÄ›nĂ akce došlo k problĂ©mu: {error} + </ErrorMessage> + )} + </CardBody> + <CardActions right className="space-x-1"> + {actions} + </CardActions> + </Card> + </Modal> + ); +}; + +export default React.memo(ModalConfirm); diff --git a/src/components/posts/RejectPostModalConfirm.jsx b/src/components/posts/RejectPostModalConfirm.jsx new file mode 100644 index 0000000000000000000000000000000000000000..51f1e3ec9f58fd3ec251c7abaa60be2162dacca6 --- /dev/null +++ b/src/components/posts/RejectPostModalConfirm.jsx @@ -0,0 +1,78 @@ +import React from "react"; + +import Button from "components/Button"; +import { + Card, + CardActions, + CardBody, + CardBodyText, + CardHeadline, +} from "components/cards"; +import ErrorMessage from "components/ErrorMessage"; + +import Modal from "./Modal"; + +const RejectPostModalConfirm = ({ + title, + children, + yesActionLabel = "OK", + yesAndArchiveActionLabel = "OK", + cancelActionLabel = "Zrušit", + onCancel, + onConfirm, + onConfirmAndArchive, + confirming, + error, + ...props +}) => { + return ( + <Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}> + <Card> + <CardBody> + <div className="flex items-center justify-between mb-4"> + <CardHeadline>{title}</CardHeadline> + <button onClick={onCancel} type="button"> + <i className="ico--cross"></i> + </button> + </div> + <CardBodyText>{children}</CardBodyText> + {error && ( + <ErrorMessage className="mt-2"> + PĹ™i provádÄ›nĂ akce došlo k problĂ©mu: {error} + </ErrorMessage> + )} + </CardBody> + <CardActions right className="space-x-1"> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={onConfirm} + loading={confirming} + > + {yesActionLabel} + </Button> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={onConfirm} + loading={confirming} + > + {yesAndArchiveActionLabel} + </Button> + <Button + hoverActive + color="red-600" + className="text-sm" + onClick={onCancel} + > + {cancelActionLabel} + </Button> + </CardActions> + </Card> + </Modal> + ); +}; + +export default React.memo(RejectPostModalConfirm); diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx index daf2a39ccb9609dac229d5f8ed9122ddca04b299..5daa03d0479e6388d69f5460919c7b4a920602d8 100644 --- a/src/containers/PostsContainer.jsx +++ b/src/containers/PostsContainer.jsx @@ -15,8 +15,10 @@ import { rejectProposalByChairman, } from "actions/posts"; import { ban, unban } from "actions/users"; +import Button from "components/Button"; import ErrorMessage from "components/ErrorMessage"; import ModalConfirm from "components/modals/ModalConfirm"; +import ModalWithActions from "components/modals/ModalWithActions"; import PostEditModal from "components/posts/PostEditModal"; import PostList from "components/posts/PostList"; import { useActionState, useItemActionConfirm } from "hooks"; @@ -68,13 +70,19 @@ const PostsContainer = ({ className, showAddPostCta }) => { setPostToReject, onRejectConfirm, onRejectCancel, - ] = useItemActionConfirm(rejectProposal); + ] = useItemActionConfirm(rejectProposal, (item, archive) => ({ + proposal: item, + archive, + })); const [ postToRejectByChairman, setPostToRejectByChairman, onRejectByChairmanConfirm, onRejectByChairmanCancel, - ] = useItemActionConfirm(rejectProposalByChairman); + ] = useItemActionConfirm(rejectProposalByChairman, (item, archive) => ({ + proposal: item, + archive, + })); const { isAuthenticated, user } = AuthStore.useState(); const { window, items } = PostStore.useState((state) => @@ -104,12 +112,29 @@ const PostsContainer = ({ className, showAddPostCta }) => { ); const [rejectingProposal, rejectingProposalError] = useActionState( rejectProposal, - postToReject + { + proposal: postToReject, + archive: false, + } ); + const [ + rejectingAndArchivingProposal, + rejectingAndArchivingProposalError, + ] = useActionState(rejectProposal, { proposal: postToReject, archive: true }); const [ rejectingProposalByChairman, rejectingProposalByChairmanError, - ] = useActionState(rejectProposalByChairman, postToRejectByChairman); + ] = useActionState(rejectProposalByChairman, { + proposal: postToRejectByChairman, + archive: false, + }); + const [ + rejectingProposalByChairmanAndArchiving, + rejectingProposalByChairmanAndArchivingError, + ] = useActionState(rejectProposalByChairman, { + proposal: postToRejectByChairman, + archive: true, + }); const { 2: loadResult } = loadPosts.useWatch(); @@ -130,7 +155,7 @@ const PostsContainer = ({ className, showAddPostCta }) => { setConfirmingEdit(false); } }, - [postToEdit, setPostToEdit] + [postToEdit, setPostToEdit, setConfirmingEdit] ); const cancelEdit = useCallback(() => { @@ -253,29 +278,88 @@ const PostsContainer = ({ className, showAddPostCta }) => { > ProcedurálnĂ návrh bude <strong>schválen</strong>. Opravdu to chcete? </ModalConfirm> - <ModalConfirm + <ModalWithActions isOpen={!!postToReject} - onConfirm={onRejectConfirm} onCancel={onRejectCancel} - confirming={rejectingProposal} - error={rejectingProposalError} + error={rejectingProposalError || rejectingAndArchivingProposalError} title="ZamĂtnout procedurálnĂ návrh?" - yesActionLabel="ZamĂtnout návrh" + containerClassName="max-w-lg" + actions={ + <> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={() => onRejectConfirm(false)} + loading={rejectingProposal} + > + ZamĂtnout + </Button> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={() => onRejectConfirm(true)} + loading={rejectingAndArchivingProposal} + > + ZamĂtnout a archivovat + </Button> + <Button + hoverActive + color="grey-125" + className="text-sm" + onClick={onRejectCancel} + > + Zrušit + </Button> + </> + } > ProcedurálnĂ návrh bude <strong>zamĂtnut</strong>. Opravdu to chcete? - </ModalConfirm> - <ModalConfirm + </ModalWithActions> + <ModalWithActions isOpen={!!postToRejectByChairman} - onConfirm={onRejectByChairmanConfirm} onCancel={onRejectByChairmanCancel} - confirming={rejectingProposalByChairman} - error={rejectingProposalByChairmanError} + error={ + rejectingProposalByChairmanError || + rejectingProposalByChairmanAndArchivingError + } title="ZamĂtnout procedurálnĂ návrh pĹ™edsedajĂcĂcm?" - yesActionLabel="ZamĂtnout návrh pĹ™edsedajĂcĂm" + containerClassName="max-w-lg" + actions={ + <> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={() => onRejectByChairmanConfirm(false)} + loading={rejectingProposalByChairman} + > + ZamĂtnout + </Button> + <Button + hoverActive + color="blue-300" + className="text-sm" + onClick={() => onRejectByChairmanConfirm(true)} + loading={rejectingProposalByChairmanAndArchiving} + > + ZamĂtnout a archivovat + </Button> + <Button + hoverActive + color="grey-125" + className="text-sm" + onClick={onRejectCancel} + > + Zrušit + </Button> + </> + } > ProcedurálnĂ návrh bude <strong>zamĂtnut pĹ™edsedajĂcĂm</strong>. Opravdu to chcete? - </ModalConfirm> + </ModalWithActions> {postToEdit && ( <PostEditModal isOpen={true} diff --git a/src/hooks.js b/src/hooks.js index 7573be066410e2f17db501f4950614af9c577834..648d410a509a226d729052f04bd2296cc194e38e 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -1,17 +1,26 @@ import { useCallback, useState } from "react"; -export const useItemActionConfirm = (actionFn) => { +const baseActionParamsBuilder = (item, args) => { + return item; +}; + +export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => { const [item, setItem] = useState(null); - const onActionConfirm = useCallback(async () => { - if (item) { - const result = await actionFn.run(item); + const onActionConfirm = useCallback( + async (args) => { + if (item) { + const result = await actionFn.run( + (actionParamsBuilder || baseActionParamsBuilder)(item, args) + ); - if (!result.error) { - setItem(null); + if (!result.error) { + setItem(null); + } } - } - }, [item, setItem, actionFn]); + }, + [item, setItem, actionFn, actionParamsBuilder] + ); const onActionCancel = useCallback(() => { setItem(null);