aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordan <[email protected]>2024-01-03 18:36:53 -0500
committerdan <[email protected]>2024-01-03 18:36:53 -0500
commit98be17522ef0474a40b134a3ca4e0d2e8096d469 (patch)
tree9ae3b31c03aa2b3cbd8c2f6942445a1c5a439a4e
parent3baf3c177f86dcc01ce49e14daec7c5c895eefe6 (diff)
downloaddraggable-form-demo-master.tar.gz
draggable-form-demo-master.tar.bz2
draggable-form-demo-master.zip
feat: stuff changedHEADmaster
-rw-r--r--src/App.js10
-rw-r--r--src/components/FormBuilder/FinalForm.js12
-rw-r--r--src/components/FormBuilder/index.js201
-rw-r--r--src/hooks/useForm.js20
-rw-r--r--src/hooks/useLocalStorage.js17
-rw-r--r--src/pages/NewSurvey/index.js5
-rw-r--r--src/pages/Survey/index.js7
-rw-r--r--src/pages/SurveyAnswer/index.js27
-rw-r--r--src/pages/SurveyResults/index.js58
-rw-r--r--src/pages/Surveys/SurveysList.js36
10 files changed, 299 insertions, 94 deletions
diff --git a/src/App.js b/src/App.js
index 28e3a52..bb574c5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -7,7 +7,9 @@ import {
import Login from "./pages/Login";
import Surveys from "./pages/Surveys";
import NewSurvey from "./pages/NewSurvey";
+import Survey from "./pages/Survey";
import SurveyResults from "./pages/SurveyResults";
+import SurveyAnswer from "./pages/SurveyAnswer";
import SurveyAssignees from "./pages/SurveyAssignees";
import Users from "./pages/Users";
import NavBar from "./components/NavBar";
@@ -53,10 +55,18 @@ function routes({ login, logout, isLoggedIn }) {
element: withNavBar(<NewSurvey />),
},
{
+ path: "/surveys/:formId",
+ element: withNavBar(<Survey />),
+ },
+ {
path: "/surveys/:surveyId/results",
element: withNavBar(<SurveyResults />),
},
{
+ path: "/surveys/:surveyId/results/:runId/answer",
+ element: withNavBar(<SurveyAnswer />),
+ },
+ {
path: "/surveys/:surveyId/assignees",
element: withNavBar(<SurveyAssignees />),
},
diff --git a/src/components/FormBuilder/FinalForm.js b/src/components/FormBuilder/FinalForm.js
new file mode 100644
index 0000000..070a0fe
--- /dev/null
+++ b/src/components/FormBuilder/FinalForm.js
@@ -0,0 +1,12 @@
+import { Form } from "./index";
+
+export default function FinalForm({ formId, setResults, results }) {
+ return (
+ <Form
+ formId={formId}
+ editable={false}
+ setResults={setResults}
+ results={results}
+ />
+ );
+}
diff --git a/src/components/FormBuilder/index.js b/src/components/FormBuilder/index.js
index 5636b32..5c77c25 100644
--- a/src/components/FormBuilder/index.js
+++ b/src/components/FormBuilder/index.js
@@ -8,7 +8,9 @@ import {
TextField,
} from "@mui/material";
import React, { useRef, useState } from "react";
-export default function FormBuilder() {
+import useForm from "../../hooks/useForm";
+
+export default function FormBuilder({ formId }) {
return (
<Stack direction={"column"}>
<h3>FormBuilder</h3>
@@ -18,7 +20,7 @@ export default function FormBuilder() {
alignItems={"flex-start"}
spacing={2}
>
- <Form initialForm={false} editable />
+ <Form initialForm={false} editable formId={formId} />
<WidgetsLibrary />
</Stack>
</Stack>
@@ -28,43 +30,57 @@ export default function FormBuilder() {
const availableWidgets = {
label: {
name: "Label",
- element: ({ id, name, setName }) => (
- <TextField
- id={id}
- variant="standard"
- value={name}
- onChange={(e) => setName(e.target.value)}
- fullWidth
- />
- ),
- finalElement: ({ id }) => <div>{id}</div>,
+ element: ({ id, name, setName, finalField }) =>
+ finalField ? (
+ <div>{name}</div>
+ ) : (
+ <TextField
+ id={id}
+ variant="standard"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ fullWidth
+ />
+ ),
},
number: {
name: "Number",
- element: ({ id }) => (
+ element: ({ id, value, setValue }) => (
<TextField
id={id}
label={id ? `example field ${id}` : "Outlined"}
variant="outlined"
type={"number"}
+ value={value || ""}
+ onChange={(e) => {
+ if (setValue) {
+ setValue(e.target.value);
+ }
+ }}
fullWidth
/>
),
},
text: {
name: "Text",
- element: ({ id }) => (
+ element: ({ id, setValue, value }) => (
<TextField
id={id}
label={id ? `example field ${id}` : "Outlined"}
variant="outlined"
fullWidth
+ value={value || ""}
+ onChange={(e) => {
+ if (setValue) {
+ setValue(e.target.value);
+ }
+ }}
/>
),
},
multiline: {
name: "Multiline",
- element: ({ id }) => (
+ element: ({ id, setValue, value }) => (
<TextField
id={id}
label={id ? `example field ${id}` : "Multiline"}
@@ -72,6 +88,12 @@ const availableWidgets = {
multiline
minRows={4}
fullWidth
+ value={value || ""}
+ onChange={(e) => {
+ if (setValue) {
+ setValue(e.target.value);
+ }
+ }}
/>
),
},
@@ -134,7 +156,14 @@ function WidgetCard({ name, element, type, finalElement }) {
}
}
-function FormField({ formField, setFormField, deleteFormField }) {
+function FormField({
+ formField,
+ setFormField,
+ deleteFormField,
+ editable,
+ setResult,
+ result,
+}) {
const { id, type, name } = formField;
function setName(name) {
@@ -147,33 +176,47 @@ function FormField({ formField, setFormField, deleteFormField }) {
const { element: Element } = availableWidgets[type];
const dragHandleRef = useRef();
const [target, setTarget] = useState();
- return (
- <Stack
- direction={"row"}
- sx={{ width: "100%" }}
- alignItems={"center"}
- justifyContent={"space-between"}
- draggable
- onMouseDown={(e) => {
- setTarget(e.target);
- }}
- onDragStart={(e) => {
- if (dragHandleRef.current.contains(target)) {
- e.dataTransfer.setData("application/formwidgetid", id);
- } else {
- e.preventDefault();
- }
- }}
- >
- <span ref={dragHandleRef} style={{ cursor: "grab" }}>
- <DragIndicator />
- </span>
- <Element id={id} name={name} setName={setName} type={type} />
- <IconButton onClick={deleteFormField}>
- <Clear />
- </IconButton>
- </Stack>
- );
+ if (!editable) {
+ return (
+ <Element
+ id={id}
+ name={name}
+ setName={setName}
+ type={type}
+ finalField={!editable}
+ setValue={setResult}
+ value={result?.result}
+ />
+ );
+ } else {
+ return (
+ <Stack
+ direction={"row"}
+ sx={{ width: "100%" }}
+ alignItems={"center"}
+ justifyContent={"space-between"}
+ draggable
+ onMouseDown={(e) => {
+ setTarget(e.target);
+ }}
+ onDragStart={(e) => {
+ if (dragHandleRef.current.contains(target)) {
+ e.dataTransfer.setData("application/formwidgetid", id);
+ } else {
+ e.preventDefault();
+ }
+ }}
+ >
+ <span ref={dragHandleRef} style={{ cursor: "grab" }}>
+ <DragIndicator />
+ </span>
+ <Element id={id} name={name} setName={setName} type={type} />
+ <IconButton onClick={deleteFormField}>
+ <Clear />
+ </IconButton>
+ </Stack>
+ );
+ }
}
function capture(e) {
@@ -214,8 +257,9 @@ function DropZone({ insertField, index, moveField }) {
);
}
-function Form({ initialForm, editable }) {
- const [form, setForm] = useState({ fields: [], ...initialForm });
+export function Form({ editable, formId, setResults, results }) {
+ // const [form, setForm] = useState({ fields: [], ...initialForm });
+ const [form, setForm] = useForm(formId);
function updateFormField(newField) {
setForm({
@@ -254,41 +298,42 @@ function Form({ initialForm, editable }) {
setForm({ ...form });
}
- if (!editable && !initialForm) {
- return;
- // else if (!initialForm) {
- // return <FormInitialPrompt />;
- // }
- } else {
- return (
- <Stack sx={{ width: "100%" }}>
- <FormHeader form={form} />
- {editable && (
- <DropZone
- moveField={moveField}
- insertField={insertField}
- index={-1}
+ function setResult(fieldId, result) {
+ const fieldResult = {
+ fieldId,
+ result,
+ fieldName: form?.fields?.find(({ id }) => id === fieldId)?.name,
+ };
+ setResults({ ...results, [fieldId]: fieldResult });
+ }
+
+ return (
+ <Stack sx={{ width: "100%" }} spacing={editable ? 0 : 1}>
+ <FormHeader form={form} />
+ {editable && (
+ <DropZone moveField={moveField} insertField={insertField} index={-1} />
+ )}
+ {form?.fields?.map((formField, i) => (
+ <Grouping key={formField.id}>
+ <FormField
+ setFormField={(field) => updateFormField(field)}
+ deleteFormField={() => deleteFormField(formField.id)}
+ formField={formField}
+ editable={editable}
+ setResult={results && ((result) => setResult(formField.id, result))}
+ result={results && results[formField.id]}
/>
- )}
- {form?.fields?.map((formField, i) => (
- <Grouping key={formField.id}>
- <FormField
- setFormField={(field) => updateFormField(field)}
- deleteFormField={() => deleteFormField(formField.id)}
- formField={formField}
+ {editable && (
+ <DropZone
+ moveField={moveField}
+ insertField={insertField}
+ index={i}
/>
- {editable && (
- <DropZone
- moveField={moveField}
- insertField={insertField}
- index={i}
- />
- )}
- </Grouping>
- ))}
- </Stack>
- );
- }
+ )}
+ </Grouping>
+ ))}
+ </Stack>
+ );
}
function FormHeader() {
diff --git a/src/hooks/useForm.js b/src/hooks/useForm.js
new file mode 100644
index 0000000..6270c1e
--- /dev/null
+++ b/src/hooks/useForm.js
@@ -0,0 +1,20 @@
+import useLocalStorage from "./useLocalStorage";
+
+export default function useForm(formId) {
+ const [forms, setForms] = useLocalStorage(`forms`, {});
+
+ const form = forms[formId] || { fields: [], id: formId };
+
+ function setForm(newForm) {
+ console.log(
+ "setForm",
+ newForm,
+ { ...forms, [newForm.id]: newForm },
+ { form: forms[formId], formId, forms },
+ );
+ setForms({ ...forms, [newForm.id]: newForm });
+ }
+
+ console.log("useForm", formId, form);
+ return [form, setForm];
+}
diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js
new file mode 100644
index 0000000..6dbed36
--- /dev/null
+++ b/src/hooks/useLocalStorage.js
@@ -0,0 +1,17 @@
+import { useState } from "react";
+
+const useLocalStorage = (key, initialValue) => {
+ const storedVal = localStorage[key]
+ ? JSON.parse(localStorage[key]) || initialValue
+ : initialValue;
+ const [value, setValue] = useState(storedVal);
+
+ function update(newValue) {
+ localStorage[key] = JSON.stringify(newValue);
+ setValue(newValue);
+ }
+
+ return [value, update];
+};
+
+export default useLocalStorage;
diff --git a/src/pages/NewSurvey/index.js b/src/pages/NewSurvey/index.js
index f1d4b89..335eb93 100644
--- a/src/pages/NewSurvey/index.js
+++ b/src/pages/NewSurvey/index.js
@@ -1,4 +1,5 @@
-import FormBuilder from "../../components/FormBuilder";
+import { Navigate } from "react-router-dom";
export default function NewSurvey() {
- return <FormBuilder />;
+ const id = crypto.randomUUID();
+ return <Navigate to={{ pathname: `/surveys/${id}` }} />;
}
diff --git a/src/pages/Survey/index.js b/src/pages/Survey/index.js
new file mode 100644
index 0000000..41bac5e
--- /dev/null
+++ b/src/pages/Survey/index.js
@@ -0,0 +1,7 @@
+import FormBuilder from "../../components/FormBuilder";
+import { useParams } from "react-router-dom";
+
+export default function Survey() {
+ const { formId } = useParams();
+ return <FormBuilder formId={formId} />;
+}
diff --git a/src/pages/SurveyAnswer/index.js b/src/pages/SurveyAnswer/index.js
new file mode 100644
index 0000000..d1676c1
--- /dev/null
+++ b/src/pages/SurveyAnswer/index.js
@@ -0,0 +1,27 @@
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import Form from "../../components/FormBuilder/FinalForm";
+import useLocalStorage from "../../hooks/useLocalStorage";
+
+export default function SurveyAnswers() {
+ const { runId, surveyId } = useParams();
+ const [resultsSet, setResultsSet] = useLocalStorage(
+ `results-${surveyId}-${runId}`,
+ {},
+ );
+ const [answerSetId] = useState(crypto.randomUUID());
+ const results = (resultsSet && resultsSet[answerSetId]) || {
+ datetime: new Date().toISOString(),
+ };
+
+ function setResults(newResults) {
+ setResultsSet({ ...resultsSet, [answerSetId]: newResults });
+ }
+
+ return (
+ <div>
+ {runId}
+ <Form formId={surveyId} results={results} setResults={setResults} />
+ </div>
+ );
+}
diff --git a/src/pages/SurveyResults/index.js b/src/pages/SurveyResults/index.js
index 82d41c6..dec182e 100644
--- a/src/pages/SurveyResults/index.js
+++ b/src/pages/SurveyResults/index.js
@@ -1,3 +1,59 @@
+import { Button } from "@mui/material";
+import { DataGrid } from "@mui/x-data-grid";
+import { Link, useParams } from "react-router-dom";
+import useLocalStorage from "../../hooks/useLocalStorage";
+import useForm from "../../hooks/useForm";
+
export default function SurveyResults() {
- return <>SurveyResults</>;
+ const { surveyId } = useParams();
+ const [runs, setRuns] = useLocalStorage(`runs-${surveyId}`, []);
+ const [form] = useForm(surveyId);
+
+ const columns = form?.fields?.map(({ id, name }) => ({
+ field: id,
+ headerName: name,
+ }));
+
+ return (
+ <>
+ <h3>SurveyRuns</h3>
+ {surveyId}
+ <Button onClick={() => setRuns([{ id: crypto.randomUUID() }, ...runs])}>
+ New Run
+ </Button>
+
+ {runs &&
+ runs.map((r) => {
+ const runRs = runResults(surveyId, r.id);
+
+ console.log(runRs);
+
+ return (
+ <div key={r.id}>
+ <Link to={`${r.id}/answer`}>Add Answer</Link> {JSON.stringify(r)}
+ Total Results: {Object.keys(runRs).length}
+ <DataGrid
+ rows={[]}
+ columns={columns}
+ initialState={{
+ pagination: {
+ paginationModel: {
+ pageSize: 25,
+ },
+ },
+ }}
+ pageSizeOptions={[25, 50, 100]}
+ checkboxSelection
+ disableRowSelectionOnClick
+ />
+ </div>
+ );
+ })}
+ </>
+ );
+}
+
+function runResults(surveyId, runId) {
+ const str = localStorage[`results-${surveyId}-${runId}`];
+ return !str ? {} : JSON.parse(str);
}
diff --git a/src/pages/Surveys/SurveysList.js b/src/pages/Surveys/SurveysList.js
index c9c5b90..a320716 100644
--- a/src/pages/Surveys/SurveysList.js
+++ b/src/pages/Surveys/SurveysList.js
@@ -1,9 +1,10 @@
import React from "react";
import Box from "@mui/material/Box";
import { DataGrid } from "@mui/x-data-grid";
+import useLocalStorage from "../../hooks/useLocalStorage";
+import { Link } from "react-router-dom";
const columns = [
- { field: "id", headerName: "ID", width: 90 },
{
field: "name",
headerName: "Name",
@@ -14,21 +15,30 @@ const columns = [
headerName: "Next Run",
width: 150,
},
-];
-
-const rows = [
- { id: 1, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 2, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 3, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 4, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 5, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 6, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 7, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 8, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
- { id: 9, name: "survey", next_run_at: "Mon 28 Aug 16:35:36 EDT 2023" },
+ {
+ field: "edit_survey_link",
+ headerName: "",
+ renderCell: ({ row }) => <Link to={`${row?.path}`}>Edit</Link>,
+ width: 150,
+ },
+ {
+ field: "results_survey_link",
+ headerName: "",
+ renderCell: ({ row }) => <Link to={`${row?.path}/results`}>Results</Link>,
+ width: 150,
+ },
];
export default function SurveysList() {
+ const [forms] = useLocalStorage(`forms`, {});
+
+ const rows = Object.keys(forms).map((id, i) => ({
+ id,
+ name: `Survey ${i}`,
+ next_run_at: "Mon 28 Aug 16:35:36 EDT 2023",
+ path: `/surveys/${id}`,
+ }));
+
return (
<Box sx={{ minHeight: "100%", width: "100%" }}>
<DataGrid