#11 Refactor: Introduce apollo reactive variables for global state

Use it to streamline entangle the delete dialog(s) from the list(s)
This commit is contained in:
Christoph Lienhard 2020-12-30 18:26:19 +01:00
parent 42dc7f285d
commit fc8bc6724b
Signed by: christoph.lienhard
GPG key ID: 6B98870DDC270884
8 changed files with 233 additions and 181 deletions

View file

@ -5,20 +5,21 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@apollo/client": { "@apollo/client": {
"version": "3.1.3", "version": "3.3.6",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.6.tgz",
"integrity": "sha512-zXMiaj+dX0sgXIwEV5d/PI6B8SZT2bqlKNjZWcEXRY7NjESF5J3nd4v8KOsrhHe+A3YhNv63tIl35Sq7uf41Pg==", "integrity": "sha512-XSm/STyNS8aHdDigLLACKNMHwI0qaQmEHWHtTP+jHe/E1wZRnn66VZMMgwKLy2V4uHISHfmiZ4KpUKDPeJAKqg==",
"requires": { "requires": {
"@graphql-typed-document-node/core": "^3.0.0",
"@types/zen-observable": "^0.8.0", "@types/zen-observable": "^0.8.0",
"@wry/context": "^0.5.2", "@wry/context": "^0.5.2",
"@wry/equality": "^0.2.0", "@wry/equality": "^0.3.0",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
"graphql-tag": "^2.11.0", "graphql-tag": "^2.11.0",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"optimism": "^0.12.1", "optimism": "^0.13.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"symbol-observable": "^1.2.0", "symbol-observable": "^2.0.0",
"ts-invariant": "^0.4.4", "ts-invariant": "^0.6.0",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"zen-observable": "^0.8.14" "zen-observable": "^0.8.14"
} }
@ -1171,6 +1172,11 @@
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
}, },
"@graphql-typed-document-node/core": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
},
"@hapi/address": { "@hapi/address": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@ -2066,6 +2072,11 @@
"@types/jest": "*" "@types/jest": "*"
} }
}, },
"@types/ungap__global-this": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@types/ungap__global-this/-/ungap__global-this-0.3.1.tgz",
"integrity": "sha512-+/DsiV4CxXl6ZWefwHZDXSe1Slitz21tom38qPCaG0DYCS1NnDPIQDTKcmQ/tvK/edJUKkmuIDBJbmKDiB0r/g=="
},
"@types/yargs": { "@types/yargs": {
"version": "13.0.10", "version": "13.0.10",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz",
@ -2080,9 +2091,9 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
}, },
"@types/zen-observable": { "@types/zen-observable": {
"version": "0.8.0", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz",
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==" "integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "2.34.0", "version": "2.34.0",
@ -2138,6 +2149,11 @@
} }
} }
}, },
"@ungap/global-this": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@ungap/global-this/-/global-this-0.4.3.tgz",
"integrity": "sha512-MuHEpDBurNVeD6mV9xBcAN2wfTwuaFQhHuhWkJuXmyVJ5P5sBCw+nnFpdfb0tAvgWkfefWCsAoAsh7MTUr3LPg=="
},
"@webassemblyjs/ast": { "@webassemblyjs/ast": {
"version": "1.8.5", "version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@ -2297,19 +2313,33 @@
} }
}, },
"@wry/context": { "@wry/context": {
"version": "0.5.2", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.3.tgz",
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==", "integrity": "sha512-n0uKHiWpf2ArHhmcHcUsKA+Dj0gtye/h56VmsDcoMRuK/ZPFeHKi8ck5L/ftqtF12ZbQR9l8xMPV7y+xybaRDA==",
"requires": { "requires": {
"tslib": "^1.9.3" "tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
} }
}, },
"@wry/equality": { "@wry/equality": {
"version": "0.2.0", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.3.1.tgz",
"integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==", "integrity": "sha512-8/Ftr3jUZ4EXhACfSwPIfNsE8V6WKesdjp+Dxi78Bej6qlasAxiz0/F8j0miACRj9CL4vC5Y5FsfwwEYAuhWbg==",
"requires": { "requires": {
"tslib": "^1.9.3" "tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
} }
}, },
"@xtuc/ieee754": { "@xtuc/ieee754": {
@ -9607,7 +9637,6 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.3.tgz", "resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.3.tgz",
"integrity": "sha512-bRGF/eg2qNQ8BwagPLkHiqrz+W00PYtGY5Xl33I0Of1BTm7arksZO1JxssPTlti0qw127CxuWxm637ipn0eZ9g==", "integrity": "sha512-bRGF/eg2qNQ8BwagPLkHiqrz+W00PYtGY5Xl33I0Of1BTm7arksZO1JxssPTlti0qw127CxuWxm637ipn0eZ9g==",
"dev": true,
"requires": { "requires": {
"clsx": "^1.1.0", "clsx": "^1.1.0",
"hoist-non-react-statics": "^3.3.0" "hoist-non-react-statics": "^3.3.0"
@ -9909,9 +9938,9 @@
} }
}, },
"optimism": { "optimism": {
"version": "0.12.1", "version": "0.13.2",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz", "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.13.2.tgz",
"integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==", "integrity": "sha512-kJkpDUEs/Rp8HsAYYlDzyvQHlT6YZa95P+2GGNR8p/VvsIkt6NilAk7oeTvMRKCq7BeclB7+bmdIexog2859GQ==",
"requires": { "requires": {
"@wry/context": "^0.5.2" "@wry/context": "^0.5.2"
} }
@ -13378,9 +13407,9 @@
} }
}, },
"symbol-observable": { "symbol-observable": {
"version": "1.2.0", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA=="
}, },
"symbol-tree": { "symbol-tree": {
"version": "3.2.4", "version": "3.2.4",
@ -13704,10 +13733,12 @@
} }
}, },
"ts-invariant": { "ts-invariant": {
"version": "0.4.4", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.6.0.tgz",
"integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", "integrity": "sha512-caoafsfgb8QxdrKzFfjKt627m4i8KTtfAiji0DYJfWI4A/S9ORNNpzYuD9br64kyKFgxn9UNaLLbSupam84mCA==",
"requires": { "requires": {
"@types/ungap__global-this": "^0.3.1",
"@ungap/global-this": "^0.4.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },

View file

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.1.3", "@apollo/client": "^3.2",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.57", "@material-ui/lab": "^4.0.0-alpha.57",

View file

@ -19,4 +19,5 @@ const authLink = setContext((_, { headers }) => {
export const client = new ApolloClient({ export const client = new ApolloClient({
cache: new InMemoryCache(), cache: new InMemoryCache(),
link: authLink.concat(httpLink), link: authLink.concat(httpLink),
connectToDevTools: true,
}); });

View file

@ -1,7 +1,7 @@
import {Paper, Typography} from "@material-ui/core"; import {Paper, Typography} from "@material-ui/core";
import React, {useState} from "react"; import React, {useState} from "react";
import {makeStyles} from "@material-ui/core/styles"; import {makeStyles} from "@material-ui/core/styles";
import {Reference, useMutation, useQuery} from "@apollo/client"; import {useMutation, useQuery} from "@apollo/client";
import AddCard from "./AddCard"; import AddCard from "./AddCard";
import AccordionWithEdit from "./AccordionWithEdit"; import AccordionWithEdit from "./AccordionWithEdit";
import { import {
@ -12,18 +12,19 @@ import {
} from "../backend/queries/category"; } from "../backend/queries/category";
import ChangeCategoryDialog, {ChangeCategoryDialogContent} from "./ChangeCategoryDialog"; import ChangeCategoryDialog, {ChangeCategoryDialogContent} from "./ChangeCategoryDialog";
import {useSnackbar} from "notistack"; import {useSnackbar} from "notistack";
import DeleteConfirmationDialog, {DeleteConfirmationDialogContent} from "./DeleteConfirmationDialog";
import { import {
ADD_CATEGORY, ADD_CATEGORY,
AddCategoryResponse, AddCategoryResponse,
AddCategoryVariables, AddCategoryVariables,
DELETE_CATEGORY,
DeleteCategoryResponse,
DeleteCategoryVariables,
EDIT_CATEGORY, EDIT_CATEGORY,
EditCategoryResponse, EditCategoryResponse,
EditCategoryVariables EditCategoryVariables
} from "../backend/mutations/category"; } from "../backend/mutations/category";
import DeleteCategoryDialog, {
deleteCategoryDialogId,
deleteCategoryDialogOpen,
deleteCategoryDialogTitle
} from "./DeleteCategoryDialog";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@ -39,19 +40,11 @@ const emptyChangeCategoryDialog: ChangeCategoryDialogContent = {
details: "", details: "",
} }
const emptyDeleteConfirmationDialogContent: DeleteConfirmationDialogContent = {
id: "",
type: "Kategorie",
title: "",
}
export default function CategoryList() { export default function CategoryList() {
const [changeDialogOpen, setChangeDialogOpen] = useState(false); const [changeDialogOpen, setChangeDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [dialogTitle, setDialogTitle] = useState(""); const [dialogTitle, setDialogTitle] = useState("");
const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState(""); const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState("");
const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeCategoryDialog); const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeCategoryDialog);
const [deleteDialogContent, setDeleteDialogContent] = useState(emptyDeleteConfirmationDialogContent);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const categories = useQuery<GetAllCategoriesResponse, null>(GET_ALL_CATEGORIES).data?.allCategories.nodes; const categories = useQuery<GetAllCategoriesResponse, null>(GET_ALL_CATEGORIES).data?.allCategories.nodes;
const [editCategory, {loading: editLoading}] = useMutation<EditCategoryResponse, EditCategoryVariables>(EDIT_CATEGORY, { const [editCategory, {loading: editLoading}] = useMutation<EditCategoryResponse, EditCategoryVariables>(EDIT_CATEGORY, {
@ -90,28 +83,7 @@ export default function CategoryList() {
}); });
} }
}); });
const [deleteCategory, {loading: deleteLoading}] = useMutation<DeleteCategoryResponse, DeleteCategoryVariables>(DELETE_CATEGORY, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}),
onCompleted: (response) => {
if (response.deleteCategory) {
enqueueSnackbar("Kategorie erfolgreich gelöscht.", { variant: "success"})
setDeleteDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"})
}
},
update: (cache, { data }) => {
const idToRemove = data?.deleteCategory?.category.id;
cache.modify({
fields: {
allCategories(existingCategoriesRef: { nodes: Array<Reference>} = { nodes: []}, {readField}) {
console.log("existingCategory: ", existingCategoriesRef)
return {nodes: existingCategoriesRef.nodes.filter(categoryRef => readField('id', categoryRef) !== idToRemove)};
}
}
});
}
});
const classes = useStyles(); const classes = useStyles();
const loading = editLoading || addLoading; const loading = editLoading || addLoading;
@ -139,12 +111,9 @@ export default function CategoryList() {
}; };
const handleDeleteButtonClick = (category: BasicCategoryResponse) => { const handleDeleteButtonClick = (category: BasicCategoryResponse) => {
setDeleteDialogContent({ deleteCategoryDialogTitle(category.title);
...deleteDialogContent, deleteCategoryDialogId(category.id);
id: category.id, deleteCategoryDialogOpen(true);
title: category.title,
});
setDeleteDialogOpen(true);
} }
const handleDialogContentChange = (content: ChangeCategoryDialogContent) => { const handleDialogContentChange = (content: ChangeCategoryDialogContent) => {
@ -170,14 +139,6 @@ export default function CategoryList() {
} }
}; };
const handleDeleteConfirmButtonClick = () => {
deleteCategory({
variables: {
id: deleteDialogContent.id
}
})
};
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<Typography component={"h2"} variant="h6" color="primary" gutterBottom>Kategorien</Typography> <Typography component={"h2"} variant="h6" color="primary" gutterBottom>Kategorien</Typography>
@ -200,13 +161,7 @@ export default function CategoryList() {
handleConfirmButtonClick={handleChangeConfirmButtonClick} handleConfirmButtonClick={handleChangeConfirmButtonClick}
handleClose={() => setChangeDialogOpen(false)} handleClose={() => setChangeDialogOpen(false)}
/> />
<DeleteConfirmationDialog <DeleteCategoryDialog/>
content={deleteDialogContent}
open={deleteDialogOpen}
loading={deleteLoading}
handleConfirmButtonClick={handleDeleteConfirmButtonClick}
handleClose={() => setDeleteDialogOpen(false)}
/>
</Paper> </Paper>
) )
} }

