diff --git a/apps/form-builder/src/app/forms/[formId]/page.tsx b/apps/form-builder/src/app/forms/[formId]/page.tsx
index c6a1235..01163ec 100644
--- a/apps/form-builder/src/app/forms/[formId]/page.tsx
+++ b/apps/form-builder/src/app/forms/[formId]/page.tsx
@@ -1,7 +1,7 @@
import { getFormById } from '@/server/actions/forms';
import * as React from 'react';
import FormTabs from '@/components/FormTabs';
-
+import { Typography } from '@mui/material';
export default async function FormPage({
params,
}: {
@@ -13,15 +13,26 @@ export default async function FormPage({
return;
}
+ const updatedAtDate: Date = new Date(form.updatedAt);
+ const createdAtDate: Date = new Date(form.createdAt);
+
return (
{/* display properties of a form */}
-
Title: {form.title}
-
Description: {form.description}
-
Responder Type: {form.responderType}
-
Anonymous: {form.isAnonymous.toLocaleString()}
-
Created At: {form.createdAt.toLocaleString()}
-
Updated At: {form.updatedAt.toLocaleString()}
+
Title: {form.title}
+
Description: {form.description}
+
+ Responder Type: {form.responderType}
+
+
+ Anonymous: {form.isAnonymous.toLocaleString()}
+
+
+ Created At: {createdAtDate.toLocaleString()}
+
+
+ Last Updated At: {updatedAtDate.toLocaleString()}
+
{/* //added tabs and a button */}
diff --git a/apps/form-builder/src/app/forms/create/page.tsx b/apps/form-builder/src/app/forms/create/page.tsx
index 7a6911b..516bf78 100644
--- a/apps/form-builder/src/app/forms/create/page.tsx
+++ b/apps/form-builder/src/app/forms/create/page.tsx
@@ -1,3 +1,10 @@
+import UpsertForm from '@/components/UpsertForm/UpsertForm';
+import { Box } from '@mui/material';
+
export default async function NewFormPage() {
- return New form page
;
+ return (
+
+
+
+ );
}
diff --git a/apps/form-builder/src/components/FileUploadOptions/FileUploadOptions.tsx b/apps/form-builder/src/components/FileUploadOptions/FileUploadOptions.tsx
new file mode 100644
index 0000000..56dcd60
--- /dev/null
+++ b/apps/form-builder/src/components/FileUploadOptions/FileUploadOptions.tsx
@@ -0,0 +1,94 @@
+import {
+ Box,
+ Checkbox,
+ FormControl,
+ InputLabel,
+ ListItemText,
+ MenuItem,
+ OutlinedInput,
+ Select,
+ SelectChangeEvent,
+ TextField,
+} from '@mui/material';
+import React from 'react';
+
+export default function FileUploadOptions() {
+ const [value, setValue] = React.useState('KB');
+
+ const handleChange = (event: SelectChangeEvent) => {
+ setValue(event.target.value);
+ };
+
+ const fileTypes = [
+ 'PDF',
+ 'TXT',
+ 'DOC/DOCX',
+ 'XLS',
+ 'XLSX',
+ 'HTML',
+ 'PPT/PPTX',
+ 'JPEG',
+ 'PNG',
+ 'GIF',
+ 'MP4',
+ 'AVI',
+ 'MOV',
+ 'MP3',
+ ];
+
+ const [fileType, setFileType] = React.useState([]);
+
+ const handleFileChange = (event: SelectChangeEvent) => {
+ const {
+ target: { value },
+ } = event;
+ setFileType(typeof value === 'string' ? value.split(',') : value);
+ };
+
+ return (
+
+
+
+ File Type
+ }
+ renderValue={(selected) => selected.join(', ')}
+ >
+ {fileTypes.map((type) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ File Size
+
+
+
+
+ );
+}
diff --git a/apps/form-builder/src/components/FormAnalytics/index.tsx b/apps/form-builder/src/components/FormAnalytics/index.tsx
index 91721a1..dd1353a 100644
--- a/apps/form-builder/src/components/FormAnalytics/index.tsx
+++ b/apps/form-builder/src/components/FormAnalytics/index.tsx
@@ -34,7 +34,18 @@ export default function FormAnalytics(props: FormAnalyticsProps) {
{props.question.title}
- {props.responses.reduce(numAnswers, 0)} Responses
+
+ {props.responses.reduce(numAnswers, 0) === 1 && (
+
+ {props.responses.reduce(numAnswers, 0)} Response
+
+ )}
+ {props.responses.reduce(numAnswers, 0) > 1 && (
+
+ {props.responses.reduce(numAnswers, 0)} Responses
+
+ )}
+
{questionType === 'Text' && (
void;
+}
+
+export default function FormQuestionForm({
+ formData,
+ onChange,
+}: CreateFormQuestionProps) {
+ const [value, setValue] = React.useState('');
+
+ const [questionSettings, setQuestionSettings] = React.useState('');
+
+ const handleChange = (event: SelectChangeEvent) => {
+ setValue(event.target.value);
+ setQuestionSettings(event.target.value);
+ };
+
+ return (
+
+ onChange({ ...formData, title: e.target.value })}
+ />
+ onChange({ ...formData, description: e.target.value })}
+ />
+
+ onChange({ ...formData, isRequired: e.target.checked })
+ }
+ />
+ }
+ label="Required?"
+ />
+
+ Question Type
+
+
+
+ {questionSettings === 'Multiple' && (
+
+ )}
+ {questionSettings === 'Numeric' && }
+ {questionSettings === 'Text' && }
+ {questionSettings === 'FileUpload' && (
+
+ )}
+
+
+ );
+}
diff --git a/apps/form-builder/src/components/FormQuestions/index.tsx b/apps/form-builder/src/components/FormQuestions/index.tsx
new file mode 100644
index 0000000..5ee3ab8
--- /dev/null
+++ b/apps/form-builder/src/components/FormQuestions/index.tsx
@@ -0,0 +1,73 @@
+'use client';
+import AddIcon from '@mui/icons-material/Add';
+import { Box, Divider, IconButton, Typography } from '@mui/material';
+import React, { useState } from 'react';
+import FormQuestionForm, { FormQuestionFormData } from '../FormQuestionForm';
+import {
+ FormQuestionResponse,
+ FormResponse,
+} from '@hack4impact-utk/internal-models';
+
+interface FormQuestionsProps {
+ form?: FormResponse;
+}
+
+function FormQuestionResponsesToFormQuestionFormData(
+ formQuestionResponses: FormQuestionResponse[]
+) {
+ return formQuestionResponses.map(
+ (formQuestionResponse): FormQuestionFormData => {
+ return {
+ title: formQuestionResponse.title,
+ description: formQuestionResponse.description,
+ isRequired: formQuestionResponse.isRequired,
+ };
+ }
+ );
+}
+
+export default function FormQuestions(props: FormQuestionsProps) {
+ const [questionsFormData, setQuestionsFormData] = useState<
+ FormQuestionFormData[]
+ >(FormQuestionResponsesToFormQuestionFormData(props.form?.questions || []));
+
+ function addFormQuestion() {
+ const newQuestion: FormQuestionFormData = {
+ isRequired: false,
+ title: '',
+ description: '',
+ };
+ setQuestionsFormData((currQuestions) => [...currQuestions, newQuestion]);
+ }
+
+ function onFormQuestionChange(index: number, newData: FormQuestionFormData) {
+ setQuestionsFormData((currFormDataArr) =>
+ currFormDataArr.map((formData, i) => (i === index ? newData : formData))
+ );
+ }
+
+ return (
+
+ {questionsFormData.map((question, i) => (
+
+
+ Question {i + 1}
+
+ {
+ onFormQuestionChange(i, formData);
+ }}
+ />
+
+
+ ))}
+
+ Add new question
+
+
+
+
+
+ );
+}
diff --git a/apps/form-builder/src/components/FormSubmissions/MultipleChoiceQuestionAnalytics/Index.tsx b/apps/form-builder/src/components/FormSubmissions/MultipleChoiceQuestionAnalytics/Index.tsx
index a33f921..a933040 100644
--- a/apps/form-builder/src/components/FormSubmissions/MultipleChoiceQuestionAnalytics/Index.tsx
+++ b/apps/form-builder/src/components/FormSubmissions/MultipleChoiceQuestionAnalytics/Index.tsx
@@ -10,6 +10,8 @@ import {
ListItemText,
Typography,
} from '@mui/material';
+import PieChartAnalytics from '../PieChartAnalytics';
+import BarGraphAnalytics from '../BarGraphAnalytics/Index';
// Takes in the question and response as props
interface Props {
@@ -24,20 +26,41 @@ export default function MultipleChoiceQuestionAnalytics({
}: Props) {
// Gets the specific submission to a question from each response from a single form
function getAnswers() {
- let array = [];
+ const otherAnswers = [];
+ const allAnswers: string[] = [];
// For a given form, the findIndex function will find the specific question from the list of questionResponses
for (let i = 0; i < responses.length; i++) {
- let num = responses[i].questionResponses.findIndex(
+ const num = responses[i].questionResponses.findIndex(
(element) => element.question._id.toString() === question._id.toString()
);
+ console.log(num);
if (num === -1) {
continue;
} else {
// Get the answer and options
- let answer = responses[i].questionResponses[num].answer;
- let options =
+ const answer = responses[i].questionResponses[num].answer;
+ const options =
responses[i].questionResponses[num].question.multipleChoiceOptions
?.options;
+ console.log('starting a new response');
+ if (Array.isArray(answer)) {
+ for (const choice of answer) {
+ console.log(choice, options);
+ for (let j = 0; j < options!.length; j++) {
+ if (options?.findIndex((option) => option == choice) === -1) {
+ array.push(choice);
+ }
+ }
+ }
+ } else {
+ // Check if the answer is not in the options array
+ let answerInOptions = false;
+ for (let j = 0; j < options!.length; j++) {
+ if (options![j] == answer) {
+ answerInOptions = true;
+ break;
+ }
+ allAnswers.push(answer as string);
// Check if the answer is not in the options array
let answerInOptions = false;
@@ -46,35 +69,46 @@ export default function MultipleChoiceQuestionAnalytics({
answerInOptions = true;
break;
}
- }
// If the answer is not in the options, add it to the array
if (!answerInOptions) {
- array.push(answer);
+ otherAnswers.push(answer);
}
}
}
- return array;
+ return [otherAnswers, allAnswers];
}
-
+ const qType = question.multipleChoiceOptions!.choiceType;
// Displays the submissions in a list format with scroll functionality
return (
<>
-
- {'Choice Type: '}
- {question.multipleChoiceOptions?.choiceType}
-
-
-
- {getAnswers().map((submission, index) => (
-
-
- {submission}
-
-
- ))}
-
-
+ {`${qType} Select`}
+ {qType === 'Single' && (
+
+ )}
+ {qType === 'Multiple' && (
+
+ )}
+ {question.multipleChoiceOptions!.allowOther && qType !== 'Multiple' && (
+
+ {'"Other"'} Answers:
+
+ {getAnswers()[0].map((submission, index) => (
+
+
+ {submission}
+
+
+ ))}
+
+
+ )}
>
);
}
diff --git a/apps/form-builder/src/components/FormSubmissions/PieChartAnalytics.tsx b/apps/form-builder/src/components/FormSubmissions/PieChartAnalytics.tsx
index aac6ff9..82b0e1f 100644
--- a/apps/form-builder/src/components/FormSubmissions/PieChartAnalytics.tsx
+++ b/apps/form-builder/src/components/FormSubmissions/PieChartAnalytics.tsx
@@ -4,7 +4,7 @@ import React from 'react';
interface Props {
choices: string[];
- answers: string[][];
+ answers: string[];
}
//Function to display pie chart
@@ -20,19 +20,21 @@ export default function PieChartAnalytics({ choices, answers }: Props) {
let totalAnswers = 0;
// Loop through each answer and count the occurrences of each choice
- answers.forEach((answerSet) => {
- answerSet.forEach((answer) => {
- // Check if the answer is one of the choices
- if (choiceCounts.hasOwnProperty(answer)) {
- choiceCounts[answer]++;
- } else {
- // Increment the count for "Other"
- choiceCounts['Other']++;
- }
- totalAnswers++;
- });
+ answers.forEach((answer) => {
+ // Check if the answer is one of the choices
+ if (choiceCounts.hasOwnProperty(answer)) {
+ choiceCounts[answer]++;
+ } else {
+ // Increment the count for "Other"
+ choiceCounts['Other']++;
+ }
+ totalAnswers++;
});
+ if (choiceCounts['Other'] === 0) {
+ delete choiceCounts['Other'];
+ }
+
// Extract the counts to an array to use as data for the BarChart, including the count for "Other"
const data = Object.keys(choiceCounts).map((key) => ({
label: key,
@@ -62,6 +64,7 @@ export default function PieChartAnalytics({ choices, answers }: Props) {
}}
//Changes size of pie chart
height={600}
+ width={500}
/>
);
}
diff --git a/apps/form-builder/src/components/FormTabs/index.tsx b/apps/form-builder/src/components/FormTabs/index.tsx
index 613521f..528891e 100644
--- a/apps/form-builder/src/components/FormTabs/index.tsx
+++ b/apps/form-builder/src/components/FormTabs/index.tsx
@@ -2,12 +2,16 @@
import { Box, Tabs, Tab, Button, Divider, Typography } from '@mui/material';
import React from 'react';
import FormSubmissionTable from '../FormSubmissions/FormSubmissionTable';
-import { FormResponse } from '@hack4impact-utk/internal-models';
+import FormQuestions from '../FormQuestions';
import FormAnalytics from '../FormAnalytics';
-import { FormQuestionResponse } from '@hack4impact-utk/internal-models';
+import {
+ FormQuestionResponse,
+ FormResponse,
+} from '@hack4impact-utk/internal-models';
+import FormSettings from '../FormSettings';
-interface FormsTabProp {
- form: FormResponse;
+interface FormTabsProps {
+ form?: FormResponse;
}
interface TabPanelProps {
@@ -31,12 +35,11 @@ function CustomTabPanel(props: TabPanelProps) {
);
}
-export default function FormTabs(props: FormsTabProp) {
+export default function FormTabs(props: FormTabsProps) {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
-
return (
<>
@@ -51,37 +54,42 @@ export default function FormTabs(props: FormsTabProp) {
{/*Submissions*/}
{/*Analytics*/}
- {props.form.questions.map(
- (question: FormQuestionResponse, i: number) => (
-
-
- Question {i + 1}
-
-
-
+ {props.form &&
+ props.form.questions.map(
+ (question: FormQuestionResponse, i: number) => (
+
+
+ Question {i + 1}
+
+
+
+
+ {i !== props.form!.questions.length - 1 && (
+
+ )}
- {i !== props.form.questions.length - 1 && (
-
- )}
-
- )
- )}
+ )
+ )}
{/*Questions*/}
-
+
+
+
{/*Settings*/}
-
+
+
+
+
+
+ );
+}
diff --git a/apps/form-builder/src/components/NumericOptions.tsx b/apps/form-builder/src/components/NumericOptions/NumericOptions.tsx
similarity index 100%
rename from apps/form-builder/src/components/NumericOptions.tsx
rename to apps/form-builder/src/components/NumericOptions/NumericOptions.tsx
diff --git a/packages/internal-models/src/db/models/FormBuilder/FormQuestion.ts b/packages/internal-models/src/db/models/FormBuilder/FormQuestion.ts
index 2258010..23158c2 100644
--- a/packages/internal-models/src/db/models/FormBuilder/FormQuestion.ts
+++ b/packages/internal-models/src/db/models/FormBuilder/FormQuestion.ts
@@ -52,7 +52,6 @@ export const FormQuestionSchema = new Schema(
},
supportedFileTypes: {
type: Schema.Types.String,
- enum: fileTypes,
required: true,
},
},
@@ -71,7 +70,6 @@ export const FormQuestionSchema = new Schema(
},
choiceType: {
type: Schema.Types.String,
- enum: multipleChoiceTypes,
required: true,
},
},