mirror of
https://github.com/IT4Change/ohmyform-ui.git
synced 2026-01-20 19:31:17 +00:00
upgrade packages, improve field logic, fix slider, hide empty submissions, fix urls for buttons, improve data handling for fields, improve sqlite migration handling
This commit is contained in:
parent
e33b3ff392
commit
ca5edbbb3b
@ -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
|
||||
|
||||
|
||||
@ -78,7 +78,6 @@ export const FieldsTab: React.FC<Props> = (props) => {
|
||||
title: '',
|
||||
description: '',
|
||||
required: false,
|
||||
value: '',
|
||||
}
|
||||
|
||||
add(defaults)
|
||||
|
||||
@ -37,7 +37,7 @@ export const LogicBlock: React.FC<Props> = ({
|
||||
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)'}
|
||||
>
|
||||
<Mentions rows={1}>
|
||||
{fields.map((field) => (
|
||||
@ -54,10 +54,10 @@ export const LogicBlock: React.FC<Props> = ({
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ export const CheckboxType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:checkbox:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@ -11,7 +11,7 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({ field }) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:date.default')}
|
||||
name={[field.name as string, 'value']}
|
||||
name={[field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
getValueFromEvent={(e: Moment) => (e ? e.format('YYYY-MM-DD') : undefined)}
|
||||
getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })}
|
||||
|
||||
@ -10,7 +10,7 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:dropdown.default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@ -10,7 +10,7 @@ export const EmailType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:email.default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
rules={[{ type: 'email', message: t('validation:emailRequired') }]}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
|
||||
@ -10,7 +10,7 @@ export const HiddenType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:hidden.default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@ -10,7 +10,7 @@ export const LinkType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:link.default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
|
||||
@ -10,12 +10,8 @@ export const NumberType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:number:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
|
||||
@ -10,7 +10,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:radio:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@ -10,13 +10,9 @@ export const RatingType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:rating:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
extra={t('type:rating.clearNote')}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })}
|
||||
>
|
||||
<Rate allowHalf allowClear />
|
||||
</Form.Item>
|
||||
|
||||
@ -35,11 +35,8 @@ export const SliderType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('type:slider.default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(value: string) => ({ value: value ? parseFloat(value) : undefined })}
|
||||
>
|
||||
<Slider min={min} max={max} step={step} dots={(max - min) / step <= 10} />
|
||||
@ -57,10 +54,6 @@ export const SliderType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
]}
|
||||
labelCol={{ span: 6 }}
|
||||
initialValue={0}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@ -74,10 +67,6 @@ export const SliderType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
]}
|
||||
labelCol={{ span: 6 }}
|
||||
initialValue={100}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@ -91,10 +80,6 @@ export const SliderType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
]}
|
||||
labelCol={{ span: 6 }}
|
||||
initialValue={1}
|
||||
getValueFromEvent={(value: number) =>
|
||||
typeof value === 'number' ? value.toFixed(2) : value
|
||||
}
|
||||
getValueProps={(e: string) => ({ value: e ? parseFloat(e) : undefined })}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
|
||||
@ -9,7 +9,7 @@ export const TextType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('type:textfield:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@ -10,7 +10,7 @@ export const TextareaType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:textarea:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
>
|
||||
<Input.TextArea autoSize />
|
||||
|
||||
@ -10,11 +10,9 @@ export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t('type:yes_no:default')}
|
||||
name={[props.field.name as string, 'value']}
|
||||
name={[props.field.name as string, 'defaultValue']}
|
||||
labelCol={{ span: 6 }}
|
||||
valuePropName={'checked'}
|
||||
getValueFromEvent={(checked: boolean) => (checked ? '1' : '')}
|
||||
getValueProps={(e: string) => ({ checked: !!e })}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
@ -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<LayoutProps> = (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<LayoutProps> = (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<LayoutProps> = (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<LayoutProps> = (props) => {
|
||||
values
|
||||
)
|
||||
|
||||
console.log('result', r)
|
||||
return Boolean(r)
|
||||
} catch {
|
||||
return true
|
||||
@ -108,19 +128,7 @@ export const CardLayout: React.FC<LayoutProps> = (props) => {
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={finish}
|
||||
onValuesChange={() => {
|
||||
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') {
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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) {
|
||||
|
||||
@ -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<FieldTypeProps> = ({ field, design, urlValue, focus }) => {
|
||||
const [min, setMin] = useState<Dayjs>()
|
||||
const [max, setMax] = useState<Dayjs>()
|
||||
@ -24,12 +27,16 @@ export const DateType: React.FC<FieldTypeProps> = ({ 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 (
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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 (
|
||||
<div>
|
||||
<Form.Item
|
||||
name={[field.id, 'value']}
|
||||
rules={[{ required: field.required, message: t('validation:valueRequired') }]}
|
||||
initialValue={urlValue || field.value || null}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<StyledSelect
|
||||
autoFocus={focus}
|
||||
|
||||
@ -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/email')
|
||||
|
||||
export const EmailType: React.FC<FieldTypeProps> = ({ 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 (
|
||||
<div>
|
||||
<Form.Item
|
||||
@ -15,7 +32,7 @@ export const EmailType: React.FC<FieldTypeProps> = ({ field, design, urlValue, f
|
||||
{ required: field.required, message: t('validation:valueRequired') },
|
||||
{ type: 'email', message: t('validation:invalidEmail') },
|
||||
]}
|
||||
initialValue={urlValue || field.value}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<StyledInput autoFocus={focus} design={design} allowClear size={'large'} />
|
||||
</Form.Item>
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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 (
|
||||
<div>
|
||||
<Form.Item
|
||||
@ -15,7 +32,7 @@ export const LinkType: React.FC<FieldTypeProps> = ({ field, design, urlValue, fo
|
||||
{ required: field.required, message: t('validation:valueRequired') },
|
||||
{ type: 'url', message: t('validation:invalidUrl') },
|
||||
]}
|
||||
initialValue={urlValue || field.value}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<StyledInput autoFocus={focus} design={design} allowClear size={'large'} />
|
||||
</Form.Item>
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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) {
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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) {
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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) {
|
||||
|
||||
@ -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<FieldTypeProps> = ({ field, urlValue }) => {
|
||||
const [min, setMin] = useState<number>()
|
||||
const [max, setMax] = useState<number>()
|
||||
@ -14,13 +17,25 @@ export const SliderType: React.FC<FieldTypeProps> = ({ 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<FieldTypeProps> = ({ 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<FieldTypeProps> = ({ 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 })}
|
||||
>
|
||||
<Slider
|
||||
min={min}
|
||||
|
||||
@ -1,19 +1,36 @@
|
||||
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/text')
|
||||
|
||||
export const TextType: React.FC<FieldTypeProps> = ({ 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 (
|
||||
<div>
|
||||
<Form.Item
|
||||
name={[field.id, 'value']}
|
||||
rules={[{ required: field.required, message: t('validation:valueRequired') }]}
|
||||
initialValue={urlValue || field.value}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<StyledInput autoFocus={focus} design={design} allowClear size={'large'} />
|
||||
</Form.Item>
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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 (
|
||||
<div>
|
||||
<Form.Item
|
||||
name={[field.id, 'value']}
|
||||
rules={[{ required: field.required, message: t('validation:valueRequired') }]}
|
||||
initialValue={urlValue || field.value}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<StyledTextareaInput autoFocus={focus} design={design} allowClear autoSize />
|
||||
</Form.Item>
|
||||
|
||||
@ -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<FieldTypeProps> = ({ 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<FieldTypeProps> = ({ field, urlValue }) => {
|
||||
rules={[{ required: field.required, message: t('validation:valueRequired') }]}
|
||||
initialValue={initialValue}
|
||||
valuePropName={'checked'}
|
||||
getValueFromEvent={(checked: boolean) => (checked ? '1' : '')}
|
||||
getValueProps={(e: string) => ({ checked: !!e })}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
@ -185,9 +185,9 @@ export const Structure: FunctionComponent<Props> = (props) => {
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item onClick={signOut}>Logout</Menu.Item>
|
||||
<Menu.Item key={'profile'} onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
|
||||
<Menu.Divider key={'d1'} />
|
||||
<Menu.Item key={'logout'} onClick={signOut}>Logout</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
onVisibleChange={setUserMenu}
|
||||
@ -237,7 +237,7 @@ export const Structure: FunctionComponent<Props> = (props) => {
|
||||
{buildMenu(sideMenu)}
|
||||
</Menu>
|
||||
<Menu mode="inline" selectable={false}>
|
||||
<Menu.Item className={'language-selector'}>
|
||||
<Menu.Item className={'language-selector'} key={'language-selector'}>
|
||||
<Select
|
||||
bordered={false}
|
||||
value={i18n.language.replace(/-.*/, '')}
|
||||
@ -253,10 +253,10 @@ export const Structure: FunctionComponent<Props> = (props) => {
|
||||
))}
|
||||
</Select>
|
||||
</Menu.Item>
|
||||
<Menu.Item style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Menu.Item style={{ display: 'flex', alignItems: 'center' }} key={'github'}>
|
||||
<GitHubButton type="stargazers" namespace="ohmyform" repo="ohmyform" />
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Menu.Item key={'version'}>
|
||||
Version: <Tag color="gold">{process.env.version}</Tag>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -85,11 +85,11 @@ const Index: NextPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
|
||||
<Tooltip title={user.email}>
|
||||
<Tooltip title={user.email}>
|
||||
<Link href={`/admin/users/${user.id}`} passHref>
|
||||
<Button type={'dashed'}>{user.username}</Button>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
responsive: ['lg'],
|
||||
@ -124,15 +124,15 @@ const Index: NextPage = () => {
|
||||
render(_, row) {
|
||||
return (
|
||||
<Space direction={width < 600 ? 'vertical' : 'horizontal'}>
|
||||
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
|
||||
<Tooltip title={'Show Submissions'}>
|
||||
<Tooltip title={'Show Submissions'}>
|
||||
<Link href={`/admin/forms/${row.id}/submissions`} passHref>
|
||||
<Button>
|
||||
<UnorderedListOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<Link href={'/admin/forms/[id]'} as={`/admin/forms/${row.id}`}>
|
||||
<Link href={`/admin/forms/${row.id}`} passHref>
|
||||
<Button type={'primary'}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
|
||||
@ -85,7 +85,7 @@ const Index: NextPage = () => {
|
||||
render(_, row) {
|
||||
return (
|
||||
<Space direction={width < 600 ? 'vertical' : 'horizontal'}>
|
||||
<Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}>
|
||||
<Link href={`/admin/users/${row.id}`} passHref>
|
||||
<Button type={'primary'}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
|
||||
20
yarn.lock
20
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user