View file

@ -0,0 +1,60 @@
import React from 'react';
import {makeVar, Reference, useMutation, useReactiveVar} from "@apollo/client";
import DeleteConfirmationDialog from "./DeleteConfirmationDialog";
import {useSnackbar} from "notistack";
import {DELETE_CATEGORY, DeleteCategoryResponse, DeleteCategoryVariables} from "../backend/mutations/category";
export const deleteCategoryDialogId = makeVar<string>("");
export const deleteCategoryDialogTitle = makeVar<string>("");
export const deleteCategoryDialogOpen = makeVar<boolean>(false);
export default function DeleteCategoryDialog() {
const {enqueueSnackbar} = useSnackbar();
const [deleteCategory, {loading}] = useMutation<DeleteCategoryResponse, DeleteCategoryVariables>(DELETE_CATEGORY, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}),
onCompleted: (response) => {
if (response.deleteCategory) {
enqueueSnackbar("Kategorie erfolgreich gelöscht.", {variant: "success"})
deleteCategoryDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"})
}
},
update: (cache, {data}) => {
const idToRemove = data?.deleteCategory?.category.id;
cache.modify({
fields: {
allCategories(existingCategoriesRef: { nodes: Array<Reference> } = {nodes: []}, {readField}) {
console.log("existingCategory: ", existingCategoriesRef)
return {nodes: existingCategoriesRef.nodes.filter(categoryRef => readField('id', categoryRef) !== idToRemove)};
}
}
});
}
});
const open = useReactiveVar(deleteCategoryDialogOpen);
const title = useReactiveVar(deleteCategoryDialogTitle);
const id = useReactiveVar(deleteCategoryDialogId);
const handleConfirmButtonClick = () => {
deleteCategory({
variables: {
id
}
})
}
return (
<DeleteConfirmationDialog
open={open}
type={"Kategorie"}
title={title}
onConfirmButtonClick={handleConfirmButtonClick}
onClose={() => deleteCategoryDialogOpen(false)}
loading={loading}
/>
);
}

