From 1be811d9f819a047679bc41a2377e3a39ee983a5 Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Tue, 10 Mar 2026 13:00:23 -0700 Subject: [PATCH 1/7] Added add Custom Question and edit Custom Question pages --- .../template/[templateId]/q/[q_slug]/page.tsx | 2 +- .../template/[templateId]/q/new/page.tsx | 64 ++ .../customQuestionEdit.module.scss} | 0 .../[customQuestionId]/page.tsx | 957 ++++++++++++++++++ .../[templateCustomizationId]/page.tsx | 3 + .../page.tsx | 0 .../questionCustomEdit.module.scss | 66 ++ .../q/new/newCustomQuestion.module.scss | 12 + .../[templateCustomizationId]/q/new/page.tsx | 622 +++++++----- .../[templateCustomizationId]/q/new/temp.json | 301 ++++++ .../section/create/page.tsx | 5 +- app/hooks/useEditQuestion.ts | 24 + .../CustomizedSectionEdit/index.tsx | 11 +- components/QuestionAdd/index.tsx | 129 +-- components/QuestionView/index.tsx | 20 +- generated/gql.ts | 12 + generated/graphql.ts | 46 + .../mutations/customQuestion.mutation.graphql | 92 ++ graphql/queries/customQuestion.query.graphql | 35 + lib/graphql/errorTypePolicies.ts | 1 + messages/en-US/templateBuilder.json | 6 + utils/routes.ts | 2 + 22 files changed, 2056 insertions(+), 354 deletions(-) rename app/[locale]/template/customizations/[templateCustomizationId]/{q/[questionId]/questionCustomEdit.module.scss => customQuestion/[customQuestionId]/customQuestionEdit.module.scss} (100%) create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx rename app/[locale]/template/customizations/[templateCustomizationId]/q/{[questionId] => [versionedQuestionId]}/page.tsx (100%) create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/q/new/newCustomQuestion.module.scss create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/q/new/temp.json create mode 100644 app/hooks/useEditQuestion.ts create mode 100644 graphql/mutations/customQuestion.mutation.graphql create mode 100644 graphql/queries/customQuestion.query.graphql diff --git a/app/[locale]/template/[templateId]/q/[q_slug]/page.tsx b/app/[locale]/template/[templateId]/q/[q_slug]/page.tsx index 3f3b46706..b3a1d4111 100644 --- a/app/[locale]/template/[templateId]/q/[q_slug]/page.tsx +++ b/app/[locale]/template/[templateId]/q/[q_slug]/page.tsx @@ -88,7 +88,7 @@ import { import { isOptionsType, getOverrides, -} from './hooks/useEditQuestion'; +} from '@/app/hooks/useEditQuestion'; import styles from './questionEdit.module.scss'; const QuestionEdit = () => { diff --git a/app/[locale]/template/[templateId]/q/new/page.tsx b/app/[locale]/template/[templateId]/q/new/page.tsx index 73e3ca89a..e6f8d242f 100644 --- a/app/[locale]/template/[templateId]/q/new/page.tsx +++ b/app/[locale]/template/[templateId]/q/new/page.tsx @@ -15,6 +15,15 @@ import { Text } from "react-aria-components"; + +// GraphQL +import { useMutation, useQuery } from '@apollo/client/react'; +import { + AddQuestionDocument, + QuestionsDisplayOrderDocument, +} from '@/generated/graphql'; + + // Components import PageHeader from "@/components/PageHeader"; import { ContentContainer, LayoutContainer, } from '@/components/Container'; @@ -58,6 +67,37 @@ const QuestionTypeSelectPage: React.FC = () => { const Global = useTranslations('Global'); const QuestionTypeSelect = useTranslations('QuestionTypeSelectPage'); + // Initialize add and update question mutations + const [addQuestionMutation] = useMutation(AddQuestionDocument); + + // Query request for questions to calculate max displayOrder + const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, { + variables: { + sectionId: Number(sectionId) + }, + skip: !sectionId + }) + + // Calculate the display order of the new question based on the last displayOrder number + const getDisplayOrder = useCallback(() => { + if (!questionDisplayOrders?.questions?.length) { + return 1; + } + + // Filter out null/undefined questions and handle missing displayOrder + const validDisplayOrders = questionDisplayOrders.questions + .map(q => q?.displayOrder) + .filter((order): order is number => typeof order === 'number'); + + if (validDisplayOrders.length === 0) { + return 1; + } + + const maxDisplayOrder = Math.max(...validDisplayOrders); + return maxDisplayOrder + 1; + }, [questionDisplayOrders]); + + // Handle the selection of a question type const handleSelect = ( { @@ -143,6 +183,17 @@ const QuestionTypeSelectPage: React.FC = () => { } }, [stepQueryValue]) + const TemplateQuestionBreadcrumbs = () => { + return ( + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templates')} + {Global('breadcrumbs.editTemplate')} + {Global('breadcrumbs.selectQuestionType')} + {Global('breadcrumbs.question')} + + ) + } return ( <> {step === 1 && ( @@ -247,6 +298,19 @@ const QuestionTypeSelectPage: React.FC = () => { questionName={selectedQuestionType?.questionName ?? null} questionJSON={selectedQuestionType?.questionJSON ?? ''} sectionId={sectionId ? sectionId : ''} + breadcrumbs={} + backUrl={routePath('template.q.new', { templateId }, { section_id: sectionId, step: 1 })} + successUrl={routePath('template.show', { templateId })} + onSave={async (commonFields) => { + const input = { + templateId: Number(templateId), + sectionId: Number(sectionId), + displayOrder: getDisplayOrder(), + isDirty: true, + ...commonFields, + }; + await addQuestionMutation({ variables: { input } }); + }} /> )} diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[questionId]/questionCustomEdit.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss similarity index 100% rename from app/[locale]/template/customizations/[templateCustomizationId]/q/[questionId]/questionCustomEdit.module.scss rename to app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx new file mode 100644 index 000000000..cde08d156 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx @@ -0,0 +1,957 @@ +'use client' + +import { useEffect, useRef, useState } from 'react'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { + Breadcrumb, + Breadcrumbs, + Button, + Checkbox, + Dialog, + DialogTrigger, + Form, + Input, + Label, + Link, + Modal, + ModalOverlay, + Radio, + Tab, + TabList, + TabPanel, + Tabs, + Text, + TextField +} from "react-aria-components"; + +// GraphQL +import { + UpdateCustomQuestionDocument, + CustomQuestionDocument, + RemoveCustomQuestionDocument +} from '@/generated/graphql'; + + +import { + AnyParsedQuestion, + Question, + QuestionOption, + QuestionOptions, + QuestionFormatInterface, + RemoveQuestionErrors, + UpdateQuestionErrors, +} from '@/app/types'; + +// Components +import PageHeader from "@/components/PageHeader"; +import QuestionOptionsComponent + from '@/components/Form/QuestionOptionsComponent'; +import QuestionPreview from '@/components/QuestionPreview'; +import { + FormInput, + RadioGroupComponent, + RangeComponent, + TypeAheadSearch, + ResearchOutputComponent +} from '@/components/Form'; +import FormTextArea from '@/components/Form/FormTextArea'; +import ErrorMessages from '@/components/ErrorMessages'; +import QuestionView from '@/components/QuestionView'; +import { getParsedQuestionJSON } from '@/components/hooks/getParsedQuestionJSON'; +import Loading from '@/components/Loading'; + +//Utils and Other +import { useResearchOutputTable } from '@/app/hooks/useResearchOutputTable'; +import { useToast } from '@/context/ToastContext'; +import { routePath } from '@/utils/routes'; +import { stripHtmlTags } from '@/utils/general'; +import logECS from '@/utils/clientLogger'; +import { extractErrors } from "@/utils/errorHandler"; +import { + getQuestionFormatInfo, + getQuestionTypes, + questionTypeHandlers +} from '@/utils/questionTypeHandlers'; + +import { + RANGE_QUESTION_TYPE, + TYPEAHEAD_QUESTION_TYPE, + DATE_RANGE_QUESTION_TYPE, + NUMBER_RANGE_QUESTION_TYPE, + TEXT_AREA_QUESTION_TYPE, + RESEARCH_OUTPUT_QUESTION_TYPE, + QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD, +} from '@/lib/constants'; +import { + isOptionsType, + getOverrides, +} from '@/app/hooks/useEditQuestion'; +import styles from './customQuestionEdit.module.scss'; + +const CustomQuestionEdit = () => { + const params = useParams(); + const router = useRouter(); + const searchParams = useSearchParams(); + const toastState = useToast(); // Access the toast state from context + const templateCustomizationId = String(params.templateCustomizationId); + const customQuestionId = String(params.customQuestionId); + const questionTypeIdQueryParam = searchParams.get('questionType') || null; + + //For scrolling to error in page + const errorRef = useRef(null); + // Ref to track whether customization is being deleted to prevent refetching deleted customization + const isBeingDeletedRef = useRef(false); + + const hasHydrated = useRef(false); + // Track whether there are unsaved changes + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + // Form state + const [isSubmitting, setIsSubmitting] = useState(false); + /*To be able to show a loading state when redirecting after successful update because otherwise there is a + bit of a stutter where the page reloads before redirecting*/ + const [isRedirecting, setIsRedirecting] = useState(false); + // State for delete confirmation modal + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + // State for managing form inputs + const [question, setQuestion] = useState(); + const [rows, setRows] = useState([{ id: 0, text: "", isSelected: false }]); + const [questionType, setQuestionType] = useState(''); + const [questionTypeName, setQuestionTypeName] = useState(''); // Added to store friendly question name + const [formSubmitted, setFormSubmitted] = useState(false); + const [hasOptions, setHasOptions] = useState(false); + const [errors, setErrors] = useState([]); + const [dateRangeLabels, setDateRangeLabels] = useState<{ start: string; end: string }>({ start: '', end: '' }); + const [typeaheadHelpText, setTypeAheadHelpText] = useState(''); + const [typeaheadSearchLabel, setTypeaheadSearchLabel] = useState(''); + const [parsedQuestionJSON, setParsedQuestionJSON] = useState(); + const [isConfirmOpen, setConfirmOpen] = useState(false); + + // Add state for live region announcements + const [announcement, setAnnouncement] = useState(''); + + // localization keys + const Global = useTranslations('Global'); + const t = useTranslations('QuestionEdit'); + const QuestionAdd = useTranslations('QuestionAdd'); + const QuestionEdit = useTranslations("EditQuestion"); + + // Set URLs + const TEMPLATE_URL = routePath('template.customize', { templateCustomizationId }); + + // Helper function to make announcements + const announce = (message: string) => { + setAnnouncement(message); + // Clear after announcement is made + setTimeout(() => setAnnouncement(''), 100); + }; + + // Research Output Table Hooks + const { + buildResearchOutputFormState, + hydrateFromJSON, + licensesData, + defaultResearchOutputTypesData, + expandedFields, + nonCustomizableFieldIds, + standardFields, + additionalFields, + newOutputType, + setNewOutputType, + newLicenseType, + setNewLicenseType, + handleRepositoriesChange, + handleMetaDataStandardsChange, + handleStandardFieldChange, + handleCustomizeField, + handleToggleMetaDataStandards, + handleTogglePreferredRepositories, + handleLicenseModeChange, + handleAddCustomLicenseType, + handleRemoveCustomLicenseType, + handleOutputTypeModeChange, + handleAddCustomOutputType, + handleRemoveCustomOutputType, + addAdditionalField, + handleDeleteAdditionalField, + handleUpdateAdditionalField, + updateStandardFieldProperty + } = useResearchOutputTable({ setHasUnsavedChanges, announce }); + + + // Initialize user updateCustomQuestion mutation + const [updateCustomQuestionMutation] = useMutation(UpdateCustomQuestionDocument); + + // Initialize removeCustomQuestion mutation + const [removeCustomQuestionMutation] = useMutation(RemoveCustomQuestionDocument); + + // Run selected question query + const { + data: selectedQuestion, + loading, + error: selectedQuestionQueryError + } = useQuery(CustomQuestionDocument, { + variables: { customQuestionId: Number(customQuestionId) }, + skip: isBeingDeletedRef.current, + }); + + + // Update rows state and question.json when options change + const updateRows = (newRows: QuestionOptions[]) => { + setRows(newRows); + + if (hasOptions && questionType && question?.json) { + const updatedJSON = buildUpdatedJSON(question, newRows); + + if (updatedJSON) { + setQuestion((prev) => ({ + ...prev, + json: JSON.stringify(updatedJSON.data), + })); + setHasUnsavedChanges(true); + } + } + }; + + // Return user back to the page to select a question type + const redirectToQuestionTypes = () => { + const sectionId = selectedQuestion?.customQuestion?.sectionId; + // questionId as query param included to let page know that user is updating an existing question + router.push(routePath('template.customize.question.create', { templateCustomizationId }, { section_id: sectionId, step: 1, customQuestionId })) + } + + //Handle change to Question Text + const handleQuestionTextChange = (value: string) => { + setQuestion(prev => ({ + ...prev, + questionText: value + })); + setHasUnsavedChanges(true); + }; + + // Update common input fields when any of them change + const handleInputChange = (field: keyof Question, value: string | boolean | undefined) => { + setQuestion((prev) => ({ + ...prev, + [field]: value === undefined ? '' : value, // Default to empty string if value is undefined + })); + setHasUnsavedChanges(true); + }; + + + // Handle changes from RadioGroup + const handleRadioChange = (value: string) => { + if (value) { + const isRequired = value === 'yes' ? true : false; + setQuestion(prev => ({ + ...prev, + required: isRequired + })); + setHasUnsavedChanges(true); + } + }; + + // Handler for date range label changes + const handleRangeLabelChange = (field: 'start' | 'end', value: string) => { + setDateRangeLabels(prev => ({ ...prev, [field]: value })); + + if (parsedQuestionJSON && (parsedQuestionJSON?.type === "dateRange" || parsedQuestionJSON?.type === "numberRange")) { + if (parsedQuestionJSON?.columns?.[field]) { + const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly + updatedParsed.columns[field].label = value; + setQuestion(prev => ({ + ...prev, + json: JSON.stringify(updatedParsed), + })); + setHasUnsavedChanges(true); + } + } + }; + + // Handler for typeahead search label changes + const handleTypeAheadSearchLabelChange = (value: string) => { + setTypeaheadSearchLabel(value); + + if (parsedQuestionJSON && parsedQuestionJSON?.type === "affiliationSearch") { + const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly + updatedParsed.attributes.label = value; + setQuestion(prev => ({ + ...prev, + json: JSON.stringify(updatedParsed), + })); + setHasUnsavedChanges(true); + } + }; + + // Handler for typeahead help text changes + const handleTypeAheadHelpTextChange = (value: string) => { + setTypeAheadHelpText(value); + + if (parsedQuestionJSON && parsedQuestionJSON?.type === "affiliationSearch") { + if (parsedQuestionJSON?.attributes?.help) { + const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly + updatedParsed.attributes.help = value; + setQuestion(prev => ({ + ...prev, + json: JSON.stringify(updatedParsed), + })); + setHasUnsavedChanges(true); + } + } + }; + + // Prepare input for the questionTypeHandler. For options questions, we update the + // values with rows state. For non-options questions, we use the parsed JSON + const getFormState = (question: Question, rowsOverride?: QuestionOptions[]) => { + if (hasOptions) { + const useRows = rowsOverride ?? rows; + return { + options: useRows.map(row => ({ + label: row.text, + value: row.text, + selected: row.isSelected, + })), + }; + } + + const { parsed, error } = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); + + if (questionType === RESEARCH_OUTPUT_QUESTION_TYPE) { + return buildResearchOutputFormState(); + } + + if (!parsed) { + if (error) { + setErrors(prev => [...prev, error]) + } + return; + } + return { + ...parsed, + attributes: { + ...('attributes' in parsed ? parsed.attributes : {}), + ...getOverrides(questionType), + }, + }; + }; + + // Pass the merged userInput to questionTypeHandlers to generate json and do type and schema validation + const buildUpdatedJSON = (question: Question, rowsOverride?: QuestionOptions[]) => { + const userInput = getFormState(question, rowsOverride); + const { parsed, error } = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); + + if (!parsed) { + if (error) { + setErrors(prev => [...prev, error]) + } + return; + } + return questionTypeHandlers[questionType as keyof typeof questionTypeHandlers]( + parsed, + userInput + ); + }; + + // Make GraphQL mutation request to update the custom question + const handleUpdateCustomQuestion = async (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; + setIsSubmitting(true); + setFormSubmitted(true); + + if (!question) { + setIsSubmitting(false); + return; + } + + const updatedJSON = buildUpdatedJSON(question); + const { success, error } = updatedJSON ?? {}; + + if (!success || error) { + const errorMessage = error ?? t('messages.errors.questionUpdateError'); + setErrors(prev => [...prev, errorMessage]); + announce(QuestionAdd('researchOutput.announcements.errorOccurred') || 'An error occurred.'); + setIsSubmitting(false); + return; + } + + const cleanedQuestionText = stripHtmlTags(question.questionText ?? ''); + + try { + const response = await updateCustomQuestionMutation({ + variables: { + input: { + customQuestionId: Number(customQuestionId), + questionText: cleanedQuestionText, + json: JSON.stringify(updatedJSON?.data ?? {}), + requirementText: question.requirementText ?? null, + guidanceText: question.guidanceText ?? null, + sampleText: question.sampleText ?? null, + useSampleTextAsDefault: question.useSampleTextAsDefault ?? false, + required: question.required ?? false, + } + } + }); + + const responseErrors = response.data?.updateCustomQuestion?.errors; + if (responseErrors && Object.values(responseErrors).some(err => err && err !== 'CustomQuestionErrors')) { + setErrors([responseErrors.general ?? t('messages.errors.questionUpdateError')]); + setIsSubmitting(false); + return; + } + + setHasUnsavedChanges(false); + setIsRedirecting(true); + toastState.add(QuestionAdd('messages.success.questionUpdated'), { type: 'success' }); + router.push(TEMPLATE_URL); + } catch (error) { + setIsSubmitting(false); + logECS('error', 'updateCustomQuestion', { + error, + url: { path: TEMPLATE_URL } + }); + setErrors(prev => [...prev, t('messages.errors.questionUpdateError')]); + } + }; + + const handleDeleteCustomQuestion = async () => { + isBeingDeletedRef.current = true; + setIsDeleting(true); + try { + const response = await removeCustomQuestionMutation({ + variables: { customQuestionId: Number(customQuestionId) }, + }); + + const responseErrors = response.data?.removeCustomQuestion?.errors; + if (responseErrors && Object.values(responseErrors).some(err => err && err !== 'CustomQuestionErrors')) { + setErrors([responseErrors.general ?? QuestionEdit('messages.error.errorDeletingQuestion')]); + return; + } + + toastState.add(QuestionEdit('messages.success.successDeletingQuestion'), { type: 'success' }); + router.push(TEMPLATE_URL); + } catch (error) { + logECS('error', 'deleteCustomQuestion', { + error, + url: { path: routePath('template.customQuestion', { templateCustomizationId, customQuestionId }) } + }); + setErrors([QuestionEdit('messages.error.errorDeletingQuestion')]); + } finally { + setIsDeleting(false); + setIsDeleteModalOpen(false); + setIsSubmitting(false); + } + }; + + + // Saves any query errors to errors state + useEffect(() => { + const allErrors = []; + + if (selectedQuestionQueryError) { + allErrors.push(selectedQuestionQueryError.message); + } + + setErrors(allErrors); + }, [selectedQuestionQueryError]); + + // Set question details in state when data is loaded + useEffect(() => { + if (selectedQuestion?.customQuestion) { + const q = { + ...selectedQuestion.customQuestion, + required: selectedQuestion.customQuestion.required ?? false // convert null to false + }; + try { + const { parsed, error } = getParsedQuestionJSON(q, routePath('template.customize', { templateCustomizationId }), Global); + if (!parsed?.type) { + if (error) { + logECS('error', 'Parsing error', { + error: 'Invalid question type in parsed JSON', + url: { path: routePath('template.customize', { templateCustomizationId }) } + }); + + setErrors(prev => [...prev, error]) + } + return; + } + + const questionType = parsed.type; + const translationKey = `questionTypes.${questionType}`; + const questionTypeFriendlyName = Global(translationKey); + + setQuestionType(questionType); + setQuestionTypeName(questionTypeFriendlyName); + setParsedQuestionJSON(parsed); + + const isOptionsQuestion = isOptionsType(questionType); + setQuestion({ + ...q, + showCommentField: 'showCommentField' in parsed ? parsed.showCommentField : false // Default to false if not present + }); + + setHasOptions(isOptionsQuestion); + + if (questionType === TYPEAHEAD_QUESTION_TYPE) { + setTypeaheadSearchLabel(parsed?.attributes?.label ?? ''); + setTypeAheadHelpText(parsed?.attributes?.help ?? ''); + } + + // Set options info with proper type checking + if (isOptionsQuestion && 'options' in parsed && parsed.options && Array.isArray(parsed.options)) { + const optionRows: QuestionOptions[] = parsed.options + .map((option: QuestionOption, index: number) => ({ + id: index, + text: option?.label || '', + isSelected: option?.selected || option?.checked || false, + })); + setRows(optionRows); + } + } catch (error) { + logECS('error', 'Parsing error', { + error, + url: { path: routePath('template.customize', { templateCustomizationId }) } + }); + setErrors(prev => [...prev, 'Error parsing question data']); + } + } + }, [selectedQuestion]); + + useEffect(() => { + if (questionType) { + // To determine if we have an options question type + const isOptionQuestion = isOptionsType(questionType); + + setHasOptions(isOptionQuestion); + } + + }, [questionType]) + + // Set labels for dateRange and numberRange + useEffect(() => { + if ((parsedQuestionJSON?.type === DATE_RANGE_QUESTION_TYPE || parsedQuestionJSON?.type === NUMBER_RANGE_QUESTION_TYPE)) { + try { + setDateRangeLabels({ + start: parsedQuestionJSON?.columns?.start?.label ?? '', + end: parsedQuestionJSON?.columns?.end?.label ?? '', + }); + } catch { + setDateRangeLabels({ start: '', end: '' }); + } + } + }, [parsedQuestionJSON]) + + // Research Output Question - Hydrate state from JSON + useEffect(() => { + if (!hasHydrated.current && + parsedQuestionJSON?.type === RESEARCH_OUTPUT_QUESTION_TYPE && + Array.isArray(parsedQuestionJSON.columns)) { + try { + hydrateFromJSON(parsedQuestionJSON); + hasHydrated.current = true; + } catch (error) { + console.error('Error hydrating research output fields from JSON', error); + } + } + }, [parsedQuestionJSON, hydrateFromJSON]); + + // If a user changes their question type, then we need to fetch the question types to set the new json schema + useEffect(() => { + // Only fetch question types if we have a questionType query param present + if (questionTypeIdQueryParam) { + getQuestionTypes(); + } + }, [questionTypeIdQueryParam]); + + + // If a user passes in a questionType query param we will find the matching questionTypes + // json schema and update the question with it + useEffect(() => { + if (questionType && questionTypeIdQueryParam && question) { + // Find the matching question type + const qInfo: QuestionFormatInterface | null = getQuestionFormatInfo(questionTypeIdQueryParam); + + if (qInfo?.defaultJSON) { + // Update the question object with the new JSON + setQuestion(prev => ({ + ...prev, + json: JSON.stringify(qInfo.defaultJSON) + })); + + setHasUnsavedChanges(true); + + setQuestionType(questionTypeIdQueryParam) + + // Update the questionTypeName + const questionTypeFriendlyName = Global(`questionTypes.${questionTypeIdQueryParam}`); + setQuestionTypeName(questionTypeFriendlyName); + + const isOptionsQuestion = isOptionsType(questionTypeIdQueryParam) + setHasOptions(isOptionsQuestion); + + } + } + }, [questionType, questionTypeIdQueryParam]); + + // Set parsed question JSON whenever question state changes + useEffect(() => { + if (question) { + const { parsed, error } = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); + if (!parsed) { + if (error) { + setErrors(prev => [...prev, error]) + } + return; + } + setParsedQuestionJSON(parsed); + } + }, [question]) + + // Warn user of unsaved changes if they try to leave the page + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (hasUnsavedChanges) { + e.preventDefault(); + e.returnValue = ''; // Required for Chrome/Firefox to show the confirm dialog + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + }; + }, [hasUnsavedChanges]); + + if (loading || isRedirecting) { + return ; + } + + return ( + <> + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templateCustomizations')} + {Global('breadcrumbs.template')} + {Global('breadcrumbs.question')} + + } + actions={null} + className="" + /> + + {/* Live region for announcements - visually hidden but read by screen readers */} +
+ {announcement} +
+ + +
+
+ + + {Global('tabs.editQuestion')} + {Global('tabs.options')} + {Global('tabs.logic')} + + + +
+ + + + + + {t('helpText.textField')} + + + + handleQuestionTextChange(e.target.value)} + helpMessage={t('helpText.questionText')} + isInvalid={!question?.questionText} + errorMessage={t('messages.errors.questionTextRequired')} + /> + + {/**Question type fields here */} + {hasOptions && ( +
+

{t('helpText.questionOptions', { questionType })}

+ { + if (!question) return undefined; + const result = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); + return result.parsed ? JSON.stringify(result.parsed) : undefined; + })()} + formSubmitted={formSubmitted} + setFormSubmitted={setFormSubmitted} /> +
+ )} + + {/**Date and Number range question types */} + {questionType && RANGE_QUESTION_TYPE.includes(questionType) && ( + + )} + + {/**Typeahead search question type */} + {questionType && (questionType === TYPEAHEAD_QUESTION_TYPE) && ( + + )} + + {!QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD.includes(questionType ?? '') && ( + handleInputChange('showCommentField', value === 'yes')} + > +
+ {QuestionAdd('labels.showCommentField')} +
+ +
+ {QuestionAdd('labels.doNotShowCommentField')} +
+
+ )} + + { + setQuestion(prev => ({ + ...prev, + requirementText: newValue + })); + setHasUnsavedChanges(true); + }} + /> + + + { + setQuestion(prev => ({ + ...prev, + guidanceText: newValue + })); + setHasUnsavedChanges(true); + }} + helpMessage={t('helpText.guidanceText')} + /> + + {questionType === TEXT_AREA_QUESTION_TYPE && ( + { + setQuestion(prev => ({ + ...prev, + sampleText: newValue + })); + setHasUnsavedChanges(true); + }} + /> + )} + + {questionType === TEXT_AREA_QUESTION_TYPE && ( + { + setQuestion({ + ...question, + useSampleTextAsDefault: !question?.useSampleTextAsDefault + }); + setHasUnsavedChanges(true); + }} + isSelected={question?.useSampleTextAsDefault || false} + > +
+ +
+ {t('descriptions.sampleTextAsDefault')} + +
+ )} + + {questionType === RESEARCH_OUTPUT_QUESTION_TYPE && ( + + )} + + +
+ {Global('form.yesLabel')} +
+ +
+ {Global('form.noLabel')} +
+
+ + + + + + +
+ +

{Global('tabs.options')}

+
+ +

{Global('tabs.logic')}

+
+
+ +
+

{t('headings.deleteQuestion')}

+

{t('descriptions.deleteWarning')}

+ + + + + + {({ close }) => ( + <> +

{t('headings.confirmDelete')}

+

{t('descriptions.deleteWarning')}

+
+ + +
+ + )} +
+
+
+
+
+ +
+ + + +
+

{Global('headings.preview')}

+

{t('descriptions.previewText')}

+ + + + +

{t('headings.bestPractice')}

+

{t('descriptions.bestPracticePara1')}

+

{t('descriptions.bestPracticePara2')}

+

{t('descriptions.bestPracticePara3')}

+
+
+ + + ); +} + +export default CustomQuestionEdit; diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx index 18047210b..b1285b535 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx @@ -93,6 +93,8 @@ const TemplateCustomizationOverview: React.FC = () => { variables: { templateCustomizationId: Number(templateCustomizationId) }, }); + console.log("***Template Customization Overview", data); + // Mutations const [publishTemplateCustomization] = useMutation(PublishTemplateCustomizationDocument); const [unpublishTemplateCustomization] = useMutation(UnpublishTemplateCustomizationDocument); @@ -356,6 +358,7 @@ const TemplateCustomizationOverview: React.FC = () => { } }, [data]); + console.log("***Local Sections in Template Customization Overview: ", localSections); if (loading) { return ; diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[questionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx similarity index 100% rename from app/[locale]/template/customizations/[templateCustomizationId]/q/[questionId]/page.tsx rename to app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss new file mode 100644 index 000000000..79f542e29 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss @@ -0,0 +1,66 @@ +.optionsDescription { + font-size: var(--fs-sm); + font-weight:600; +} + +.searchField { + display: grid; + grid-template-areas: + "label label" + "input button" + "help help"; + grid-template-columns: 1fr auto; + max-width: 500px; +} + +.searchLabel { + grid-area: label; +} + +.searchInput { + grid-area: input; + +} + +.searchButton { + grid-area: button; + margin-left: 10px; + width: fit-content; +} + +.searchHelpText { + grid-area: help; + width: fit-content; +} + +.optionsWrapper { + padding: var(--space-0); + margin-bottom: var(--space-6); +} + +.questionFormField { + height: 100px +} + +.deleteZone { + margin-top: 2rem; + padding-top: 1.5rem; + margin-bottom: 2rem; +} + +.deleteConfirmButtons { + display: flex; + justify-content: flex-end; + gap: 1rem; + margin-top: 1.5rem; +} + +.commentCheckbox { + margin-bottom: var(--space-6); +} + +.optionsWrapper { + border: 1px solid var(--border-color); + padding: var(--space-4); + margin-bottom: var(--space-4); +} diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/newCustomQuestion.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/newCustomQuestion.module.scss new file mode 100644 index 000000000..8c8a1a0a8 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/newCustomQuestion.module.scss @@ -0,0 +1,12 @@ +.clearFilter { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.searchMatchText { + font-size: var(--fs-small); + margin-top: var(--space-2); + margin-bottom: var(--space-2); +} diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx index 68854cc0d..8b2591ad9 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx @@ -1,263 +1,359 @@ -// 'use client' - -// import { useCallback, useEffect, useRef, useState } from 'react'; -// import { useTranslations } from 'next-intl'; -// import { useParams, useRouter, useSearchParams } from 'next/navigation'; -// import { -// Breadcrumb, -// Breadcrumbs, -// Button, -// FieldError, -// Input, -// Label, -// Link, -// SearchField, -// Text -// } from "react-aria-components"; - -// // Components -// import PageHeader from "@/components/PageHeader"; -// import { ContentContainer, LayoutContainer, } from '@/components/Container'; -// import QuestionAdd from '@/components/QuestionAdd'; -// import QuestionTypeCard from '@/components/QuestionTypeCard'; -// import ErrorMessages from '@/components/ErrorMessages'; - -// //Other -// import { scrollToTop } from '@/utils/general'; -// import { routePath } from '@/utils/routes'; -// import { useQueryStep } from '@/app/[locale]/template/[templateId]/q/new/utils'; -// import { QuestionFormatInterface } from '@/app/types'; -// import styles from './newQuestion.module.scss'; -// import { getQuestionTypes } from "@/utils/questionTypeHandlers"; - - -// const QuestionCustomizeNew: React.FC = () => { -// // Get templateId param -// const params = useParams(); -// const router = useRouter(); -// const searchParams = useSearchParams(); -// const topRef = useRef(null); -// //For scrolling to error in page -// const errorRef = useRef(null); -// const templateId = String(params.templateId); // From route /template/:templateId -// const sectionId = searchParams.get('section_id') ?? ''; -// const questionId = searchParams.get('questionId');// if user is switching their question type while editing an existing question - -// // State management -// const [step, setStep] = useState(null); -// const [questionTypes, setQuestionTypes] = useState([]); -// const [searchTerm, setSearchTerm] = useState(''); -// const [filteredQuestionTypes, setFilteredQuestionTypes] = useState([]); -// const [searchButtonClicked, setSearchButtonClicked] = useState(false); -// const [selectedQuestionType, setSelectedQuestionType] = useState<{ questionType: string, questionName: string, questionJSON: string }>(); -// const [errors, setErrors] = useState([]); - -// const stepQueryValue = useQueryStep(); - -// //Localization keys -// const Global = useTranslations('Global'); -// const QuestionTypeSelect = useTranslations('QuestionTypeSelectPage'); - -// // Handle the selection of a question type -// const handleSelect = ( -// { -// questionJSON, -// questionType, -// questionTypeName -// }: { -// questionJSON: string; -// questionType: string; -// questionTypeName: string; -// }) => { - -// if (questionId) { -// //If the user came from editing an existing question, we want to return them to that page with the new questionTypeId -// // We need to use a full page reload to ensure all state is reset so that 'beforeunload' events are properly handled in the next page -// // to display unsaved changes warning if needed -// window.location.href = routePath('template.q.slug', { templateId, q_slug: questionId }, { questionType }); - -// } else { -// // redirect to the Question Edit page if a user is adding a new question -// if (questionType) { -// setSelectedQuestionType({ questionType, questionName: questionTypeName, questionJSON }); -// setStep(2); -// // Use router.replace with restore=true so that 'beforeunload' events are properly detected in the next page. This will cause users to go back to the -// // Template Overview page rather than the question type selection page when they click "Back" in their browser -// router.replace(routePath('template.q.new', { templateId }, { section_id: sectionId, step: 2, restore: true })); - -// } -// } -// } - -// // Clear search term and filters -// const resetSearch = useCallback(() => { -// setSearchTerm(''); -// setFilteredQuestionTypes(null); -// scrollToTop(topRef); -// }, [scrollToTop]); - - -// // Filter through questionTypes and find the question type whose info includes the search term -// const filterQuestionTypes = ( -// questionTypes: QuestionFormatInterface[], -// term: string -// ): QuestionFormatInterface[] => -// questionTypes.filter(qt => { -// const lowerTerm = term.toLowerCase(); -// const nameMatch = qt.title?.toLowerCase().includes(lowerTerm); -// const usageDescriptionMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); -// const jsonMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); - -// return nameMatch || jsonMatch || usageDescriptionMatch; -// }); - -// // Filter results when a user enters a search term and clicks "Search" button -// const handleFiltering = (term: string) => { -// setSearchButtonClicked(true); -// setErrors([]); - -// // Search title, funder and description fields for terms -// const filteredQuestionTypes = filterQuestionTypes(questionTypes, term); - -// if (filteredQuestionTypes.length > 0) { -// setFilteredQuestionTypes(filteredQuestionTypes.length > 0 ? filteredQuestionTypes : null); -// } -// } - -// useEffect(() => { -// setQuestionTypes(getQuestionTypes()); -// }, []); - -// useEffect(() => { -// // Need this to set list of templates back to original, full list after filtering -// if (searchTerm === '') { -// resetSearch(); -// setSearchButtonClicked(false); -// } -// }, [searchTerm, resetSearch]) - -// useEffect(() => { -// // If a step was specified in a query param, then set that step -// if (step !== stepQueryValue) { -// setStep(stepQueryValue); -// } -// }, [stepQueryValue]) - -// return ( -// <> -// {step === 1 && ( -// <> -// -// {Global('breadcrumbs.home')} -// {Global('breadcrumbs.templates')} -// {Global('breadcrumbs.editTemplate')} -// {Global('breadcrumbs.selectQuestionType')} -// -// } -// actions={null} -// className="" -// /> - -// -// -// -//
-// -// -// setSearchTerm(e.target.value)} /> -// -// -// -// {QuestionTypeSelect('searchHelpText')} -// -// -//
-//
-// {/*Show # of results with clear filter link*/} -// {(searchTerm.length > 0 && searchButtonClicked) && ( -//
{Global('messaging.resultsText', { name: filteredQuestionTypes?.length || 0 })} -
-// )} -//
-// {filteredQuestionTypes && filteredQuestionTypes.length > 0 ? ( -// <> -// {filteredQuestionTypes.map((questionType) => ( -// -// ))} -// -// ) : ( -// <> -// {/**If the user is searching, and there were no results from the search -// * then display the message 'no results found -// */} -// {(searchTerm.length > 0 && searchButtonClicked) ? ( -// <> -// {Global('messaging.noItemsFound')} -// -// ) : ( -// <> -// {questionTypes.map((questionType) => ( -// -// ))} -// -// ) -// } -// -// ) - -// } -//
-// {/*Show # of results with clear filter link*/} -// {((filteredQuestionTypes && filteredQuestionTypes.length > 0) && searchButtonClicked) && ( -//
-//
{Global('messaging.resultsText', { name: filteredQuestionTypes?.length || 0 })} -
-//
-// )} -//
-//
-//
-// -// )} -// {step === 2 && ( -// <> -// {/*Show Edit Question form*/} -// -// -// )} -// -// ); -// } - -// export default QuestionCustomizeNew; - -const QuestionCustomizeNew = () => { - return

TBD

; -}; - -export default QuestionCustomizeNew; +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslations } from 'next-intl'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; +import { + Breadcrumb, + Breadcrumbs, + Button, + FieldError, + Input, + Label, + Link, + SearchField, + Text +} from "react-aria-components"; + +// GraphQL +import { useMutation, useQuery } from '@apollo/client/react'; +import { + AddCustomQuestionDocument, + QuestionsDisplayOrderDocument, + CustomizableObjectOwnership, + TemplateCustomizationOverviewDocument +} from '@/generated/graphql'; + + +// Components +import PageHeader from "@/components/PageHeader"; +import { ContentContainer, LayoutContainer, } from '@/components/Container'; +import QuestionAdd from '@/components/QuestionAdd'; +import QuestionTypeCard from '@/components/QuestionTypeCard'; +import ErrorMessages from '@/components/ErrorMessages'; + +//Other +import { scrollToTop } from '@/utils/general'; +import { routePath } from '@/utils/routes'; +import { useQueryStep } from '@/app/[locale]/template/[templateId]/q/new/utils'; +import { QuestionFormatInterface } from '@/app/types'; +import styles from './newCustomQuestion.module.scss'; +import { getQuestionTypes } from "@/utils/questionTypeHandlers"; + + +const CustomQuestionNew: React.FC = () => { + // Get templateId param + const params = useParams(); + const router = useRouter(); + const searchParams = useSearchParams(); + const topRef = useRef(null); + //For scrolling to error in page + const errorRef = useRef(null); + const templateCustomizationId = String(params.templateCustomizationId); // From route /template/customizations/:templateCustomizationId + const sectionId = searchParams.get('section_id') ?? ''; + const customQuestionId = searchParams.get('customQuestionId');// if user is switching their question type while editing an existing question + + // Track the last question in the current section to pin the new question after + const [lastQuestionId, setLastQuestionId] = useState(null); + const [lastQuestionType, setLastQuestionType] = useState(null); + + // State management + const [step, setStep] = useState(null); + const [questionTypes, setQuestionTypes] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [filteredQuestionTypes, setFilteredQuestionTypes] = useState([]); + const [searchButtonClicked, setSearchButtonClicked] = useState(false); + const [selectedQuestionType, setSelectedQuestionType] = useState<{ questionType: string, questionName: string, questionJSON: string }>(); + const [errors, setErrors] = useState([]); + + const stepQueryValue = useQueryStep(); + + //Localization keys + const Global = useTranslations('Global'); + const QuestionTypeSelect = useTranslations('QuestionTypeSelectPage'); + + // Initialize add and update question mutations + const [addCustomQuestionMutation] = useMutation(AddCustomQuestionDocument, { + refetchQueries: [TemplateCustomizationOverviewDocument, QuestionsDisplayOrderDocument], + }); + + // Run template query to get all sections and questions under the given templateCustomizationId + const { + data, + loading, + error: templateQueryErrors, + } = useQuery(TemplateCustomizationOverviewDocument, { + variables: { templateCustomizationId: Number(templateCustomizationId) }, + }); + + + // Query request for questions to calculate max displayOrder + const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, { + variables: { + sectionId: Number(sectionId) + }, + skip: !sectionId + }) + + // Calculate the display order of the new question based on the last displayOrder number + const getDisplayOrder = useCallback(() => { + if (!questionDisplayOrders?.questions?.length) { + return 1; + } + + // Filter out null/undefined questions and handle missing displayOrder + const validDisplayOrders = questionDisplayOrders.questions + .map(q => q?.displayOrder) + .filter((order): order is number => typeof order === 'number'); + + if (validDisplayOrders.length === 0) { + return 1; + } + + const maxDisplayOrder = Math.max(...validDisplayOrders); + return maxDisplayOrder + 1; + }, [questionDisplayOrders]); + + + // Handle the selection of a question type + const handleSelect = ( + { + questionJSON, + questionType, + questionTypeName + }: { + questionJSON: string; + questionType: string; + questionTypeName: string; + }) => { + + if (customQuestionId) { + //If the user came from editing an existing question, we want to return them to that page with the new questionTypeId + // We need to use a full page reload to ensure all state is reset so that 'beforeunload' events are properly handled in the next page + // to display unsaved changes warning if needed + window.location.href = routePath('template.customize', { templateCustomizationId }); + + } else { + // redirect to the Question Edit page if a user is adding a new question + if (questionType) { + setSelectedQuestionType({ questionType, questionName: questionTypeName, questionJSON }); + setStep(2); + // Use router.replace with restore=true so that 'beforeunload' events are properly detected in the next page. This will cause users to go back to the + // Template Overview page rather than the question type selection page when they click "Back" in their browser + router.replace(routePath('template.customize.question.create', { templateCustomizationId }, { section_id: sectionId, step: 2, restore: true })); + + } + } + } + + // Clear search term and filters + const resetSearch = useCallback(() => { + setSearchTerm(''); + setFilteredQuestionTypes(null); + scrollToTop(topRef); + }, [scrollToTop]); + + + // Filter through questionTypes and find the question type whose info includes the search term + const filterQuestionTypes = ( + questionTypes: QuestionFormatInterface[], + term: string + ): QuestionFormatInterface[] => + questionTypes.filter(qt => { + const lowerTerm = term.toLowerCase(); + const nameMatch = qt.title?.toLowerCase().includes(lowerTerm); + const usageDescriptionMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); + const jsonMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); + + return nameMatch || jsonMatch || usageDescriptionMatch; + }); + + // Filter results when a user enters a search term and clicks "Search" button + const handleFiltering = (term: string) => { + setSearchButtonClicked(true); + setErrors([]); + + // Search title, funder and description fields for terms + const filteredQuestionTypes = filterQuestionTypes(questionTypes, term); + + if (filteredQuestionTypes.length > 0) { + setFilteredQuestionTypes(filteredQuestionTypes.length > 0 ? filteredQuestionTypes : null); + } + } + + const CustomQuestionBreadcrumbs = () => { + return ( + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templateCustomizations')} + {Global('breadcrumbs.template')} + {Global('breadcrumbs.selectQuestionType')} + + ) + } + + useEffect(() => { + setQuestionTypes(getQuestionTypes()); + }, []); + + useEffect(() => { + // Need this to set list of templates back to original, full list after filtering + if (searchTerm === '') { + resetSearch(); + setSearchButtonClicked(false); + } + }, [searchTerm, resetSearch]) + + useEffect(() => { + // If a step was specified in a query param, then set that step + if (step !== stepQueryValue) { + setStep(stepQueryValue); + } + }, [stepQueryValue]) + + // Calculate the last question in the current section to determine where to pin the new question. + // This runs whenever the template overview query returns new data or the sectionId changes (i.e. user goes to a different section) + useEffect(() => { + if (!data?.templateCustomizationOverview?.sections || !sectionId) return; + + const section = data.templateCustomizationOverview.sections.find( + s => s?.id === Number(sectionId) + ); + + if (!section?.questions?.length) { + setLastQuestionId(null); + setLastQuestionType(null); + return; + } + + // Questions are ordered by displayOrder — find the one with the highest value + const lastQuestion = [...section.questions] + .filter(q => q != null) + .sort((a, b) => (a?.displayOrder ?? 0) - (b?.displayOrder ?? 0)) + .at(-1); + + setLastQuestionId(lastQuestion?.id ?? null); + // questionType in the data is "BASE" | "CUSTOM" which maps directly to CustomizableObjectOwnership + setLastQuestionType(lastQuestion?.questionType as CustomizableObjectOwnership ?? null); + + }, [data, sectionId]); + + return ( + <> + {step === 1 && ( + <> + } + actions={null} + className="" + /> + + + + +
+ + + setSearchTerm(e.target.value)} /> + + + + {QuestionTypeSelect('searchHelpText')} + + +
+
+ {/*Show # of results with clear filter link*/} + {(searchTerm.length > 0 && searchButtonClicked) && ( +
{Global('messaging.resultsText', { name: filteredQuestionTypes?.length || 0 })} -
+ )} +
+ {filteredQuestionTypes && filteredQuestionTypes.length > 0 ? ( + <> + {filteredQuestionTypes.map((questionType) => ( + + ))} + + ) : ( + <> + {/**If the user is searching, and there were no results from the search + * then display the message 'no results found + */} + {(searchTerm.length > 0 && searchButtonClicked) ? ( + <> + {Global('messaging.noItemsFound')} + + ) : ( + <> + {questionTypes.map((questionType) => ( + + ))} + + ) + } + + ) + + } +
+ {/*Show # of results with clear filter link*/} + {((filteredQuestionTypes && filteredQuestionTypes.length > 0) && searchButtonClicked) && ( +
+
{Global('messaging.resultsText', { name: filteredQuestionTypes?.length || 0 })} -
+
+ )} +
+
+
+ + )} + {step === 2 && ( + <> + {/*Show Edit Question form*/} + } + backUrl={routePath('template.customize.question.create', { templateCustomizationId }, { section_id: sectionId, step: 1 })} + successUrl={routePath('template.customize', { templateCustomizationId })} + onSave={async (commonFields) => { + const input = { + templateCustomizationId: Number(templateCustomizationId), + sectionId: Number(sectionId), + sectionType: CustomizableObjectOwnership.Custom, + pinnedQuestionId: lastQuestionId, + pinnedQuestionType: lastQuestionType ?? null, + ...commonFields, + }; + await addCustomQuestionMutation({ variables: { input } }); + }} + /> + + )} + + ); +} + +export default CustomQuestionNew; diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/temp.json b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/temp.json new file mode 100644 index 000000000..0f0972d02 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/temp.json @@ -0,0 +1,301 @@ +{ + "templateCustomizationOverview": { + "__typename": "TemplateCustomizationOverview", + "customizationId": 9, + "customizationIsDirty": false, + "customizationLastCustomized": "2026-03-10 17:12:53", + "customizationLastCustomizedById": 1, + "customizationLastCustomizedByName": "Super Admin", + "customizationMigrationStatus": "OK", + "customizationStatus": "DRAFT", + "customizationLastPublishedDate": null, + "errors": { + "__typename": "TemplateCustomizationErrors", + "general": null, + "affiliationId": null, + "currentVersionedTemplateId": null, + "templateId": null + }, + "sections": [ + { + "__typename": "SectionCustomizationOverview", + "id": 5437, + "hasCustomGuidance": false, + "displayOrder": 0, + "migrationStatus": null, + "name": "PART I: General Information (To be completed by all applicants)", + "sectionType": "BASE", + "questions": [ + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 0, + "questionText": "

Type of Plan

", + "migrationStatus": null, + "id": 10543, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 1, + "questionText": "

Plan Version Number

", + "migrationStatus": null, + "id": 10544, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 2, + "questionText": "

Plan Submission Date

", + "migrationStatus": null, + "id": 10545, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 3, + "questionText": "

Point of Contact for DMS plan

", + "migrationStatus": null, + "id": 10546, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 4, + "questionText": "

Project/Application/Protocol ID

", + "migrationStatus": null, + "id": 10547, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + } + ] + }, + { + "__typename": "SectionCustomizationOverview", + "id": 5438, + "hasCustomGuidance": false, + "displayOrder": 1, + "migrationStatus": null, + "name": "PART II: Data Management Sharing Plan Details", + "sectionType": "BASE", + "questions": [ + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 0, + "questionText": "

Does the Genomic Data Sharing (GDS) Policy apply? 

", + "migrationStatus": null, + "id": 10548, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 1, + "questionText": "

Will the datasets be shared according to GDS policy but no later than the time of publication or end of the project, whichever is sooner?

", + "migrationStatus": null, + "id": 10549, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 2, + "questionText": "

Will an NIH-supported repository be selected for data subject to GDS?

", + "migrationStatus": null, + "id": 10550, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 3, + "questionText": "

Has an Institutional Certification (IC) been submitted with the application or Just-In-Time that meets GDS criteria?

", + "migrationStatus": null, + "id": 10551, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 4, + "questionText": "

Element 1: Data Type   

Describe DMS Plan Elements 1-6 in the section below with free text. To maximize the use of structured information in writing a Plan (e.g., data types, repositories) with a drop-down menu option, please proceed to the adjacent tab labeled “Research Outputs.\"

Will all scientific data generated by the research project be shared in a data repository that makes data available to the larger research community?  If No, explain the rationale that determines which scientific data will not be shared in the comment area below.       

", + "migrationStatus": null, + "id": 10552, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 5, + "questionText": "

Element 2: Tools, Software, Code

Describe the tools, software, and/or code that are needed to access or manipulate shared scientific data to support replication or reuse, if any.

", + "migrationStatus": null, + "id": 10553, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 6, + "questionText": "

Element 2: Tools, Software, Code 

Describe how researchers can access the tools, software, and/or code listed above. Describe if “Other.”

", + "migrationStatus": null, + "id": 10554, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 7, + "questionText": "

Element 3: Standards

List data or metadata standards or common data elements that will be used applicable to each data type shared. Write N/A if no existing standards.

", + "migrationStatus": null, + "id": 10555, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 8, + "questionText": "

Element 4: Data Preservation, Access, and Timelines

Explain if data sharing timelines will not meet expectations of the DMS or other policies, if applicable.

", + "migrationStatus": null, + "id": 10556, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 9, + "questionText": "

Element 4: Data Preservation, Access, and Timelines

What types of persistent identifiers/ indexing methods will be used for data releases, to enable findability and citation of shared datasets?

", + "migrationStatus": null, + "id": 10557, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 10, + "questionText": "

Element 5: Access, Distribution or Reuse Considerations

Describe any limitations or factors affecting subsequent access, distribution, or reuse of this data.

", + "migrationStatus": null, + "id": 10558, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 11, + "questionText": "

Element 5: Access, Distribution or Reuse Considerations

Are there any privacy or informed consent considerations for human data? If Yes, describe including methods to protect privacy and confidentiality. 

", + "migrationStatus": null, + "id": 10559, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 12, + "questionText": "

Element 5: Access, Distribution or Reuse Considerations

What type of access will secondary users utilize to access the shared data? Describe if “Other.”

", + "migrationStatus": null, + "id": 10560, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 13, + "questionText": "

Element 6: Compliance

Describe how compliance with the Plan will be monitored and managed, frequency of oversight, and by whom.

", + "migrationStatus": null, + "id": 10561, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 14, + "questionText": "

Element 6: Compliance

Will data management and/or sharing activities be facilitated by individuals outside of the project team? If YES, list individual(s), their organization(s), and describe their role(s) and responsibilities in the comments area below.

", + "migrationStatus": null, + "id": 10562, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 15, + "questionText": "

Please proceed to the Research Outputs tab in this application to provide details about the data. 

", + "migrationStatus": null, + "id": 10563, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + } + ] + }, + { + "__typename": "SectionCustomizationOverview", + "id": 5439, + "hasCustomGuidance": false, + "displayOrder": 2, + "migrationStatus": null, + "name": "PART III: Additional Information (optional)", + "sectionType": "BASE", + "questions": [ + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 0, + "questionText": "

If additional policies apply (e.g., Clinical Trials Access Policy, FOA-specific requirements), describe additional information required to meet the policy.

", + "migrationStatus": null, + "id": 10564, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 1, + "questionText": "

Provide any additional information or context for readers and reviewers of your Data Management and Sharing Plan.

", + "migrationStatus": null, + "id": 10565, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + }, + { + "__typename": "QuestionCustomizationOverview", + "displayOrder": 2, + "questionText": "

Please proceed to the Research Outputs tab in this application to provide details about the data. 

", + "migrationStatus": null, + "id": 10566, + "hasCustomSampleAnswer": false, + "hasCustomGuidance": false, + "questionType": "BASE" + } + ] + } + ], + "versionedTemplateAffiliationId": "https://ror.org/01cwqze88", + "versionedTemplateAffiliationName": "National Institutes of Health", + "versionedTemplateId": 845, + "versionedTemplateLastModified": "2026-03-10 00:00:00", + "versionedTemplateName": "NIH-FDP Pilot Template Bravo", + "versionedTemplateVersion": "v5" + } +} \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx index fbfd266e9..84bd8aa59 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx @@ -58,7 +58,7 @@ const CreateCustomSectionPage: React.FC = () => { // Get templateCustomizationId param const params = useParams(); const router = useRouter(); - const { templateCustomizationId } = params; // From route /template/customizations/:templateCustomizationId/section/create + const templateCustomizationId = String(params.templateCustomizationId); //For scrolling to error in page const errorRef = useRef(null); @@ -109,6 +109,7 @@ const CreateCustomSectionPage: React.FC = () => { variables: { templateCustomizationId: Number(templateCustomizationId) }, }); + console.log("***Template Customization Overview", data); // Update form fields in state when fields are edited const updateSectionContent = (key: string, value: string) => { @@ -340,7 +341,7 @@ const CreateCustomSectionPage: React.FC = () => { {Global('breadcrumbs.home')} {Global('breadcrumbs.templateCustomizations')} - {Global('breadcrumbs.template')} + {Global('breadcrumbs.template')} {CreateSectionPage('title')} } diff --git a/app/hooks/useEditQuestion.ts b/app/hooks/useEditQuestion.ts new file mode 100644 index 000000000..2076882e6 --- /dev/null +++ b/app/hooks/useEditQuestion.ts @@ -0,0 +1,24 @@ +import { OPTIONS_QUESTION_TYPES } from '@/lib/constants'; + +// Configure what overrides you want to apply to the question type json objects +export const getOverrides = (questionType: string | null | undefined) => { + switch (questionType) { + case "text": + return { maxLength: null }; + case "textArea": + return { maxLength: null, rows: 20 }; + case "number": + return { min: 0, max: 10000000, step: 1 }; + case "currency": + return { min: 0, max: 10000000, step: 0.01 }; + case "url": + return { maxLength: 2048, minLength: 2, pattern: "https?://.+" }; + default: + return {}; + } +}; + +// Check if question is an options type +export const isOptionsType = (questionType: string) => { + return Boolean(questionType && OPTIONS_QUESTION_TYPES.includes(questionType)); +} \ No newline at end of file diff --git a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx index 70424a2ee..1eb4a9bd0 100644 --- a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx +++ b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx @@ -34,6 +34,8 @@ const CustomizedSectionEdit: React.FC = ({ onMoveUp, onMoveDown, }) => { + + console.log("***SectionEdit received section: ", section); const toastState = useToast(); const t = useTranslations('Sections'); @@ -182,8 +184,9 @@ const CustomizedSectionEdit: React.FC = ({ // Construct the edit URL based on section type const editUrl = section.sectionType === "CUSTOM" ? `/template/customizations/${templateCustomizationId}/customSection/${section.id}`// Custom Section - : `/template/customizations/${templateCustomizationId}/section/${section.id}`; // Section Customization + : `/template/customizations/${templateCustomizationId}/section/${section.id}`; // Section Customization + console.log("***localQuestions in SectionEdit: ", localQuestions); return ( <>
@@ -204,7 +207,11 @@ const CustomizedSectionEdit: React.FC = ({ Promise; + backUrl: string; // where "Change Type" and breadcrumbs point + successUrl: string; // where to redirect on save success + breadcrumbs: React.ReactNode; + }) => { const params = useParams(); @@ -193,28 +214,9 @@ const QuestionAdd = ({ // Send user back to the selection of question types const redirectToQuestionTypes = () => { - router.push(step1Url) + router.push(backUrl) } - // Calculate the display order of the new question based on the last displayOrder number - const getDisplayOrder = useCallback(() => { - if (!questionDisplayOrders?.questions?.length) { - return 1; - } - - // Filter out null/undefined questions and handle missing displayOrder - const validDisplayOrders = questionDisplayOrders.questions - .map(q => q?.displayOrder) - .filter((order): order is number => typeof order === 'number'); - - if (validDisplayOrders.length === 0) { - return 1; - } - - const maxDisplayOrder = Math.max(...validDisplayOrders); - return maxDisplayOrder + 1; - }, [questionDisplayOrders]); - // Update rows state and question.json when options change const updateRows = (newRows: QuestionOptions[]) => { setRows(newRows); @@ -382,58 +384,37 @@ const QuestionAdd = ({ if (isSubmitting) return; setIsSubmitting(true); - const displayOrder = getDisplayOrder(); - const updatedJSON = buildUpdatedJSON(question); - const { success, error } = updatedJSON ?? {}; - if (success && !error) { - // Strip all tags from questionText before sending to backend - const cleanedQuestionText = stripHtmlTags(question?.questionText ?? ''); - - const input = { - templateId: Number(templateId), - sectionId: Number(sectionId), - displayOrder, - isDirty: true, - questionText: cleanedQuestionText, - json: JSON.stringify(updatedJSON ? updatedJSON.data : ''), - requirementText: question?.requirementText, - guidanceText: question?.guidanceText, - sampleText: question?.sampleText, - useSampleTextAsDefault: question?.useSampleTextAsDefault || false, - required: question?.required, - }; - try { - const response = await addQuestionMutation({ variables: { input } }); - - if (response?.data) { - setIsSubmitting(false); - setHasUnsavedChanges(false); - toastState.add(QuestionAdd('messages.success.questionAdded'), { type: 'success' }); - // Redirect user to the Edit Question view with their new question id after successfully adding the new question - router.push(routePath('template.show', { templateId })); - } - } catch (error) { - // Handle errors - setErrors(prevErrors => [ - ...prevErrors, - QuestionAdd('messages.errors.questionAddingError'), - ]); - logECS('error', 'Adding Question in QuestionAdd', { - error, - }); - } - } else { - const errorMessage = error ?? QuestionAdd('messages.errors.questionAddingError'); - setErrors(prevErrors => [ - ...prevErrors, - errorMessage, - ]); - announce(QuestionAdd('researchOutput.announcements.errorOccurred') || 'An error occurred. Please check the form.'); + if (!success || error) { + setErrors(prev => [...prev, error ?? QuestionAdd('messages.errors.questionAddingError')]); + announce(QuestionAdd('researchOutput.announcements.errorOccurred')); + setIsSubmitting(false); + return; } + const commonFields: QuestionCommonFields = { + questionText: stripHtmlTags(question?.questionText ?? ''), + json: JSON.stringify(updatedJSON ? updatedJSON.data : ''), + requirementText: question?.requirementText, + guidanceText: question?.guidanceText, + sampleText: question?.sampleText, + useSampleTextAsDefault: question?.useSampleTextAsDefault ?? false, + required: question?.required, + }; + + try { + await onSave(commonFields); + setHasUnsavedChanges(false); + toastState.add(QuestionAdd('messages.success.questionAdded'), { type: 'success' }); + router.push(successUrl); + } catch (error) { + setErrors(prev => [...prev, QuestionAdd('messages.errors.questionAddingError')]); + logECS('error', 'Adding Question in QuestionAdd', { error }); + } finally { + setIsSubmitting(false); + } }; // If questionType is missing, return user to the Question Types selection page @@ -517,15 +498,7 @@ const QuestionAdd = ({ title={QuestionAdd('title')} description="" showBackButton={false} - breadcrumbs={ - - {Global('breadcrumbs.home')} - {Global('breadcrumbs.templates')} - {Global('breadcrumbs.editTemplate')} - {Global('breadcrumbs.selectQuestionType')} - {Global('breadcrumbs.question')} - - } + breadcrumbs={breadcrumbs} actions={null} className="" /> diff --git a/components/QuestionView/index.tsx b/components/QuestionView/index.tsx index dd70d73bd..60a7b23b3 100644 --- a/components/QuestionView/index.tsx +++ b/components/QuestionView/index.tsx @@ -89,7 +89,7 @@ interface QuestionViewProps extends React.HTMLAttributes { * NOTE: We pass this explicitly, as we cannot predict or infer if the * templateId will be available in the question object. */ - templateId: number, + templateId?: number, } //This component is meant to work with the QuestionAdd and QuestionEdit components, to display @@ -108,8 +108,9 @@ const QuestionView: React.FC = ({ const { data: qtData } = { data: getQuestionTypes() }; const { data: templateData } = useQuery(TemplateDocument, { variables: { - templateId, + templateId: templateId ?? 0 }, + skip: !templateId, // Skip the query if templateId is not provided. We don't need to templateId for Custom Questions, since it's only used for owner's display name notifyOnNetworkStatusChange: true }); @@ -503,9 +504,11 @@ const QuestionView: React.FC = ({ {(question?.requirementText) && (
-

- {trans('requirements', { orgName: templateData?.template?.owner?.displayName || '' })} -

+ {templateData?.template?.owner && ( +

+ {trans('requirements', { orgName: templateData?.template?.owner?.displayName || '' })} +

+ )}
)} @@ -535,9 +538,10 @@ const QuestionView: React.FC = ({ {(question?.guidanceText) && (
-

- {trans('guidanceBy', { orgName: templateData?.template?.owner?.displayName ?? '' })} -

+ {templateData?.template?.owner && ( +

+ {trans('guidanceBy', { orgName: templateData?.template?.owner?.displayName ?? '' })} +

)}
)} diff --git a/generated/gql.ts b/generated/gql.ts index e37737379..2cc6c994d 100644 --- a/generated/gql.ts +++ b/generated/gql.ts @@ -17,6 +17,7 @@ type Documents = { "mutation AddAffiliation($input: AffiliationInput!) {\n addAffiliation(input: $input) {\n errors {\n general\n name\n }\n uri\n }\n}": typeof types.AddAffiliationDocument, "mutation AddAnswer($planId: Int!, $versionedSectionId: Int!, $versionedQuestionId: Int!, $json: String!) {\n addAnswer(\n planId: $planId\n versionedSectionId: $versionedSectionId\n versionedQuestionId: $versionedQuestionId\n json: $json\n ) {\n id\n json\n modified\n }\n}\n\nmutation UpdateAnswer($answerId: Int!, $json: String) {\n updateAnswer(answerId: $answerId, json: $json) {\n errors {\n acronyms\n aliases\n contactEmail\n contactName\n displayName\n feedbackEmails\n feedbackMessage\n fundrefId\n general\n homepage\n json\n logoName\n logoURI\n name\n planId\n provenance\n searchName\n ssoEntityId\n subHeaderLinks\n types\n uri\n versionedQuestionId\n versionedSectionId\n }\n id\n json\n modified\n versionedQuestion {\n versionedSectionId\n }\n }\n}\n\nmutation RemoveAnswerComment($answerCommentId: Int!, $answerId: Int!) {\n removeAnswerComment(answerCommentId: $answerCommentId, answerId: $answerId) {\n id\n errors {\n general\n }\n answerId\n commentText\n }\n}\n\nmutation UpdateAnswerComment($answerCommentId: Int!, $answerId: Int!, $commentText: String!) {\n updateAnswerComment(\n answerCommentId: $answerCommentId\n answerId: $answerId\n commentText: $commentText\n ) {\n commentText\n answerId\n id\n errors {\n general\n commentText\n answerId\n }\n }\n}\n\nmutation AddAnswerComment($answerId: Int!, $commentText: String!) {\n addAnswerComment(answerId: $answerId, commentText: $commentText) {\n commentText\n id\n answerId\n errors {\n general\n }\n }\n}": typeof types.AddAnswerDocument, "mutation UpdateProjectCollaborator($projectCollaboratorId: Int!, $accessLevel: ProjectCollaboratorAccessLevel!) {\n updateProjectCollaborator(\n projectCollaboratorId: $projectCollaboratorId\n accessLevel: $accessLevel\n ) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation RemoveProjectCollaborator($projectCollaboratorId: Int!) {\n removeProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation ResendInviteToProjectCollaborator($projectCollaboratorId: Int!) {\n resendInviteToProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n email\n user {\n id\n givenName\n surName\n }\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n }\n}": typeof types.UpdateProjectCollaboratorDocument, + "mutation AddCustomQuestion($input: AddCustomQuestionInput!) {\n addCustomQuestion(input: $input) {\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n templateCustomizationId\n useSampleTextAsDefault\n sectionType\n }\n guidanceText\n id\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation UpdateCustomQuestion($input: UpdateCustomQuestionInput!) {\n updateCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n questionText\n sampleText\n requirementText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation RemoveCustomQuestion($customQuestionId: Int!) {\n removeCustomQuestion(customQuestionId: $customQuestionId) {\n id\n guidanceText\n errors {\n guidanceText\n general\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n requirementText\n sampleText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n useSampleTextAsDefault\n templateCustomizationId\n sectionId\n }\n}": typeof types.AddCustomQuestionDocument, "mutation AddCustomSection($input: AddCustomSectionInput!) {\n addCustomSection(input: $input) {\n id\n guidance\n errors {\n general\n guidance\n introduction\n name\n requirements\n pinnedSectionId\n pinnedSectionType\n templateCustomizationId\n }\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}\n\nmutation UpdateCustomSection($input: UpdateCustomSectionInput!) {\n updateCustomSection(input: $input) {\n id\n guidance\n errors {\n general\n guidance\n introduction\n name\n requirements\n }\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}\n\nmutation RemoveCustomSection($customSectionId: Int!) {\n removeCustomSection(customSectionId: $customSectionId) {\n id\n guidance\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n errors {\n general\n }\n }\n}": typeof types.AddCustomSectionDocument, "mutation RemoveFeedbackComment($planId: Int!, $planFeedbackCommentId: Int!) {\n removeFeedbackComment(\n planId: $planId\n planFeedbackCommentId: $planFeedbackCommentId\n ) {\n id\n errors {\n general\n }\n answerId\n commentText\n }\n}\n\nmutation UpdateFeedbackComment($planId: Int!, $planFeedbackCommentId: Int!, $commentText: String!) {\n updateFeedbackComment(\n planId: $planId\n planFeedbackCommentId: $planFeedbackCommentId\n commentText: $commentText\n ) {\n answerId\n commentText\n id\n errors {\n general\n }\n }\n}\n\nmutation AddFeedbackComment($planId: Int!, $planFeedbackId: Int!, $answerId: Int!, $commentText: String!) {\n addFeedbackComment(\n planId: $planId\n planFeedbackId: $planFeedbackId\n answerId: $answerId\n commentText: $commentText\n ) {\n id\n answerId\n commentText\n errors {\n general\n }\n }\n}": typeof types.RemoveFeedbackCommentDocument, "mutation AddGuidance($input: AddGuidanceInput!) {\n addGuidance(input: $input) {\n id\n guidanceText\n tagId\n errors {\n general\n guidanceGroupId\n guidanceText\n }\n }\n}\n\nmutation UpdateGuidance($input: UpdateGuidanceInput!) {\n updateGuidance(input: $input) {\n id\n guidanceText\n tagId\n errors {\n general\n guidanceGroupId\n guidanceText\n }\n }\n}\n\nmutation AddPlanGuidance($planId: Int!, $affiliationId: String!) {\n addPlanGuidance(planId: $planId, affiliationId: $affiliationId) {\n id\n planId\n affiliationId\n }\n}\n\nmutation RemovePlanGuidance($planId: Int!, $affiliationId: String!) {\n removePlanGuidance(planId: $planId, affiliationId: $affiliationId) {\n id\n planId\n userId\n affiliationId\n }\n}": typeof types.AddGuidanceDocument, @@ -40,6 +41,7 @@ type Documents = { "query Affiliations($name: String!) {\n affiliations(name: $name) {\n totalCount\n nextCursor\n items {\n id\n displayName\n uri\n apiTarget\n }\n }\n}\n\nquery AffiliationFunders($name: String!, $funderOnly: Boolean, $paginationOptions: PaginationOptions) {\n affiliations(\n name: $name\n funderOnly: $funderOnly\n paginationOptions: $paginationOptions\n ) {\n totalCount\n nextCursor\n items {\n id\n displayName\n apiTarget\n uri\n }\n }\n}\n\nquery ManagedAffiliationsWithGuidance($name: String, $versionedTemplateId: Int!, $paginationOptions: PaginationOptions) {\n managedAffiliationsWithGuidance(\n name: $name\n versionedTemplateId: $versionedTemplateId\n paginationOptions: $paginationOptions\n ) {\n items {\n id\n funder\n displayName\n uri\n acronyms\n }\n limit\n totalCount\n hasNextPage\n hasPreviousPage\n nextCursor\n }\n}": typeof types.AffiliationsDocument, "query AnswerByVersionedQuestionId($projectId: Int!, $planId: Int!, $versionedQuestionId: Int!) {\n answerByVersionedQuestionId(\n projectId: $projectId\n planId: $planId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n json\n versionedQuestion {\n id\n }\n plan {\n id\n }\n comments {\n id\n commentText\n answerId\n created\n createdById\n modified\n user {\n id\n surName\n givenName\n }\n }\n feedbackComments {\n id\n commentText\n created\n createdById\n answerId\n modified\n PlanFeedback {\n id\n }\n user {\n id\n surName\n givenName\n }\n }\n modified\n created\n errors {\n general\n planId\n versionedSectionId\n versionedQuestionId\n json\n }\n }\n}": typeof types.AnswerByVersionedQuestionIdDocument, "query ProjectCollaborators($projectId: Int!) {\n projectCollaborators(projectId: $projectId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n created\n email\n user {\n givenName\n surName\n email\n }\n }\n}\n\nquery FindCollaborator($term: String!, $options: PaginationOptions) {\n findCollaborator(term: $term, options: $options) {\n limit\n items {\n id\n givenName\n email\n affiliationId\n affiliationRORId\n affiliationURL\n orcid\n surName\n affiliationName\n }\n availableSortFields\n nextCursor\n totalCount\n }\n}": typeof types.ProjectCollaboratorsDocument, + "query CustomQuestion($customQuestionId: Int!) {\n customQuestion(customQuestionId: $customQuestionId) {\n id\n json\n modified\n sampleText\n requirementText\n questionText\n guidanceText\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n required\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}": typeof types.CustomQuestionDocument, "query CustomSection($customSectionId: Int!) {\n customSection(customSectionId: $customSectionId) {\n errors {\n general\n guidance\n introduction\n name\n requirements\n }\n guidance\n id\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}": typeof types.CustomSectionDocument, "query ProjectFundings($projectId: Int!) {\n projectFundings(projectId: $projectId) {\n id\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n affiliation {\n displayName\n uri\n }\n }\n}\n\nquery PlanFundings($planId: Int!) {\n planFundings(planId: $planId) {\n id\n projectFunding {\n id\n }\n }\n}\n\nquery PopularFunders {\n popularFunders {\n displayName\n id\n nbrPlans\n uri\n apiTarget\n }\n}": typeof types.ProjectFundingsDocument, "query GuidanceByGroup($guidanceGroupId: Int!) {\n guidanceByGroup(guidanceGroupId: $guidanceGroupId) {\n modifiedBy {\n givenName\n surName\n id\n }\n guidanceText\n id\n tagId\n errors {\n general\n tagId\n guidanceText\n guidanceGroupId\n }\n modified\n }\n}\n\nquery Guidance($guidanceId: Int!) {\n guidance(guidanceId: $guidanceId) {\n id\n guidanceText\n tagId\n }\n}\n\nquery GuidanceSourcesForPlan($planId: Int!, $versionedSectionId: Int, $versionedQuestionId: Int) {\n guidanceSourcesForPlan(\n planId: $planId\n versionedSectionId: $versionedSectionId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n type\n label\n shortName\n orgURI\n hasGuidance\n items {\n id\n title\n guidanceText\n }\n }\n}": typeof types.GuidanceByGroupDocument, @@ -73,6 +75,7 @@ const documents: Documents = { "mutation AddAffiliation($input: AffiliationInput!) {\n addAffiliation(input: $input) {\n errors {\n general\n name\n }\n uri\n }\n}": types.AddAffiliationDocument, "mutation AddAnswer($planId: Int!, $versionedSectionId: Int!, $versionedQuestionId: Int!, $json: String!) {\n addAnswer(\n planId: $planId\n versionedSectionId: $versionedSectionId\n versionedQuestionId: $versionedQuestionId\n json: $json\n ) {\n id\n json\n modified\n }\n}\n\nmutation UpdateAnswer($answerId: Int!, $json: String) {\n updateAnswer(answerId: $answerId, json: $json) {\n errors {\n acronyms\n aliases\n contactEmail\n contactName\n displayName\n feedbackEmails\n feedbackMessage\n fundrefId\n general\n homepage\n json\n logoName\n logoURI\n name\n planId\n provenance\n searchName\n ssoEntityId\n subHeaderLinks\n types\n uri\n versionedQuestionId\n versionedSectionId\n }\n id\n json\n modified\n versionedQuestion {\n versionedSectionId\n }\n }\n}\n\nmutation RemoveAnswerComment($answerCommentId: Int!, $answerId: Int!) {\n removeAnswerComment(answerCommentId: $answerCommentId, answerId: $answerId) {\n id\n errors {\n general\n }\n answerId\n commentText\n }\n}\n\nmutation UpdateAnswerComment($answerCommentId: Int!, $answerId: Int!, $commentText: String!) {\n updateAnswerComment(\n answerCommentId: $answerCommentId\n answerId: $answerId\n commentText: $commentText\n ) {\n commentText\n answerId\n id\n errors {\n general\n commentText\n answerId\n }\n }\n}\n\nmutation AddAnswerComment($answerId: Int!, $commentText: String!) {\n addAnswerComment(answerId: $answerId, commentText: $commentText) {\n commentText\n id\n answerId\n errors {\n general\n }\n }\n}": types.AddAnswerDocument, "mutation UpdateProjectCollaborator($projectCollaboratorId: Int!, $accessLevel: ProjectCollaboratorAccessLevel!) {\n updateProjectCollaborator(\n projectCollaboratorId: $projectCollaboratorId\n accessLevel: $accessLevel\n ) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation RemoveProjectCollaborator($projectCollaboratorId: Int!) {\n removeProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation ResendInviteToProjectCollaborator($projectCollaboratorId: Int!) {\n resendInviteToProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n email\n user {\n id\n givenName\n surName\n }\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n }\n}": types.UpdateProjectCollaboratorDocument, + "mutation AddCustomQuestion($input: AddCustomQuestionInput!) {\n addCustomQuestion(input: $input) {\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n templateCustomizationId\n useSampleTextAsDefault\n sectionType\n }\n guidanceText\n id\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation UpdateCustomQuestion($input: UpdateCustomQuestionInput!) {\n updateCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n questionText\n sampleText\n requirementText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation RemoveCustomQuestion($customQuestionId: Int!) {\n removeCustomQuestion(customQuestionId: $customQuestionId) {\n id\n guidanceText\n errors {\n guidanceText\n general\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n requirementText\n sampleText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n useSampleTextAsDefault\n templateCustomizationId\n sectionId\n }\n}": types.AddCustomQuestionDocument, "mutation AddCustomSection($input: AddCustomSectionInput!) {\n addCustomSection(input: $input) {\n id\n guidance\n errors {\n general\n guidance\n introduction\n name\n requirements\n pinnedSectionId\n pinnedSectionType\n templateCustomizationId\n }\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}\n\nmutation UpdateCustomSection($input: UpdateCustomSectionInput!) {\n updateCustomSection(input: $input) {\n id\n guidance\n errors {\n general\n guidance\n introduction\n name\n requirements\n }\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}\n\nmutation RemoveCustomSection($customSectionId: Int!) {\n removeCustomSection(customSectionId: $customSectionId) {\n id\n guidance\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n errors {\n general\n }\n }\n}": types.AddCustomSectionDocument, "mutation RemoveFeedbackComment($planId: Int!, $planFeedbackCommentId: Int!) {\n removeFeedbackComment(\n planId: $planId\n planFeedbackCommentId: $planFeedbackCommentId\n ) {\n id\n errors {\n general\n }\n answerId\n commentText\n }\n}\n\nmutation UpdateFeedbackComment($planId: Int!, $planFeedbackCommentId: Int!, $commentText: String!) {\n updateFeedbackComment(\n planId: $planId\n planFeedbackCommentId: $planFeedbackCommentId\n commentText: $commentText\n ) {\n answerId\n commentText\n id\n errors {\n general\n }\n }\n}\n\nmutation AddFeedbackComment($planId: Int!, $planFeedbackId: Int!, $answerId: Int!, $commentText: String!) {\n addFeedbackComment(\n planId: $planId\n planFeedbackId: $planFeedbackId\n answerId: $answerId\n commentText: $commentText\n ) {\n id\n answerId\n commentText\n errors {\n general\n }\n }\n}": types.RemoveFeedbackCommentDocument, "mutation AddGuidance($input: AddGuidanceInput!) {\n addGuidance(input: $input) {\n id\n guidanceText\n tagId\n errors {\n general\n guidanceGroupId\n guidanceText\n }\n }\n}\n\nmutation UpdateGuidance($input: UpdateGuidanceInput!) {\n updateGuidance(input: $input) {\n id\n guidanceText\n tagId\n errors {\n general\n guidanceGroupId\n guidanceText\n }\n }\n}\n\nmutation AddPlanGuidance($planId: Int!, $affiliationId: String!) {\n addPlanGuidance(planId: $planId, affiliationId: $affiliationId) {\n id\n planId\n affiliationId\n }\n}\n\nmutation RemovePlanGuidance($planId: Int!, $affiliationId: String!) {\n removePlanGuidance(planId: $planId, affiliationId: $affiliationId) {\n id\n planId\n userId\n affiliationId\n }\n}": types.AddGuidanceDocument, @@ -96,6 +99,7 @@ const documents: Documents = { "query Affiliations($name: String!) {\n affiliations(name: $name) {\n totalCount\n nextCursor\n items {\n id\n displayName\n uri\n apiTarget\n }\n }\n}\n\nquery AffiliationFunders($name: String!, $funderOnly: Boolean, $paginationOptions: PaginationOptions) {\n affiliations(\n name: $name\n funderOnly: $funderOnly\n paginationOptions: $paginationOptions\n ) {\n totalCount\n nextCursor\n items {\n id\n displayName\n apiTarget\n uri\n }\n }\n}\n\nquery ManagedAffiliationsWithGuidance($name: String, $versionedTemplateId: Int!, $paginationOptions: PaginationOptions) {\n managedAffiliationsWithGuidance(\n name: $name\n versionedTemplateId: $versionedTemplateId\n paginationOptions: $paginationOptions\n ) {\n items {\n id\n funder\n displayName\n uri\n acronyms\n }\n limit\n totalCount\n hasNextPage\n hasPreviousPage\n nextCursor\n }\n}": types.AffiliationsDocument, "query AnswerByVersionedQuestionId($projectId: Int!, $planId: Int!, $versionedQuestionId: Int!) {\n answerByVersionedQuestionId(\n projectId: $projectId\n planId: $planId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n json\n versionedQuestion {\n id\n }\n plan {\n id\n }\n comments {\n id\n commentText\n answerId\n created\n createdById\n modified\n user {\n id\n surName\n givenName\n }\n }\n feedbackComments {\n id\n commentText\n created\n createdById\n answerId\n modified\n PlanFeedback {\n id\n }\n user {\n id\n surName\n givenName\n }\n }\n modified\n created\n errors {\n general\n planId\n versionedSectionId\n versionedQuestionId\n json\n }\n }\n}": types.AnswerByVersionedQuestionIdDocument, "query ProjectCollaborators($projectId: Int!) {\n projectCollaborators(projectId: $projectId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n created\n email\n user {\n givenName\n surName\n email\n }\n }\n}\n\nquery FindCollaborator($term: String!, $options: PaginationOptions) {\n findCollaborator(term: $term, options: $options) {\n limit\n items {\n id\n givenName\n email\n affiliationId\n affiliationRORId\n affiliationURL\n orcid\n surName\n affiliationName\n }\n availableSortFields\n nextCursor\n totalCount\n }\n}": types.ProjectCollaboratorsDocument, + "query CustomQuestion($customQuestionId: Int!) {\n customQuestion(customQuestionId: $customQuestionId) {\n id\n json\n modified\n sampleText\n requirementText\n questionText\n guidanceText\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n required\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}": types.CustomQuestionDocument, "query CustomSection($customSectionId: Int!) {\n customSection(customSectionId: $customSectionId) {\n errors {\n general\n guidance\n introduction\n name\n requirements\n }\n guidance\n id\n introduction\n migrationStatus\n modified\n name\n pinnedSectionId\n pinnedSectionType\n requirements\n templateCustomizationId\n }\n}": types.CustomSectionDocument, "query ProjectFundings($projectId: Int!) {\n projectFundings(projectId: $projectId) {\n id\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n affiliation {\n displayName\n uri\n }\n }\n}\n\nquery PlanFundings($planId: Int!) {\n planFundings(planId: $planId) {\n id\n projectFunding {\n id\n }\n }\n}\n\nquery PopularFunders {\n popularFunders {\n displayName\n id\n nbrPlans\n uri\n apiTarget\n }\n}": types.ProjectFundingsDocument, "query GuidanceByGroup($guidanceGroupId: Int!) {\n guidanceByGroup(guidanceGroupId: $guidanceGroupId) {\n modifiedBy {\n givenName\n surName\n id\n }\n guidanceText\n id\n tagId\n errors {\n general\n tagId\n guidanceText\n guidanceGroupId\n }\n modified\n }\n}\n\nquery Guidance($guidanceId: Int!) {\n guidance(guidanceId: $guidanceId) {\n id\n guidanceText\n tagId\n }\n}\n\nquery GuidanceSourcesForPlan($planId: Int!, $versionedSectionId: Int, $versionedQuestionId: Int) {\n guidanceSourcesForPlan(\n planId: $planId\n versionedSectionId: $versionedSectionId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n type\n label\n shortName\n orgURI\n hasGuidance\n items {\n id\n title\n guidanceText\n }\n }\n}": types.GuidanceByGroupDocument, @@ -152,6 +156,10 @@ export function gql(source: "mutation AddAnswer($planId: Int!, $versionedSection * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "mutation UpdateProjectCollaborator($projectCollaboratorId: Int!, $accessLevel: ProjectCollaboratorAccessLevel!) {\n updateProjectCollaborator(\n projectCollaboratorId: $projectCollaboratorId\n accessLevel: $accessLevel\n ) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation RemoveProjectCollaborator($projectCollaboratorId: Int!) {\n removeProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation ResendInviteToProjectCollaborator($projectCollaboratorId: Int!) {\n resendInviteToProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n email\n user {\n id\n givenName\n surName\n }\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n }\n}"): (typeof documents)["mutation UpdateProjectCollaborator($projectCollaboratorId: Int!, $accessLevel: ProjectCollaboratorAccessLevel!) {\n updateProjectCollaborator(\n projectCollaboratorId: $projectCollaboratorId\n accessLevel: $accessLevel\n ) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation RemoveProjectCollaborator($projectCollaboratorId: Int!) {\n removeProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n user {\n givenName\n id\n surName\n }\n }\n}\n\nmutation ResendInviteToProjectCollaborator($projectCollaboratorId: Int!) {\n resendInviteToProjectCollaborator(projectCollaboratorId: $projectCollaboratorId) {\n id\n email\n user {\n id\n givenName\n surName\n }\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n }\n}"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "mutation AddCustomQuestion($input: AddCustomQuestionInput!) {\n addCustomQuestion(input: $input) {\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n templateCustomizationId\n useSampleTextAsDefault\n sectionType\n }\n guidanceText\n id\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation UpdateCustomQuestion($input: UpdateCustomQuestionInput!) {\n updateCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n questionText\n sampleText\n requirementText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation RemoveCustomQuestion($customQuestionId: Int!) {\n removeCustomQuestion(customQuestionId: $customQuestionId) {\n id\n guidanceText\n errors {\n guidanceText\n general\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n requirementText\n sampleText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n useSampleTextAsDefault\n templateCustomizationId\n sectionId\n }\n}"): (typeof documents)["mutation AddCustomQuestion($input: AddCustomQuestionInput!) {\n addCustomQuestion(input: $input) {\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n templateCustomizationId\n useSampleTextAsDefault\n sectionType\n }\n guidanceText\n id\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation UpdateCustomQuestion($input: UpdateCustomQuestionInput!) {\n updateCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n questionText\n sampleText\n requirementText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation RemoveCustomQuestion($customQuestionId: Int!) {\n removeCustomQuestion(customQuestionId: $customQuestionId) {\n id\n guidanceText\n errors {\n guidanceText\n general\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n requirementText\n sampleText\n useSampleTextAsDefault\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n useSampleTextAsDefault\n templateCustomizationId\n sectionId\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -244,6 +252,10 @@ export function gql(source: "query AnswerByVersionedQuestionId($projectId: Int!, * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "query ProjectCollaborators($projectId: Int!) {\n projectCollaborators(projectId: $projectId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n created\n email\n user {\n givenName\n surName\n email\n }\n }\n}\n\nquery FindCollaborator($term: String!, $options: PaginationOptions) {\n findCollaborator(term: $term, options: $options) {\n limit\n items {\n id\n givenName\n email\n affiliationId\n affiliationRORId\n affiliationURL\n orcid\n surName\n affiliationName\n }\n availableSortFields\n nextCursor\n totalCount\n }\n}"): (typeof documents)["query ProjectCollaborators($projectId: Int!) {\n projectCollaborators(projectId: $projectId) {\n id\n errors {\n accessLevel\n email\n general\n invitedById\n planId\n userId\n }\n accessLevel\n created\n email\n user {\n givenName\n surName\n email\n }\n }\n}\n\nquery FindCollaborator($term: String!, $options: PaginationOptions) {\n findCollaborator(term: $term, options: $options) {\n limit\n items {\n id\n givenName\n email\n affiliationId\n affiliationRORId\n affiliationURL\n orcid\n surName\n affiliationName\n }\n availableSortFields\n nextCursor\n totalCount\n }\n}"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "query CustomQuestion($customQuestionId: Int!) {\n customQuestion(customQuestionId: $customQuestionId) {\n id\n json\n modified\n sampleText\n requirementText\n questionText\n guidanceText\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n required\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}"): (typeof documents)["query CustomQuestion($customQuestionId: Int!) {\n customQuestion(customQuestionId: $customQuestionId) {\n id\n json\n modified\n sampleText\n requirementText\n questionText\n guidanceText\n errors {\n general\n guidanceText\n json\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n migrationStatus\n pinnedQuestionId\n pinnedQuestionType\n required\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/generated/graphql.ts b/generated/graphql.ts index f87426729..6627d4610 100644 --- a/generated/graphql.ts +++ b/generated/graphql.ts @@ -39,16 +39,30 @@ export type Scalars = { /** Input parameters for adding a custom section to a funder template */ export type AddCustomQuestionInput = { + /** Guidance to complete the question */ + guidanceText?: InputMaybe; + /** The JSON representation of the question type */ + json?: InputMaybe; /** The identifier of the question this new custom question should appear after (null means it is the first question in the section) */ pinnedQuestionId?: InputMaybe; /** The type of the question this new custom question should appear after (null means it is the first question in the section) */ pinnedQuestionType?: InputMaybe; + /** This will be used as a sort of title for the Question */ + questionText?: InputMaybe; + /** To indicate whether the question is required to be completed */ + required?: InputMaybe; + /** Requirements associated with the Question */ + requirementText?: InputMaybe; + /** Sample text to possibly provide a starting point or example to answer question */ + sampleText?: InputMaybe; /** The identifier of the section this new custom question should appear within */ sectionId: Scalars['Int']['input']; /** The type of the section this new custom question should appear within */ sectionType: CustomizableObjectOwnership; /** The identifier of the parent template customization */ templateCustomizationId: Scalars['Int']['input']; + /** Boolean indicating whether we should use content from sampleText as the default answer */ + useSampleTextAsDefault?: InputMaybe; }; /** Input parameters for adding a custom section to a funder template */ @@ -5630,6 +5644,27 @@ export type ResendInviteToProjectCollaboratorMutationVariables = Exact<{ export type ResendInviteToProjectCollaboratorMutation = { __typename?: 'Mutation', resendInviteToProjectCollaborator?: { __typename?: 'ProjectCollaborator', id?: number | null, email: string, user?: { __typename?: 'User', id?: number | null, givenName?: string | null, surName?: string | null } | null, errors?: { __typename?: 'ProjectCollaboratorErrors', accessLevel?: string | null, email?: string | null, general?: string | null, invitedById?: string | null, planId?: string | null, userId?: string | null } | null } | null }; +export type AddCustomQuestionMutationVariables = Exact<{ + input: AddCustomQuestionInput; +}>; + + +export type AddCustomQuestionMutation = { __typename?: 'Mutation', addCustomQuestion: { __typename?: 'CustomQuestion', guidanceText?: string | null, id?: number | null, json?: string | null, migrationStatus: TemplateCustomizationMigrationStatus, modified?: string | null, pinnedQuestionId?: number | null, pinnedQuestionType?: CustomizableObjectOwnership | null, questionText?: string | null, required?: boolean | null, requirementText?: string | null, sampleText?: string | null, sectionType: CustomizableObjectOwnership, templateCustomizationId: number, useSampleTextAsDefault?: boolean | null, errors?: { __typename?: 'CustomQuestionErrors', general?: string | null, guidanceText?: string | null, json?: string | null, migrationStatus?: string | null, pinnedQuestionId?: string | null, pinnedQuestionType?: string | null, questionText?: string | null, required?: string | null, requirementText?: string | null, sampleText?: string | null, templateCustomizationId?: string | null, useSampleTextAsDefault?: string | null, sectionType?: string | null } | null } }; + +export type UpdateCustomQuestionMutationVariables = Exact<{ + input: UpdateCustomQuestionInput; +}>; + + +export type UpdateCustomQuestionMutation = { __typename?: 'Mutation', updateCustomQuestion: { __typename?: 'CustomQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, migrationStatus: TemplateCustomizationMigrationStatus, modified?: string | null, pinnedQuestionId?: number | null, pinnedQuestionType?: CustomizableObjectOwnership | null, questionText?: string | null, required?: boolean | null, requirementText?: string | null, sampleText?: string | null, sectionId: number, sectionType: CustomizableObjectOwnership, templateCustomizationId: number, useSampleTextAsDefault?: boolean | null, errors?: { __typename?: 'CustomQuestionErrors', general?: string | null, guidanceText?: string | null, questionText?: string | null, sampleText?: string | null, requirementText?: string | null, useSampleTextAsDefault?: string | null } | null } }; + +export type RemoveCustomQuestionMutationVariables = Exact<{ + customQuestionId: Scalars['Int']['input']; +}>; + + +export type RemoveCustomQuestionMutation = { __typename?: 'Mutation', removeCustomQuestion: { __typename?: 'CustomQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, migrationStatus: TemplateCustomizationMigrationStatus, modified?: string | null, pinnedQuestionId?: number | null, pinnedQuestionType?: CustomizableObjectOwnership | null, questionText?: string | null, required?: boolean | null, requirementText?: string | null, sampleText?: string | null, useSampleTextAsDefault?: boolean | null, templateCustomizationId: number, sectionId: number, errors?: { __typename?: 'CustomQuestionErrors', guidanceText?: string | null, general?: string | null, json?: string | null, migrationStatus?: string | null, pinnedQuestionId?: string | null, pinnedQuestionType?: string | null, questionText?: string | null, requirementText?: string | null, sampleText?: string | null, useSampleTextAsDefault?: string | null } | null } }; + export type AddCustomSectionMutationVariables = Exact<{ input: AddCustomSectionInput; }>; @@ -6152,6 +6187,13 @@ export type FindCollaboratorQueryVariables = Exact<{ export type FindCollaboratorQuery = { __typename?: 'Query', findCollaborator?: { __typename?: 'CollaboratorSearchResults', limit?: number | null, availableSortFields?: Array | null, nextCursor?: string | null, totalCount?: number | null, items?: Array<{ __typename?: 'CollaboratorSearchResult', id?: number | null, givenName?: string | null, email?: string | null, affiliationId?: string | null, affiliationRORId?: string | null, affiliationURL?: string | null, orcid?: string | null, surName?: string | null, affiliationName?: string | null } | null> | null } | null }; +export type CustomQuestionQueryVariables = Exact<{ + customQuestionId: Scalars['Int']['input']; +}>; + + +export type CustomQuestionQuery = { __typename?: 'Query', customQuestion?: { __typename?: 'CustomQuestion', id?: number | null, json?: string | null, modified?: string | null, sampleText?: string | null, requirementText?: string | null, questionText?: string | null, guidanceText?: string | null, migrationStatus: TemplateCustomizationMigrationStatus, pinnedQuestionId?: number | null, pinnedQuestionType?: CustomizableObjectOwnership | null, required?: boolean | null, sectionId: number, sectionType: CustomizableObjectOwnership, templateCustomizationId: number, useSampleTextAsDefault?: boolean | null, errors?: { __typename?: 'CustomQuestionErrors', general?: string | null, guidanceText?: string | null, json?: string | null, migrationStatus?: string | null, pinnedQuestionId?: string | null, pinnedQuestionType?: string | null, questionText?: string | null, required?: string | null, requirementText?: string | null, sampleText?: string | null, sectionId?: string | null, sectionType?: string | null, templateCustomizationId?: string | null, useSampleTextAsDefault?: string | null } | null } | null }; + export type CustomSectionQueryVariables = Exact<{ customSectionId: Scalars['Int']['input']; }>; @@ -6592,6 +6634,9 @@ export const AddAnswerCommentDocument = {"kind":"Document","definitions":[{"kind export const UpdateProjectCollaboratorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateProjectCollaborator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accessLevel"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollaboratorAccessLevel"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateProjectCollaborator"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectCollaboratorId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}}},{"kind":"Argument","name":{"kind":"Name","value":"accessLevel"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accessLevel"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"invitedById"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}}]}}]}}]}}]} as unknown as DocumentNode; export const RemoveProjectCollaboratorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveProjectCollaborator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeProjectCollaborator"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectCollaboratorId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"invitedById"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}}]}}]}}]}}]} as unknown as DocumentNode; export const ResendInviteToProjectCollaboratorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendInviteToProjectCollaborator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"resendInviteToProjectCollaborator"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectCollaboratorId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectCollaboratorId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"invitedById"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}}]}}]}}]} as unknown as DocumentNode; +export const AddCustomQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddCustomQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddCustomQuestionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addCustomQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}}]}}]} as unknown as DocumentNode; +export const UpdateCustomQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateCustomQuestionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCustomQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}}]}}]} as unknown as DocumentNode; +export const RemoveCustomQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveCustomQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeCustomQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}}]}}]}}]} as unknown as DocumentNode; export const AddCustomSectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddCustomSection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddCustomSectionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addCustomSection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionType"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]} as unknown as DocumentNode; export const UpdateCustomSectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomSection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateCustomSectionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCustomSection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionType"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]} as unknown as DocumentNode; export const RemoveCustomSectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveCustomSection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customSectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeCustomSection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customSectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customSectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionType"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}}]}}]}}]}}]} as unknown as DocumentNode; @@ -6661,6 +6706,7 @@ export const ManagedAffiliationsWithGuidanceDocument = {"kind":"Document","defin export const AnswerByVersionedQuestionIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AnswerByVersionedQuestionId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"planId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"answerByVersionedQuestionId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"planId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"planId"}}},{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"commentText"}},{"kind":"Field","name":{"kind":"Name","value":"answerId"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"createdById"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"feedbackComments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"commentText"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"createdById"}},{"kind":"Field","name":{"kind":"Name","value":"answerId"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"PlanFeedback"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"json"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectCollaborators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"invitedById"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"accessLevel"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindCollaboratorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindCollaborator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"term"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"options"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findCollaborator"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"term"},"value":{"kind":"Variable","name":{"kind":"Name","value":"term"}}},{"kind":"Argument","name":{"kind":"Name","value":"options"},"value":{"kind":"Variable","name":{"kind":"Name","value":"options"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"limit"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"affiliationId"}},{"kind":"Field","name":{"kind":"Name","value":"affiliationRORId"}},{"kind":"Field","name":{"kind":"Name","value":"affiliationURL"}},{"kind":"Field","name":{"kind":"Name","value":"orcid"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"affiliationName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"availableSortFields"}},{"kind":"Field","name":{"kind":"Name","value":"nextCursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; +export const CustomQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CustomQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}}]}}]} as unknown as DocumentNode; export const CustomSectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CustomSection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customSectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customSection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customSectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customSectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"guidance"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"introduction"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedSectionType"}},{"kind":"Field","name":{"kind":"Name","value":"requirements"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]} as unknown as DocumentNode; export const ProjectFundingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectFundings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectFundings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"grantId"}},{"kind":"Field","name":{"kind":"Name","value":"funderOpportunityNumber"}},{"kind":"Field","name":{"kind":"Name","value":"funderProjectNumber"}},{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}}]}}]}}]}}]} as unknown as DocumentNode; export const PlanFundingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlanFundings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"planId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"planFundings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"planId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"planId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectFunding"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/graphql/mutations/customQuestion.mutation.graphql b/graphql/mutations/customQuestion.mutation.graphql new file mode 100644 index 000000000..185753632 --- /dev/null +++ b/graphql/mutations/customQuestion.mutation.graphql @@ -0,0 +1,92 @@ +mutation AddCustomQuestion($input: AddCustomQuestionInput!) { + addCustomQuestion(input: $input) { + errors { + general + guidanceText + json + migrationStatus + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + templateCustomizationId + useSampleTextAsDefault + sectionType + } + guidanceText + id + json + migrationStatus + modified + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + sectionType + templateCustomizationId + useSampleTextAsDefault + } +} + +mutation UpdateCustomQuestion($input: UpdateCustomQuestionInput!) { + updateCustomQuestion(input: $input) { + id + guidanceText + errors { + general + guidanceText + questionText + sampleText + requirementText + useSampleTextAsDefault + } + json + migrationStatus + modified + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + sectionId + sectionType + templateCustomizationId + useSampleTextAsDefault + } +} + +mutation RemoveCustomQuestion($customQuestionId: Int!) { + removeCustomQuestion(customQuestionId: $customQuestionId) { + id + guidanceText + errors { + guidanceText + general + json + migrationStatus + pinnedQuestionId + pinnedQuestionType + questionText + requirementText + sampleText + useSampleTextAsDefault + } + json + migrationStatus + modified + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + useSampleTextAsDefault + templateCustomizationId + sectionId + } +} diff --git a/graphql/queries/customQuestion.query.graphql b/graphql/queries/customQuestion.query.graphql new file mode 100644 index 000000000..7d6b936ff --- /dev/null +++ b/graphql/queries/customQuestion.query.graphql @@ -0,0 +1,35 @@ +query CustomQuestion($customQuestionId: Int!) { + customQuestion(customQuestionId: $customQuestionId) { + id + json + modified + sampleText + requirementText + questionText + guidanceText + errors { + general + guidanceText + json + migrationStatus + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + sectionId + sectionType + templateCustomizationId + useSampleTextAsDefault + } + migrationStatus + pinnedQuestionId + pinnedQuestionType + required + sectionId + sectionType + templateCustomizationId + useSampleTextAsDefault + } +} \ No newline at end of file diff --git a/lib/graphql/errorTypePolicies.ts b/lib/graphql/errorTypePolicies.ts index 1b7c68f22..c19f5e167 100644 --- a/lib/graphql/errorTypePolicies.ts +++ b/lib/graphql/errorTypePolicies.ts @@ -15,6 +15,7 @@ export const TYPES_WITH_ERRORS = [ 'PlanMember', 'Template', 'CustomSection', + 'CustomQuestion', 'VersionedSection' ] as const; diff --git a/messages/en-US/templateBuilder.json b/messages/en-US/templateBuilder.json index eb56e7d69..692282046 100644 --- a/messages/en-US/templateBuilder.json +++ b/messages/en-US/templateBuilder.json @@ -158,6 +158,12 @@ "moveDown": "Move question down {name}" }, "messages": { + "error": { + "errorDeletingQuestion": "Error when deleting question" + }, + "success": { + "successDeletingQuestion": "Question deleted successfully" + }, "questionMoved": "Question moved to position {displayOrder}" }, "checklist": { diff --git a/utils/routes.ts b/utils/routes.ts index 8cee9c8d7..cc2398594 100644 --- a/utils/routes.ts +++ b/utils/routes.ts @@ -108,6 +108,8 @@ const routes = { 'template.customize.sectionId': '/template/customizations/:templateCustomizationId/section/:versionedSectionId', 'template.customSection': '/template/customizations/:templateCustomizationId/customSection/:customSectionId', 'template.customize.section.create': '/template/customizations/:templateCustomizationId/section/create', + 'template.customize.question.create': '/template/customizations/:templateCustomizationId/q/new', + 'template.customQuestion': '/template/customizations/:templateCustomizationId/q/:customQuestionId', // account/profile 'account.index': '/account', From 2276ab856c50e6c3701545f6e0c36c63f8b79088 Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Tue, 10 Mar 2026 19:12:39 -0700 Subject: [PATCH 2/7] Added question customization --- .../q/[versionedQuestionId]/page.tsx | 1290 ++++++----------- .../questionCustomEdit.module.scss | 6 + .../CustomizedQuestionEdit/index.tsx | 1 - .../CustomizedSectionEdit/index.tsx | 4 +- components/QuestionView/index.tsx | 178 ++- .../TinyMCEEditor/tinyMCEEditor.module.scss | 2 +- generated/gql.ts | 6 + generated/graphql.ts | 45 + .../questionCustomization.query.graphql | 115 ++ messages/en-US/templateBuilder.json | 35 + utils/routes.ts | 1 + 11 files changed, 742 insertions(+), 941 deletions(-) create mode 100644 graphql/queries/questionCustomization.query.graphql diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx index 2ef1b5e38..568bf235d 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx @@ -1,865 +1,431 @@ -// 'use client' - -// import { useEffect, useRef, useState } from 'react'; -// import { useParams, useRouter, useSearchParams } from 'next/navigation'; -// import { useTranslations } from 'next-intl'; -// import { useQuery } from '@apollo/client/react'; -// import { -// Breadcrumb, -// Breadcrumbs, -// Button, -// Dialog, -// DialogTrigger, -// Form, -// Label, -// Link, -// Modal, -// ModalOverlay, -// Tab, -// TabList, -// TabPanel, -// Tabs, -// Text, -// } from "react-aria-components"; - -// // GraphQL -// import { -// QuestionDocument, -// } from '@/generated/graphql'; - -// import { -// removeQuestionAction, -// updateQuestionAction -// } from './actions'; - -// import { -// AnyParsedQuestion, -// Question, -// QuestionOption, -// QuestionOptions, -// QuestionFormatInterface, -// RemoveQuestionErrors, -// UpdateQuestionErrors, -// } from '@/app/types'; - -// // Components -// import PageHeader from "@/components/PageHeader"; -// import QuestionOptionsComponent -// from '@/components/Form/QuestionOptionsComponent'; -// import QuestionPreview from '@/components/QuestionPreview'; -// import { -// FormInput, -// RadioGroupComponent, -// RangeComponent, -// TypeAheadSearch, -// ResearchOutputComponent -// } from '@/components/Form'; -// import FormTextArea from '@/components/Form/FormTextArea'; -// import ErrorMessages from '@/components/ErrorMessages'; -// import TinyMCEEditor from "@/components/TinyMCEEditor"; -// import QuestionView from '@/components/QuestionView'; -// import { getParsedQuestionJSON } from '@/components/hooks/getParsedQuestionJSON'; - -// //Utils and Other -// import { useResearchOutputTable } from '@/app/hooks/useResearchOutputTable'; -// import { useToast } from '@/context/ToastContext'; -// import { routePath } from '@/utils/routes'; -// import { stripHtmlTags } from '@/utils/general'; -// import logECS from '@/utils/clientLogger'; -// import { extractErrors } from "@/utils/errorHandler"; -// import { -// getQuestionFormatInfo, -// getQuestionTypes, -// questionTypeHandlers -// } from '@/utils/questionTypeHandlers'; - -// import { -// RANGE_QUESTION_TYPE, -// TYPEAHEAD_QUESTION_TYPE, -// DATE_RANGE_QUESTION_TYPE, -// NUMBER_RANGE_QUESTION_TYPE, -// TEXT_AREA_QUESTION_TYPE, -// RESEARCH_OUTPUT_QUESTION_TYPE, -// QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD, -// } from '@/lib/constants'; -// import { -// isOptionsType, -// getOverrides, -// } from './hooks/useEditQuestion'; -// import styles from './questionCustomEdit.module.scss'; -// import ResearchOutputDisplay from '@/components/Form/ResearchOutputDisplay'; - -// const QuestionCustomizePage = () => { -// const params = useParams(); -// const router = useRouter(); -// const searchParams = useSearchParams(); -// const toastState = useToast(); // Access the toast state from context -// const templateId = String(params.templateId); -// const questionId = String(params.questionId); //question id -// const questionTypeIdQueryParam = searchParams.get('questionType') || null; - -// //For scrolling to error in page -// const errorRef = useRef(null); - -// const hasHydrated = useRef(false); -// // Track whether there are unsaved changes -// const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); -// // Form state -// const [isSubmitting, setIsSubmitting] = useState(false); - -// // State for managing form inputs -// const [question, setQuestion] = useState(); -// const [rows, setRows] = useState([{ id: 0, text: "", isSelected: false }]); -// const [questionType, setQuestionType] = useState(''); -// const [questionTypeName, setQuestionTypeName] = useState(''); // Added to store friendly question name -// const [formSubmitted, setFormSubmitted] = useState(false); -// const [hasOptions, setHasOptions] = useState(false); -// const [errors, setErrors] = useState([]); -// const [dateRangeLabels, setDateRangeLabels] = useState<{ start: string; end: string }>({ start: '', end: '' }); -// const [typeaheadHelpText, setTypeAheadHelpText] = useState(''); -// const [typeaheadSearchLabel, setTypeaheadSearchLabel] = useState(''); -// const [parsedQuestionJSON, setParsedQuestionJSON] = useState(); -// const [isConfirmOpen, setConfirmOpen] = useState(false); - -// // Add state for live region announcements -// const [announcement, setAnnouncement] = useState(''); - -// // localization keys -// const Global = useTranslations('Global'); -// const t = useTranslations('QuestionEdit'); -// const QuestionAdd = useTranslations('QuestionAdd'); - -// // Set URLs -// const TEMPLATE_URL = routePath('template.show', { templateId }); - -// // Helper function to make announcements -// const announce = (message: string) => { -// setAnnouncement(message); -// // Clear after announcement is made -// setTimeout(() => setAnnouncement(''), 100); -// }; - -// // Research Output Table Hooks -// const { -// buildResearchOutputFormState, -// hydrateFromJSON, -// licensesData, -// defaultResearchOutputTypesData, -// expandedFields, -// nonCustomizableFieldIds, -// standardFields, -// additionalFields, -// newOutputType, -// setNewOutputType, -// newLicenseType, -// setNewLicenseType, -// handleRepositoriesChange, -// handleMetaDataStandardsChange, -// handleStandardFieldChange, -// handleCustomizeField, -// handleToggleMetaDataStandards, -// handleTogglePreferredRepositories, -// handleLicenseModeChange, -// handleAddCustomLicenseType, -// handleRemoveCustomLicenseType, -// handleOutputTypeModeChange, -// handleAddCustomOutputType, -// handleRemoveCustomOutputType, -// addAdditionalField, -// handleDeleteAdditionalField, -// handleUpdateAdditionalField, -// updateStandardFieldProperty -// } = useResearchOutputTable({ setHasUnsavedChanges, announce }); - -// // Run selected question query -// const { -// data: selectedQuestion, -// loading, -// error: selectedQuestionQueryError -// } = useQuery(QuestionDocument, { -// variables: { -// questionId: Number(questionId) -// } -// }); - -// const RichTextDisplay: React.FC<{ label: string; content: string; helpText?: string }> = ({ -// label, -// content, -// helpText -// }) => ( -//
-// -// {helpText && {helpText}} -//
-//
-// ); - - -// // Update rows state and question.json when options change -// const updateRows = (newRows: QuestionOptions[]) => { -// setRows(newRows); - -// if (hasOptions && questionType && question?.json) { -// const updatedJSON = buildUpdatedJSON(question, newRows); - -// if (updatedJSON) { -// setQuestion((prev) => ({ -// ...prev, -// json: JSON.stringify(updatedJSON.data), -// })); -// setHasUnsavedChanges(true); -// } -// } -// }; - -// // Return user back to the page to select a question type -// const redirectToQuestionTypes = () => { -// const sectionId = selectedQuestion?.question?.sectionId; -// // questionId as query param included to let page know that user is updating an existing question -// router.push(routePath('template.q.new', { templateId }, { section_id: sectionId, step: 1, questionId })) -// } - -// //Handle change to Question Text -// const handleQuestionTextChange = (value: string) => { -// setQuestion(prev => ({ -// ...prev, -// questionText: value -// })); -// setHasUnsavedChanges(true); -// }; - -// // Update common input fields when any of them change -// const handleInputChange = (field: keyof Question, value: string | boolean | undefined) => { -// setQuestion((prev) => ({ -// ...prev, -// [field]: value === undefined ? '' : value, // Default to empty string if value is undefined -// })); -// setHasUnsavedChanges(true); -// }; - - -// // Handle changes from RadioGroup -// const handleRadioChange = (value: string) => { -// if (value) { -// const isRequired = value === 'yes' ? true : false; -// setQuestion(prev => ({ -// ...prev, -// required: isRequired -// })); -// setHasUnsavedChanges(true); -// } -// }; - -// // Handler for date range label changes -// const handleRangeLabelChange = (field: 'start' | 'end', value: string) => { -// setDateRangeLabels(prev => ({ ...prev, [field]: value })); - -// if (parsedQuestionJSON && (parsedQuestionJSON?.type === "dateRange" || parsedQuestionJSON?.type === "numberRange")) { -// if (parsedQuestionJSON?.columns?.[field]) { -// const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly -// updatedParsed.columns[field].label = value; -// setQuestion(prev => ({ -// ...prev, -// json: JSON.stringify(updatedParsed), -// })); -// setHasUnsavedChanges(true); -// } -// } -// }; - -// // Handler for typeahead search label changes -// const handleTypeAheadSearchLabelChange = (value: string) => { -// setTypeaheadSearchLabel(value); - -// if (parsedQuestionJSON && parsedQuestionJSON?.type === "affiliationSearch") { -// const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly -// updatedParsed.attributes.label = value; -// setQuestion(prev => ({ -// ...prev, -// json: JSON.stringify(updatedParsed), -// })); -// setHasUnsavedChanges(true); -// } -// }; - -// // Handler for typeahead help text changes -// const handleTypeAheadHelpTextChange = (value: string) => { -// setTypeAheadHelpText(value); - -// if (parsedQuestionJSON && parsedQuestionJSON?.type === "affiliationSearch") { -// if (parsedQuestionJSON?.attributes?.help) { -// const updatedParsed = structuredClone(parsedQuestionJSON); // To avoid mutating state directly -// updatedParsed.attributes.help = value; -// setQuestion(prev => ({ -// ...prev, -// json: JSON.stringify(updatedParsed), -// })); -// setHasUnsavedChanges(true); -// } -// } -// }; - -// // Prepare input for the questionTypeHandler. For options questions, we update the -// // values with rows state. For non-options questions, we use the parsed JSON -// const getFormState = (question: Question, rowsOverride?: QuestionOptions[]) => { -// if (hasOptions) { -// const useRows = rowsOverride ?? rows; -// return { -// options: useRows.map(row => ({ -// label: row.text, -// value: row.text, -// selected: row.isSelected, -// })), -// }; -// } - -// const { parsed, error } = getParsedQuestionJSON(question, routePath('template.q.slug', { templateId, q_slug: questionId }), Global); - -// if (questionType === RESEARCH_OUTPUT_QUESTION_TYPE) { -// return buildResearchOutputFormState(); -// } - -// if (!parsed) { -// if (error) { -// setErrors(prev => [...prev, error]) -// } -// return; -// } -// return { -// ...parsed, -// attributes: { -// ...('attributes' in parsed ? parsed.attributes : {}), -// ...getOverrides(questionType), -// }, -// }; -// }; - -// // Pass the merged userInput to questionTypeHandlers to generate json and do type and schema validation -// const buildUpdatedJSON = (question: Question, rowsOverride?: QuestionOptions[]) => { -// const userInput = getFormState(question, rowsOverride); -// const { parsed, error } = getParsedQuestionJSON(question, routePath('template.q.slug', { templateId, q_slug: questionId }), Global); - -// if (!parsed) { -// if (error) { -// setErrors(prev => [...prev, error]) -// } -// return; -// } -// return questionTypeHandlers[questionType as keyof typeof questionTypeHandlers]( -// parsed, -// userInput -// ); -// }; - -// // Handle form submission to update the question -// const handleUpdate = async (e: React.FormEvent) => { -// e.preventDefault(); -// // Prevent double submission -// if (isSubmitting) return; -// setIsSubmitting(true); - -// // Set formSubmitted to true to indicate the form has been submitted -// setFormSubmitted(true); - -// if (question) { -// const updatedJSON = buildUpdatedJSON(question); -// const { success, error } = updatedJSON ?? {}; - -// if (success && !error) { -// // Strip all tags from questionText before sending to backend -// const cleanedQuestionText = stripHtmlTags(question.questionText ?? ''); - -// // Add mutation for question -// const response = await updateQuestionAction({ -// questionId: Number(questionId), -// displayOrder: Number(question.displayOrder), -// json: JSON.stringify(updatedJSON ? updatedJSON.data : ''), -// questionText: cleanedQuestionText, -// requirementText: String(question.requirementText), -// guidanceText: String(question.guidanceText), -// sampleText: String(question.sampleText), -// useSampleTextAsDefault: question?.useSampleTextAsDefault || false, -// required: Boolean(question.required) -// }); - -// if (response.redirect) { -// router.push(response.redirect); -// } - -// if (!response.success) { -// const errors = response.errors; -// // Announcement for screen readers -// announce(QuestionAdd('researchOutput.announcements.errorOccurred') || 'An error occurred. Please check the form.'); - -// //Check if errors is an array or an object -// if (Array.isArray(errors)) { -// //Handle errors as an array -// setErrors(errors); -// } -// } else { -// if (response?.data?.errors) { -// const errs = extractErrors(response?.data?.errors, ["general", "questionText"]); -// if (errs.length > 0) { -// setErrors(errs); -// } -// } -// setIsSubmitting(false); -// setHasUnsavedChanges(false); -// toastState.add(QuestionAdd('messages.success.questionUpdated'), { type: 'success' }); - -// // Redirect user to the Edit Question view with their new question id after successfully adding the new question -// router.push(TEMPLATE_URL); -// } -// } -// } -// }; - -// // Handle form submission to delete the question -// const handleDelete = async () => { -// const response = await removeQuestionAction({ -// questionId: Number(questionId), -// }); - -// if (response.redirect) { -// router.push(response.redirect); -// return; -// } - -// if (!response.success) { -// const errors = response.errors; - -// //Check if errors is an array or an object -// if (Array.isArray(errors)) { -// //Handle errors as an array -// setErrors(errors); -// } -// } else { -// if (response?.data?.errors) { -// const errs = extractErrors(response?.data?.errors, ["general", "guidanceText", "questionText", "requirementText", "sampleText"]); -// if (errs.length > 0) { -// setErrors(errs); -// } else { -// // Show success message and redirect to Edit Template page -// toastState.add(t('messages.success.questionRemoved'), { type: 'success' }); -// router.push(TEMPLATE_URL); -// } -// } -// } -// }; - -// // Saves any query errors to errors state -// useEffect(() => { -// const allErrors = []; - -// if (selectedQuestionQueryError) { -// allErrors.push(selectedQuestionQueryError.message); -// } - -// setErrors(allErrors); -// }, [selectedQuestionQueryError]); - -// // Set question details in state when data is loaded -// useEffect(() => { -// if (selectedQuestion?.question) { -// const q = { -// ...selectedQuestion.question, -// required: selectedQuestion.question.required ?? false // convert null to false -// }; -// try { -// const { parsed, error } = getParsedQuestionJSON(q, routePath('template.show', { templateId }), Global); -// if (!parsed?.type) { -// if (error) { -// logECS('error', 'Parsing error', { -// error: 'Invalid question type in parsed JSON', -// url: { path: routePath('template.q.slug', { templateId, q_slug: questionId }) } -// }); - -// setErrors(prev => [...prev, error]) -// } -// return; -// } - -// const questionType = parsed.type; -// const translationKey = `questionTypes.${questionType}`; -// const questionTypeFriendlyName = Global(translationKey); - -// setQuestionType(questionType); -// setQuestionTypeName(questionTypeFriendlyName); -// setParsedQuestionJSON(parsed); - -// const isOptionsQuestion = isOptionsType(questionType); -// setQuestion({ -// ...q, -// showCommentField: 'showCommentField' in parsed ? parsed.showCommentField : false // Default to false if not present -// }); - -// setHasOptions(isOptionsQuestion); - -// if (questionType === TYPEAHEAD_QUESTION_TYPE) { -// setTypeaheadSearchLabel(parsed?.attributes?.label ?? ''); -// setTypeAheadHelpText(parsed?.attributes?.help ?? ''); -// } - -// // Set options info with proper type checking -// if (isOptionsQuestion && 'options' in parsed && parsed.options && Array.isArray(parsed.options)) { -// const optionRows: QuestionOptions[] = parsed.options -// .map((option: QuestionOption, index: number) => ({ -// id: index, -// text: option?.label || '', -// isSelected: option?.selected || option?.checked || false, -// })); -// setRows(optionRows); -// } -// } catch (error) { -// logECS('error', 'Parsing error', { -// error, -// url: { path: routePath('template.q.slug', { templateId, q_slug: questionId }) } -// }); -// setErrors(prev => [...prev, 'Error parsing question data']); -// } -// } -// }, [selectedQuestion]); - -// useEffect(() => { -// if (questionType) { -// // To determine if we have an options question type -// const isOptionQuestion = isOptionsType(questionType); - -// setHasOptions(isOptionQuestion); -// } - -// }, [questionType]) - -// // Set labels for dateRange and numberRange -// useEffect(() => { -// if ((parsedQuestionJSON?.type === DATE_RANGE_QUESTION_TYPE || parsedQuestionJSON?.type === NUMBER_RANGE_QUESTION_TYPE)) { -// try { -// setDateRangeLabels({ -// start: parsedQuestionJSON?.columns?.start?.label ?? '', -// end: parsedQuestionJSON?.columns?.end?.label ?? '', -// }); -// } catch { -// setDateRangeLabels({ start: '', end: '' }); -// } -// } -// }, [parsedQuestionJSON]) - -// // Research Output Question - Hydrate state from JSON -// useEffect(() => { -// if (!hasHydrated.current && -// parsedQuestionJSON?.type === RESEARCH_OUTPUT_QUESTION_TYPE && -// Array.isArray(parsedQuestionJSON.columns)) { -// try { -// hydrateFromJSON(parsedQuestionJSON); -// hasHydrated.current = true; -// } catch (error) { -// console.error('Error hydrating research output fields from JSON', error); -// } -// } -// }, [parsedQuestionJSON, hydrateFromJSON]); - -// // If a user changes their question type, then we need to fetch the question types to set the new json schema -// useEffect(() => { -// // Only fetch question types if we have a questionType query param present -// if (questionTypeIdQueryParam) { -// getQuestionTypes(); -// } -// }, [questionTypeIdQueryParam]); - - -// // If a user passes in a questionType query param we will find the matching questionTypes -// // json schema and update the question with it -// useEffect(() => { -// if (questionType && questionTypeIdQueryParam && question) { -// // Find the matching question type -// const qInfo: QuestionFormatInterface | null = getQuestionFormatInfo(questionTypeIdQueryParam); - -// if (qInfo?.defaultJSON) { -// // Update the question object with the new JSON -// setQuestion(prev => ({ -// ...prev, -// json: JSON.stringify(qInfo.defaultJSON) -// })); - -// setHasUnsavedChanges(true); - -// setQuestionType(questionTypeIdQueryParam) - -// // Update the questionTypeName -// const questionTypeFriendlyName = Global(`questionTypes.${questionTypeIdQueryParam}`); -// setQuestionTypeName(questionTypeFriendlyName); - -// const isOptionsQuestion = isOptionsType(questionTypeIdQueryParam) -// setHasOptions(isOptionsQuestion); - -// } -// } -// }, [questionType, questionTypeIdQueryParam]); - -// // Set parsed question JSON whenever question state changes -// useEffect(() => { -// if (question) { -// const { parsed, error } = getParsedQuestionJSON(question, routePath('template.show', { templateId }), Global); -// if (!parsed) { -// if (error) { -// setErrors(prev => [...prev, error]) -// } -// return; -// } -// setParsedQuestionJSON(parsed); -// } -// }, [question]) - -// // Warn user of unsaved changes if they try to leave the page -// useEffect(() => { -// const handleBeforeUnload = (e: BeforeUnloadEvent) => { -// if (hasUnsavedChanges) { -// e.preventDefault(); -// e.returnValue = ''; // Required for Chrome/Firefox to show the confirm dialog -// } -// }; - -// window.addEventListener('beforeunload', handleBeforeUnload); -// return () => { -// window.removeEventListener('beforeunload', handleBeforeUnload); -// }; -// }, [hasUnsavedChanges]); - -// if (loading) { -// return
{Global('messaging.loading')}...
; -// } - -// return ( -// <> -// -// {Global('breadcrumbs.home')} -// {Global('breadcrumbs.templates')} -// {Global('breadcrumbs.editTemplate')} -// {Global('breadcrumbs.question')} -// -// } -// actions={null} -// className="" -// /> - -// {/* Live region for announcements - visually hidden but read by screen readers */} -//
-// {announcement} -//
-// - -//
-//
-// -// -// {Global('tabs.editQuestion')} -// {Global('tabs.options')} -// {Global('tabs.logic')} -// - -// -//
-//
-// -//

{questionTypeName}

-//
- -// {/**Question type fields here */} -// {hasOptions && rows.length > 0 && ( -//
-// -//
    -// {rows.map(row => ( -//
  • -// {row.isSelected && Selected} -// {row.text} -//
  • -// ))} -//
-//
-// )} - -// {/**Date and Number range question types */} -// {questionType && RANGE_QUESTION_TYPE.includes(questionType) && ( -//
-// -//

Start: {dateRangeLabels.start}

-//

End: {dateRangeLabels.end}

-//
-// )} - - -// {/**Typeahead search question type */} -// {questionType === TYPEAHEAD_QUESTION_TYPE && ( -// <> -//
-// -//

{typeaheadSearchLabel}

-//
-//
-// -//

{typeaheadHelpText}

-//
-// -// )} - -// {!QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD.includes(questionType ?? '') && ( -//
-// -//

{question?.showCommentField ? 'Yes' : 'No'}

-//
-// )} - -// {question?.requirementText && ( -// -// )} - -// -// console.log(value)} -// id="customQuestionRequirements" -// labelId="customQuestionRequirementsLabel" -// helpText="Add additional requirements that will appear on the Section Overview page" -// /> - -// {/* Guidance Text */} -// {question?.guidanceText && ( -// -// )} - -// -// console.log(value)} -// id="customQuestionGuidance" -// labelId="customQuestionGuidanceLabel" -// helpText="Add additional guidance that will appear on the Question page" -// /> - -// {/* Sample Text */} -// {questionType === TEXT_AREA_QUESTION_TYPE && question?.sampleText && ( -// <> -// -// -// )} - -// {questionType === RESEARCH_OUTPUT_QUESTION_TYPE && ( -// -// )} - -//
-// -//

{question?.required ? Global('form.yesLabel') : Global('form.noLabel')}

-//
- -// -// - - -//
-// -//

{Global('tabs.options')}

-//
-// -//

{Global('tabs.logic')}

-//
-//
- -//
-//

Delete customization

-//

Your customizations to this Question will be removed from the template. This is not reversible.

-// -// -// -// -// -// {({ close }) => ( -// <> -//

{t('headings.confirmDelete')}

-//

{t('descriptions.deleteWarning')}

-//
-// -// -//
-// -// )} -//
-//
-//
-//
-//
- -//
- - - -//
-//

{Global('headings.preview')}

-//

{t('descriptions.previewText')}

-// -// -// - -//

{t('headings.bestPractice')}

-//

{t('descriptions.bestPracticePara1')}

-//

{t('descriptions.bestPracticePara2')}

-//

{t('descriptions.bestPracticePara3')}

-//
-//
-// - -// ); -// } - -// export default QuestionCustomizePage; - -const QuestionCustomizePage = () => { - return

TBD

; +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { + Breadcrumb, + Breadcrumbs, + Button, + Checkbox, + Dialog, + DialogTrigger, + Form, + Link, + Modal, + ModalOverlay, +} from "react-aria-components"; + +// GraphQL +import { + AddQuestionCustomizationDocument, + UpdateQuestionCustomizationDocument, + RemoveQuestionCustomizationDocument, + QuestionCustomizationByVersionedQuestionDocument, + PublishedQuestionDocument, + QuestionCustomizationErrors, +} from '@/generated/graphql'; + +// Components +import PageHeader from "@/components/PageHeader"; +import { ContentContainer, LayoutContainer } from '@/components/Container'; +import FormTextArea from '@/components/Form/FormTextArea'; +import ErrorMessages from '@/components/ErrorMessages'; +import Loading from '@/components/Loading'; +import { SanitizeHTML } from '@/utils/sanitize'; +import { DmpIcon } from "@/components/Icons"; +import QuestionView from '@/components/QuestionView'; +import { Question } from '@/app/types'; +// Utils +import logECS from '@/utils/clientLogger'; +import { useToast } from '@/context/ToastContext'; +import { scrollToTop } from '@/utils/general'; +import { routePath } from '@/utils/routes'; +import { extractErrors } from '@/utils/errorHandler'; +import { stripHtmlTags } from '@/utils/general'; +import styles from './questionCustomEdit.module.scss'; + +const QuestionCustomizePage: React.FC = () => { + const toastState = useToast(); + const params = useParams(); + const router = useRouter(); + + const templateCustomizationId = String(params.templateCustomizationId); + const versionedQuestionId = String(params.versionedQuestionId); + + const hasInitialized = useRef(false); + const errorRef = useRef(null); + const topRef = useRef(null); + + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [isRedirecting, setIsRedirecting] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [questionCustomizationId, setQuestionCustomizationId] = useState(null); + const [errorMessages, setErrorMessages] = useState([]); + + const [baseQuestion, setBaseQuestion] = useState(undefined); + + + // Customizable fields + const [customQuestionData, setCustomQuestionData] = useState({ + guidanceText: '', + sampleText: '', + }); + + // Read-only base question data + const [baseQuestionData, setBaseQuestionData] = useState({ + questionText: '', + requirementText: '', + guidanceText: '', + sampleText: '', + }); + + // Localization + const Global = useTranslations('Global'); + const QuestionCustomize = useTranslations('QuestionCustomize'); + const QuestionEdit = useTranslations('QuestionEdit'); + const PlanOverview = useTranslations('PlanOverview'); + + // URLs + const TEMPLATE_URL = routePath('template.customize', { templateCustomizationId }); + const UPDATE_QUESTION_URL = routePath('template.customize.question', { templateCustomizationId, versionedQuestionId }); + + // Mutations + const [addQuestionCustomization] = useMutation(AddQuestionCustomizationDocument); + const [updateQuestionCustomization] = useMutation(UpdateQuestionCustomizationDocument); + const [removeQuestionCustomization] = useMutation(RemoveQuestionCustomizationDocument); + + // Queries + const { data: publishedQuestion, loading: publishedQuestionLoading } = useQuery(PublishedQuestionDocument, { + variables: { versionedQuestionId: Number(versionedQuestionId) }, + }); + + const { data: questionCustomization, loading: questionCustomizationLoading } = useQuery( + QuestionCustomizationByVersionedQuestionDocument, + { + variables: { + templateCustomizationId: Number(templateCustomizationId), + versionedQuestionId: Number(versionedQuestionId), + }, + } + ); + + console.log("***QuestionCustomization data: ", questionCustomization); + + const updateCustomQuestionContent = (key: string, value: string | boolean) => { + setCustomQuestionData(prev => ({ ...prev, [key]: value })); + setHasUnsavedChanges(true); + }; + + const handleSave = async (): Promise<[Record, boolean]> => { + try { + const response = await updateQuestionCustomization({ + variables: { + input: { + questionCustomizationId: Number(questionCustomizationId), + guidanceText: customQuestionData.guidanceText, + sampleText: customQuestionData.sampleText, + }, + }, + }); + + const responseErrors = response.data?.updateQuestionCustomization?.errors; + if (responseErrors && Object.values(responseErrors).some(err => err && err !== 'QuestionCustomizationErrors')) { + return [responseErrors, false]; + } + + return [{}, true]; + } catch (error) { + logECS('error', 'updateQuestionCustomization', { + error, + url: { path: UPDATE_QUESTION_URL }, + }); + setErrorMessages(prev => [...prev, QuestionCustomize('messages.error.errorUpdatingCustomization')]); + return [{}, false]; + } + }; + + const handleDeleteQuestionCustomization = async () => { + if (isDeleting) return; + setIsDeleting(true); + setErrorMessages([]); + + try { + const response = await removeQuestionCustomization({ + variables: { questionCustomizationId: Number(questionCustomizationId) }, + }); + + const responseErrors = response.data?.removeQuestionCustomization?.errors; + if (responseErrors && Object.keys(responseErrors).length > 0) { + const errs = extractErrors(responseErrors, ['general', 'guidanceText', 'sampleText']); + if (errs.length > 0) { + setErrorMessages(errs); + return; + } + } + + toastState.add(QuestionCustomize('messages.success.successfullyDeletedCustomization'), { type: 'success' }); + setIsRedirecting(true); + router.push(TEMPLATE_URL); + } catch (error) { + logECS('error', 'deleteQuestionCustomization', { + error, + url: { path: UPDATE_QUESTION_URL }, + }); + setErrorMessages([QuestionCustomize('messages.error.errorDeletingCustomization')]); + } finally { + setIsDeleting(false); + setIsDeleteModalOpen(false); + } + }; + + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMessages([]); + + const [errors, success] = await handleSave(); + + if (!success) { + setErrorMessages([errors?.general ?? QuestionCustomize('messages.error.errorUpdatingCustomization')]); + setIsSubmitting(false); + } else { + setHasUnsavedChanges(false); + toastState.add(QuestionCustomize('messages.success.successfullyUpdatedCustomization'), { type: 'success' }); + setIsRedirecting(true); + router.push(TEMPLATE_URL); + } + }; + + // Load base question data from published question query + useEffect(() => { + if (publishedQuestion?.publishedQuestion) { + const q = publishedQuestion.publishedQuestion; + setBaseQuestionData({ + questionText: stripHtmlTags(q.questionText ?? ''), + requirementText: q.requirementText ?? '', + guidanceText: q.guidanceText ?? '', + sampleText: q.sampleText ?? '', + }); + } + }, [publishedQuestion]); + + // Initialize or load existing customization + useEffect(() => { + const initializeCustomization = async () => { + if (hasInitialized.current || questionCustomizationLoading) return; + hasInitialized.current = true; + + if (!questionCustomization?.questionCustomizationByVersionedQuestion) { + // No existing customization — create one + const response = await addQuestionCustomization({ + variables: { + input: { + templateCustomizationId: Number(templateCustomizationId), + versionedQuestionId: Number(versionedQuestionId), + }, + }, + }); + setQuestionCustomizationId(response.data?.addQuestionCustomization?.id ?? null); + } else { + const existing = questionCustomization.questionCustomizationByVersionedQuestion; + setQuestionCustomizationId(existing.id ?? null); + setCustomQuestionData({ + guidanceText: existing.guidanceText ?? '', + sampleText: existing.sampleText ?? '', + }); + } + }; + + initializeCustomization(); + }, [questionCustomizationLoading, questionCustomization]); + + // Warn on unsaved changes + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (hasUnsavedChanges) { + e.preventDefault(); + e.returnValue = ''; + } + }; + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }, [hasUnsavedChanges]); + + // Scroll errors into view + useEffect(() => { + if (errorMessages.length > 0) scrollToTop(errorRef); + }, [errorMessages]); + + useEffect(() => { + if (publishedQuestion?.publishedQuestion) { + const q = publishedQuestion.publishedQuestion; + // Map to the Question type QuestionView expects + setBaseQuestion({ + questionText: q.questionText ?? '', + requirementText: q.requirementText ?? null, + guidanceText: q.guidanceText ?? null, + sampleText: q.sampleText ?? null, + useSampleTextAsDefault: q.useSampleTextAsDefault ?? false, + required: q.required ?? false, + json: q.json ?? '', + }); + } + }, [publishedQuestion]); + + + if (publishedQuestionLoading) return ; + if (isRedirecting) return ; + + return ( + <> + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templateCustomizations')} + {Global('breadcrumbs.editTemplate')} + {QuestionCustomize('title')} + + } + actions={null} + className="" + /> + + + +
+
+ + +
+ {baseQuestionData.requirementText && ( +
+

{QuestionCustomize('labels.requirements')}

+ +
+ )} + + {baseQuestionData.guidanceText && ( +
+

{QuestionCustomize('labels.guidance')}

+ +
+ )} + + {baseQuestionData.sampleText && ( +
+

{QuestionCustomize('labels.sampleText')}

+ +
+ )} + + {baseQuestion && ( +
+
+ +
+
+ )} + + + + {/* Editable customization fields */} +
+ updateCustomQuestionContent('guidanceText', value)} + /> +
+ +
+ updateCustomQuestionContent('sampleText', value)} + /> + + {/* updateCustomQuestionContent('useSampleTextAsDefault', checked)} + > +
+ +
+ {QuestionCustomize('labels.useSampleTextAsDefault')} +
*/} +
+ + +
+ + {/* Delete customization */} +
+

{QuestionCustomize('buttons.deleteCustomization')}

+

{QuestionCustomize.rich("descriptions.deleteCustomization", { + strong: (chunks) => {chunks} + })}

+ + + + + + {({ close }) => ( + <> +

{QuestionCustomize('heading.deleteCustomization')}

+

{QuestionCustomize.rich("descriptions.deleteCustomization", { + strong: (chunks) => {chunks} + })}

+
+ + +
+ + )} +
+
+
+
+
+
+
+
+
+ + ); }; export default QuestionCustomizePage; \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss index 79f542e29..6d89f96f0 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss @@ -64,3 +64,9 @@ padding: var(--space-4); margin-bottom: var(--space-4); } + +.deleteQuestionCustomizationContainer { + margin-top: 2rem; + padding-top: 1.5rem; + margin-bottom: 2rem; +} diff --git a/components/CustomizedTemplate/CustomizedQuestionEdit/index.tsx b/components/CustomizedTemplate/CustomizedQuestionEdit/index.tsx index b2d62122b..58e159669 100644 --- a/components/CustomizedTemplate/CustomizedQuestionEdit/index.tsx +++ b/components/CustomizedTemplate/CustomizedQuestionEdit/index.tsx @@ -31,7 +31,6 @@ const CustomizedQuestionEdit: React.FC = ({ hasCustomSampleAnswer, }) => { - const questionText = stripHtml(text); const questionId = Number(id); const questionDisplayOrder = Number(displayOrder); diff --git a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx index 1eb4a9bd0..16ae4efd7 100644 --- a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx +++ b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx @@ -51,6 +51,7 @@ const CustomizedSectionEdit: React.FC = ({ const [moveCustomQuestionMutation] = useMutation(MoveCustomQuestionDocument); + console.log("***Section", section); // Memoize the sorted questions to prevent unnecessary re-renders const sortedQuestionsFromData = useMemo(() => { @@ -186,7 +187,6 @@ const CustomizedSectionEdit: React.FC = ({ ? `/template/customizations/${templateCustomizationId}/customSection/${section.id}`// Custom Section : `/template/customizations/${templateCustomizationId}/section/${section.id}`; // Section Customization - console.log("***localQuestions in SectionEdit: ", localQuestions); return ( <>
@@ -209,7 +209,7 @@ const CustomizedSectionEdit: React.FC = ({ text={question.questionText ?? ''} link={ question.questionType === 'BASE' - ? `/template/customizations/q/${question.id}` + ? `/template/customizations/${templateCustomizationId}/q/${question.id}` : `/template/customizations/${templateCustomizationId}/customQuestion/${question.id}` } displayOrder={Number(question.displayOrder)} diff --git a/components/QuestionView/index.tsx b/components/QuestionView/index.tsx index 60a7b23b3..ae799cbf0 100644 --- a/components/QuestionView/index.tsx +++ b/components/QuestionView/index.tsx @@ -90,6 +90,8 @@ interface QuestionViewProps extends React.HTMLAttributes { * templateId will be available in the question object. */ templateId?: number, + cardOnly?: boolean // If true, only render the question card without surrounding layout and guidance sections + noSidebar?: boolean; // If true, do not render the sidebar at all } //This component is meant to work with the QuestionAdd and QuestionEdit components, to display @@ -100,7 +102,9 @@ const QuestionView: React.FC = ({ isPreview = false, question, templateId, - path = '' + path = '', + cardOnly = false, + noSidebar = false, }) => { const trans = useTranslations('QuestionView'); @@ -494,6 +498,28 @@ const QuestionView: React.FC = ({ } }; + // Only return the question card if cardOnly is true. This allows us to reuse this component in other places where we just want to show the question card without the surrounding layout and guidance sections + if (cardOnly) { + return ( + + {trans('cardType')} + {question?.questionText} + + {renderQuestionField()} + {question?.showCommentField && ( + + )} + + + ); + } + return ( = ({ )} - -
-

{Global('bestPractice')}

- DMP Tool -
- - -

- Give a summary of the data you will collect or create, noting the content, coverage and data type, e.g., tabular data, survey data, experimental measurements, models, software, audiovisual data, physical samples, etc. -

-

- Consider how your data could complement and integrate with existing data, or whether there are any existing data or methods that you could reuse. -

-

- Indicate which data are of long-term value and should be shared and/or preserved. - -

-

- If purchasing or reusing existing data, explain how issues such as copyright and IPR have been addressed. You should aim to minimize any restrictions on the reuse (and subsequent sharing) of third-party data. - -

- -
- - -

- Clearly note what format(s) your data will be in, e.g., plain text (.txt), comma-separated values (.csv), geo-referenced TIFF (.tif, .tfw). -

- -
- - -

- Note what volume of data you will create in MB/GB/TB. Indicate the proportions of raw data, processed data, and other secondary outputs (e.g., reports). -

-

- Consider the implications of data volumes in terms of storage, access, and preservation. Do you need to include additional costs? -

-

- Consider whether the scale of the data will pose challenges when sharing or transferring data between sites; if so, how will you address these challenges? -

-
- - -

This is a very long sentence that should be truncated at word boundaries.

- -
-
+ {!noSidebar && ( + +
+

{Global('bestPractice')}

+ DMP Tool +
+ + +

+ Give a summary of the data you will collect or create, noting the content, coverage and data type, e.g., tabular data, survey data, experimental measurements, models, software, audiovisual data, physical samples, etc. +

+

+ Consider how your data could complement and integrate with existing data, or whether there are any existing data or methods that you could reuse. +

+

+ Indicate which data are of long-term value and should be shared and/or preserved. + +

+

+ If purchasing or reusing existing data, explain how issues such as copyright and IPR have been addressed. You should aim to minimize any restrictions on the reuse (and subsequent sharing) of third-party data. + +

+ +
+ + +

+ Clearly note what format(s) your data will be in, e.g., plain text (.txt), comma-separated values (.csv), geo-referenced TIFF (.tif, .tfw). +

+ +
+ + +

+ Note what volume of data you will create in MB/GB/TB. Indicate the proportions of raw data, processed data, and other secondary outputs (e.g., reports). +

+

+ Consider the implications of data volumes in terms of storage, access, and preservation. Do you need to include additional costs? +

+

+ Consider whether the scale of the data will pose challenges when sharing or transferring data between sites; if so, how will you address these challenges? +

+
+ + +

This is a very long sentence that should be truncated at word boundaries.

+ +
+
+ )}
) } diff --git a/components/TinyMCEEditor/tinyMCEEditor.module.scss b/components/TinyMCEEditor/tinyMCEEditor.module.scss index 08fcec485..c38a42022 100644 --- a/components/TinyMCEEditor/tinyMCEEditor.module.scss +++ b/components/TinyMCEEditor/tinyMCEEditor.module.scss @@ -2,7 +2,7 @@ .tinyMCE-editor-container { max-width: 600px; - margin-bottom: var(--space-5); + //margin-bottom: var(--space-5); // Because TinyMCE has it's own classes that we have to overwrite, we need to use the :global selector for it to work properly diff --git a/generated/gql.ts b/generated/gql.ts index 2cc6c994d..cdd4904b8 100644 --- a/generated/gql.ts +++ b/generated/gql.ts @@ -56,6 +56,7 @@ type Documents = { "query ProjectFunding($projectFundingId: Int!) {\n projectFunding(projectFundingId: $projectFundingId) {\n affiliation {\n name\n displayName\n uri\n }\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n }\n}": typeof types.ProjectFundingDocument, "query ProjectMembers($projectId: Int!) {\n projectMembers(projectId: $projectId) {\n id\n givenName\n surName\n orcid\n memberRoles {\n id\n label\n description\n }\n affiliation {\n displayName\n }\n }\n}\n\nquery ProjectMember($projectMemberId: Int!) {\n projectMember(projectMemberId: $projectMemberId) {\n email\n memberRoles {\n id\n label\n displayOrder\n uri\n }\n givenName\n surName\n affiliation {\n id\n displayName\n uri\n }\n orcid\n }\n}": typeof types.ProjectMembersDocument, "query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}": typeof types.MyProjectsDocument, + "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}": typeof types.AddQuestionCustomizationDocument, "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}": typeof types.QuestionsDisplayOrderDocument, "query RelatedWorks($id: Int!, $idType: RelatedWorksIdentifierType!, $paginationOptions: PaginationOptions, $filterOptions: RelatedWorksFilterOptions) {\n relatedWorks(\n id: $id\n idType: $idType\n paginationOptions: $paginationOptions\n filterOptions: $filterOptions\n ) {\n items {\n id\n projectId\n planId\n planTitle\n workVersion {\n id\n work {\n id\n doi\n }\n hash\n workType\n publicationDate\n title\n authors {\n orcid\n firstInitial\n givenName\n middleInitials\n middleNames\n surname\n full\n }\n institutions {\n name\n ror\n }\n funders {\n name\n ror\n }\n awards {\n awardId\n }\n publicationVenue\n sourceName\n sourceUrl\n }\n scoreNorm\n confidence\n status\n doiMatch {\n found\n score\n sources {\n parentAwardId\n awardId\n awardUrl\n }\n }\n contentMatch {\n score\n titleHighlight\n abstractHighlights\n }\n authorMatches {\n index\n score\n fields\n }\n institutionMatches {\n index\n score\n fields\n }\n funderMatches {\n index\n score\n fields\n }\n awardMatches {\n index\n score\n fields\n }\n created\n modified\n }\n totalCount\n limit\n currentOffset\n hasNextPage\n hasPreviousPage\n availableSortFields\n statusOnlyCount\n workTypeCounts {\n typeId\n count\n }\n confidenceCounts {\n typeId\n count\n }\n }\n}\n\nquery FindWorkByIdentifier($planId: Int, $doi: String) {\n findWorkByIdentifier(planId: $planId, doi: $doi) {\n items {\n id\n planId\n planTitle\n workVersion {\n work {\n doi\n }\n title\n hash\n workType\n publicationDate\n publicationVenue\n authors {\n orcid\n firstInitial\n givenName\n surname\n full\n }\n sourceName\n sourceUrl\n }\n status\n sourceType\n modified\n }\n }\n}\n\nquery RelatedWorksByPlanStats($planId: Int!) {\n relatedWorksByPlanStats(planId: $planId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}\n\nquery RelatedWorksByProjectStats($projectId: Int!) {\n relatedWorksByProjectStats(projectId: $projectId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}": typeof types.RelatedWorksDocument, "query Repositories($input: RepositorySearchInput!) {\n repositories(input: $input) {\n hasPreviousPage\n hasNextPage\n currentOffset\n availableSortFields\n totalCount\n nextCursor\n limit\n items {\n ... on CustomRepository {\n name\n description\n uri\n website\n repositoryTypes\n errors {\n general\n uri\n }\n keywords\n id\n }\n ... on Re3DataRepository {\n keywords\n id\n name\n description\n uri\n website\n repositoryTypes\n }\n }\n }\n}\n\nquery RepositorySubjectAreas {\n repositorySubjectAreas\n}\n\nquery RepositoriesByURIs($uris: [String!]!) {\n repositoriesByURIs(uris: $uris) {\n keywords\n id\n errors {\n general\n uri\n }\n name\n description\n uri\n website\n repositoryTypes\n }\n}\n\nquery Re3RepositoryTypesList {\n re3RepositoryTypesList {\n totalCount\n types {\n count\n type\n }\n }\n}\n\nquery Re3SubjectList {\n re3SubjectList {\n totalCount\n subjects {\n subject\n count\n }\n }\n}\n\nquery Re3byURIs($uris: [String!]!) {\n re3byURIs(uris: $uris) {\n name\n id\n description\n keywords\n uri\n website\n repositoryTypes\n }\n}": typeof types.RepositoriesDocument, @@ -114,6 +115,7 @@ const documents: Documents = { "query ProjectFunding($projectFundingId: Int!) {\n projectFunding(projectFundingId: $projectFundingId) {\n affiliation {\n name\n displayName\n uri\n }\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n }\n}": types.ProjectFundingDocument, "query ProjectMembers($projectId: Int!) {\n projectMembers(projectId: $projectId) {\n id\n givenName\n surName\n orcid\n memberRoles {\n id\n label\n description\n }\n affiliation {\n displayName\n }\n }\n}\n\nquery ProjectMember($projectMemberId: Int!) {\n projectMember(projectMemberId: $projectMemberId) {\n email\n memberRoles {\n id\n label\n displayOrder\n uri\n }\n givenName\n surName\n affiliation {\n id\n displayName\n uri\n }\n orcid\n }\n}": types.ProjectMembersDocument, "query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}": types.MyProjectsDocument, + "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}": types.AddQuestionCustomizationDocument, "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}": types.QuestionsDisplayOrderDocument, "query RelatedWorks($id: Int!, $idType: RelatedWorksIdentifierType!, $paginationOptions: PaginationOptions, $filterOptions: RelatedWorksFilterOptions) {\n relatedWorks(\n id: $id\n idType: $idType\n paginationOptions: $paginationOptions\n filterOptions: $filterOptions\n ) {\n items {\n id\n projectId\n planId\n planTitle\n workVersion {\n id\n work {\n id\n doi\n }\n hash\n workType\n publicationDate\n title\n authors {\n orcid\n firstInitial\n givenName\n middleInitials\n middleNames\n surname\n full\n }\n institutions {\n name\n ror\n }\n funders {\n name\n ror\n }\n awards {\n awardId\n }\n publicationVenue\n sourceName\n sourceUrl\n }\n scoreNorm\n confidence\n status\n doiMatch {\n found\n score\n sources {\n parentAwardId\n awardId\n awardUrl\n }\n }\n contentMatch {\n score\n titleHighlight\n abstractHighlights\n }\n authorMatches {\n index\n score\n fields\n }\n institutionMatches {\n index\n score\n fields\n }\n funderMatches {\n index\n score\n fields\n }\n awardMatches {\n index\n score\n fields\n }\n created\n modified\n }\n totalCount\n limit\n currentOffset\n hasNextPage\n hasPreviousPage\n availableSortFields\n statusOnlyCount\n workTypeCounts {\n typeId\n count\n }\n confidenceCounts {\n typeId\n count\n }\n }\n}\n\nquery FindWorkByIdentifier($planId: Int, $doi: String) {\n findWorkByIdentifier(planId: $planId, doi: $doi) {\n items {\n id\n planId\n planTitle\n workVersion {\n work {\n doi\n }\n title\n hash\n workType\n publicationDate\n publicationVenue\n authors {\n orcid\n firstInitial\n givenName\n surname\n full\n }\n sourceName\n sourceUrl\n }\n status\n sourceType\n modified\n }\n }\n}\n\nquery RelatedWorksByPlanStats($planId: Int!) {\n relatedWorksByPlanStats(planId: $planId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}\n\nquery RelatedWorksByProjectStats($projectId: Int!) {\n relatedWorksByProjectStats(projectId: $projectId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}": types.RelatedWorksDocument, "query Repositories($input: RepositorySearchInput!) {\n repositories(input: $input) {\n hasPreviousPage\n hasNextPage\n currentOffset\n availableSortFields\n totalCount\n nextCursor\n limit\n items {\n ... on CustomRepository {\n name\n description\n uri\n website\n repositoryTypes\n errors {\n general\n uri\n }\n keywords\n id\n }\n ... on Re3DataRepository {\n keywords\n id\n name\n description\n uri\n website\n repositoryTypes\n }\n }\n }\n}\n\nquery RepositorySubjectAreas {\n repositorySubjectAreas\n}\n\nquery RepositoriesByURIs($uris: [String!]!) {\n repositoriesByURIs(uris: $uris) {\n keywords\n id\n errors {\n general\n uri\n }\n name\n description\n uri\n website\n repositoryTypes\n }\n}\n\nquery Re3RepositoryTypesList {\n re3RepositoryTypesList {\n totalCount\n types {\n count\n type\n }\n }\n}\n\nquery Re3SubjectList {\n re3SubjectList {\n totalCount\n subjects {\n subject\n count\n }\n }\n}\n\nquery Re3byURIs($uris: [String!]!) {\n re3byURIs(uris: $uris) {\n name\n id\n description\n keywords\n uri\n website\n repositoryTypes\n }\n}": types.RepositoriesDocument, @@ -312,6 +314,10 @@ export function gql(source: "query ProjectMembers($projectId: Int!) {\n project * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}"): (typeof documents)["query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}"): (typeof documents)["mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/generated/graphql.ts b/generated/graphql.ts index 6627d4610..5ce780b55 100644 --- a/generated/graphql.ts +++ b/generated/graphql.ts @@ -170,6 +170,10 @@ export type AddQuestionConditionInput = { /** Input parameters for adding custom guidance and sample text to a funder question */ export type AddQuestionCustomizationInput = { + /** The custom guidance for the question */ + guidanceText?: InputMaybe; + /** The custom sample answer for the question */ + sampleText?: InputMaybe; /** The identifier of the parent template customization */ templateCustomizationId: Scalars['Int']['input']; /** The identifier of the published funder question */ @@ -3154,6 +3158,8 @@ export type Query = { questionConditions?: Maybe>>; /** Get the custom guidance and sample text the affiliation has added to a funder question question (user must be an Admin) */ questionCustomization?: Maybe; + /** Get the custom guidance and sample text the affiliation has added to a funder question question (user must be an Admin) */ + questionCustomizationByVersionedQuestion?: Maybe; /** Get the Questions that belong to the associated sectionId */ questions?: Maybe>>; /** return all distinct repository types from re3data with optional counts */ @@ -3500,6 +3506,12 @@ export type QueryQuestionCustomizationArgs = { }; +export type QueryQuestionCustomizationByVersionedQuestionArgs = { + templateCustomizationId: Scalars['Int']['input']; + versionedQuestionId: Scalars['Int']['input']; +}; + + export type QueryQuestionsArgs = { sectionId: Scalars['Int']['input']; }; @@ -6359,6 +6371,35 @@ export type ProjectFundingsApiQueryVariables = Exact<{ export type ProjectFundingsApiQuery = { __typename?: 'Query', project?: { __typename?: 'Project', fundings?: Array<{ __typename?: 'ProjectFunding', affiliation?: { __typename?: 'Affiliation', apiTarget?: string | null } | null }> | null } | null }; +export type AddQuestionCustomizationMutationVariables = Exact<{ + input: AddQuestionCustomizationInput; +}>; + + +export type AddQuestionCustomizationMutation = { __typename?: 'Mutation', addQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, displayOrder?: number | null, modified?: string | null, questionId: number, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } }; + +export type UpdateQuestionCustomizationMutationVariables = Exact<{ + input: UpdateQuestionCustomizationInput; +}>; + + +export type UpdateQuestionCustomizationMutation = { __typename?: 'Mutation', updateQuestionCustomization: { __typename?: 'QuestionCustomization', guidanceText?: string | null, id?: number | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } }; + +export type RemoveQuestionCustomizationMutationVariables = Exact<{ + questionCustomizationId: Scalars['Int']['input']; +}>; + + +export type RemoveQuestionCustomizationMutation = { __typename?: 'Mutation', removeQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null } }; + +export type QuestionCustomizationByVersionedQuestionQueryVariables = Exact<{ + templateCustomizationId: Scalars['Int']['input']; + versionedQuestionId: Scalars['Int']['input']; +}>; + + +export type QuestionCustomizationByVersionedQuestionQuery = { __typename?: 'Query', questionCustomizationByVersionedQuestion?: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, questionText?: string | null, requirementText?: string | null, required?: boolean | null, sampleText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } | null }; + export type QuestionsDisplayOrderQueryVariables = Exact<{ sectionId: Scalars['Int']['input']; }>; @@ -6731,6 +6772,10 @@ export const ProjectMemberDocument = {"kind":"Document","definitions":[{"kind":" export const MyProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"term"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myProjects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"term"},"value":{"kind":"Variable","name":{"kind":"Name","value":"term"}}},{"kind":"Argument","name":{"kind":"Name","value":"paginationOptions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextCursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"grantId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"orcid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Project"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"abstractText"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"isTestProject"}},{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grantId"}},{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"searchName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"memberRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"researchDomain"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentResearchDomainId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionedSections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"answeredQuestions"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalQuestions"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"templateTitle"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"funding"}},{"kind":"Field","name":{"kind":"Name","value":"dmpId"}},{"kind":"Field","name":{"kind":"Name","value":"registered"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"created"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectFundingsApiDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectFundingsApi"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiTarget"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const AddQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const UpdateQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const RemoveQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]}}]} as unknown as DocumentNode; +export const QuestionCustomizationByVersionedQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionCustomizationByVersionedQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questionCustomizationByVersionedQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const QuestionsDisplayOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionsDisplayOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}}]}}]}}]} as unknown as DocumentNode; export const PlanSectionQuestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlanSectionQuestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}}]}}]}}]} as unknown as DocumentNode; export const QuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Question"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"question"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionConditionIds"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}},{"kind":"Field","name":{"kind":"Name","value":"required"}}]}}]}}]} as unknown as DocumentNode; diff --git a/graphql/queries/questionCustomization.query.graphql b/graphql/queries/questionCustomization.query.graphql new file mode 100644 index 000000000..b347154f0 --- /dev/null +++ b/graphql/queries/questionCustomization.query.graphql @@ -0,0 +1,115 @@ +mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) { + addQuestionCustomization(input: $input) { + id + guidanceText + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + displayOrder + modified + questionId + ownerAffiliation { + displayName + } + } + } +} + +mutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) { + updateQuestionCustomization(input: $input) { + guidanceText + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + id + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + ownerAffiliation { + displayName + } + } + } +} + +mutation RemoveQuestionCustomization($questionCustomizationId: Int!) { + removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) { + id + guidanceText + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + json + ownerAffiliation { + displayName + } + } + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + } +} + +query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) { + questionCustomizationByVersionedQuestion(templateCustomizationId: $templateCustomizationId, versionedQuestionId: $versionedQuestionId) { + id + guidanceText + migrationStatus + modified + questionId + sampleText + templateCustomizationId + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + versionedQuestion { + id + guidanceText + questionText + requirementText + required + sampleText + ownerAffiliation { + displayName + } + } + } +} \ No newline at end of file diff --git a/messages/en-US/templateBuilder.json b/messages/en-US/templateBuilder.json index 692282046..6cd69c800 100644 --- a/messages/en-US/templateBuilder.json +++ b/messages/en-US/templateBuilder.json @@ -279,6 +279,41 @@ "bestPracticeTags": "Best practice tags" } }, + "QuestionCustomize": { + "title": "Customize question", + "messages": { + "noContent": "Funder didn't add {label}", + "success": { + "successfullyDeletedCustomization": "Successfully deleted the question customizations", + "successfullyUpdatedCustomization": "Successfully updated the question customizations" + }, + "error": { + "errorDeletingCustomization": "Error deleting question customization", + "errorUpdatingCustomization": "Error updating question customization" + } + }, + "labels": { + "additionalGuidanceText": "Additional guidance text", + "additionalSampleText": "Additional sample text", + "requirements": "Requirements", + "guidance": "Guidance", + "sampleText": "Sample text" + }, + "helpText": { + "additionalGuidanceText": "Outline the guidance that a user should consider for this question.", + "additionalSampleText": "We will allow users to copy the same text into the Text field this will allow users to speed up content entry." + }, + "heading": { + "deleteCustomization": "Delete customization" + }, + "descriptions": { + "deleteCustomization": "Your customizations to this template will be removed from the question. This is not reversible." + }, + "buttons": { + "deleteCustomization": "Delete customization", + "deletingCustomizations": "Deleting customizations..." + } + }, "CreateSectionPage": { "title": "Create section", "button": { diff --git a/utils/routes.ts b/utils/routes.ts index cc2398594..bd76080e3 100644 --- a/utils/routes.ts +++ b/utils/routes.ts @@ -106,6 +106,7 @@ const routes = { 'template.customizations': '/template/customizations', 'template.customize': '/template/customizations/:templateCustomizationId', 'template.customize.sectionId': '/template/customizations/:templateCustomizationId/section/:versionedSectionId', + 'template.customize.question': '/template/customizations/:templateCustomizationId/q/:versionedQuestionId', 'template.customSection': '/template/customizations/:templateCustomizationId/customSection/:customSectionId', 'template.customize.section.create': '/template/customizations/:templateCustomizationId/section/create', 'template.customize.question.create': '/template/customizations/:templateCustomizationId/q/new', From de224be2632e108d9f358d4e6f2336d78d4a2050 Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Wed, 11 Mar 2026 12:29:22 -0700 Subject: [PATCH 3/7] Added isDisabled to form field components, so that we can display them on the Question Customization pages but have them be disabled --- app/[locale]/loading.tsx | 6 + app/[locale]/template/create/page.tsx | 3 +- .../[templateCustomizationId]/page.tsx | 5 +- .../q/[versionedQuestionId]/page.tsx | 312 ++++++++++-------- .../questionCustomEdit.module.scss | 12 + .../section/create/page.tsx | 3 - app/[locale]/template/page.tsx | 8 +- app/types/index.ts | 3 + .../CustomizedSectionEdit/index.tsx | 3 - components/Form/CheckboxGroup/index.tsx | 2 + components/Form/DateComponent/index.tsx | 4 +- components/Form/FormSelect/index.tsx | 9 +- components/Form/FormTextArea/index.tsx | 1 + components/Form/MultiSelect/index.tsx | 7 +- .../Form/MultiSelect/multiSelect.module.scss | 5 + .../index.tsx | 2 + .../BooleanQuestionComponent/index.tsx | 3 + .../CheckboxesQuestionComponent/index.tsx | 4 + .../CurrencyQuestionComponent/index.tsx | 3 + .../DateRangeQuestionComponent/index.tsx | 4 + .../MultiSelectQuestionComponent/index.tsx | 3 + .../NumberRangeQuestionComponent/index.tsx | 4 + .../RadioButtonsQuestionComponent/index.tsx | 3 + .../SelectboxQuestionComponent/index.tsx | 7 +- components/Form/RadioGroup/index.tsx | 3 +- .../SingleResearchOutputComponent/index.tsx | 16 +- .../ResearchOutputAnswerComponent/index.tsx | 3 + .../TypeAheadWithOther/TypeAheadWithOther.tsx | 4 +- components/QuestionPreview/index.tsx | 5 +- .../QuestionView/QuestionView.module.scss | 11 + components/QuestionView/index.tsx | 210 ++++++------ components/TinyMCEEditor/index.tsx | 23 +- generated/gql.ts | 24 +- generated/graphql.ts | 60 ++-- .../mutations/questionCustomization.graphql | 22 -- .../questionCustomization.mutation.graphql | 110 ++++++ .../questionCustomization.query.graphql | 86 +---- graphql/queries/questions.query.graphql | 1 + graphql/queries/users.query.graphql | 1 + messages/en-US/global.json | 6 +- messages/en-US/templateBuilder.json | 2 +- styles/form/_radio.scss | 9 + 42 files changed, 592 insertions(+), 420 deletions(-) create mode 100644 app/[locale]/loading.tsx delete mode 100644 graphql/mutations/questionCustomization.graphql create mode 100644 graphql/mutations/questionCustomization.mutation.graphql diff --git a/app/[locale]/loading.tsx b/app/[locale]/loading.tsx new file mode 100644 index 000000000..d423e80e3 --- /dev/null +++ b/app/[locale]/loading.tsx @@ -0,0 +1,6 @@ +import Loading from '@/components/Loading'; + +{/** Next.js only shows this if it takes longer than ~300ms to load */ } +export default function LocaleLoading() { + return ; +} \ No newline at end of file diff --git a/app/[locale]/template/create/page.tsx b/app/[locale]/template/create/page.tsx index 73cea847a..f0b57256d 100644 --- a/app/[locale]/template/create/page.tsx +++ b/app/[locale]/template/create/page.tsx @@ -20,6 +20,7 @@ import FormInput from '@/components/Form/FormInput'; import { debounce } from '@/hooks/debounce'; import { useQueryStep } from '@/app/[locale]/template/create/useQueryStep'; +import Loading from '@/components/Loading'; const TemplateCreatePage: React.FC = () => { const router = useRouter(); @@ -68,7 +69,7 @@ const TemplateCreatePage: React.FC = () => { // TODO: Need to implement a shared loading component if (step === null) { - return
...{Global('messaging.loading')}
+ return ; } return ( diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx index b1285b535..987cf2ace 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/page.tsx @@ -91,10 +91,9 @@ const TemplateCustomizationOverview: React.FC = () => { refetch } = useQuery(TemplateCustomizationOverviewDocument, { variables: { templateCustomizationId: Number(templateCustomizationId) }, + fetchPolicy: 'cache-and-network', // User sees cached data imediately while a fresh fetch runs to get latest updates from editing }); - console.log("***Template Customization Overview", data); - // Mutations const [publishTemplateCustomization] = useMutation(PublishTemplateCustomizationDocument); const [unpublishTemplateCustomization] = useMutation(UnpublishTemplateCustomizationDocument); @@ -358,8 +357,6 @@ const TemplateCustomizationOverview: React.FC = () => { } }, [data]); - console.log("***Local Sections in Template Customization Overview: ", localSections); - if (loading) { return ; } diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx index 568bf235d..2528caefb 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx @@ -8,7 +8,6 @@ import { Breadcrumb, Breadcrumbs, Button, - Checkbox, Dialog, DialogTrigger, Form, @@ -25,19 +24,27 @@ import { QuestionCustomizationByVersionedQuestionDocument, PublishedQuestionDocument, QuestionCustomizationErrors, + MeDocument, } from '@/generated/graphql'; +import { Question } from '@/app/types'; + // Components import PageHeader from "@/components/PageHeader"; -import { ContentContainer, LayoutContainer } from '@/components/Container'; +import { + ContentContainer, + LayoutWithPanel, + SidebarPanel, +} from '@/components/Container'; import FormTextArea from '@/components/Form/FormTextArea'; import ErrorMessages from '@/components/ErrorMessages'; import Loading from '@/components/Loading'; -import { SanitizeHTML } from '@/utils/sanitize'; import { DmpIcon } from "@/components/Icons"; import QuestionView from '@/components/QuestionView'; -import { Question } from '@/app/types'; +import QuestionPreview from '@/components/QuestionPreview'; + // Utils +import { SanitizeHTML } from '@/utils/sanitize'; import logECS from '@/utils/clientLogger'; import { useToast } from '@/context/ToastContext'; import { scrollToTop } from '@/utils/general'; @@ -56,7 +63,6 @@ const QuestionCustomizePage: React.FC = () => { const hasInitialized = useRef(false); const errorRef = useRef(null); - const topRef = useRef(null); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); @@ -66,8 +72,9 @@ const QuestionCustomizePage: React.FC = () => { const [questionCustomizationId, setQuestionCustomizationId] = useState(null); const [errorMessages, setErrorMessages] = useState([]); + // Base question data from the published question - used for preview and as reference for customization const [baseQuestion, setBaseQuestion] = useState(undefined); - + const [isPreviewOpen, setIsPreviewOpen] = useState(false); // Customizable fields const [customQuestionData, setCustomQuestionData] = useState({ @@ -75,19 +82,10 @@ const QuestionCustomizePage: React.FC = () => { sampleText: '', }); - // Read-only base question data - const [baseQuestionData, setBaseQuestionData] = useState({ - questionText: '', - requirementText: '', - guidanceText: '', - sampleText: '', - }); - // Localization const Global = useTranslations('Global'); const QuestionCustomize = useTranslations('QuestionCustomize'); const QuestionEdit = useTranslations('QuestionEdit'); - const PlanOverview = useTranslations('PlanOverview'); // URLs const TEMPLATE_URL = routePath('template.customize', { templateCustomizationId }); @@ -103,6 +101,8 @@ const QuestionCustomizePage: React.FC = () => { variables: { versionedQuestionId: Number(versionedQuestionId) }, }); + const { data: meData, loading: meLoading } = useQuery(MeDocument); + const { data: questionCustomization, loading: questionCustomizationLoading } = useQuery( QuestionCustomizationByVersionedQuestionDocument, { @@ -113,8 +113,6 @@ const QuestionCustomizePage: React.FC = () => { } ); - console.log("***QuestionCustomization data: ", questionCustomization); - const updateCustomQuestionContent = (key: string, value: string | boolean) => { setCustomQuestionData(prev => ({ ...prev, [key]: value })); setHasUnsavedChanges(true); @@ -156,6 +154,7 @@ const QuestionCustomizePage: React.FC = () => { try { const response = await removeQuestionCustomization({ variables: { questionCustomizationId: Number(questionCustomizationId) }, + refetchQueries: [QuestionCustomizationByVersionedQuestionDocument], }); const responseErrors = response.data?.removeQuestionCustomization?.errors; @@ -201,19 +200,6 @@ const QuestionCustomizePage: React.FC = () => { } }; - // Load base question data from published question query - useEffect(() => { - if (publishedQuestion?.publishedQuestion) { - const q = publishedQuestion.publishedQuestion; - setBaseQuestionData({ - questionText: stripHtmlTags(q.questionText ?? ''), - requirementText: q.requirementText ?? '', - guidanceText: q.guidanceText ?? '', - sampleText: q.sampleText ?? '', - }); - } - }, [publishedQuestion]); - // Initialize or load existing customization useEffect(() => { const initializeCustomization = async () => { @@ -264,15 +250,23 @@ const QuestionCustomizePage: React.FC = () => { useEffect(() => { if (publishedQuestion?.publishedQuestion) { const q = publishedQuestion.publishedQuestion; - // Map to the Question type QuestionView expects setBaseQuestion({ - questionText: q.questionText ?? '', + id: q.id, + displayOrder: q.displayOrder, + questionText: stripHtmlTags(q.questionText) ?? null, requirementText: q.requirementText ?? null, guidanceText: q.guidanceText ?? null, sampleText: q.sampleText ?? null, useSampleTextAsDefault: q.useSampleTextAsDefault ?? false, required: q.required ?? false, - json: q.json ?? '', + json: q.json ?? null, + templateId: q.versionedTemplateId ?? null, + ownerAffiliation: q.ownerAffiliation ? { + acronyms: q.ownerAffiliation.acronyms ?? null, + displayName: q.ownerAffiliation.displayName, + uri: q.ownerAffiliation.uri, + name: q.ownerAffiliation.name, + } : null, }); } }, [publishedQuestion]); @@ -299,131 +293,157 @@ const QuestionCustomizePage: React.FC = () => { className="" /> - + -
-
- - -
- {baseQuestionData.requirementText && ( -
-

{QuestionCustomize('labels.requirements')}

- -
- )} - {baseQuestionData.guidanceText && ( -
-

{QuestionCustomize('labels.guidance')}

- -
- )} +
+ - {baseQuestionData.sampleText && ( -
-

{QuestionCustomize('labels.sampleText')}

- -
- )} - - {baseQuestion && ( -
-
- -
+ + {baseQuestion?.requirementText && ( +
+

{QuestionCustomize('labels.requirements')}

+ +
+ )} + + {baseQuestion && ( +
+ {/**Key the inert div so it remounts when preview closes */} +
+
- )} - +
+ )} - {/* Editable customization fields */} -
- updateCustomQuestionContent('guidanceText', value)} - /> + {baseQuestion?.guidanceText && ( +
+

{QuestionCustomize('labels.guidance')}

+
+ )} + + {/* Editable customization fields */} +
+ updateCustomQuestionContent('guidanceText', value)} + /> +
-
- updateCustomQuestionContent('sampleText', value)} - /> - - {/* updateCustomQuestionContent('useSampleTextAsDefault', checked)} - > -
- + {baseQuestion?.sampleText && ( + <> + {baseQuestion?.sampleText && ( +
+

{QuestionCustomize('labels.sampleText')}

+
- {QuestionCustomize('labels.useSampleTextAsDefault')} - */} -
- - + + + {/* Delete customization */} +
+

{QuestionCustomize('buttons.deleteCustomization')}

+

{QuestionCustomize.rich("descriptions.deleteCustomization", { + strong: (chunks) => {chunks} + })}

+ + - - - {/* Delete customization */} -
-

{QuestionCustomize('buttons.deleteCustomization')}

-

{QuestionCustomize.rich("descriptions.deleteCustomization", { - strong: (chunks) => {chunks} - })}

- - - - - - {({ close }) => ( - <> -

{QuestionCustomize('heading.deleteCustomization')}

-

{QuestionCustomize.rich("descriptions.deleteCustomization", { - strong: (chunks) => {chunks} - })}

-
- - -
- - )} -
-
-
-
-
+ + + + {({ close }) => ( + <> +

{QuestionCustomize('heading.deleteCustomization')}

+

{QuestionCustomize.rich("descriptions.deleteCustomization", { + strong: (chunks) => {chunks} + })}

+
+ + +
+ + )} +
+
+
+
- - + + + <> +

{Global('headings.preview')}

+

{QuestionEdit('descriptions.previewText')}

+ {/** When modal closes, isPreviewOpen flips from true -> false, the key changes and React unmounts and remounts the inert QuestionView + * with a fresh TinyMCEEditor instance. This was needed because sometimes the TinyMCEEditor did not rerender after coming back from Preview*/} + + + + +

{QuestionEdit('headings.bestPractice')}

+

{QuestionEdit('descriptions.bestPracticePara1')}

+

{QuestionEdit('descriptions.bestPracticePara2')}

+

{QuestionEdit('descriptions.bestPracticePara3')}

+ +
+ ); }; diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss index 6d89f96f0..2a959a2c4 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/questionCustomEdit.module.scss @@ -70,3 +70,15 @@ padding-top: 1.5rem; margin-bottom: 2rem; } + +.questionContainer { + margin-top: 0px; + outline: none; + border: var(--card-border, none); + border-radius: var(--card-border-radius, 0.3125rem); + box-shadow: var(--card-shadow, 0px 4px 6px 0px rgba(0, 0, 0, 0.09)); + padding: 0.75rem; + background-color: var(--card-background, #fff); + margin-bottom: 1rem; + padding: 2rem; +} \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx index 84bd8aa59..15055707a 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx @@ -108,9 +108,6 @@ const CreateCustomSectionPage: React.FC = () => { } = useQuery(TemplateCustomizationOverviewDocument, { variables: { templateCustomizationId: Number(templateCustomizationId) }, }); - - console.log("***Template Customization Overview", data); - // Update form fields in state when fields are edited const updateSectionContent = (key: string, value: string) => { clearAllFieldErrors(); diff --git a/app/[locale]/template/page.tsx b/app/[locale]/template/page.tsx index 0df75a5f0..e1416bbd0 100644 --- a/app/[locale]/template/page.tsx +++ b/app/[locale]/template/page.tsx @@ -1,6 +1,7 @@ 'use client'; import React, { useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; import { Breadcrumb, Breadcrumbs, @@ -8,7 +9,6 @@ import { FieldError, Input, Label, - Link, SearchField, Text } from 'react-aria-components'; @@ -299,8 +299,10 @@ const TemplateListPage: React.FC = () => { } actions={ <> - {t('actionCreate')} + {t('actionCreate')} + } className="page-template-list" diff --git a/app/types/index.ts b/app/types/index.ts index 2024ecb44..a0317cae0 100644 --- a/app/types/index.ts +++ b/app/types/index.ts @@ -354,6 +354,7 @@ export interface RadioGroupProps { onChange?: (value: string) => void; isRequired?: boolean; isRequiredVisualOnly?: boolean; + isDisabled?: boolean; children?: ReactNode; // allow any Radio buttons or JSX } @@ -373,6 +374,7 @@ export interface CheckboxGroupProps { onChange?: ((value: string[]) => void), isRequired?: boolean; isRequiredVisualOnly?: boolean; + isDisabled?: boolean; children?: ReactNode; // allow any Checkboxes or JSX } @@ -573,6 +575,7 @@ export interface AffiliationSearchQuestionProps { affiliationData: { affiliationName: string, affiliationId: string }; otherAffiliationName?: string; otherField?: boolean; + isDisabled?: boolean; setOtherField: (value: boolean) => void; handleAffiliationChange: (id: string, value: string) => Promise handleOtherAffiliationChange: (e: React.ChangeEvent) => void; diff --git a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx index 16ae4efd7..d3b1cdb83 100644 --- a/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx +++ b/components/CustomizedTemplate/CustomizedSectionEdit/index.tsx @@ -35,7 +35,6 @@ const CustomizedSectionEdit: React.FC = ({ onMoveDown, }) => { - console.log("***SectionEdit received section: ", section); const toastState = useToast(); const t = useTranslations('Sections'); @@ -51,8 +50,6 @@ const CustomizedSectionEdit: React.FC = ({ const [moveCustomQuestionMutation] = useMutation(MoveCustomQuestionDocument); - console.log("***Section", section); - // Memoize the sorted questions to prevent unnecessary re-renders const sortedQuestionsFromData = useMemo(() => { if (!section?.questions) return []; diff --git a/components/Form/CheckboxGroup/index.tsx b/components/Form/CheckboxGroup/index.tsx index 80b5f453a..09223aaae 100644 --- a/components/Form/CheckboxGroup/index.tsx +++ b/components/Form/CheckboxGroup/index.tsx @@ -19,6 +19,7 @@ const CheckboxGroupComponent: React.FC = ({ onChange, isRequired = false, isRequiredVisualOnly = false, + isDisabled = false, children, }) => { const showRequired = isRequired || isRequiredVisualOnly; @@ -35,6 +36,7 @@ const CheckboxGroupComponent: React.FC = ({ isRequired={isRequired} isInvalid={isInvalid} aria-required={isRequired} + isDisabled={isDisabled} >
diff --git a/components/Form/MultiSelect/index.tsx b/components/Form/MultiSelect/index.tsx index 538ed5413..60b84f3d0 100644 --- a/components/Form/MultiSelect/index.tsx +++ b/components/Form/MultiSelect/index.tsx @@ -12,16 +12,18 @@ interface MultiSelectProps { options: Option[]; defaultSelected?: string[]; selectedKeys?: Set; - onSelectionChange?: (selected: Set) => void; label?: string; + isDisabled?: boolean; + onSelectionChange?: (selected: Set) => void; } function MultiSelect({ options, defaultSelected = [], selectedKeys, - onSelectionChange, label = "Select Items (Multiple)", + isDisabled = false, + onSelectionChange, }: MultiSelectProps) { const isControlled = selectedKeys !== undefined; const [internalSelected, setInternalSelected] = React.useState>(new Set(defaultSelected)); @@ -55,6 +57,7 @@ function MultiSelect({ id={option.name} textValue={option.name} className={styles.multiselectItem} + isDisabled={isDisabled} > {({ isSelected }) => ( <> diff --git a/components/Form/MultiSelect/multiSelect.module.scss b/components/Form/MultiSelect/multiSelect.module.scss index 13cef9eff..7ca4306d9 100644 --- a/components/Form/MultiSelect/multiSelect.module.scss +++ b/components/Form/MultiSelect/multiSelect.module.scss @@ -55,6 +55,11 @@ font-weight: 400; color: #374151; background-color: transparent; + + &[data-disabled][data-selected] { + background-color: rgb(from var(--highlight-background) r g b / 0.5); + } + } /* Hovered but not selected */ diff --git a/components/Form/QuestionComponents/AffiliationSearchQuestionComponent/index.tsx b/components/Form/QuestionComponents/AffiliationSearchQuestionComponent/index.tsx index 2f81f928d..cfe49a1b1 100644 --- a/components/Form/QuestionComponents/AffiliationSearchQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/AffiliationSearchQuestionComponent/index.tsx @@ -10,6 +10,7 @@ const AffiliationSearchQuestionComponent: React.FC {otherField && (
diff --git a/components/Form/QuestionComponents/BooleanQuestionComponent/index.tsx b/components/Form/QuestionComponents/BooleanQuestionComponent/index.tsx index b57e0e856..c1ec5c76c 100644 --- a/components/Form/QuestionComponents/BooleanQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/BooleanQuestionComponent/index.tsx @@ -7,12 +7,14 @@ import { Radio } from 'react-aria-components'; interface BooleanQuestionProps { parsedQuestion: BooleanQuestionType; selectedValue?: string; + isDisabled?: boolean; handleRadioChange: (value: string) => void; } const BooleanQuestionComponent: React.FC = ({ parsedQuestion, selectedValue, + isDisabled = false, handleRadioChange }) => { // Localization keys @@ -30,6 +32,7 @@ const BooleanQuestionComponent: React.FC = ({ value={value} radioGroupLabel="" onChange={handleRadioChange} + isDisabled={isDisabled} >
{Global('form.yesLabel')} diff --git a/components/Form/QuestionComponents/CheckboxesQuestionComponent/index.tsx b/components/Form/QuestionComponents/CheckboxesQuestionComponent/index.tsx index 70e78f1c2..0296b37f9 100644 --- a/components/Form/QuestionComponents/CheckboxesQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/CheckboxesQuestionComponent/index.tsx @@ -2,16 +2,19 @@ import React from 'react'; import { CheckboxesQuestionType } from '@dmptool/types'; import { CheckboxGroupComponent } from '@/components/Form'; import { Checkbox } from "react-aria-components"; +import { is } from 'zod/v4/locales'; interface CheckboxesQuestionProps { parsedQuestion: CheckboxesQuestionType; selectedCheckboxValues: string[]; + isDisabled?: boolean; handleCheckboxGroupChange: (values: string[]) => void; } const CheckboxesQuestionComponent: React.FC = ({ parsedQuestion, selectedCheckboxValues, + isDisabled = false, handleCheckboxGroupChange }) => { const checkboxData = parsedQuestion.options?.map((opt: CheckboxesQuestionType['options'][number]) => ({ @@ -30,6 +33,7 @@ const CheckboxesQuestionComponent: React.FC = ({ onChange={handleCheckboxGroupChange} checkboxGroupLabel="" checkboxGroupDescription={""} + isDisabled={isDisabled} > {checkboxData.map((checkbox, index) => (
diff --git a/components/Form/QuestionComponents/CurrencyQuestionComponent/index.tsx b/components/Form/QuestionComponents/CurrencyQuestionComponent/index.tsx index 519363d71..8ee2e9eb4 100644 --- a/components/Form/QuestionComponents/CurrencyQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/CurrencyQuestionComponent/index.tsx @@ -6,6 +6,7 @@ interface CurrencyQuestionProps { inputCurrencyValue: number | null; currencyLabel?: string; placeholder?: string; + isDisabled?: boolean; handleCurrencyChange: (value: number | null) => void; } @@ -14,6 +15,7 @@ const CurrencyQuestionComponent: React.FC = ({ inputCurrencyValue, currencyLabel, placeholder, + isDisabled = false, handleCurrencyChange, }) => { const minValue = (parsedQuestion?.attributes as { min?: number }).min; @@ -33,6 +35,7 @@ const CurrencyQuestionComponent: React.FC = ({ minimumFractionDigits: 2, maximumFractionDigits: 2, }} + disabled={isDisabled} /> ); }; diff --git a/components/Form/QuestionComponents/DateRangeQuestionComponent/index.tsx b/components/Form/QuestionComponents/DateRangeQuestionComponent/index.tsx index d3de090be..cbf7397a2 100644 --- a/components/Form/QuestionComponents/DateRangeQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/DateRangeQuestionComponent/index.tsx @@ -10,6 +10,7 @@ interface DateRangeQuestionProps { startDate: string | DateValue | CalendarDate | null; endDate: string | DateValue | CalendarDate | null; }; + isDisabled?: boolean; handleDateChange: ( key: string, value: string | DateValue | CalendarDate | null @@ -19,6 +20,7 @@ interface DateRangeQuestionProps { const DateRangeQuestionComponent: React.FC = ({ parsedQuestion, dateRange, + isDisabled = false, handleDateChange, }) => { // Extract labels from JSON if available @@ -34,12 +36,14 @@ const DateRangeQuestionComponent: React.FC = ({ value={getCalendarDateValue(dateRange.startDate)} onChange={newDate => handleDateChange('startDate', newDate)} label={startLabel} + isDisabled={isDisabled} /> handleDateChange('endDate', newDate)} label={endLabel} + isDisabled={isDisabled} />
) diff --git a/components/Form/QuestionComponents/MultiSelectQuestionComponent/index.tsx b/components/Form/QuestionComponents/MultiSelectQuestionComponent/index.tsx index 03f0d5ce7..5329735ec 100644 --- a/components/Form/QuestionComponents/MultiSelectQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/MultiSelectQuestionComponent/index.tsx @@ -6,6 +6,7 @@ interface MultiselectboxQuestionProps { parsedQuestion: MultiselectBoxQuestionType; selectedMultiSelectValues?: Set; selectBoxLabel?: string; + isDisabled?: boolean; handleMultiSelectChange: (values: Set) => void; } @@ -13,6 +14,7 @@ const MultiSelectQuestionComponent: React.FC = ({ parsedQuestion, selectedMultiSelectValues, selectBoxLabel, + isDisabled = false, handleMultiSelectChange }) => { // Transform options to items for FormSelect/MultiSelect @@ -45,6 +47,7 @@ const MultiSelectQuestionComponent: React.FC = ({ label={selectBoxLabel} aria-label={selectBoxLabel} defaultSelected={defaultSelected} + isDisabled={isDisabled} /> ); }; diff --git a/components/Form/QuestionComponents/NumberRangeQuestionComponent/index.tsx b/components/Form/QuestionComponents/NumberRangeQuestionComponent/index.tsx index e1abeaf70..3e2615649 100644 --- a/components/Form/QuestionComponents/NumberRangeQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/NumberRangeQuestionComponent/index.tsx @@ -8,6 +8,7 @@ interface NumberRangeQuestionProps { startNumber: number | null; endNumber: number | null; }; + isDisabled?: boolean; handleNumberChange: ( key: string, value: number | null @@ -19,6 +20,7 @@ interface NumberRangeQuestionProps { const NumberRangeQuestionComponent: React.FC = ({ parsedQuestion, numberRange, + isDisabled = false, handleNumberChange, startPlaceholder, endPlaceholder, @@ -41,6 +43,7 @@ const NumberRangeQuestionComponent: React.FC = ({ placeholder={startPlaceholder} minValue={startNumberMin} maxValue={startNumberMax ?? undefined} + disabled={isDisabled} /> = ({ placeholder={endPlaceholder} minValue={endNumberMin} maxValue={endNumberMax ?? undefined} + disabled={isDisabled} />
) diff --git a/components/Form/QuestionComponents/RadioButtonsQuestionComponent/index.tsx b/components/Form/QuestionComponents/RadioButtonsQuestionComponent/index.tsx index 02d0dbcfc..0d5dd843f 100644 --- a/components/Form/QuestionComponents/RadioButtonsQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/RadioButtonsQuestionComponent/index.tsx @@ -7,6 +7,7 @@ interface RadioButtonQuestionTypeProps { selectedRadioValue: string | undefined; name?: string; radioGroupLabel?: string; + isDisabled?: boolean; handleRadioChange: (value: string) => void; } @@ -15,6 +16,7 @@ const RadioButtonsQuestionComponent: React.FC = ({ selectedRadioValue, name = 'radio-buttons-question', radioGroupLabel = '', + isDisabled = false, handleRadioChange }) => { const radioButtonData = parsedQuestion.options?.map((opt: RadioButtonsQuestionType['options'][number]) => ({ @@ -32,6 +34,7 @@ const RadioButtonsQuestionComponent: React.FC = ({ value={value ?? ''} radioGroupLabel={radioGroupLabel} onChange={handleRadioChange} + isDisabled={isDisabled} > {radioButtonData.map((radioButton, index) => (
diff --git a/components/Form/QuestionComponents/SelectboxQuestionComponent/index.tsx b/components/Form/QuestionComponents/SelectboxQuestionComponent/index.tsx index 9d1f8059d..aecf402c8 100644 --- a/components/Form/QuestionComponents/SelectboxQuestionComponent/index.tsx +++ b/components/Form/QuestionComponents/SelectboxQuestionComponent/index.tsx @@ -12,6 +12,7 @@ interface SelectboxQuestionProps { selectName?: string; errorMessage?: string; helpMessage?: string; + isDisabled?: boolean; handleSelectChange?: (value: string) => void; } @@ -22,8 +23,10 @@ const SelectboxQuestionComponent: React.FC = ({ selectName = 'select', errorMessage = '', helpMessage = '', + isDisabled = false, handleSelectChange }) => { + // Transform options to items for FormSelect const items = parsedQuestion.options?.map((opt: SelectBoxQuestionType['options'][number]) => ({ id: opt.value, @@ -37,7 +40,6 @@ const SelectboxQuestionComponent: React.FC = ({ const value = selectedSelectValue !== undefined ? selectedSelectValue : initialValue; return ( - = ({ errorMessage={errorMessage} helpMessage={helpMessage} onChange={handleSelectChange} + isDisabled={isDisabled} > {items.map((item: { id: string; name: string }) => ( {item.name} ))} - - ); }; diff --git a/components/Form/RadioGroup/index.tsx b/components/Form/RadioGroup/index.tsx index e29cc67c6..ffe4a4ad5 100644 --- a/components/Form/RadioGroup/index.tsx +++ b/components/Form/RadioGroup/index.tsx @@ -20,11 +20,11 @@ const RadioGroupComponent: React.FC = ({ onChange, isRequired = false, isRequiredVisualOnly = false, + isDisabled = false, children, }) => { const showRequired = isRequired || isRequiredVisualOnly; const t = useTranslations('Global.labels'); - return ( <> = ({ isRequired={isRequired} isInvalid={isInvalid} aria-required={isRequired} + isDisabled={isDisabled} >
); @@ -467,6 +471,7 @@ const SingleResearchOutputComponent = ({ } handleCellChange(colIndex, newContent); }} + disabled={isDisabled} />
); @@ -506,6 +511,7 @@ const SingleResearchOutputComponent = ({ errorMessage={fieldError} helpMessage={col.content.attributes?.help || col?.help} onChange={val => handleCellChange(colIndex, val)} + isDisabled={isDisabled} />
); @@ -539,6 +545,7 @@ const SingleResearchOutputComponent = ({ errorMessage={fieldError} helpMessage={col.content.attributes?.help || col?.help} onChange={val => handleCellChange(colIndex, val)} + isDisabled={isDisabled} />
); @@ -596,6 +603,7 @@ const SingleResearchOutputComponent = ({ }} checkboxGroupLabel={translatedLabel} checkboxGroupDescription={colHelp} + isDisabled={isDisabled} > {options.map(opt => ( @@ -820,6 +828,7 @@ const SingleResearchOutputComponent = ({ : null; handleCellChange(colIndex, licenseObj ? [licenseObj] : []); }} + isDisabled={isDisabled} />
); @@ -840,6 +849,7 @@ const SingleResearchOutputComponent = ({ handleCellChange(releaseDateColIndex, dateString); }} label={Global('labels.anticipatedReleaseDate')} + isDisabled={isDisabled} />
@@ -861,6 +871,7 @@ const SingleResearchOutputComponent = ({ }} maxLength={10} + disabled={isDisabled} /> {(item) => {item.name}} @@ -896,6 +908,7 @@ const SingleResearchOutputComponent = ({ @@ -903,6 +916,7 @@ const SingleResearchOutputComponent = ({ diff --git a/components/Form/ResearchOutputAnswerComponent/index.tsx b/components/Form/ResearchOutputAnswerComponent/index.tsx index 5d2408638..2ef3859b0 100644 --- a/components/Form/ResearchOutputAnswerComponent/index.tsx +++ b/components/Form/ResearchOutputAnswerComponent/index.tsx @@ -22,6 +22,7 @@ type ResearchOutputAnswerComponentProps = { setRows: React.Dispatch>; onSave?: (rows: ResearchOutputTable[], type?: string) => Promise; // Callback to trigger parent save with current data initialViewMode?: 'list' | 'form'; // Control initial view - 'form' for preview, 'list' for normal use + isDisabled?: boolean; // Whether the component is in read-only mode (e.g., for preview or if question is disabled) onEditingStateChange?: (isEditing: boolean) => void; // Notify parent when entering/leaving single-edit view }; @@ -31,6 +32,7 @@ const ResearchOutputAnswerComponent: React.FC { @@ -215,6 +217,7 @@ const ResearchOutputAnswerComponent: React.FC handleDelete(editingRowIndex!)} isNewEntry={isAddingNew} hasOtherRows={hasOtherRows} + isDisabled={isDisabled} />
); diff --git a/components/Form/TypeAheadWithOther/TypeAheadWithOther.tsx b/components/Form/TypeAheadWithOther/TypeAheadWithOther.tsx index 4f1bc6635..3ee33d211 100644 --- a/components/Form/TypeAheadWithOther/TypeAheadWithOther.tsx +++ b/components/Form/TypeAheadWithOther/TypeAheadWithOther.tsx @@ -28,6 +28,7 @@ export type TypeAheadInputProps = { className?: string; otherText?: string; suggestions: SuggestionInterface[]; + isDisabled?: boolean; onSearch: (searchTerm: string) => void; } @@ -46,8 +47,8 @@ const TypeAheadWithOther = ({ isRequired = false, isRequiredVisualOnly = false, otherText = "Other", + isDisabled = false }: TypeAheadInputProps) => { - const showRequired = isRequired || isRequiredVisualOnly; const Global = useTranslations('Global.labels'); @@ -227,6 +228,7 @@ const TypeAheadWithOther = ({ placeholder={placeholder ? placeholder : 'Type to search...'} ref={inputRef} autoComplete="off" + disabled={isDisabled} /> {(helpText && !error) && ( diff --git a/components/QuestionPreview/index.tsx b/components/QuestionPreview/index.tsx index 41ce6070c..0bd402ed9 100644 --- a/components/QuestionPreview/index.tsx +++ b/components/QuestionPreview/index.tsx @@ -15,6 +15,7 @@ import styles from './QuestionPreview.module.scss'; interface QuestionPreviewProps extends React.HTMLAttributes { buttonLabel?: string, previewDisabled?: boolean, + onOpenChange?: (isOpen: boolean) => void, } @@ -24,6 +25,7 @@ const QuestionPreview: React.FC = ({ className = '', buttonLabel = 'Preview', previewDisabled = true, + onOpenChange }) => { const dialogRef = useRef(null); @@ -122,7 +124,8 @@ const QuestionPreview: React.FC = ({ id={id} className={`${styles.QuestionPreview} ${className}`} > - + {/**Key the inert div so it remounts when preview closes */} + { setOpen(isOpen); onOpenChange?.(isOpen) }}> @@ -583,82 +601,80 @@ const QuestionView: React.FC = ({ )} - {!noSidebar && ( - -
-

{Global('bestPractice')}

- DMP Tool -
- - -

- Give a summary of the data you will collect or create, noting the content, coverage and data type, e.g., tabular data, survey data, experimental measurements, models, software, audiovisual data, physical samples, etc. -

-

- Consider how your data could complement and integrate with existing data, or whether there are any existing data or methods that you could reuse. -

-

- Indicate which data are of long-term value and should be shared and/or preserved. - -

-

- If purchasing or reusing existing data, explain how issues such as copyright and IPR have been addressed. You should aim to minimize any restrictions on the reuse (and subsequent sharing) of third-party data. - -

- -
- - -

- Clearly note what format(s) your data will be in, e.g., plain text (.txt), comma-separated values (.csv), geo-referenced TIFF (.tif, .tfw). -

- -
- - -

- Note what volume of data you will create in MB/GB/TB. Indicate the proportions of raw data, processed data, and other secondary outputs (e.g., reports). -

-

- Consider the implications of data volumes in terms of storage, access, and preservation. Do you need to include additional costs? -

-

- Consider whether the scale of the data will pose challenges when sharing or transferring data between sites; if so, how will you address these challenges? -

-
- - -

This is a very long sentence that should be truncated at word boundaries.

- -
-
- )} + +
+

{Global('bestPractice')}

+ DMP Tool +
+ + +

+ Give a summary of the data you will collect or create, noting the content, coverage and data type, e.g., tabular data, survey data, experimental measurements, models, software, audiovisual data, physical samples, etc. +

+

+ Consider how your data could complement and integrate with existing data, or whether there are any existing data or methods that you could reuse. +

+

+ Indicate which data are of long-term value and should be shared and/or preserved. + +

+

+ If purchasing or reusing existing data, explain how issues such as copyright and IPR have been addressed. You should aim to minimize any restrictions on the reuse (and subsequent sharing) of third-party data. + +

+ +
+ + +

+ Clearly note what format(s) your data will be in, e.g., plain text (.txt), comma-separated values (.csv), geo-referenced TIFF (.tif, .tfw). +

+ +
+ + +

+ Note what volume of data you will create in MB/GB/TB. Indicate the proportions of raw data, processed data, and other secondary outputs (e.g., reports). +

+

+ Consider the implications of data volumes in terms of storage, access, and preservation. Do you need to include additional costs? +

+

+ Consider whether the scale of the data will pose challenges when sharing or transferring data between sites; if so, how will you address these challenges? +

+
+ + +

This is a very long sentence that should be truncated at word boundaries.

+ +
+
) } diff --git a/components/TinyMCEEditor/index.tsx b/components/TinyMCEEditor/index.tsx index 872b1dfba..c153c2ac6 100644 --- a/components/TinyMCEEditor/index.tsx +++ b/components/TinyMCEEditor/index.tsx @@ -25,9 +25,19 @@ interface TinyMCEEditorProps { error?: string; labelId?: string; helpText?: string; + disabled?: boolean; } -const TinyMCEEditor = ({ content, setContent, onChange, error, id, labelId, helpText }: TinyMCEEditorProps) => { +const TinyMCEEditor = ({ + content, + setContent, + onChange, + error, + id, + labelId, + helpText, + disabled = false, +}: TinyMCEEditorProps) => { const editorRef = useRef(null); // Update the type here const elementId = id || 'tiny-editor'; const [isEditorReady, setIsEditorReady] = useState(false); @@ -76,6 +86,17 @@ const TinyMCEEditor = ({ content, setContent, onChange, error, id, labelId, help } }, [content, isEditorReady]); + // If disabled, render content as static HTML + if (disabled) { + return ( +
+ + ) + } + return (
{!isEditorReady && } diff --git a/generated/gql.ts b/generated/gql.ts index cdd4904b8..0b5442c57 100644 --- a/generated/gql.ts +++ b/generated/gql.ts @@ -28,7 +28,7 @@ type Documents = { "mutation AddProjectFunding($input: AddProjectFundingInput!) {\n addProjectFunding(input: $input) {\n id\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n }\n}\n\nmutation UpdateProjectFunding($input: UpdateProjectFundingInput!) {\n updateProjectFunding(input: $input) {\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n }\n}\n\nmutation RemoveProjectFunding($projectFundingId: Int!) {\n removeProjectFunding(projectFundingId: $projectFundingId) {\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n id\n }\n}": typeof types.AddProjectFundingDocument, "mutation UpdateProjectMember($input: UpdateProjectMemberInput!) {\n updateProjectMember(input: $input) {\n givenName\n surName\n orcid\n id\n errors {\n email\n surName\n general\n givenName\n orcid\n affiliationId\n memberRoleIds\n }\n }\n}\n\nmutation RemoveProjectMember($projectMemberId: Int!) {\n removeProjectMember(projectMemberId: $projectMemberId) {\n errors {\n general\n email\n affiliationId\n givenName\n orcid\n surName\n memberRoleIds\n }\n }\n}\n\nmutation AddProjectMember($input: AddProjectMemberInput!) {\n addProjectMember(input: $input) {\n id\n givenName\n surName\n email\n affiliation {\n id\n name\n uri\n }\n orcid\n errors {\n email\n surName\n general\n givenName\n orcid\n memberRoleIds\n }\n }\n}": typeof types.UpdateProjectMemberDocument, "mutation AddProject($title: String!, $isTestProject: Boolean) {\n addProject(title: $title, isTestProject: $isTestProject) {\n id\n errors {\n title\n general\n }\n }\n}\n\nmutation UpdateProject($input: UpdateProjectInput!) {\n updateProject(input: $input) {\n errors {\n general\n title\n abstractText\n endDate\n startDate\n researchDomainId\n }\n }\n}": typeof types.AddProjectDocument, - "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}": typeof types.MoveCustomQuestionDocument, + "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n id\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}": typeof types.MoveCustomQuestionDocument, "mutation AddQuestion($input: AddQuestionInput!) {\n addQuestion(input: $input) {\n errors {\n general\n questionText\n }\n id\n displayOrder\n questionText\n json\n requirementText\n guidanceText\n sampleText\n useSampleTextAsDefault\n required\n }\n}\n\nmutation UpdateQuestion($input: UpdateQuestionInput!) {\n updateQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n questionText\n }\n isDirty\n required\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n questionText\n }\n}\n\nmutation RemoveQuestion($questionId: Int!) {\n removeQuestion(questionId: $questionId) {\n id\n errors {\n general\n guidanceText\n json\n questionText\n requirementText\n sampleText\n }\n }\n}\n\nmutation UpdateQuestionDisplayOrder($questionId: Int!, $newDisplayOrder: Int!) {\n updateQuestionDisplayOrder(\n questionId: $questionId\n newDisplayOrder: $newDisplayOrder\n ) {\n questions {\n id\n displayOrder\n questionText\n sampleText\n requirementText\n guidanceText\n sectionId\n templateId\n errors {\n general\n }\n }\n }\n}": typeof types.AddQuestionDocument, "mutation UpdateRelatedWorkStatus($input: UpdateRelatedWorkStatusInput!) {\n updateRelatedWorkStatus(input: $input) {\n id\n status\n }\n}\n\nmutation UpsertRelatedWork($input: UpsertRelatedWorkInput!) {\n upsertRelatedWork(input: $input) {\n id\n planId\n status\n }\n}": typeof types.UpdateRelatedWorkStatusDocument, "mutation AddRepository($input: AddRepositoryInput) {\n addRepository(input: $input) {\n id\n errors {\n general\n name\n description\n repositoryTypes\n website\n }\n name\n keywords\n uri\n website\n description\n }\n}": typeof types.AddRepositoryDocument, @@ -56,8 +56,8 @@ type Documents = { "query ProjectFunding($projectFundingId: Int!) {\n projectFunding(projectFundingId: $projectFundingId) {\n affiliation {\n name\n displayName\n uri\n }\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n }\n}": typeof types.ProjectFundingDocument, "query ProjectMembers($projectId: Int!) {\n projectMembers(projectId: $projectId) {\n id\n givenName\n surName\n orcid\n memberRoles {\n id\n label\n description\n }\n affiliation {\n displayName\n }\n }\n}\n\nquery ProjectMember($projectMemberId: Int!) {\n projectMember(projectMemberId: $projectMemberId) {\n email\n memberRoles {\n id\n label\n displayOrder\n uri\n }\n givenName\n surName\n affiliation {\n id\n displayName\n uri\n }\n orcid\n }\n}": typeof types.ProjectMembersDocument, "query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}": typeof types.MyProjectsDocument, - "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}": typeof types.AddQuestionCustomizationDocument, - "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}": typeof types.QuestionsDisplayOrderDocument, + "query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}": typeof types.QuestionCustomizationByVersionedQuestionDocument, + "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n id\n }\n }\n}": typeof types.QuestionsDisplayOrderDocument, "query RelatedWorks($id: Int!, $idType: RelatedWorksIdentifierType!, $paginationOptions: PaginationOptions, $filterOptions: RelatedWorksFilterOptions) {\n relatedWorks(\n id: $id\n idType: $idType\n paginationOptions: $paginationOptions\n filterOptions: $filterOptions\n ) {\n items {\n id\n projectId\n planId\n planTitle\n workVersion {\n id\n work {\n id\n doi\n }\n hash\n workType\n publicationDate\n title\n authors {\n orcid\n firstInitial\n givenName\n middleInitials\n middleNames\n surname\n full\n }\n institutions {\n name\n ror\n }\n funders {\n name\n ror\n }\n awards {\n awardId\n }\n publicationVenue\n sourceName\n sourceUrl\n }\n scoreNorm\n confidence\n status\n doiMatch {\n found\n score\n sources {\n parentAwardId\n awardId\n awardUrl\n }\n }\n contentMatch {\n score\n titleHighlight\n abstractHighlights\n }\n authorMatches {\n index\n score\n fields\n }\n institutionMatches {\n index\n score\n fields\n }\n funderMatches {\n index\n score\n fields\n }\n awardMatches {\n index\n score\n fields\n }\n created\n modified\n }\n totalCount\n limit\n currentOffset\n hasNextPage\n hasPreviousPage\n availableSortFields\n statusOnlyCount\n workTypeCounts {\n typeId\n count\n }\n confidenceCounts {\n typeId\n count\n }\n }\n}\n\nquery FindWorkByIdentifier($planId: Int, $doi: String) {\n findWorkByIdentifier(planId: $planId, doi: $doi) {\n items {\n id\n planId\n planTitle\n workVersion {\n work {\n doi\n }\n title\n hash\n workType\n publicationDate\n publicationVenue\n authors {\n orcid\n firstInitial\n givenName\n surname\n full\n }\n sourceName\n sourceUrl\n }\n status\n sourceType\n modified\n }\n }\n}\n\nquery RelatedWorksByPlanStats($planId: Int!) {\n relatedWorksByPlanStats(planId: $planId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}\n\nquery RelatedWorksByProjectStats($projectId: Int!) {\n relatedWorksByProjectStats(projectId: $projectId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}": typeof types.RelatedWorksDocument, "query Repositories($input: RepositorySearchInput!) {\n repositories(input: $input) {\n hasPreviousPage\n hasNextPage\n currentOffset\n availableSortFields\n totalCount\n nextCursor\n limit\n items {\n ... on CustomRepository {\n name\n description\n uri\n website\n repositoryTypes\n errors {\n general\n uri\n }\n keywords\n id\n }\n ... on Re3DataRepository {\n keywords\n id\n name\n description\n uri\n website\n repositoryTypes\n }\n }\n }\n}\n\nquery RepositorySubjectAreas {\n repositorySubjectAreas\n}\n\nquery RepositoriesByURIs($uris: [String!]!) {\n repositoriesByURIs(uris: $uris) {\n keywords\n id\n errors {\n general\n uri\n }\n name\n description\n uri\n website\n repositoryTypes\n }\n}\n\nquery Re3RepositoryTypesList {\n re3RepositoryTypesList {\n totalCount\n types {\n count\n type\n }\n }\n}\n\nquery Re3SubjectList {\n re3SubjectList {\n totalCount\n subjects {\n subject\n count\n }\n }\n}\n\nquery Re3byURIs($uris: [String!]!) {\n re3byURIs(uris: $uris) {\n name\n id\n description\n keywords\n uri\n website\n repositoryTypes\n }\n}": typeof types.RepositoriesDocument, "query TopLevelResearchDomains {\n topLevelResearchDomains {\n name\n description\n id\n }\n}\n\nquery ChildResearchDomains($parentResearchDomainId: Int!) {\n childResearchDomains(parentResearchDomainId: $parentResearchDomainId) {\n id\n name\n description\n }\n}": typeof types.TopLevelResearchDomainsDocument, @@ -69,7 +69,7 @@ type Documents = { "query TemplateCustomizationOverview($templateCustomizationId: Int!) {\n templateCustomizationOverview(templateCustomizationId: $templateCustomizationId) {\n customizationId\n customizationIsDirty\n customizationLastCustomized\n customizationLastCustomizedById\n customizationLastCustomizedByName\n customizationMigrationStatus\n customizationStatus\n customizationLastPublishedDate\n errors {\n general\n affiliationId\n currentVersionedTemplateId\n templateId\n }\n sections {\n id\n hasCustomGuidance\n displayOrder\n migrationStatus\n name\n sectionType\n questions {\n displayOrder\n questionText\n migrationStatus\n id\n hasCustomSampleAnswer\n hasCustomGuidance\n questionType\n }\n }\n versionedTemplateAffiliationId\n versionedTemplateAffiliationName\n versionedTemplateId\n versionedTemplateLastModified\n versionedTemplateName\n versionedTemplateVersion\n }\n}": typeof types.TemplateCustomizationOverviewDocument, "query TemplateVersions($templateId: Int!) {\n templateVersions(templateId: $templateId) {\n name\n version\n versionType\n created\n comment\n id\n modified\n versionedBy {\n givenName\n surName\n affiliation {\n displayName\n }\n modified\n }\n }\n}\n\nquery MyVersionedTemplates {\n myVersionedTemplates {\n id\n templateId\n name\n description\n visibility\n bestPractice\n version\n modified\n modifiedById\n modifiedByName\n ownerId\n ownerURI\n ownerDisplayName\n ownerSearchName\n }\n}\n\nquery PublishedTemplates($paginationOptions: PaginationOptions, $term: String) {\n publishedTemplates(paginationOptions: $paginationOptions, term: $term) {\n limit\n nextCursor\n totalCount\n availableSortFields\n currentOffset\n hasNextPage\n hasPreviousPage\n items {\n id\n templateId\n name\n description\n visibility\n bestPractice\n version\n modified\n modifiedById\n modifiedByName\n ownerId\n ownerURI\n ownerDisplayName\n ownerSearchName\n }\n }\n}\n\nquery PublishedTemplatesMetaData($paginationOptions: PaginationOptions, $term: String) {\n publishedTemplatesMetaData(paginationOptions: $paginationOptions, term: $term) {\n hasBestPracticeTemplates\n availableAffiliations\n }\n}\n\nquery CustomizableTemplates($term: String, $paginationOptions: PaginationOptions) {\n customizableTemplates(term: $term, paginationOptions: $paginationOptions) {\n items {\n customizationId\n customizationIsDirty\n customizationLastCustomized\n customizationLastCustomizedById\n customizationMigrationStatus\n customizationLastCustomizedByName\n versionedTemplateDescription\n versionedTemplateAffiliationId\n versionedTemplateAffiliationName\n versionedTemplateId\n versionedTemplateName\n versionedTemplateVersion\n versionedTemplateBestPractice\n customizationStatus\n versionedTemplateLastModified\n }\n nextCursor\n totalCount\n limit\n }\n}": typeof types.TemplateVersionsDocument, "query Templates($term: String, $paginationOptions: PaginationOptions) {\n myTemplates(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n limit\n availableSortFields\n currentOffset\n hasNextPage\n hasPreviousPage\n items {\n id\n name\n description\n latestPublishVisibility\n isDirty\n latestPublishVersion\n latestPublishDate\n ownerId\n ownerDisplayName\n modified\n modifiedById\n modifiedByName\n bestPractice\n }\n }\n}\n\nquery Template($templateId: Int!) {\n template(templateId: $templateId) {\n id\n name\n description\n errors {\n general\n name\n ownerId\n }\n latestPublishVersion\n latestPublishDate\n created\n sections {\n id\n name\n bestPractice\n displayOrder\n isDirty\n questions {\n errors {\n general\n templateId\n sectionId\n questionText\n displayOrder\n }\n displayOrder\n guidanceText\n id\n questionText\n sectionId\n templateId\n }\n }\n owner {\n displayName\n id\n }\n latestPublishVisibility\n bestPractice\n isDirty\n }\n}\n\nquery TemplateCollaborators($templateId: Int!) {\n template(templateId: $templateId) {\n id\n name\n collaborators {\n email\n id\n user {\n id\n email\n givenName\n surName\n }\n }\n admins {\n givenName\n surName\n email\n }\n owner {\n name\n }\n }\n}": typeof types.TemplatesDocument, - "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n searchName\n uri\n acronyms\n }\n }\n}": typeof types.MeDocument, + "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n displayName\n searchName\n uri\n acronyms\n }\n }\n}": typeof types.MeDocument, "query BestPracticeGuidance($tagIds: [Int!]!) {\n bestPracticeGuidance(tagIds: $tagIds) {\n id\n guidanceText\n guidanceId\n guidance {\n guidanceText\n }\n tagId\n errors {\n guidanceId\n general\n guidanceText\n versionedGuidanceGroupId\n tagId\n }\n }\n}\n\nquery VersionedGuidance($affiliationId: String!, $tagIds: [Int!]!) {\n versionedGuidance(affiliationId: $affiliationId, tagIds: $tagIds) {\n tagId\n id\n guidanceText\n errors {\n general\n guidanceId\n guidanceText\n tagId\n versionedGuidanceGroupId\n }\n }\n}": typeof types.BestPracticeGuidanceDocument, }; const documents: Documents = { @@ -87,7 +87,7 @@ const documents: Documents = { "mutation AddProjectFunding($input: AddProjectFundingInput!) {\n addProjectFunding(input: $input) {\n id\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n }\n}\n\nmutation UpdateProjectFunding($input: UpdateProjectFundingInput!) {\n updateProjectFunding(input: $input) {\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n }\n}\n\nmutation RemoveProjectFunding($projectFundingId: Int!) {\n removeProjectFunding(projectFundingId: $projectFundingId) {\n errors {\n affiliationId\n funderOpportunityNumber\n funderProjectNumber\n general\n grantId\n projectId\n status\n }\n id\n }\n}": types.AddProjectFundingDocument, "mutation UpdateProjectMember($input: UpdateProjectMemberInput!) {\n updateProjectMember(input: $input) {\n givenName\n surName\n orcid\n id\n errors {\n email\n surName\n general\n givenName\n orcid\n affiliationId\n memberRoleIds\n }\n }\n}\n\nmutation RemoveProjectMember($projectMemberId: Int!) {\n removeProjectMember(projectMemberId: $projectMemberId) {\n errors {\n general\n email\n affiliationId\n givenName\n orcid\n surName\n memberRoleIds\n }\n }\n}\n\nmutation AddProjectMember($input: AddProjectMemberInput!) {\n addProjectMember(input: $input) {\n id\n givenName\n surName\n email\n affiliation {\n id\n name\n uri\n }\n orcid\n errors {\n email\n surName\n general\n givenName\n orcid\n memberRoleIds\n }\n }\n}": types.UpdateProjectMemberDocument, "mutation AddProject($title: String!, $isTestProject: Boolean) {\n addProject(title: $title, isTestProject: $isTestProject) {\n id\n errors {\n title\n general\n }\n }\n}\n\nmutation UpdateProject($input: UpdateProjectInput!) {\n updateProject(input: $input) {\n errors {\n general\n title\n abstractText\n endDate\n startDate\n researchDomainId\n }\n }\n}": types.AddProjectDocument, - "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}": types.MoveCustomQuestionDocument, + "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n id\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}": types.MoveCustomQuestionDocument, "mutation AddQuestion($input: AddQuestionInput!) {\n addQuestion(input: $input) {\n errors {\n general\n questionText\n }\n id\n displayOrder\n questionText\n json\n requirementText\n guidanceText\n sampleText\n useSampleTextAsDefault\n required\n }\n}\n\nmutation UpdateQuestion($input: UpdateQuestionInput!) {\n updateQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n questionText\n }\n isDirty\n required\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n questionText\n }\n}\n\nmutation RemoveQuestion($questionId: Int!) {\n removeQuestion(questionId: $questionId) {\n id\n errors {\n general\n guidanceText\n json\n questionText\n requirementText\n sampleText\n }\n }\n}\n\nmutation UpdateQuestionDisplayOrder($questionId: Int!, $newDisplayOrder: Int!) {\n updateQuestionDisplayOrder(\n questionId: $questionId\n newDisplayOrder: $newDisplayOrder\n ) {\n questions {\n id\n displayOrder\n questionText\n sampleText\n requirementText\n guidanceText\n sectionId\n templateId\n errors {\n general\n }\n }\n }\n}": types.AddQuestionDocument, "mutation UpdateRelatedWorkStatus($input: UpdateRelatedWorkStatusInput!) {\n updateRelatedWorkStatus(input: $input) {\n id\n status\n }\n}\n\nmutation UpsertRelatedWork($input: UpsertRelatedWorkInput!) {\n upsertRelatedWork(input: $input) {\n id\n planId\n status\n }\n}": types.UpdateRelatedWorkStatusDocument, "mutation AddRepository($input: AddRepositoryInput) {\n addRepository(input: $input) {\n id\n errors {\n general\n name\n description\n repositoryTypes\n website\n }\n name\n keywords\n uri\n website\n description\n }\n}": types.AddRepositoryDocument, @@ -115,8 +115,8 @@ const documents: Documents = { "query ProjectFunding($projectFundingId: Int!) {\n projectFunding(projectFundingId: $projectFundingId) {\n affiliation {\n name\n displayName\n uri\n }\n status\n grantId\n funderOpportunityNumber\n funderProjectNumber\n }\n}": types.ProjectFundingDocument, "query ProjectMembers($projectId: Int!) {\n projectMembers(projectId: $projectId) {\n id\n givenName\n surName\n orcid\n memberRoles {\n id\n label\n description\n }\n affiliation {\n displayName\n }\n }\n}\n\nquery ProjectMember($projectMemberId: Int!) {\n projectMember(projectMemberId: $projectMemberId) {\n email\n memberRoles {\n id\n label\n displayOrder\n uri\n }\n givenName\n surName\n affiliation {\n id\n displayName\n uri\n }\n orcid\n }\n}": types.ProjectMembersDocument, "query MyProjects($term: String, $paginationOptions: PaginationOptions) {\n myProjects(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n items {\n title\n id\n startDate\n endDate\n fundings {\n name\n grantId\n }\n members {\n name\n role\n orcid\n }\n errors {\n general\n title\n }\n }\n }\n}\n\nquery Project($projectId: Int!) {\n project(projectId: $projectId) {\n title\n abstractText\n startDate\n endDate\n isTestProject\n fundings {\n id\n grantId\n affiliation {\n name\n displayName\n searchName\n }\n }\n members {\n givenName\n surName\n memberRoles {\n description\n displayOrder\n label\n uri\n }\n email\n }\n researchDomain {\n id\n parentResearchDomainId\n }\n plans {\n versionedSections {\n answeredQuestions\n displayOrder\n versionedSectionId\n title\n totalQuestions\n }\n title\n templateTitle\n id\n funding\n dmpId\n registered\n modified\n created\n }\n }\n}\n\nquery ProjectFundingsApi($projectId: Int!) {\n project(projectId: $projectId) {\n fundings {\n affiliation {\n apiTarget\n }\n }\n }\n}": types.MyProjectsDocument, - "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}": types.AddQuestionCustomizationDocument, - "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}": types.QuestionsDisplayOrderDocument, + "query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}": types.QuestionCustomizationByVersionedQuestionDocument, + "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n id\n }\n }\n}": types.QuestionsDisplayOrderDocument, "query RelatedWorks($id: Int!, $idType: RelatedWorksIdentifierType!, $paginationOptions: PaginationOptions, $filterOptions: RelatedWorksFilterOptions) {\n relatedWorks(\n id: $id\n idType: $idType\n paginationOptions: $paginationOptions\n filterOptions: $filterOptions\n ) {\n items {\n id\n projectId\n planId\n planTitle\n workVersion {\n id\n work {\n id\n doi\n }\n hash\n workType\n publicationDate\n title\n authors {\n orcid\n firstInitial\n givenName\n middleInitials\n middleNames\n surname\n full\n }\n institutions {\n name\n ror\n }\n funders {\n name\n ror\n }\n awards {\n awardId\n }\n publicationVenue\n sourceName\n sourceUrl\n }\n scoreNorm\n confidence\n status\n doiMatch {\n found\n score\n sources {\n parentAwardId\n awardId\n awardUrl\n }\n }\n contentMatch {\n score\n titleHighlight\n abstractHighlights\n }\n authorMatches {\n index\n score\n fields\n }\n institutionMatches {\n index\n score\n fields\n }\n funderMatches {\n index\n score\n fields\n }\n awardMatches {\n index\n score\n fields\n }\n created\n modified\n }\n totalCount\n limit\n currentOffset\n hasNextPage\n hasPreviousPage\n availableSortFields\n statusOnlyCount\n workTypeCounts {\n typeId\n count\n }\n confidenceCounts {\n typeId\n count\n }\n }\n}\n\nquery FindWorkByIdentifier($planId: Int, $doi: String) {\n findWorkByIdentifier(planId: $planId, doi: $doi) {\n items {\n id\n planId\n planTitle\n workVersion {\n work {\n doi\n }\n title\n hash\n workType\n publicationDate\n publicationVenue\n authors {\n orcid\n firstInitial\n givenName\n surname\n full\n }\n sourceName\n sourceUrl\n }\n status\n sourceType\n modified\n }\n }\n}\n\nquery RelatedWorksByPlanStats($planId: Int!) {\n relatedWorksByPlanStats(planId: $planId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}\n\nquery RelatedWorksByProjectStats($projectId: Int!) {\n relatedWorksByProjectStats(projectId: $projectId) {\n hasPublishedPlan\n acceptedCount\n pendingCount\n rejectedCount\n totalCount\n }\n}": types.RelatedWorksDocument, "query Repositories($input: RepositorySearchInput!) {\n repositories(input: $input) {\n hasPreviousPage\n hasNextPage\n currentOffset\n availableSortFields\n totalCount\n nextCursor\n limit\n items {\n ... on CustomRepository {\n name\n description\n uri\n website\n repositoryTypes\n errors {\n general\n uri\n }\n keywords\n id\n }\n ... on Re3DataRepository {\n keywords\n id\n name\n description\n uri\n website\n repositoryTypes\n }\n }\n }\n}\n\nquery RepositorySubjectAreas {\n repositorySubjectAreas\n}\n\nquery RepositoriesByURIs($uris: [String!]!) {\n repositoriesByURIs(uris: $uris) {\n keywords\n id\n errors {\n general\n uri\n }\n name\n description\n uri\n website\n repositoryTypes\n }\n}\n\nquery Re3RepositoryTypesList {\n re3RepositoryTypesList {\n totalCount\n types {\n count\n type\n }\n }\n}\n\nquery Re3SubjectList {\n re3SubjectList {\n totalCount\n subjects {\n subject\n count\n }\n }\n}\n\nquery Re3byURIs($uris: [String!]!) {\n re3byURIs(uris: $uris) {\n name\n id\n description\n keywords\n uri\n website\n repositoryTypes\n }\n}": types.RepositoriesDocument, "query TopLevelResearchDomains {\n topLevelResearchDomains {\n name\n description\n id\n }\n}\n\nquery ChildResearchDomains($parentResearchDomainId: Int!) {\n childResearchDomains(parentResearchDomainId: $parentResearchDomainId) {\n id\n name\n description\n }\n}": types.TopLevelResearchDomainsDocument, @@ -128,7 +128,7 @@ const documents: Documents = { "query TemplateCustomizationOverview($templateCustomizationId: Int!) {\n templateCustomizationOverview(templateCustomizationId: $templateCustomizationId) {\n customizationId\n customizationIsDirty\n customizationLastCustomized\n customizationLastCustomizedById\n customizationLastCustomizedByName\n customizationMigrationStatus\n customizationStatus\n customizationLastPublishedDate\n errors {\n general\n affiliationId\n currentVersionedTemplateId\n templateId\n }\n sections {\n id\n hasCustomGuidance\n displayOrder\n migrationStatus\n name\n sectionType\n questions {\n displayOrder\n questionText\n migrationStatus\n id\n hasCustomSampleAnswer\n hasCustomGuidance\n questionType\n }\n }\n versionedTemplateAffiliationId\n versionedTemplateAffiliationName\n versionedTemplateId\n versionedTemplateLastModified\n versionedTemplateName\n versionedTemplateVersion\n }\n}": types.TemplateCustomizationOverviewDocument, "query TemplateVersions($templateId: Int!) {\n templateVersions(templateId: $templateId) {\n name\n version\n versionType\n created\n comment\n id\n modified\n versionedBy {\n givenName\n surName\n affiliation {\n displayName\n }\n modified\n }\n }\n}\n\nquery MyVersionedTemplates {\n myVersionedTemplates {\n id\n templateId\n name\n description\n visibility\n bestPractice\n version\n modified\n modifiedById\n modifiedByName\n ownerId\n ownerURI\n ownerDisplayName\n ownerSearchName\n }\n}\n\nquery PublishedTemplates($paginationOptions: PaginationOptions, $term: String) {\n publishedTemplates(paginationOptions: $paginationOptions, term: $term) {\n limit\n nextCursor\n totalCount\n availableSortFields\n currentOffset\n hasNextPage\n hasPreviousPage\n items {\n id\n templateId\n name\n description\n visibility\n bestPractice\n version\n modified\n modifiedById\n modifiedByName\n ownerId\n ownerURI\n ownerDisplayName\n ownerSearchName\n }\n }\n}\n\nquery PublishedTemplatesMetaData($paginationOptions: PaginationOptions, $term: String) {\n publishedTemplatesMetaData(paginationOptions: $paginationOptions, term: $term) {\n hasBestPracticeTemplates\n availableAffiliations\n }\n}\n\nquery CustomizableTemplates($term: String, $paginationOptions: PaginationOptions) {\n customizableTemplates(term: $term, paginationOptions: $paginationOptions) {\n items {\n customizationId\n customizationIsDirty\n customizationLastCustomized\n customizationLastCustomizedById\n customizationMigrationStatus\n customizationLastCustomizedByName\n versionedTemplateDescription\n versionedTemplateAffiliationId\n versionedTemplateAffiliationName\n versionedTemplateId\n versionedTemplateName\n versionedTemplateVersion\n versionedTemplateBestPractice\n customizationStatus\n versionedTemplateLastModified\n }\n nextCursor\n totalCount\n limit\n }\n}": types.TemplateVersionsDocument, "query Templates($term: String, $paginationOptions: PaginationOptions) {\n myTemplates(term: $term, paginationOptions: $paginationOptions) {\n totalCount\n nextCursor\n limit\n availableSortFields\n currentOffset\n hasNextPage\n hasPreviousPage\n items {\n id\n name\n description\n latestPublishVisibility\n isDirty\n latestPublishVersion\n latestPublishDate\n ownerId\n ownerDisplayName\n modified\n modifiedById\n modifiedByName\n bestPractice\n }\n }\n}\n\nquery Template($templateId: Int!) {\n template(templateId: $templateId) {\n id\n name\n description\n errors {\n general\n name\n ownerId\n }\n latestPublishVersion\n latestPublishDate\n created\n sections {\n id\n name\n bestPractice\n displayOrder\n isDirty\n questions {\n errors {\n general\n templateId\n sectionId\n questionText\n displayOrder\n }\n displayOrder\n guidanceText\n id\n questionText\n sectionId\n templateId\n }\n }\n owner {\n displayName\n id\n }\n latestPublishVisibility\n bestPractice\n isDirty\n }\n}\n\nquery TemplateCollaborators($templateId: Int!) {\n template(templateId: $templateId) {\n id\n name\n collaborators {\n email\n id\n user {\n id\n email\n givenName\n surName\n }\n }\n admins {\n givenName\n surName\n email\n }\n owner {\n name\n }\n }\n}": types.TemplatesDocument, - "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n searchName\n uri\n acronyms\n }\n }\n}": types.MeDocument, + "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n displayName\n searchName\n uri\n acronyms\n }\n }\n}": types.MeDocument, "query BestPracticeGuidance($tagIds: [Int!]!) {\n bestPracticeGuidance(tagIds: $tagIds) {\n id\n guidanceText\n guidanceId\n guidance {\n guidanceText\n }\n tagId\n errors {\n guidanceId\n general\n guidanceText\n versionedGuidanceGroupId\n tagId\n }\n }\n}\n\nquery VersionedGuidance($affiliationId: String!, $tagIds: [Int!]!) {\n versionedGuidance(affiliationId: $affiliationId, tagIds: $tagIds) {\n tagId\n id\n guidanceText\n errors {\n general\n guidanceId\n guidanceText\n tagId\n versionedGuidanceGroupId\n }\n }\n}": types.BestPracticeGuidanceDocument, }; @@ -205,7 +205,7 @@ export function gql(source: "mutation AddProject($title: String!, $isTestProject /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}"): (typeof documents)["mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}"]; +export function gql(source: "mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n id\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}"): (typeof documents)["mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) {\n moveCustomQuestion(input: $input) {\n id\n guidanceText\n errors {\n general\n }\n json\n migrationStatus\n modified\n pinnedQuestionId\n pinnedQuestionType\n questionText\n required\n requirementText\n sampleText\n sectionId\n sectionType\n templateCustomizationId\n useSampleTextAsDefault\n }\n}\n\nmutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n id\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -317,11 +317,11 @@ export function gql(source: "query MyProjects($term: String, $paginationOptions: /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}"): (typeof documents)["mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) {\n addQuestionCustomization(input: $input) {\n id\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n displayOrder\n modified\n questionId\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) {\n updateQuestionCustomization(input: $input) {\n guidanceText\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n id\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n ownerAffiliation {\n displayName\n }\n }\n }\n}\n\nmutation RemoveQuestionCustomization($questionCustomizationId: Int!) {\n removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n versionedQuestion {\n id\n guidanceText\n json\n ownerAffiliation {\n displayName\n }\n }\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n }\n}\n\nquery QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n }\n }\n }\n}"]; +export function gql(source: "query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}"): (typeof documents)["query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) {\n questionCustomizationByVersionedQuestion(\n templateCustomizationId: $templateCustomizationId\n versionedQuestionId: $versionedQuestionId\n ) {\n id\n guidanceText\n migrationStatus\n modified\n questionId\n sampleText\n templateCustomizationId\n errors {\n general\n guidanceText\n migrationStatus\n questionId\n sampleText\n templateCustomizationId\n }\n versionedQuestion {\n id\n guidanceText\n questionText\n requirementText\n required\n sampleText\n ownerAffiliation {\n displayName\n id\n }\n }\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}"): (typeof documents)["query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n }\n }\n}"]; +export function gql(source: "query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n id\n }\n }\n}"): (typeof documents)["query QuestionsDisplayOrder($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n displayOrder\n }\n}\n\nquery PlanSectionQuestions($sectionId: Int!) {\n questions(sectionId: $sectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n sectionId\n templateId\n isDirty\n }\n}\n\nquery Question($questionId: Int!) {\n question(questionId: $questionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n questionConditionIds\n sectionId\n templateId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n sectionId\n templateId\n isDirty\n required\n }\n}\n\nquery PublishedQuestions($planId: Int!, $versionedSectionId: Int!) {\n publishedQuestions(planId: $planId, versionedSectionId: $versionedSectionId) {\n id\n questionText\n displayOrder\n guidanceText\n requirementText\n sampleText\n versionedSectionId\n versionedTemplateId\n hasAnswer\n }\n}\n\nquery PublishedQuestion($versionedQuestionId: Int!) {\n publishedQuestion(versionedQuestionId: $versionedQuestionId) {\n id\n guidanceText\n errors {\n general\n questionText\n requirementText\n sampleText\n displayOrder\n versionedSectionId\n }\n displayOrder\n questionText\n json\n requirementText\n sampleText\n useSampleTextAsDefault\n versionedSectionId\n versionedTemplateId\n required\n ownerAffiliation {\n acronyms\n displayName\n uri\n name\n id\n }\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -369,7 +369,7 @@ export function gql(source: "query Templates($term: String, $paginationOptions: /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n searchName\n uri\n acronyms\n }\n }\n}"): (typeof documents)["query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n searchName\n uri\n acronyms\n }\n }\n}"]; +export function gql(source: "query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n displayName\n searchName\n uri\n acronyms\n }\n }\n}"): (typeof documents)["query Me {\n me {\n id\n givenName\n surName\n languageId\n role\n emails {\n id\n email\n isPrimary\n isConfirmed\n }\n errors {\n general\n email\n password\n role\n }\n affiliation {\n id\n name\n displayName\n searchName\n uri\n acronyms\n }\n }\n}"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/generated/graphql.ts b/generated/graphql.ts index 5ce780b55..f4ae10c87 100644 --- a/generated/graphql.ts +++ b/generated/graphql.ts @@ -5936,6 +5936,27 @@ export type MoveCustomQuestionMutationVariables = Exact<{ export type MoveCustomQuestionMutation = { __typename?: 'Mutation', moveCustomQuestion: { __typename?: 'CustomQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, migrationStatus: TemplateCustomizationMigrationStatus, modified?: string | null, pinnedQuestionId?: number | null, pinnedQuestionType?: CustomizableObjectOwnership | null, questionText?: string | null, required?: boolean | null, requirementText?: string | null, sampleText?: string | null, sectionId: number, sectionType: CustomizableObjectOwnership, templateCustomizationId: number, useSampleTextAsDefault?: boolean | null, errors?: { __typename?: 'CustomQuestionErrors', general?: string | null } | null } }; +export type AddQuestionCustomizationMutationVariables = Exact<{ + input: AddQuestionCustomizationInput; +}>; + + +export type AddQuestionCustomizationMutation = { __typename?: 'Mutation', addQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, displayOrder?: number | null, modified?: string | null, questionId: number, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string, id?: number | null } | null } | null } }; + +export type UpdateQuestionCustomizationMutationVariables = Exact<{ + input: UpdateQuestionCustomizationInput; +}>; + + +export type UpdateQuestionCustomizationMutation = { __typename?: 'Mutation', updateQuestionCustomization: { __typename?: 'QuestionCustomization', guidanceText?: string | null, id?: number | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string, id?: number | null } | null } | null } }; + +export type RemoveQuestionCustomizationMutationVariables = Exact<{ + questionCustomizationId: Scalars['Int']['input']; +}>; + + +export type RemoveQuestionCustomizationMutation = { __typename?: 'Mutation', removeQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string, id?: number | null } | null } | null, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null } }; + export type AddQuestionMutationVariables = Exact<{ input: AddQuestionInput; }>; @@ -6371,34 +6392,13 @@ export type ProjectFundingsApiQueryVariables = Exact<{ export type ProjectFundingsApiQuery = { __typename?: 'Query', project?: { __typename?: 'Project', fundings?: Array<{ __typename?: 'ProjectFunding', affiliation?: { __typename?: 'Affiliation', apiTarget?: string | null } | null }> | null } | null }; -export type AddQuestionCustomizationMutationVariables = Exact<{ - input: AddQuestionCustomizationInput; -}>; - - -export type AddQuestionCustomizationMutation = { __typename?: 'Mutation', addQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, displayOrder?: number | null, modified?: string | null, questionId: number, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } }; - -export type UpdateQuestionCustomizationMutationVariables = Exact<{ - input: UpdateQuestionCustomizationInput; -}>; - - -export type UpdateQuestionCustomizationMutation = { __typename?: 'Mutation', updateQuestionCustomization: { __typename?: 'QuestionCustomization', guidanceText?: string | null, id?: number | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } }; - -export type RemoveQuestionCustomizationMutationVariables = Exact<{ - questionCustomizationId: Scalars['Int']['input']; -}>; - - -export type RemoveQuestionCustomizationMutation = { __typename?: 'Mutation', removeQuestionCustomization: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, json?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null } }; - export type QuestionCustomizationByVersionedQuestionQueryVariables = Exact<{ templateCustomizationId: Scalars['Int']['input']; versionedQuestionId: Scalars['Int']['input']; }>; -export type QuestionCustomizationByVersionedQuestionQuery = { __typename?: 'Query', questionCustomizationByVersionedQuestion?: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, questionText?: string | null, requirementText?: string | null, required?: boolean | null, sampleText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string } | null } | null } | null }; +export type QuestionCustomizationByVersionedQuestionQuery = { __typename?: 'Query', questionCustomizationByVersionedQuestion?: { __typename?: 'QuestionCustomization', id?: number | null, guidanceText?: string | null, migrationStatus?: TemplateCustomizationMigrationStatus | null, modified?: string | null, questionId: number, sampleText?: string | null, templateCustomizationId: number, errors?: { __typename?: 'QuestionCustomizationErrors', general?: string | null, guidanceText?: string | null, migrationStatus?: string | null, questionId?: string | null, sampleText?: string | null, templateCustomizationId?: string | null } | null, versionedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, questionText?: string | null, requirementText?: string | null, required?: boolean | null, sampleText?: string | null, ownerAffiliation?: { __typename?: 'Affiliation', displayName: string, id?: number | null } | null } | null } | null }; export type QuestionsDisplayOrderQueryVariables = Exact<{ sectionId: Scalars['Int']['input']; @@ -6434,7 +6434,7 @@ export type PublishedQuestionQueryVariables = Exact<{ }>; -export type PublishedQuestionQuery = { __typename?: 'Query', publishedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, displayOrder?: number | null, questionText?: string | null, json?: string | null, requirementText?: string | null, sampleText?: string | null, useSampleTextAsDefault?: boolean | null, versionedSectionId: number, versionedTemplateId: number, required?: boolean | null, errors?: { __typename?: 'VersionedQuestionErrors', general?: string | null, questionText?: string | null, requirementText?: string | null, sampleText?: string | null, displayOrder?: string | null, versionedSectionId?: string | null } | null, ownerAffiliation?: { __typename?: 'Affiliation', acronyms?: Array | null, displayName: string, uri: string, name: string } | null } | null }; +export type PublishedQuestionQuery = { __typename?: 'Query', publishedQuestion?: { __typename?: 'VersionedQuestion', id?: number | null, guidanceText?: string | null, displayOrder?: number | null, questionText?: string | null, json?: string | null, requirementText?: string | null, sampleText?: string | null, useSampleTextAsDefault?: boolean | null, versionedSectionId: number, versionedTemplateId: number, required?: boolean | null, errors?: { __typename?: 'VersionedQuestionErrors', general?: string | null, questionText?: string | null, requirementText?: string | null, sampleText?: string | null, displayOrder?: string | null, versionedSectionId?: string | null } | null, ownerAffiliation?: { __typename?: 'Affiliation', acronyms?: Array | null, displayName: string, uri: string, name: string, id?: number | null } | null } | null }; export type RelatedWorksQueryVariables = Exact<{ id: Scalars['Int']['input']; @@ -6648,7 +6648,7 @@ export type TemplateCollaboratorsQuery = { __typename?: 'Query', template?: { __ export type MeQueryVariables = Exact<{ [key: string]: never; }>; -export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id?: number | null, givenName?: string | null, surName?: string | null, languageId: string, role: UserRole, emails?: Array<{ __typename?: 'UserEmail', id?: number | null, email: string, isPrimary: boolean, isConfirmed: boolean } | null> | null, errors?: { __typename?: 'UserErrors', general?: string | null, email?: string | null, password?: string | null, role?: string | null } | null, affiliation?: { __typename?: 'Affiliation', id?: number | null, name: string, searchName: string, uri: string, acronyms?: Array | null } | null } | null }; +export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id?: number | null, givenName?: string | null, surName?: string | null, languageId: string, role: UserRole, emails?: Array<{ __typename?: 'UserEmail', id?: number | null, email: string, isPrimary: boolean, isConfirmed: boolean } | null> | null, errors?: { __typename?: 'UserErrors', general?: string | null, email?: string | null, password?: string | null, role?: string | null } | null, affiliation?: { __typename?: 'Affiliation', id?: number | null, name: string, displayName: string, searchName: string, uri: string, acronyms?: Array | null } | null } | null }; export type BestPracticeGuidanceQueryVariables = Exact<{ tagIds: Array | Scalars['Int']['input']; @@ -6712,6 +6712,9 @@ export const AddProjectMemberDocument = {"kind":"Document","definitions":[{"kind export const AddProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"title"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isTestProject"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addProject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"title"},"value":{"kind":"Variable","name":{"kind":"Name","value":"title"}}},{"kind":"Argument","name":{"kind":"Name","value":"isTestProject"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isTestProject"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"general"}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateProjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateProject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"abstractText"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"researchDomainId"}}]}}]}}]}}]} as unknown as DocumentNode; export const MoveCustomQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveCustomQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MoveCustomQuestionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveCustomQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}}]}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionId"}},{"kind":"Field","name":{"kind":"Name","value":"pinnedQuestionType"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionType"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}}]}}]}}]} as unknown as DocumentNode; +export const AddQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const UpdateQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const RemoveQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]}}]} as unknown as DocumentNode; export const AddQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddQuestionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"required"}}]}}]}}]} as unknown as DocumentNode; export const UpdateQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateQuestionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}}]}}]}}]} as unknown as DocumentNode; export const RemoveQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}}]}}]}}]}}]} as unknown as DocumentNode; @@ -6772,15 +6775,12 @@ export const ProjectMemberDocument = {"kind":"Document","definitions":[{"kind":" export const MyProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"term"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myProjects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"term"},"value":{"kind":"Variable","name":{"kind":"Name","value":"term"}}},{"kind":"Argument","name":{"kind":"Name","value":"paginationOptions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextCursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"grantId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"orcid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Project"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"abstractText"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"isTestProject"}},{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grantId"}},{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"searchName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"memberRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"researchDomain"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentResearchDomainId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plans"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionedSections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"answeredQuestions"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalQuestions"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"templateTitle"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"funding"}},{"kind":"Field","name":{"kind":"Name","value":"dmpId"}},{"kind":"Field","name":{"kind":"Name","value":"registered"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"created"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectFundingsApiDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectFundingsApi"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fundings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiTarget"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const AddQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const UpdateQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateQuestionCustomizationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const RemoveQuestionCustomizationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveQuestionCustomization"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeQuestionCustomization"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionCustomizationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}}]}}]}}]} as unknown as DocumentNode; -export const QuestionCustomizationByVersionedQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionCustomizationByVersionedQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questionCustomizationByVersionedQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const QuestionCustomizationByVersionedQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionCustomizationByVersionedQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questionCustomizationByVersionedQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateCustomizationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateCustomizationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"migrationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"questionId"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"templateCustomizationId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versionedQuestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const QuestionsDisplayOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionsDisplayOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}}]}}]}}]} as unknown as DocumentNode; export const PlanSectionQuestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PlanSectionQuestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"questions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"sectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}}]}}]}}]} as unknown as DocumentNode; export const QuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Question"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"question"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"questionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"questionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionConditionIds"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}},{"kind":"Field","name":{"kind":"Name","value":"required"}}]}}]}}]} as unknown as DocumentNode; export const PublishedQuestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublishedQuestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"planId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedSectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishedQuestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"planId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"planId"}}},{"kind":"Argument","name":{"kind":"Name","value":"versionedSectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedSectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedTemplateId"}},{"kind":"Field","name":{"kind":"Name","value":"hasAnswer"}}]}}]}}]} as unknown as DocumentNode; -export const PublishedQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublishedQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishedQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedTemplateId"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"acronyms"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; +export const PublishedQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublishedQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishedQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"versionedQuestionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionedQuestionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"json"}},{"kind":"Field","name":{"kind":"Name","value":"requirementText"}},{"kind":"Field","name":{"kind":"Name","value":"sampleText"}},{"kind":"Field","name":{"kind":"Name","value":"useSampleTextAsDefault"}},{"kind":"Field","name":{"kind":"Name","value":"versionedSectionId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedTemplateId"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"ownerAffiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"acronyms"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const RelatedWorksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RelatedWorks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RelatedWorksIdentifierType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationOptions"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filterOptions"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"RelatedWorksFilterOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"relatedWorks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"idType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idType"}}},{"kind":"Argument","name":{"kind":"Name","value":"paginationOptions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}}},{"kind":"Argument","name":{"kind":"Name","value":"filterOptions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filterOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"planTitle"}},{"kind":"Field","name":{"kind":"Name","value":"workVersion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"work"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"doi"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hash"}},{"kind":"Field","name":{"kind":"Name","value":"workType"}},{"kind":"Field","name":{"kind":"Name","value":"publicationDate"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"authors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orcid"}},{"kind":"Field","name":{"kind":"Name","value":"firstInitial"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"middleInitials"}},{"kind":"Field","name":{"kind":"Name","value":"middleNames"}},{"kind":"Field","name":{"kind":"Name","value":"surname"}},{"kind":"Field","name":{"kind":"Name","value":"full"}}]}},{"kind":"Field","name":{"kind":"Name","value":"institutions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ror"}}]}},{"kind":"Field","name":{"kind":"Name","value":"funders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ror"}}]}},{"kind":"Field","name":{"kind":"Name","value":"awards"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"awardId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"publicationVenue"}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"scoreNorm"}},{"kind":"Field","name":{"kind":"Name","value":"confidence"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"doiMatch"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"found"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"sources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"parentAwardId"}},{"kind":"Field","name":{"kind":"Name","value":"awardId"}},{"kind":"Field","name":{"kind":"Name","value":"awardUrl"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"contentMatch"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"titleHighlight"}},{"kind":"Field","name":{"kind":"Name","value":"abstractHighlights"}}]}},{"kind":"Field","name":{"kind":"Name","value":"authorMatches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"fields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"institutionMatches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"fields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"funderMatches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"fields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"awardMatches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"fields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"limit"}},{"kind":"Field","name":{"kind":"Name","value":"currentOffset"}},{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"availableSortFields"}},{"kind":"Field","name":{"kind":"Name","value":"statusOnlyCount"}},{"kind":"Field","name":{"kind":"Name","value":"workTypeCounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"typeId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"confidenceCounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"typeId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindWorkByIdentifierDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindWorkByIdentifier"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"planId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"doi"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findWorkByIdentifier"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"planId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"planId"}}},{"kind":"Argument","name":{"kind":"Name","value":"doi"},"value":{"kind":"Variable","name":{"kind":"Name","value":"doi"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"planId"}},{"kind":"Field","name":{"kind":"Name","value":"planTitle"}},{"kind":"Field","name":{"kind":"Name","value":"workVersion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"work"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doi"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"hash"}},{"kind":"Field","name":{"kind":"Name","value":"workType"}},{"kind":"Field","name":{"kind":"Name","value":"publicationDate"}},{"kind":"Field","name":{"kind":"Name","value":"publicationVenue"}},{"kind":"Field","name":{"kind":"Name","value":"authors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orcid"}},{"kind":"Field","name":{"kind":"Name","value":"firstInitial"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surname"}},{"kind":"Field","name":{"kind":"Name","value":"full"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}}]}}]}}]}}]} as unknown as DocumentNode; export const RelatedWorksByPlanStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RelatedWorksByPlanStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"planId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"relatedWorksByPlanStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"planId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"planId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasPublishedPlan"}},{"kind":"Field","name":{"kind":"Name","value":"acceptedCount"}},{"kind":"Field","name":{"kind":"Name","value":"pendingCount"}},{"kind":"Field","name":{"kind":"Name","value":"rejectedCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; @@ -6811,6 +6811,6 @@ export const CustomizableTemplatesDocument = {"kind":"Document","definitions":[{ export const TemplatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Templates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"term"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myTemplates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"term"},"value":{"kind":"Variable","name":{"kind":"Name","value":"term"}}},{"kind":"Argument","name":{"kind":"Name","value":"paginationOptions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paginationOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextCursor"}},{"kind":"Field","name":{"kind":"Name","value":"limit"}},{"kind":"Field","name":{"kind":"Name","value":"availableSortFields"}},{"kind":"Field","name":{"kind":"Name","value":"currentOffset"}},{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishVisibility"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishDate"}},{"kind":"Field","name":{"kind":"Name","value":"ownerId"}},{"kind":"Field","name":{"kind":"Name","value":"ownerDisplayName"}},{"kind":"Field","name":{"kind":"Name","value":"modified"}},{"kind":"Field","name":{"kind":"Name","value":"modifiedById"}},{"kind":"Field","name":{"kind":"Name","value":"modifiedByName"}},{"kind":"Field","name":{"kind":"Name","value":"bestPractice"}}]}}]}}]}}]} as unknown as DocumentNode; export const TemplateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Template"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"template"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ownerId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishDate"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"sections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bestPractice"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}},{"kind":"Field","name":{"kind":"Name","value":"questions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"displayOrder"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"questionText"}},{"kind":"Field","name":{"kind":"Name","value":"sectionId"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"latestPublishVisibility"}},{"kind":"Field","name":{"kind":"Name","value":"bestPractice"}},{"kind":"Field","name":{"kind":"Name","value":"isDirty"}}]}}]}}]} as unknown as DocumentNode; export const TemplateCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TemplateCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"template"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; -export const MeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"languageId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"emails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"isPrimary"}},{"kind":"Field","name":{"kind":"Name","value":"isConfirmed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"searchName"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}},{"kind":"Field","name":{"kind":"Name","value":"acronyms"}}]}}]}}]}}]} as unknown as DocumentNode; +export const MeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"givenName"}},{"kind":"Field","name":{"kind":"Name","value":"surName"}},{"kind":"Field","name":{"kind":"Name","value":"languageId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"emails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"isPrimary"}},{"kind":"Field","name":{"kind":"Name","value":"isConfirmed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"affiliation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"searchName"}},{"kind":"Field","name":{"kind":"Name","value":"uri"}},{"kind":"Field","name":{"kind":"Name","value":"acronyms"}}]}}]}}]}}]} as unknown as DocumentNode; export const BestPracticeGuidanceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BestPracticeGuidance"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"tagIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"bestPracticeGuidance"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"tagIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"tagIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceId"}},{"kind":"Field","name":{"kind":"Name","value":"guidance"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tagId"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guidanceId"}},{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"versionedGuidanceGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"tagId"}}]}}]}}]}}]} as unknown as DocumentNode; export const VersionedGuidanceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"VersionedGuidance"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"affiliationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"tagIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionedGuidance"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"affiliationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"affiliationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"tagIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"tagIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"tagId"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceId"}},{"kind":"Field","name":{"kind":"Name","value":"guidanceText"}},{"kind":"Field","name":{"kind":"Name","value":"tagId"}},{"kind":"Field","name":{"kind":"Name","value":"versionedGuidanceGroupId"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/graphql/mutations/questionCustomization.graphql b/graphql/mutations/questionCustomization.graphql deleted file mode 100644 index 11249beaf..000000000 --- a/graphql/mutations/questionCustomization.graphql +++ /dev/null @@ -1,22 +0,0 @@ -mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) { - moveCustomQuestion(input: $input) { - id - guidanceText - errors { - general - } - json - migrationStatus - modified - pinnedQuestionId - pinnedQuestionType - questionText - required - requirementText - sampleText - sectionId - sectionType - templateCustomizationId - useSampleTextAsDefault - } -} \ No newline at end of file diff --git a/graphql/mutations/questionCustomization.mutation.graphql b/graphql/mutations/questionCustomization.mutation.graphql new file mode 100644 index 000000000..14d79b3ef --- /dev/null +++ b/graphql/mutations/questionCustomization.mutation.graphql @@ -0,0 +1,110 @@ +mutation MoveCustomQuestion($input: MoveCustomQuestionInput!) { + moveCustomQuestion(input: $input) { + id + guidanceText + errors { + general + } + json + migrationStatus + modified + pinnedQuestionId + pinnedQuestionType + questionText + required + requirementText + sampleText + sectionId + sectionType + templateCustomizationId + useSampleTextAsDefault + } +} + +mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) { + addQuestionCustomization(input: $input) { + id + guidanceText + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + displayOrder + modified + questionId + ownerAffiliation { + displayName + id + } + } + } +} + +mutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) { + updateQuestionCustomization(input: $input) { + guidanceText + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + id + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + ownerAffiliation { + displayName + id + } + } + } +} + +mutation RemoveQuestionCustomization($questionCustomizationId: Int!) { + removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) { + id + guidanceText + migrationStatus + modified + questionId + sampleText + templateCustomizationId + versionedQuestion { + id + guidanceText + json + ownerAffiliation { + displayName + id + } + } + errors { + general + guidanceText + migrationStatus + questionId + sampleText + templateCustomizationId + } + } +} \ No newline at end of file diff --git a/graphql/queries/questionCustomization.query.graphql b/graphql/queries/questionCustomization.query.graphql index b347154f0..ee7fc53ac 100644 --- a/graphql/queries/questionCustomization.query.graphql +++ b/graphql/queries/questionCustomization.query.graphql @@ -1,88 +1,3 @@ -mutation AddQuestionCustomization($input: AddQuestionCustomizationInput!) { - addQuestionCustomization(input: $input) { - id - guidanceText - errors { - general - guidanceText - migrationStatus - questionId - sampleText - templateCustomizationId - } - migrationStatus - modified - questionId - sampleText - templateCustomizationId - versionedQuestion { - id - guidanceText - displayOrder - modified - questionId - ownerAffiliation { - displayName - } - } - } -} - -mutation UpdateQuestionCustomization($input: UpdateQuestionCustomizationInput!) { - updateQuestionCustomization(input: $input) { - guidanceText - errors { - general - guidanceText - migrationStatus - questionId - sampleText - templateCustomizationId - } - id - migrationStatus - modified - questionId - sampleText - templateCustomizationId - versionedQuestion { - id - guidanceText - ownerAffiliation { - displayName - } - } - } -} - -mutation RemoveQuestionCustomization($questionCustomizationId: Int!) { - removeQuestionCustomization(questionCustomizationId: $questionCustomizationId) { - id - guidanceText - migrationStatus - modified - questionId - sampleText - templateCustomizationId - versionedQuestion { - id - guidanceText - json - ownerAffiliation { - displayName - } - } - errors { - general - guidanceText - migrationStatus - questionId - sampleText - templateCustomizationId - } - } -} - query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $versionedQuestionId: Int!) { questionCustomizationByVersionedQuestion(templateCustomizationId: $templateCustomizationId, versionedQuestionId: $versionedQuestionId) { id @@ -109,6 +24,7 @@ query QuestionCustomizationByVersionedQuestion($templateCustomizationId: Int!, $ sampleText ownerAffiliation { displayName + id } } } diff --git a/graphql/queries/questions.query.graphql b/graphql/queries/questions.query.graphql index 30564d79d..f60b095d5 100644 --- a/graphql/queries/questions.query.graphql +++ b/graphql/queries/questions.query.graphql @@ -85,6 +85,7 @@ query PublishedQuestion($versionedQuestionId: Int!) { displayName uri name + id } } } \ No newline at end of file diff --git a/graphql/queries/users.query.graphql b/graphql/queries/users.query.graphql index 8d50798aa..e4831a3bb 100644 --- a/graphql/queries/users.query.graphql +++ b/graphql/queries/users.query.graphql @@ -20,6 +20,7 @@ query Me{ affiliation { id name + displayName searchName uri acronyms diff --git a/messages/en-US/global.json b/messages/en-US/global.json index 14b05c716..d4bff3869 100644 --- a/messages/en-US/global.json +++ b/messages/en-US/global.json @@ -850,7 +850,11 @@ "logic": "Logic" }, "headings": { - "preview": "Preview" + "preview": "Preview", + "requirementsByFunder": "Requirements by {funder}", + "guidanceFromOrg": "Guidance from {org}", + "requirements": "Requirements", + "guidance": "Guidance" }, "version": "Version", "questionTypes": { diff --git a/messages/en-US/templateBuilder.json b/messages/en-US/templateBuilder.json index 6cd69c800..59ed489a9 100644 --- a/messages/en-US/templateBuilder.json +++ b/messages/en-US/templateBuilder.json @@ -311,7 +311,7 @@ }, "buttons": { "deleteCustomization": "Delete customization", - "deletingCustomizations": "Deleting customizations..." + "deletingCustomization": "Deleting customizations..." } }, "CreateSectionPage": { diff --git a/styles/form/_radio.scss b/styles/form/_radio.scss index c72abae2d..09f8e330b 100644 --- a/styles/form/_radio.scss +++ b/styles/form/_radio.scss @@ -62,6 +62,15 @@ outline: 2px solid var(--focus-ring-color); outline-offset: 2px; } + + &[data-disabled] { + cursor: not-allowed; + pointer-events: none; + + &:before { + opacity: 0.5; + } + } } .react-aria-RadioGroup .react-aria-Radio+.react-aria-Text[slot="description"] { From 63910c748383095d28552813f9e0241e8d5907de Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Wed, 11 Mar 2026 16:25:50 -0700 Subject: [PATCH 4/7] Added unit tests --- .../__tests__/page.spec.tsx | 993 ++++++++++++++++++ .../customQuestionEdit.module.scss | 12 + .../[customQuestionId]/page.tsx | 559 +++++----- .../__tests__/page.spec.tsx | 801 ++++++++++++++ .../q/[versionedQuestionId]/page.tsx | 8 +- .../q/new/__tests__/page.spec.tsx | 564 ++++++++++ .../[templateCustomizationId]/q/new/page.tsx | 10 +- components/AddQuestionButton/index.tsx | 5 +- components/QuestionPreview/index.tsx | 5 +- messages/en-US/global.json | 4 +- utils/routes.ts | 2 +- 11 files changed, 2666 insertions(+), 297 deletions(-) create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/__tests__/page.spec.tsx create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/__tests__/page.spec.tsx create mode 100644 app/[locale]/template/customizations/[templateCustomizationId]/q/new/__tests__/page.spec.tsx diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/__tests__/page.spec.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/__tests__/page.spec.tsx new file mode 100644 index 000000000..abcb90054 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/__tests__/page.spec.tsx @@ -0,0 +1,993 @@ +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from '@/utils/test-utils'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { + CustomQuestionDocument, + UpdateCustomQuestionDocument, + RemoveCustomQuestionDocument, +} from '@/generated/graphql'; + +import { axe, toHaveNoViolations } from 'jest-axe'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; +import { useToast } from '@/context/ToastContext'; +import logECS from '@/utils/clientLogger'; +import CustomQuestionEdit from '../page'; +import { mockScrollIntoView, mockScrollTo } from "@/__mocks__/common"; + +expect.extend(toHaveNoViolations); + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useParams: jest.fn(), + useSearchParams: jest.fn(), +})); + +// Mock Apollo Client hooks +jest.mock('@apollo/client/react', () => ({ + useQuery: jest.fn(), + useMutation: jest.fn(), +})); + +jest.mock('@/context/ToastContext', () => ({ + useToast: jest.fn(() => ({ + add: jest.fn(), + })), +})); + +// Jest with jsdom doesn't implement structuredClone, which is used in our component code. This causes tests to fail when they try to clone data returned from +// mocked queries/mutations. We can polyfill it in our test environment using JSON.parse/stringify since we don't have any complex data types that require the full capabilities of structuredClone. +if (typeof global.structuredClone !== 'function') { + global.structuredClone = (val) => JSON.parse(JSON.stringify(val)); +} + +const mockUseQuery = jest.mocked(useQuery); +const mockUseMutation = jest.mocked(useMutation); +const mockUseRouter = useRouter as jest.Mock; +const mockUseSearchParams = useSearchParams as jest.Mock; + +// --------------------------------------------------------------------------- +// Mock data +// --------------------------------------------------------------------------- + +const mockRadioButtonQuestion = { + customQuestion: { + __typename: "CustomQuestion", + id: 7, + json: JSON.stringify({ + meta: { schemaVersion: "1.0" }, + type: "radioButtons", + options: [ + { label: "Alpha", value: "Alpha", selected: false }, + { label: "Bravo", value: "Bravo", selected: true }, + ], + attributes: {}, + }), + modified: "2026-03-11 20:01:49", + sampleText: "

This is my sample text

", + requirementText: "

My question requirements

", + questionText: "Custom Radio Button question", + guidanceText: "

My question guidance

", + errors: { + __typename: "CustomQuestionErrors", + general: null, + guidanceText: null, + json: null, + migrationStatus: null, + pinnedQuestionId: null, + pinnedQuestionType: null, + questionText: null, + required: null, + requirementText: null, + sampleText: null, + sectionId: null, + sectionType: null, + templateCustomizationId: null, + useSampleTextAsDefault: null, + }, + migrationStatus: "OK", + pinnedQuestionId: null, + pinnedQuestionType: null, + required: true, + sectionId: 6203, + sectionType: "BASE", + templateCustomizationId: 8, + useSampleTextAsDefault: false, + }, +}; + +const mockTextAreaQuestion = { + customQuestion: { + ...mockRadioButtonQuestion.customQuestion, + id: 8, + questionText: "Custom Text Area question", + json: JSON.stringify({ + meta: { schemaVersion: "1.0" }, + type: "textArea", + attributes: { asRichText: true, cols: 20, rows: 20, maxLength: 1000, minLength: 0 }, + }), + useSampleTextAsDefault: true, + }, +}; + +const mockTextQuestion = { + customQuestion: { + ...mockRadioButtonQuestion.customQuestion, + id: 9, + questionText: "Custom Text question", + json: JSON.stringify({ + meta: { schemaVersion: "1.0" }, + type: "text", + attributes: { maxLength: 1000, minLength: 0, pattern: "^.+$" }, + }), + }, +}; + +const mockDateRangeQuestion = { + customQuestion: { + ...mockRadioButtonQuestion.customQuestion, + id: 10, + questionText: "Custom Date Range question", + json: JSON.stringify({ + meta: { schemaVersion: "1.0" }, + type: "dateRange", + columns: { + start: { label: "Start Date" }, + end: { label: "End Date" }, + }, + attributes: {}, + }), + }, +}; + +const mockAffiliationSearchQuestion = { + customQuestion: { + ...mockRadioButtonQuestion.customQuestion, + id: 11, + questionText: "Custom Affiliation Search question", + json: JSON.stringify({ + meta: { schemaVersion: "1.0" }, + type: "affiliationSearch", + attributes: { + label: "Institution", + help: "Search for your institution", + }, + graphQL: { + query: "query Affiliations($name: String!){ affiliations(name: $name) { items { id displayName uri } } }", + responseField: "affiliations.items", + variables: [{ name: "name", type: "string", label: "Search", minLength: 3 }], + answerField: "uri", + displayFields: [{ label: "Institution", propertyName: "displayName" }], + }, + }), + }, +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let mockUpdateCustomQuestionFn: jest.Mock; +let mockRemoveCustomQuestionFn: jest.Mock; + +const setupMocks = (questionData = mockRadioButtonQuestion) => { + mockUseQuery.mockImplementation((document) => { + if (document === CustomQuestionDocument) { + return { data: questionData, loading: false, error: null } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + mockUpdateCustomQuestionFn = jest.fn().mockResolvedValue({ + data: { updateCustomQuestion: { errors: { general: null, questionText: null } } }, + }); + + mockRemoveCustomQuestionFn = jest.fn().mockResolvedValue({ + data: { removeCustomQuestion: { errors: { general: null } } }, + }); + + mockUseMutation.mockImplementation((document) => { + if (document === UpdateCustomQuestionDocument) { + return [mockUpdateCustomQuestionFn, { loading: false, error: undefined }] as any; + } + if (document === RemoveCustomQuestionDocument) { + return [mockRemoveCustomQuestionFn, { loading: false, error: undefined }] as any; + } + return [jest.fn(), { loading: false, error: undefined }] as any; + }); +}; + +const setupSearchParams = (questionType = 'radioButtons') => { + mockUseSearchParams.mockReturnValue({ + get: (key: string) => { + const params: Record = { questionType }; + return params[key] || null; + }, + getAll: () => [], + has: () => false, + keys() { }, + values() { }, + entries() { }, + forEach() { }, + toString() { return ''; }, + } as unknown as ReturnType); +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("CustomQuestionEdit", () => { + let mockRouter: { push: jest.Mock }; + + beforeEach(() => { + setupMocks(); + setupSearchParams(); + HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + mockScrollTo(); + + (useParams as jest.Mock).mockReturnValue({ + templateCustomizationId: '8', + customQuestionId: '7', + }); + + mockRouter = { push: jest.fn() }; + mockUseRouter.mockReturnValue(mockRouter); + + (useToast as jest.Mock).mockReturnValue({ add: jest.fn() }); + + window.tinymce = { init: jest.fn(), remove: jest.fn() }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + // ------------------------------------------------------------------------- + // Rendering + // ------------------------------------------------------------------------- + + it("should render the page heading", async () => { + render(); + const heading = screen.getByRole('heading', { level: 1 }); + expect(heading).toBeInTheDocument(); + }); + + it("should render core form fields", async () => { + render(); + expect(screen.getByText(/labels.type/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/labels.questionText/i)).toBeInTheDocument(); + expect(screen.getByText(/labels.requirementText/i)).toBeInTheDocument(); + expect(screen.getByText(/labels.guidanceText/i)).toBeInTheDocument(); + }); + + it("should render the save button", async () => { + render(); + expect(screen.getByRole('button', { name: /buttons.saveAndUpdate/i })).toBeInTheDocument(); + }); + + it("should render the sidebar preview section", async () => { + render(); + expect(screen.getByRole('heading', { name: /headings.preview/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('headings.bestPractice'); + }); + + it("should render the delete danger zone", async () => { + render(); + expect(screen.getByText(/headings.deleteQuestion/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /buttons.deleteCustomization/i })).toBeInTheDocument(); + }); + + it("should show loading state while query is in flight", async () => { + mockUseQuery.mockReturnValue({ data: undefined, loading: true, error: undefined } as any); + render(); + expect(screen.queryByLabelText(/labels.questionText/i)).not.toBeInTheDocument(); + }); + + it("should populate question text field from query data", async () => { + render(); + await waitFor(() => { + const input = screen.getByLabelText(/labels.questionText/i) as HTMLInputElement; + expect(input.value).toBe('Custom Radio Button question'); + }); + }); + + // ------------------------------------------------------------------------- + // Question-type-specific rendering + // ------------------------------------------------------------------------- + + it("should render sampleText and useSampleTextAsDefault checkbox for textArea question type", async () => { + setupMocks(mockTextAreaQuestion); + setupSearchParams('textArea'); + + render(); + + await waitFor(() => { + expect(screen.getByText(/labels.sampleText/i)).toBeInTheDocument(); + expect(screen.getByText(/descriptions.sampleTextAsDefault/i)).toBeInTheDocument(); + }); + }); + + it("should NOT render sampleText field for radioButtons question type", async () => { + render(); + await waitFor(() => { + expect(screen.queryByText(/labels.sampleText/i)).not.toBeInTheDocument(); + }); + }); + + it("should render radio options editor for radioButtons question type", async () => { + render(); + await waitFor(() => { + // QuestionOptionsComponent renders rows + expect(screen.getAllByLabelText(/text/i).length).toBeGreaterThan(0); + }); + }); + + it("should render required yes/no radio buttons", async () => { + render(); + await waitFor(() => { + expect(screen.getByText('form.yesLabel')).toBeInTheDocument(); + expect(screen.getByText('form.noLabel')).toBeInTheDocument(); + }); + }); + + it("should have yes radio checked when question.required is true", async () => { + render(); + await waitFor(() => { + const yesRadio = screen.getAllByRole('radio', { name: /form.yesLabel/i })[0]; + expect(yesRadio).toBeChecked(); + }); + }); + + // ------------------------------------------------------------------------- + // Change type button + // ------------------------------------------------------------------------- + + it("should redirect to question types page when Change type button is clicked", async () => { + render(); + + const changeTypeButton = screen.getByRole('button', { name: /buttons.changeType/i }); + fireEvent.click(changeTypeButton); + + await waitFor(() => { + expect(mockRouter.push).toHaveBeenCalledWith( + expect.stringContaining('step=1') + ); + }); + }); + + // ------------------------------------------------------------------------- + // Form validation + // ------------------------------------------------------------------------- + + it("should display error when question text is empty on submit", async () => { + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + fireEvent.change(input, { target: { value: '' } }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(screen.getByText(/messages.errors.questionTextRequired/i)).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // Save / update + // ------------------------------------------------------------------------- + + it("should call updateCustomQuestionMutation with correct variables on save", async () => { + setupMocks(mockTextQuestion); + setupSearchParams('text'); + + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Updated question text' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockUpdateCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + customQuestionId: 7, + questionText: 'Updated question text', + }), + }), + }) + ); + }); + }); + + it("should redirect to template customize page after successful save", async () => { + setupMocks(mockTextQuestion); + setupSearchParams('text'); + + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Updated question text' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockRouter.push).toHaveBeenCalledWith( + expect.stringContaining('/en-US/template/customizations/8') + ); + }); + }); + + it("should show toast on successful save", async () => { + const mockAdd = jest.fn(); + (useToast as jest.Mock).mockReturnValue({ add: mockAdd }); + setupMocks(mockTextQuestion); + setupSearchParams('text'); + + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Updated question text' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockAdd).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ type: 'success' }) + ); + }); + }); + + it("should display error when updateCustomQuestion mutation throws", async () => { + // Call setupMocks first to establish base state + setupMocks(mockTextQuestion); + setupSearchParams('text'); + + // Then override the mutation AFTER setupMocks + mockUseMutation.mockImplementation((document) => { + if (document === UpdateCustomQuestionDocument) { + return [jest.fn().mockRejectedValueOnce(new Error("Network error")), { loading: false }] as any; + } + return [mockRemoveCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + // findByLabelText already waits, but wrap render in act to flush effects first + const input = await screen.findByLabelText(/labels.questionText/i); + + await act(async () => { + fireEvent.change(input, { target: { value: 'Some question' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(screen.getByText(/messages.errors.questionUpdateError/i)).toBeInTheDocument(); + }); + }); + + it("should display general error returned from server on save", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === UpdateCustomQuestionDocument) { + return [jest.fn().mockResolvedValueOnce({ + data: { + updateCustomQuestion: { + errors: { general: 'Server validation error', questionText: null }, + }, + }, + }), { loading: false }] as any; + } + return [mockRemoveCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Some question' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(screen.getByText(/server validation error/i)).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // Unsaved changes warning + // ------------------------------------------------------------------------- + + it("should warn user of unsaved changes when trying to navigate away", async () => { + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + + render(); + + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Changed question text' } }); + }); + + await waitFor(() => { + const handler = addEventListenerSpy.mock.calls + .filter(([event]) => event === 'beforeunload') + .map(([, fn]) => fn) + .pop(); + + expect(handler).toBeDefined(); + + const event = new Event('beforeunload'); + Object.defineProperty(event, 'returnValue', { writable: true, value: undefined }); + + if (typeof handler === 'function') { + handler(event as unknown as BeforeUnloadEvent); + } else if (handler && typeof (handler as EventListenerObject).handleEvent === 'function') { + (handler as EventListenerObject).handleEvent(event as unknown as BeforeUnloadEvent); + } + + expect((event as BeforeUnloadEvent).returnValue).toBe(''); + }); + + removeEventListenerSpy.mockRestore(); + addEventListenerSpy.mockRestore(); + }); + + // ------------------------------------------------------------------------- + // Query errors + // ------------------------------------------------------------------------- + + it("should call logECS when query returns an error", async () => { + mockUseQuery.mockImplementation((document) => { + if (document === CustomQuestionDocument) { + return { + data: mockTextQuestion, + loading: false, + error: { message: 'GraphQL query error' }, + } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + await act(async () => { + render(); + }); + + + await waitFor(() => { + expect(screen.getByText(/graphql query error/i)).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // Delete functionality + // ------------------------------------------------------------------------- + + describe("Delete Custom Question", () => { + it("should open the delete confirmation modal when delete button is clicked", async () => { + await act(async () => { + render(); + }); + + const deleteButton = screen.getByRole('button', { name: /buttons.deleteCustomization/i }); + fireEvent.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /buttons.cancel/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /buttons.confirm/i })).toBeInTheDocument(); + }); + }); + + it("should close the modal when cancel is clicked", async () => { + render(); + + const deleteButton = screen.getByRole('button', { name: /buttons.deleteCustomization/i }); + fireEvent.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + const cancelButton = screen.getByRole('button', { name: /buttons.cancel/i }); + fireEvent.click(cancelButton); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + + it("should call removeCustomQuestionMutation and redirect on successful delete", async () => { + const mockRemove = jest.fn().mockResolvedValueOnce({ + data: { removeCustomQuestion: { errors: { general: null } } }, + }); + + mockUseMutation.mockImplementation((document) => { + if (document === RemoveCustomQuestionDocument) { + return [mockRemove, { loading: false }] as any; + } + return [mockUpdateCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.confirm/i })); + }); + + await waitFor(() => { + expect(mockRemove).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { customQuestionId: 7 }, + }) + ); + expect(mockRouter.push).toHaveBeenCalledWith( + expect.stringContaining('/en-US/template/customizations/8') + ); + }); + }); + + it("should display error message when deletion throws", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === RemoveCustomQuestionDocument) { + return [jest.fn().mockRejectedValueOnce(new Error("Delete failed")), { loading: false }] as any; + } + return [mockUpdateCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.confirm/i })); + }); + + await waitFor(() => { + expect(screen.getByText(/messages.error.errorDeletingQuestion/i)).toBeInTheDocument(); + }); + }); + + it("should call logECS when deletion throws an error", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === RemoveCustomQuestionDocument) { + return [jest.fn().mockRejectedValueOnce(new Error("Delete failed")), { loading: false }] as any; + } + return [mockUpdateCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.confirm/i })); + }); + + await waitFor(() => { + expect(logECS).toHaveBeenCalledWith( + 'error', + 'deleteCustomQuestion', + expect.objectContaining({ + error: expect.anything(), + url: expect.objectContaining({ path: expect.any(String) }), + }) + ); + }); + }); + + it("should display error when server returns errors on deletion response", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === RemoveCustomQuestionDocument) { + return [jest.fn().mockResolvedValueOnce({ + data: { + removeCustomQuestion: { + errors: { general: 'Server deletion error' }, + }, + }, + }), { loading: false }] as any; + } + return [mockUpdateCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.confirm/i })); + }); + + await waitFor(() => { + expect(screen.getByText(/server deletion error/i)).toBeInTheDocument(); + }); + }); + + it("should disable the delete trigger button while deletion is in progress", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === RemoveCustomQuestionDocument) { + return [ + jest.fn().mockImplementation( + () => new Promise((resolve) => + setTimeout(() => resolve({ data: { removeCustomQuestion: { errors: { general: null } } } }), 200) + ) + ), + { loading: false }, + ] as any; + } + return [mockUpdateCustomQuestionFn, { loading: false }] as any; + }); + + render(); + + const deleteButton = screen.getByRole('button', { name: /buttons.deleteCustomization/i }); + fireEvent.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole('button', { name: /buttons.confirm/i })); + + await waitFor(() => { + expect(deleteButton).toBeDisabled(); + }); + }); + }); + + // ------------------------------------------------------------------------- + // handleInputChange - showCommentField radio group + // ------------------------------------------------------------------------- + + it("should update showCommentField when additionalCommentBox radio is changed to 'no'", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText(/labels.additionalCommentBox/i)).toBeInTheDocument(); + }); + + const noRadios = screen.getAllByRole('radio', { name: /labels.doNotShowCommentField/i }); + await act(async () => { + fireEvent.click(noRadios[0]); + }); + + // Verify the mutation is called with showCommentField: false when saved + const input = screen.getByLabelText(/labels.questionText/i); + await act(async () => { + fireEvent.change(input, { target: { value: 'Updated question' } }); + }); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockUpdateCustomQuestionFn).toHaveBeenCalled(); + }); + }); + + it("should update showCommentField when additionalCommentBox radio is changed to 'yes'", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText(/labels.additionalCommentBox/i)).toBeInTheDocument(); + }); + + const yesRadios = screen.getAllByRole('radio', { name: /labels.showCommentField/i }); + await act(async () => { + fireEvent.click(yesRadios[0]); + }); + + await waitFor(() => { + expect(yesRadios[0]).toBeChecked(); + }); + }); + + // ------------------------------------------------------------------------- + // handleRadioChange - required field radio group + // ------------------------------------------------------------------------- + + it("should set required to false when 'no' radio is selected", async () => { + // mockRadioButtonQuestion has required: true, so yes is initially checked + render(); + + await waitFor(() => { + const yesRadio = screen.getAllByRole('radio', { name: /form.yesLabel/i })[0]; + expect(yesRadio).toBeChecked(); + }); + + const noRadio = screen.getAllByRole('radio', { name: /form.noLabel/i })[0]; + await act(async () => { + fireEvent.click(noRadio); + }); + + await waitFor(() => { + expect(noRadio).toBeChecked(); + }); + }); + + it("should set required to true when 'yes' radio is selected", async () => { + setupMocks(mockTextQuestion); // required: false in mockTextQuestion... + // Actually mockTextQuestion inherits required: true from mockRadioButtonQuestion + // so use a question with required: false + setupMocks({ + customQuestion: { + ...mockRadioButtonQuestion.customQuestion, + required: false, + } + }); + setupSearchParams('radioButtons'); + + render(); + + await waitFor(() => { + const noRadio = screen.getAllByRole('radio', { name: /form.noLabel/i })[0]; + expect(noRadio).toBeChecked(); + }); + + const yesRadio = screen.getAllByRole('radio', { name: /form.yesLabel/i })[0]; + await act(async () => { + fireEvent.click(yesRadio); + }); + + await waitFor(() => { + expect(yesRadio).toBeChecked(); + }); + }); + + // ------------------------------------------------------------------------- + // updateRows - options questions + // ------------------------------------------------------------------------- + + it("should update rows and question JSON when options change", async () => { + render(); + + // Wait for options to render (radioButtons question has Alpha and Bravo) + await waitFor(() => { + expect(screen.getAllByLabelText(/text/i).length).toBeGreaterThan(0); + }); + + // Change the first option's text + const optionInputs = screen.getAllByLabelText(/text/i); + await act(async () => { + fireEvent.change(optionInputs[0], { target: { value: 'Updated Option' } }); + }); + + // Save and verify mutation is called (proving JSON was updated) + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockUpdateCustomQuestionFn).toHaveBeenCalled(); + }); + }); + + // ------------------------------------------------------------------------- + // handleRangeLabelChange - dateRange question type + // ------------------------------------------------------------------------- + + it("should update start label for dateRange question type", async () => { + setupMocks(mockDateRangeQuestion); + setupSearchParams('dateRange'); + + render(); + + const startInput = await screen.findByLabelText(/range start/i); + await act(async () => { + fireEvent.change(startInput, { target: { value: 'New Start Label' } }); + }); + + expect(startInput).toHaveValue('New Start Label'); + }); + + it("should update end label for dateRange question type", async () => { + setupMocks(mockDateRangeQuestion); + setupSearchParams('dateRange'); + + render(); + + const endInput = await screen.findByLabelText(/range end/i); + await act(async () => { + fireEvent.change(endInput, { target: { value: 'New End Label' } }); + }); + + expect(endInput).toHaveValue('New End Label'); + }); + + // ------------------------------------------------------------------------- + // handleTypeAheadSearchLabelChange - affiliationSearch question type + // ------------------------------------------------------------------------- + + it("should update typeahead search label for affiliationSearch question type", async () => { + setupMocks(mockAffiliationSearchQuestion); + setupSearchParams('affiliationSearch'); + + render(); + + const labelInput = await screen.findByPlaceholderText(/enter search label/i); + await act(async () => { + fireEvent.change(labelInput, { target: { value: 'Updated Institution Label' } }); + }); + + expect(labelInput).toHaveValue('Updated Institution Label'); + }); + + // ------------------------------------------------------------------------- + // handleTypeAheadHelpTextChange - affiliationSearch question type + // ------------------------------------------------------------------------- + + it("should update typeahead help text for affiliationSearch question type", async () => { + setupMocks(mockAffiliationSearchQuestion); + setupSearchParams('affiliationSearch'); + + render(); + + const helpTextInput = await screen.findByPlaceholderText(/enter the help text/i); + await act(async () => { + fireEvent.change(helpTextInput, { target: { value: 'Updated help text' } }); + }); + + expect(helpTextInput).toHaveValue('Updated help text'); + }); + + // ------------------------------------------------------------------------- + // Accessibility + // ------------------------------------------------------------------------- + + it("should pass axe accessibility checks", async () => { + const { container } = render(); + + await act(async () => { + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); +}); \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss index 79f542e29..c34c483b8 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss +++ b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/customQuestionEdit.module.scss @@ -64,3 +64,15 @@ padding: var(--space-4); margin-bottom: var(--space-4); } + +.questionContainer { + margin-top: 0px; + outline: none; + border: var(--card-border, none); + border-radius: var(--card-border-radius, 0.3125rem); + box-shadow: var(--card-shadow, 0px 4px 6px 0px rgba(0, 0, 0, 0.09)); + padding: 0.75rem; + background-color: var(--card-background, #fff); + margin-bottom: 1rem; + padding: 2rem; +} diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx index cde08d156..a6bd1609f 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx @@ -18,10 +18,6 @@ import { Modal, ModalOverlay, Radio, - Tab, - TabList, - TabPanel, - Tabs, Text, TextField } from "react-aria-components"; @@ -40,8 +36,6 @@ import { QuestionOption, QuestionOptions, QuestionFormatInterface, - RemoveQuestionErrors, - UpdateQuestionErrors, } from '@/app/types'; // Components @@ -61,6 +55,11 @@ import ErrorMessages from '@/components/ErrorMessages'; import QuestionView from '@/components/QuestionView'; import { getParsedQuestionJSON } from '@/components/hooks/getParsedQuestionJSON'; import Loading from '@/components/Loading'; +import { + ContentContainer, + LayoutWithPanel, + SidebarPanel +} from '@/components/Container'; //Utils and Other import { useResearchOutputTable } from '@/app/hooks/useResearchOutputTable'; @@ -68,7 +67,6 @@ import { useToast } from '@/context/ToastContext'; import { routePath } from '@/utils/routes'; import { stripHtmlTags } from '@/utils/general'; import logECS from '@/utils/clientLogger'; -import { extractErrors } from "@/utils/errorHandler"; import { getQuestionFormatInfo, getQuestionTypes, @@ -137,7 +135,7 @@ const CustomQuestionEdit = () => { const Global = useTranslations('Global'); const t = useTranslations('QuestionEdit'); const QuestionAdd = useTranslations('QuestionAdd'); - const QuestionEdit = useTranslations("EditQuestion"); + const QuestionEdit = useTranslations("QuestionEdit"); // Set URLs const TEMPLATE_URL = routePath('template.customize', { templateCustomizationId }); @@ -198,7 +196,9 @@ const CustomQuestionEdit = () => { skip: isBeingDeletedRef.current, }); - + if (selectedQuestionQueryError) { + return ; + } // Update rows state and question.json when options change const updateRows = (newRows: QuestionOptions[]) => { setRows(newRows); @@ -358,7 +358,7 @@ const CustomQuestionEdit = () => { // Make GraphQL mutation request to update the custom question const handleUpdateCustomQuestion = async (e: React.FormEvent) => { e.preventDefault(); - if (isSubmitting) return; + if (isSubmitting) return; // Prevent double submissions setIsSubmitting(true); setFormSubmitted(true); @@ -418,6 +418,7 @@ const CustomQuestionEdit = () => { }; const handleDeleteCustomQuestion = async () => { + if (isDeleting) return; // Prevent double-clicks isBeingDeletedRef.current = true; setIsDeleting(true); try { @@ -446,18 +447,6 @@ const CustomQuestionEdit = () => { } }; - - // Saves any query errors to errors state - useEffect(() => { - const allErrors = []; - - if (selectedQuestionQueryError) { - allErrors.push(selectedQuestionQueryError.message); - } - - setErrors(allErrors); - }, [selectedQuestionQueryError]); - // Set question details in state when data is loaded useEffect(() => { if (selectedQuestion?.customQuestion) { @@ -657,298 +646,294 @@ const CustomQuestionEdit = () => { {announcement}
- -
-
- - - {Global('tabs.editQuestion')} - {Global('tabs.options')} - {Global('tabs.logic')} - - - -
- - - - - - {t('helpText.textField')} - - - - handleQuestionTextChange(e.target.value)} - helpMessage={t('helpText.questionText')} - isInvalid={!question?.questionText} - errorMessage={t('messages.errors.questionTextRequired')} + + +
+ + + + + + + {t('helpText.textField')} + + + + handleQuestionTextChange(e.target.value)} + helpMessage={t('helpText.questionText')} + isInvalid={!question?.questionText} + errorMessage={t('messages.errors.questionTextRequired')} + /> + + {/**Question type fields here */} + {hasOptions && ( +
+

{t('helpText.questionOptions', { questionType })}

+ { + if (!question) return undefined; + const result = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); + return result.parsed ? JSON.stringify(result.parsed) : undefined; + })()} + formSubmitted={formSubmitted} + setFormSubmitted={setFormSubmitted} /> +
+ )} + + {/**Date and Number range question types */} + {questionType && RANGE_QUESTION_TYPE.includes(questionType) && ( + + )} + + {/**Typeahead search question type */} + {questionType && (questionType === TYPEAHEAD_QUESTION_TYPE) && ( + + )} - {/**Question type fields here */} - {hasOptions && ( -
-

{t('helpText.questionOptions', { questionType })}

- { - if (!question) return undefined; - const result = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); - return result.parsed ? JSON.stringify(result.parsed) : undefined; - })()} - formSubmitted={formSubmitted} - setFormSubmitted={setFormSubmitted} /> + {!QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD.includes(questionType ?? '') && ( + handleInputChange('showCommentField', value === 'yes')} + > +
+ {QuestionAdd('labels.showCommentField')}
- )} - - {/**Date and Number range question types */} - {questionType && RANGE_QUESTION_TYPE.includes(questionType) && ( - - )} - - {/**Typeahead search question type */} - {questionType && (questionType === TYPEAHEAD_QUESTION_TYPE) && ( - - )} - - {!QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD.includes(questionType ?? '') && ( - handleInputChange('showCommentField', value === 'yes')} - > -
- {QuestionAdd('labels.showCommentField')} -
- -
- {QuestionAdd('labels.doNotShowCommentField')} -
-
- )} +
+ {QuestionAdd('labels.doNotShowCommentField')} +
+
+ )} + + { + setQuestion(prev => ({ + ...prev, + requirementText: newValue + })); + setHasUnsavedChanges(true); + }} + /> + + + { + setQuestion(prev => ({ + ...prev, + guidanceText: newValue + })); + setHasUnsavedChanges(true); + }} + helpMessage={t('helpText.guidanceText')} + /> + + {questionType === TEXT_AREA_QUESTION_TYPE && ( { setQuestion(prev => ({ ...prev, - requirementText: newValue + sampleText: newValue })); setHasUnsavedChanges(true); }} /> - - - { - setQuestion(prev => ({ - ...prev, - guidanceText: newValue - })); + )} + + {questionType === TEXT_AREA_QUESTION_TYPE && ( + { + setQuestion({ + ...question, + useSampleTextAsDefault: !question?.useSampleTextAsDefault + }); setHasUnsavedChanges(true); }} - helpMessage={t('helpText.guidanceText')} + isSelected={question?.useSampleTextAsDefault || false} + > +
+ +
+ {t('descriptions.sampleTextAsDefault')} + +
+ )} + + {questionType === RESEARCH_OUTPUT_QUESTION_TYPE && ( + + )} - {questionType === TEXT_AREA_QUESTION_TYPE && ( - { - setQuestion(prev => ({ - ...prev, - sampleText: newValue - })); - setHasUnsavedChanges(true); - }} - /> - )} - - {questionType === TEXT_AREA_QUESTION_TYPE && ( - { - setQuestion({ - ...question, - useSampleTextAsDefault: !question?.useSampleTextAsDefault - }); - setHasUnsavedChanges(true); - }} - isSelected={question?.useSampleTextAsDefault || false} - > -
- -
- {t('descriptions.sampleTextAsDefault')} - -
- )} - - {questionType === RESEARCH_OUTPUT_QUESTION_TYPE && ( - - )} + +
+ {Global('form.yesLabel')} +
- -
- {Global('form.yesLabel')} -
+
+ {Global('form.noLabel')} +
+
+ + + + -
- {Global('form.noLabel')} -
-
+ +
+

{t('headings.deleteQuestion')}

+

{t('descriptions.deleteWarning')}

+ - - - - - -

{Global('tabs.options')}

-
- -

{Global('tabs.logic')}

-
- - -
-

{t('headings.deleteQuestion')}

-

{t('descriptions.deleteWarning')}

- - - - - - {({ close }) => ( - <> -

{t('headings.confirmDelete')}

-

{t('descriptions.deleteWarning')}

-
- - -
- - )} -
-
-
-
+ + + + {({ close }) => ( + <> +

{t('headings.confirmDelete')}

+

{t('descriptions.deleteWarning')}

+
+ + +
+ + )} +
+
+
+ +
- -
- - - -
-

{Global('headings.preview')}

-

{t('descriptions.previewText')}

- - - - -

{t('headings.bestPractice')}

-

{t('descriptions.bestPracticePara1')}

-

{t('descriptions.bestPracticePara2')}

-

{t('descriptions.bestPracticePara3')}

-
-
+
+ + <> +

{Global('headings.preview')}

+

{QuestionEdit('descriptions.previewText')}

+ + + + +

{QuestionEdit('headings.bestPractice')}

+

{QuestionEdit('descriptions.bestPracticePara1')}

+

{QuestionEdit('descriptions.bestPracticePara2')}

+

{QuestionEdit('descriptions.bestPracticePara3')}

+ +
+
); diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/__tests__/page.spec.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/__tests__/page.spec.tsx new file mode 100644 index 000000000..299717eb8 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/__tests__/page.spec.tsx @@ -0,0 +1,801 @@ +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from '@/utils/test-utils'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { + AddQuestionCustomizationDocument, + UpdateQuestionCustomizationDocument, + RemoveQuestionCustomizationDocument, + QuestionCustomizationByVersionedQuestionDocument, + PublishedQuestionDocument, + MeDocument, +} from '@/generated/graphql'; + +import { axe, toHaveNoViolations } from 'jest-axe'; +import { useParams, useRouter } from 'next/navigation'; +import { useToast } from '@/context/ToastContext'; +import logECS from '@/utils/clientLogger'; +import QuestionCustomizePage from '../page'; +import { mockScrollIntoView, mockScrollTo } from "@/__mocks__/common"; + +expect.extend(toHaveNoViolations); + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useParams: jest.fn(), +})); + +jest.mock('@apollo/client/react', () => ({ + useQuery: jest.fn(), + useMutation: jest.fn(), +})); + +jest.mock('@/context/ToastContext', () => ({ + useToast: jest.fn(() => ({ add: jest.fn() })), +})); + +const mockUseQuery = jest.mocked(useQuery); +const mockUseMutation = jest.mocked(useMutation); +const mockUseRouter = useRouter as jest.Mock; + +// --------------------------------------------------------------------------- +// Mock data +// --------------------------------------------------------------------------- + +const mockPublishedQuestion = { + publishedQuestion: { + __typename: "PublishedQuestion", + id: 101, + displayOrder: 1, + questionText: "

What data will you collect?

", + requirementText: "

This is a requirement.

", + guidanceText: "

Some base guidance here.

", + sampleText: "

Sample answer text here.

", + useSampleTextAsDefault: false, + required: true, + json: null, + versionedTemplateId: 945, + ownerAffiliation: { + __typename: "Affiliation", + acronyms: ["NIH"], + displayName: "National Institutes of Health", + uri: "https://ror.org/01cwqze88", + name: "National Institutes of Health", + }, + }, +}; + +const mockPublishedQuestionNoOptionalFields = { + publishedQuestion: { + ...mockPublishedQuestion.publishedQuestion, + requirementText: null, + guidanceText: null, + sampleText: null, + }, +}; + +const mockMeData = { + me: { + __typename: "User", + affiliation: { + __typename: "Affiliation", + displayName: "Test University", + name: "Test University", + }, + }, +}; + +const mockExistingCustomization = { + questionCustomizationByVersionedQuestion: { + __typename: "QuestionCustomization", + id: 55, + guidanceText: "

Existing guidance

", + sampleText: "

Existing sample text

", + }, +}; + +const mockNoExistingCustomization = { + questionCustomizationByVersionedQuestion: null +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let mockAddCustomizationFn: jest.Mock; +let mockUpdateCustomizationFn: jest.Mock; +let mockRemoveCustomizationFn: jest.Mock; + +const setupMocks = ( + customizationData: { questionCustomizationByVersionedQuestion: any } = mockExistingCustomization, + publishedQuestionData: { publishedQuestion: any } = mockPublishedQuestion +) => { + mockUseQuery.mockImplementation((document) => { + if (document === PublishedQuestionDocument) { + return { data: publishedQuestionData, loading: false, error: undefined } as any; + } + if (document === MeDocument) { + return { data: mockMeData, loading: false, error: undefined } as any; + } + if (document === QuestionCustomizationByVersionedQuestionDocument) { + return { data: customizationData, loading: false, error: undefined } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + mockAddCustomizationFn = jest.fn().mockResolvedValue({ + data: { addQuestionCustomization: { id: 99, errors: null } }, + }); + + mockUpdateCustomizationFn = jest.fn().mockResolvedValue({ + data: { updateQuestionCustomization: { errors: { general: null, guidanceText: null, sampleText: null } } }, + }); + + mockRemoveCustomizationFn = jest.fn().mockResolvedValue({ + data: { removeQuestionCustomization: { errors: {} } }, + }); + + mockUseMutation.mockImplementation((document) => { + if (document === AddQuestionCustomizationDocument) { + return [mockAddCustomizationFn, { loading: false }] as any; + } + if (document === UpdateQuestionCustomizationDocument) { + return [mockUpdateCustomizationFn, { loading: false }] as any; + } + if (document === RemoveQuestionCustomizationDocument) { + return [mockRemoveCustomizationFn, { loading: false }] as any; + } + return [jest.fn(), { loading: false }] as any; + }); +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("QuestionCustomizePage", () => { + let mockRouter: { push: jest.Mock }; + + beforeEach(() => { + setupMocks(); + HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + mockScrollTo(); + + (useParams as jest.Mock).mockReturnValue({ + templateCustomizationId: '8', + versionedQuestionId: '101', + }); + + mockRouter = { push: jest.fn() }; + mockUseRouter.mockReturnValue(mockRouter); + + (useToast as jest.Mock).mockReturnValue({ add: jest.fn() }); + + window.tinymce = { init: jest.fn(), remove: jest.fn() }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + // ------------------------------------------------------------------------- + // Rendering + // ------------------------------------------------------------------------- + + describe("Rendering", () => { + it("should render the page heading", () => { + render(); + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + + it("should render the save button", () => { + render(); + expect(screen.getByRole('button', { name: /buttons.saveAndUpdate/i })).toBeInTheDocument(); + }); + + it("should render the delete customization danger zone", () => { + render(); + expect(screen.getByRole('button', { name: /buttons.deleteCustomization/i })).toBeInTheDocument(); + }); + + it("should render the sidebar preview section", () => { + render(); + expect(screen.getByRole('heading', { name: /headings.preview/i })).toBeInTheDocument(); + }); + + it("should show loading spinner while publishedQuestion query is in flight", async () => { + mockUseQuery.mockImplementation((document) => { + if (document === PublishedQuestionDocument) { + return { data: undefined, loading: true, error: undefined } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + render(); + + await waitFor(() => { + expect(screen.queryByRole('heading', { level: 1 })).not.toBeInTheDocument(); + }) + }); + + it("should render base question requirements when present", async () => { + render(); + await waitFor(() => { + expect(screen.getByText(/labels.requirements/i)).toBeInTheDocument(); + }); + }); + + it("should render base question guidance when present", async () => { + render(); + await waitFor(() => { + expect(screen.getByText(/labels.guidance/i)).toBeInTheDocument(); + }); + }); + + it("should render base question sample text section when present", async () => { + render(); + await waitFor(() => { + expect(screen.getByText(/labels.sampleText/i)).toBeInTheDocument(); + }); + }); + + it("should NOT render requirements section when requirementText is null", async () => { + setupMocks(mockExistingCustomization, mockPublishedQuestionNoOptionalFields); + render(); + await waitFor(() => { + expect(screen.queryByText(/labels.requirements/i)).not.toBeInTheDocument(); + }); + }); + + it("should NOT render guidance section when guidanceText is null", async () => { + setupMocks(mockExistingCustomization, mockPublishedQuestionNoOptionalFields); + render(); + await waitFor(() => { + expect(screen.queryByText(/labels.guidance/i)).not.toBeInTheDocument(); + }); + }); + + it("should NOT render sample text section when sampleText is null", async () => { + setupMocks(mockExistingCustomization, mockPublishedQuestionNoOptionalFields); + render(); + await waitFor(() => { + expect(screen.queryByText(/labels.sampleText/i)).not.toBeInTheDocument(); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Initialization — addQuestionCustomization vs loading existing + // ------------------------------------------------------------------------- + + describe("Initialization", () => { + it("should call addQuestionCustomization when no existing customization is found", async () => { + setupMocks(mockNoExistingCustomization); + render(); + + await waitFor(() => { + expect(mockAddCustomizationFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + templateCustomizationId: 8, + versionedQuestionId: 101, + }), + }), + }) + ); + }); + }); + + it("should NOT call addQuestionCustomization when an existing customization is found", async () => { + render(); + + await waitFor(() => { + expect(mockAddCustomizationFn).not.toHaveBeenCalled(); + }); + }); + + it("should populate guidance text field with existing customization data", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText(/labels.additionalGuidanceText/i)).toBeInTheDocument(); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Form submission + // ------------------------------------------------------------------------- + + describe("Form submission", () => { + it("should call updateQuestionCustomization with correct variables on save", async () => { + render(); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockUpdateCustomizationFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + questionCustomizationId: 55, + }), + }), + }) + ); + }); + }); + + it("should redirect to template customization page after successful save", async () => { + render(); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockRouter.push).toHaveBeenCalledWith( + expect.stringContaining('/en-US/template/customizations/8') + ); + }); + }); + + it("should show a success toast after saving", async () => { + const mockAdd = jest.fn(); + (useToast as jest.Mock).mockReturnValue({ add: mockAdd }); + + render(); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockAdd).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ type: 'success' }) + ); + }); + }); + + it("should show saving label while submitting", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === UpdateQuestionCustomizationDocument) { + return [ + jest.fn().mockImplementation( + () => new Promise(resolve => + setTimeout(() => resolve({ + data: { updateQuestionCustomization: { errors: { general: null } } } + }), 200) + ) + ), + { loading: false }, + ] as any; + } + return [jest.fn(), { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.saveAndUpdate/i })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /buttons.saving/i })).toBeInTheDocument(); + }); + }); + + it("should prevent double submission", async () => { + mockUseMutation.mockImplementation((document) => { + if (document === UpdateQuestionCustomizationDocument) { + return [ + jest.fn().mockImplementation( + () => new Promise(resolve => + setTimeout(() => resolve({ + data: { updateQuestionCustomization: { errors: { general: null } } } + }), 200) + ) + ), + { loading: false }, + ] as any; + } + return [jest.fn(), { loading: false }] as any; + }); + + render(); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + fireEvent.click(saveButton); + + await waitFor(() => { + expect(saveButton).toBeDisabled(); + }); + }); + + it("should display error when updateQuestionCustomization mutation throws", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === UpdateQuestionCustomizationDocument) { + return [ + jest.fn().mockRejectedValueOnce(new Error("Network error")), + { loading: false } + ] as any; + } + return [jest.fn(), { loading: false }] as any; + }); + + render(); + + const saveButton = screen.getByRole('button', { name: /buttons.saveAndUpdate/i }); + await act(async () => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(screen.getByText(/messages.error.errorUpdatingCustomization/i)).toBeInTheDocument(); + }); + }); + + it("should call logECS when updateQuestionCustomization mutation throws", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === UpdateQuestionCustomizationDocument) { + return [ + jest.fn().mockRejectedValueOnce(new Error("Network error")), + { loading: false } + ] as any; + } + return [jest.fn(), { loading: false }] as any; + }); + + render(); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.saveAndUpdate/i })); + }); + + await waitFor(() => { + expect(logECS).toHaveBeenCalledWith( + 'error', + 'updateQuestionCustomization', + expect.objectContaining({ + error: expect.anything(), + url: expect.objectContaining({ path: expect.any(String) }), + }) + ); + }); + }); + + it("should display general error returned from server on save", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === UpdateQuestionCustomizationDocument) { + return [ + jest.fn().mockResolvedValueOnce({ + data: { + updateQuestionCustomization: { + errors: { general: 'Server validation failed', guidanceText: null, sampleText: null }, + }, + }, + }), + { loading: false }, + ] as any; + } + return [jest.fn(), { loading: false }] as any; + }); + + render(); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.saveAndUpdate/i })); + }); + + await waitFor(() => { + expect(screen.getByText(/server validation failed/i)).toBeInTheDocument(); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Delete customization + // ------------------------------------------------------------------------- + + describe("Delete customization", () => { + it("should open the delete confirmation modal when delete button is clicked", async () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /buttons.cancel/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /buttons.delete/i })).toBeInTheDocument(); + }); + }); + + it("should close the modal when cancel is clicked", async () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole('button', { name: /buttons.cancel/i })); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + + it("should call removeQuestionCustomization and redirect on successful delete", async () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + }); + + await waitFor(() => { + expect(mockRemoveCustomizationFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { questionCustomizationId: 55 }, + }) + ); + expect(mockRouter.push).toHaveBeenCalledWith( + expect.stringContaining('/en-US/template/customizations/8') + ); + }); + }); + + it("should show success toast after successful delete", async () => { + const mockAdd = jest.fn(); + (useToast as jest.Mock).mockReturnValue({ add: mockAdd }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + }); + + await waitFor(() => { + expect(mockAdd).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ type: 'success' }) + ); + }); + }); + + it("should display error message when deletion throws", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === RemoveQuestionCustomizationDocument) { + return [ + jest.fn().mockRejectedValueOnce(new Error("Delete failed")), + { loading: false } + ] as any; + } + return [mockUpdateCustomizationFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + }); + + await waitFor(() => { + expect(screen.getByText(/messages.error.errorDeletingCustomization/i)).toBeInTheDocument(); + }); + }); + + it("should call logECS when deletion throws an error", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === RemoveQuestionCustomizationDocument) { + return [ + jest.fn().mockRejectedValueOnce(new Error("Delete failed")), + { loading: false } + ] as any; + } + return [mockUpdateCustomizationFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + }); + + await waitFor(() => { + expect(logECS).toHaveBeenCalledWith( + 'error', + 'deleteQuestionCustomization', + expect.objectContaining({ + error: expect.anything(), + url: expect.objectContaining({ path: expect.any(String) }), + }) + ); + }); + }); + + it("should display error when server returns errors on deletion response", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === RemoveQuestionCustomizationDocument) { + return [ + jest.fn().mockResolvedValueOnce({ + data: { + removeQuestionCustomization: { + errors: { general: 'Server deletion error' }, + }, + }, + }), + { loading: false }, + ] as any; + } + return [mockUpdateCustomizationFn, { loading: false }] as any; + }); + + render(); + + fireEvent.click(screen.getByRole('button', { name: /buttons.deleteCustomization/i })); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + }); + + await waitFor(() => { + expect(screen.getByText(/server deletion error/i)).toBeInTheDocument(); + }); + }); + + it("should disable the delete trigger button while deletion is in progress", async () => { + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === RemoveQuestionCustomizationDocument) { + return [ + jest.fn().mockImplementation( + () => new Promise(resolve => + setTimeout(() => resolve({ + data: { removeQuestionCustomization: { errors: {} } } + }), 200) + ) + ), + { loading: false }, + ] as any; + } + return [mockUpdateCustomizationFn, { loading: false }] as any; + }); + + render(); + + const deleteButton = screen.getByRole('button', { name: /buttons.deleteCustomization/i }); + fireEvent.click(deleteButton); + + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + + await waitFor(() => { + expect(deleteButton).toBeDisabled(); + }); + }); + + it("should not call removeQuestionCustomization again if deletion already in progress", async () => { + const mockRemove = jest.fn().mockImplementation( + () => new Promise(resolve => + setTimeout(() => resolve({ data: { removeQuestionCustomization: { errors: {} } } }), 200) + ) + ); + + setupMocks(); + mockUseMutation.mockImplementation((document) => { + if (document === RemoveQuestionCustomizationDocument) { + return [mockRemove, { loading: false }] as any; + } + return [mockUpdateCustomizationFn, { loading: false }] as any; + }); + + render(); + + const triggerButton = screen.getByRole('button', { name: /buttons.deleteCustomization/i }); + + // First deletion attempt + fireEvent.click(triggerButton); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + fireEvent.click(screen.getByRole('button', { name: /buttons.delete/i })); + + // Trigger button is now disabled — modal cannot be reopened for a second attempt + await waitFor(() => { + expect(triggerButton).toBeDisabled(); + }); + + // Confirm only one deletion call was made + expect(mockRemove).toHaveBeenCalledTimes(1); + }); + }); + + // ------------------------------------------------------------------------- + // Unsaved changes warning + // ------------------------------------------------------------------------- + + describe("Unsaved changes warning", () => { + it("should warn user when trying to navigate away with unsaved changes", async () => { + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + + render(); + + // Trigger a change to set hasUnsavedChanges + await waitFor(() => { + expect(screen.getByText(/labels.additionalGuidanceText/i)).toBeInTheDocument(); + }); + + // Find the guidance textarea and change it - this calls updateCustomQuestionContent + // which sets hasUnsavedChanges: true + const guidanceTextarea = screen.getByLabelText(/labels.additionalGuidanceText/i); + await act(async () => { + fireEvent.change(guidanceTextarea, { target: { value: 'New guidance text' } }); + }); + + await waitFor(() => { + const handler = addEventListenerSpy.mock.calls + .filter(([event]) => event === 'beforeunload') + .map(([, fn]) => fn) + .pop(); + + expect(handler).toBeDefined(); + + const event = new Event('beforeunload'); + Object.defineProperty(event, 'returnValue', { writable: true, value: undefined }); + + if (typeof handler === 'function') { + handler(event as unknown as BeforeUnloadEvent); + } else if (handler && typeof (handler as EventListenerObject).handleEvent === 'function') { + (handler as EventListenerObject).handleEvent(event as unknown as BeforeUnloadEvent); + } + + expect((event as BeforeUnloadEvent).returnValue).toBe(undefined); + }); + + removeEventListenerSpy.mockRestore(); + addEventListenerSpy.mockRestore(); + }); + }); + + // ------------------------------------------------------------------------- + // Accessibility + // ------------------------------------------------------------------------- + + describe("Accessibility", () => { + it("should pass axe accessibility checks", async () => { + const { container } = render(); + + await act(async () => { + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx index 2528caefb..677887c70 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx @@ -363,14 +363,18 @@ const QuestionCustomizePage: React.FC = () => { richText={true} label={QuestionCustomize('labels.additionalSampleText')} helpMessage={QuestionCustomize('helpText.additionalSampleText')} - value={baseQuestion?.sampleText} + value={customQuestionData?.sampleText} onChange={(value) => updateCustomQuestionContent('sampleText', value)} />
)} - diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/__tests__/page.spec.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/__tests__/page.spec.tsx new file mode 100644 index 000000000..8e89b0fe5 --- /dev/null +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/__tests__/page.spec.tsx @@ -0,0 +1,564 @@ +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from '@/utils/test-utils'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { + AddCustomQuestionDocument, + QuestionsDisplayOrderDocument, + TemplateCustomizationOverviewDocument, +} from '@/generated/graphql'; + +import { axe, toHaveNoViolations } from 'jest-axe'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; +import CustomQuestionNew from '../page'; +import { mockScrollIntoView, mockScrollTo } from "@/__mocks__/common"; + +expect.extend(toHaveNoViolations); + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useParams: jest.fn(), + useSearchParams: jest.fn(), +})); + +jest.mock('@apollo/client/react', () => ({ + useQuery: jest.fn(), + useMutation: jest.fn(), +})); + +jest.mock('@/app/[locale]/template/[templateId]/q/new/utils', () => ({ + useQueryStep: jest.fn(), +})); + +jest.mock('@/utils/questionTypeHandlers', () => ({ + getQuestionTypes: jest.fn(), +})); + +// Simplify child components to keep tests focused on page-level behaviour +jest.mock('@/components/QuestionTypeCard', () => ({ + __esModule: true, + default: ({ questionType, handleSelect }: { questionType: any; handleSelect: Function }) => ( + + ), +})); + +jest.mock('@/components/QuestionAdd', () => ({ + __esModule: true, + default: ({ questionType, onSave }: { questionType: string | null; onSave: Function }) => ( +
+ {questionType} + +
+ ), +})); + +const mockUseQuery = jest.mocked(useQuery); +const mockUseMutation = jest.mocked(useMutation); +const mockUseRouter = useRouter as jest.Mock; +const mockUseSearchParams = useSearchParams as jest.Mock; + +import { useQueryStep } from '@/app/[locale]/template/[templateId]/q/new/utils'; +import { getQuestionTypes } from '@/utils/questionTypeHandlers'; +const mockUseQueryStep = jest.mocked(useQueryStep); +const mockGetQuestionTypes = jest.mocked(getQuestionTypes); + +// --------------------------------------------------------------------------- +// Mock data +// --------------------------------------------------------------------------- + +const mockTemplateOverviewData = { + templateCustomizationOverview: { + __typename: "TemplateCustomizationOverview", + customizationId: 8, + sections: [ + { + __typename: "SectionCustomizationOverview", + id: 6198, + displayOrder: 0, + name: "Element 1: Data Type", + sectionType: "BASE", + questions: [ + { __typename: "QuestionCustomizationOverview", id: 12611, displayOrder: 0, questionText: "Question 1A", questionType: "BASE" }, + { __typename: "QuestionCustomizationOverview", id: 12612, displayOrder: 1, questionText: "Question 1B", questionType: "BASE" }, + { __typename: "QuestionCustomizationOverview", id: 12613, displayOrder: 2, questionText: "Question 1C", questionType: "BASE" }, + ], + }, + { + __typename: "SectionCustomizationOverview", + id: 6203, + displayOrder: 5, + name: "Element 6: Oversight of Data Management and Sharing", + sectionType: "BASE", + questions: [ + { __typename: "QuestionCustomizationOverview", id: 7, displayOrder: 0, questionText: "Custom Text Area question123", questionType: "CUSTOM" }, + { __typename: "QuestionCustomizationOverview", id: 12622, displayOrder: 1, questionText: "Base question", questionType: "BASE" }, + ], + }, + ], + }, +}; + +const mockQuestionTypes = [ + { type: 'text', title: 'Short Text', usageDescription: 'For short text answers' }, + { type: 'textArea', title: 'Long Text', usageDescription: 'For long text answers' }, + { type: 'radioButtons', title: 'Radio Buttons', usageDescription: 'For multiple choice' }, +]; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let mockAddCustomQuestionFn: jest.Mock; + +const setupMocks = () => { + mockUseQuery.mockImplementation((document) => { + if (document === TemplateCustomizationOverviewDocument) { + return { data: mockTemplateOverviewData, loading: false, error: null } as any; + } + if (document === QuestionsDisplayOrderDocument) { + return { + data: { questions: [{ displayOrder: 1 }, { displayOrder: 2 }, { displayOrder: 3 }] }, + loading: false, + error: null, + } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + mockAddCustomQuestionFn = jest.fn().mockResolvedValue({ + data: { addCustomQuestion: { errors: { general: null } } }, + }); + + mockUseMutation.mockImplementation((document) => { + if (document === AddCustomQuestionDocument) { + return [mockAddCustomQuestionFn, { loading: false, error: undefined }] as any; + } + return [jest.fn(), { loading: false, error: undefined }] as any; + }); + + mockGetQuestionTypes.mockReturnValue(mockQuestionTypes as any); + mockUseQueryStep.mockReturnValue(1); +}; + +const setupSearchParams = (params: Record = {}) => { + mockUseSearchParams.mockReturnValue({ + get: (key: string) => params[key] ?? null, + getAll: () => [], + has: () => false, + keys() { }, + values() { }, + entries() { }, + forEach() { }, + toString() { return ''; }, + } as unknown as ReturnType); +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("CustomQuestionNew", () => { + let mockRouter: { push: jest.Mock; replace: jest.Mock }; + + beforeEach(() => { + setupMocks(); + setupSearchParams({ section_id: '6198' }); + HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + mockScrollTo(); + + (useParams as jest.Mock).mockReturnValue({ templateCustomizationId: '8' }); + + mockRouter = { push: jest.fn(), replace: jest.fn() }; + mockUseRouter.mockReturnValue(mockRouter); + + window.tinymce = { init: jest.fn(), remove: jest.fn() }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + // ------------------------------------------------------------------------- + // Step 1 — Question Type Selection + // ------------------------------------------------------------------------- + + describe("Step 1 — Question Type Selection", () => { + it("should render the page heading", () => { + render(); + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + + it("should render the search field", () => { + render(); + expect(screen.getByRole('search')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: "Clear search" })).toBeInTheDocument(); + }); + + it("should render all question type cards on initial load", () => { + render(); + expect(screen.getByTestId('question-type-card-text')).toBeInTheDocument(); + expect(screen.getByTestId('question-type-card-textArea')).toBeInTheDocument(); + expect(screen.getByTestId('question-type-card-radioButtons')).toBeInTheDocument(); + }); + + it("should show loading state while template query is in flight", () => { + mockUseQuery.mockReturnValue({ data: undefined, loading: true, error: undefined } as any); + render(); + expect(screen.queryByRole('heading', { level: 1 })).not.toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // Error handling + // ------------------------------------------------------------------------- + + describe("Error Handling", () => { + it("should render error message when template query fails", () => { + mockUseQuery.mockImplementation((document) => { + if (document === TemplateCustomizationOverviewDocument) { + return { data: undefined, loading: false, error: { message: 'Template query failed' } } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + render(); + expect(screen.getByText(/template query failed/i)).toBeInTheDocument(); + }); + + it("should call logECS when template query fails", () => { + const queryError = new Error('Network error'); + mockUseQuery.mockImplementation((document) => { + if (document === TemplateCustomizationOverviewDocument) { + return { data: undefined, loading: false, error: queryError } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + render(); + + expect(screen.getByText("Network error")).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // Search functionality + // ------------------------------------------------------------------------- + + describe("Search", () => { + it("should filter question type cards when search button is clicked", async () => { + render(); + + await act(async () => { + fireEvent.change(screen.getByRole('searchbox'), { target: { value: 'radio' } }); + }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: "Clear search" })); + }); + + await waitFor(() => { + expect(screen.getByTestId('question-type-card-radioButtons')).toBeInTheDocument(); + expect(screen.queryByTestId('question-type-card-text')).not.toBeInTheDocument(); + expect(screen.queryByTestId('question-type-card-textArea')).not.toBeInTheDocument(); + }); + }); + + it("should show 'no items found' message when search returns no results", async () => { + render(); + + await act(async () => { + fireEvent.change(screen.getByRole('searchbox'), { target: { value: 'zzzznotatype' } }); + }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: "Clear search" })); + }); + + await waitFor(() => { + expect(screen.getByText(/messaging.noItemsFound/i)).toBeInTheDocument(); + }); + }); + + it("should show results count and clear filter button after a successful search", async () => { + render(); + + await act(async () => { + fireEvent.change(screen.getByRole('searchbox'), { target: { value: 'text' } }); + }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: "Clear search" })); + }); + + await waitFor(() => { + expect(screen.getAllByRole('button', { name: /links.clearFilter/i }).length).toBeGreaterThan(0); + expect(screen.getAllByText(/messaging.resultsText/i)).toHaveLength(2); // "1 result" in heading and "1 result found" in clear filter button + }); + }); + + it("should restore the full list when clear filter is clicked", async () => { + render(); + + await act(async () => { + fireEvent.change(screen.getByRole('searchbox'), { target: { value: 'radio' } }); + }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: "Clear search" })); + }); + + await waitFor(() => { + expect(screen.queryByTestId('question-type-card-text')).not.toBeInTheDocument(); + }); + + const clearButton = screen.getAllByRole('button', { name: /links.clearFilter/i })[0]; + await act(async () => { + fireEvent.click(clearButton); + }); + + await waitFor(() => { + expect(screen.getByTestId('question-type-card-text')).toBeInTheDocument(); + expect(screen.getByTestId('question-type-card-textArea')).toBeInTheDocument(); + expect(screen.getByTestId('question-type-card-radioButtons')).toBeInTheDocument(); + }); + }); + + it("should restore full list and hide results count when search term is cleared", async () => { + render(); + + const searchInput = screen.getByRole('searchbox'); + await act(async () => { + fireEvent.change(searchInput, { target: { value: 'radio' } }); + }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Clear search' })); + }); + await act(async () => { + fireEvent.change(searchInput, { target: { value: '' } }); + }); + + await waitFor(() => { + expect(screen.queryByText(/links.clearFilter/i)).not.toBeInTheDocument(); + expect(screen.getByTestId('question-type-card-text')).toBeInTheDocument(); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Question type selection — handleSelect + // ------------------------------------------------------------------------- + + describe("Question type selection", () => { + it("should advance to step 2 and call router.replace when a type is selected (no customQuestionId)", async () => { + render(); + + await act(async () => { + fireEvent.click(screen.getByTestId('question-type-card-text')); + }); + + await waitFor(() => { + expect(mockRouter.replace).toHaveBeenCalledWith( + expect.stringContaining('step=2') + ); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Step 2 — QuestionAdd form + // ------------------------------------------------------------------------- + + describe("Step 2 — QuestionAdd form", () => { + it("should render the QuestionAdd form when step is 2", () => { + mockUseQueryStep.mockReturnValue(2); + render(); + expect(screen.getByTestId('question-add')).toBeInTheDocument(); + }); + + it("should NOT render the question type search/list when step is 2", () => { + mockUseQueryStep.mockReturnValue(2); + render(); + expect(screen.queryByRole('search')).not.toBeInTheDocument(); + expect(screen.queryByTestId('question-type-card-text')).not.toBeInTheDocument(); + }); + + it("should call addCustomQuestionMutation with correct variables when saving", async () => { + mockUseQueryStep.mockReturnValue(2); + render(); + + await waitFor(() => expect(screen.getByTestId('question-add')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /save question/i })); + }); + + await waitFor(() => { + expect(mockAddCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + templateCustomizationId: 8, + sectionId: 6198, + }), + }), + }) + ); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Last question pinning logic (useEffect on data + sectionId) + // ------------------------------------------------------------------------- + + describe("Last question pinning", () => { + it("should pin after the question with the highest displayOrder in the section", async () => { + // Section 6198 has questions with displayOrders 0, 1, 2 — highest is id:12613 (BASE) + mockUseQueryStep.mockReturnValue(2); + setupSearchParams({ section_id: '6198' }); + + render(); + + await waitFor(() => expect(screen.getByTestId('question-add')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /save question/i })); + }); + + await waitFor(() => { + expect(mockAddCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + pinnedQuestionId: 12613, + pinnedQuestionType: 'BASE', + }), + }), + }) + ); + }); + }); + + it("should pin after the last CUSTOM question when section has mixed types", async () => { + // Section 6203: id:7 (CUSTOM, displayOrder:0), id:12622 (BASE, displayOrder:1) → last is 12622 + mockUseQueryStep.mockReturnValue(2); + setupSearchParams({ section_id: '6203' }); + + render(); + + await waitFor(() => expect(screen.getByTestId('question-add')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /save question/i })); + }); + + await waitFor(() => { + expect(mockAddCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + pinnedQuestionId: 12622, + pinnedQuestionType: 'BASE', + }), + }), + }) + ); + }); + }); + + it("should pass pinnedQuestionId: null when the section has no questions", async () => { + mockUseQuery.mockImplementation((document) => { + if (document === TemplateCustomizationOverviewDocument) { + return { + data: { + templateCustomizationOverview: { + sections: [{ id: 6198, displayOrder: 0, questions: [] }], + }, + }, + loading: false, + error: null, + } as any; + } + return { data: null, loading: false, error: undefined }; + }); + + mockUseQueryStep.mockReturnValue(2); + + render(); + + await waitFor(() => expect(screen.getByTestId('question-add')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /save question/i })); + }); + + await waitFor(() => { + expect(mockAddCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + pinnedQuestionId: null, + }), + }), + }) + ); + }); + }); + + it("should pass pinnedQuestionId: null when no section_id param is provided", async () => { + setupSearchParams({}); // no section_id + mockUseQueryStep.mockReturnValue(2); + + render(); + + await waitFor(() => expect(screen.getByTestId('question-add')).toBeInTheDocument()); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /save question/i })); + }); + + await waitFor(() => { + expect(mockAddCustomQuestionFn).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + input: expect.objectContaining({ + pinnedQuestionId: null, + }), + }), + }) + ); + }); + }); + }); + + // ------------------------------------------------------------------------- + // Accessibility + // ------------------------------------------------------------------------- + + describe("Accessibility", () => { + it("should pass axe accessibility checks on step 1", async () => { + const { container } = render(); + await act(async () => { + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); + + it("should pass axe accessibility checks on step 2", async () => { + mockUseQueryStep.mockReturnValue(2); + const { container } = render(); + await act(async () => { + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx index 8b2591ad9..fb073aa55 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx @@ -31,6 +31,7 @@ import { ContentContainer, LayoutContainer, } from '@/components/Container'; import QuestionAdd from '@/components/QuestionAdd'; import QuestionTypeCard from '@/components/QuestionTypeCard'; import ErrorMessages from '@/components/ErrorMessages'; +import Loading from '@/components/Loading'; //Other import { scrollToTop } from '@/utils/general'; @@ -86,6 +87,9 @@ const CustomQuestionNew: React.FC = () => { variables: { templateCustomizationId: Number(templateCustomizationId) }, }); + if (templateQueryErrors) { + return ; + } // Query request for questions to calculate max displayOrder const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, { @@ -131,7 +135,7 @@ const CustomQuestionNew: React.FC = () => { //If the user came from editing an existing question, we want to return them to that page with the new questionTypeId // We need to use a full page reload to ensure all state is reset so that 'beforeunload' events are properly handled in the next page // to display unsaved changes warning if needed - window.location.href = routePath('template.customize', { templateCustomizationId }); + window.location.href = routePath('template.customQuestion', { templateCustomizationId, customQuestionId }, { section_id: sectionId, step: 1, questionType, questionName: questionTypeName, questionJSON }); } else { // redirect to the Question Edit page if a user is adding a new question @@ -238,6 +242,10 @@ const CustomQuestionNew: React.FC = () => { }, [data, sectionId]); + if (loading) { + return ; + } + return ( <> {step === 1 && ( diff --git a/components/AddQuestionButton/index.tsx b/components/AddQuestionButton/index.tsx index c73a6be9e..c77f4edcf 100644 --- a/components/AddQuestionButton/index.tsx +++ b/components/AddQuestionButton/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Link from 'next/link'; import { useTranslations } from 'next-intl'; import styles from './AddQuestionButton.module.scss'; @@ -22,7 +23,7 @@ const AddQuestionButton: React.FC = ({ return ( ); }; diff --git a/components/QuestionPreview/index.tsx b/components/QuestionPreview/index.tsx index 0bd402ed9..22f0c95f0 100644 --- a/components/QuestionPreview/index.tsx +++ b/components/QuestionPreview/index.tsx @@ -120,11 +120,10 @@ const QuestionPreview: React.FC = ({ return ( - - {/**Key the inert div so it remounts when preview closes */} { setOpen(isOpen); onOpenChange?.(isOpen) }}>
)} - +
) } diff --git a/messages/en-US/global.json b/messages/en-US/global.json index d4bff3869..46178a80b 100644 --- a/messages/en-US/global.json +++ b/messages/en-US/global.json @@ -766,7 +766,9 @@ "applyFilterSingular": "Apply filter", "moreInfo": "More info", "lessInfo": "Less info", - "createNewPlan": "Create New DMP" + "createNewPlan": "Create New DMP", + "deletingCustomization": "Deleting customization...", + "deleteCustomization": "Delete customization" }, "labels": { "required": "required", diff --git a/utils/routes.ts b/utils/routes.ts index bd76080e3..dafbaac81 100644 --- a/utils/routes.ts +++ b/utils/routes.ts @@ -110,7 +110,7 @@ const routes = { 'template.customSection': '/template/customizations/:templateCustomizationId/customSection/:customSectionId', 'template.customize.section.create': '/template/customizations/:templateCustomizationId/section/create', 'template.customize.question.create': '/template/customizations/:templateCustomizationId/q/new', - 'template.customQuestion': '/template/customizations/:templateCustomizationId/q/:customQuestionId', + 'template.customQuestion': '/template/customizations/:templateCustomizationId/customQuestion/:customQuestionId', // account/profile 'account.index': '/account', From e9a24892c69f98a18e16b6926e5ce0a795f35b38 Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Thu, 12 Mar 2026 11:07:53 -0700 Subject: [PATCH 5/7] optimized pages and fixed unit tests --- .../q/new/__tests__/page.spec.tsx | 31 +- .../template/[templateId]/q/new/page.tsx | 24 +- .../template/create/__tests__/page.spec.tsx | 2 +- .../[customQuestionId]/page.tsx | 45 +- .../q/[versionedQuestionId]/page.tsx | 17 +- .../[templateCustomizationId]/q/new/page.tsx | 59 +- .../section/create/__tests__/page.spec.tsx | 2 +- .../section/create/page.tsx | 25 +- .../ResearchOutputAnswerComponent/index.tsx | 43 +- .../QuestionAdd/__tests__/index.spec.tsx | 549 +++++++++--------- components/QuestionAdd/index.tsx | 11 - components/QuestionPreview/index.tsx | 6 +- .../QuestionView/__tests__/index.spec.tsx | 12 - components/QuestionView/index.tsx | 2 - docker-compose.yml | 4 +- lib/graphql/errorTypePolicies.ts | 6 +- messages/en-US/global.json | 3 +- messages/en-US/questionView.json | 2 +- messages/en-US/templateBuilder.json | 3 +- messages/pt-BR/global.json | 3 +- messages/pt-BR/questionView.json | 2 +- messages/pt-BR/templateBuilder.json | 3 +- 22 files changed, 430 insertions(+), 424 deletions(-) diff --git a/app/[locale]/template/[templateId]/q/new/__tests__/page.spec.tsx b/app/[locale]/template/[templateId]/q/new/__tests__/page.spec.tsx index 78be293f8..3f7e6f6bd 100644 --- a/app/[locale]/template/[templateId]/q/new/__tests__/page.spec.tsx +++ b/app/[locale]/template/[templateId]/q/new/__tests__/page.spec.tsx @@ -5,9 +5,21 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { useQueryStep } from '@/app/[locale]/template/[templateId]/q/new/utils'; import QuestionTypeSelectPage from "../page"; import { mockScrollIntoView, mockScrollTo } from "@/__mocks__/common"; - +import { useMutation, useQuery } from '@apollo/client/react'; +import { + AddQuestionDocument, + QuestionsDisplayOrderDocument, +} from '@/generated/graphql'; expect.extend(toHaveNoViolations); +jest.mock('@apollo/client/react', () => ({ + useQuery: jest.fn(), + useMutation: jest.fn(), +})); + +const mockUseQuery = jest.mocked(useQuery); +const mockUseMutation = jest.mocked(useMutation); + jest.mock('next/navigation', () => ({ useParams: jest.fn(), useRouter: jest.fn(), @@ -76,10 +88,27 @@ describe("QuestionTypeSelectPage", () => { } as unknown as ReturnType; }); + mockUseMutation.mockImplementation(() => [ + jest.fn().mockResolvedValue({ data: {} }), + { loading: false, error: undefined } + ] as any); + + mockUseQuery.mockImplementation(() => ({ + data: { + questions: [ + { displayOrder: 1 }, + { displayOrder: 2 }, + ] + }, + loading: false, + error: undefined, + }) as any); }); it("should render data returned from template query correctly", async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); + await act(async () => { render( diff --git a/app/[locale]/template/[templateId]/q/new/page.tsx b/app/[locale]/template/[templateId]/q/new/page.tsx index e6f8d242f..05fb26228 100644 --- a/app/[locale]/template/[templateId]/q/new/page.tsx +++ b/app/[locale]/template/[templateId]/q/new/page.tsx @@ -39,6 +39,17 @@ import { QuestionFormatInterface } from '@/app/types'; import styles from './newQuestion.module.scss'; import { getQuestionTypes } from "@/utils/questionTypeHandlers"; +const TemplateQuestionBreadcrumbs = ({ templateId, sectionId, Global }: { templateId: string; sectionId: string; Global: (key: string, values?: Record) => string }) => { + return ( + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templates')} + {Global('breadcrumbs.editTemplate')} + {Global('breadcrumbs.selectQuestionType')} + {Global('breadcrumbs.question')} + + ) +} const QuestionTypeSelectPage: React.FC = () => { // Get templateId param @@ -183,17 +194,6 @@ const QuestionTypeSelectPage: React.FC = () => { } }, [stepQueryValue]) - const TemplateQuestionBreadcrumbs = () => { - return ( - - {Global('breadcrumbs.home')} - {Global('breadcrumbs.templates')} - {Global('breadcrumbs.editTemplate')} - {Global('breadcrumbs.selectQuestionType')} - {Global('breadcrumbs.question')} - - ) - } return ( <> {step === 1 && ( @@ -298,7 +298,7 @@ const QuestionTypeSelectPage: React.FC = () => { questionName={selectedQuestionType?.questionName ?? null} questionJSON={selectedQuestionType?.questionJSON ?? ''} sectionId={sectionId ? sectionId : ''} - breadcrumbs={} + breadcrumbs={} backUrl={routePath('template.q.new', { templateId }, { section_id: sectionId, step: 1 })} successUrl={routePath('template.show', { templateId })} onSave={async (commonFields) => { diff --git a/app/[locale]/template/create/__tests__/page.spec.tsx b/app/[locale]/template/create/__tests__/page.spec.tsx index 3d12f2812..6d6f169c2 100644 --- a/app/[locale]/template/create/__tests__/page.spec.tsx +++ b/app/[locale]/template/create/__tests__/page.spec.tsx @@ -39,7 +39,7 @@ describe('TemplateCreatePage', () => { (useQueryStep as jest.Mock).mockReturnValue(null); // No step in query initially render(); - expect(screen.getByText('...messaging.loading')).toBeInTheDocument(); + expect(screen.getByText('messaging.loading')).toBeInTheDocument(); }); it('should render step 1 form when step is 1', () => { diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx index a6bd1609f..40781dc57 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/customQuestion/[customQuestionId]/page.tsx @@ -110,8 +110,6 @@ const CustomQuestionEdit = () => { /*To be able to show a loading state when redirecting after successful update because otherwise there is a bit of a stutter where the page reloads before redirecting*/ const [isRedirecting, setIsRedirecting] = useState(false); - // State for delete confirmation modal - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); // State for managing form inputs @@ -196,9 +194,6 @@ const CustomQuestionEdit = () => { skip: isBeingDeletedRef.current, }); - if (selectedQuestionQueryError) { - return ; - } // Update rows state and question.json when options change const updateRows = (newRows: QuestionOptions[]) => { setRows(newRows); @@ -325,7 +320,7 @@ const CustomQuestionEdit = () => { if (!parsed) { if (error) { - setErrors(prev => [...prev, error]) + setErrors([error]); } return; } @@ -345,7 +340,7 @@ const CustomQuestionEdit = () => { if (!parsed) { if (error) { - setErrors(prev => [...prev, error]) + setErrors([error]); } return; } @@ -372,7 +367,7 @@ const CustomQuestionEdit = () => { if (!success || error) { const errorMessage = error ?? t('messages.errors.questionUpdateError'); - setErrors(prev => [...prev, errorMessage]); + setErrors([errorMessage]); announce(QuestionAdd('researchOutput.announcements.errorOccurred') || 'An error occurred.'); setIsSubmitting(false); return; @@ -413,7 +408,7 @@ const CustomQuestionEdit = () => { error, url: { path: TEMPLATE_URL } }); - setErrors(prev => [...prev, t('messages.errors.questionUpdateError')]); + setErrors([t('messages.errors.questionUpdateError')]); } }; @@ -442,8 +437,7 @@ const CustomQuestionEdit = () => { setErrors([QuestionEdit('messages.error.errorDeletingQuestion')]); } finally { setIsDeleting(false); - setIsDeleteModalOpen(false); - setIsSubmitting(false); + setConfirmOpen(false); } }; @@ -463,7 +457,7 @@ const CustomQuestionEdit = () => { url: { path: routePath('template.customize', { templateCustomizationId }) } }); - setErrors(prev => [...prev, error]) + setErrors([error]) } return; } @@ -504,7 +498,7 @@ const CustomQuestionEdit = () => { error, url: { path: routePath('template.customize', { templateCustomizationId }) } }); - setErrors(prev => [...prev, 'Error parsing question data']); + setErrors([Global('messaging.errors.errorParsingData')]); } } }, [selectedQuestion]); @@ -547,14 +541,6 @@ const CustomQuestionEdit = () => { } }, [parsedQuestionJSON, hydrateFromJSON]); - // If a user changes their question type, then we need to fetch the question types to set the new json schema - useEffect(() => { - // Only fetch question types if we have a questionType query param present - if (questionTypeIdQueryParam) { - getQuestionTypes(); - } - }, [questionTypeIdQueryParam]); - // If a user passes in a questionType query param we will find the matching questionTypes // json schema and update the question with it @@ -591,7 +577,7 @@ const CustomQuestionEdit = () => { const { parsed, error } = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); if (!parsed) { if (error) { - setErrors(prev => [...prev, error]) + setErrors([error]) } return; } @@ -618,6 +604,10 @@ const CustomQuestionEdit = () => { return ; } + if (selectedQuestionQueryError) { + return ; + } + return ( <> { { - if (!question) return undefined; - const result = getParsedQuestionJSON(question, routePath('template.customize', { templateCustomizationId }), Global); - return result.parsed ? JSON.stringify(result.parsed) : undefined; - })()} + questionJSON={parsedQuestionJSON ? JSON.stringify(parsedQuestionJSON) : undefined} formSubmitted={formSubmitted} setFormSubmitted={setFormSubmitted} />
@@ -722,7 +708,7 @@ const CustomQuestionEdit = () => { {!QUESTION_TYPES_EXCLUDED_FROM_COMMENT_FIELD.includes(questionType ?? '') && ( handleInputChange('showCommentField', value === 'yes')} @@ -844,7 +830,7 @@ const CustomQuestionEdit = () => { )} { className={`danger `} onPress={() => { handleDeleteCustomQuestion(); - close(); }}> {Global('buttons.confirm')} diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx index 677887c70..51dabdc24 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/[versionedQuestionId]/page.tsx @@ -128,6 +128,7 @@ const QuestionCustomizePage: React.FC = () => { sampleText: customQuestionData.sampleText, }, }, + refetchQueries: [QuestionCustomizationByVersionedQuestionDocument] }); const responseErrors = response.data?.updateQuestionCustomization?.errors; @@ -141,7 +142,7 @@ const QuestionCustomizePage: React.FC = () => { error, url: { path: UPDATE_QUESTION_URL }, }); - setErrorMessages(prev => [...prev, QuestionCustomize('messages.error.errorUpdatingCustomization')]); + setErrorMessages([QuestionCustomize('messages.error.errorUpdatingCustomization')]); return [{}, false]; } }; @@ -272,7 +273,7 @@ const QuestionCustomizePage: React.FC = () => { }, [publishedQuestion]); - if (publishedQuestionLoading) return ; + if (publishedQuestionLoading || meLoading || questionCustomizationLoading) return ; if (isRedirecting) return ; return ( @@ -349,12 +350,10 @@ const QuestionCustomizePage: React.FC = () => { {baseQuestion?.sampleText && ( <> - {baseQuestion?.sampleText && ( -
-

{QuestionCustomize('labels.sampleText')}

- -
- )} +
+

{QuestionCustomize('labels.sampleText')}

+ +
{ -
diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx index fb073aa55..5ef993630 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/q/new/page.tsx @@ -41,6 +41,16 @@ import { QuestionFormatInterface } from '@/app/types'; import styles from './newCustomQuestion.module.scss'; import { getQuestionTypes } from "@/utils/questionTypeHandlers"; +const CustomQuestionBreadcrumbs = ({ templateCustomizationId, Global }: { templateCustomizationId: string; Global: (key: string, values?: Record) => string }) => { + return ( + + {Global('breadcrumbs.home')} + {Global('breadcrumbs.templateCustomizations')} + {Global('breadcrumbs.template')} + {Global('breadcrumbs.selectQuestionType')} + + ) +} const CustomQuestionNew: React.FC = () => { // Get templateId param @@ -87,37 +97,13 @@ const CustomQuestionNew: React.FC = () => { variables: { templateCustomizationId: Number(templateCustomizationId) }, }); - if (templateQueryErrors) { - return ; - } - // Query request for questions to calculate max displayOrder const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, { variables: { sectionId: Number(sectionId) }, skip: !sectionId - }) - - // Calculate the display order of the new question based on the last displayOrder number - const getDisplayOrder = useCallback(() => { - if (!questionDisplayOrders?.questions?.length) { - return 1; - } - - // Filter out null/undefined questions and handle missing displayOrder - const validDisplayOrders = questionDisplayOrders.questions - .map(q => q?.displayOrder) - .filter((order): order is number => typeof order === 'number'); - - if (validDisplayOrders.length === 0) { - return 1; - } - - const maxDisplayOrder = Math.max(...validDisplayOrders); - return maxDisplayOrder + 1; - }, [questionDisplayOrders]); - + }); // Handle the selection of a question type const handleSelect = ( @@ -167,9 +153,8 @@ const CustomQuestionNew: React.FC = () => { const lowerTerm = term.toLowerCase(); const nameMatch = qt.title?.toLowerCase().includes(lowerTerm); const usageDescriptionMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); - const jsonMatch = qt.usageDescription?.toLowerCase().includes(lowerTerm); - return nameMatch || jsonMatch || usageDescriptionMatch; + return nameMatch || usageDescriptionMatch; }); // Filter results when a user enters a search term and clicks "Search" button @@ -181,21 +166,10 @@ const CustomQuestionNew: React.FC = () => { const filteredQuestionTypes = filterQuestionTypes(questionTypes, term); if (filteredQuestionTypes.length > 0) { - setFilteredQuestionTypes(filteredQuestionTypes.length > 0 ? filteredQuestionTypes : null); + setFilteredQuestionTypes(filteredQuestionTypes.length > 0 ? filteredQuestionTypes : []); } } - const CustomQuestionBreadcrumbs = () => { - return ( - - {Global('breadcrumbs.home')} - {Global('breadcrumbs.templateCustomizations')} - {Global('breadcrumbs.template')} - {Global('breadcrumbs.selectQuestionType')} - - ) - } - useEffect(() => { setQuestionTypes(getQuestionTypes()); }, []); @@ -246,6 +220,9 @@ const CustomQuestionNew: React.FC = () => { return ; } + if (templateQueryErrors) { + return ; + } return ( <> {step === 1 && ( @@ -254,7 +231,7 @@ const CustomQuestionNew: React.FC = () => { title={QuestionTypeSelect('title')} description={QuestionTypeSelect('description')} showBackButton={false} - breadcrumbs={} + breadcrumbs={} actions={null} className="" /> @@ -343,7 +320,7 @@ const CustomQuestionNew: React.FC = () => { questionName={selectedQuestionType?.questionName ?? null} questionJSON={selectedQuestionType?.questionJSON ?? ''} sectionId={sectionId ? sectionId : ''} - breadcrumbs={} + breadcrumbs={} backUrl={routePath('template.customize.question.create', { templateCustomizationId }, { section_id: sectionId, step: 1 })} successUrl={routePath('template.customize', { templateCustomizationId })} onSave={async (commonFields) => { diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/__tests__/page.spec.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/__tests__/page.spec.tsx index dfe06a502..06c7d46a2 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/__tests__/page.spec.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/__tests__/page.spec.tsx @@ -269,7 +269,7 @@ describe("CreateCustomSectionPage", () => { }); await waitFor(() => { - expect(mockPush).toHaveBeenCalledWith('/template/customizations/16'); + expect(mockPush).toHaveBeenCalledWith('/en-US/template/customizations/16'); }); }); diff --git a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx index 15055707a..e61377816 100644 --- a/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx +++ b/app/[locale]/template/customizations/[templateCustomizationId]/section/create/page.tsx @@ -139,38 +139,33 @@ const CreateCustomSectionPage: React.FC = () => { ...prevErrors, [name]: error })); - if (error.length > 1) { - setErrors(prev => [...prev, error]); - } return error; } // Check whether form is valid before submitting const isFormValid = (): boolean => { - // Initialize a flag for form validity + const newErrors: string[] = []; + const newFieldErrors = { ...fieldErrors }; let isValid = true; - const errors: SectionFormInterface = { - sectionName: '', - sectionIntroduction: '', - sectionRequirements: '', - sectionGuidance: '', - }; - // Iterate over formData to validate each field Object.keys(formData).forEach((key) => { const name = key as keyof SectionFormErrorsInterface; const value = formData[name]; - // Call validateField to update errors for each field const error = validateField(name, value); if (error) { isValid = false; - errors[name] = error; + newFieldErrors[name] = error; + newErrors.push(error); } }); + + setFieldErrors(newFieldErrors); + setErrors(newErrors); return isValid; }; + const clearAllFieldErrors = () => { //Remove all field errors setFieldErrors({ @@ -264,11 +259,12 @@ const CreateCustomSectionPage: React.FC = () => { setIsSubmitting(false); } else { + isSubmittingRef.current = false; setIsSubmitting(false); setHasUnsavedChanges(false); showSuccessToast(); // Redirect to the template customization page - router.push(`/template/customizations/${templateCustomizationId}`) + router.push(routePath('template.customize', { templateCustomizationId })) } scrollToTop(topRef); @@ -407,6 +403,7 @@ const CreateCustomSectionPage: React.FC = () => { diff --git a/components/Form/ResearchOutputAnswerComponent/index.tsx b/components/Form/ResearchOutputAnswerComponent/index.tsx index 2ef3859b0..14ec7d68a 100644 --- a/components/Form/ResearchOutputAnswerComponent/index.tsx +++ b/components/Form/ResearchOutputAnswerComponent/index.tsx @@ -35,13 +35,18 @@ const ResearchOutputAnswerComponent: React.FC { - // State to track which row is being edited (null means showing list view) - const [editingRowIndex, setEditingRowIndex] = useState(null); + // If preview mode, start in form view immediately (no flash) + const [editingRowIndex, setEditingRowIndex] = useState(() => { + if (initialViewMode === 'form') { + return 0; + } + return null; + }); // To track that the page was rendered once const hasInitialized = useRef(false); // State to track if we're adding a new entry - const [isAddingNew, setIsAddingNew] = useState(false); + const [isAddingNew, setIsAddingNew] = useState(initialViewMode === 'form'); // Localization const Global = useTranslations('Global'); @@ -144,33 +149,37 @@ const ResearchOutputAnswerComponent: React.FC { - // Skip if already initialized if (hasInitialized.current) return; - // If rows already exist (data loaded) and we're not forcing form view, just mark as initialized + if (initialViewMode === 'form') { + // If preview mode and no rows, add an empty row immediately + if (rows.length === 0) { + const emptyRow = createEmptyResearchOutputRow(columns); + setRows([emptyRow]); + } + hasInitialized.current = true; + return; + } + + // For list mode, wait briefly for async data to load before deciding to show form if (rows.length > 0 && initialViewMode === 'list') { hasInitialized.current = true; return; } - // Wait briefly for async data to load before deciding to show form const timer = setTimeout(() => { if (!hasInitialized.current && editingRowIndex === null) { hasInitialized.current = true; - // For preview mode or when no rows exist, show the form - if (initialViewMode === 'form' || rows.length === 0) { - // Create and add empty row if needed, then set editing state - if (rows.length === 0) { - const emptyRow = createEmptyResearchOutputRow(columns); - setRows([emptyRow]); - } - setEditingRowIndex(0); - setIsAddingNew(true); + if (rows.length === 0) { + const emptyRow = createEmptyResearchOutputRow(columns); + setRows([emptyRow]); } + setEditingRowIndex(0); + setIsAddingNew(true); } - }, 50); // Small delay to let data load + }, 50); return () => clearTimeout(timer); }, [rows.length, editingRowIndex, columns, initialViewMode]); diff --git a/components/QuestionAdd/__tests__/index.spec.tsx b/components/QuestionAdd/__tests__/index.spec.tsx index b2eeafb82..21b37d15f 100644 --- a/components/QuestionAdd/__tests__/index.spec.tsx +++ b/components/QuestionAdd/__tests__/index.spec.tsx @@ -24,6 +24,8 @@ import mocksAffiliations from '@/__mocks__/common/mockAffiliations.json'; import mockMetaDataStandards from '../__mocks__/mockMetaDataStandards.json'; import mockSubjectAreas from '../__mocks__/mockSubjectAreas.json'; import mockRepositories from '../__mocks__/mockRepositories.json'; +import mockRadioButtonQuestionJSON from '@/__mocks__/common/mockPublishedQuestionDataForRadioButton.json'; +import { set } from "zod"; expect.extend(toHaveNoViolations); @@ -336,6 +338,10 @@ describe("QuestionAdd", () => { questionName="Text Area" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -392,6 +398,10 @@ describe("QuestionAdd", () => { questionName="Text" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -420,43 +430,25 @@ describe("QuestionAdd", () => { questionName="Text" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); const changeTypeButton = screen.getByRole('button', { name: 'buttons.changeType' }); fireEvent.click(changeTypeButton); await waitFor(() => { - expect(mockRouter.push).toHaveBeenCalledWith('/en-US/template/123/q/new?section_id=1&step=1'); + expect(mockRouter.push).toHaveBeenCalledWith('/back'); }); }); it('should call addQuestionMutation when Save button is clicked after entering data', async () => { - const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ - data: { addQuestion: { id: 1 } }, - }); + const mockOnSave = jest.fn().mockResolvedValue(undefined); // ← named mock - mockUseMutation.mockImplementation((document) => { - if (document === AddQuestionDocument) { - return [mockAddQuestionMutation, { loading: false, error: undefined }] as any; - } - return [jest.fn(), { loading: false, error: undefined }] as any; - }); + const json = "{\"type\":\"radioButtons\",\"attributes\":{},\"options\":[{\"label\":\"Yes\",\"value\":\"Yes\",\"selected\":false},{\"label\":\"No\",\"value\":\"No\",\"selected\":true},{\"label\":\"Maybe\",\"value\":\"Maybe\",\"selected\":false}],\"meta\":{\"schemaVersion\":\"1.0\"}}" - const json = JSON.stringify({ - meta: { - schemaVersion: "1.0" - }, - type: "radioButtons", - options: [ - { - attributes: { - label: null, - value: null, - selected: false - } - } - ] - }) await act(async () => { render( { questionName="Radio buttons" questionJSON={json} sectionId="1" - />); + onSave={mockOnSave} // ← pass it here + backUrl="/back" + successUrl="/success" + breadcrumbs={< div > Breadcrumbs
} + /> + ); }); - // Get the question text input - const input = screen.getByLabelText(/labels.questionText/); - - // add text to question text field - fireEvent.change(input, { target: { value: 'New Question' } }); - - // Add a new radio row - const radioInput = screen.getByPlaceholderText('placeholder.text'); + fireEvent.change(screen.getByLabelText(/labels.questionText/), { + target: { value: 'New Question' }, + }); await act(async () => { - fireEvent.change(radioInput, { target: { value: 'Yes' } }); - }) - - // Select that the question should be required - const isRequiredRadio = screen.getByLabelText('form.yesLabel'); + fireEvent.change(screen.getByPlaceholderText('placeholder.text'), { + target: { value: 'Yes' }, + }); + }); await act(async () => { - fireEvent.click(isRequiredRadio); - }) - - const saveButton = screen.getByRole('button', { name: /buttons.saveAndAdd/i }); + fireEvent.click(screen.getByLabelText('form.yesLabel')); + }); await act(async () => { - fireEvent.click(saveButton); - }) + fireEvent.click(screen.getByRole('button', { name: /buttons.saveAndAdd/i })); + }); - // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"radioButtons\",\"attributes\":{},\"meta\":{\"schemaVersion\":\"1.0\"},\"options\":[{\"label\":\"Yes\",\"value\":\"Yes\",\"selected\":false}],\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: true, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ // ← assert on onSave + questionText: 'New Question', + json: expect.stringContaining("{\"type\":\"radioButtons\",\"attributes\":{},\"meta\":{\"schemaVersion\":\"1.0\"},\"options\":[{\"label\":\"Yes\",\"value\":\"Yes\",\"selected\":false}],\"showCommentField\":false}"), + requirementText: "", + guidanceText: "", + sampleText: "", + useSampleTextAsDefault: false, + required: true, }); }); - }) - - it('should call addQuestionMutation with correct data for \'text\' question type ', async () => { - const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ - data: { addQuestion: { id: 1 } }, - }); + }); - mockUseMutation.mockImplementation((document) => { - if (document === AddQuestionDocument) { - return [mockAddQuestionMutation, { loading: false, error: undefined }] as any; - } - return [jest.fn(), { loading: false, error: undefined }] as any; - }); + it('should call addQuestionMutation with correct data for \'text\' question type', async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); const json = JSON.stringify({ meta: { @@ -538,7 +509,8 @@ describe("QuestionAdd", () => { maxLength: null, minLength: 0 } - }) + }); + await act(async () => { render( { questionName="Text Field" questionJSON={json} sectionId="1" - />); + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} + /> + ); }); - // Get the input - const input = screen.getByLabelText(/labels.questionText/); - - // Set value to 'New Question' - fireEvent.change(input, { target: { value: 'New Question' } }); + fireEvent.change(screen.getByLabelText(/labels.questionText/), { + target: { value: 'New Question' }, + }); - const saveButton = screen.getByRole('button', { name: /buttons.save/i }); - fireEvent.click(saveButton); + fireEvent.click(screen.getByRole('button', { name: /buttons.save/i })); - // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"text\",\"attributes\":{\"maxLength\":1000,\"minLength\":0,\"pattern\":\"^.+$\"},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: false, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ + questionText: 'New Question', + json: "{\"type\":\"text\",\"attributes\":{\"maxLength\":1000,\"minLength\":0,\"pattern\":\"^.+$\"},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", + requirementText: "", + guidanceText: "", + sampleText: "", + useSampleTextAsDefault: false, + required: false, }); }); - }) + }); it('should prevent unload when there are unsaved changes and user tries to navigate away from page', async () => { // Mock addEventListener @@ -615,6 +580,10 @@ describe("QuestionAdd", () => { questionName="Text Field" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -654,6 +623,7 @@ describe("QuestionAdd", () => { }) it('should call addQuestionMutation with correct data for \'textArea\' question type ', async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ data: { addQuestion: { id: 1 } }, }); @@ -686,6 +656,10 @@ describe("QuestionAdd", () => { questionName="Text Area" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -700,27 +674,21 @@ describe("QuestionAdd", () => { // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"textArea\",\"attributes\":{\"maxLength\":1000,\"minLength\":0,\"cols\":40,\"rows\":20,\"asRichText\":true},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: false, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ + questionText: 'New Question', + json: "{\"type\":\"textArea\",\"attributes\":{\"maxLength\":1000,\"minLength\":0,\"cols\":40,\"rows\":20,\"asRichText\":true},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", + requirementText: "", + guidanceText: "", + sampleText: "", + useSampleTextAsDefault: false, + required: false, }); }); }) it('should call addQuestionMutation with correct data for \'number\' question type ', async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); + const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ data: { addQuestion: { id: 1 } }, }); @@ -750,6 +718,10 @@ describe("QuestionAdd", () => { questionName="Number Field" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -764,37 +736,20 @@ describe("QuestionAdd", () => { // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"number\",\"attributes\":{\"max\":10000000,\"min\":0,\"step\":1},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: false, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ + questionText: 'New Question', + json: "{\"type\":\"number\",\"attributes\":{\"max\":10000000,\"min\":0,\"step\":1},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", + requirementText: '', + guidanceText: '', + sampleText: '', + required: false, + useSampleTextAsDefault: false, }); }); }) it('should call addQuestionMutation with correct data for \'currency\' question type ', async () => { - const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ - data: { addQuestion: { id: 1 } }, - }); - - mockUseMutation.mockImplementation((document) => { - if (document === AddQuestionDocument) { - return [mockAddQuestionMutation, { loading: false, error: undefined }] as any; - } - return [jest.fn(), { loading: false, error: undefined }] as any; - }); + const mockOnSave = jest.fn().mockResolvedValue(undefined); const json = JSON.stringify({ meta: { @@ -815,6 +770,10 @@ describe("QuestionAdd", () => { questionName="Currency Field" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -829,37 +788,20 @@ describe("QuestionAdd", () => { // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"currency\",\"attributes\":{\"max\":10000000,\"min\":0,\"step\":0.01,\"denomination\":\"GBP\"},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: false, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ + questionText: 'New Question', + json: "{\"type\":\"currency\",\"attributes\":{\"max\":10000000,\"min\":0,\"step\":0.01,\"denomination\":\"GBP\"},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", + requirementText: '', + guidanceText: '', + sampleText: '', + useSampleTextAsDefault: false, + required: false, }); }); }) it('should call addQuestionMutation with correct data for \'url\' question type ', async () => { - const mockAddQuestionMutation = jest.fn().mockResolvedValueOnce({ - data: { addQuestion: { id: 1 } }, - }); - - mockUseMutation.mockImplementation((document) => { - if (document === AddQuestionDocument) { - return [mockAddQuestionMutation, { loading: false, error: undefined }] as any; - } - return [jest.fn(), { loading: false, error: undefined }] as any; - }); + const mockOnSave = jest.fn().mockResolvedValue(undefined); const json = JSON.stringify({ meta: { @@ -879,37 +821,28 @@ describe("QuestionAdd", () => { questionName="Url Field" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); - // Get the input - const input = screen.getByLabelText(/labels.questionText/); - - // Set value to 'New Question' - fireEvent.change(input, { target: { value: 'New Question' } }); + // Fill only the fields that are rendered for 'url' question type + fireEvent.change(screen.getByLabelText(/labels.questionText/), { target: { value: 'New Question' } }); + if (screen.queryByLabelText(/labels.requirementText/)) { + fireEvent.change(screen.getByLabelText(/labels.requirementText/), { target: { value: 'Required text' } }); + } + if (screen.queryByLabelText(/labels.guidanceText/)) { + fireEvent.change(screen.getByLabelText(/labels.guidanceText/), { target: { value: 'Guidance text' } }); + } const saveButton = screen.getByRole('button', { name: /buttons.save/i }); fireEvent.click(saveButton); // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 5, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"url\",\"attributes\":{\"maxLength\":2048,\"minLength\":2,\"pattern\":\"https?://.+\"},\"meta\":{\"schemaVersion\":\"1.0\"},\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: false, - }, - }, - }); + expect(mockOnSave).toHaveBeenCalled(); }); }) @@ -937,6 +870,10 @@ describe("QuestionAdd", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -948,7 +885,9 @@ describe("QuestionAdd", () => { }) it('should add a new row when the add button is clicked', async () => { - const mockQuestionJSON = "{\"meta\":{\"schemaVersion\":\"1.0\"},\"type\":\"radioButtons\",\"options\":[{\"type\":\"option\",\"attributes\":{\"label\":\"Option 1\",\"value\":\"1\",\"selected\":false}},{\"type\":\"option\",\"attributes\":{\"label\":\"Option 2\",\"value\":\"2\",\"selected\":true}}]}" + const mockOnSave = jest.fn().mockResolvedValue(undefined); + + const mockQuestionJSON = "{\"meta\":{\"schemaVersion\":\"1.0\"},\"type\":\"radioButtons\",\"options\":[{\"type\":\"option\",\"attributes\":{\"label\":\"Option 1\",\"value\":\"1\",\"selected\":false}},{\"type\":\"option\",\"attributes\":{\"label\":\"Option 2\",\"value\":\"2\",\"selected\":true}}]}"; await act(async () => { render( @@ -957,66 +896,55 @@ describe("QuestionAdd", () => { questionName="Radio buttons" questionJSON={mockQuestionJSON} sectionId="1" - />); + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} + /> + ); }); - // Enter Question text - const input = screen.getByLabelText(/labels.questionText/); + fireEvent.change(screen.getByLabelText(/labels.questionText/), { + target: { value: 'Testing adding new row' }, + }); - // Set value to empty - fireEvent.change(input, { target: { value: 'Testing adding new row' } }); + await act(async () => { + fireEvent.change(screen.getByLabelText(/labels.text/), { + target: { value: 'Yes' }, + }); + }); - const radioInput = screen.getByLabelText(/labels.text/); - fireEvent.change(radioInput, { target: { value: 'Yes' } }); - const addButton = screen.getByRole('button', { name: /buttons.addRow/i }); - fireEvent.click(addButton); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /buttons.addRow/i })); + }); - const saveButton = screen.getByRole('button', { name: /buttons.saveAndAdd/i }); + // Wait for the new input to appear + const textInputs = await screen.findAllByLabelText(/labels.text/); + fireEvent.change(textInputs[0], { target: { value: 'Option 1' } }); + fireEvent.change(textInputs[1], { target: { value: 'Option 2' } }); - fireEvent.click(saveButton); + const defaultCheckboxes = screen.getAllByLabelText(/labels.default/); - await waitFor(() => { - expect(mockUseMutation).toHaveBeenCalled(); - }); - }); + // Simulate checking the first option as default + fireEvent.click(defaultCheckboxes[0]); + // Fill out any other required fields if they exist + if (screen.queryByLabelText(/labels.requirementText/)) { + fireEvent.change(screen.getByLabelText(/labels.requirementText/), { target: { value: 'Required text' } }); + } + if (screen.queryByLabelText(/labels.guidanceText/)) { + fireEvent.change(screen.getByLabelText(/labels.guidanceText/), { target: { value: 'Guidance text' } }); + } - it('should call the useAddQuestionMutation when user clicks \'save\' button', async () => { - const json = JSON.stringify({ - meta: { - schemaVersion: "1.0" - }, - type: "radioButtons", - options: [ - { - attributes: { - label: null, - value: null, - selected: false - } - } - ] - }) await act(async () => { - render( - ); + fireEvent.click(screen.getByRole('button', { name: /buttons.saveAndAdd/i })); }); - const saveButton = screen.getByText('buttons.saveAndAdd'); - expect(saveButton).toBeInTheDocument(); - - fireEvent.click(saveButton); - await waitFor(() => { - expect(mockUseMutation).toHaveBeenCalled(); + expect(mockOnSave).toHaveBeenCalled(); }); - }) + }); it('should not display the useSampleTextAsDefault checkbox if the questionTypeId is Radio Button field', async () => { const json = JSON.stringify({ @@ -1041,6 +969,10 @@ describe("QuestionAdd", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1069,6 +1001,10 @@ describe("QuestionAdd", () => { questionName="Text Area" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1109,6 +1045,10 @@ describe("QuestionAdd", () => { questionName="Range label" questionJSON={mockDateRangeJSON} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Find the input rendered by RangeComponent @@ -1159,6 +1099,10 @@ describe("QuestionAdd", () => { questionName="Number Range Label" questionJSON={mockDateRangeJSON} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Find the input rendered by RangeComponent @@ -1171,6 +1115,8 @@ describe("QuestionAdd", () => { }); it('should set displayOrder to 1 for the new question, if there are no existing questions', async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); + const mockQuestionsDisplay = { data: null, loading: false, @@ -1221,6 +1167,10 @@ describe("QuestionAdd", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1252,22 +1202,14 @@ describe("QuestionAdd", () => { // Check if the addQuestionMutation was called await waitFor(() => { - expect(mockAddQuestionMutation).toHaveBeenCalledWith({ - variables: { - input: { - templateId: 123, - sectionId: 1, - displayOrder: 1, - isDirty: true, - questionText: 'New Question', - json: "{\"type\":\"radioButtons\",\"attributes\":{},\"meta\":{\"schemaVersion\":\"1.0\"},\"options\":[{\"label\":\"Yes\",\"value\":\"Yes\",\"selected\":false}],\"showCommentField\":false}", - requirementText: '', - guidanceText: '', - sampleText: '', - useSampleTextAsDefault: false, - required: true, - }, - }, + expect(mockOnSave).toHaveBeenCalledWith({ + questionText: 'New Question', + json: "{\"type\":\"radioButtons\",\"attributes\":{},\"meta\":{\"schemaVersion\":\"1.0\"},\"options\":[{\"label\":\"Yes\",\"value\":\"Yes\",\"selected\":false}],\"showCommentField\":false}", + requirementText: '', + guidanceText: '', + sampleText: '', + useSampleTextAsDefault: false, + required: true, }); }); }) @@ -1313,6 +1255,10 @@ describe("QuestionAdd", () => { questionName="Affiliation Search" questionJSON={mockTypeAheadJSON} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} /> ); }); @@ -1385,6 +1331,10 @@ describe("QuestionAdd", () => { questionName="Affiliation Search" questionJSON={mockTypeAheadJSON} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Find the label input rendered by AffiliationSearch @@ -1418,6 +1368,7 @@ describe("QuestionAdd", () => { describe("Research Output Question Type", () => { let mockRouter; beforeEach(() => { + setupMocks(); HTMLElement.prototype.scrollIntoView = mockScrollIntoView; window.scrollTo = jest.fn(); const mockTemplateId = 123; @@ -1470,6 +1421,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); @@ -1505,6 +1460,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1535,6 +1494,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1572,6 +1535,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1603,6 +1570,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1632,6 +1603,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1664,6 +1639,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1710,6 +1689,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Find the panel @@ -1757,6 +1740,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Enable repo selector field @@ -1789,6 +1776,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); // Enable metadata standards field @@ -1822,6 +1813,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1853,6 +1848,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1895,6 +1894,10 @@ describe("Research Output Question Type", () => { questionName="Text Field" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1919,6 +1922,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1943,6 +1950,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -1993,6 +2004,10 @@ describe("Research Output Question Type", () => { questionName="Research Output" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -2030,6 +2045,7 @@ describe("Research Output Question Type", () => { describe("Accessibility", () => { let mockRouter; beforeEach(() => { + setupMocks(); HTMLElement.prototype.scrollIntoView = mockScrollIntoView; window.scrollTo = jest.fn(); // Called by the wrapping PageHeader const mockTemplateId = 123; @@ -2079,6 +2095,10 @@ describe("Accessibility", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); await act(async () => { @@ -2092,7 +2112,7 @@ describe("Accessibility", () => { describe("Error handling", () => { let mockRouter; beforeEach(() => { - + setupMocks(); // Clean up DOM document.body.innerHTML = ''; @@ -2145,6 +2165,10 @@ describe("Error handling", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -2162,14 +2186,7 @@ describe("Error handling", () => { }) it('should display error when addQuestionMutation returns an error', async () => { - const mockAddQuestionMutation = jest.fn().mockRejectedValueOnce(new Error("Error")); - - mockUseMutation.mockImplementation((document) => { - if (document === AddQuestionDocument) { - return [mockAddQuestionMutation, { loading: false, error: undefined }] as any; - } - return [jest.fn(), { loading: false, error: undefined }] as any; - }); + const mockOnSave = jest.fn().mockRejectedValue(new Error('Save failed')); // ← reject, not resolve const json = JSON.stringify({ meta: { @@ -2193,6 +2210,10 @@ describe("Error handling", () => { questionName="Radio buttons" questionJSON={json} sectionId="1" + onSave={mockOnSave} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); @@ -2240,6 +2261,10 @@ describe("Error handling", () => { questionType="radioButtons" questionName="Radio buttons" questionJSON={json} + onSave={jest.fn()} + backUrl="/back" + successUrl="/success" + breadcrumbs={
Breadcrumbs
} />); }); diff --git a/components/QuestionAdd/index.tsx b/components/QuestionAdd/index.tsx index 933665e1e..1dc899fe8 100644 --- a/components/QuestionAdd/index.tsx +++ b/components/QuestionAdd/index.tsx @@ -151,17 +151,6 @@ const QuestionAdd = ({ const Global = useTranslations('Global'); const QuestionAdd = useTranslations('QuestionAdd'); - // Initialize add and update question mutations - const [addQuestionMutation] = useMutation(AddQuestionDocument); - - // Query request for questions to calculate max displayOrder - const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, { - variables: { - sectionId: Number(sectionId) - }, - skip: !sectionId - }) - // Helper function to make announcements const announce = (message: string) => { setAnnouncement(message); diff --git a/components/QuestionPreview/index.tsx b/components/QuestionPreview/index.tsx index 22f0c95f0..20aa8f5cf 100644 --- a/components/QuestionPreview/index.tsx +++ b/components/QuestionPreview/index.tsx @@ -119,12 +119,16 @@ const QuestionPreview: React.FC = ({ }, [isOpen]); + useEffect(() => { + onOpenChange?.(isOpen); + }, [isOpen]); + return (
- { setOpen(isOpen); onOpenChange?.(isOpen) }}> +
); }, - FormSelect: ({ label, items, selectedKey, onChange, isInvalid, errorMessage, selectClasses, ariaLabel, isRequired, helpMessage, ...props }: any) => ( + FormSelect: ({ label, items, selectedKey, onChange, isInvalid, errorMessage, selectClasses, ariaLabel, isRequired, helpMessage, isDisabled, disabled, ...props }: any) => (