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

Update user admission to support json schema #1179

Merged
merged 1 commit into from
Oct 26, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 18 additions & 6 deletions frontend/src/components/TextAreaField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from "react";
import React, { useMemo } from "react";
import InputValidationFeedback from "src/components/InputValidationFeedback";
import { FieldLabel, StyledTextAreaField } from "src/components/styledFields";
import { traverseObject } from "src/utils/methods";

interface TextAreaFieldProps {
placeholder: string;
title: string;
label: string;
disabled: boolean;
field: {
name: string;
onChange: React.ChangeEventHandler<HTMLTextAreaElement>;
Expand All @@ -19,16 +21,25 @@ interface TextAreaFieldProps {

const TextAreaField: React.FC<TextAreaFieldProps> = ({
placeholder,
title,
label,
disabled,
field: { name, onChange, value },
form: { touched, errors, handleBlur },
}) => {
const error = touched[name] && errors[name];
const fieldTouched = useMemo(
() => traverseObject(name, touched),
[name, touched]
);
const fieldError = useMemo(
() => traverseObject(name, errors),
[name, errors]
);
const error = fieldTouched && fieldError;

return (
<div>
<FieldLabel>
{title}
<FieldLabel htmlFor={name}>
{label}
<InputValidationFeedback error={error} />
</FieldLabel>

Expand All @@ -40,6 +51,7 @@ const TextAreaField: React.FC<TextAreaFieldProps> = ({
placeholder={placeholder}
value={value}
rows={10}
disabled={disabled}
/>
</div>
);
Expand Down
64 changes: 14 additions & 50 deletions frontend/src/containers/GroupApplication/Application.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,35 @@
import React, { useEffect } from "react";
import React from "react";
import styled from "styled-components";
import { media } from "src/styles/mediaQueries";
import {
FieldLabel,
InputValidationFeedback,
StyledTextAreaField,
} from "src/components/styledFields";
import readmeIfy from "src/components/ReadmeLogo";
import useDebouncedState from "src/utils/useDebouncedState";
import { saveApplicationTextDraft } from "src/utils/draftHelper";
import { Group } from "src/types";
import { FieldInputProps, FormikProps } from "formik";
import JsonFieldParser from "src/routes/ApplicationForm/JsonFieldParser";

type FieldValue = string;
export type FormValues = Record<string, string>;

export interface ApplicationProps {
responseLabel: string;
group: Group;
field: FieldInputProps<FieldValue>;
form: FormikProps<FormValues>;
disabled: boolean;
disabled?: boolean;
}

const Application: React.FC<ApplicationProps> = ({
responseLabel,
group,
field: { name, onChange, value },
form: { touched, errors, handleBlur },
disabled,
}) => {
const debouncedValue = useDebouncedState(value);

useEffect(() => {
saveApplicationTextDraft([group.name, value]);
}, [debouncedValue]);

const error = touched[name] ? errors[name] : undefined;

const Application: React.FC<ApplicationProps> = ({ group, disabled }) => {
return (
<Container>
<LogoNameWrapper>
<Logo src={group.logo} />
<Name>{readmeIfy(group.name)}</Name>
</LogoNameWrapper>
{responseLabel && (
<ResponseLabel>{readmeIfy(responseLabel, true)}</ResponseLabel>
)}
<InputWrapper>
<FieldLabel htmlFor={group.name.toLowerCase()}>Søknadstekst</FieldLabel>
<InputArea
className="textarea"
name={name}
id={name}
onChange={onChange}
onBlur={handleBlur}
placeholder="Skriv søknadstekst her..."
value={value}
$error={!!error}
rows={10}
disabled={disabled}
/>
<InputValidationFeedback error={error} />
{group.questions &&
Array.isArray(group.questions) &&
group.questions.map((question, index) => (
<JsonFieldParser
key={index}
group={group}
jsonField={question}
disabled={disabled}
/>
))}
</InputWrapper>
</Container>
);
Expand Down Expand Up @@ -143,7 +111,3 @@ export const InputWrapper = styled.div`
font-family: var(--font-family);
font-size: 1rem;
`;

const InputArea = styled(StyledTextAreaField)`
min-height: 10rem;
`;
21 changes: 10 additions & 11 deletions frontend/src/containers/GroupApplication/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ import { getApplictionTextDrafts } from "src/utils/draftHelper";
import Application, { ApplicationProps } from "./Application";

const GroupApplication = (props: ApplicationProps) => {
useEffect(() => {
initializeValue();
}, []);
// useEffect(() => {
// initializeValue();
// }, []);
// const initializeValue = () => {
// const groupName = props.group.name.toLowerCase();

const initializeValue = () => {
const groupName = props.group.name.toLowerCase();
// const restoredApplicationText = getApplictionTextDrafts();

const restoredApplicationText = getApplictionTextDrafts();

if (restoredApplicationText != null && restoredApplicationText[groupName]) {
props.form.setFieldValue(groupName, restoredApplicationText[groupName]);
}
};
// if (restoredApplicationText != null && restoredApplicationText[groupName]) {
// props.form.setFieldValue(groupName, restoredApplicationText[groupName]);
// }
// };

return <Application {...props} />;
};
Expand Down
16 changes: 10 additions & 6 deletions frontend/src/query/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { JsonFieldInput } from "src/types";
import { apiClient } from "../utils/callApi";

// Admin mutations
Expand Down Expand Up @@ -103,13 +104,13 @@ interface UpdateGroupProps {
groupPrimaryKey: number;
updatedGroupData: {
description: string;
response_label: string;
questions: JsonFieldInput[];
};
}

interface UpdateGroupErrorData {
description: string[];
response_label: string[];
questions: string[];
}

export const useUpdateGroupMutation = () =>
Expand All @@ -119,9 +120,8 @@ export const useUpdateGroupMutation = () =>
);

export interface MutationApplication {
text: string;
phone_number: string;
applications: Record<string, string>;
responses: Record<string, string>;
group_applications: Record<string, Record<string, string>>;
}

interface CreateApplicationProps {
Expand All @@ -130,7 +130,11 @@ interface CreateApplicationProps {

export const useCreateApplicationMutation = (admissionSlug: string) => {
const queryClient = useQueryClient();
return useMutation<unknown, AxiosError, CreateApplicationProps>(
return useMutation<
unknown,
AxiosError<Record<string, Record<string, string>>>,
CreateApplicationProps
>(
({ newApplication }) =>
apiClient.post(
`/admission/${admissionSlug}/application/`,
Expand Down
40 changes: 10 additions & 30 deletions frontend/src/routes/ApplicationForm/FormStructure.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React, { ReactNode } from "react";
import { Form, Field, FormikValues } from "formik";
import { Form, FormikValues } from "formik";
import FormatTime from "src/components/Time/FormatTime";
import Icon from "src/components/Icon";
import LegoButton from "src/components/LegoButton";
import PriorityTextField from "./PriorityTextField";
import PhoneNumberField from "./PhoneNumberField";
import ToggleGroups from "./ToggleGroups";
import ErrorFocus from "./ErrorFocus";
import { useMyApplication } from "src/query/hooks";
Expand All @@ -29,6 +27,7 @@ import {
Title,
} from "./FormStructureStyle";
import { Admission, Group } from "src/types";
import JsonFieldParser from "./JsonFieldParser";

interface FormStructureProps extends FormikValues {
admission: Admission;
Expand Down Expand Up @@ -74,34 +73,16 @@ const FormStructure: React.FC<FormStructureProps> = ({
<SeparatorLine />
<GeneralInfoSection>
<SectionHeader>Generelt</SectionHeader>
<HelpText>
<Icon name="information-circle-outline" />
Mobilnummeret vil bli brukt til å kalle deg inn på intervju av
komitéledere.
</HelpText>
<Field name="phoneNumber" component={PhoneNumberField} />

<HelpText>
<Icon name="information-circle-outline" />
Kun leder av Abakus kan se det du skriver inn i prioriterings- og
kommentarfeltet.
<Icon name="information-circle-outline" />
Det er ikke sikkert prioriteringslisten vil bli tatt hensyn til.
Ikke søk på en komité du ikke ønsker å bli med i.
</HelpText>
<Field
name="priorityText"
component={PriorityTextField}
label="Prioriteringer, og andre kommentarer"
optional
/>
{admission.questions.map((question, index) => (
<JsonFieldParser key={index} jsonField={question} />
))}
</GeneralInfoSection>
<SeparatorLine />
<GroupsSection>
<Sidebar>
<div>
<SectionHeader>Komiteer</SectionHeader>
<HelpText>
<HelpText $indented>
<Icon name="information-circle-outline" />
Her skriver du søknaden til komiteen(e) du har valgt. Hver
komité kan kun se søknaden til sin egen komité.
Expand Down Expand Up @@ -154,13 +135,12 @@ const FormStructure: React.FC<FormStructureProps> = ({
</div>
)}
<SubmitInfo>
Oppdateringer etter søknadsfristen kan ikke garanteres å bli sett
av komiteen(e) du søker deg til.
Oppdateringer etter søknadsfristen kan ikke garanteres å bli sett.
</SubmitInfo>
<SubmitInfo>
Din søknad til hver komité kan kun ses av den aktuelle komiteen og
leder av Abakus. All søknadsinformasjon slettes etter opptaket er
gjennomført.
Din søknad kan kun ses av den aktuelle gruppen og leder av Abakus.
<br />
All søknadsinformasjon slettes etter opptaket er gjennomført.
</SubmitInfo>
<SubmitInfo>Du kan når som helst trekke søknaden din.</SubmitInfo>
</div>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/routes/ApplicationForm/FormStructureStyle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,16 @@ export const Text = styled.p`
`}
`;

export const HelpText = styled.span`
type HelpTextProps = {
$indented?: boolean;
};

export const HelpText = styled.span<HelpTextProps>`
color: rgba(57, 75, 89, 0.75);
font-size: 0.9rem;
line-height: 1.2rem;
display: flex;
margin-left: calc(-2.6rem + 4px);
margin-left: ${(props) => (props.$indented ? "calc(-2.6rem + 4px)" : "0")};

i {
color: var(--lego-gray-medium);
Expand Down
63 changes: 63 additions & 0 deletions frontend/src/routes/ApplicationForm/JsonFieldParser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from "react";
import { Field } from "formik";
import { Group, JsonFieldInput } from "src/types";
import PhoneNumberField from "./PhoneNumberField";
import { HelpText } from "./FormStructureStyle";
import Icon from "src/components/Icon";
import PriorityTextField from "./PriorityTextField";
import TextAreaField from "src/components/TextAreaField";

type JsonFieldParserProps = {
group?: Group;
jsonField: JsonFieldInput;
disabled?: boolean;
};

const JsonFieldParser: React.FC<JsonFieldParserProps> = ({
group,
jsonField,
disabled,
}) => {
if (jsonField.type === "text") {
return (
<HelpText $indented={!group}>
{!group && <Icon name="information-circle-outline" />}
{jsonField.text}
</HelpText>
);
}

const id =
(group ? "groupResponses." + group.name + "." : "responses.") +
jsonField.id;

if (jsonField.type === "textarea")
return (
<>
<Field
name={id}
component={TextAreaField}
placeholder={jsonField.placeholder}
label={jsonField.label}
disabled={!!disabled}
/>
</>
);
if (jsonField.type === "phoneinput")
return (
<Field name={id} component={PhoneNumberField} disabled={!!disabled} />
);
if (jsonField.type === "textinput")
return (
<Field
name={id}
component={TextAreaField}
label="Prioriteringer, og andre kommentarer"
optional
disabled={!!disabled}
/>
);
return null;
};

export default JsonFieldParser;