View file

@ -6,43 +6,38 @@ import {Button, DialogActions, DialogContentText} from "@material-ui/core";
import ButtonWithSpinner from "./ButtonWithSpinner"; import ButtonWithSpinner from "./ButtonWithSpinner";
export interface DeleteConfirmationDialogContent { interface DeleteConfirmationDialogProps {
id: string, open: boolean,
type: string, type: string,
title: string, title: string,
}
interface DeleteConfirmationDialogProps {
content: DeleteConfirmationDialogContent,
open: boolean,
loading?: boolean, loading?: boolean,
handleConfirmButtonClick(): void, onConfirmButtonClick(): void,
handleClose(): void, onClose(): void,
} }
export default function DeleteConfirmationDialog(props: DeleteConfirmationDialogProps) { export default function DeleteConfirmationDialog(props: DeleteConfirmationDialogProps) {
return ( return (
<Dialog <Dialog
open={props.open} open={props.open}
onClose={props.handleClose} onClose={props.onClose}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title">{props.content.type} löschen?</DialogTitle> <DialogTitle id="alert-dialog-title">{props.type} löschen?</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Möchten Sie die {props.content.type} Möchten Sie die {props.type}
"{props.content.title}" "{props.title}"
wirklich löschen? wirklich löschen?
</DialogContentText> </DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={props.handleClose} color="primary"> <Button onClick={props.onClose} color="primary">
Abbrechen Abbrechen
</Button> </Button>
<ButtonWithSpinner onClick={props.handleConfirmButtonClick} autoFocus loading={props.loading}> <ButtonWithSpinner onClick={props.onConfirmButtonClick} autoFocus loading={props.loading}>
Löschen Löschen
</ButtonWithSpinner> </ButtonWithSpinner>
</DialogActions> </DialogActions>

View file

@ -0,0 +1,59 @@
import React from 'react';
import {makeVar, Reference, useMutation, useReactiveVar} from "@apollo/client";
import DeleteConfirmationDialog from "./DeleteConfirmationDialog";
import {DELETE_QUESTION, DeleteQuestionResponse, DeleteQuestionVariables} from "../backend/mutations/question";
import {useSnackbar} from "notistack";
export const deleteQuestionDialogId = makeVar<string>("");
export const deleteQuestionDialogTitle = makeVar<string>("");
export const deleteQuestionDialogOpen = makeVar<boolean>(false);
export default function DeleteQuestionDialog() {
const {enqueueSnackbar} = useSnackbar();
const [deleteQuestion, {loading}] = useMutation<DeleteQuestionResponse, DeleteQuestionVariables>(DELETE_QUESTION, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}),
onCompleted: (response) => {
if (response.deleteQuestion) {
enqueueSnackbar("Frage erfolgreich gelöscht.", {variant: "success"})
deleteQuestionDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"})
}
},
update: (cache, {data}) => {
const idToRemove = data?.deleteQuestion?.question.id;
cache.modify({
fields: {
allQuestions(existingQuestionsRef: { nodes: Array<Reference> } = {nodes: []}, {readField}) {
return {nodes: existingQuestionsRef.nodes.filter(questionRef => readField('id', questionRef) !== idToRemove)};
}
}
});
}
});
const open = useReactiveVar(deleteQuestionDialogOpen);
const title = useReactiveVar(deleteQuestionDialogTitle);
const id = useReactiveVar(deleteQuestionDialogId);
const handleConfirmButtonClick = () => {
deleteQuestion({
variables: {
id
}
})
}
return (
<DeleteConfirmationDialog
open={open}
type={"Frage"}
title={title}
onConfirmButtonClick={handleConfirmButtonClick}
onClose={() => deleteQuestionDialogOpen(false)}
loading={loading}
/>
);
}

View file

@ -1,7 +1,7 @@
import {Paper, Typography} from "@material-ui/core"; import {Paper, Typography} from "@material-ui/core";
import React, {useState} from "react"; import React, {useState} from "react";
import {makeStyles} from "@material-ui/core/styles"; import {makeStyles} from "@material-ui/core/styles";
import {Reference, useMutation, useQuery} from "@apollo/client"; import {useMutation, useQuery} from "@apollo/client";
import AddCard from "./AddCard"; import AddCard from "./AddCard";
import AccordionWithEdit from "./AccordionWithEdit"; import AccordionWithEdit from "./AccordionWithEdit";
import { import {
@ -16,15 +16,12 @@ import {
ADD_QUESTION, ADD_QUESTION,
AddQuestionResponse, AddQuestionResponse,
AddQuestionVariables, AddQuestionVariables,
DELETE_QUESTION,
DeleteQuestionResponse,
DeleteQuestionVariables,
EDIT_QUESTION, EDIT_QUESTION,
EditQuestionResponse, EditQuestionResponse,
EditQuestionVariables EditQuestionVariables
} from "../backend/mutations/question"; } from "../backend/mutations/question";
import {useSnackbar} from 'notistack'; import {useSnackbar} from 'notistack';
import DeleteConfirmationDialog, {DeleteConfirmationDialogContent} from "./DeleteConfirmationDialog"; import DeleteQuestionDialog, {deleteQuestionDialogId, deleteQuestionDialogOpen, deleteQuestionDialogTitle} from "./DeleteQuestionDialog";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@ -41,47 +38,39 @@ const emptyChangeQuestionDialog: ChangeQuestionDialogContent = {
categoryId: null, categoryId: null,
} }
const emptyDeleteConfirmationDialogContent: DeleteConfirmationDialogContent = {
id: "",
type: "Frage",
title: "",
}
export default function QuestionList() { export default function QuestionList() {
const [changeDialogOpen, setChangeDialogOpen] = useState(false); const [changeDialogOpen, setChangeDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [dialogTitle, setDialogTitle] = useState(""); const [dialogTitle, setDialogTitle] = useState("");
const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState(""); const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState("");
const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeQuestionDialog); const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeQuestionDialog);
const [deleteDialogContent, setDeleteDialogContent] = useState(emptyDeleteConfirmationDialogContent); const {enqueueSnackbar} = useSnackbar();
const { enqueueSnackbar } = useSnackbar();
const questions = useQuery<GetAllQuestionsResponse, null>(GET_ALL_QUESTIONS).data?.allQuestions.nodes; const questions = useQuery<GetAllQuestionsResponse, null>(GET_ALL_QUESTIONS).data?.allQuestions.nodes;
const categories = useQuery<GetAllCategoriesResponse, null>(GET_ALL_CATEGORIES).data?.allCategories.nodes; const categories = useQuery<GetAllCategoriesResponse, null>(GET_ALL_CATEGORIES).data?.allCategories.nodes;
const [editQuestion, {loading: editLoading}] = useMutation<EditQuestionResponse, EditQuestionVariables>(EDIT_QUESTION, { const [editQuestion, {loading: editLoading}] = useMutation<EditQuestionResponse, EditQuestionVariables>(EDIT_QUESTION, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}), onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}),
onCompleted: (response) => {
if (response.updateQuestion) {
enqueueSnackbar("Frage erfolgreich geändert.", { variant: "success"})
setChangeDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"})
}
}
});
const [addQuestion, {loading: addLoading}] = useMutation<AddQuestionResponse, AddQuestionVariables>(ADD_QUESTION, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}),
onCompleted: (response) => { onCompleted: (response) => {
if (response.createQuestion) { if (response.updateQuestion) {
enqueueSnackbar("Frage erfolgreich hinzugefügt.", { variant: "success"}) enqueueSnackbar("Frage erfolgreich geändert.", {variant: "success"})
setChangeDialogOpen(false); setChangeDialogOpen(false);
} else { } else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"}) enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"})
}
}
});
const [addQuestion, {loading: addLoading}] = useMutation<AddQuestionResponse, AddQuestionVariables>(ADD_QUESTION, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}),
onCompleted: (response) => {
if (response.createQuestion) {
enqueueSnackbar("Frage erfolgreich hinzugefügt.", {variant: "success"})
setChangeDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"})
} }
}, },
update: (cache, { data }) => { update: (cache, {data}) => {
cache.modify({ cache.modify({
fields: { fields: {
allQuestions(existingQuestions = { nodes: []}) { allQuestions(existingQuestions = {nodes: []}) {
const newQuestionRef = cache.writeFragment<BasicQuestionResponse | undefined>({ const newQuestionRef = cache.writeFragment<BasicQuestionResponse | undefined>({
data: data?.createQuestion?.question, data: data?.createQuestion?.question,
fragment: BasicQuestionFragment, fragment: BasicQuestionFragment,
@ -93,27 +82,6 @@ export default function QuestionList() {
}); });
} }
}); });
const [deleteQuestion, {loading: deleteLoading}] = useMutation<DeleteQuestionResponse, DeleteQuestionVariables>(DELETE_QUESTION, {
onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}),
onCompleted: (response) => {
if (response.deleteQuestion) {
enqueueSnackbar("Frage erfolgreich gelöscht.", { variant: "success"})
setDeleteDialogOpen(false);
} else {
enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"})
}
},
update: (cache, { data }) => {
const idToRemove = data?.deleteQuestion?.question.id;
cache.modify({
fields: {
allQuestions(existingQuestionsRef: { nodes: Array<Reference>} = { nodes: []}, {readField}) {
return {nodes: existingQuestionsRef.nodes.filter(questionRef => readField('id', questionRef) !== idToRemove)};
}
}
});
}
});
const classes = useStyles(); const classes = useStyles();
const loading = editLoading || addLoading; const loading = editLoading || addLoading;
@ -142,12 +110,9 @@ export default function QuestionList() {
}; };
const handleDeleteButtonClick = (question: BasicQuestionResponse) => { const handleDeleteButtonClick = (question: BasicQuestionResponse) => {
setDeleteDialogContent({ deleteQuestionDialogTitle(question.title);
...deleteDialogContent, deleteQuestionDialogId(question.id);
id: question.id, deleteQuestionDialogOpen(true);
title: question.title,
});
setDeleteDialogOpen(true);
} }
const handleDialogContentChange = (content: ChangeQuestionDialogContent) => { const handleDialogContentChange = (content: ChangeQuestionDialogContent) => {
@ -175,25 +140,17 @@ export default function QuestionList() {
} }
}; };
const handleDeleteConfirmButtonClick = () => {
deleteQuestion({
variables: {
id: deleteDialogContent.id
}
})
};
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<Typography component={"h2"} variant="h6" color="primary" gutterBottom>Fragen</Typography> <Typography component={"h2"} variant="h6" color="primary" gutterBottom>Fragen</Typography>
{questions?.map(question => <AccordionWithEdit {questions?.map(question => <AccordionWithEdit
key={question.id} key={question.id}
title={question.title} title={question.title}
subTitle={question.categoryByCategoryRowId?.title} subTitle={question.categoryByCategoryRowId?.title}
description={question.description} description={question.description}
onEditButtonClick={() => handleEditButtonClick(question)} onEditButtonClick={() => handleEditButtonClick(question)}
onDeleteButtonClick={() => handleDeleteButtonClick(question)} onDeleteButtonClick={() => handleDeleteButtonClick(question)}
/> />
)} )}
<AddCard handleClick={handleAddClick}/> <AddCard handleClick={handleAddClick}/>
<ChangeQuestionDialog <ChangeQuestionDialog
@ -207,13 +164,7 @@ export default function QuestionList() {
handleConfirmButtonClick={handleChangeConfirmButtonClick} handleConfirmButtonClick={handleChangeConfirmButtonClick}
handleClose={() => setChangeDialogOpen(false)} handleClose={() => setChangeDialogOpen(false)}
/> />
<DeleteConfirmationDialog <DeleteQuestionDialog/>
content={deleteDialogContent}
open={deleteDialogOpen}
loading={deleteLoading}
handleConfirmButtonClick={handleDeleteConfirmButtonClick}
handleClose={() => setDeleteDialogOpen(false)}
/>
</Paper> </Paper>
) )
} }