Skip to content

Commit

Permalink
Merge pull request #33 from the-deep/feature/toc
Browse files Browse the repository at this point in the history
Add table of contents
  • Loading branch information
AdityaKhatri authored Sep 4, 2023
2 parents c0059fd + a064ffd commit 2d4a184
Show file tree
Hide file tree
Showing 17 changed files with 302 additions and 144 deletions.
2 changes: 1 addition & 1 deletion backend
Submodule backend updated 34 files
+66 −25 apps/common/management/commands/load_dummy_data.py
+3 −2 apps/project/migrations/0001_initial.py
+2 −2 apps/project/migrations/0002_initial.py
+0 −18 apps/project/migrations/0003_project_description.py
+0 −18 apps/project/migrations/0004_alter_projectmembership_role.py
+5 −0 apps/qbank/apps.py
+50 −0 apps/qbank/models.py
+28 −1 apps/questionnaire/admin.py
+19 −1 apps/questionnaire/enums.py
+98 −4 apps/questionnaire/factories.py
+9 −17 apps/questionnaire/filters.py
+46 −49 apps/questionnaire/migrations/0001_initial.py
+118 −0 apps/questionnaire/migrations/0002_initial.py
+0 −98 apps/questionnaire/migrations/0002_question_appearance_question_calculation_and_more.py
+22 −0 apps/questionnaire/migrations/0003_remove_questionleafgroup_label_and_more.py
+35 −0 apps/questionnaire/migrations/0004_rename_order_idx_questionnai_order_435c27_idx_and_more.py
+17 −0 apps/questionnaire/migrations/0005_alter_questionleafgroup_unique_together.py
+383 −9 apps/questionnaire/models.py
+47 −36 apps/questionnaire/mutations.py
+0 −7 apps/questionnaire/orders.py
+3 −11 apps/questionnaire/queries.py
+21 −38 apps/questionnaire/serializers.py
+231 −228 apps/questionnaire/tests/test_mutations.py
+141 −106 apps/questionnaire/tests/test_queries.py
+48 −31 apps/questionnaire/types.py
+42 −8 apps/user/admin.py
+1 −1 apps/user/migrations/0001_initial.py
+5 −2 apps/user/types.py
+5 −0 main/settings.py
+3 −3 main/template_tags.py
+150 −45 schema.graphql
+13 −0 utils/db.py
+96 −0 utils/strawberry/enums.py
+14 −2 utils/strawberry/mutations.py
19 changes: 11 additions & 8 deletions src/components/PillarSelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,27 @@ const PILLARS = gql`
) {
private {
projectScope(pk: $projectId) {
groups (filters: {questionnaire: {pk: $questionnaireId}}){
items {
questionnaire(pk: $questionnaireId) {
leafGroups {
id
name
label
parentId
questionnaireId
type
category1
category2
category3
category4
order
}
}
}
}
}
`;

type Pillar = NonNullable<PillarsQuery['private']['projectScope']>['groups']['items'][number];
type Pillar = NonNullable<NonNullable<NonNullable<PillarsQuery['private']>['projectScope']>['questionnaire']>['leafGroups'][number];

const pillarKeySelector = (data: Pillar) => data.id;
const pillarLabelSelector = (data: Pillar) => data.label;
const pillarLabelSelector = (data: Pillar) => data.name;

interface PillarProps<T>{
projectId: string;
Expand Down Expand Up @@ -74,7 +77,7 @@ function PillarSelectInput<T extends string>(props: PillarProps<T>) {
},
);

const pillarsOptions = pillarsResponse?.private?.projectScope?.groups.items ?? [];
const pillarsOptions = pillarsResponse?.private?.projectScope?.questionnaire?.leafGroups ?? [];

