Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[F] Inline questions #71

Merged
merged 5 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions components/content-blocks/Questions/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,7 @@ export const QuestionList = styled.ol`
list-style-position: inside;
padding: var(--list-padding, 0);

> li {
& > * + * {
margin-block-start: var(--PADDING_SMALL, 20px);
}
}

& > li + li {
margin-block-start: var(--PADDING_SMALL, 20px);
}
`;

export const QuestionLabel = styled.div`
display: inline;

ul,
ol {
list-style: inside;

::marker {
margin: 0;
}
}

& > *:first-child {
display: inline;
}

& > * + * {
margin-block-start: var(--PADDING_SMALL, 20px);
}
`;
7 changes: 6 additions & 1 deletion components/educator-schema/saveAnswersAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export default async function saveAnswers(
return "statusError";
}

const answerSet = Object.values(answers);
const answerSet = Object.values(answers).map(({ data, ...values }) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylepratuch I wanted your input here on stringifying the data property before putting it into the query. We talked about the DB storing all values as a string type, since the typing of data is very loose for the different question types (it can be a string, array of strings, generic object, mildly typed object), I opted to stringify all the data rather than trying to adapt the limited GQL types to fit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexgoff I'm not experienced enough with typescript to know at a glance if this is going to do what we discussed, but I trust that you do. :) Generally, I don't see an issue consistently storing the Answer data field as strings of JSON since that's really just supposed to consist of the values provided by users. If you start needing to store additional attributes beyond just the given answer values it might be worth adding some additional fields to the Answer element instead.

return {
data: JSON.stringify(data),
...values,
};
});

