From ca5edbbb3ba272bb8429b0fe320d1ffd4ec170ce Mon Sep 17 00:00:00 2001 From: Michael Schramm Date: Sun, 27 Feb 2022 12:58:52 +0100 Subject: [PATCH] upgrade packages, improve field logic, fix slider, hide empty submissions, fix urls for buttons, improve data handling for fields, improve sqlite migration handling --- CHANGELOG.md | 9 +++- components/form/admin/fields.tab.tsx | 1 - components/form/admin/logic.block.tsx | 6 +-- components/form/admin/types/checkbox.type.tsx | 2 +- components/form/admin/types/date.type.tsx | 2 +- components/form/admin/types/dropdown.type.tsx | 2 +- components/form/admin/types/email.type.tsx | 2 +- components/form/admin/types/hidden.type.tsx | 2 +- components/form/admin/types/link.type.tsx | 2 +- components/form/admin/types/number.type.tsx | 6 +-- components/form/admin/types/radio.type.tsx | 2 +- components/form/admin/types/rating.type.tsx | 6 +-- components/form/admin/types/slider.type.tsx | 17 +------ components/form/admin/types/text.type.tsx | 2 +- components/form/admin/types/textarea.type.tsx | 2 +- components/form/admin/types/yes_no.type.tsx | 4 +- components/form/layouts/card/index.tsx | 46 +++++++++++-------- components/form/types/checkbox.type.tsx | 11 ++++- components/form/types/date.type.tsx | 13 ++++-- components/form/types/dropdown.type.tsx | 19 +++++++- components/form/types/email.type.tsx | 19 +++++++- components/form/types/link.type.tsx | 19 +++++++- components/form/types/number.type.tsx | 11 ++++- components/form/types/radio.type.tsx | 11 ++++- components/form/types/rating.type.tsx | 11 ++++- components/form/types/slider.type.tsx | 33 +++++++++---- components/form/types/text.type.tsx | 19 +++++++- components/form/types/textarea.type.tsx | 19 +++++++- components/form/types/yes_no.type.tsx | 16 +++++-- components/structure.tsx | 12 ++--- components/use.math.ts | 17 +++++-- graphql/fragment/form.fragment.ts | 4 +- graphql/fragment/form.public.fragment.ts | 4 +- graphql/query/submission.pager.query.ts | 8 +++- package.json | 2 +- pages/admin/forms/[id]/index.tsx | 15 ++++-- pages/admin/forms/[id]/submissions.tsx | 3 ++ pages/admin/forms/index.tsx | 18 ++++---- pages/admin/users/index.tsx | 2 +- yarn.lock | 20 ++------ 40 files changed, 282 insertions(+), 137 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b4bc3..3470ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - new slider field type - new card layout for forms - field logic -- add enviroment config +- add environment config - anonymous form submissions (fixes https://github.com/ohmyform/ohmyform/issues/108) - checkbox field type (fixed https://github.com/ohmyform/ohmyform/issues/138) @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - use exported hooks for graphql - disable swipe gesture - upgrade to nextjs 12 +- change default value from value to defaultValue +- handle options and values as json correctly +- exclude empty submissions per default (https://github.com/ohmyform/ohmyform/issues/153) ### Fixed @@ -153,11 +156,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - `export` uses now spa mode for initial loading screen +- change value to defaultValue for initial form ### Fixed -- [OMF#93](https://github.com/ohmyform/ohmyform/issues/93) dropdown options are not saved +- dropdown options are not saved (https://github.com/ohmyform/ohmyform/issues/93) - redirect attempts on static export +- date can now be prefilled by url ## [0.9.2] - 2020-06-04 diff --git a/components/form/admin/fields.tab.tsx b/components/form/admin/fields.tab.tsx index 6112a45..3ab66cd 100644 --- a/components/form/admin/fields.tab.tsx +++ b/components/form/admin/fields.tab.tsx @@ -78,7 +78,6 @@ export const FieldsTab: React.FC = (props) => { title: '', description: '', required: false, - value: '', } add(defaults) diff --git a/components/form/admin/logic.block.tsx b/components/form/admin/logic.block.tsx index a7bff3c..7f806e5 100644 --- a/components/form/admin/logic.block.tsx +++ b/components/form/admin/logic.block.tsx @@ -37,7 +37,7 @@ export const LogicBlock: React.FC = ({ labelCol={{ span: 6 }} label={'Formula'} rules={[{ required: true, message: 'combine other fields' }]} - extra={'Save form to get new IDs and slugs'} + extra={'Save form to get new @IDs and $slugs. (example: $slug < 21 or @id = 42)'} > {fields.map((field) => ( @@ -54,10 +54,10 @@ export const LogicBlock: React.FC = ({ const defaults = {} fields.forEach((field) => { - defaults[`@${field.id}`] = field.value + defaults[`@${field.id}`] = field.defaultValue if (field.slug) { - defaults[`$${field.slug}`] = field.value + defaults[`$${field.slug}`] = field.defaultValue } }) diff --git a/components/form/admin/types/checkbox.type.tsx b/components/form/admin/types/checkbox.type.tsx index 1a07e8f..81db3a3 100644 --- a/components/form/admin/types/checkbox.type.tsx +++ b/components/form/admin/types/checkbox.type.tsx @@ -10,7 +10,7 @@ export const CheckboxType: React.FC = (props) => {
diff --git a/components/form/admin/types/date.type.tsx b/components/form/admin/types/date.type.tsx index 2a6b6a4..d8d8463 100644 --- a/components/form/admin/types/date.type.tsx +++ b/components/form/admin/types/date.type.tsx @@ -11,7 +11,7 @@ export const DateType: React.FC = ({ field }) => {
(e ? e.format('YYYY-MM-DD') : undefined)} getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })} diff --git a/components/form/admin/types/dropdown.type.tsx b/components/form/admin/types/dropdown.type.tsx index 5a29b34..012dfbd 100644 --- a/components/form/admin/types/dropdown.type.tsx +++ b/components/form/admin/types/dropdown.type.tsx @@ -10,7 +10,7 @@ export const DropdownType: React.FC = (props) => {
diff --git a/components/form/admin/types/email.type.tsx b/components/form/admin/types/email.type.tsx index 752c105..b833492 100644 --- a/components/form/admin/types/email.type.tsx +++ b/components/form/admin/types/email.type.tsx @@ -10,7 +10,7 @@ export const EmailType: React.FC = (props) => {
diff --git a/components/form/admin/types/hidden.type.tsx b/components/form/admin/types/hidden.type.tsx index 4fdb71b..b36e03b 100644 --- a/components/form/admin/types/hidden.type.tsx +++ b/components/form/admin/types/hidden.type.tsx @@ -10,7 +10,7 @@ export const HiddenType: React.FC = (props) => {
diff --git a/components/form/admin/types/link.type.tsx b/components/form/admin/types/link.type.tsx index 77dee7f..1a46ae6 100644 --- a/components/form/admin/types/link.type.tsx +++ b/components/form/admin/types/link.type.tsx @@ -10,7 +10,7 @@ export const LinkType: React.FC = (props) => {
diff --git a/components/form/admin/types/number.type.tsx b/components/form/admin/types/number.type.tsx index ac624b0..e9e48c6 100644 --- a/components/form/admin/types/number.type.tsx +++ b/components/form/admin/types/number.type.tsx @@ -10,12 +10,8 @@ export const NumberType: React.FC = (props) => {
- typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })} > diff --git a/components/form/admin/types/radio.type.tsx b/components/form/admin/types/radio.type.tsx index 7f0b93a..47f6451 100644 --- a/components/form/admin/types/radio.type.tsx +++ b/components/form/admin/types/radio.type.tsx @@ -10,7 +10,7 @@ export const RadioType: React.FC = (props) => {
diff --git a/components/form/admin/types/rating.type.tsx b/components/form/admin/types/rating.type.tsx index d0f7f7e..9c8b2ee 100644 --- a/components/form/admin/types/rating.type.tsx +++ b/components/form/admin/types/rating.type.tsx @@ -10,13 +10,9 @@ export const RatingType: React.FC = (props) => {
- typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })} > diff --git a/components/form/admin/types/slider.type.tsx b/components/form/admin/types/slider.type.tsx index 5848a09..ef86b99 100644 --- a/components/form/admin/types/slider.type.tsx +++ b/components/form/admin/types/slider.type.tsx @@ -35,11 +35,8 @@ export const SliderType: React.FC = (props) => { return ( - typeof value === 'number' ? value.toFixed(2) : value - } getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })} > @@ -57,10 +54,6 @@ export const SliderType: React.FC = (props) => { ]} labelCol={{ span: 6 }} initialValue={0} - getValueFromEvent={(value: number) => - typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })} > @@ -74,10 +67,6 @@ export const SliderType: React.FC = (props) => { ]} labelCol={{ span: 6 }} initialValue={100} - getValueFromEvent={(value: number) => - typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })} > @@ -91,10 +80,6 @@ export const SliderType: React.FC = (props) => { ]} labelCol={{ span: 6 }} initialValue={1} - getValueFromEvent={(value: number) => - typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })} > diff --git a/components/form/admin/types/text.type.tsx b/components/form/admin/types/text.type.tsx index da8fd05..6c2253e 100644 --- a/components/form/admin/types/text.type.tsx +++ b/components/form/admin/types/text.type.tsx @@ -9,7 +9,7 @@ export const TextType: React.FC = (props) => { return ( diff --git a/components/form/admin/types/textarea.type.tsx b/components/form/admin/types/textarea.type.tsx index 5e63c0f..11c4f0b 100644 --- a/components/form/admin/types/textarea.type.tsx +++ b/components/form/admin/types/textarea.type.tsx @@ -10,7 +10,7 @@ export const TextareaType: React.FC = (props) => {
diff --git a/components/form/admin/types/yes_no.type.tsx b/components/form/admin/types/yes_no.type.tsx index 35be5f4..a39633d 100644 --- a/components/form/admin/types/yes_no.type.tsx +++ b/components/form/admin/types/yes_no.type.tsx @@ -10,11 +10,9 @@ export const YesNoType: React.FC = (props) => {
(checked ? '1' : '')} - getValueProps={(e: string) => ({ checked: !!e })} > diff --git a/components/form/layouts/card/index.tsx b/components/form/layouts/card/index.tsx index 178e031..3d9dbf1 100644 --- a/components/form/layouts/card/index.tsx +++ b/components/form/layouts/card/index.tsx @@ -1,6 +1,7 @@ import { Card, Form, message, Modal, Spin } from 'antd' +import debug from 'debug' import { darken, lighten } from 'polished' -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { FormPublicFieldFragment } from '../../../../graphql/fragment/form.public.fragment' @@ -13,6 +14,8 @@ import { Page } from './page' type Step = 'start' | 'form' | 'end' +const logger = debug('layout/card') + const MyCard = styled.div<{ background: string }>` background: ${(props) => darken(0.1, props.background)}; height: 100%; @@ -41,8 +44,28 @@ export const CardLayout: React.FC = (props) => { const { design, startPage, endPage, fields } = props.form const { setField } = props.submission + const updateValues = useCallback(() => { + const defaults = {} + + fields.forEach(field => { + const defaultValue = field.defaultValue ? JSON.parse(field.defaultValue) : null + + defaults[`@${field.id}`] = form.getFieldValue([field.id, 'value']) ?? defaultValue + + if (field.slug) { + defaults[`$${field.slug}`] = form.getFieldValue([field.id, 'value']) ?? defaultValue + } + }) + + setValues(defaults) + }, [fields, form]) + + useEffect(() => { + updateValues() + }, [updateValues]) + const finish = async (data: { [key: number]: unknown }) => { - console.log('data', data) + logger('finish form %O', data) setLoading(true) try { @@ -63,7 +86,7 @@ export const CardLayout: React.FC = (props) => { }) } } catch (e) { - console.error(e) + logger('failed to finish form %O', e) void message.error({ content: 'Error saving Input', }) @@ -75,8 +98,6 @@ export const CardLayout: React.FC = (props) => { const isVisible = useCallback((field: FormPublicFieldFragment): boolean => { if (!field.logic) return true - console.log('DEFAULTS', values) - return field.logic .filter(logic => logic.action === 'visible') .map(logic => { @@ -86,7 +107,6 @@ export const CardLayout: React.FC = (props) => { values ) - console.log('result', r) return Boolean(r) } catch { return true @@ -108,19 +128,7 @@ export const CardLayout: React.FC = (props) => {
{ - const defaults = {} - - fields.forEach(field => { - defaults[`@${field.id}`] = form.getFieldValue([field.id, 'value']) - - if (field.slug) { - defaults[`$${field.slug}`] = form.getFieldValue([field.id, 'value']) - } - }) - - setValues(defaults) - }} + onValuesChange={updateValues} > {fields.map((field, i) => { if (field.type === 'hidden') { diff --git a/components/form/types/checkbox.type.tsx b/components/form/types/checkbox.type.tsx index 6b68513..36ff904 100644 --- a/components/form/types/checkbox.type.tsx +++ b/components/form/types/checkbox.type.tsx @@ -1,16 +1,23 @@ import { Checkbox, Form } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { StyledCheckbox } from '../../styled/checkbox' import { FieldTypeProps } from './type.props' +const logger = debug('field/checkbox') + export const CheckboxType: React.FC = ({ field, design, urlValue }) => { const { t } = useTranslation() let initialValue: string = undefined - if (field.value) { - initialValue = field.value + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { diff --git a/components/form/types/date.type.tsx b/components/form/types/date.type.tsx index bfe876c..a57d1bd 100644 --- a/components/form/types/date.type.tsx +++ b/components/form/types/date.type.tsx @@ -1,11 +1,14 @@ import { Form } from 'antd' import dayjs, { Dayjs } from 'dayjs' +import debug from 'debug' import moment, { Moment } from 'moment' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { StyledDateInput } from '../../styled/date.input' import { FieldTypeProps } from './type.props' +const logger = debug('field/date') + export const DateType: React.FC = ({ field, design, urlValue, focus }) => { const [min, setMin] = useState() const [max, setMax] = useState() @@ -24,12 +27,16 @@ export const DateType: React.FC = ({ field, design, urlValue, fo let initialValue: Moment = undefined - if (field.value) { - initialValue = moment(field.value) + if (field.defaultValue) { + try { + initialValue = moment(JSON.parse(field.defaultValue)) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { - initialValue = moment(field.value) + initialValue = moment(urlValue) } return ( diff --git a/components/form/types/dropdown.type.tsx b/components/form/types/dropdown.type.tsx index a650df4..950eb49 100644 --- a/components/form/types/dropdown.type.tsx +++ b/components/form/types/dropdown.type.tsx @@ -1,19 +1,36 @@ import { Form, Select } from 'antd' +import debug from 'debug' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { StyledSelect } from '../../styled/select' import { FieldTypeProps } from './type.props' +const logger = debug('field/dropdown') + export const DropdownType: React.FC = ({ field, design, urlValue, focus }) => { const [open, setOpen] = useState(false) const { t } = useTranslation() + let initialValue = null + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } + + if (urlValue) { + initialValue = urlValue + } + return (
= ({ field, design, urlValue, focus }) => { const { t } = useTranslation() + let initialValue = null + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } + + if (urlValue) { + initialValue = urlValue + } + return (
= ({ field, design, urlValue, f { required: field.required, message: t('validation:valueRequired') }, { type: 'email', message: t('validation:invalidEmail') }, ]} - initialValue={urlValue || field.value} + initialValue={initialValue} > diff --git a/components/form/types/link.type.tsx b/components/form/types/link.type.tsx index 372dc0b..0396cfb 100644 --- a/components/form/types/link.type.tsx +++ b/components/form/types/link.type.tsx @@ -1,12 +1,29 @@ import { Form } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { StyledInput } from '../../styled/input' import { FieldTypeProps } from './type.props' +const logger = debug('field/link') + export const LinkType: React.FC = ({ field, design, urlValue, focus }) => { const { t } = useTranslation() + let initialValue = null + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } + + if (urlValue) { + initialValue = urlValue + } + return (
= ({ field, design, urlValue, fo { required: field.required, message: t('validation:valueRequired') }, { type: 'url', message: t('validation:invalidUrl') }, ]} - initialValue={urlValue || field.value} + initialValue={initialValue} > diff --git a/components/form/types/number.type.tsx b/components/form/types/number.type.tsx index 70a4cd0..03d245b 100644 --- a/components/form/types/number.type.tsx +++ b/components/form/types/number.type.tsx @@ -1,16 +1,23 @@ import { Form } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { StyledNumberInput } from '../../styled/number.input' import { FieldTypeProps } from './type.props' +const logger = debug('field/number') + export const NumberType: React.FC = ({ field, design, urlValue, focus }) => { const { t } = useTranslation() let initialValue: number = undefined - if (field.value) { - initialValue = parseFloat(field.value) + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { diff --git a/components/form/types/radio.type.tsx b/components/form/types/radio.type.tsx index f88746f..4cc1b9e 100644 --- a/components/form/types/radio.type.tsx +++ b/components/form/types/radio.type.tsx @@ -1,16 +1,23 @@ import { Form, Radio } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { StyledRadio } from '../../styled/radio' import { FieldTypeProps } from './type.props' +const logger = debug('field/radio') + export const RadioType: React.FC = ({ field, design, urlValue }) => { const { t } = useTranslation() let initialValue: string = undefined - if (field.value) { - initialValue = field.value + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { diff --git a/components/form/types/rating.type.tsx b/components/form/types/rating.type.tsx index aab7781..a19bad8 100644 --- a/components/form/types/rating.type.tsx +++ b/components/form/types/rating.type.tsx @@ -1,15 +1,22 @@ import { Form, Rate } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { FieldTypeProps } from './type.props' +const logger = debug('field/rating') + export const RatingType: React.FC = ({ field, urlValue }) => { const { t } = useTranslation() let initialValue: number = undefined - if (field.value) { - initialValue = parseFloat(field.value) + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { diff --git a/components/form/types/slider.type.tsx b/components/form/types/slider.type.tsx index 2aa61d7..34eceeb 100644 --- a/components/form/types/slider.type.tsx +++ b/components/form/types/slider.type.tsx @@ -1,8 +1,11 @@ import { Form, Slider, Spin } from 'antd' +import debug from 'debug' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { FieldTypeProps } from './type.props' +const logger = debug('field/slider') + export const SliderType: React.FC = ({ field, urlValue }) => { const [min, setMin] = useState() const [max, setMax] = useState() @@ -14,13 +17,25 @@ export const SliderType: React.FC = ({ field, urlValue }) => { useEffect(() => { field.options.forEach((option) => { if (option.key === 'min') { - setMin(parseFloat(option.value)) + try { + setMin(JSON.parse(option.value)) + } catch (e) { + logger('invalid min value %O', e) + } } if (option.key === 'max') { - setMax(parseFloat(option.value)) + try { + setMax(JSON.parse(option.value)) + } catch (e) { + logger('invalid max value %O', e) + } } if (option.key === 'step') { - setStep(parseFloat(option.value)) + try { + setStep(JSON.parse(option.value)) + } catch (e) { + logger('invalid step value %O', e) + } } }) @@ -29,8 +44,12 @@ export const SliderType: React.FC = ({ field, urlValue }) => { let initialValue: number = undefined - if (field.value) { - initialValue = parseFloat(field.value) + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } } if (urlValue) { @@ -51,10 +70,6 @@ export const SliderType: React.FC = ({ field, urlValue }) => { name={[field.id, 'value']} rules={[{ required: field.required, message: t('validation:valueRequired') }]} initialValue={initialValue} - getValueFromEvent={(value: number) => - typeof value === 'number' ? value.toFixed(2) : value - } - getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })} > = ({ field, design, urlValue, focus }) => { const { t } = useTranslation() // TODO focus when becomes visible + let initialValue = undefined + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } + + if (urlValue) { + initialValue = urlValue + } + return (
diff --git a/components/form/types/textarea.type.tsx b/components/form/types/textarea.type.tsx index 4c6f1db..7102ebe 100644 --- a/components/form/types/textarea.type.tsx +++ b/components/form/types/textarea.type.tsx @@ -1,18 +1,35 @@ import { Form } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { StyledTextareaInput } from '../../styled/textarea.input' import { FieldTypeProps } from './type.props' +const logger = debug('field/textarea') + export const TextareaType: React.FC = ({ field, design, urlValue, focus }) => { const { t } = useTranslation() + let initialValue = undefined + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } + + if (urlValue) { + initialValue = urlValue + } + return (
diff --git a/components/form/types/yes_no.type.tsx b/components/form/types/yes_no.type.tsx index 10c78cf..ac9313b 100644 --- a/components/form/types/yes_no.type.tsx +++ b/components/form/types/yes_no.type.tsx @@ -1,12 +1,24 @@ import { Form, Switch } from 'antd' +import debug from 'debug' import React from 'react' import { useTranslation } from 'react-i18next' import { FieldTypeProps } from './type.props' +const logger = debug('field/link') + export const YesNoType: React.FC = ({ field, urlValue }) => { const { t } = useTranslation() - let initialValue = !!field.value + + let initialValue: boolean = undefined + + if (field.defaultValue) { + try { + initialValue = JSON.parse(field.defaultValue) + } catch (e) { + logger('invalid default value %O', e) + } + } if (urlValue !== undefined) { initialValue = !!urlValue @@ -19,8 +31,6 @@ export const YesNoType: React.FC = ({ field, urlValue }) => { rules={[{ required: field.required, message: t('validation:valueRequired') }]} initialValue={initialValue} valuePropName={'checked'} - getValueFromEvent={(checked: boolean) => (checked ? '1' : '')} - getValueProps={(e: string) => ({ checked: !!e })} > diff --git a/components/structure.tsx b/components/structure.tsx index ef58b06..df8fc5d 100644 --- a/components/structure.tsx +++ b/components/structure.tsx @@ -185,9 +185,9 @@ export const Structure: FunctionComponent = (props) => { - router.push('/admin/profile')}>Profile - - Logout + router.push('/admin/profile')}>Profile + + Logout } onVisibleChange={setUserMenu} @@ -237,7 +237,7 @@ export const Structure: FunctionComponent = (props) => { {buildMenu(sideMenu)} - + - + - + Version: {process.env.version} diff --git a/components/use.math.ts b/components/use.math.ts index 1f0b4f5..2273492 100644 --- a/components/use.math.ts +++ b/components/use.math.ts @@ -1,6 +1,5 @@ import debug from 'debug' -import { all, create } from 'mathjs' -import { useState } from 'react' +import { formula, init } from 'expressionparser' const logger = debug('useMath') @@ -8,9 +7,15 @@ export const useMath = (): (( expression: string, values?: { [id: string]: string | number } ) => boolean) => { - const [math] = useState(create(all, {})) - return (expression, values) => { + const parser = init(formula, (term: string) => { + if (values[term]) { + return values[term] + } + + throw new Error(`Invalid term: ${term}`); + }) + try { let processed = expression @@ -24,7 +29,9 @@ export const useMath = (): (( } }) - return Boolean(math.evaluate(processed)) + parser.expressionToValue(expression) + + return Boolean(parser.expressionToValue(expression)) } catch (e) { logger( 'failed to calculate %O: %s', diff --git a/graphql/fragment/form.fragment.ts b/graphql/fragment/form.fragment.ts index 21296a5..16db857 100644 --- a/graphql/fragment/form.fragment.ts +++ b/graphql/fragment/form.fragment.ts @@ -46,7 +46,7 @@ export interface FormFieldFragment { type: string description: string required: boolean - value: string + defaultValue?: string options: FormFieldOptionFragment[] optionKeys?: FormFieldOptionKeysFragment @@ -135,7 +135,7 @@ export const FORM_FRAGMENT = gql` type description required - value + defaultValue options { id diff --git a/graphql/fragment/form.public.fragment.ts b/graphql/fragment/form.public.fragment.ts index ae1514e..5e08d43 100644 --- a/graphql/fragment/form.public.fragment.ts +++ b/graphql/fragment/form.public.fragment.ts @@ -42,7 +42,7 @@ export interface FormPublicFieldFragment { type: string description: string required: boolean - value: string + defaultValue: string options: FormPublicFieldOptionFragment[] @@ -93,7 +93,7 @@ export const FORM_PUBLIC_FRAGMENT = gql` type description required - value + defaultValue logic { id diff --git a/graphql/query/submission.pager.query.ts b/graphql/query/submission.pager.query.ts index 0d3de61..972f160 100644 --- a/graphql/query/submission.pager.query.ts +++ b/graphql/query/submission.pager.query.ts @@ -20,15 +20,19 @@ interface Variables { form: string start?: number limit?: number + filter?: { + finished?: boolean + excludeEmpty?: boolean + } } const QUERY = gql` - query listSubmissions($form: ID!, $start: Int, $limit: Int) { + query listSubmissions($form: ID!, $start: Int, $limit: Int, $filter: SubmissionPagerFilterInput) { form: getFormById(id: $form) { ...PagerForm } - pager: listSubmissions(form: $form, start: $start, limit: $limit) { + pager: listSubmissions(form: $form, start: $start, limit: $limit, filter: $filter) { entries { ...Submission } diff --git a/package.json b/package.json index 3a58faa..73ccaba 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ "dayjs": "^1.10.7", "debug": "^4.3.3", "exceljs": "^4.3.0", + "expressionparser": "^1.1.5", "graphql": "^16.3.0", "i18next": "^21.6.12", "i18next-browser-languagedetector": "^6.1.3", "imagemin-optipng": "^8.0.0", "isomorphic-fetch": "^3.0.0", "jimp": "^0.16.1", - "mathjs": "^10.1.1", "next": "^12.1.0", "next-compose-plugins": "^2.2.1", "next-optimized-images": "^2.6.2", diff --git a/pages/admin/forms/[id]/index.tsx b/pages/admin/forms/[id]/index.tsx index 9f95dc5..34960d0 100644 --- a/pages/admin/forms/[id]/index.tsx +++ b/pages/admin/forms/[id]/index.tsx @@ -9,6 +9,7 @@ import { NotificationsTab } from 'components/form/admin/notifications.tab' import { StartPageTab } from 'components/form/admin/start.page.tab' import { Structure } from 'components/structure' import { withAuth } from 'components/with.auth' +import debug from 'debug' import { useFormUpdateMutation } from 'graphql/mutation/form.update.mutation' import { NextPage } from 'next' import Link from 'next/link' @@ -22,6 +23,8 @@ import { } from '../../../../graphql/fragment/form.fragment' import { Data, useFormQuery } from '../../../../graphql/query/form.query' +const logger = debug('page/admin/form/[id]') + const Index: NextPage = () => { const { t } = useTranslation() const router = useRouter() @@ -40,12 +43,17 @@ const Index: NextPage = () => { field.options.forEach((option) => { if (option.key) { - keys[option.key] = option.value + try { + keys[option.key] = JSON.parse(option.value) + } catch (e) { + logger('invalid option value %O', e) + } } }) return { ...field, + defaultValue: field.defaultValue ? JSON.parse(field.defaultValue) : null, options: field.options.filter((option) => !option.key), optionKeys: keys, } @@ -76,13 +84,13 @@ const Index: NextPage = () => { if (optionKeys) { Object.keys(optionKeys).forEach((key) => { - if (!optionKeys[key]) { + if (optionKeys[key] === undefined) { return } options.push({ id: null, // TODO improve this - value: optionKeys[key], + value: JSON.stringify(optionKeys[key]), key, }) }) @@ -90,6 +98,7 @@ const Index: NextPage = () => { return { ...field, + defaultValue: field.defaultValue !== null ? JSON.stringify(field.defaultValue) : null, options, idx: index, } diff --git a/pages/admin/forms/[id]/submissions.tsx b/pages/admin/forms/[id]/submissions.tsx index 2ee4b6a..7bd53c7 100644 --- a/pages/admin/forms/[id]/submissions.tsx +++ b/pages/admin/forms/[id]/submissions.tsx @@ -31,6 +31,9 @@ const Submissions: NextPage = () => { form: router.query.id as string, limit: pagination.pageSize, start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0, + filter: { + excludeEmpty: true, + }, }, onCompleted: ({ pager, form }) => { setPagination({ diff --git a/pages/admin/forms/index.tsx b/pages/admin/forms/index.tsx index c70b447..72b25b0 100644 --- a/pages/admin/forms/index.tsx +++ b/pages/admin/forms/index.tsx @@ -85,11 +85,11 @@ const Index: NextPage = () => { } return ( - - + + - - + + ) }, responsive: ['lg'], @@ -124,15 +124,15 @@ const Index: NextPage = () => { render(_, row) { return ( - - + + - - + + - + diff --git a/pages/admin/users/index.tsx b/pages/admin/users/index.tsx index f7b357a..899ac21 100644 --- a/pages/admin/users/index.tsx +++ b/pages/admin/users/index.tsx @@ -85,7 +85,7 @@ const Index: NextPage = () => { render(_, row) { return ( - + diff --git a/yarn.lock b/yarn.lock index 16e4005..34ada73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2339,6 +2339,11 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expressionparser@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/expressionparser/-/expressionparser-1.1.5.tgz#b16bc45e7557be437c2259e2ab0abd8b857e979b" + integrity sha512-9GldsRvXhcJ+ZPiK7fel5KBpgU+LjjQjnyWQVugJcvRc99lXTBICwXKaQV2laZ1KUlTVUBnMmFpgtkMVwwMiHA== + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -3713,21 +3718,6 @@ mathjs@*: tiny-emitter "^2.1.0" typed-function "^2.0.0" -mathjs@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-10.1.1.tgz#99b647387b65c4b5c47b71e11d59481473ccfa0d" - integrity sha512-4QJP8a0Vy90ajFYESnITSluCrQBZnI+2XQhKJIRdo/6t95oupffS5qA4MTWnLGm5GsEZF179JSMjST7wCdZQkA== - dependencies: - "@babel/runtime" "^7.16.7" - complex.js "^2.0.15" - decimal.js "^10.3.1" - escape-latex "^1.2.0" - fraction.js "^4.1.2" - javascript-natural-sort "^0.7.1" - seedrandom "^3.0.5" - tiny-emitter "^2.1.0" - typed-function "^2.0.0" - mdast-util-definitions@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817"