Skip to content

Commit

Permalink
[M2-2643] Feature: Matrix: Single Selection per Row Item (#421)
Browse files Browse the repository at this point in the history
* The singleSelectRows type was added into supportableResponseTypes

* ActivityItemDetailsDTO was extended with SingleSeletPerRow

* Create SingleSelectPerRow type for Redux store

* Refactor of Matrix elements to make it reusable

* Create the MatrixSingleSelect component

* Create mapper to answer

* Fix mobile version of matrix grid

---------

Co-authored-by: Viktor Riabkov <[email protected]>
  • Loading branch information
moiskillnadne and Viktor Riabkov authored Apr 4, 2024
1 parent 9f6fae9 commit 7ebf2ae
Show file tree
Hide file tree
Showing 19 changed files with 334 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/abstract/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export const supportableResponseTypes = [
'timeRange',
'audioPlayer',
'multiSelectRows',
'singleSelectRows',
];
23 changes: 22 additions & 1 deletion src/entities/activity/lib/types/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { ConditionalLogic } from '~/shared/api';

export type DefaultAnswer = Array<string>;
export type MatrixMultiSelectAnswer = Array<Array<string | null>>;
export type SingleMultiSelectAnswer = Array<string | null>;

export type Answer = DefaultAnswer | MatrixMultiSelectAnswer;
export type Answer = DefaultAnswer | MatrixMultiSelectAnswer | SingleMultiSelectAnswer;

export type ActivityItemType =
| 'text'
Expand Down Expand Up @@ -313,3 +314,23 @@ export type MatrixSelectRow = {
rowImage: string | null;
tooltip: string | null;
};

export interface SingleSelectionRowsItem extends ActivityItemBase {
responseType: 'singleSelectRows';
config: SingleSelectionRowsItemConfig;
responseValues: SingleSelectionRowsItemResponseValues;
answer: SingleMultiSelectAnswer;
}

export type SingleSelectionRowsItemConfig = ButtonsConfig &
TimerConfig & {
addScores: boolean;
setAlerts: boolean;
addTooltip: boolean;
};

export type SingleSelectionRowsItemResponseValues = {
rows: Array<MatrixSelectRow>;
options: Array<MatrixSelectOption>;
dataMatrix: DataMatrix;
};
13 changes: 12 additions & 1 deletion src/entities/activity/ui/items/ItemPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AudioPlayerItem } from './AudioPlayerItem';
import { CheckboxItem } from './CheckboxItem';
import { DateItem } from './DateItem';
import { MatrixCheckboxItem } from './MatrixCheckboxItem';
import { MatrixCheckboxItem } from './Matrix/MatrixMultiSelectItem';
import { MatrixRadioItem } from './Matrix/MatrixSingleSelectItem';
import { RadioItem } from './RadioItem';
import { SelectorItem } from './SelectorItem';
import { SliderItem } from './SliderItem';
Expand Down Expand Up @@ -103,6 +104,16 @@ export const ItemPicker = ({ item, onValueChange, isDisabled, replaceText }: Ite
/>
);

case 'singleSelectRows':
return (
<MatrixRadioItem
item={item}
values={item.answer}
onValueChange={onValueChange}
replaceText={replaceText}
/>
);