const { data, error } = await mutateAPI({
query: Mutation,
Expand Down
43 changes: 40 additions & 3 deletions components/factories/QuestionFactory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { ComponentType, FunctionComponent } from "react";
import { graphql, useFragment, FragmentType } from "@/gql/public-schema";
import SimpleQuestion from "@/components/questions/SimpleQuestion";
import TabularQuestion from "@/components/questions/TabularQuestion";
import InlineQuestion from "@/components/questions/InlineQuestion";

const Fragment = graphql(`
fragment QuestionFactory on questions_default_Entry {
answerType
answerOptions {
options: answerOptions {
... on answerOptions_option_BlockType {
label: optionLabel
value: optionValue
Expand All @@ -24,6 +25,39 @@ const Fragment = graphql(`
}
}
}
multiPartBlocks {
... on multiPartBlocks_select_BlockType {
id
type: typeHandle
options: answerOptions {
... on answerOptions_option_BlockType {
id
label: optionLabel
value: optionValue
}
}
}
... on multiPartBlocks_text_BlockType {
id
type: typeHandle
}
... on multiPartBlocks_multiselect_BlockType {
id
type: typeHandle
options: answerOptions {
... on answerOptions_option_BlockType {
id
label: optionLabel
value: optionValue
}
}
}
... on multiPartBlocks_readonlyText_BlockType {
id
type: typeHandle
text: questionText
}
}
}
`);

Expand All @@ -38,6 +72,7 @@ const QUESTION_MAP: Record<string, ComponentType<any>> = {
tabular: TabularQuestion,
widget: SimpleQuestion,
textarea: SimpleQuestion,
multiPart: InlineQuestion,
};

const QuestionFactory: FunctionComponent<QuestionProps> = ({
Expand All @@ -48,9 +83,10 @@ const QuestionFactory: FunctionComponent<QuestionProps> = ({
answerType,
id,
questionWidgetsBlock = [],
answerOptions,
options,
questionText,
widgetInstructions,
multiPartBlocks = [],
} = useFragment(Fragment, props.data);

if (!id || !answerType) return null;
Expand All @@ -64,9 +100,10 @@ const QuestionFactory: FunctionComponent<QuestionProps> = ({
id={id}
type={answerType}
questionText={questionText || widgetInstructions}
options={answerOptions}
options={options}
widgetConfig={questionWidgetsBlock[0] || {}}
number={number}
parts={multiPartBlocks}
/>
);
};
Expand Down
2 changes: 1 addition & 1 deletion components/factories/ReviewFactory/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentType, FunctionComponent } from "react";
import { QuestionCategory } from "@/components/shapes/questions";
import { QuestionCategory } from "@/types/questions";
import SimpleReview from "@/components/questions/SimpleQuestion/Review";
import InlineReview from "@/components/questions/InlineQuestion/Review";

Expand Down
2 changes: 1 addition & 1 deletion components/questions/InlineQuestion/Review/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { FunctionComponent } from "react";
import { useTranslation } from "react-i18next";
import * as Styled from "./styles";
import { SelectQuestion, TextQuestion } from "@/components/shapes/questions";
import { SelectQuestion, TextQuestion } from "@/types/questions";

const InlineText: FunctionComponent<{
value?: string;
Expand Down
5 changes: 1 addition & 4 deletions components/questions/InlineQuestion/Review/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { ComponentType, FunctionComponent } from "react";
import {
BaseReviewProps,
InlineQuestionType,
} from "@/components/shapes/questions";
import { BaseReviewProps, InlineQuestionType } from "@/types/questions";
import * as Styled from "./styles";

import Readonly from "./Readonly";
Expand Down
2 changes: 1 addition & 1 deletion components/questions/InlineQuestion/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface InlineTextProps extends InlineTextPart {
const InlineText: FunctionComponent<InlineTextProps> = ({
onChangeCallback,
isDisabled,
value,
value = "",
id,
}) => (
<Styled.InlineTextInput
Expand Down
36 changes: 21 additions & 15 deletions components/questions/InlineQuestion/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { ComponentType, FunctionComponent } from "react";
import { ComponentType, FunctionComponent, useContext } from "react";
import { Option } from "@/components/shapes/option";
import {
BaseQuestionProps,
InlineQuestionType,
} from "@/components/shapes/questions";
import { BaseQuestionProps, InlineQuestionType } from "@/types/questions";
import * as Styled from "./styles";
import Readonly from "./Readonly";
import Text from "./Text";
import Select from "./Select";
import Multiselect from "./Multiselect";
import StoredAnswersContext from "@/contexts/StoredAnswersContext";
import { InlineQuestionData } from "@/types/answers";

interface InlineQuestionPart {
id: string;
Expand All @@ -30,7 +29,6 @@ export interface InlineMultiselectPart extends InlineQuestionPart {
}

export interface InlineQuestionProps extends BaseQuestionProps {
value?: string | string[];
parts: Array<
| InlineReadonlyPart
| InlineTextPart
Expand All @@ -40,26 +38,25 @@ export interface InlineQuestionProps extends BaseQuestionProps {
}

const INPUT_MAP: Record<InlineQuestionType, ComponentType<any>> = {
readonly: Readonly,
readonlyText: Readonly,
text: Text,
select: Select,
multiselect: Multiselect,
};

const InlineQuestion: FunctionComponent<InlineQuestionProps> = ({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
id,
number,
isDisabled,
parts = [],
}) => {
const callback = (value: string | string[], id: string) => {
console.info({ value, id });
};
const { answers, onChangeCallback } = useContext(StoredAnswersContext);
const storedAnswer = answers[id] || {};
const { data = {} } = storedAnswer;

return (
<Styled.InlineContainer value={number}>
{parts.map(({ id, type, ...props }) => {
{parts.map(({ id: partId, type, ...props }) => {
const Input = INPUT_MAP[type];

if (!Input) {
Expand All @@ -72,9 +69,18 @@ const InlineQuestion: FunctionComponent<InlineQuestionProps> = ({

return (
<Input
key={id}
onChangeCallback={callback}
{...{ ...props, isDisabled, id }}
key={partId}
id={partId}
onChangeCallback={(value: string | string[]) =>
onChangeCallback &&
onChangeCallback(
{ ...(data as InlineQuestionData), [partId]: value },
id,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updates the data object with a value using the inline question part's ID as the key.

storedAnswer?.id
)
}
value={(data as InlineQuestionData)[partId]}
{...{ ...props, isDisabled }}
/>
);
})}
Expand Down
5 changes: 3 additions & 2 deletions components/questions/InlineQuestion/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import styled from "styled-components";

export const InlineContainer = styled.li`
> * {
margin-inline-end: 1ch;
> * + * {
margin: 0;
margin-inline-start: 0.5ch;
}
`;
2 changes: 1 addition & 1 deletion components/questions/SimpleQuestion/Review/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SelectQuestion,
TextAreaQuestion,
TextQuestion,
} from "@/components/shapes/questions";
} from "@/types/questions";

interface SimpleTextProps {
value?: string;
Expand Down
5 changes: 1 addition & 4 deletions components/questions/SimpleQuestion/Review/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { ComponentType, FunctionComponent } from "react";
import {
BaseReviewProps,
SimpleQuestionType,
} from "@/components/shapes/questions";
import { BaseReviewProps, SimpleQuestionType } from "@/types/questions";
import * as Styled from "./styles";

import Text from "./Text";
Expand Down
11 changes: 4 additions & 7 deletions components/questions/SimpleQuestion/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
"use client";

import { ComponentType, FunctionComponent, useContext } from "react";
import {
BaseQuestionProps,
SimpleQuestionType,
} from "@/components/shapes/questions";
import { BaseQuestionProps, SimpleQuestionType } from "@/types/questions";
import { Option } from "@/components/shapes/option";

import Text from "./Text";
Expand All @@ -13,7 +10,7 @@ import Select from "./Select";
import Multiselect from "./Multiselect";
import Widget from "./Widget";
import StoredAnswersContext from "@/contexts/StoredAnswersContext";
import * as Styled from "@/components/content-blocks/Questions/styles";
import * as Styled from "./styles";

export interface SimpleQuestionProps extends BaseQuestionProps {
type: SimpleQuestionType;
Expand Down Expand Up @@ -53,7 +50,7 @@ const SimpleQuestion: FunctionComponent<SimpleQuestionProps> = ({
}

return (
<li value={number}>
<Styled.SimpleContainer value={number}>
{type === "widget" ? (
<Styled.QuestionLabel
id={labelId}
Expand All @@ -75,7 +72,7 @@ const SimpleQuestion: FunctionComponent<SimpleQuestionProps> = ({
widgetConfig,
}}
/>
</li>
</Styled.SimpleContainer>
);
};

Expand Down
29 changes: 29 additions & 0 deletions components/questions/SimpleQuestion/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";
import styled from "styled-components";

export const SimpleContainer = styled.li`
& > * + * {
margin-block-start: var(--PADDING_SMALL, 20px);
}
`;

export const QuestionLabel = styled.div`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

co-locate these styles with the SimpleQuestion component so they don't pollute the inline questions

display: inline;

ul,
ol {
list-style: inside;

::marker {
margin: 0;
}
}

& > *:first-child {
display: inline;
}

& > * + * {
margin-block-start: var(--PADDING_SMALL, 20px);
}
`;
5 changes: 1 addition & 4 deletions components/questions/TabularQuestion/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { FunctionComponent, ComponentType } from "react";
import Table, { TableHeader, TableRow } from "@/components/layout/Table/Table";
import { Option } from "@/components/shapes/option";
import {
BaseQuestionProps,
TabularQuestionType,
} from "@/components/shapes/questions";
import { BaseQuestionProps, TabularQuestionType } from "@/types/questions";
import Text from "./Text";
import Select from "./Select";

Expand Down
7 changes: 6 additions & 1 deletion components/student-schema/saveAnswersAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export default async function saveAnswers(
return "statusError";
}

const answerSet = Object.values(answers);
const answerSet = Object.values(answers).map(({ data, ...values }) => {
return {
data: JSON.stringify(data),
...values,
};
});

const { data, error } = await mutateAPI({
query: Mutation,
Expand Down
Loading
Loading