diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx index 91ff46b5dd..efe04fe634 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx @@ -75,6 +75,10 @@ export const RankingQuestionForm = ({ const addOption = () => { const choices = !question.choices ? [] : question.choices; + if (choices.length >= 25) { + return; + } + const newChoice = { id: createId(), label: createI18nString("", surveyLanguageCodes), @@ -224,6 +228,7 @@ export const RankingQuestionForm = ({ variant="secondary" EndIcon={PlusIcon} type="button" + disabled={question.choices?.length >= 25} onClick={() => addOption()}> {t("environments.surveys.edit.add_option")} diff --git a/packages/surveys/src/components/questions/RankingQuestion.tsx b/packages/surveys/src/components/questions/RankingQuestion.tsx index ba840ed95c..76e87fbf67 100644 --- a/packages/surveys/src/components/questions/RankingQuestion.tsx +++ b/packages/surveys/src/components/questions/RankingQuestion.tsx @@ -57,52 +57,55 @@ export const RankingQuestion = ({ const [parent] = useAutoAnimate(); const [error, setError] = useState(null); - const isMediaAvailable = question.imageUrl || question.videoUrl; - useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId); + useTtc(question.id, ttc, setTtc, startTime, setStartTime, isCurrent); + + const [localValue, setLocalValue] = useState(value || []); const sortedItems = useMemo(() => { - return value + return localValue .map((id) => question.choices.find((c) => c.id === id)) .filter((item): item is TSurveyQuestionChoice => item !== undefined); - }, [value, question.choices]); + }, [localValue, question.choices]); const unsortedItems = useMemo(() => { if (question.shuffleOption === "all" && sortedItems.length === 0) { return shuffledChoicesIds.map((id) => question.choices.find((c) => c.id === id)); } else { - return question.choices.filter((c) => !value.includes(c.id)); + return question.choices.filter((c) => !localValue.includes(c.id)); } - }, [question.choices, value, question.shuffleOption]); + }, [question.choices, question.shuffleOption, localValue, sortedItems, shuffledChoicesIds]); const handleItemClick = useCallback( (item: TSurveyQuestionChoice) => { - const isAlreadySorted = sortedItems.some((sortedItem) => sortedItem.id === item.id); - const newSortedItems = isAlreadySorted - ? sortedItems.filter((sortedItem) => sortedItem.id !== item.id) - : [...sortedItems, item]; - onChange({ [question.id]: newSortedItems.map((item) => getLocalizedValue(item.label, languageCode)) }); + const isAlreadySorted = localValue.includes(item.id); + const newLocalValue = isAlreadySorted + ? localValue.filter((id) => id !== item.id) + : [...localValue, item.id]; + + setLocalValue(newLocalValue); + setError(null); }, - [onChange, question.id, sortedItems] + [localValue] ); const handleMove = useCallback( (itemId: string, direction: "up" | "down") => { - const index = sortedItems.findIndex((item) => item.id === itemId); + const index = localValue.findIndex((id) => id === itemId); if (index === -1) return; - const newSortedItems = [...sortedItems]; - const [movedItem] = newSortedItems.splice(index, 1); + const newLocalValue = [...localValue]; + const [movedItem] = newLocalValue.splice(index, 1); const newIndex = - direction === "up" ? Math.max(0, index - 1) : Math.min(newSortedItems.length, index + 1); + direction === "up" ? Math.max(0, index - 1) : Math.min(newLocalValue.length, index + 1); + newLocalValue.splice(newIndex, 0, movedItem); + setLocalValue(newLocalValue); - newSortedItems.splice(newIndex, 0, movedItem); - onChange({ [question.id]: newSortedItems.map((item) => getLocalizedValue(item.label, languageCode)) }); setError(null); }, - [sortedItems, onChange, question.id] + [localValue] ); const handleSubmit = (e: Event) => { @@ -119,12 +122,24 @@ export const RankingQuestion = ({ const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime); setTtc(updatedTtcObj); + onChange({ + [question.id]: sortedItems.map((item) => getLocalizedValue(item.label, languageCode)), + }); onSubmit( { [question.id]: sortedItems.map((item) => getLocalizedValue(item.label, languageCode)) }, updatedTtcObj ); }; + const handleBack = () => { + const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime); + setTtc(updatedTtcObj); + onChange({ + [question.id]: sortedItems.map((item) => getLocalizedValue(item.label, languageCode)), + }); + onBack(); + }; + return (
@@ -144,7 +159,7 @@ export const RankingQuestion = ({ Ranking Items
{[...sortedItems, ...unsortedItems].map((item, idx) => { - if (!item) return; + if (!item) return null; const isSorted = sortedItems.includes(item); const isFirst = isSorted && idx === 0; const isLast = isSorted && idx === sortedItems.length - 1; @@ -253,11 +268,7 @@ export const RankingQuestion = ({ { - const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime); - setTtc(updatedTtcObj); - onBack(); - }} + onClick={handleBack} /> )}
diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts index 0de7f23995..bcd206f44c 100644 --- a/packages/types/surveys/types.ts +++ b/packages/types/surveys/types.ts @@ -617,7 +617,8 @@ export const ZSurveyRankingQuestion = ZSurveyQuestionBase.extend({ type: z.literal(TSurveyQuestionTypeEnum.Ranking), choices: z .array(ZSurveyQuestionChoice) - .min(2, { message: "Ranking Question must have at least two options" }), + .min(2, { message: "Ranking Question must have at least two options" }) + .max(25, { message: "Ranking Question can have at most 25 options" }), otherOptionPlaceholder: ZI18nString.optional(), shuffleOption: ZShuffleOption.optional(), });