refactor: Extract MatrixLabelSection component to eliminate code duplication

- Create reusable MatrixLabelSection component for both rows and columns
- Consolidate duplicate DndContext/SortableContext patterns
- Reduce code duplication from 22.5% to meet SonarCloud 3% threshold
- Maintain all existing drag and drop functionality
- All tests passing locally (13/13 matrix tests)

Co-Authored-By: Johannes <johannes@formbricks.com>
This commit is contained in:
Devin AI
2025-06-25 08:23:21 +00:00
parent 64ed3e231c
commit 272846a7ad
2 changed files with 130 additions and 86 deletions
@@ -0,0 +1,95 @@
"use client";
import { MatrixLabelChoice } from "@/modules/survey/editor/components/matrix-label-choice";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
interface MatrixLabelSectionProps {
type: "row" | "column";
labels: TI18nString[];
question: TSurveyMatrixQuestion;
questionIdx: number;
updateMatrixLabel: (index: number, type: "row" | "column", data: TI18nString) => void;
handleDeleteLabel: (type: "row" | "column", index: number) => void;
handleKeyDown: (e: React.KeyboardEvent, type: "row" | "column") => void;
handleAddLabel: (type: "row" | "column") => void;
onDragEnd: (event: any) => void;
isInvalid: boolean;
localSurvey: TSurvey;
selectedLanguageCode: string;
setSelectedLanguageCode: (language: string) => void;
locale: TUserLocale;
parent: any;
}
export const MatrixLabelSection = ({
type,
labels,
question,
questionIdx,
updateMatrixLabel,
handleDeleteLabel,
handleKeyDown,
handleAddLabel,
onDragEnd,
isInvalid,
localSurvey,
selectedLanguageCode,
setSelectedLanguageCode,
locale,
parent,
}: MatrixLabelSectionProps) => {
const { t } = useTranslate();
const labelKey = type === "row" ? "rows" : "columns";
const addKey = type === "row" ? "add_row" : "add_column";
return (
<div>
<Label htmlFor={labelKey}>{t(`environments.surveys.edit.${labelKey}`)}</Label>
<div className="mt-2" id={labelKey}>
<DndContext id={`matrix-${labelKey}`} onDragEnd={onDragEnd}>
<SortableContext
items={labels.map((_, idx) => `${type}-${idx}`)}
strategy={verticalListSortingStrategy}>
<div className="flex flex-col gap-2" ref={parent}>
{labels.map((_, index) => (
<MatrixLabelChoice
key={`${type}-${index}`}
labelIdx={index}
type={type}
questionIdx={questionIdx}
updateMatrixLabel={updateMatrixLabel}
handleDeleteLabel={handleDeleteLabel}
handleKeyDown={handleKeyDown}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
question={question}
locale={locale}
/>
))}
</div>
</SortableContext>
</DndContext>
<Button
variant="secondary"
size="sm"
className="mt-2 w-fit"
onClick={(e) => {
e.preventDefault();
handleAddLabel(type);
}}>
<PlusIcon />
{t(`environments.surveys.edit.${addKey}`)}
</Button>
</div>
</div>
);
};
@@ -2,13 +2,10 @@
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { QuestionFormInput } from "@/modules/survey/components/question-form-input";
import { MatrixLabelChoice } from "@/modules/survey/editor/components/matrix-label-choice";
import { MatrixLabelSection } from "@/modules/survey/editor/components/matrix-label-section";
import { findOptionUsedInLogic } from "@/modules/survey/editor/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
@@ -215,89 +212,41 @@ export const MatrixQuestionForm = ({
)}
</div>
<div className="mt-3 grid grid-cols-2 gap-4">
<MatrixLabelSection
type="row"
labels={question.rows}
question={question}
questionIdx={questionIdx}
updateMatrixLabel={updateMatrixLabel}
handleDeleteLabel={handleDeleteLabel}
handleKeyDown={handleKeyDown}
handleAddLabel={handleAddLabel}
onDragEnd={handleRowDragEnd}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
locale={locale}
parent={parent}
/>
<div>
{/* Rows section */}
<Label htmlFor="rows">{t("environments.surveys.edit.rows")}</Label>
<div className="mt-2" id="rows">
<DndContext id="matrix-rows" onDragEnd={handleRowDragEnd}>
<SortableContext
items={question.rows.map((_, idx) => `row-${idx}`)}
strategy={verticalListSortingStrategy}>
<div className="flex flex-col gap-2" ref={parent}>
{question.rows.map((_, index) => (
<MatrixLabelChoice
key={`row-${index}`}
labelIdx={index}
type="row"
questionIdx={questionIdx}
updateMatrixLabel={updateMatrixLabel}
handleDeleteLabel={handleDeleteLabel}
handleKeyDown={handleKeyDown}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
question={question}
locale={locale}
/>
))}
</div>
</SortableContext>
</DndContext>
<Button
variant="secondary"
size="sm"
className="mt-2 w-fit"
onClick={(e) => {
e.preventDefault();
handleAddLabel("row");
}}>
<PlusIcon />
{t("environments.surveys.edit.add_row")}
</Button>
</div>
</div>
<div>
{/* Columns section */}
<Label htmlFor="columns">{t("environments.surveys.edit.columns")}</Label>
<div className="mt-2" id="columns">
<DndContext id="matrix-columns" onDragEnd={handleColumnDragEnd}>
<SortableContext
items={question.columns.map((_, idx) => `column-${idx}`)}
strategy={verticalListSortingStrategy}>
<div className="flex flex-col gap-2" ref={parent}>
{question.columns.map((_, index) => (
<MatrixLabelChoice
key={`column-${index}`}
labelIdx={index}
type="column"
questionIdx={questionIdx}
updateMatrixLabel={updateMatrixLabel}
handleDeleteLabel={handleDeleteLabel}
handleKeyDown={handleKeyDown}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
question={question}
locale={locale}
/>
))}
</div>
</SortableContext>
</DndContext>
<Button
variant="secondary"
size="sm"
className="mt-2 w-fit"
onClick={(e) => {
e.preventDefault();
handleAddLabel("column");
}}>
<PlusIcon />
{t("environments.surveys.edit.add_column")}
</Button>
</div>
<MatrixLabelSection
type="column"
labels={question.columns}
question={question}
questionIdx={questionIdx}
updateMatrixLabel={updateMatrixLabel}
handleDeleteLabel={handleDeleteLabel}
handleKeyDown={handleKeyDown}
handleAddLabel={handleAddLabel}
onDragEnd={handleColumnDragEnd}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
locale={locale}
parent={parent}
/>
<div className="mt-3 flex flex-1 items-center justify-end gap-2">
<ShuffleOptionSelect
shuffleOptionsTypes={shuffleOptionsTypes}