return (
<SelectInput
Expand Down
107 changes: 59 additions & 48 deletions src/components/TocList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,33 @@ import {
QuickActionButton,
} from '@the-deep/deep-ui';
import SortableList, { Attributes, Listeners } from '#components/SortableList';
// import reorder from '#utils/common';
import {
QuestionnaireQuery,
} from '#generated/types';

import styles from './index.module.css';

type QuestionGroup = NonNullable<NonNullable<NonNullable<NonNullable<QuestionnaireQuery['private']>['projectScope']>['groups']>['items']>[number];
type QuestionGroup = {
key: string;
parentKeys: string[];
label: string;
nodes: QuestionGroup[];
};

interface TocProps {
id: string;
key: string;
mainIndex: number;
item: QuestionGroup;
attributes?: Attributes;
listeners?: Listeners;
selectedGroups: string[];
orderedOptions: QuestionGroup[] | undefined;
onOrderedOptionsChange: React.Dispatch<React.SetStateAction<QuestionGroup[]>>;
onOrderedOptionsChange: (newVal: QuestionGroup[] | undefined, index: number) => void;
onSelectedGroupsChange: React.Dispatch<React.SetStateAction<string[]>>;
onActiveTabChange: React.Dispatch<React.SetStateAction<string | undefined>>;
}

function TocRenderer(props: TocProps) {
const {
id,
key,
mainIndex,
item,
orderedOptions,
selectedGroups,
onOrderedOptionsChange,
onSelectedGroupsChange,
Expand All @@ -50,37 +51,44 @@ function TocRenderer(props: TocProps) {
const handleGroupSelect = useCallback((val: boolean) => {
onSelectedGroupsChange((oldVal) => {
if (val) {
return ([...oldVal, id]);
return ([...oldVal, key]);
}
const newVal = [...oldVal];
newVal.splice(oldVal.indexOf(id), 1);
newVal.splice(oldVal.indexOf(key), 1);

return newVal;
});
onActiveTabChange((oldActiveTab) => {
if (isNotDefined(oldActiveTab) && val) {
return id;
return key;
}
if (!val && oldActiveTab === id) {
if (!val && oldActiveTab === key) {
return undefined;
}
return oldActiveTab;
});
}, [
onActiveTabChange,
onSelectedGroupsChange,
id,
key,
]);

const filteredOptions = orderedOptions?.filter((group) => id === group.parentId);
const nodeList = item.nodes;

const handleListChange = useCallback((newVal: QuestionGroup[] | undefined) => {
onOrderedOptionsChange(newVal, mainIndex);
}, [
mainIndex,
onOrderedOptionsChange,
]);

return (
<Container
className={styles.groupItem}
headerIcons={(
<div className={styles.headerIcons}>
<QuickActionButton
name={id}
name={key}
// FIXME: use translation
title="Drag"
variant="transparent"
Expand All @@ -92,8 +100,8 @@ function TocRenderer(props: TocProps) {
<GrDrag />
</QuickActionButton>
<Checkbox
name={item.id}
value={selectedGroups.includes(id)}
name={item.key}
value={selectedGroups.includes(key)}
onChange={handleGroupSelect}
/>
</div>
Expand All @@ -104,13 +112,12 @@ function TocRenderer(props: TocProps) {
headingSize="extraSmall"
spacing="none"
>
{isDefined(filteredOptions) && filteredOptions.length > 0 && (
{isDefined(nodeList) && nodeList.length > 0 && (
// eslint-disable-next-line @typescript-eslint/no-use-before-define
<TocList
className={styles.nestedList}
parentId={item.id}
orderedOptions={orderedOptions}
onOrderedOptionsChange={onOrderedOptionsChange}
orderedOptions={nodeList}
onOrderedOptionsChange={handleListChange}
selectedGroups={selectedGroups}
onSelectedGroupsChange={onSelectedGroupsChange}
onActiveTabChange={onActiveTabChange}
Expand All @@ -120,13 +127,12 @@ function TocRenderer(props: TocProps) {
);
}

const keySelector = (g: QuestionGroup) => g.id;
const keySelector = (g: QuestionGroup) => g.key;

interface Props {
className?: string;
parentId: string | null;
orderedOptions: QuestionGroup[] | undefined;
onOrderedOptionsChange: React.Dispatch<React.SetStateAction<QuestionGroup[]>>
onOrderedOptionsChange: (newVal: QuestionGroup[] | undefined) => void;
selectedGroups: string[];
onSelectedGroupsChange: React.Dispatch<React.SetStateAction<string[]>>;
onActiveTabChange: React.Dispatch<React.SetStateAction<string | undefined>>;
Expand All @@ -135,52 +141,57 @@ interface Props {
function TocList(props: Props) {
const {
className,
parentId,
orderedOptions,
onOrderedOptionsChange,
selectedGroups,
onSelectedGroupsChange,
onActiveTabChange,
} = props;

const filteredOptions = orderedOptions?.filter(
(group: QuestionGroup) => group.parentId === parentId,
);

const tocRendererParams = useCallback((key: string, datum: QuestionGroup): TocProps => ({
const handleChildrenOrderChange = useCallback((
newValue: QuestionGroup[] | undefined,
parentIndex: number,
) => {
if (!newValue) {
return;
}
const newList = [...orderedOptions ?? []];
newList[parentIndex] = {
...newList[parentIndex],
nodes: newValue,
};
onOrderedOptionsChange(newList);
}, [
orderedOptions,
onOrderedOptionsChange,
]);

const tocRendererParams = useCallback((
key: string,
datum: QuestionGroup,
mainIndex: number,
): TocProps => ({
onOrderedOptionsChange: handleChildrenOrderChange,
onSelectedGroupsChange,
onActiveTabChange,
selectedGroups,
id: key,
key,
mainIndex,
item: datum,
}), [
orderedOptions,
handleChildrenOrderChange,
selectedGroups,
onSelectedGroupsChange,
onOrderedOptionsChange,
onActiveTabChange,
]);

const handleGroupOrderChange = useCallback((oldValue: QuestionGroup[]) => {
const nonParentOptions = orderedOptions?.filter((group) => !oldValue.includes(group)) ?? [];
onOrderedOptionsChange([
...nonParentOptions,
...oldValue,
]);
}, [
onOrderedOptionsChange,
orderedOptions,
]);

return (
<SortableList
className={_cs(styles.sortableList, className)}
direction="vertical"
name="toc"
onChange={handleGroupOrderChange}
data={filteredOptions}
onChange={onOrderedOptionsChange}
data={orderedOptions}
keySelector={keySelector}
renderer={TocRenderer}
rendererParams={tocRendererParams}
Expand Down
18 changes: 18 additions & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isDefined } from '@togglecorp/fujs';

// eslint-disable-next-line import/prefer-default-export
export function flatten<A, K>(
lst: A[],
valueSelector: (item: A) => K,
childSelector: (item: A) => A[] | undefined,
): K[] {
if (lst.length <= 0) {
return [];
}
const itemsByParent = lst.map(valueSelector);
const itemsByChildren = lst.map(childSelector).filter(isDefined).flat();
return [
...itemsByParent,
...flatten(itemsByChildren, valueSelector, childSelector),
];
}
13 changes: 7 additions & 6 deletions src/views/QuestionnaireEdit/DateQuestionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@apollo/client';
import {
ObjectSchema,
removeNull,
createSubmitHandler,
requiredStringCondition,
getErrorObject,
Expand Down Expand Up @@ -109,7 +110,7 @@ const schema: FormSchema = {
required: true,
requiredValidation: requiredStringCondition,
},
group: {
leafGroup: {
required: true,
requiredValidation: requiredStringCondition,
},
Expand Down Expand Up @@ -170,13 +171,13 @@ function DateQuestionForm(props: Props) {
skip: isNotDefined(questionInfoVariables),
variables: questionInfoVariables,
onCompleted: (response) => {
const questionResponse = response.private.projectScope?.question;
const questionResponse = removeNull(response.private.projectScope?.question);
setValue({
name: questionResponse?.name,
type: questionResponse?.type,
questionnaire: questionResponse?.questionnaireId,
label: questionResponse?.label,
group: questionResponse?.groupId,
leafGroup: questionResponse?.leafGroupId,
hint: questionResponse?.hint,
});
},
Expand Down Expand Up @@ -313,11 +314,11 @@ function DateQuestionForm(props: Props) {
onChange={setFieldValue}
/>
<PillarSelectInput
name="group"
name="leafGroup"
projectId={projectId}
questionnaireId={questionnaireId}
value={formValue.group}
error={fieldError?.group}
value={formValue.leafGroup}
error={fieldError?.leafGroup}
onChange={setFieldValue}
/>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/views/QuestionnaireEdit/FileQuestionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import {
ObjectSchema,
createSubmitHandler,
removeNull,
requiredStringCondition,
getErrorObject,
useForm,
Expand Down Expand Up @@ -109,7 +110,7 @@ const schema: FormSchema = {
required: true,
requiredValidation: requiredStringCondition,
},
group: {
leafGroup: {
required: true,
requiredValidation: requiredStringCondition,
},
Expand Down Expand Up @@ -170,13 +171,13 @@ function FileQuestionForm(props: Props) {
skip: isNotDefined(questionInfoVariables),
variables: questionInfoVariables,
onCompleted: (response) => {
const questionResponse = response.private.projectScope?.question;
const questionResponse = removeNull(response.private.projectScope?.question);
setValue({
name: questionResponse?.name,
type: questionResponse?.type,
questionnaire: questionResponse?.questionnaireId,
label: questionResponse?.label,
group: questionResponse?.groupId,
leafGroup: questionResponse?.leafGroupId,
hint: questionResponse?.hint,
});
},
Expand Down Expand Up @@ -312,11 +313,11 @@ function FileQuestionForm(props: Props) {
onChange={setFieldValue}
/>
<PillarSelectInput
name="group"
name="leafGroup"
projectId={projectId}
questionnaireId={questionnaireId}
value={formValue.group}
error={fieldError?.group}
value={formValue.leafGroup}
error={fieldError?.leafGroup}
onChange={setFieldValue}
/>
</div>
Expand Down
Loading

0 comments on commit 2d4a184

Please sign in to comment.