diff --git a/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx b/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx index dc13e1c..ef3a689 100644 --- a/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx +++ b/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx @@ -18,6 +18,9 @@ import { SingleOptionListQuery, SingleOptionListQueryVariables, } from '#generated/types'; +import { + type ProjectScope, +} from '#utils/common'; import styles from './index.module.css'; @@ -43,7 +46,7 @@ const SINGLE_OPTION_LIST = gql` } `; -type ChoiceType = NonNullable['projectScope']>['choiceCollection']>['choices']>[number]; +type ChoiceType = NonNullable['choiceCollection']>['choices']>[number]; const choiceCollectionKeySelector = (d: ChoiceType) => d.id; const choiceCollectionLabelSelector = (d: ChoiceType) => d.label; diff --git a/src/utils/common.ts b/src/utils/common.ts index 12116ce..817a778 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -77,3 +77,7 @@ export const enumKeySelector = (d: EnumEntity) => ( export const enumLabelSelector = (d: EnumEntity) => ( d.description ?? capitalize(String(d.name)) ); + +export type ProjectScope = T extends { + private ?: { projectScope?: infer X } +} ? NonNullable : never; diff --git a/src/views/QuestionnaireEdit/QuestionList/index.tsx b/src/views/QuestionnaireEdit/QuestionList/index.tsx index b713d32..6c1e026 100644 --- a/src/views/QuestionnaireEdit/QuestionList/index.tsx +++ b/src/views/QuestionnaireEdit/QuestionList/index.tsx @@ -31,6 +31,7 @@ import { import { TocItem, getChildren, + type ProjectScope, } from '#utils/common'; import QuestionPreview from '../QuestionPreview'; @@ -125,7 +126,7 @@ const UPDATE_QUESTIONS_VISIBILITY = gql` } `; -type Question = NonNullable['projectScope']>['questions']>['items']>[number]; +type Question = NonNullable['questions']>['items']>[number]; const questionKeySelector = (q: Question) => q.id; @@ -185,6 +186,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { const { loading: questionsPending, + refetch: retriggerQuestionsFetch, } = useQuery( QUESTIONS_FOR_LEAF_GROUP, { @@ -308,6 +310,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { selectedQuestions, onSelectedQuestionsChange: handleSelectedQuestionsChange, setSelectedLeafGroupId, + refetchQuestionList: retriggerQuestionsFetch, }), [ onEditQuestionClick, projectId, @@ -316,6 +319,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { selectedQuestions, handleSelectedQuestionsChange, setSelectedLeafGroupId, + retriggerQuestionsFetch, ]); return ( diff --git a/src/views/QuestionnaireEdit/QuestionPreview/index.tsx b/src/views/QuestionnaireEdit/QuestionPreview/index.tsx index 4528f59..eb723a6 100644 --- a/src/views/QuestionnaireEdit/QuestionPreview/index.tsx +++ b/src/views/QuestionnaireEdit/QuestionPreview/index.tsx @@ -5,6 +5,10 @@ import { import { GrDrag, } from 'react-icons/gr'; +import { + gql, + useMutation, +} from '@apollo/client'; import { isNotDefined, isDefined, @@ -15,11 +19,10 @@ import { QuickActionDropdownMenu, QuickActionButton, DropdownMenuItem, + useAlert, + useConfirmation, } from '@the-deep/deep-ui'; -import { - QuestionsByGroupQuery, -} from '#generated/types'; import TextQuestionPreview from '#components/questionPreviews/TextQuestionPreview'; import IntegerQuestionPreview from '#components/questionPreviews/IntegerQuestionPreview'; import RankQuestionPreview from '#components/questionPreviews/RankQuestionPreview'; @@ -31,11 +34,40 @@ import FileQuestionPreview from '#components/questionPreviews/FileQuestionPrevie import SelectOneQuestionPreview from '#components/questionPreviews/SelectOneQuestionPreview'; import SelectMultipleQuestionPreview from '#components/questionPreviews/SelectMultipleQuestionPreview'; import { Attributes, Listeners } from '#components/SortableList'; +import { + QuestionsByGroupQuery, + DeleteQuestionMutation, + DeleteQuestionMutationVariables, +} from '#generated/types'; + +import { + QUESTION_FRAGMENT, +} from '../queries'; import styles from './index.module.css'; type Question = NonNullable['projectScope']>['questions']>['items']>[number]; +const DELETE_QUESTION = gql` + ${QUESTION_FRAGMENT} + mutation DeleteQuestion ( + $projectId: ID!, + $questionId: ID!, + ) { + private { + projectScope(pk: $projectId) { + deleteQuestion(id: $questionId) { + errors + ok + result { + ...QuestionResponse + } + } + } + } + } +`; + interface QuestionProps { question: Question; showAddQuestionPane: (val?: string | undefined) => void; @@ -45,6 +77,7 @@ interface QuestionProps { selectedQuestions: string[] | undefined; onSelectedQuestionsChange: (val: boolean, id: string) => void; setSelectedLeafGroupId : React.Dispatch>; + refetchQuestionList: () => void; attributes?: Attributes; listeners?: Listeners; } @@ -59,10 +92,40 @@ function QuestionPreview(props: QuestionProps) { onSelectedQuestionsChange, setSelectedLeafGroupId, projectId, + refetchQuestionList, attributes, listeners, } = props; + const alert = useAlert(); + + const [ + triggerQuestionDelete, + ] = useMutation( + DELETE_QUESTION, + { + onCompleted: (res) => { + const questionResponse = res.private.projectScope?.deleteQuestion; + if (!questionResponse?.ok) { + alert.show( + 'Failed to delete question.', + { variant: 'error' }, + ); + } + alert.show( + 'Successfully deleted question.', + { variant: 'success' }, + ); + }, + onError: () => { + alert.show( + 'Failed to delete question.', + { variant: 'error' }, + ); + }, + }, + ); + const handleEditQuestionClick = useCallback((val: string) => { if (isNotDefined(question.leafGroupId)) { return; @@ -79,6 +142,33 @@ function QuestionPreview(props: QuestionProps) { question, ]); + const handleDeleteQuestionClick = useCallback(() => { + if (isNotDefined(projectId)) { + return; + } + triggerQuestionDelete({ + variables: { + projectId, + questionId: question.id, + }, + }); + refetchQuestionList(); + }, [ + triggerQuestionDelete, + projectId, + question, + refetchQuestionList, + ]); + + const [ + modal, + onDeleteQuestionClick, + ] = useConfirmation({ + showConfirmationInitially: false, + onConfirm: handleDeleteQuestionClick, + message: 'Are you sure you wish to delete the question from this questionnaire? This cannot be undone.', + }); + return ( Edit question + + Delete Question + )} > @@ -193,6 +289,7 @@ function QuestionPreview(props: QuestionProps) { choiceCollectionId={question.choiceCollection?.id} /> )} + {modal} ); }