mirror of
https://github.com/IT4Change/ohmyform-ui.git
synced 2025-12-13 09:45:50 +00:00
apply eslint
This commit is contained in:
parent
c2608f047e
commit
8d81390c83
16
.eslintrc.js
16
.eslintrc.js
@ -2,21 +2,25 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaFeatures: { jsx: true }
|
ecmaFeatures: { jsx: true },
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
"plugin:jsx-a11y/recommended",
|
'plugin:jsx-a11y/recommended',
|
||||||
'prettier/@typescript-eslint',
|
'prettier/@typescript-eslint',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': [
|
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
|
||||||
"error", {}, { "usePrettierrc": true }
|
|
||||||
],
|
|
||||||
'react/prop-types': 'off',
|
'react/prop-types': 'off',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'off',
|
||||||
},
|
},
|
||||||
};
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@ -9,4 +9,3 @@ cache:
|
|||||||
script:
|
script:
|
||||||
- yarn
|
- yarn
|
||||||
- yarn lint
|
- yarn lint
|
||||||
- yarn build
|
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import {UpOutlined} from '@ant-design/icons/lib'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import {useQuery} from '@apollo/react-hooks'
|
import { Button, Select } from 'antd'
|
||||||
import {Button, Menu, Select} from 'antd'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {SETTINGS_QUERY, SettingsQueryData} from '../../graphql/query/settings.query'
|
import { SETTINGS_QUERY, SettingsQueryData } from '../../graphql/query/settings.query'
|
||||||
import {languages} from '../../i18n'
|
import { languages } from '../../i18n'
|
||||||
import {clearAuth, withAuth} from '../with.auth'
|
import { clearAuth, withAuth } from '../with.auth'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
me?: {
|
me?: {
|
||||||
@ -16,10 +15,10 @@ interface Props {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthFooterInner: React.FC<Props> = props => {
|
const AuthFooterInner: React.FC<Props> = (props) => {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {data} = useQuery<SettingsQueryData>(SETTINGS_QUERY)
|
const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
clearAuth()
|
clearAuth()
|
||||||
@ -38,67 +37,55 @@ const AuthFooterInner: React.FC<Props> = props => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href={'/admin'}>
|
<Link href={'/admin'}>
|
||||||
<Button
|
<Button type={'link'} ghost>
|
||||||
type={'link'}
|
|
||||||
ghost
|
|
||||||
>
|
|
||||||
{t('admin')}
|
{t('admin')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
{props.me ? (
|
{props.me
|
||||||
[
|
? [
|
||||||
<span style={{color: '#FFF'}} key={'user'}>
|
<span style={{ color: '#FFF' }} key={'user'}>
|
||||||
Hi, {props.me.username}
|
Hi, {props.me.username}
|
||||||
</span>,
|
</span>,
|
||||||
<Button
|
<Button key={'logout'} type={'link'} ghost onClick={logout}>
|
||||||
key={'logout'}
|
{t('logout')}
|
||||||
type={'link'}
|
</Button>,
|
||||||
ghost
|
]
|
||||||
onClick={logout}
|
: [
|
||||||
>
|
<Link href={'/login'} key={'login'}>
|
||||||
{t('logout')}
|
<Button type={'link'} ghost>
|
||||||
</Button>
|
{t('login')}
|
||||||
]
|
</Button>
|
||||||
): (
|
</Link>,
|
||||||
[
|
<Link href={'/register'} key={'register'}>
|
||||||
<Link href={'/login'} key={'login'}>
|
<Button type={'link'} ghost disabled={data ? data.disabledSignUp.value : false}>
|
||||||
<Button
|
{t('register')}
|
||||||
type={'link'}
|
</Button>
|
||||||
ghost
|
</Link>,
|
||||||
>
|
]}
|
||||||
{t('login')}
|
<div style={{ flex: 1 }} />
|
||||||
</Button>
|
|
||||||
</Link>,
|
|
||||||
<Link href={'/register'} key={'register'}>
|
|
||||||
<Button
|
|
||||||
type={'link'}
|
|
||||||
ghost
|
|
||||||
disabled={data ? data.disabledSignUp.value : false}
|
|
||||||
>
|
|
||||||
{t('register')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
]
|
|
||||||
)}
|
|
||||||
<div style={{flex: 1}} />
|
|
||||||
<Select
|
<Select
|
||||||
bordered={false}
|
bordered={false}
|
||||||
value={i18n.language.replace(/-.*/, '')}
|
value={i18n.language.replace(/-.*/, '')}
|
||||||
onChange={next => i18n.changeLanguage(next)}
|
onChange={(next) => i18n.changeLanguage(next)}
|
||||||
style={{
|
style={{
|
||||||
color: '#FFF',
|
color: '#FFF',
|
||||||
}}
|
}}
|
||||||
suffixIcon={false}
|
suffixIcon={false}
|
||||||
>
|
>
|
||||||
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
|
{languages.map((language) => (
|
||||||
|
<Select.Option value={language} key={language}>
|
||||||
|
{t(`language:${language}`)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
type={'link'}
|
type={'link'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
|
rel={'noreferrer'}
|
||||||
ghost
|
ghost
|
||||||
href={'https://www.ohmyform.com'}
|
href={'https://www.ohmyform.com'}
|
||||||
style={{
|
style={{
|
||||||
color: '#FFF'
|
color: '#FFF',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
© OhMyForm
|
© OhMyForm
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
import {Layout, Spin} from 'antd'
|
import { Layout, Spin } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthLayout: React.FC<Props> = props => {
|
export const AuthLayout: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Spin spinning={props.loading}>
|
<Spin spinning={props.loading}>
|
||||||
<Layout style={{
|
<Layout
|
||||||
height: '100vh',
|
style={{
|
||||||
background: '#437fdc'
|
height: '100vh',
|
||||||
}}>
|
background: '#437fdc',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Layout>
|
</Layout>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const omitDeepArrayWalk = (arr, key) => {
|
const omitDeepArrayWalk = (arr, key) => {
|
||||||
return arr.map((val) => {
|
return arr.map((val) => {
|
||||||
if (Array.isArray(val)) return omitDeepArrayWalk(val, key)
|
if (Array.isArray(val)) return omitDeepArrayWalk(val, key)
|
||||||
@ -7,19 +6,24 @@ const omitDeepArrayWalk = (arr, key) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const omitDeep = (obj: any, key: string | number): any => {
|
const omitDeep = (obj: any, key: string | number): any => {
|
||||||
const keys: Array<any> = Object.keys(obj);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const newObj: any = {};
|
const keys: Array<any> = Object.keys(obj)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const newObj: any = {}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
keys.forEach((i: any) => {
|
keys.forEach((i: any) => {
|
||||||
if (i !== key) {
|
if (i !== key) {
|
||||||
const val: any = obj[i];
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if (val instanceof Date) newObj[i] = val;
|
const val: any = obj[i]
|
||||||
else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key);
|
if (val instanceof Date) newObj[i] = val
|
||||||
else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key);
|
else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key)
|
||||||
else newObj[i] = val;
|
else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key)
|
||||||
|
else newObj[i] = val
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return newObj;
|
return newObj
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cleanInput = <T>(obj: T): T => {
|
export const cleanInput = <T>(obj: T): T => {
|
||||||
|
|||||||
@ -7,13 +7,15 @@ interface Props {
|
|||||||
hideTime?: boolean
|
hideTime?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DateTime: React.FC<Props> = props => {
|
export const DateTime: React.FC<Props> = (props) => {
|
||||||
const format = props.hideTime ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm'
|
const format = props.hideTime ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
display: 'inline-block'
|
style={{
|
||||||
}}>
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{dayjs(props.date).format(format)}
|
{dayjs(props.date).format(format)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,13 +2,15 @@ import React from 'react'
|
|||||||
|
|
||||||
export const ErrorPage: React.FC = () => {
|
export const ErrorPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
height: '100vh',
|
style={{
|
||||||
justifyContent: 'center',
|
height: '100vh',
|
||||||
alignItems: 'center',
|
justifyContent: 'center',
|
||||||
display: 'flex',
|
alignItems: 'center',
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
}}>
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<h1>ERROR</h1>
|
<h1>ERROR</h1>
|
||||||
<p>there was an error with your request</p>
|
<p>there was an error with your request</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import {Form, Input, Select, Switch, Tabs} from 'antd'
|
import { Form, Input, Select, Switch, Tabs } from 'antd'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {languages} from '../../../i18n'
|
import { languages } from '../../../i18n'
|
||||||
|
|
||||||
export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
export const BaseDataTab: React.FC<TabPaneProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,7 +41,11 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
|
{languages.map((language) => (
|
||||||
|
<Select.Option value={language} key={language}>
|
||||||
|
{t(`language:${language}`)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -52,7 +56,6 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import {Form, Input, Tabs} from 'antd'
|
import { Form, Input, Tabs } from 'antd'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {InputColor} from '../../input/color'
|
import { InputColor } from '../../input/color'
|
||||||
|
|
||||||
export const DesignTab: React.FC<TabPaneProps> = props => {
|
export const DesignTab: React.FC<TabPaneProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.TabPane {...props}>
|
<Tabs.TabPane {...props}>
|
||||||
<Form.Item
|
<Form.Item label={t('form:design.font')} name={['form', 'design', 'font']}>
|
||||||
label={t('form:design.font')}
|
|
||||||
name={['form', 'design', 'font']}
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -23,8 +20,12 @@ export const DesignTab: React.FC<TabPaneProps> = props => {
|
|||||||
'buttonColor',
|
'buttonColor',
|
||||||
'buttonActiveColor',
|
'buttonActiveColor',
|
||||||
'buttonTextColor',
|
'buttonTextColor',
|
||||||
].map(name => (
|
].map((name) => (
|
||||||
<Form.Item key={name} label={t(`form:design.${name}`)} name={['form', 'design', 'colors', name]}>
|
<Form.Item
|
||||||
|
key={name}
|
||||||
|
label={t(`form:design.${name}`)}
|
||||||
|
name={['form', 'design', 'colors', name]}
|
||||||
|
>
|
||||||
<InputColor />
|
<InputColor />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib'
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons/lib'
|
||||||
import {Button, Card, Form, Input, Switch, Tabs} from 'antd'
|
import { Button, Card, Form, Input, Switch, Tabs } from 'antd'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {InputColor} from '../../input/color'
|
import { InputColor } from '../../input/color'
|
||||||
|
|
||||||
export const EndPageTab: React.FC<TabPaneProps> = props => {
|
export const EndPageTab: React.FC<TabPaneProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -18,17 +18,11 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('form:endPage.title')} name={['form', 'endPage', 'title']}>
|
||||||
label={t('form:endPage.title')}
|
|
||||||
name={['form', 'endPage', 'title']}
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('form:endPage.paragraph')} name={['form', 'endPage', 'paragraph']}>
|
||||||
label={t('form:endPage.paragraph')}
|
|
||||||
name={['form', 'endPage', 'paragraph']}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize />
|
<Input.TextArea autoSize />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -39,54 +33,65 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.List
|
<Form.List name={['form', 'endPage', 'buttons']}>
|
||||||
name={['form', 'endPage', 'buttons']}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{
|
wrapperCol={{
|
||||||
sm: { offset: index === 0 ? 0 : 6 },
|
sm: { offset: index === 0 ? 0 : 6 },
|
||||||
}}
|
}}
|
||||||
label={index === 0 ? t('form:endPage.buttons') : ''}
|
label={index === 0 ? t('form:endPage.buttons') : ''}
|
||||||
key={field.key}
|
key={field.key}
|
||||||
>
|
>
|
||||||
<Card
|
<Card actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}>
|
||||||
actions={[
|
<Form.Item
|
||||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
label={t('form:endPage.url')}
|
||||||
]}
|
name={[field.key, 'url']}
|
||||||
|
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Input />
|
||||||
label={t('form:endPage.url')}
|
</Form.Item>
|
||||||
name={[field.key, 'url']}
|
<Form.Item
|
||||||
rules={[
|
label={t('form:endPage.action')}
|
||||||
{type: 'url', message: t('validation:invalidUrl')}
|
name={[field.key, 'action']}
|
||||||
]}
|
labelCol={{ span: 6 }}
|
||||||
labelCol={{ span: 6 }}
|
>
|
||||||
>
|
<Input />
|
||||||
<Input />
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item label={t('form:endPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
label={t('form:endPage.text')}
|
||||||
<Input />
|
name={[field.key, 'text']}
|
||||||
</Form.Item>
|
labelCol={{ span: 6 }}
|
||||||
<Form.Item label={t('form:endPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('form:endPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
<Form.Item
|
||||||
<InputColor />
|
label={t('form:endPage.bgColor')}
|
||||||
</Form.Item>
|
name={[field.key, 'bgColor']}
|
||||||
<Form.Item label={t('form:endPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
|
labelCol={{ span: 6 }}
|
||||||
<InputColor />
|
>
|
||||||
</Form.Item>
|
<InputColor />
|
||||||
<Form.Item label={t('form:endPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
</Form.Item>
|
||||||
<InputColor />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t('form:endPage.activeColor')}
|
||||||
</Card>
|
name={[field.key, 'activeColor']}
|
||||||
</Form.Item>
|
labelCol={{ span: 6 }}
|
||||||
)
|
>
|
||||||
)}
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t('form:endPage.color')}
|
||||||
|
name={[field.key, 'color']}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{
|
wrapperCol={{
|
||||||
sm: { offset: 6 },
|
sm: { offset: 6 },
|
||||||
@ -95,7 +100,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
add();
|
add()
|
||||||
}}
|
}}
|
||||||
style={{ width: '60%' }}
|
style={{ width: '60%' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,50 +1,47 @@
|
|||||||
import {DeleteOutlined} from '@ant-design/icons/lib'
|
import { DeleteOutlined } from '@ant-design/icons/lib'
|
||||||
import {Button, Card, Checkbox, Form, Input, Popconfirm, Tag} from 'antd'
|
import { Button, Card, Checkbox, Form, Input, Popconfirm, Tag } from 'antd'
|
||||||
import {FormInstance} from 'antd/lib/form'
|
import { FormInstance } from 'antd/lib/form'
|
||||||
import {FieldData} from 'rc-field-form/lib/interface'
|
import { FieldData } from 'rc-field-form/lib/interface'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
|
||||||
import {adminTypes} from './types'
|
import { adminTypes } from './types'
|
||||||
import {TextType} from './types/text.type'
|
import { TextType } from './types/text.type'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
fields: AdminFormFieldFragment[]
|
fields: AdminFormFieldFragment[]
|
||||||
onChangeFields: (fields: AdminFormFieldFragment[]) => any
|
onChangeFields: (fields: AdminFormFieldFragment[]) => void
|
||||||
field: FieldData
|
field: FieldData
|
||||||
remove: (index: number) => void
|
remove: (index: number) => void
|
||||||
index: number
|
index: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldCard: React.FC<Props> = props => {
|
export const FieldCard: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const {
|
const { form, field, fields, onChangeFields, remove, index } = props
|
||||||
form,
|
|
||||||
field,
|
|
||||||
fields,
|
|
||||||
onChangeFields,
|
|
||||||
remove,
|
|
||||||
index,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type'])
|
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type'])
|
||||||
const TypeComponent = adminTypes[type] || TextType
|
const TypeComponent = adminTypes[type] || TextType
|
||||||
|
|
||||||
const [nextTitle, setNextTitle] = useState(form.getFieldValue(['form', 'fields', field.name as string, 'title']))
|
const [nextTitle, setNextTitle] = useState(
|
||||||
|
form.getFieldValue(['form', 'fields', field.name as string, 'title'])
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
onChangeFields(fields.map((field, i) => {
|
onChangeFields(
|
||||||
if (i === index) {
|
fields.map((field, i) => {
|
||||||
return {
|
if (i === index) {
|
||||||
...field,
|
return {
|
||||||
title: nextTitle,
|
...field,
|
||||||
|
title: nextTitle,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return field
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
return field
|
)
|
||||||
}
|
|
||||||
}))
|
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
return () => clearTimeout(id)
|
return () => clearTimeout(id)
|
||||||
@ -54,7 +51,7 @@ export const FieldCard: React.FC<Props> = props => {
|
|||||||
<Card
|
<Card
|
||||||
title={nextTitle}
|
title={nextTitle}
|
||||||
type={'inner'}
|
type={'inner'}
|
||||||
extra={(
|
extra={
|
||||||
<div>
|
<div>
|
||||||
<Tag color={'blue'}>{t(`type:${type}.name`)}</Tag>
|
<Tag color={'blue'}>{t(`type:${type}.name`)}</Tag>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@ -67,24 +64,24 @@ export const FieldCard: React.FC<Props> = props => {
|
|||||||
onChangeFields(fields.filter((e, i) => i !== index))
|
onChangeFields(fields.filter((e, i) => i !== index))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button danger><DeleteOutlined /></Button>
|
<Button danger>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
actions={[
|
actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}
|
||||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Form.Item name={[field.name as string, 'type']} noStyle><Input type={'hidden'} /></Form.Item>
|
<Form.Item name={[field.name as string, 'type']} noStyle>
|
||||||
|
<Input type={'hidden'} />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('type:title')}
|
label={t('type:title')}
|
||||||
name={[field.name as string, 'title']}
|
name={[field.name as string, 'title']}
|
||||||
rules={[
|
rules={[{ required: true, message: 'Title is required' }]}
|
||||||
{ required: true, message: 'Title is required' }
|
|
||||||
]}
|
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input onChange={e => setNextTitle(e.target.value)}/>
|
<Input onChange={(e) => setNextTitle(e.target.value)} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('type:description')}
|
label={t('type:description')}
|
||||||
@ -103,10 +100,7 @@ export const FieldCard: React.FC<Props> = props => {
|
|||||||
<Checkbox />
|
<Checkbox />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<TypeComponent
|
<TypeComponent field={field} form={form} />
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,86 +1,90 @@
|
|||||||
import {PlusOutlined} from '@ant-design/icons/lib'
|
import { PlusOutlined } from '@ant-design/icons/lib'
|
||||||
import {Button, Form, Select, Space, Tabs} from 'antd'
|
import { Button, Form, Select, Space, Tabs } from 'antd'
|
||||||
import {FormInstance} from 'antd/lib/form'
|
import { FormInstance } from 'antd/lib/form'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React, {useCallback, useState} from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
|
||||||
import {FieldCard} from './field.card'
|
import { FieldCard } from './field.card'
|
||||||
import {adminTypes} from './types'
|
import { adminTypes } from './types'
|
||||||
|
|
||||||
interface Props extends TabPaneProps {
|
interface Props extends TabPaneProps {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
fields: AdminFormFieldFragment[]
|
fields: AdminFormFieldFragment[]
|
||||||
onChangeFields: (fields: AdminFormFieldFragment[]) => any
|
onChangeFields: (fields: AdminFormFieldFragment[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldsTab: React.FC<Props> = props => {
|
export const FieldsTab: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [nextType, setNextType] = useState('textfield')
|
const [nextType, setNextType] = useState('textfield')
|
||||||
|
|
||||||
const renderType = useCallback((field, index, remove) => {
|
const renderType = useCallback(
|
||||||
return (
|
(field, index, remove) => {
|
||||||
<FieldCard
|
return (
|
||||||
form={props.form}
|
<FieldCard
|
||||||
field={field}
|
form={props.form}
|
||||||
index={index}
|
field={field}
|
||||||
remove={remove}
|
index={index}
|
||||||
fields={props.fields}
|
remove={remove}
|
||||||
onChangeFields={props.onChangeFields}
|
fields={props.fields}
|
||||||
/>
|
onChangeFields={props.onChangeFields}
|
||||||
)
|
/>
|
||||||
}, [props.fields])
|
)
|
||||||
|
},
|
||||||
|
[props.fields]
|
||||||
|
)
|
||||||
|
|
||||||
const addField = useCallback((add, index) => {
|
const addField = useCallback(
|
||||||
return (
|
(add, index) => {
|
||||||
<Form.Item
|
return (
|
||||||
wrapperCol={{span: 24}}
|
<Form.Item wrapperCol={{ span: 24 }}>
|
||||||
>
|
<Space
|
||||||
<Space
|
style={{
|
||||||
style={{
|
width: '100%',
|
||||||
width: '100%',
|
justifyContent: 'flex-end',
|
||||||
justifyContent: 'flex-end',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
|
|
||||||
{Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{t(`type:${type}.name`)}</Select.Option> )}
|
|
||||||
</Select>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => {
|
|
||||||
const defaults: AdminFormFieldFragment = {
|
|
||||||
logicJump: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
options: [],
|
|
||||||
id: `NEW-${Date.now()}`,
|
|
||||||
type: nextType,
|
|
||||||
title: '',
|
|
||||||
description: '',
|
|
||||||
required: false,
|
|
||||||
value: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
add(defaults)
|
|
||||||
const next = [...props.fields]
|
|
||||||
next.splice(index, 0, defaults)
|
|
||||||
props.onChangeFields(next)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlusOutlined /> Add Field
|
<Select value={nextType} onChange={(e) => setNextType(e)} style={{ minWidth: 200 }}>
|
||||||
</Button>
|
{Object.keys(adminTypes).map((type) => (
|
||||||
</Space>
|
<Select.Option value={type} key={type}>
|
||||||
</Form.Item>
|
{t(`type:${type}.name`)}
|
||||||
)
|
</Select.Option>
|
||||||
}, [props.fields, nextType])
|
))}
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
const defaults: AdminFormFieldFragment = {
|
||||||
|
logicJump: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
id: `NEW-${Date.now()}`,
|
||||||
|
type: nextType,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
required: false,
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
add(defaults)
|
||||||
|
const next = [...props.fields]
|
||||||
|
next.splice(index, 0, defaults)
|
||||||
|
props.onChangeFields(next)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined /> Add Field
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[props.fields, nextType]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.TabPane {...props}>
|
<Tabs.TabPane {...props}>
|
||||||
|
<Form.List name={['form', 'fields']}>
|
||||||
<Form.List
|
|
||||||
name={['form', 'fields']}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
const addAndMove = (index) => (defaults) => {
|
const addAndMove = (index) => (defaults) => {
|
||||||
add(defaults)
|
add(defaults)
|
||||||
@ -102,7 +106,6 @@ export const FieldsTab: React.FC<Props> = props => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
import {CheckCircleOutlined, CloseCircleOutlined} from '@ant-design/icons/lib'
|
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons/lib'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isLive: boolean
|
isLive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormIsLive: React.FC<Props> = props => {
|
export const FormIsLive: React.FC<Props> = (props) => {
|
||||||
if (props.isLive) {
|
if (props.isLive) {
|
||||||
return (
|
return (
|
||||||
<CheckCircleOutlined style={{
|
<CheckCircleOutlined
|
||||||
color: 'green'
|
style={{
|
||||||
}} />
|
color: 'green',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CloseCircleOutlined style={{
|
<CloseCircleOutlined
|
||||||
color: 'red'
|
style={{
|
||||||
}} />
|
color: 'red',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import {InfoCircleOutlined} from '@ant-design/icons/lib'
|
import { InfoCircleOutlined } from '@ant-design/icons/lib'
|
||||||
import {Form, Input, Select, Switch, Tabs} from 'antd'
|
import { Form, Input, Select, Switch, Tabs } from 'antd'
|
||||||
import {FormInstance} from 'antd/lib/form'
|
import { FormInstance } from 'antd/lib/form'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {Trans, useTranslation} from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
|
||||||
|
|
||||||
interface Props extends TabPaneProps {
|
interface Props extends TabPaneProps {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
fields: AdminFormFieldFragment[]
|
fields: AdminFormFieldFragment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RespondentNotificationsTab: React.FC<Props> = props => {
|
export const RespondentNotificationsTab: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [enabled, setEnabled] = useState<boolean>()
|
const [enabled, setEnabled] = useState<boolean>()
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
|
|||||||
|
|
||||||
const groups = {}
|
const groups = {}
|
||||||
|
|
||||||
props.fields.forEach(field => {
|
props.fields.forEach((field) => {
|
||||||
if (!groups[field.type]) {
|
if (!groups[field.type]) {
|
||||||
groups[field.type] = []
|
groups[field.type] = []
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
|
|||||||
name={['form', 'respondentNotifications', 'enabled']}
|
name={['form', 'respondentNotifications', 'enabled']}
|
||||||
valuePropName={'checked'}
|
valuePropName={'checked'}
|
||||||
>
|
>
|
||||||
<Switch onChange={e => setEnabled(e.valueOf())} />
|
<Switch onChange={(e) => setEnabled(e.valueOf())} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -72,12 +72,13 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
|
|||||||
message: t('validation:templateRequired'),
|
message: t('validation:templateRequired'),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
extra={(
|
extra={
|
||||||
<div>
|
<div>
|
||||||
<Trans>form:respondentNotifications.htmlTemplateInfo</Trans>
|
<Trans>form:respondentNotifications.htmlTemplateInfo</Trans>
|
||||||
<a
|
<a
|
||||||
href={'https://mjml.io/try-it-live'}
|
href={'https://mjml.io/try-it-live'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
|
rel={'noreferrer'}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 16,
|
marginLeft: 16,
|
||||||
}}
|
}}
|
||||||
@ -85,7 +86,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
|
|||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize />
|
<Input.TextArea autoSize />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -102,10 +103,12 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{Object.keys(groups).map(key => (
|
{Object.keys(groups).map((key) => (
|
||||||
<Select.OptGroup label={key.toUpperCase()} key={key}>
|
<Select.OptGroup label={key.toUpperCase()} key={key}>
|
||||||
{groups[key].map(field => (
|
{groups[key].map((field) => (
|
||||||
<Select.Option value={field.id} key={field.id}>{field.title}</Select.Option>
|
<Select.Option value={field.id} key={field.id}>
|
||||||
|
{field.title}
|
||||||
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select.OptGroup>
|
</Select.OptGroup>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import {InfoCircleOutlined} from '@ant-design/icons/lib'
|
import { InfoCircleOutlined } from '@ant-design/icons/lib'
|
||||||
import {Form, Input, Select, Switch, Tabs} from 'antd'
|
import { Form, Input, Select, Switch, Tabs } from 'antd'
|
||||||
import {FormInstance} from 'antd/lib/form'
|
import { FormInstance } from 'antd/lib/form'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {Trans, useTranslation} from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
|
||||||
|
|
||||||
interface Props extends TabPaneProps {
|
interface Props extends TabPaneProps {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
fields: AdminFormFieldFragment[]
|
fields: AdminFormFieldFragment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelfNotificationsTab: React.FC<Props> = props => {
|
export const SelfNotificationsTab: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [enabled, setEnabled] = useState<boolean>()
|
const [enabled, setEnabled] = useState<boolean>()
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
|
|||||||
}, [enabled])
|
}, [enabled])
|
||||||
|
|
||||||
const groups = {}
|
const groups = {}
|
||||||
props.fields.forEach(field => {
|
props.fields.forEach((field) => {
|
||||||
if (!groups[field.type]) {
|
if (!groups[field.type]) {
|
||||||
groups[field.type] = []
|
groups[field.type] = []
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
|
|||||||
name={['form', 'selfNotifications', 'enabled']}
|
name={['form', 'selfNotifications', 'enabled']}
|
||||||
valuePropName={'checked'}
|
valuePropName={'checked'}
|
||||||
>
|
>
|
||||||
<Switch onChange={e => setEnabled(e.valueOf())} />
|
<Switch onChange={(e) => setEnabled(e.valueOf())} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -70,12 +70,13 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
|
|||||||
message: t('validation:templateRequired'),
|
message: t('validation:templateRequired'),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
extra={(
|
extra={
|
||||||
<div>
|
<div>
|
||||||
<Trans>form:selfNotifications.htmlTemplateInfo</Trans>
|
<Trans>form:selfNotifications.htmlTemplateInfo</Trans>
|
||||||
<a
|
<a
|
||||||
href={'https://mjml.io/try-it-live'}
|
href={'https://mjml.io/try-it-live'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
|
rel={'noreferrer'}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 16,
|
marginLeft: 16,
|
||||||
}}
|
}}
|
||||||
@ -83,7 +84,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
|
|||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize />
|
<Input.TextArea autoSize />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -94,10 +95,12 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
|
|||||||
extra={t('form:selfNotifications.fromFieldInfo')}
|
extra={t('form:selfNotifications.fromFieldInfo')}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{Object.keys(groups).map(key => (
|
{Object.keys(groups).map((key) => (
|
||||||
<Select.OptGroup label={key.toUpperCase()} key={key}>
|
<Select.OptGroup label={key.toUpperCase()} key={key}>
|
||||||
{groups[key].map(field => (
|
{groups[key].map((field) => (
|
||||||
<Select.Option value={field.id} key={field.id}>{field.title}</Select.Option>
|
<Select.Option value={field.id} key={field.id}>
|
||||||
|
{field.title}
|
||||||
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select.OptGroup>
|
</Select.OptGroup>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib'
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons/lib'
|
||||||
import {Button, Card, Form, Input, Switch, Tabs} from 'antd'
|
import { Button, Card, Form, Input, Switch, Tabs } from 'antd'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {InputColor} from '../../input/color'
|
import { InputColor } from '../../input/color'
|
||||||
|
|
||||||
export const StartPageTab: React.FC<TabPaneProps> = props => {
|
export const StartPageTab: React.FC<TabPaneProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -18,17 +18,11 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('form:startPage.title')} name={['form', 'startPage', 'title']}>
|
||||||
label={t('form:startPage.title')}
|
|
||||||
name={['form', 'startPage', 'title']}
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('form:startPage.paragraph')} name={['form', 'startPage', 'paragraph']}>
|
||||||
label={t('form:startPage.paragraph')}
|
|
||||||
name={['form', 'startPage', 'paragraph']}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize />
|
<Input.TextArea autoSize />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -39,54 +33,65 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.List
|
<Form.List name={['form', 'startPage', 'buttons']}>
|
||||||
name={['form', 'startPage', 'buttons']}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{
|
wrapperCol={{
|
||||||
sm: { offset: index === 0 ? 0 : 6 },
|
sm: { offset: index === 0 ? 0 : 6 },
|
||||||
}}
|
}}
|
||||||
label={index === 0 ? t('form:startPage.buttons') : ''}
|
label={index === 0 ? t('form:startPage.buttons') : ''}
|
||||||
key={field.key}
|
key={field.key}
|
||||||
>
|
>
|
||||||
<Card
|
<Card actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}>
|
||||||
actions={[
|
<Form.Item
|
||||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
label={t('form:startPage.url')}
|
||||||
]}
|
name={[field.key, 'url']}
|
||||||
|
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Input />
|
||||||
label={t('form:startPage.url')}
|
</Form.Item>
|
||||||
name={[field.key, 'url']}
|
<Form.Item
|
||||||
rules={[
|
label={t('form:startPage.action')}
|
||||||
{type: 'url', message: t('validation:invalidUrl')}
|
name={[field.key, 'action']}
|
||||||
]}
|
labelCol={{ span: 6 }}
|
||||||
labelCol={{ span: 6 }}
|
>
|
||||||
>
|
<Input />
|
||||||
<Input />
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item label={t('form:startPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
label={t('form:startPage.text')}
|
||||||
<Input />
|
name={[field.key, 'text']}
|
||||||
</Form.Item>
|
labelCol={{ span: 6 }}
|
||||||
<Form.Item label={t('form:startPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('form:startPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
<Form.Item
|
||||||
<InputColor />
|
label={t('form:startPage.bgColor')}
|
||||||
</Form.Item>
|
name={[field.key, 'bgColor']}
|
||||||
<Form.Item label={t('form:startPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
|
labelCol={{ span: 6 }}
|
||||||
<InputColor />
|
>
|
||||||
</Form.Item>
|
<InputColor />
|
||||||
<Form.Item label={t('form:startPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
</Form.Item>
|
||||||
<InputColor />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t('form:startPage.activeColor')}
|
||||||
</Card>
|
name={[field.key, 'activeColor']}
|
||||||
</Form.Item>
|
labelCol={{ span: 6 }}
|
||||||
)
|
>
|
||||||
)}
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t('form:startPage.color')}
|
||||||
|
name={[field.key, 'color']}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{
|
wrapperCol={{
|
||||||
sm: { offset: 6 },
|
sm: { offset: 6 },
|
||||||
@ -95,7 +100,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
add();
|
add()
|
||||||
}}
|
}}
|
||||||
style={{ width: '60%' }}
|
style={{ width: '60%' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import {Descriptions, Table} from 'antd'
|
import { Descriptions, Table } from 'antd'
|
||||||
import {ColumnsType} from 'antd/lib/table/interface'
|
import { ColumnsType } from 'antd/lib/table/interface'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
AdminPagerSubmissionEntryFieldQueryData,
|
AdminPagerSubmissionEntryFieldQueryData,
|
||||||
AdminPagerSubmissionEntryQueryData,
|
AdminPagerSubmissionEntryQueryData,
|
||||||
AdminPagerSubmissionFormQueryData
|
AdminPagerSubmissionFormQueryData,
|
||||||
} from '../../../graphql/query/admin.pager.submission.query'
|
} from '../../../graphql/query/admin.pager.submission.query'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -13,24 +13,23 @@ interface Props {
|
|||||||
submission: AdminPagerSubmissionEntryQueryData
|
submission: AdminPagerSubmissionEntryQueryData
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubmissionValues: React.FC<Props> = props => {
|
export const SubmissionValues: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const columns: ColumnsType<AdminPagerSubmissionEntryFieldQueryData> = [
|
const columns: ColumnsType<AdminPagerSubmissionEntryFieldQueryData> = [
|
||||||
{
|
{
|
||||||
title: t('submission:field'),
|
title: t('submission:field'),
|
||||||
render: (row: AdminPagerSubmissionEntryFieldQueryData) => {
|
render: (row: AdminPagerSubmissionEntryFieldQueryData) => {
|
||||||
|
|
||||||
if (row.field) {
|
if (row.field) {
|
||||||
return `${row.field.title}${row.field.required ? '*' : ''}`
|
return `${row.field.title}${row.field.required ? '*' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${row.id}`
|
return `${row.id}`
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('submission:value'),
|
title: t('submission:value'),
|
||||||
render: row => {
|
render: (row) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(row.value)
|
const data = JSON.parse(row.value)
|
||||||
|
|
||||||
@ -38,24 +37,28 @@ export const SubmissionValues: React.FC<Props> = props => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return row.value
|
return row.value
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Descriptions title={t('submission:submission')}>
|
<Descriptions title={t('submission:submission')}>
|
||||||
<Descriptions.Item label={t('submission:country')}>{props.submission.geoLocation.country}</Descriptions.Item>
|
<Descriptions.Item label={t('submission:country')}>
|
||||||
<Descriptions.Item label={t('submission:city')}>{props.submission.geoLocation.city}</Descriptions.Item>
|
{props.submission.geoLocation.country}
|
||||||
<Descriptions.Item label={t('submission:device.type')}>{props.submission.device.type}</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={t('submission:device.name')}>{props.submission.device.name}</Descriptions.Item>
|
<Descriptions.Item label={t('submission:city')}>
|
||||||
|
{props.submission.geoLocation.city}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t('submission:device.type')}>
|
||||||
|
{props.submission.device.type}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t('submission:device.name')}>
|
||||||
|
{props.submission.device.name}
|
||||||
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
|
|
||||||
<Table
|
<Table columns={columns} dataSource={props.submission.fields} rowKey={'id'} />
|
||||||
columns={columns}
|
|
||||||
dataSource={props.submission.fields}
|
|
||||||
rowKey={'id'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import {DatePicker, Form} from 'antd'
|
import { DatePicker, Form } from 'antd'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
|
export const DateType: React.FC<AdminFieldTypeProps> = ({ field }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -13,19 +13,17 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
|
|||||||
label={t('type:date.default')}
|
label={t('type:date.default')}
|
||||||
name={[field.name, 'value']}
|
name={[field.name, 'value']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
getValueFromEvent={e => e ? e.format('YYYY-MM-DD') : undefined}
|
getValueFromEvent={(e) => (e ? e.format('YYYY-MM-DD') : undefined)}
|
||||||
getValueProps={e => ({value: e ? moment(e) : undefined})}
|
getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker format={'YYYY-MM-DD'} />
|
||||||
format={'YYYY-MM-DD'}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('type:date.min')}
|
label={t('type:date.min')}
|
||||||
name={[field.name, 'optionKeys', 'min']}
|
name={[field.name, 'optionKeys', 'min']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
getValueFromEvent={e => e.format('YYYY-MM-DD')}
|
getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
|
||||||
getValueProps={e => ({value: e ? moment(e) : undefined})}
|
getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
|
||||||
>
|
>
|
||||||
<DatePicker />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -34,8 +32,8 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
|
|||||||
label={t('type:date.max')}
|
label={t('type:date.max')}
|
||||||
name={[field.name, 'optionKeys', 'max']}
|
name={[field.name, 'optionKeys', 'max']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
getValueFromEvent={e => e.format('YYYY-MM-DD')}
|
getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
|
||||||
getValueProps={e => ({value: e ? moment(e) : undefined})}
|
getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
|
||||||
>
|
>
|
||||||
<DatePicker />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Button, Col, Form, Input, Row} from 'antd'
|
import { Button, Col, Form, Input, Row } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
export const DropdownType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,11 +16,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.List
|
<Form.List name={[props.field.name, 'options']}>
|
||||||
name={[props.field.name, 'options']}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@ -37,7 +34,7 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{ span: 24 }}
|
wrapperCol={{ span: 24 }}
|
||||||
name={[field.name, 'title']}
|
name={[field.name, 'title']}
|
||||||
style={{marginBottom: 0}}
|
style={{ marginBottom: 0 }}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('type:dropdown.titlePlaceholder')} />
|
<Input placeholder={t('type:dropdown.titlePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -46,10 +43,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{ span: 24 }}
|
wrapperCol={{ span: 24 }}
|
||||||
name={[field.name, 'value']}
|
name={[field.name, 'value']}
|
||||||
style={{marginBottom: 0}}
|
style={{ marginBottom: 0 }}
|
||||||
rules={[
|
rules={[{ required: true, message: t('validation:valueRequired') }]}
|
||||||
{ required: true, message: t('validation:valueRequired') }
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder={t('type:dropdown.valuePlaceholder')} />
|
<Input placeholder={t('type:dropdown.valuePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -69,11 +64,9 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
}}
|
}}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Button
|
<Button type={'dashed'} onClick={() => add()}>
|
||||||
type={'dashed'}
|
{t('type:dropdown.addOption')}
|
||||||
onClick={() => add()}
|
</Button>
|
||||||
>
|
|
||||||
{t('type:dropdown.addOption')}</Button>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const EmailType: React.FC<AdminFieldTypeProps> = props => {
|
export const EmailType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -11,9 +11,7 @@ export const EmailType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('type:email.default')}
|
label={t('type:email.default')}
|
||||||
name={[props.field.name, 'value']}
|
name={[props.field.name, 'value']}
|
||||||
rules={[
|
rules={[{ type: 'email', message: t('validation:emailRequired') }]}
|
||||||
{ type: 'email', message: t('validation:emailRequired') }
|
|
||||||
]}
|
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input type={'email'} />
|
<Input type={'email'} />
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const HiddenType: React.FC<AdminFieldTypeProps> = props => {
|
export const HiddenType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {DateType} from './date.type'
|
import { DateType } from './date.type'
|
||||||
import {DropdownType} from './dropdown.type'
|
import { DropdownType } from './dropdown.type'
|
||||||
import {EmailType} from './email.type'
|
import { EmailType } from './email.type'
|
||||||
import {HiddenType} from './hidden.type'
|
import { HiddenType } from './hidden.type'
|
||||||
import {LinkType} from './link.type'
|
import { LinkType } from './link.type'
|
||||||
import {NumberType} from './number.type'
|
import { NumberType } from './number.type'
|
||||||
import {RadioType} from './radio.type'
|
import { RadioType } from './radio.type'
|
||||||
import {RatingType} from './rating.type'
|
import { RatingType } from './rating.type'
|
||||||
import {TextType} from './text.type'
|
import { TextType } from './text.type'
|
||||||
import {TextareaType} from './textarea.type'
|
import { TextareaType } from './textarea.type'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
import {YesNoType} from './yes_no.type'
|
import { YesNoType } from './yes_no.type'
|
||||||
|
|
||||||
export const adminTypes: {
|
export const adminTypes: {
|
||||||
[key: string]: React.FC<AdminFieldTypeProps>
|
[key: string]: React.FC<AdminFieldTypeProps>
|
||||||
} = {
|
} = {
|
||||||
'textfield': TextType,
|
textfield: TextType,
|
||||||
'date': DateType,
|
date: DateType,
|
||||||
'email': EmailType,
|
email: EmailType,
|
||||||
'textarea': TextareaType,
|
textarea: TextareaType,
|
||||||
'link': LinkType,
|
link: LinkType,
|
||||||
'dropdown': DropdownType,
|
dropdown: DropdownType,
|
||||||
'rating': RatingType,
|
rating: RatingType,
|
||||||
'radio': RadioType,
|
radio: RadioType,
|
||||||
'hidden': HiddenType,
|
hidden: HiddenType,
|
||||||
'yes_no': YesNoType,
|
yes_no: YesNoType,
|
||||||
'number': NumberType,
|
number: NumberType,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const LinkType: React.FC<AdminFieldTypeProps> = props => {
|
export const LinkType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -11,9 +11,7 @@ export const LinkType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('type:link.default')}
|
label={t('type:link.default')}
|
||||||
name={[props.field.name, 'value']}
|
name={[props.field.name, 'value']}
|
||||||
rules={[
|
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
|
||||||
{ type: 'url', message: t('validation:invalidUrl') }
|
|
||||||
]}
|
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input type={'url'} />
|
<Input type={'url'} />
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, InputNumber} from 'antd'
|
import { Form, InputNumber } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const NumberType: React.FC<AdminFieldTypeProps> = props => {
|
export const NumberType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Button, Col, Form, Input, Row} from 'antd'
|
import { Button, Col, Form, Input, Row } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
export const RadioType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,11 +16,8 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.List
|
<Form.List name={[props.field.name, 'options']}>
|
||||||
name={[props.field.name, 'options']}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
@ -37,7 +34,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{ span: 24 }}
|
wrapperCol={{ span: 24 }}
|
||||||
name={[field.name, 'title']}
|
name={[field.name, 'title']}
|
||||||
style={{marginBottom: 0}}
|
style={{ marginBottom: 0 }}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('type:radio:titlePlaceholder')} />
|
<Input placeholder={t('type:radio:titlePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -46,10 +43,8 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
wrapperCol={{ span: 24 }}
|
wrapperCol={{ span: 24 }}
|
||||||
name={[field.name, 'value']}
|
name={[field.name, 'value']}
|
||||||
style={{marginBottom: 0}}
|
style={{ marginBottom: 0 }}
|
||||||
rules={[
|
rules={[{ required: true, message: t('validation:valueRequired') }]}
|
||||||
{ required: true, message: t('validation:valueRequired') }
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder={t('type:radio:valuePlaceholder')} />
|
<Input placeholder={t('type:radio:valuePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -69,10 +64,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
}}
|
}}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Button
|
<Button type={'dashed'} onClick={() => add()}>
|
||||||
type={'dashed'}
|
|
||||||
onClick={() => add()}
|
|
||||||
>
|
|
||||||
{t('type:radio:addOption')}
|
{t('type:radio:addOption')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Rate} from 'antd'
|
import { Form, Rate } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const RatingType: React.FC<AdminFieldTypeProps> = props => {
|
export const RatingType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
// TODO add ratings
|
// TODO add ratings
|
||||||
@ -15,10 +15,7 @@ export const RatingType: React.FC<AdminFieldTypeProps> = props => {
|
|||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
extra={t('type:rating.clearNote')}
|
extra={t('type:rating.clearNote')}
|
||||||
>
|
>
|
||||||
<Rate
|
<Rate allowHalf allowClear />
|
||||||
allowHalf
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const TextType: React.FC<AdminFieldTypeProps> = props => {
|
export const TextType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const TextareaType: React.FC<AdminFieldTypeProps> = props => {
|
export const TextareaType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {FormInstance} from 'antd/lib/form'
|
import { FormInstance } from 'antd/lib/form'
|
||||||
|
|
||||||
export interface AdminFieldTypeProps {
|
export interface AdminFieldTypeProps {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
field: any
|
field: { name: string }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {AdminFieldTypeProps} from './type.props'
|
import { AdminFieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const YesNoType: React.FC<AdminFieldTypeProps> = props => {
|
export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
// TODO add switch
|
// TODO add switch
|
||||||
|
|||||||
@ -1,24 +1,25 @@
|
|||||||
import {Form, message} from 'antd'
|
import { Form, message } from 'antd'
|
||||||
import {useForm} from 'antd/lib/form/Form'
|
import { useForm } from 'antd/lib/form/Form'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FormDesignFragment, FormFieldFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment, FormFieldFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {StyledButton} from '../styled/button'
|
import { StyledButton } from '../styled/button'
|
||||||
import {StyledH1} from '../styled/h1'
|
import { StyledH1 } from '../styled/h1'
|
||||||
import {StyledP} from '../styled/p'
|
import { StyledP } from '../styled/p'
|
||||||
import {fieldTypes} from './types'
|
import { fieldTypes } from './types'
|
||||||
import {TextType} from './types/text.type'
|
import { TextType } from './types/text.type'
|
||||||
import {FieldTypeProps} from './types/type.props'
|
import { FieldTypeProps } from './types/type.props'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
field: FormFieldFragment
|
field: FormFieldFragment
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
|
|
||||||
save: (data: any) => any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
next: () => any
|
save: (data: any) => void
|
||||||
prev: () => any
|
next: () => void
|
||||||
|
prev: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Field: React.FC<Props> = ({field, save, design, children, next, prev, ...props}) => {
|
export const Field: React.FC<Props> = ({ field, save, design, next, prev, ...props }) => {
|
||||||
const [form] = useForm()
|
const [form] = useForm()
|
||||||
|
|
||||||
const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType
|
const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType
|
||||||
@ -44,33 +45,42 @@ export const Field: React.FC<Props> = ({field, save, design, children, next, pre
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{
|
<div
|
||||||
flex: 1,
|
style={{
|
||||||
display: 'flex',
|
flex: 1,
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
padding: 32,
|
flexDirection: 'column',
|
||||||
justifyContent: 'flex-end',
|
padding: 32,
|
||||||
}}>
|
justifyContent: 'flex-end',
|
||||||
<StyledH1 design={design} type={'question'}>{field.title}</StyledH1>
|
}}
|
||||||
{field.description && <StyledP design={design} type={'question'}>{field.description}</StyledP>}
|
>
|
||||||
|
<StyledH1 design={design} type={'question'}>
|
||||||
|
{field.title}
|
||||||
|
</StyledH1>
|
||||||
|
{field.description && (
|
||||||
|
<StyledP design={design} type={'question'}>
|
||||||
|
{field.description}
|
||||||
|
</StyledP>
|
||||||
|
)}
|
||||||
|
|
||||||
<FieldInput
|
<FieldInput design={design} field={field} />
|
||||||
design={design}
|
|
||||||
field={field}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div
|
||||||
padding: 32,
|
style={{
|
||||||
display: 'flex',
|
padding: 32,
|
||||||
}}>
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
background={design.colors.buttonColor}
|
background={design.colors.buttonColor}
|
||||||
color={design.colors.buttonTextColor}
|
color={design.colors.buttonTextColor}
|
||||||
highlight={design.colors.buttonActiveColor}
|
highlight={design.colors.buttonActiveColor}
|
||||||
onClick={prev}
|
onClick={prev}
|
||||||
>{'Previous'}</StyledButton>
|
>
|
||||||
|
{'Previous'}
|
||||||
|
</StyledButton>
|
||||||
|
|
||||||
<div style={{flex: 1}} />
|
<div style={{ flex: 1 }} />
|
||||||
|
|
||||||
<StyledButton
|
<StyledButton
|
||||||
background={design.colors.buttonColor}
|
background={design.colors.buttonColor}
|
||||||
@ -78,7 +88,9 @@ export const Field: React.FC<Props> = ({field, save, design, children, next, pre
|
|||||||
highlight={design.colors.buttonActiveColor}
|
highlight={design.colors.buttonActiveColor}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
onClick={form.submit}
|
onClick={form.submit}
|
||||||
>{'Next'}</StyledButton>
|
>
|
||||||
|
{'Next'}
|
||||||
|
</StyledButton>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,43 +1,54 @@
|
|||||||
import {Space} from 'antd'
|
import { Space } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FormDesignFragment, FormPageFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment, FormPageFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {StyledButton} from '../styled/button'
|
import { StyledButton } from '../styled/button'
|
||||||
import {StyledH1} from '../styled/h1'
|
import { StyledH1 } from '../styled/h1'
|
||||||
import {StyledP} from '../styled/p'
|
import { StyledP } from '../styled/p'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'start' | 'end'
|
type: 'start' | 'end'
|
||||||
page: FormPageFragment
|
page: FormPageFragment
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
|
|
||||||
next: () => any
|
next: () => void
|
||||||
prev: () => any
|
prev: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormPage: React.FC<Props> = ({page, design, next, prev, type, children, ...props}) => {
|
export const FormPage: React.FC<Props> = ({ page, design, next, ...props }) => {
|
||||||
if (!page.show) {
|
if (!page.show) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
display: 'flex',
|
style={{
|
||||||
flexDirection: 'column',
|
|
||||||
}} {...props}>
|
|
||||||
<div style={{
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}>
|
}}
|
||||||
<StyledH1 design={design} type={'question'}>{page.title}</StyledH1>
|
{...props}
|
||||||
<StyledP design={design} type={'question'}>{page.paragraph}</StyledP>
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledH1 design={design} type={'question'}>
|
||||||
|
{page.title}
|
||||||
|
</StyledH1>
|
||||||
|
<StyledP design={design} type={'question'}>
|
||||||
|
{page.paragraph}
|
||||||
|
</StyledP>
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div
|
||||||
padding: 32,
|
style={{
|
||||||
display: 'flex',
|
padding: 32,
|
||||||
}}>
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{page.buttons.length > 0 && (
|
{page.buttons.length > 0 && (
|
||||||
<Space>
|
<Space>
|
||||||
{page.buttons.map((button, key) => {
|
{page.buttons.map((button, key) => {
|
||||||
@ -49,13 +60,16 @@ export const FormPage: React.FC<Props> = ({page, design, next, prev, type, child
|
|||||||
key={key}
|
key={key}
|
||||||
href={button.url}
|
href={button.url}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
>{button.text}</StyledButton>
|
rel={'noreferrer'}
|
||||||
|
>
|
||||||
|
{button.text}
|
||||||
|
</StyledButton>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{flex: 1}} />
|
<div style={{ flex: 1 }} />
|
||||||
|
|
||||||
<StyledButton
|
<StyledButton
|
||||||
background={design.colors.buttonColor}
|
background={design.colors.buttonColor}
|
||||||
@ -63,7 +77,9 @@ export const FormPage: React.FC<Props> = ({page, design, next, prev, type, child
|
|||||||
highlight={design.colors.buttonActiveColor}
|
highlight={design.colors.buttonActiveColor}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
onClick={next}
|
onClick={next}
|
||||||
>{page.buttonText || 'Continue'}</StyledButton>
|
>
|
||||||
|
{page.buttonText || 'Continue'}
|
||||||
|
</StyledButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import dayjs, {Dayjs} from 'dayjs'
|
import dayjs, { Dayjs } from 'dayjs'
|
||||||
import moment from 'moment'
|
import moment, { Moment } from 'moment'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {StyledDateInput} from '../../styled/date.input'
|
import { StyledDateInput } from '../../styled/date.input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const DateType: React.FC<FieldTypeProps> = ({ field, design}) => {
|
export const DateType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
const [min, setMin] = useState<Dayjs>()
|
const [min, setMin] = useState<Dayjs>()
|
||||||
const [max, setMax] = useState<Dayjs>()
|
const [max, setMax] = useState<Dayjs>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.options.forEach(option => {
|
field.options.forEach((option) => {
|
||||||
if (option.key === 'min') {
|
if (option.key === 'min') {
|
||||||
setMin(dayjs(option.value))
|
setMin(dayjs(option.value))
|
||||||
}
|
}
|
||||||
@ -24,22 +24,19 @@ export const DateType: React.FC<FieldTypeProps> = ({ field, design}) => {
|
|||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
|
||||||
]}
|
getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
|
||||||
getValueFromEvent={e => e.format('YYYY-MM-DD')}
|
|
||||||
getValueProps={e => ({value: e ? moment(e) : undefined})}
|
|
||||||
initialValue={field.value ? moment(field.value) : undefined}
|
initialValue={field.value ? moment(field.value) : undefined}
|
||||||
>
|
>
|
||||||
<StyledDateInput
|
<StyledDateInput
|
||||||
size={'large'}
|
size={'large'}
|
||||||
design={design}
|
design={design}
|
||||||
autoFocus
|
disabledDate={(d: Moment) => {
|
||||||
disabledDate={(d: any) => {
|
if (min && min.isAfter(d.toDate())) {
|
||||||
if (min && min.isAfter(d)) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (max && max.isBefore(d)) {
|
if (max && max.isBefore(d.toDate())) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -1,24 +1,32 @@
|
|||||||
import {Form, Select} from 'antd'
|
import { Form, Select } from 'antd'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {StyledSelect} from '../../styled/select'
|
import { StyledSelect } from '../../styled/select'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const DropdownType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const DropdownType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
|
||||||
]}
|
|
||||||
initialValue={field.value || null}
|
initialValue={field.value || null}
|
||||||
>
|
>
|
||||||
<StyledSelect design={design} open={open} onBlur={() => setOpen(false)} onFocus={() => setOpen(true)} onSelect={() => setOpen(false)}>
|
<StyledSelect
|
||||||
{field.options.filter(option => option.key === null).map(option => (
|
design={design}
|
||||||
<Select.Option value={option.value} key={option.value}>OK{option.title || option.value}</Select.Option>
|
open={open}
|
||||||
))}
|
onBlur={() => setOpen(false)}
|
||||||
|
onFocus={() => setOpen(true)}
|
||||||
|
onSelect={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
{field.options
|
||||||
|
.filter((option) => option.key === null)
|
||||||
|
.map((option) => (
|
||||||
|
<Select.Option value={option.value} key={option.value}>
|
||||||
|
OK{option.title || option.value}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</StyledSelect>
|
</StyledSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,24 +1,20 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledInput} from '../../styled/input'
|
import { StyledInput } from '../../styled/input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const EmailType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const EmailType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
{ type: 'email', message: 'Must be a valid email' }
|
{ type: 'email', message: 'Must be a valid email' },
|
||||||
]}
|
]}
|
||||||
initialValue={field.value}
|
initialValue={field.value}
|
||||||
>
|
>
|
||||||
<StyledInput
|
<StyledInput design={design} allowClear size={'large'} />
|
||||||
design={design}
|
|
||||||
allowClear
|
|
||||||
size={'large'}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,27 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {DateType} from './date.type'
|
import { DateType } from './date.type'
|
||||||
import {DropdownType} from './dropdown.type'
|
import { DropdownType } from './dropdown.type'
|
||||||
import {EmailType} from './email.type'
|
import { EmailType } from './email.type'
|
||||||
import {LinkType} from './link.type'
|
import { LinkType } from './link.type'
|
||||||
import {NumberType} from './number.type'
|
import { NumberType } from './number.type'
|
||||||
import {RadioType} from './radio.type'
|
import { RadioType } from './radio.type'
|
||||||
import {RatingType} from './rating.type'
|
import { RatingType } from './rating.type'
|
||||||
import {TextType} from './text.type'
|
import { TextType } from './text.type'
|
||||||
import {TextareaType} from './textarea.type'
|
import { TextareaType } from './textarea.type'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
import {YesNoType} from './yes_no.type'
|
import { YesNoType } from './yes_no.type'
|
||||||
|
|
||||||
export const fieldTypes: {
|
export const fieldTypes: {
|
||||||
[key: string]: React.FC<FieldTypeProps>
|
[key: string]: React.FC<FieldTypeProps>
|
||||||
} = {
|
} = {
|
||||||
'textfield': TextType,
|
textfield: TextType,
|
||||||
'date': DateType,
|
date: DateType,
|
||||||
'email': EmailType,
|
email: EmailType,
|
||||||
'textarea': TextareaType,
|
textarea: TextareaType,
|
||||||
'link': LinkType,
|
link: LinkType,
|
||||||
'dropdown': DropdownType,
|
dropdown: DropdownType,
|
||||||
'rating': RatingType,
|
rating: RatingType,
|
||||||
'radio': RadioType,
|
radio: RadioType,
|
||||||
'yes_no': YesNoType,
|
yes_no: YesNoType,
|
||||||
'number': NumberType,
|
number: NumberType,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,20 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledInput} from '../../styled/input'
|
import { StyledInput } from '../../styled/input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const LinkType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const LinkType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
{ type: 'url', message: 'Must be a valid URL' }
|
{ type: 'url', message: 'Must be a valid URL' },
|
||||||
]}
|
]}
|
||||||
initialValue={field.value}
|
initialValue={field.value}
|
||||||
>
|
>
|
||||||
<StyledInput
|
<StyledInput design={design} allowClear size={'large'} />
|
||||||
design={design}
|
|
||||||
allowClear
|
|
||||||
size={'large'}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledNumberInput} from '../../styled/number.input'
|
import { StyledNumberInput } from '../../styled/number.input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const NumberType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const NumberType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -14,10 +14,7 @@ export const NumberType: React.FC<FieldTypeProps> = ({field, design}) => {
|
|||||||
]}
|
]}
|
||||||
initialValue={parseFloat(field.value)}
|
initialValue={parseFloat(field.value)}
|
||||||
>
|
>
|
||||||
<StyledNumberInput
|
<StyledNumberInput design={design} size={'large'} />
|
||||||
design={design}
|
|
||||||
size={'large'}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,28 +1,26 @@
|
|||||||
import {Form, Radio} from 'antd'
|
import { Form, Radio } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledRadio} from '../../styled/radio'
|
import { StyledRadio } from '../../styled/radio'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const RadioType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const RadioType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
initialValue={field.options
|
||||||
]}
|
.map((option) => option.value)
|
||||||
initialValue={field.options.map(option => option.value).find(value => value === field.value)}
|
.find((value) => value === field.value)}
|
||||||
>
|
>
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
{field.options.filter(option => option.key === null).map(option => (
|
{field.options
|
||||||
<StyledRadio
|
.filter((option) => option.key === null)
|
||||||
design={design}
|
.map((option) => (
|
||||||
value={option.value}
|
<StyledRadio design={design} value={option.value} key={option.value}>
|
||||||
key={option.value}
|
{option.title || option.value}
|
||||||
>
|
</StyledRadio>
|
||||||
{option.title || option.value}
|
))}
|
||||||
</StyledRadio>
|
|
||||||
))}
|
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
import {Form, Rate} from 'antd'
|
import { Form, Rate } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const RatingType: React.FC<FieldTypeProps> = ({field}) => {
|
export const RatingType: React.FC<FieldTypeProps> = ({ field }) => {
|
||||||
// TODO add ratings
|
// TODO add ratings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
|
||||||
]}
|
|
||||||
initialValue={parseFloat(field.value)}
|
initialValue={parseFloat(field.value)}
|
||||||
>
|
>
|
||||||
<Rate allowHalf />
|
<Rate allowHalf />
|
||||||
|
|||||||
@ -1,27 +1,19 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledInput} from '../../styled/input'
|
import { StyledInput } from '../../styled/input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const TextType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const TextType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
// TODO focus when becomes visible
|
// TODO focus when becomes visible
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' }
|
|
||||||
]}
|
|
||||||
initialValue={field.value}
|
initialValue={field.value}
|
||||||
>
|
>
|
||||||
<StyledInput
|
<StyledInput design={design} allowClear size={'large'} />
|
||||||
design={design}
|
|
||||||
allowClear
|
|
||||||
size={'large'}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
import {Form} from 'antd'
|
import { Form } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyledTextareaInput} from '../../styled/textarea.input'
|
import { StyledTextareaInput } from '../../styled/textarea.input'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const TextareaType: React.FC<FieldTypeProps> = ({field, design}) => {
|
export const TextareaType: React.FC<FieldTypeProps> = ({ field, design }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
|
||||||
]}
|
|
||||||
initialValue={field.value}
|
initialValue={field.value}
|
||||||
>
|
>
|
||||||
<StyledTextareaInput
|
<StyledTextareaInput design={design} allowClear autoSize />
|
||||||
design={design}
|
|
||||||
allowClear
|
|
||||||
autoSize
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {FormDesignFragment, FormFieldFragment} from '../../../graphql/fragment/form.fragment'
|
import { FormDesignFragment, FormFieldFragment } from '../../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
export interface FieldTypeProps {
|
export interface FieldTypeProps {
|
||||||
field: FormFieldFragment
|
field: FormFieldFragment
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
import {Form, Switch} from 'antd'
|
import { Form, Switch } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FieldTypeProps} from './type.props'
|
import { FieldTypeProps } from './type.props'
|
||||||
|
|
||||||
export const YesNoType: React.FC<FieldTypeProps> = ({field}) => {
|
export const YesNoType: React.FC<FieldTypeProps> = ({ field }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={[field.id, 'value']}
|
name={[field.id, 'value']}
|
||||||
rules={[
|
rules={[{ required: field.required, message: 'Please provide Information' }]}
|
||||||
{ required: field.required, message: 'Please provide Information' },
|
|
||||||
]}
|
|
||||||
initialValue={field.value}
|
initialValue={field.value}
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import React, {useEffect} from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import {BlockPicker} from 'react-color'
|
import { BlockPicker } from 'react-color'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value?: string
|
value?: string
|
||||||
onChange?: any
|
onChange?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputColor: React.FC<Props> = props => {
|
export const InputColor: React.FC<Props> = (props) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!props.value) {
|
if (!props.value) {
|
||||||
props.onChange('#FFF')
|
props.onChange('#FFF')
|
||||||
@ -18,13 +18,13 @@ export const InputColor: React.FC<Props> = props => {
|
|||||||
triangle={'hide'}
|
triangle={'hide'}
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
color={props.value}
|
color={props.value}
|
||||||
onChange={e => props.onChange(e.hex)}
|
onChange={(e) => props.onChange(e.hex)}
|
||||||
styles={{
|
styles={{
|
||||||
default: {
|
default: {
|
||||||
card: {
|
card: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
boxShadow: 'none'
|
boxShadow: 'none',
|
||||||
},
|
},
|
||||||
head: {
|
head: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
import {Spin} from 'antd'
|
import { Spin } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message?: string
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadingPage: React.FC<Props> = props => {
|
export const LoadingPage: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
height: '100vh',
|
style={{
|
||||||
justifyContent: 'center',
|
height: '100vh',
|
||||||
alignItems: 'center',
|
justifyContent: 'center',
|
||||||
display: 'flex',
|
alignItems: 'center',
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
}}>
|
flexDirection: 'column',
|
||||||
<Spin size="large"/>
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="large" />
|
||||||
{props.message}
|
{props.message}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {HomeOutlined, MessageOutlined, TeamOutlined} from '@ant-design/icons'
|
import { HomeOutlined, MessageOutlined, TeamOutlined } from '@ant-design/icons'
|
||||||
import {UserOutlined} from '@ant-design/icons/lib'
|
import { UserOutlined } from '@ant-design/icons/lib'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export interface SideMenuElement {
|
export interface SideMenuElement {
|
||||||
@ -9,7 +9,7 @@ export interface SideMenuElement {
|
|||||||
name: string
|
name: string
|
||||||
group?: boolean
|
group?: boolean
|
||||||
href?: string
|
href?: string
|
||||||
icon?: any
|
icon?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sideMenu: SideMenuElement[] = [
|
export const sideMenu: SideMenuElement[] = [
|
||||||
@ -36,7 +36,7 @@ export const sideMenu: SideMenuElement[] = [
|
|||||||
href: '/admin/forms',
|
href: '/admin/forms',
|
||||||
icon: <MessageOutlined />,
|
icon: <MessageOutlined />,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'administration',
|
key: 'administration',
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import {CaretDownOutlined, UserOutlined} from '@ant-design/icons'
|
import { CaretDownOutlined, UserOutlined } from '@ant-design/icons'
|
||||||
import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons/lib'
|
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons/lib'
|
||||||
import {Dropdown, Layout, Menu, PageHeader, Select, Spin, Tag} from 'antd'
|
import { Dropdown, Layout, Menu, PageHeader, Select, Spin, Tag } from 'antd'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {FunctionComponent} from 'react'
|
import React, { CSSProperties, FunctionComponent } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {languages} from '../i18n'
|
import { languages } from '../i18n'
|
||||||
import {sideMenu, SideMenuElement} from './sidemenu'
|
import { sideMenu, SideMenuElement } from './sidemenu'
|
||||||
import {useWindowSize} from './use.window.size'
|
import { useWindowSize } from './use.window.size'
|
||||||
import {clearAuth} from './with.auth'
|
import { clearAuth } from './with.auth'
|
||||||
|
|
||||||
const { SubMenu, ItemGroup } = Menu
|
const { SubMenu, ItemGroup } = Menu
|
||||||
const { Header, Content, Sider } = Layout
|
const { Header, Content, Sider } = Layout
|
||||||
@ -22,15 +22,14 @@ interface BreadcrumbEntry {
|
|||||||
interface Props {
|
interface Props {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
padded?: boolean
|
padded?: boolean
|
||||||
style?: any
|
style?: CSSProperties
|
||||||
|
|
||||||
selected?: string
|
selected?: string
|
||||||
|
|
||||||
|
|
||||||
breadcrumbs?: BreadcrumbEntry[]
|
breadcrumbs?: BreadcrumbEntry[]
|
||||||
title?: string
|
title?: string
|
||||||
subTitle?: string
|
subTitle?: string
|
||||||
extra?: any[]
|
extra?: JSX.Element[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const Structure: FunctionComponent<Props> = (props) => {
|
const Structure: FunctionComponent<Props> = (props) => {
|
||||||
@ -63,58 +62,62 @@ const Structure: FunctionComponent<Props> = (props) => {
|
|||||||
}, [props.selected])
|
}, [props.selected])
|
||||||
|
|
||||||
const buildMenu = (data: SideMenuElement[]): JSX.Element[] => {
|
const buildMenu = (data: SideMenuElement[]): JSX.Element[] => {
|
||||||
return data.map((element): JSX.Element => {
|
return data.map(
|
||||||
if (element.items && element.items.length > 0) {
|
(element): JSX.Element => {
|
||||||
if (element.group) {
|
if (element.items && element.items.length > 0) {
|
||||||
|
if (element.group) {
|
||||||
|
return (
|
||||||
|
<ItemGroup
|
||||||
|
key={element.key}
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
paddingTop: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#444',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{element.icon}
|
||||||
|
{element.name}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{buildMenu(element.items)}
|
||||||
|
</ItemGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGroup
|
<SubMenu
|
||||||
key={element.key}
|
key={element.key}
|
||||||
title={(
|
title={
|
||||||
<div style={{
|
<span>
|
||||||
textTransform: 'uppercase',
|
|
||||||
paddingTop: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#444'
|
|
||||||
}}>
|
|
||||||
{element.icon}
|
{element.icon}
|
||||||
{element.name}
|
{element.name}
|
||||||
</div>
|
</span>
|
||||||
)}
|
}
|
||||||
>
|
>
|
||||||
{buildMenu(element.items)}
|
{buildMenu(element.items)}
|
||||||
</ItemGroup>
|
</SubMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenu
|
<Menu.Item
|
||||||
|
onClick={(): void => {
|
||||||
|
if (element.href) {
|
||||||
|
router.push(element.href)
|
||||||
|
}
|
||||||
|
}}
|
||||||
key={element.key}
|
key={element.key}
|
||||||
title={
|
|
||||||
<span>
|
|
||||||
{element.icon}
|
|
||||||
{element.name}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{buildMenu(element.items)}
|
{element.icon}
|
||||||
</SubMenu>
|
{element.name}
|
||||||
|
</Menu.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
return (
|
|
||||||
<Menu.Item
|
|
||||||
onClick={(): void => {
|
|
||||||
if (element.href) {
|
|
||||||
router.push(element.href)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key={element.key}
|
|
||||||
>
|
|
||||||
{element.icon}
|
|
||||||
{element.name}
|
|
||||||
</Menu.Item>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const signOut = async (): Promise<void> => {
|
const signOut = async (): Promise<void> => {
|
||||||
@ -129,46 +132,57 @@ const Structure: FunctionComponent<Props> = (props) => {
|
|||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{
|
<div
|
||||||
float: 'left',
|
style={{
|
||||||
color: '#FFF',
|
float: 'left',
|
||||||
fontSize: 14,
|
color: '#FFF',
|
||||||
marginRight: 26,
|
fontSize: 14,
|
||||||
fontWeight: 'bold'
|
marginRight: 26,
|
||||||
}}>
|
fontWeight: 'bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, {
|
{React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, {
|
||||||
className: 'sidebar-toggle',
|
className: 'sidebar-toggle',
|
||||||
onClick: () => setSidebar(!sidebar),
|
onClick: () => setSidebar(!sidebar),
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<img src={require('assets/images/logo_white_small.png')} height={30} style={{marginRight: 16}} alt={'OhMyForm'} />
|
<img
|
||||||
|
src={require('assets/images/logo_white_small.png')}
|
||||||
|
height={30}
|
||||||
|
style={{ marginRight: 16 }}
|
||||||
|
alt={'OhMyForm'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{float: 'right', display: 'flex', height: '100%'}}>
|
<div style={{ float: 'right', display: 'flex', height: '100%' }}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
overlay={(
|
overlay={
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
|
<Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
|
||||||
<Menu.Divider/>
|
<Menu.Divider />
|
||||||
<Menu.Item onClick={signOut}>Logout</Menu.Item>
|
<Menu.Item onClick={signOut}>Logout</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
}
|
||||||
onVisibleChange={setUserMenu}
|
onVisibleChange={setUserMenu}
|
||||||
visible={userMenu}
|
visible={userMenu}
|
||||||
>
|
>
|
||||||
<a style={{
|
<div
|
||||||
color: '#FFF',
|
style={{
|
||||||
alignItems: 'center',
|
color: '#FFF',
|
||||||
display: 'inline-flex',
|
alignItems: 'center',
|
||||||
}}>
|
display: 'inline-flex',
|
||||||
<UserOutlined style={{fontSize: 24}} />
|
}}
|
||||||
|
>
|
||||||
|
<UserOutlined style={{ fontSize: 24 }} />
|
||||||
<CaretDownOutlined />
|
<CaretDownOutlined />
|
||||||
</a>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</Header>
|
</Header>
|
||||||
<Layout style={{
|
<Layout
|
||||||
height: '100%',
|
style={{
|
||||||
}}>
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Sider
|
<Sider
|
||||||
collapsed={sidebar}
|
collapsed={sidebar}
|
||||||
trigger={null}
|
trigger={null}
|
||||||
@ -193,20 +207,21 @@ const Structure: FunctionComponent<Props> = (props) => {
|
|||||||
>
|
>
|
||||||
{buildMenu(sideMenu)}
|
{buildMenu(sideMenu)}
|
||||||
</Menu>
|
</Menu>
|
||||||
<Menu
|
<Menu mode="inline" selectable={false}>
|
||||||
mode="inline"
|
|
||||||
selectable={false}
|
|
||||||
>
|
|
||||||
<Menu.Item className={'language-selector'}>
|
<Menu.Item className={'language-selector'}>
|
||||||
<Select
|
<Select
|
||||||
bordered={false}
|
bordered={false}
|
||||||
value={i18n.language.replace(/-.*/, '')}
|
value={i18n.language.replace(/-.*/, '')}
|
||||||
onChange={next => i18n.changeLanguage(next)}
|
onChange={(next) => i18n.changeLanguage(next)}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
|
{languages.map((language) => (
|
||||||
|
<Select.Option value={language} key={language}>
|
||||||
|
{t(`language:${language}`)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
@ -222,17 +237,17 @@ const Structure: FunctionComponent<Props> = (props) => {
|
|||||||
extra={props.extra}
|
extra={props.extra}
|
||||||
breadcrumb={{
|
breadcrumb={{
|
||||||
routes: [
|
routes: [
|
||||||
...(props.breadcrumbs || []).map(b => ({
|
...(props.breadcrumbs || []).map((b) => ({
|
||||||
breadcrumbName: b.name,
|
breadcrumbName: b.name,
|
||||||
path: ''
|
path: '',
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
breadcrumbName: props.title,
|
breadcrumbName: props.title,
|
||||||
path: ''
|
path: '',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
params: props.breadcrumbs,
|
params: props.breadcrumbs,
|
||||||
itemRender: (route, params: BreadcrumbEntry[], routes, paths) => {
|
itemRender(route, params: BreadcrumbEntry[], routes) {
|
||||||
if (routes.indexOf(route) === routes.length - 1) {
|
if (routes.indexOf(route) === routes.length - 1) {
|
||||||
return <span>{route.breadcrumbName}</span>
|
return <span>{route.breadcrumbName}</span>
|
||||||
}
|
}
|
||||||
@ -240,27 +255,22 @@ const Structure: FunctionComponent<Props> = (props) => {
|
|||||||
const entry = params[routes.indexOf(route)]
|
const entry = params[routes.indexOf(route)]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link href={entry.href} as={entry.as || entry.href}>
|
||||||
href={entry.href}
|
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||||
as={entry.as || entry.href}
|
<a>{entry.name}</a>
|
||||||
>
|
|
||||||
<a>
|
|
||||||
{entry.name}
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}}}
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spin
|
<Spin spinning={!!props.loading}>
|
||||||
spinning={!!props.loading}
|
|
||||||
>
|
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
background: props.padded ? '#fff' : null,
|
background: props.padded ? '#fff' : null,
|
||||||
padding: props.padded ? 24 : 0,
|
padding: props.padded ? 24 : 0,
|
||||||
...props.style
|
...props.style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@ -1,29 +1,27 @@
|
|||||||
import {Button} from 'antd'
|
import { Button } from 'antd'
|
||||||
import {ButtonProps} from 'antd/lib/button/button'
|
import { ButtonProps } from 'antd/lib/button/button'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {darken, lighten} from './color.change'
|
import { darken, lighten } from './color.change'
|
||||||
|
|
||||||
interface Props extends ButtonProps {
|
interface Props extends ButtonProps {
|
||||||
background: any
|
background: string
|
||||||
highlight: any
|
highlight: string
|
||||||
color: any
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Styled = styled(Button)`
|
const Styled = styled(Button)`
|
||||||
background: ${props => props.background};
|
background: ${(props) => props.background};
|
||||||
color: ${props => props.color};
|
color: ${(props) => props.color};
|
||||||
border-color: ${props => darken(props.background, 10)};
|
border-color: ${(props) => darken(props.background, 10)};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
color: ${props => props.highlight};
|
color: ${(props) => props.highlight};
|
||||||
background-color: ${props => lighten(props.background, 10)};
|
background-color: ${(props) => lighten(props.background, 10)};
|
||||||
border-color: ${props => darken(props.highlight, 10)};
|
border-color: ${(props) => darken(props.highlight, 10)};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledButton: React.FC<Props> = ({children, ...props}) => {
|
export const StyledButton: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Styled {...props}>{children}</Styled>
|
||||||
<Styled {...props}>{children}</Styled>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,58 +3,58 @@
|
|||||||
*
|
*
|
||||||
* @author Chris Coyier
|
* @author Chris Coyier
|
||||||
*/
|
*/
|
||||||
function LightenDarkenColor(col, amt) {
|
function LightenDarkenColor(col, amt): string {
|
||||||
let usePound = false;
|
let usePound = false
|
||||||
|
|
||||||
if (col[0] == "#") {
|
if (col[0] == '#') {
|
||||||
col = col.slice(1);
|
col = col.slice(1)
|
||||||
usePound = true;
|
usePound = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = parseInt(col, 16)
|
const num = parseInt(col, 16)
|
||||||
|
|
||||||
let r = (num >> 16) + amt;
|
let r = (num >> 16) + amt
|
||||||
|
|
||||||
if (r > 255) r = 255;
|
if (r > 255) r = 255
|
||||||
else if (r < 0) r = 0;
|
else if (r < 0) r = 0
|
||||||
|
|
||||||
let b = ((num >> 8) & 0x00FF) + amt;
|
let b = ((num >> 8) & 0x00ff) + amt
|
||||||
|
|
||||||
if (b > 255) b = 255;
|
if (b > 255) b = 255
|
||||||
else if (b < 0) b = 0;
|
else if (b < 0) b = 0
|
||||||
|
|
||||||
let g = (num & 0x0000FF) + amt;
|
let g = (num & 0x0000ff) + amt
|
||||||
|
|
||||||
if (g > 255) g = 255;
|
if (g > 255) g = 255
|
||||||
else if (g < 0) g = 0;
|
else if (g < 0) g = 0
|
||||||
|
|
||||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transparentize = (col: string, amt: number) => {
|
export const transparentize = (col: string, amt: number): string => {
|
||||||
if (col[0] == "#") {
|
if (col[0] == '#') {
|
||||||
col = col.slice(1);
|
col = col.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = parseInt(col, 16)
|
const num = parseInt(col, 16)
|
||||||
|
|
||||||
let r = (num >> 16) + amt;
|
let r = (num >> 16) + amt
|
||||||
|
|
||||||
if (r > 255) r = 255;
|
if (r > 255) r = 255
|
||||||
else if (r < 0) r = 0;
|
else if (r < 0) r = 0
|
||||||
|
|
||||||
let b = ((num >> 8) & 0x00FF) + amt;
|
let b = ((num >> 8) & 0x00ff) + amt
|
||||||
|
|
||||||
if (b > 255) b = 255;
|
if (b > 255) b = 255
|
||||||
else if (b < 0) b = 0;
|
else if (b < 0) b = 0
|
||||||
|
|
||||||
let g = (num & 0x0000FF) + amt;
|
let g = (num & 0x0000ff) + amt
|
||||||
|
|
||||||
if (g > 255) g = 255;
|
if (g > 255) g = 255
|
||||||
else if (g < 0) g = 0;
|
else if (g < 0) g = 0
|
||||||
|
|
||||||
return `rgba(${r}, ${b}, ${g}, ${1 - amt / 100})`
|
return `rgba(${r}, ${b}, ${g}, ${1 - amt / 100})`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lighten = (color: string, amount: number) => LightenDarkenColor(color, amount)
|
export const lighten = (color: string, amount: number): string => LightenDarkenColor(color, amount)
|
||||||
export const darken = (color: string, amount: number) => LightenDarkenColor(color, -amount)
|
export const darken = (color: string, amount: number): string => LightenDarkenColor(color, -amount)
|
||||||
|
|||||||
@ -1,51 +1,49 @@
|
|||||||
import {DatePicker} from 'antd'
|
import { DatePicker } from 'antd'
|
||||||
import {PickerProps} from 'antd/lib/date-picker/generatePicker'
|
import { PickerProps } from 'antd/lib/date-picker/generatePicker'
|
||||||
import {Moment} from 'moment'
|
import { Moment } from 'moment'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {transparentize} from './color.change'
|
import { transparentize } from './color.change'
|
||||||
|
|
||||||
type Props = { design: FormDesignFragment } & PickerProps<Moment>
|
type Props = { design: FormDesignFragment } & PickerProps<Moment>
|
||||||
|
|
||||||
const Field = styled(DatePicker)`
|
const Field = styled(DatePicker)`
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:hover,
|
:hover,
|
||||||
:active {
|
:active {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ant-picker {
|
&.ant-picker {
|
||||||
box-shadow: none
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-picker-clear {
|
.ant-picker-clear {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${props => transparentize(props.design.colors.answerColor, 60)}
|
color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledDateInput: React.FC<Props> = ({children, ...props}) => {
|
export const StyledDateInput: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'question' | 'answer'
|
type: 'question' | 'answer'
|
||||||
@ -8,11 +8,12 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Header = styled.h1`
|
const Header = styled.h1`
|
||||||
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor}
|
color: ${(props) =>
|
||||||
|
props.type === 'question'
|
||||||
|
? props.design.colors.questionColor
|
||||||
|
: props.design.colors.answerColor};
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledH1: React.FC<Props> = ({children, ...props}) => {
|
export const StyledH1: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Header {...props}>{children}</Header>
|
||||||
<Header {...props}>{children}</Header>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'question' | 'answer'
|
type: 'question' | 'answer'
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
}
|
}
|
||||||
const Header = styled.h2`
|
const Header = styled.h2`
|
||||||
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor}
|
color: ${(props) =>
|
||||||
|
props.type === 'question'
|
||||||
|
? props.design.colors.questionColor
|
||||||
|
: props.design.colors.answerColor};
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledH2: React.FC<Props> = ({children, ...props}) => {
|
export const StyledH2: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Header {...props}>{children}</Header>
|
||||||
<Header {...props}>{children}</Header>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +1,50 @@
|
|||||||
import {Input} from 'antd'
|
import { Input } from 'antd'
|
||||||
import {InputProps} from 'antd/lib/input/Input'
|
import { InputProps } from 'antd/lib/input/Input'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {transparentize} from './color.change'
|
import { transparentize } from './color.change'
|
||||||
|
|
||||||
interface Props extends InputProps{
|
interface Props extends InputProps {
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = styled(Input)`
|
const Field = styled(Input)`
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: ${props => props.design.colors.answerColor} auto 5px
|
outline: ${(props) => props.design.colors.answerColor} auto 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:hover,
|
:hover,
|
||||||
:active {
|
:active {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ant-input-affix-wrapper {
|
&.ant-input-affix-wrapper {
|
||||||
box-shadow: none
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${props => transparentize(props.design.colors.answerColor, 60)}
|
color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledInput: React.FC<Props> = ({children, ...props}) => {
|
export const StyledInput: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,53 +1,51 @@
|
|||||||
import {InputNumber} from 'antd'
|
import { InputNumber } from 'antd'
|
||||||
import {InputNumberProps} from 'antd/lib/input-number'
|
import { InputNumberProps } from 'antd/lib/input-number'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {transparentize} from './color.change'
|
import { transparentize } from './color.change'
|
||||||
|
|
||||||
interface Props extends InputNumberProps {
|
interface Props extends InputNumberProps {
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = styled(InputNumber)`
|
const Field = styled(InputNumber)`
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: ${props => props.design.colors.answerColor} auto 5px
|
outline: ${(props) => props.design.colors.answerColor} auto 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:hover,
|
:hover,
|
||||||
:active {
|
:active {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ant-input-number {
|
&.ant-input-number {
|
||||||
box-shadow: none
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${props => transparentize(props.design.colors.answerColor, 60)}
|
color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledNumberInput: React.FC<Props> = ({children, ...props}) => {
|
export const StyledNumberInput: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'question' | 'answer'
|
type: 'question' | 'answer'
|
||||||
@ -8,11 +8,12 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Paragraph = styled.p`
|
const Paragraph = styled.p`
|
||||||
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor}
|
color: ${(props) =>
|
||||||
|
props.type === 'question'
|
||||||
|
? props.design.colors.questionColor
|
||||||
|
: props.design.colors.answerColor};
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledP: React.FC<Props> = ({children, ...props}) => {
|
export const StyledP: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Paragraph {...props}>{children}</Paragraph>
|
||||||
<Paragraph {...props}>{children}</Paragraph>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,37 @@
|
|||||||
import {Radio} from 'antd'
|
import { Radio } from 'antd'
|
||||||
import {RadioProps} from 'antd/lib/radio/interface'
|
import { RadioProps } from 'antd/lib/radio/interface'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
interface Props extends RadioProps {
|
interface Props extends RadioProps {
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = styled(Radio)`
|
const Field = styled(Radio)`
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
.ant-radio {
|
.ant-radio {
|
||||||
.ant-radio-inner {
|
.ant-radio-inner {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: ${props => props.design.colors.answerColor};
|
background: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledRadio: React.FC<Props> = ({children, ...props}) => {
|
export const StyledRadio: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Select} from 'antd'
|
import { Select } from 'antd'
|
||||||
import {SelectProps} from 'antd/lib/select'
|
import { SelectProps } from 'antd/lib/select'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {transparentize} from './color.change'
|
import { transparentize } from './color.change'
|
||||||
|
|
||||||
interface Props extends SelectProps<string> {
|
interface Props extends SelectProps<string> {
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
@ -11,8 +11,8 @@ interface Props extends SelectProps<string> {
|
|||||||
|
|
||||||
const Field = styled(Select)`
|
const Field = styled(Select)`
|
||||||
.ant-select-selector {
|
.ant-select-selector {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor} !important;
|
border-color: ${(props) => props.design.colors.answerColor} !important;
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border-right: none !important;
|
border-right: none !important;
|
||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
@ -20,32 +20,30 @@ const Field = styled(Select)`
|
|||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: ${props => props.design.colors.answerColor} auto 5px
|
outline: ${(props) => props.design.colors.answerColor} auto 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:hover,
|
:hover,
|
||||||
:active {
|
:active {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${props => transparentize(props.design.colors.answerColor, 60)}
|
color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledSelect: React.FC<Props> = ({children, ...props}) => {
|
export const StyledSelect: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +1,48 @@
|
|||||||
import {Input} from 'antd'
|
import { Input } from 'antd'
|
||||||
import {TextAreaProps} from 'antd/lib/input/TextArea'
|
import { TextAreaProps } from 'antd/lib/input/TextArea'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
|
||||||
import {transparentize} from './color.change'
|
import { transparentize } from './color.change'
|
||||||
|
|
||||||
interface Props extends TextAreaProps {
|
interface Props extends TextAreaProps {
|
||||||
design: FormDesignFragment
|
design: FormDesignFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = styled(Input.TextArea)`
|
const Field = styled(Input.TextArea)`
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
:hover,
|
:hover,
|
||||||
:active {
|
:active {
|
||||||
border-color: ${props => props.design.colors.answerColor};
|
border-color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${props => transparentize(props.design.colors.answerColor, 60)}
|
color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
color: ${props => props.design.colors.answerColor};
|
color: ${(props) => props.design.colors.answerColor};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledTextareaInput: React.FC<Props> = ({children, ...props}) => {
|
export const StyledTextareaInput: React.FC<Props> = ({ children, ...props }) => {
|
||||||
return (
|
return <Field {...props}>{children}</Field>
|
||||||
<Field {...props}>{children}</Field>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Tooltip} from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@ -9,15 +9,17 @@ interface Props {
|
|||||||
date: string
|
date: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TimeAgo: React.FC<Props> = props => {
|
export const TimeAgo: React.FC<Props> = (props) => {
|
||||||
const date = dayjs(props.date)
|
const date = dayjs(props.date)
|
||||||
return (
|
return (
|
||||||
<Tooltip title={date.format('YYYY-MM-DD HH:mm:ss')}>
|
<Tooltip title={date.format('YYYY-MM-DD HH:mm:ss')}>
|
||||||
<div style={{
|
<div
|
||||||
display: 'inline-block'
|
style={{
|
||||||
}}>
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{date.fromNow()}
|
{date.fromNow()}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,47 @@
|
|||||||
import {useMutation} from '@apollo/react-hooks'
|
import { useMutation } from '@apollo/react-hooks'
|
||||||
import {useCallback, useEffect, useState} from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
SUBMISSION_SET_FIELD_MUTATION,
|
SUBMISSION_SET_FIELD_MUTATION,
|
||||||
SubmissionSetFieldMutationData,
|
SubmissionSetFieldMutationData,
|
||||||
SubmissionSetFieldMutationVariables
|
SubmissionSetFieldMutationVariables,
|
||||||
} from '../graphql/mutation/submission.set.field.mutation'
|
} from '../graphql/mutation/submission.set.field.mutation'
|
||||||
import {
|
import {
|
||||||
SUBMISSION_START_MUTATION,
|
SUBMISSION_START_MUTATION,
|
||||||
SubmissionStartMutationData,
|
SubmissionStartMutationData,
|
||||||
SubmissionStartMutationVariables
|
SubmissionStartMutationVariables,
|
||||||
} from '../graphql/mutation/submission.start.mutation'
|
} from '../graphql/mutation/submission.start.mutation'
|
||||||
|
|
||||||
export const useSubmission = (formId: string) => {
|
interface Submission {
|
||||||
const [submission, setSubmission] = useState<{ id: string, token: string }>()
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
setField: (fieldId: string, data: any) => Promise<void>
|
||||||
|
finish: () => void
|
||||||
|
}
|
||||||
|
|
||||||
const [start] = useMutation<SubmissionStartMutationData, SubmissionStartMutationVariables>(SUBMISSION_START_MUTATION)
|
export const useSubmission = (formId: string): Submission => {
|
||||||
const [save] = useMutation<SubmissionSetFieldMutationData, SubmissionSetFieldMutationVariables>(SUBMISSION_SET_FIELD_MUTATION)
|
const [submission, setSubmission] = useState<{ id: string; token: string }>()
|
||||||
|
|
||||||
|
const [start] = useMutation<SubmissionStartMutationData, SubmissionStartMutationVariables>(
|
||||||
|
SUBMISSION_START_MUTATION
|
||||||
|
)
|
||||||
|
const [save] = useMutation<SubmissionSetFieldMutationData, SubmissionSetFieldMutationVariables>(
|
||||||
|
SUBMISSION_SET_FIELD_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
;(async () => {
|
||||||
const token = [...Array(40)].map(() => Math.random().toString(36)[2]).join('')
|
const token = [...Array(40)].map(() => Math.random().toString(36)[2]).join('')
|
||||||
|
|
||||||
const {data} = await start({
|
const { data } = await start({
|
||||||
variables: {
|
variables: {
|
||||||
form: formId,
|
form: formId,
|
||||||
submission: {
|
submission: {
|
||||||
token,
|
token,
|
||||||
device: {
|
device: {
|
||||||
name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop',
|
name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop',
|
||||||
type: window.navigator.userAgent
|
type: window.navigator.userAgent,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
setSubmission({
|
setSubmission({
|
||||||
@ -41,19 +51,23 @@ export const useSubmission = (formId: string) => {
|
|||||||
})()
|
})()
|
||||||
}, [formId])
|
}, [formId])
|
||||||
|
|
||||||
const setField = useCallback(async (fieldId: string, data: any) => {
|
const setField = useCallback(
|
||||||
console.log('just save', fieldId, data)
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await save({
|
async (fieldId: string, data: any) => {
|
||||||
variables: {
|
console.log('just save', fieldId, data)
|
||||||
submission: submission.id,
|
await save({
|
||||||
field: {
|
variables: {
|
||||||
token: submission.token,
|
submission: submission.id,
|
||||||
field: fieldId,
|
field: {
|
||||||
data: JSON.stringify(data)
|
token: submission.token,
|
||||||
}
|
field: fieldId,
|
||||||
}
|
data: JSON.stringify(data),
|
||||||
})
|
},
|
||||||
}, [submission])
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[submission]
|
||||||
|
)
|
||||||
|
|
||||||
const finish = useCallback(() => {
|
const finish = useCallback(() => {
|
||||||
console.log('finish submission!!', formId)
|
console.log('finish submission!!', formId)
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
import {useEffect, useState} from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
export const useWindowSize = () => {
|
export const useWindowSize = (): { width?: number; height?: number } => {
|
||||||
const isClient = typeof window === 'object';
|
const isClient = typeof window === 'object'
|
||||||
|
|
||||||
function getSize() {
|
function getSize() {
|
||||||
return {
|
return {
|
||||||
width: isClient ? window.innerWidth : undefined,
|
width: isClient ? window.innerWidth : undefined,
|
||||||
height: isClient ? window.innerHeight : undefined
|
height: isClient ? window.innerHeight : undefined,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [windowSize, setWindowSize] = useState(getSize);
|
const [windowSize, setWindowSize] = useState(getSize)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isClient) {
|
if (!isClient) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
setWindowSize(getSize());
|
setWindowSize(getSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize)
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize)
|
||||||
}, []); // Empty array ensures that effect is only run on mount and unmount
|
}, []) // Empty array ensures that effect is only run on mount and unmount
|
||||||
|
|
||||||
return windowSize;
|
return windowSize
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {Form, Input, Select, Tabs} from 'antd'
|
import { Form, Input, Select, Tabs } from 'antd'
|
||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import { TabPaneProps } from 'antd/lib/tabs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {languages} from '../../../i18n'
|
import { languages } from '../../../i18n'
|
||||||
|
|
||||||
export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
export const BaseDataTab: React.FC<TabPaneProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Tabs.TabPane {...props}>
|
<Tabs.TabPane {...props}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -45,7 +45,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
message: 'Please select a role',
|
message: 'Please select a role',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
getValueFromEvent={e => {
|
getValueFromEvent={(e) => {
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case 'superuser':
|
case 'superuser':
|
||||||
return ['user', 'admin', 'superuser']
|
return ['user', 'admin', 'superuser']
|
||||||
@ -55,7 +55,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
return ['user']
|
return ['user']
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
getValueProps={v => {
|
getValueProps={(v) => {
|
||||||
let role = 'user'
|
let role = 'user'
|
||||||
|
|
||||||
if (v && v.includes('superuser')) {
|
if (v && v.includes('superuser')) {
|
||||||
@ -65,12 +65,16 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: role
|
value: role,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{['user', 'admin', 'superuser'].map(role => <Select.Option value={role} key={role}>{role.toUpperCase()}</Select.Option> )}
|
{['user', 'admin', 'superuser'].map((role) => (
|
||||||
|
<Select.Option value={role} key={role}>
|
||||||
|
{role.toUpperCase()}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -85,24 +89,21 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{languages.map(language => <Select.Option value={language} key={language}>{language.toUpperCase()}</Select.Option> )}
|
{languages.map((language) => (
|
||||||
|
<Select.Option value={language} key={language}>
|
||||||
|
{language.toUpperCase()}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label="First Name" name={['user', 'firstName']}>
|
||||||
label="First Name"
|
<Input />
|
||||||
name={['user', 'firstName']}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label="Last Name" name={['user', 'lastName']}>
|
||||||
label="Last Name"
|
<Input />
|
||||||
name={['user', 'lastName']}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import {Tag} from "antd"
|
import { Tag } from 'antd'
|
||||||
import React, {CSSProperties} from 'react'
|
import React, { CSSProperties } from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
roles: string[]
|
roles: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserRole: React.FC<Props> = props => {
|
export const UserRole: React.FC<Props> = (props) => {
|
||||||
let color
|
let color
|
||||||
let level = 'unknown'
|
let level = 'unknown'
|
||||||
const css: CSSProperties = {}
|
const css: CSSProperties = {}
|
||||||
|
|
||||||
|
|
||||||
if (props.roles.includes('superuser')) {
|
if (props.roles.includes('superuser')) {
|
||||||
color = 'red'
|
color = 'red'
|
||||||
level = 'superuser'
|
level = 'superuser'
|
||||||
@ -24,10 +23,7 @@ export const UserRole: React.FC<Props> = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag
|
<Tag color={color} style={css}>
|
||||||
color={color}
|
|
||||||
style={css}
|
|
||||||
>
|
|
||||||
{level.toUpperCase()}
|
{level.toUpperCase()}
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import {useQuery} from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import {AxiosRequestConfig} from 'axios'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {ME_QUERY, MeQueryData} from '../graphql/query/me.query'
|
import { ME_QUERY, MeQueryData } from '../graphql/query/me.query'
|
||||||
import {LoadingPage} from './loading.page'
|
import { LoadingPage } from './loading.page'
|
||||||
|
|
||||||
export const clearAuth = async () => {
|
export const clearAuth = async (): Promise<void> => {
|
||||||
localStorage.removeItem('access')
|
localStorage.removeItem('access')
|
||||||
localStorage.removeItem('refresh')
|
localStorage.removeItem('refresh')
|
||||||
|
|
||||||
// TODO logout on server!
|
// TODO logout on server!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setAuth = (access, refresh) => {
|
export const setAuth = (access: string, refresh: string): void => {
|
||||||
localStorage.setItem('access', access)
|
localStorage.setItem('access', access)
|
||||||
localStorage.setItem('refresh', refresh)
|
localStorage.setItem('refresh', refresh)
|
||||||
}
|
}
|
||||||
@ -37,12 +37,14 @@ export const authConfig = async (config: AxiosRequestConfig = {}): Promise<Axios
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
export const withAuth = (Component, roles: string[] = []): React.FC => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
|
||||||
return props => {
|
export const withAuth = (Component: any, roles: string[] = []): React.FC => {
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
return (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [access, setAccess] = useState(false)
|
const [access, setAccess] = useState(false)
|
||||||
const {loading, data, error} = useQuery<MeQueryData>(ME_QUERY)
|
const { loading, data, error } = useQuery<MeQueryData>(ME_QUERY)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (roles.length === 0) {
|
if (roles.length === 0) {
|
||||||
@ -67,10 +69,7 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = roles
|
const next = roles.map((role) => data.me.roles.includes(role)).filter((p) => p).length > 0
|
||||||
.map(role => data.me.roles.includes(role))
|
|
||||||
.filter(p => p)
|
|
||||||
.length > 0
|
|
||||||
|
|
||||||
setAccess(next)
|
setAccess(next)
|
||||||
|
|
||||||
@ -88,5 +87,5 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <Component me={data && data.me} {...props} />
|
return <Component me={data && data.me} {...props} />
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminFormPageFragment {
|
export interface AdminFormPageFragment {
|
||||||
show: boolean
|
show: boolean
|
||||||
@ -102,7 +102,7 @@ export const ADMIN_FORM_FRAGMENT = gql`
|
|||||||
language
|
language
|
||||||
showFooter
|
showFooter
|
||||||
isLive
|
isLive
|
||||||
|
|
||||||
fields {
|
fields {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@ -110,13 +110,13 @@ export const ADMIN_FORM_FRAGMENT = gql`
|
|||||||
description
|
description
|
||||||
required
|
required
|
||||||
value
|
value
|
||||||
|
|
||||||
options {
|
options {
|
||||||
key
|
key
|
||||||
title
|
title
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
logicJump {
|
logicJump {
|
||||||
fieldA
|
fieldA
|
||||||
valueB
|
valueB
|
||||||
@ -129,7 +129,7 @@ export const ADMIN_FORM_FRAGMENT = gql`
|
|||||||
shape
|
shape
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selfNotifications {
|
selfNotifications {
|
||||||
enabled
|
enabled
|
||||||
subject
|
subject
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminProfileFragment {
|
export interface AdminProfileFragment {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminUserFragment {
|
export interface AdminUserFragment {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface FormPageFragment {
|
export interface FormPageFragment {
|
||||||
show: boolean
|
show: boolean
|
||||||
@ -77,7 +77,7 @@ export const FORM_FRAGMENT = gql`
|
|||||||
title
|
title
|
||||||
language
|
language
|
||||||
showFooter
|
showFooter
|
||||||
|
|
||||||
fields {
|
fields {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@ -85,13 +85,13 @@ export const FORM_FRAGMENT = gql`
|
|||||||
description
|
description
|
||||||
required
|
required
|
||||||
value
|
value
|
||||||
|
|
||||||
options {
|
options {
|
||||||
key
|
key
|
||||||
title
|
title
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
logicJump {
|
logicJump {
|
||||||
fieldA
|
fieldA
|
||||||
valueB
|
valueB
|
||||||
@ -104,7 +104,7 @@ export const FORM_FRAGMENT = gql`
|
|||||||
shape
|
shape
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
design {
|
design {
|
||||||
colors {
|
colors {
|
||||||
backgroundColor
|
backgroundColor
|
||||||
@ -116,7 +116,7 @@ export const FORM_FRAGMENT = gql`
|
|||||||
}
|
}
|
||||||
font
|
font
|
||||||
}
|
}
|
||||||
|
|
||||||
startPage {
|
startPage {
|
||||||
show
|
show
|
||||||
title
|
title
|
||||||
@ -131,7 +131,7 @@ export const FORM_FRAGMENT = gql`
|
|||||||
color
|
color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endPage {
|
endPage {
|
||||||
show
|
show
|
||||||
title
|
title
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface SubmissionFragment {
|
export interface SubmissionFragment {
|
||||||
id?: string
|
id?: string
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment'
|
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
|
||||||
|
|
||||||
export interface AdminFormCreateMutationData {
|
export interface AdminFormCreateMutationData {
|
||||||
form: AdminFormFragment
|
form: AdminFormFragment
|
||||||
@ -15,6 +15,6 @@ export const ADMIN_FORM_CREATE_MUTATION = gql`
|
|||||||
...AdminForm
|
...AdminForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_FORM_FRAGMENT}
|
${ADMIN_FORM_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminFormDeleteMutationData {
|
export interface AdminFormDeleteMutationData {
|
||||||
form: {
|
form: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment'
|
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
|
||||||
|
|
||||||
export interface AdminFormUpdateMutationData {
|
export interface AdminFormUpdateMutationData {
|
||||||
form: AdminFormFragment
|
form: AdminFormFragment
|
||||||
@ -15,6 +15,6 @@ export const ADMIN_FORM_UPDATE_MUTATION = gql`
|
|||||||
...AdminForm
|
...AdminForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_FORM_FRAGMENT}
|
${ADMIN_FORM_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_PROFILE_FRAGMENT} from '../fragment/admin.profile.fragment'
|
import { ADMIN_PROFILE_FRAGMENT } from '../fragment/admin.profile.fragment'
|
||||||
import {AdminUserFragment} from '../fragment/admin.user.fragment'
|
import { AdminUserFragment } from '../fragment/admin.user.fragment'
|
||||||
|
|
||||||
export interface AdminProfileUpdateMutationData {
|
export interface AdminProfileUpdateMutationData {
|
||||||
user: AdminUserFragment
|
user: AdminUserFragment
|
||||||
@ -16,6 +16,6 @@ export const ADMIN_PROFILE_UPDATE_MUTATION = gql`
|
|||||||
...AdminProfile
|
...AdminProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_PROFILE_FRAGMENT}
|
${ADMIN_PROFILE_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminUserDeleteMutationData {
|
export interface AdminUserDeleteMutationData {
|
||||||
form: {
|
form: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_USER_FRAGMENT, AdminUserFragment} from '../fragment/admin.user.fragment'
|
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
|
||||||
|
|
||||||
export interface AdminUserUpdateMutationData {
|
export interface AdminUserUpdateMutationData {
|
||||||
user: AdminUserFragment
|
user: AdminUserFragment
|
||||||
@ -15,6 +15,6 @@ export const ADMIN_USER_UPDATE_MUTATION = gql`
|
|||||||
...AdminUser
|
...AdminUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_USER_FRAGMENT}
|
${ADMIN_USER_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export const LOGIN_MUTATION = gql`
|
export const LOGIN_MUTATION = gql`
|
||||||
mutation login($username: String!, $password: String!) {
|
mutation login($username: String!, $password: String!) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export const REGISTER_MUTATION = gql`
|
export const REGISTER_MUTATION = gql`
|
||||||
mutation register($user: UserCreateInput!) {
|
mutation register($user: UserCreateInput!) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export const SUBMISSION_FINISH_MUTATION = gql`
|
export const SUBMISSION_FINISH_MUTATION = gql`
|
||||||
mutation start($submission: ID!,$field: SubmissionSetFieldInput!) {
|
mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {
|
||||||
submission: submissionSetField(submission: $submission, field: $field) {
|
submission: submissionSetField(submission: $submission, field: $field) {
|
||||||
id
|
id
|
||||||
percentageComplete
|
percentageComplete
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface SubmissionSetFieldMutationData {
|
export interface SubmissionSetFieldMutationData {
|
||||||
submission: {
|
submission: {
|
||||||
@ -17,7 +17,7 @@ export interface SubmissionSetFieldMutationVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SUBMISSION_SET_FIELD_MUTATION = gql`
|
export const SUBMISSION_SET_FIELD_MUTATION = gql`
|
||||||
mutation start($submission: ID!,$field: SubmissionSetFieldInput!) {
|
mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {
|
||||||
submission: submissionSetField(submission: $submission, field: $field) {
|
submission: submissionSetField(submission: $submission, field: $field) {
|
||||||
id
|
id
|
||||||
percentageComplete
|
percentageComplete
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface SubmissionStartMutationData {
|
export interface SubmissionStartMutationData {
|
||||||
submission: {
|
submission: {
|
||||||
@ -19,7 +19,7 @@ export interface SubmissionStartMutationVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SUBMISSION_START_MUTATION = gql`
|
export const SUBMISSION_START_MUTATION = gql`
|
||||||
mutation start($form: ID!,$submission: SubmissionStartInput!) {
|
mutation start($form: ID!, $submission: SubmissionStartInput!) {
|
||||||
submission: submissionStart(form: $form, submission: $submission) {
|
submission: submissionStart(form: $form, submission: $submission) {
|
||||||
id
|
id
|
||||||
percentageComplete
|
percentageComplete
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment'
|
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
|
||||||
|
|
||||||
export interface AdminFormQueryData {
|
export interface AdminFormQueryData {
|
||||||
form: AdminFormFragment
|
form: AdminFormFragment
|
||||||
@ -10,11 +10,11 @@ export interface AdminFormQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_FORM_QUERY = gql`
|
export const ADMIN_FORM_QUERY = gql`
|
||||||
query form($id: ID!){
|
query form($id: ID!) {
|
||||||
form:getFormById(id: $id) {
|
form: getFormById(id: $id) {
|
||||||
...AdminForm
|
...AdminForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_FORM_FRAGMENT}
|
${ADMIN_FORM_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminPagerFormEntryQueryData {
|
export interface AdminPagerFormEntryQueryData {
|
||||||
id: string
|
id: string
|
||||||
@ -30,7 +30,7 @@ export interface AdminPagerFormQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_PAGER_FORM_QUERY = gql`
|
export const ADMIN_PAGER_FORM_QUERY = gql`
|
||||||
query pager($start: Int, $limit: Int){
|
query pager($start: Int, $limit: Int) {
|
||||||
pager: listForms(start: $start, limit: $limit) {
|
pager: listForms(start: $start, limit: $limit) {
|
||||||
entries {
|
entries {
|
||||||
id
|
id
|
||||||
@ -45,7 +45,7 @@ export const ADMIN_PAGER_FORM_QUERY = gql`
|
|||||||
username
|
username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
limit
|
limit
|
||||||
start
|
start
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminPagerSubmissionFormFieldQueryData {
|
export interface AdminPagerSubmissionFormFieldQueryData {
|
||||||
title: string
|
title: string
|
||||||
@ -56,13 +56,13 @@ export interface AdminPagerSubmissionQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
|
export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
|
||||||
query pager($form: ID!, $start: Int, $limit: Int){
|
query pager($form: ID!, $start: Int, $limit: Int) {
|
||||||
form: getFormById(id: $form) {
|
form: getFormById(id: $form) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
isLive
|
isLive
|
||||||
}
|
}
|
||||||
|
|
||||||
pager: listSubmissions(form: $form, start: $start, limit: $limit) {
|
pager: listSubmissions(form: $form, start: $start, limit: $limit) {
|
||||||
entries {
|
entries {
|
||||||
id
|
id
|
||||||
@ -78,19 +78,19 @@ export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
|
|||||||
type
|
type
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
||||||
fields {
|
fields {
|
||||||
id
|
id
|
||||||
value
|
value
|
||||||
type
|
type
|
||||||
|
|
||||||
field {
|
field {
|
||||||
title
|
title
|
||||||
required
|
required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
limit
|
limit
|
||||||
start
|
start
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminPagerUserEntryQueryData {
|
export interface AdminPagerUserEntryQueryData {
|
||||||
id: string
|
id: string
|
||||||
@ -24,7 +24,7 @@ export interface AdminPagerUserQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_PAGER_USER_QUERY = gql`
|
export const ADMIN_PAGER_USER_QUERY = gql`
|
||||||
query pager($start: Int, $limit: Int){
|
query pager($start: Int, $limit: Int) {
|
||||||
pager: listUsers(start: $start, limit: $limit) {
|
pager: listUsers(start: $start, limit: $limit) {
|
||||||
entries {
|
entries {
|
||||||
id
|
id
|
||||||
@ -33,7 +33,7 @@ export const ADMIN_PAGER_USER_QUERY = gql`
|
|||||||
email
|
email
|
||||||
created
|
created
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
limit
|
limit
|
||||||
start
|
start
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_PROFILE_FRAGMENT, AdminProfileFragment} from '../fragment/admin.profile.fragment'
|
import { ADMIN_PROFILE_FRAGMENT, AdminProfileFragment } from '../fragment/admin.profile.fragment'
|
||||||
|
|
||||||
export interface AdminProfileQueryData {
|
export interface AdminProfileQueryData {
|
||||||
user: AdminProfileFragment
|
user: AdminProfileFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminProfileQueryVariables {
|
export interface AdminProfileQueryVariables {}
|
||||||
}
|
|
||||||
|
|
||||||
export const ADMIN_PROFILE_QUERY = gql`
|
export const ADMIN_PROFILE_QUERY = gql`
|
||||||
query profile {
|
query profile {
|
||||||
user:me {
|
user: me {
|
||||||
...AdminProfile
|
...AdminProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_PROFILE_FRAGMENT}
|
${ADMIN_PROFILE_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface AdminStatisticQueryData {
|
export interface AdminStatisticQueryData {
|
||||||
forms: {
|
forms: {
|
||||||
@ -12,13 +12,12 @@ export interface AdminStatisticQueryData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminStatisticQueryVariables {
|
export interface AdminStatisticQueryVariables {}
|
||||||
}
|
|
||||||
|
|
||||||
export const ADMIN_STATISTIC_QUERY = gql`
|
export const ADMIN_STATISTIC_QUERY = gql`
|
||||||
query {
|
query {
|
||||||
forms: getFormStatistic {
|
forms: getFormStatistic {
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
submissions: getSubmissionStatistic {
|
submissions: getSubmissionStatistic {
|
||||||
total
|
total
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {ADMIN_USER_FRAGMENT, AdminUserFragment} from '../fragment/admin.user.fragment'
|
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
|
||||||
|
|
||||||
export interface AdminUserQueryData {
|
export interface AdminUserQueryData {
|
||||||
user: AdminUserFragment
|
user: AdminUserFragment
|
||||||
@ -10,11 +10,11 @@ export interface AdminUserQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_USER_QUERY = gql`
|
export const ADMIN_USER_QUERY = gql`
|
||||||
query user($id: ID!){
|
query user($id: ID!) {
|
||||||
user:getUserById(id: $id) {
|
user: getUserById(id: $id) {
|
||||||
...AdminUser
|
...AdminUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ADMIN_USER_FRAGMENT}
|
${ADMIN_USER_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
import {FORM_FRAGMENT, FormFragment} from '../fragment/form.fragment'
|
import { FORM_FRAGMENT, FormFragment } from '../fragment/form.fragment'
|
||||||
|
|
||||||
export interface FormQueryData {
|
export interface FormQueryData {
|
||||||
form: FormFragment
|
form: FormFragment
|
||||||
@ -10,11 +10,11 @@ export interface FormQueryVariables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FORM_QUERY = gql`
|
export const FORM_QUERY = gql`
|
||||||
query form($id: ID!){
|
query form($id: ID!) {
|
||||||
form:getFormById(id: $id) {
|
form: getFormById(id: $id) {
|
||||||
...Form
|
...Form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${FORM_FRAGMENT}
|
${FORM_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface MeQueryData {
|
export interface MeQueryData {
|
||||||
me: {
|
me: {
|
||||||
@ -11,7 +11,7 @@ export interface MeQueryData {
|
|||||||
export const ME_QUERY = gql`
|
export const ME_QUERY = gql`
|
||||||
query {
|
query {
|
||||||
me {
|
me {
|
||||||
id
|
id
|
||||||
roles
|
roles
|
||||||
username
|
username
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {gql} from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
|
||||||
export interface SettingsQueryData {
|
export interface SettingsQueryData {
|
||||||
disabledSignUp: {
|
disabledSignUp: {
|
||||||
@ -7,9 +7,9 @@ export interface SettingsQueryData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SETTINGS_QUERY = gql`
|
export const SETTINGS_QUERY = gql`
|
||||||
query {
|
query {
|
||||||
disabledSignUp: getByKey (key: "SIGNUP_DISABLED") {
|
disabledSignUp: getByKey(key: "SIGNUP_DISABLED") {
|
||||||
value: isTrue
|
value: isTrue
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import {ApolloProvider} from '@apollo/react-common'
|
import { ApolloProvider } from '@apollo/react-common'
|
||||||
import {buildAxiosFetch} from '@lifeomic/axios-fetch'
|
import { buildAxiosFetch } from '@lifeomic/axios-fetch'
|
||||||
import 'antd/dist/antd.css'
|
import 'antd/dist/antd.css'
|
||||||
import ApolloClient from 'apollo-boost'
|
import ApolloClient from 'apollo-boost'
|
||||||
import 'assets/global.scss'
|
import 'assets/global.scss'
|
||||||
import 'assets/variables.scss'
|
import 'assets/variables.scss'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {authConfig} from 'components/with.auth'
|
import { authConfig } from 'components/with.auth'
|
||||||
import 'i18n'
|
import 'i18n'
|
||||||
import {AppProps} from 'next/app'
|
import { AppProps } from 'next/app'
|
||||||
import getConfig from 'next/config'
|
import getConfig from 'next/config'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {wrapper} from 'store'
|
import { wrapper } from 'store'
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig()
|
const { publicRuntimeConfig } = getConfig()
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const client = new ApolloClient({
|
|||||||
fetch: buildAxiosFetch(axios),
|
fetch: buildAxiosFetch(axios),
|
||||||
request: async (operation): Promise<void> => {
|
request: async (operation): Promise<void> => {
|
||||||
operation.setContext(await authConfig())
|
operation.setContext(await authConfig())
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||||
|
|||||||
@ -1,32 +1,35 @@
|
|||||||
import {useMutation, useQuery} from '@apollo/react-hooks'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
import {Button, Form, Input, message, Tabs} from 'antd'
|
import { Button, Form, Input, message, Tabs } from 'antd'
|
||||||
import {useForm} from 'antd/lib/form/Form'
|
import { useForm } from 'antd/lib/form/Form'
|
||||||
import {cleanInput} from 'components/clean.input'
|
import { cleanInput } from 'components/clean.input'
|
||||||
import {BaseDataTab} from 'components/form/admin/base.data.tab'
|
import { BaseDataTab } from 'components/form/admin/base.data.tab'
|
||||||
import {DesignTab} from 'components/form/admin/design.tab'
|
import { DesignTab } from 'components/form/admin/design.tab'
|
||||||
import {EndPageTab} from 'components/form/admin/end.page.tab'
|
import { EndPageTab } from 'components/form/admin/end.page.tab'
|
||||||
import {FieldsTab} from 'components/form/admin/fields.tab'
|
import { FieldsTab } from 'components/form/admin/fields.tab'
|
||||||
import {RespondentNotificationsTab} from 'components/form/admin/respondent.notifications.tab'
|
import { RespondentNotificationsTab } from 'components/form/admin/respondent.notifications.tab'
|
||||||
import {SelfNotificationsTab} from 'components/form/admin/self.notifications.tab'
|
import { SelfNotificationsTab } from 'components/form/admin/self.notifications.tab'
|
||||||
import {StartPageTab} from 'components/form/admin/start.page.tab'
|
import { StartPageTab } from 'components/form/admin/start.page.tab'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {
|
import {
|
||||||
AdminFormFieldFragment,
|
AdminFormFieldFragment,
|
||||||
AdminFormFieldOptionFragment,
|
AdminFormFieldOptionKeysFragment,
|
||||||
AdminFormFieldOptionKeysFragment
|
|
||||||
} from 'graphql/fragment/admin.form.fragment'
|
} from 'graphql/fragment/admin.form.fragment'
|
||||||
import {
|
import {
|
||||||
ADMIN_FORM_UPDATE_MUTATION,
|
ADMIN_FORM_UPDATE_MUTATION,
|
||||||
AdminFormUpdateMutationData,
|
AdminFormUpdateMutationData,
|
||||||
AdminFormUpdateMutationVariables
|
AdminFormUpdateMutationVariables,
|
||||||
} from 'graphql/mutation/admin.form.update.mutation'
|
} from 'graphql/mutation/admin.form.update.mutation'
|
||||||
import {ADMIN_FORM_QUERY, AdminFormQueryData, AdminFormQueryVariables} from 'graphql/query/admin.form.query'
|
import {
|
||||||
import {NextPage} from 'next'
|
ADMIN_FORM_QUERY,
|
||||||
|
AdminFormQueryData,
|
||||||
|
AdminFormQueryVariables,
|
||||||
|
} from 'graphql/query/admin.form.query'
|
||||||
|
import { NextPage } from 'next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -34,19 +37,21 @@ const Index: NextPage = () => {
|
|||||||
const [form] = useForm()
|
const [form] = useForm()
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [fields, setFields] = useState<AdminFormFieldFragment[]>([])
|
const [fields, setFields] = useState<AdminFormFieldFragment[]>([])
|
||||||
const [update] = useMutation<AdminFormUpdateMutationData, AdminFormUpdateMutationVariables>(ADMIN_FORM_UPDATE_MUTATION)
|
const [update] = useMutation<AdminFormUpdateMutationData, AdminFormUpdateMutationVariables>(
|
||||||
|
ADMIN_FORM_UPDATE_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
const processNext = (next: AdminFormQueryData): AdminFormQueryData => {
|
const processNext = (next: AdminFormQueryData): AdminFormQueryData => {
|
||||||
next.form.fields = next.form.fields.map(field => {
|
next.form.fields = next.form.fields.map((field) => {
|
||||||
const keys: AdminFormFieldOptionKeysFragment = {}
|
const keys: AdminFormFieldOptionKeysFragment = {}
|
||||||
|
|
||||||
field.options.forEach(option => {
|
field.options.forEach((option) => {
|
||||||
if (option.key) {
|
if (option.key) {
|
||||||
keys[option.key] = option.value
|
keys[option.key] = option.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
field.options = field.options.filter(option => !option.key)
|
field.options = field.options.filter((option) => !option.key)
|
||||||
field.optionKeys = keys
|
field.optionKeys = keys
|
||||||
return field
|
return field
|
||||||
})
|
})
|
||||||
@ -54,46 +59,55 @@ const Index: NextPage = () => {
|
|||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
const {data, loading, error} = useQuery<AdminFormQueryData, AdminFormQueryVariables>(ADMIN_FORM_QUERY, {
|
const { data, loading } = useQuery<AdminFormQueryData, AdminFormQueryVariables>(
|
||||||
variables: {
|
ADMIN_FORM_QUERY,
|
||||||
id: router.query.id as string
|
{
|
||||||
},
|
variables: {
|
||||||
onCompleted: next => {
|
id: router.query.id as string,
|
||||||
next = processNext(next)
|
},
|
||||||
form.setFieldsValue(next)
|
onCompleted: (next) => {
|
||||||
setFields(next.form.fields)
|
next = processNext(next)
|
||||||
|
form.setFieldsValue(next)
|
||||||
|
setFields(next.form.fields)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const save = async (formData: AdminFormQueryData) => {
|
const save = async (formData: AdminFormQueryData) => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
||||||
formData.form.fields = formData.form.fields.filter(e => e && e.type).map(({optionKeys, ...field}) => {
|
formData.form.fields = formData.form.fields
|
||||||
const options = field.options
|
.filter((e) => e && e.type)
|
||||||
|
.map(({ optionKeys, ...field }) => {
|
||||||
|
const options = field.options
|
||||||
|
|
||||||
if (optionKeys) {
|
if (optionKeys) {
|
||||||
Object.keys(optionKeys).forEach((key) => {
|
Object.keys(optionKeys).forEach((key) => {
|
||||||
if (!optionKeys[key]) {
|
if (!optionKeys[key]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
value: optionKeys[key],
|
value: optionKeys[key],
|
||||||
key,
|
key,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...field,
|
...field,
|
||||||
options
|
options,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const next = processNext((await update({
|
const next = processNext(
|
||||||
variables: cleanInput(formData),
|
(
|
||||||
})).data)
|
await update({
|
||||||
|
variables: cleanInput(formData),
|
||||||
|
})
|
||||||
|
).data
|
||||||
|
)
|
||||||
|
|
||||||
form.setFieldsValue(next)
|
form.setFieldsValue(next)
|
||||||
setFields(next.form.fields)
|
setFields(next.form.fields)
|
||||||
@ -124,18 +138,16 @@ const Index: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<Button>{t('admin:submissions')}</Button>
|
<Button>{t('admin:submissions')}</Button>
|
||||||
</Link>,
|
</Link>,
|
||||||
<Button
|
<Button key={'save'} onClick={form.submit} type={'primary'}>
|
||||||
key={'save'}
|
{t('form:updateNow')}
|
||||||
onClick={form.submit}
|
</Button>,
|
||||||
type={'primary'}
|
|
||||||
>{t('form:updateNow')}</Button>,
|
|
||||||
]}
|
]}
|
||||||
style={{paddingTop: 0}}
|
style={{ paddingTop: 0 }}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={save}
|
onFinish={save}
|
||||||
onFinishFailed={errors => {
|
onFinishFailed={() => {
|
||||||
// TODO process errors
|
// TODO process errors
|
||||||
message.error(t('validation:mandatoryFieldsMissing'))
|
message.error(t('validation:mandatoryFieldsMissing'))
|
||||||
}}
|
}}
|
||||||
@ -148,7 +160,9 @@ const Index: NextPage = () => {
|
|||||||
sm: { span: 18 },
|
sm: { span: 18 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item>
|
<Form.Item noStyle name={['form', 'id']}>
|
||||||
|
<Input type={'hidden'} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<FieldsTab
|
<FieldsTab
|
||||||
|
|||||||
@ -1,24 +1,25 @@
|
|||||||
import {useQuery} from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import {Button, Progress, Table} from 'antd'
|
import { Button, Progress, Table } from 'antd'
|
||||||
import {PaginationProps} from 'antd/es/pagination'
|
import { PaginationProps } from 'antd/es/pagination'
|
||||||
import {ColumnsType} from 'antd/lib/table/interface'
|
import { ProgressProps } from 'antd/lib/progress'
|
||||||
import {DateTime} from 'components/date.time'
|
import { ColumnsType } from 'antd/lib/table/interface'
|
||||||
|
import { DateTime } from 'components/date.time'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {TimeAgo} from 'components/time.ago'
|
import { TimeAgo } from 'components/time.ago'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {SubmissionValues} from '../../../../components/form/admin/submission.values'
|
import { SubmissionValues } from '../../../../components/form/admin/submission.values'
|
||||||
import {
|
import {
|
||||||
ADMIN_PAGER_SUBMISSION_QUERY,
|
ADMIN_PAGER_SUBMISSION_QUERY,
|
||||||
AdminPagerSubmissionEntryQueryData,
|
AdminPagerSubmissionEntryQueryData,
|
||||||
AdminPagerSubmissionFormQueryData,
|
AdminPagerSubmissionFormQueryData,
|
||||||
AdminPagerSubmissionQueryData,
|
AdminPagerSubmissionQueryData,
|
||||||
AdminPagerSubmissionQueryVariables
|
AdminPagerSubmissionQueryVariables,
|
||||||
} from '../../../../graphql/query/admin.pager.submission.query'
|
} from '../../../../graphql/query/admin.pager.submission.query'
|
||||||
|
|
||||||
const Submissions: NextPage = () => {
|
const Submissions: NextPage = () => {
|
||||||
@ -29,48 +30,56 @@ const Submissions: NextPage = () => {
|
|||||||
})
|
})
|
||||||
const [form, setForm] = useState<AdminPagerSubmissionFormQueryData>()
|
const [form, setForm] = useState<AdminPagerSubmissionFormQueryData>()
|
||||||
const [entries, setEntries] = useState<AdminPagerSubmissionEntryQueryData[]>()
|
const [entries, setEntries] = useState<AdminPagerSubmissionEntryQueryData[]>()
|
||||||
const {loading, refetch} = useQuery<AdminPagerSubmissionQueryData, AdminPagerSubmissionQueryVariables>(ADMIN_PAGER_SUBMISSION_QUERY, {
|
const { loading, refetch } = useQuery<
|
||||||
|
AdminPagerSubmissionQueryData,
|
||||||
|
AdminPagerSubmissionQueryVariables
|
||||||
|
>(ADMIN_PAGER_SUBMISSION_QUERY, {
|
||||||
variables: {
|
variables: {
|
||||||
form: router.query.id as string,
|
form: router.query.id as string,
|
||||||
limit: pagination.pageSize,
|
limit: pagination.pageSize,
|
||||||
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0
|
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
|
||||||
},
|
},
|
||||||
onCompleted: ({pager, form}) => {
|
onCompleted: ({ pager, form }) => {
|
||||||
setPagination({
|
setPagination({
|
||||||
...pagination,
|
...pagination,
|
||||||
total: pager.total,
|
total: pager.total,
|
||||||
})
|
})
|
||||||
setForm(form)
|
setForm(form)
|
||||||
setEntries(pager.entries)
|
setEntries(pager.entries)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns:ColumnsType<AdminPagerSubmissionEntryQueryData> = [
|
const columns: ColumnsType<AdminPagerSubmissionEntryQueryData> = [
|
||||||
{
|
{
|
||||||
title: t('submission:progress'),
|
title: t('submission:progress'),
|
||||||
render: (row: AdminPagerSubmissionEntryQueryData) => {
|
render(row: AdminPagerSubmissionEntryQueryData) {
|
||||||
let status: any = 'active'
|
const props: ProgressProps = {
|
||||||
|
status: 'active',
|
||||||
if (row.percentageComplete >= 1) {
|
percent: Math.round(row.percentageComplete * 100),
|
||||||
status = 'success'
|
|
||||||
} else if (dayjs().diff(dayjs(row.lastModified), 'hour') > 4) {
|
|
||||||
status = 'exception'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (row.percentageComplete >= 1) {
|
||||||
<Progress percent={Math.round(row.percentageComplete * 100)} status={status} />
|
props.status = 'success'
|
||||||
)
|
} else if (dayjs().diff(dayjs(row.lastModified), 'hour') > 4) {
|
||||||
}
|
props.status = 'exception'
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Progress {...props} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('submission:created'),
|
title: t('submission:created'),
|
||||||
dataIndex: 'created',
|
dataIndex: 'created',
|
||||||
render: date => <DateTime date={date} />
|
render(date) {
|
||||||
|
return <DateTime date={date} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('submission:lastModified'),
|
title: t('submission:lastModified'),
|
||||||
dataIndex: 'lastModified',
|
dataIndex: 'lastModified',
|
||||||
render: date => <TimeAgo date={date} />
|
render(date) {
|
||||||
|
return <TimeAgo date={date} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -82,23 +91,20 @@ const Submissions: NextPage = () => {
|
|||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ href: '/admin', name: t('admin:home') },
|
{ href: '/admin', name: t('admin:home') },
|
||||||
{ href: '/admin/forms', name: t('admin:forms') },
|
{ href: '/admin/forms', name: t('admin:forms') },
|
||||||
{ href: '/admin/forms/[id]', name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }), as: `/admin/forms/${router.query.id}` },
|
{
|
||||||
|
href: '/admin/forms/[id]',
|
||||||
|
name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }),
|
||||||
|
as: `/admin/forms/${router.query.id}`,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
padded={false}
|
padded={false}
|
||||||
extra={[
|
extra={[
|
||||||
<Link
|
<Link key={'edit'} href={'/admin/forms/[id]'} as={`/admin/forms/${router.query.id}`}>
|
||||||
key={'edit'}
|
|
||||||
href={'/admin/forms/[id]'}
|
|
||||||
as={`/admin/forms/${router.query.id}`}
|
|
||||||
>
|
|
||||||
<Button>{t('submission:edit')}</Button>
|
<Button>{t('submission:edit')}</Button>
|
||||||
</Link>,
|
</Link>,
|
||||||
<Button
|
<Button key={'web'} href={`/form/${router.query.id}`} target={'_blank'} type={'primary'}>
|
||||||
key={'web'}
|
{t('submission:add')}
|
||||||
href={`/form/${router.query.id}`}
|
</Button>,
|
||||||
target={'_blank'}
|
|
||||||
type={'primary'}
|
|
||||||
>{t('submission:add')}</Button>,
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
@ -107,10 +113,14 @@ const Submissions: NextPage = () => {
|
|||||||
rowKey={'id'}
|
rowKey={'id'}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
expandable={{
|
expandable={{
|
||||||
expandedRowRender: record => <SubmissionValues form={form} submission={record} />,
|
expandedRowRender(record) {
|
||||||
rowExpandable: record => record.percentageComplete > 0,
|
return <SubmissionValues form={form} submission={record} />
|
||||||
|
},
|
||||||
|
rowExpandable(record) {
|
||||||
|
return record.percentageComplete > 0
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onChange={next => {
|
onChange={(next) => {
|
||||||
setPagination(next)
|
setPagination(next)
|
||||||
refetch()
|
refetch()
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import {useMutation} from '@apollo/react-hooks'
|
import { useMutation } from '@apollo/react-hooks'
|
||||||
import {Button, Form, Input, message, Tabs} from 'antd'
|
import { Button, Form, Input, message, Tabs } from 'antd'
|
||||||
import {useForm} from 'antd/lib/form/Form'
|
import { useForm } from 'antd/lib/form/Form'
|
||||||
import {cleanInput} from 'components/clean.input'
|
import { cleanInput } from 'components/clean.input'
|
||||||
import {BaseDataTab} from 'components/form/admin/base.data.tab'
|
import { BaseDataTab } from 'components/form/admin/base.data.tab'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {AdminFormQueryData} from 'graphql/query/admin.form.query'
|
import { AdminFormQueryData } from 'graphql/query/admin.form.query'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
ADMIN_FORM_CREATE_MUTATION,
|
ADMIN_FORM_CREATE_MUTATION,
|
||||||
AdminFormCreateMutationData,
|
AdminFormCreateMutationData,
|
||||||
AdminFormCreateMutationVariables
|
AdminFormCreateMutationVariables,
|
||||||
} from '../../../graphql/mutation/admin.form.create.mutation'
|
} from '../../../graphql/mutation/admin.form.create.mutation'
|
||||||
|
|
||||||
const Create: NextPage = () => {
|
const Create: NextPage = () => {
|
||||||
@ -21,15 +21,19 @@ const Create: NextPage = () => {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [form] = useForm()
|
const [form] = useForm()
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [create] = useMutation<AdminFormCreateMutationData, AdminFormCreateMutationVariables>(ADMIN_FORM_CREATE_MUTATION)
|
const [create] = useMutation<AdminFormCreateMutationData, AdminFormCreateMutationVariables>(
|
||||||
|
ADMIN_FORM_CREATE_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
const save = async (formData: AdminFormQueryData) => {
|
const save = async (formData: AdminFormQueryData) => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const next = (await create({
|
const next = (
|
||||||
variables: cleanInput(formData),
|
await create({
|
||||||
})).data
|
variables: cleanInput(formData),
|
||||||
|
})
|
||||||
|
).data
|
||||||
|
|
||||||
message.success(t('form:created'))
|
message.success(t('form:created'))
|
||||||
|
|
||||||
@ -52,18 +56,16 @@ const Create: NextPage = () => {
|
|||||||
{ href: '/admin/forms', name: t('admin:forms') },
|
{ href: '/admin/forms', name: t('admin:forms') },
|
||||||
]}
|
]}
|
||||||
extra={[
|
extra={[
|
||||||
<Button
|
<Button key={'create'} onClick={form.submit} type={'primary'}>
|
||||||
key={'create'}
|
{t('form:createNow')}
|
||||||
onClick={form.submit}
|
</Button>,
|
||||||
type={'primary'}
|
|
||||||
>{t('form:createNow')}</Button>,
|
|
||||||
]}
|
]}
|
||||||
style={{paddingTop: 0}}
|
style={{ paddingTop: 0 }}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={save}
|
onFinish={save}
|
||||||
onFinishFailed={errors => {
|
onFinishFailed={() => {
|
||||||
// TODO process errors
|
// TODO process errors
|
||||||
message.error(t('validation:mandatoryFieldsMissing'))
|
message.error(t('validation:mandatoryFieldsMissing'))
|
||||||
}}
|
}}
|
||||||
@ -76,7 +78,9 @@ const Create: NextPage = () => {
|
|||||||
sm: { span: 18 },
|
sm: { span: 18 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item>
|
<Form.Item noStyle name={['form', 'id']}>
|
||||||
|
<Input type={'hidden'} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
|
<BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
|
||||||
|
|||||||
@ -1,27 +1,32 @@
|
|||||||
import {DeleteOutlined, EditOutlined, GlobalOutlined, UnorderedListOutlined} from '@ant-design/icons/lib'
|
import {
|
||||||
import {useMutation, useQuery} from '@apollo/react-hooks'
|
DeleteOutlined,
|
||||||
import {Button, message, Popconfirm, Space, Table, Tooltip} from 'antd'
|
EditOutlined,
|
||||||
import {PaginationProps} from 'antd/es/pagination'
|
GlobalOutlined,
|
||||||
import {ColumnsType} from 'antd/lib/table/interface'
|
UnorderedListOutlined,
|
||||||
import {DateTime} from 'components/date.time'
|
} from '@ant-design/icons/lib'
|
||||||
import {FormIsLive} from 'components/form/admin/is.live'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
|
import { Button, message, Popconfirm, Space, Table, Tooltip } from 'antd'
|
||||||
|
import { PaginationProps } from 'antd/es/pagination'
|
||||||
|
import { ColumnsType } from 'antd/lib/table/interface'
|
||||||
|
import { DateTime } from 'components/date.time'
|
||||||
|
import { FormIsLive } from 'components/form/admin/is.live'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {TimeAgo} from 'components/time.ago'
|
import { TimeAgo } from 'components/time.ago'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {
|
import {
|
||||||
ADMIN_PAGER_FORM_QUERY,
|
ADMIN_PAGER_FORM_QUERY,
|
||||||
AdminPagerFormEntryQueryData,
|
AdminPagerFormEntryQueryData,
|
||||||
AdminPagerFormQueryData,
|
AdminPagerFormQueryData,
|
||||||
AdminPagerFormQueryVariables
|
AdminPagerFormQueryVariables,
|
||||||
} from 'graphql/query/admin.pager.form.query'
|
} from 'graphql/query/admin.pager.form.query'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
ADMIN_FORM_DELETE_MUTATION,
|
ADMIN_FORM_DELETE_MUTATION,
|
||||||
AdminFormDeleteMutationData,
|
AdminFormDeleteMutationData,
|
||||||
AdminFormDeleteMutationVariables
|
AdminFormDeleteMutationVariables,
|
||||||
} from '../../../graphql/mutation/admin.form.delete.mutation'
|
} from '../../../graphql/mutation/admin.form.delete.mutation'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
@ -30,29 +35,35 @@ const Index: NextPage = () => {
|
|||||||
pageSize: 25,
|
pageSize: 25,
|
||||||
})
|
})
|
||||||
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
|
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
|
||||||
const {loading, refetch} = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(ADMIN_PAGER_FORM_QUERY, {
|
const { loading, refetch } = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(
|
||||||
variables: {
|
ADMIN_PAGER_FORM_QUERY,
|
||||||
limit: pagination.pageSize,
|
{
|
||||||
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0
|
variables: {
|
||||||
},
|
limit: pagination.pageSize,
|
||||||
onCompleted: ({pager}) => {
|
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
|
||||||
setPagination({
|
},
|
||||||
...pagination,
|
onCompleted: ({ pager }) => {
|
||||||
total: pager.total,
|
setPagination({
|
||||||
})
|
...pagination,
|
||||||
setEntries(pager.entries)
|
total: pager.total,
|
||||||
|
})
|
||||||
|
setEntries(pager.entries)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
const [executeDelete] = useMutation<AdminFormDeleteMutationData, AdminFormDeleteMutationVariables>(ADMIN_FORM_DELETE_MUTATION)
|
const [executeDelete] = useMutation<
|
||||||
|
AdminFormDeleteMutationData,
|
||||||
|
AdminFormDeleteMutationVariables
|
||||||
|
>(ADMIN_FORM_DELETE_MUTATION)
|
||||||
|
|
||||||
const deleteForm = async (form) => {
|
const deleteForm = async (form) => {
|
||||||
try {
|
try {
|
||||||
await executeDelete({
|
await executeDelete({
|
||||||
variables: {
|
variables: {
|
||||||
id: form.id
|
id: form.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const next = entries.filter(entry => entry.id !== form.id)
|
const next = entries.filter((entry) => entry.id !== form.id)
|
||||||
if (next.length === 0) {
|
if (next.length === 0) {
|
||||||
setPagination({ ...pagination, current: 1 })
|
setPagination({ ...pagination, current: 1 })
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +80,9 @@ const Index: NextPage = () => {
|
|||||||
{
|
{
|
||||||
title: t('form:row.isLive'),
|
title: t('form:row.isLive'),
|
||||||
dataIndex: 'isLive',
|
dataIndex: 'isLive',
|
||||||
render: live => <FormIsLive isLive={live} />
|
render(live) {
|
||||||
|
return <FormIsLive isLive={live} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('form:row.title'),
|
title: t('form:row.title'),
|
||||||
@ -78,49 +91,55 @@ const Index: NextPage = () => {
|
|||||||
{
|
{
|
||||||
title: t('form:row.admin'),
|
title: t('form:row.admin'),
|
||||||
dataIndex: 'admin',
|
dataIndex: 'admin',
|
||||||
render: user => (
|
render(user) {
|
||||||
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
|
return (
|
||||||
<Tooltip title={user.email}>
|
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
|
||||||
<Button type={'dashed'}>{user.username}</Button>
|
<Tooltip title={user.email}>
|
||||||
</Tooltip>
|
<Button type={'dashed'}>{user.username}</Button>
|
||||||
</Link>
|
</Tooltip>
|
||||||
)
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('form:row.language'),
|
title: t('form:row.language'),
|
||||||
dataIndex: 'language',
|
dataIndex: 'language',
|
||||||
render: lang => t(`language:${lang}`)
|
render(lang) {
|
||||||
|
return t(`language:${lang}`)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('form:row.created'),
|
title: t('form:row.created'),
|
||||||
dataIndex: 'created',
|
dataIndex: 'created',
|
||||||
render: date => <DateTime date={date} />
|
render(date) {
|
||||||
|
return <DateTime date={date} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('form:row.lastModified'),
|
title: t('form:row.lastModified'),
|
||||||
dataIndex: 'lastModified',
|
dataIndex: 'lastModified',
|
||||||
render: date => <TimeAgo date={date} />
|
render(date) {
|
||||||
|
return <TimeAgo date={date} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('form:row.menu'),
|
title: t('form:row.menu'),
|
||||||
align: 'right',
|
align: 'right',
|
||||||
render: row => {
|
render(row) {
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Link
|
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
|
||||||
href={'/admin/forms/[id]/submissions'}
|
|
||||||
as={`/admin/forms/${row.id}/submissions`}
|
|
||||||
>
|
|
||||||
<Tooltip title={'Show Submissions'}>
|
<Tooltip title={'Show Submissions'}>
|
||||||
<Button><UnorderedListOutlined /></Button>
|
<Button>
|
||||||
|
<UnorderedListOutlined />
|
||||||
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link href={'/admin/forms/[id]'} as={`/admin/forms/${row.id}`}>
|
||||||
href={'/admin/forms/[id]'}
|
<Button type={'primary'}>
|
||||||
as={`/admin/forms/${row.id}`}
|
<EditOutlined />
|
||||||
>
|
</Button>
|
||||||
<Button type={'primary'}><EditOutlined /></Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@ -129,20 +148,19 @@ const Index: NextPage = () => {
|
|||||||
okText={t('form:deleteNow')}
|
okText={t('form:deleteNow')}
|
||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
>
|
>
|
||||||
<Button danger><DeleteOutlined /></Button>
|
<Button danger>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|
||||||
<Tooltip title={row.isLive ? null : 'Not Public accessible!'}>
|
<Tooltip title={row.isLive ? null : 'Not Public accessible!'}>
|
||||||
<Button
|
<Button href={`/form/${row.id}`} target={'_blank'}>
|
||||||
href={`/form/${row.id}`}
|
|
||||||
target={'_blank'}
|
|
||||||
>
|
|
||||||
<GlobalOutlined />
|
<GlobalOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -151,18 +169,11 @@ const Index: NextPage = () => {
|
|||||||
title={t('admin:forms')}
|
title={t('admin:forms')}
|
||||||
selected={'forms'}
|
selected={'forms'}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
breadcrumbs={[
|
breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
|
||||||
{ href: '/admin', name: t('admin:home') },
|
|
||||||
]}
|
|
||||||
padded={false}
|
padded={false}
|
||||||
extra={[
|
extra={[
|
||||||
<Link
|
<Link key={'create'} href={'/admin/forms/create'}>
|
||||||
key={'create'}
|
<Button type={'primary'}>{t('form:new')}</Button>
|
||||||
href={'/admin/forms/create'}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type={'primary'}
|
|
||||||
>{t('form:new')}</Button>
|
|
||||||
</Link>,
|
</Link>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@ -171,7 +182,7 @@ const Index: NextPage = () => {
|
|||||||
dataSource={entries}
|
dataSource={entries}
|
||||||
rowKey={'id'}
|
rowKey={'id'}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
onChange={next => {
|
onChange={(next) => {
|
||||||
setPagination(next)
|
setPagination(next)
|
||||||
refetch()
|
refetch()
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,26 +1,24 @@
|
|||||||
import {useQuery} from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import {Col, Row, Statistic} from 'antd'
|
import { Col, Row, Statistic } from 'antd'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
ADMIN_STATISTIC_QUERY,
|
ADMIN_STATISTIC_QUERY,
|
||||||
AdminStatisticQueryData,
|
AdminStatisticQueryData,
|
||||||
AdminStatisticQueryVariables
|
AdminStatisticQueryVariables,
|
||||||
} from '../../graphql/query/admin.statistic.query'
|
} from '../../graphql/query/admin.statistic.query'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const {data, loading} = useQuery<AdminStatisticQueryData, AdminStatisticQueryVariables>(ADMIN_STATISTIC_QUERY)
|
const { data, loading } = useQuery<AdminStatisticQueryData, AdminStatisticQueryVariables>(
|
||||||
|
ADMIN_STATISTIC_QUERY
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Structure
|
<Structure title={t('admin:home')} selected={'home'} loading={loading}>
|
||||||
title={t('admin:home')}
|
|
||||||
selected={'home'}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Statistic title={t('statistic:totalForms')} value={data && data.forms.total} />
|
<Statistic title={t('statistic:totalForms')} value={data && data.forms.total} />
|
||||||
@ -31,7 +29,10 @@ const Index: NextPage = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Statistic title={t('statistic:totalSubmissions')} value={data && data.submissions.total} />
|
<Statistic
|
||||||
|
title={t('statistic:totalSubmissions')}
|
||||||
|
value={data && data.submissions.total}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Structure>
|
</Structure>
|
||||||
|
|||||||
@ -1,46 +1,51 @@
|
|||||||
import {useMutation, useQuery} from '@apollo/react-hooks'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
import {Button, Form, Input, message, Select} from 'antd'
|
import { Button, Form, Input, message, Select } from 'antd'
|
||||||
import {useForm} from 'antd/lib/form/Form'
|
import { useForm } from 'antd/lib/form/Form'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import {useRouter} from 'next/router'
|
import React, { useState } from 'react'
|
||||||
import React, {useState} from 'react'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { cleanInput } from '../../components/clean.input'
|
||||||
import {cleanInput} from '../../components/clean.input'
|
|
||||||
import Structure from '../../components/structure'
|
import Structure from '../../components/structure'
|
||||||
import {
|
import {
|
||||||
ADMIN_PROFILE_UPDATE_MUTATION,
|
ADMIN_PROFILE_UPDATE_MUTATION,
|
||||||
AdminProfileUpdateMutationData,
|
AdminProfileUpdateMutationData,
|
||||||
AdminProfileUpdateMutationVariables
|
AdminProfileUpdateMutationVariables,
|
||||||
} from '../../graphql/mutation/admin.profile.update.mutation'
|
} from '../../graphql/mutation/admin.profile.update.mutation'
|
||||||
import {
|
import {
|
||||||
ADMIN_PROFILE_QUERY,
|
ADMIN_PROFILE_QUERY,
|
||||||
AdminProfileQueryData,
|
AdminProfileQueryData,
|
||||||
AdminProfileQueryVariables
|
AdminProfileQueryVariables,
|
||||||
} from '../../graphql/query/admin.profile.query'
|
} from '../../graphql/query/admin.profile.query'
|
||||||
import {AdminUserQueryData} from '../../graphql/query/admin.user.query'
|
import { AdminUserQueryData } from '../../graphql/query/admin.user.query'
|
||||||
import {languages} from '../../i18n'
|
import { languages } from '../../i18n'
|
||||||
|
|
||||||
const Profile: NextPage = () => {
|
const Profile: NextPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
|
||||||
const [form] = useForm()
|
const [form] = useForm()
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
const {data, loading, error} = useQuery<AdminProfileQueryData, AdminProfileQueryVariables>(ADMIN_PROFILE_QUERY, {
|
const { loading } = useQuery<AdminProfileQueryData, AdminProfileQueryVariables>(
|
||||||
onCompleted: next => {
|
ADMIN_PROFILE_QUERY,
|
||||||
form.setFieldsValue(next)
|
{
|
||||||
},
|
onCompleted: (next) => {
|
||||||
})
|
form.setFieldsValue(next)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const [update] = useMutation<AdminProfileUpdateMutationData, AdminProfileUpdateMutationVariables>(ADMIN_PROFILE_UPDATE_MUTATION)
|
const [update] = useMutation<AdminProfileUpdateMutationData, AdminProfileUpdateMutationVariables>(
|
||||||
|
ADMIN_PROFILE_UPDATE_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
const save = async (formData: AdminUserQueryData) => {
|
const save = async (formData: AdminUserQueryData) => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const next = (await update({
|
const next = (
|
||||||
variables: cleanInput(formData),
|
await update({
|
||||||
})).data
|
variables: cleanInput(formData),
|
||||||
|
})
|
||||||
|
).data
|
||||||
|
|
||||||
form.setFieldsValue(next)
|
form.setFieldsValue(next)
|
||||||
|
|
||||||
@ -53,27 +58,22 @@ const Profile: NextPage = () => {
|
|||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Structure
|
<Structure
|
||||||
loading={loading || saving}
|
loading={loading || saving}
|
||||||
title={t('admin:profile')}
|
title={t('admin:profile')}
|
||||||
selected={'profile'}
|
selected={'profile'}
|
||||||
breadcrumbs={[
|
breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
|
||||||
{ href: '/admin', name: t('admin:home') },
|
|
||||||
]}
|
|
||||||
extra={[
|
extra={[
|
||||||
<Button
|
<Button key={'save'} onClick={form.submit} type={'primary'}>
|
||||||
key={'save'}
|
{t('profile:updateNow')}
|
||||||
onClick={form.submit}
|
</Button>,
|
||||||
type={'primary'}
|
|
||||||
>{t('profile:updateNow')}</Button>,
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={save}
|
onFinish={save}
|
||||||
onFinishFailed={errors => {
|
onFinishFailed={() => {
|
||||||
// TODO process errors
|
// TODO process errors
|
||||||
message.error(t('validation:mandatoryFieldsMissing'))
|
message.error(t('validation:mandatoryFieldsMissing'))
|
||||||
}}
|
}}
|
||||||
@ -86,7 +86,9 @@ const Profile: NextPage = () => {
|
|||||||
sm: { span: 18 },
|
sm: { span: 18 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item noStyle name={['user', 'id']}><Input type={'hidden'} /></Form.Item>
|
<Form.Item noStyle name={['user', 'id']}>
|
||||||
|
<Input type={'hidden'} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('profile:username')}
|
label={t('profile:username')}
|
||||||
@ -129,22 +131,20 @@ const Profile: NextPage = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
|
{languages.map((language) => (
|
||||||
|
<Select.Option value={language} key={language}>
|
||||||
|
{t(`language:${language}`)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('profile:firstName')} name={['user', 'firstName']}>
|
||||||
label={t('profile:firstName')}
|
<Input />
|
||||||
name={['user', 'firstName']}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item label={t('profile:lastName')} name={['user', 'lastName']}>
|
||||||
label={t('profile:lastName')}
|
<Input />
|
||||||
name={['user', 'lastName']}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Structure>
|
</Structure>
|
||||||
|
|||||||
@ -1,20 +1,24 @@
|
|||||||
import {useMutation, useQuery} from '@apollo/react-hooks'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
import {Button, Form, Input, message, Tabs} from 'antd'
|
import { Button, Form, Input, message, Tabs } from 'antd'
|
||||||
import {useForm} from 'antd/lib/form/Form'
|
import { useForm } from 'antd/lib/form/Form'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import {useRouter} from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {cleanInput} from '../../../../components/clean.input'
|
import { cleanInput } from '../../../../components/clean.input'
|
||||||
import {BaseDataTab} from '../../../../components/user/admin/base.data.tab'
|
import { BaseDataTab } from '../../../../components/user/admin/base.data.tab'
|
||||||
import {
|
import {
|
||||||
ADMIN_USER_UPDATE_MUTATION,
|
ADMIN_USER_UPDATE_MUTATION,
|
||||||
AdminUserUpdateMutationData,
|
AdminUserUpdateMutationData,
|
||||||
AdminUserUpdateMutationVariables
|
AdminUserUpdateMutationVariables,
|
||||||
} from '../../../../graphql/mutation/admin.user.update.mutation'
|
} from '../../../../graphql/mutation/admin.user.update.mutation'
|
||||||
import {ADMIN_USER_QUERY, AdminUserQueryData, AdminUserQueryVariables} from '../../../../graphql/query/admin.user.query'
|
import {
|
||||||
|
ADMIN_USER_QUERY,
|
||||||
|
AdminUserQueryData,
|
||||||
|
AdminUserQueryVariables,
|
||||||
|
} from '../../../../graphql/query/admin.user.query'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -22,23 +26,30 @@ const Index: NextPage = () => {
|
|||||||
const [form] = useForm()
|
const [form] = useForm()
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
const {data, loading, error} = useQuery<AdminUserQueryData, AdminUserQueryVariables>(ADMIN_USER_QUERY, {
|
const { data, loading } = useQuery<AdminUserQueryData, AdminUserQueryVariables>(
|
||||||
variables: {
|
ADMIN_USER_QUERY,
|
||||||
id: router.query.id as string
|
{
|
||||||
},
|
variables: {
|
||||||
onCompleted: next => {
|
id: router.query.id as string,
|
||||||
form.setFieldsValue(next)
|
},
|
||||||
},
|
onCompleted: (next) => {
|
||||||
})
|
form.setFieldsValue(next)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(ADMIN_USER_UPDATE_MUTATION)
|
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(
|
||||||
|
ADMIN_USER_UPDATE_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
const save = async (formData: AdminUserQueryData) => {
|
const save = async (formData: AdminUserQueryData) => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
const next = (await update({
|
const next = (
|
||||||
variables: cleanInput(formData),
|
await update({
|
||||||
})).data
|
variables: cleanInput(formData),
|
||||||
|
})
|
||||||
|
).data
|
||||||
|
|
||||||
form.setFieldsValue(next)
|
form.setFieldsValue(next)
|
||||||
|
|
||||||
@ -61,18 +72,16 @@ const Index: NextPage = () => {
|
|||||||
{ href: '/admin/users', name: t('admin:users') },
|
{ href: '/admin/users', name: t('admin:users') },
|
||||||
]}
|
]}
|
||||||
extra={[
|
extra={[
|
||||||
<Button
|
<Button key={'save'} onClick={form.submit} type={'primary'}>
|
||||||
key={'save'}
|
{t('user:updateNow')}
|
||||||
onClick={form.submit}
|
</Button>,
|
||||||
type={'primary'}
|
|
||||||
>{t('user:updateNow')}</Button>,
|
|
||||||
]}
|
]}
|
||||||
style={{paddingTop: 0}}
|
style={{ paddingTop: 0 }}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={save}
|
onFinish={save}
|
||||||
onFinishFailed={errors => {
|
onFinishFailed={() => {
|
||||||
// TODO process errors
|
// TODO process errors
|
||||||
message.error(t('validation:mandatoryFieldsMissing'))
|
message.error(t('validation:mandatoryFieldsMissing'))
|
||||||
}}
|
}}
|
||||||
@ -85,13 +94,12 @@ const Index: NextPage = () => {
|
|||||||
sm: { span: 18 },
|
sm: { span: 18 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item noStyle name={['user', 'id']}><Input type={'hidden'} /></Form.Item>
|
<Form.Item noStyle name={['user', 'id']}>
|
||||||
|
<Input type={'hidden'} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<BaseDataTab
|
<BaseDataTab key={'base_data'} tab={t('user:baseData')} />
|
||||||
key={'base_data'}
|
|
||||||
tab={t('user:baseData')}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Form>
|
</Form>
|
||||||
</Structure>
|
</Structure>
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
import {DeleteOutlined, EditOutlined} from '@ant-design/icons/lib'
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons/lib'
|
||||||
import {useMutation, useQuery} from '@apollo/react-hooks'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
import {Button, message, Popconfirm, Space, Table, Tag} from 'antd'
|
import { Button, message, Popconfirm, Space, Table, Tag } from 'antd'
|
||||||
import {PaginationProps} from 'antd/es/pagination'
|
import { PaginationProps } from 'antd/es/pagination'
|
||||||
import {ColumnsType} from 'antd/lib/table/interface'
|
import { ColumnsType } from 'antd/lib/table/interface'
|
||||||
import Structure from 'components/structure'
|
import Structure from 'components/structure'
|
||||||
import {withAuth} from 'components/with.auth'
|
import { withAuth } from 'components/with.auth'
|
||||||
import {NextPage} from 'next'
|
import { NextPage } from 'next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, {useState} from 'react'
|
import React, { useState } from 'react'
|
||||||
import {useTranslation} from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {DateTime} from '../../../components/date.time'
|
import { DateTime } from '../../../components/date.time'
|
||||||
import {UserRole} from '../../../components/user/role'
|
import { UserRole } from '../../../components/user/role'
|
||||||
import {
|
import {
|
||||||
ADMIN_USER_DELETE_MUTATION,
|
ADMIN_USER_DELETE_MUTATION,
|
||||||
AdminUserDeleteMutationData,
|
AdminUserDeleteMutationData,
|
||||||
AdminUserDeleteMutationVariables
|
AdminUserDeleteMutationVariables,
|
||||||
} from '../../../graphql/mutation/admin.user.delete.mutation'
|
} from '../../../graphql/mutation/admin.user.delete.mutation'
|
||||||
import {
|
import {
|
||||||
ADMIN_PAGER_USER_QUERY,
|
ADMIN_PAGER_USER_QUERY,
|
||||||
AdminPagerUserEntryQueryData,
|
AdminPagerUserEntryQueryData,
|
||||||
AdminPagerUserQueryData,
|
AdminPagerUserQueryData,
|
||||||
AdminPagerUserQueryVariables
|
AdminPagerUserQueryVariables,
|
||||||
} from '../../../graphql/query/admin.pager.user.query'
|
} from '../../../graphql/query/admin.pager.user.query'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
@ -29,29 +29,35 @@ const Index: NextPage = () => {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
})
|
})
|
||||||
const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>()
|
const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>()
|
||||||
const {loading, refetch} = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(ADMIN_PAGER_USER_QUERY, {
|
const { loading, refetch } = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(
|
||||||
variables: {
|
ADMIN_PAGER_USER_QUERY,
|
||||||
limit: pagination.pageSize,
|
{
|
||||||
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0
|
variables: {
|
||||||
},
|
limit: pagination.pageSize,
|
||||||
onCompleted: ({pager}) => {
|
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
|
||||||
setPagination({
|
},
|
||||||
...pagination,
|
onCompleted: ({ pager }) => {
|
||||||
total: pager.total,
|
setPagination({
|
||||||
})
|
...pagination,
|
||||||
setEntries(pager.entries)
|
total: pager.total,
|
||||||
|
})
|
||||||
|
setEntries(pager.entries)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
const [executeDelete] = useMutation<AdminUserDeleteMutationData, AdminUserDeleteMutationVariables>(ADMIN_USER_DELETE_MUTATION)
|
const [executeDelete] = useMutation<
|
||||||
|
AdminUserDeleteMutationData,
|
||||||
|
AdminUserDeleteMutationVariables
|
||||||
|
>(ADMIN_USER_DELETE_MUTATION)
|
||||||
|
|
||||||
const deleteUser = async (form) => {
|
const deleteUser = async (form) => {
|
||||||
try {
|
try {
|
||||||
await executeDelete({
|
await executeDelete({
|
||||||
variables: {
|
variables: {
|
||||||
id: form.id
|
id: form.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const next = entries.filter(entry => entry.id !== form.id)
|
const next = entries.filter((entry) => entry.id !== form.id)
|
||||||
if (next.length === 0) {
|
if (next.length === 0) {
|
||||||
setPagination({ ...pagination, current: 1 })
|
setPagination({ ...pagination, current: 1 })
|
||||||
} else {
|
} else {
|
||||||
@ -67,28 +73,33 @@ const Index: NextPage = () => {
|
|||||||
{
|
{
|
||||||
title: t('user:row.roles'),
|
title: t('user:row.roles'),
|
||||||
dataIndex: 'roles',
|
dataIndex: 'roles',
|
||||||
render: roles => <UserRole roles={roles} />
|
render(roles) {
|
||||||
|
return <UserRole roles={roles} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('user:row.email'),
|
title: t('user:row.email'),
|
||||||
render: row => <Tag color={row.verifiedEmail ? 'lime' : 'volcano' }>{row.email}</Tag>
|
render(row) {
|
||||||
|
return <Tag color={row.verifiedEmail ? 'lime' : 'volcano'}>{row.email}</Tag>
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('user:row.created'),
|
title: t('user:row.created'),
|
||||||
dataIndex: 'created',
|
dataIndex: 'created',
|
||||||
render: date => <DateTime date={date} />
|
render(date) {
|
||||||
|
return <DateTime date={date} />
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('user:row.menu'),
|
title: t('user:row.menu'),
|
||||||
align: 'right',
|
align: 'right',
|
||||||
render: row => {
|
render(row) {
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Link
|
<Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}>
|
||||||
href={'/admin/users/[id]'}
|
<Button type={'primary'}>
|
||||||
as={`/admin/users/${row.id}`}
|
<EditOutlined />
|
||||||
>
|
</Button>
|
||||||
<Button type={'primary'}><EditOutlined /></Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@ -97,11 +108,13 @@ const Index: NextPage = () => {
|
|||||||
okText={t('user:deleteNow')}
|
okText={t('user:deleteNow')}
|
||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
>
|
>
|
||||||
<Button danger><DeleteOutlined /></Button>
|
<Button danger>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -109,9 +122,7 @@ const Index: NextPage = () => {
|
|||||||
<Structure
|
<Structure
|
||||||
title={t('admin:users')}
|
title={t('admin:users')}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
breadcrumbs={[
|
breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
|
||||||
{ href: '/admin', name: t('admin:home') },
|
|
||||||
]}
|
|
||||||
padded={false}
|
padded={false}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
@ -119,7 +130,7 @@ const Index: NextPage = () => {
|
|||||||
dataSource={entries}
|
dataSource={entries}
|
||||||
rowKey={'id'}
|
rowKey={'id'}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
onChange={next => {
|
onChange={(next) => {
|
||||||
setPagination(next)
|
setPagination(next)
|
||||||
refetch()
|
refetch()
|
||||||
}}
|
}}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user