default:
return <></>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export const MatrixCell = ({ children, isRowLabel }: Props) => {

return (
<Box
display="flex"
height={lessThanTarget ? '100px' : '112px'}
padding={lessThanTarget ? '8px' : '14px'}
justifyContent="center"
alignItems="center"
width={isRowLabel ? rowLabelWidth : '100%'}
minWidth={lessThanTarget ? '70px' : '142px'}
maxWidth="400px"
data-testid="matrix-cell"
>
{children}
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Box from '@mui/material/Box';

import { MatrixCell } from '../MatrixCell';

import { CheckboxItem, SelectBaseBox } from '~/shared/ui';
import { useCustomMediaQuery } from '~/shared/utils';

type Props = {
id: string;
isChecked: boolean;
text: string;

onChange: () => void;
};

export const CheckboxButton = ({ id, isChecked, text, onChange }: Props) => {
const { lessThanSM } = useCustomMediaQuery();

return (
<Box flex={1} key={id} data-testid="matrix-checkbox-button-container">
<MatrixCell>
<SelectBaseBox
color={null}
onHandleChange={onChange}
checked={isChecked}
justifyContent="center"
padding={lessThanSM ? '12px 8px' : '16px'}
>
<CheckboxItem
id={id}
name={text}
value={text}
disabled={false}
defaultChecked={isChecked}
/>
</SelectBaseBox>
</MatrixCell>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Box from '@mui/material/Box';

import { CheckboxButton } from './CheckboxButton';
import { MatrixMultiSelectAnswer, MatrixSelectOption, MatrixSelectRow } from '../../../../lib';
import { MatrixHeader } from '../MatrixHeader';
import { MatrixRow } from '../MatrixRow';

type Props = {
options: Array<MatrixSelectOption>;
rows: Array<MatrixSelectRow>;

onChange: (rowIndex: number, optionIndex: number, value: string) => void;
values: MatrixMultiSelectAnswer;
};

export const CheckboxGrid = ({ rows, options, onChange, values }: Props) => {
return (
<Box display="flex" flex={1} flexDirection="column" data-testid="matrix-checkbox-grid">
<MatrixHeader options={options} />

{rows.map((row, rowI) => {
const isEven = rowI % 2 === 0;

return (
<MatrixRow
key={row.id}
isEven={isEven}
item={{ id: row.id, imageUrl: row.rowImage, text: row.rowName, tooltip: row.tooltip }}
>
{options.map((option, optionI) => {
const isChecked = Boolean(values[rowI]?.[optionI]);

return (
<CheckboxButton
key={option.id}
id={option.id}
text={option.text}
isChecked={isChecked}
onChange={() => onChange(rowI, optionI, option.text)}
/>
);
})}
</MatrixRow>
);
})}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMemo } from 'react';

import { StackedItemsGrid } from './StackedItemsGrid';
import { MatrixMultiSelectAnswer, MultiSelectionRowsItem } from '../../../lib';
import { CheckboxGrid } from './CheckboxGrid';
import { MatrixMultiSelectAnswer, MultiSelectionRowsItem } from '../../../../lib';

type Props = {
item: MultiSelectionRowsItem;
Expand Down Expand Up @@ -57,7 +57,7 @@ export const MatrixCheckboxItem = ({ item, values, onValueChange, replaceText }:
};

return (
<StackedItemsGrid
<CheckboxGrid
options={memoizedOptions}
rows={memoizedRows}
onChange={handleValueChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Box from '@mui/material/Box';

import { MatrixCell } from '../MatrixCell';

import { RadioOption, SelectBaseBox } from '~/shared/ui';

type Props = {
id: string;
isChecked: boolean;
text: string;

onChange: () => void;
};

export const RadioButton = ({ id, text, isChecked, onChange }: Props) => {
return (
<Box flex={1} key={id} data-testid="matrix-radio-button-container">
<MatrixCell>
<SelectBaseBox
color={null}
onHandleChange={onChange}
checked={isChecked}
justifyContent="center"
>
<RadioOption
id={id}
name={text}
value={text}
disabled={false}
defaultChecked={isChecked}
/>
</SelectBaseBox>
</MatrixCell>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Box from '@mui/material/Box';

import { RadioButton } from './RadioButton';
import { MatrixSelectOption, MatrixSelectRow, SingleMultiSelectAnswer } from '../../../../lib';
import { MatrixHeader } from '../MatrixHeader';
import { MatrixRow } from '../MatrixRow';

type Props = {
options: Array<MatrixSelectOption>;
rows: Array<MatrixSelectRow>;

onChange: (rowIndex: number, value: string) => void;
values: SingleMultiSelectAnswer;
};

export const RadioGrid = ({ rows, options, onChange, values }: Props) => {
return (
<Box display="flex" flex={1} flexDirection="column" data-testid="matrix-radio-grid">
<MatrixHeader options={options} />

{rows.map((row, rowI) => {
const isEven = rowI % 2 === 0;

return (
<MatrixRow
key={row.id}
isEven={isEven}
item={{ id: row.id, imageUrl: row.rowImage, text: row.rowName, tooltip: row.tooltip }}
>
{options.map((option) => {
const isChecked = option.text === values[rowI];

return (
<RadioButton
key={option.id}
id={option.id}
text={option.text}
isChecked={isChecked}
onChange={() => onChange(rowI, option.text)}
/>
);
})}
</MatrixRow>
);
})}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useMemo } from 'react';

import { RadioGrid } from './RadioGrid';
import { SingleMultiSelectAnswer, SingleSelectionRowsItem } from '../../../../lib';

type Props = {
item: SingleSelectionRowsItem;
values: SingleMultiSelectAnswer;

onValueChange: (value: SingleMultiSelectAnswer) => void;
replaceText: (value: string) => string;
};

export const MatrixRadioItem = ({ item, values, onValueChange, replaceText }: Props) => {
const { options, rows } = item.responseValues;

const memoizedOptions = useMemo(() => {
return options.map((el) => ({
...el,
tooltip: el.tooltip ? replaceText(el.tooltip) : null,
}));
}, [options, replaceText]);

const memoizedRows = useMemo(() => {
return rows.map((el) => ({
...el,
tooltip: el.tooltip ? replaceText(el.tooltip) : null,
}));
}, [replaceText, rows]);

const memoizedValues = useMemo(() => {
const initialAnswer = memoizedRows.map(() => null);

return values.length ? values : initialAnswer;
}, [memoizedRows, values]);

const handleValueChange = (rowIndex: number, value: string) => {
const newValues = memoizedValues.map((row, i) => {
if (i === rowIndex) {
const hasAnswer = row !== null;
const isSameAnswer = row === value;

if (hasAnswer && isSameAnswer) {
return null;
} else {
return value;
}
}

return row;
});

onValueChange(newValues);
};

return (
<RadioGrid
options={memoizedOptions}
rows={memoizedRows}
onChange={handleValueChange}
values={memoizedValues}
/>
);
};
Loading

0 comments on commit 7ebf2ae

Please sign in to comment.