diff --git a/components/form/admin/field.card.tsx b/components/form/admin/field.card.tsx index e7a10b2..16ae054 100644 --- a/components/form/admin/field.card.tsx +++ b/components/form/admin/field.card.tsx @@ -33,7 +33,6 @@ export const FieldCard: React.FC = props => { useEffect(() => { const id = setTimeout(() => { - console.log('update fields') onChangeFields(fields.map((field, i) => { if (i === index) { return { @@ -102,7 +101,10 @@ export const FieldCard: React.FC = props => { - + ) } diff --git a/components/form/admin/types/date.type.tsx b/components/form/admin/types/date.type.tsx index 853a705..5a09a0d 100644 --- a/components/form/admin/types/date.type.tsx +++ b/components/form/admin/types/date.type.tsx @@ -1,28 +1,37 @@ import {DatePicker, Form} from 'antd' +import moment from 'moment' import React from 'react' import {AdminFieldTypeProps} from './type.props' -export const DateType: React.FC = props => { +export const DateType: React.FC = ({field, form}) => { return (
e ? e.format('YYYY-MM-DD') : undefined} + getValueProps={e => ({value: e ? moment(e) : undefined})} > - + e.format('YYYY-MM-DD')} + getValueProps={e => ({value: e ? moment(e) : undefined})} > e.format('YYYY-MM-DD')} + getValueProps={e => ({value: e ? moment(e) : undefined})} > diff --git a/components/form/admin/types/text.type.tsx b/components/form/admin/types/text.type.tsx index fb91e14..71410c8 100644 --- a/components/form/admin/types/text.type.tsx +++ b/components/form/admin/types/text.type.tsx @@ -4,14 +4,12 @@ import {AdminFieldTypeProps} from './type.props' export const TextType: React.FC = props => { return ( -
- - - -
+ + + ) } diff --git a/components/form/admin/types/type.props.ts b/components/form/admin/types/type.props.ts index 4a08f5e..795294a 100644 --- a/components/form/admin/types/type.props.ts +++ b/components/form/admin/types/type.props.ts @@ -1,3 +1,6 @@ +import {FormInstance} from 'antd/lib/form' + export interface AdminFieldTypeProps { + form: FormInstance field: any } diff --git a/components/form/field.tsx b/components/form/field.tsx index b8ec064..c9c8151 100644 --- a/components/form/field.tsx +++ b/components/form/field.tsx @@ -13,18 +13,19 @@ interface Props { field: FormFieldFragment design: FormDesignFragment + save: (data: any) => any next: () => any prev: () => any } -export const Field: React.FC = ({field, design, children, next, prev, ...props}) => { +export const Field: React.FC = ({field, save, design, children, next, prev, ...props}) => { const [form] = useForm() const FieldInput: React.FC = fieldTypes[field.type] || TextType const finish = (data) => { console.log('received field data', data) - + save(data) next() } diff --git a/components/form/types/date.type.tsx b/components/form/types/date.type.tsx index 96e2a87..b4e22c0 100644 --- a/components/form/types/date.type.tsx +++ b/components/form/types/date.type.tsx @@ -4,8 +4,9 @@ import React from 'react' import {StyledDateInput} from '../../styled/date.input' import {FieldTypeProps} from './type.props' -export const DateType: React.FC = ({field, design}) => { +export const DateType: React.FC = ({ field, design}) => { // TODO check min and max + // TODO if default is passed, then the changing should not be required return (
@@ -14,6 +15,8 @@ export const DateType: React.FC = ({field, design}) => { rules={[ { required: field.required, message: 'Please provide Information' }, ]} + getValueFromEvent={e => e.format('YYYY-MM-DD')} + getValueProps={e => ({value: e ? moment(e) : undefined})} > = ({field}) => { +export const NumberType: React.FC = ({field, design}) => { return (
- +
) diff --git a/components/styled/number.input.tsx b/components/styled/number.input.tsx new file mode 100644 index 0000000..e6e3270 --- /dev/null +++ b/components/styled/number.input.tsx @@ -0,0 +1,63 @@ +import {InputNumber} from 'antd' +import {InputNumberProps} from 'antd/lib/input-number' +import React, {useEffect, useState} from 'react' +import styled from 'styled-components' +import {FormDesignFragment} from '../../graphql/fragment/form.fragment' +import {transparentize} from './color.change' + +interface Props extends InputNumberProps { + design: FormDesignFragment +} + +export const StyledNumberInput: React.FC = ({design, children, ...props}) => { + const [Field, setField] = useState() + + useEffect(() => { + setField( + styled(InputNumber)` + color: ${design.colors.answerColor}; + border-color: ${design.colors.answerColor}; + background: none !important; + border-right: none; + border-top: none; + border-left: none; + border-radius: 0; + width: 100%; + + :focus { + outline: ${design.colors.answerColor} auto 5px + } + + :hover, + :active { + border-color: ${design.colors.answerColor}; + } + + &.ant-input-number { + box-shadow: none + } + + input { + background: none !important; + color: ${design.colors.answerColor}; + + ::placeholder { + color: ${transparentize(design.colors.answerColor, 60)} + } + } + + .anticon { + color: ${design.colors.answerColor}; + } + ` + ) + }, [design]) + + if (!Field) { + return null + } + + return ( + {children} + ) +} diff --git a/components/use.submission.ts b/components/use.submission.ts new file mode 100644 index 0000000..ab82481 --- /dev/null +++ b/components/use.submission.ts @@ -0,0 +1,68 @@ +import {useMutation} from '@apollo/react-hooks' +import {useCallback, useEffect, useState} from 'react' +import { + SUBMISSION_SET_FIELD_MUTATION, + SubmissionSetFieldMutationData, + SubmissionSetFieldMutationVariables +} from '../graphql/mutation/submission.set.field.mutation' +import { + SUBMISSION_START_MUTATION, + SubmissionStartMutationData, + SubmissionStartMutationVariables +} from '../graphql/mutation/submission.start.mutation' + +export const useSubmission = (formId: string) => { + const [submission, setSubmission] = useState<{ id: string, token: string }>() + + const [start] = useMutation(SUBMISSION_START_MUTATION) + const [save] = useMutation(SUBMISSION_SET_FIELD_MUTATION) + + useEffect(() => { + (async () => { + const token = '123' // TODO generate secure token + + const {data} = await start({ + variables: { + form: formId, + submission: { + token, + device: { + name: '', + type: '' + } + } + } + }) + + setSubmission({ + id: data.submission.id, + token, + }) + })() + }, [formId]) + + const setField = useCallback(async (fieldId: string, data: any) => { + console.log('just save', fieldId, data) + await save({ + variables: { + submission: submission.id, + field: { + token: submission.token, + field: fieldId, + data: JSON.stringify(data) + } + } + }) + }, [submission]) + + const finish = useCallback(() => { + console.log('finish submission!!', formId) + }, [submission]) + + console.log('submission saver :D', formId) + + return { + setField, + finish, + } +} diff --git a/graphql/fragment/submission.fragment.ts b/graphql/fragment/submission.fragment.ts new file mode 100644 index 0000000..87a7dce --- /dev/null +++ b/graphql/fragment/submission.fragment.ts @@ -0,0 +1,16 @@ +import {gql} from 'apollo-boost' + +export interface SubmissionFragment { + id?: string + title: string + created: string +} + +export const FORM_FRAGMENT = gql` + fragment Submission on Submission { + id + title + language + showFooter + } +` diff --git a/graphql/mutation/submission.finish.mutation.ts b/graphql/mutation/submission.finish.mutation.ts new file mode 100644 index 0000000..e69de29 diff --git a/graphql/mutation/submission.set.field.mutation.ts b/graphql/mutation/submission.set.field.mutation.ts new file mode 100644 index 0000000..f24431d --- /dev/null +++ b/graphql/mutation/submission.set.field.mutation.ts @@ -0,0 +1,26 @@ +import {gql} from 'apollo-boost' + +export interface SubmissionSetFieldMutationData { + submission: { + id: string + percentageComplete: string + } +} + +export interface SubmissionSetFieldMutationVariables { + submission: string + field: { + token: string + field: string + data: string + } +} + +export const SUBMISSION_SET_FIELD_MUTATION = gql` + mutation start($submission: ID!,$field: SubmissionSetFieldInput!) { + submission: submissionSetField(submission: $submission, field: $field) { + id + percentageComplete + } + } +` diff --git a/graphql/mutation/submission.start.mutation.ts b/graphql/mutation/submission.start.mutation.ts new file mode 100644 index 0000000..95378cb --- /dev/null +++ b/graphql/mutation/submission.start.mutation.ts @@ -0,0 +1,28 @@ +import {gql} from 'apollo-boost' + +export interface SubmissionStartMutationData { + submission: { + id: string + percentageComplete: string + } +} + +export interface SubmissionStartMutationVariables { + form: string + submission: { + token: string + device: { + type: string + name: string + } + } +} + +export const SUBMISSION_START_MUTATION = gql` + mutation start($form: ID!,$submission: SubmissionStartInput!) { + submission: submissionStart(form: $form, submission: $submission) { + id + percentageComplete + } + } +` diff --git a/graphql/query/pager.form.query.ts b/graphql/query/admin.pager.form.query.ts similarity index 74% rename from graphql/query/pager.form.query.ts rename to graphql/query/admin.pager.form.query.ts index 5de469d..dc07983 100644 --- a/graphql/query/pager.form.query.ts +++ b/graphql/query/admin.pager.form.query.ts @@ -1,6 +1,6 @@ import {gql} from 'apollo-boost' -export interface PagerFormEntryQueryData { +export interface AdminPagerFormEntryQueryData { id: string created: string lastModified?: string @@ -14,9 +14,9 @@ export interface PagerFormEntryQueryData { } } -export interface PagerFormQueryData { +export interface AdminPagerFormQueryData { pager: { - entries: PagerFormEntryQueryData[] + entries: AdminPagerFormEntryQueryData[] total: number limit: number @@ -24,12 +24,12 @@ export interface PagerFormQueryData { } } -export interface PagerFormQueryVariables { +export interface AdminPagerFormQueryVariables { start?: number limit?: number } -export const PAGER_FORM_QUERY = gql` +export const ADMIN_PAGER_FORM_QUERY = gql` query pager($start: Int, $limit: Int){ pager: listForms(start: $start, limit: $limit) { entries { diff --git a/graphql/query/admin.pager.submission.query.ts b/graphql/query/admin.pager.submission.query.ts new file mode 100644 index 0000000..ab594de --- /dev/null +++ b/graphql/query/admin.pager.submission.query.ts @@ -0,0 +1,68 @@ +import {gql} from 'apollo-boost' + +export interface AdminPagerSubmissionFormQueryData { + id: string + title: string + isLive: boolean +} + +export interface AdminPagerSubmissionEntryQueryData { + id: string + created: string + lastModified?: string + percentageComplete: number + timeElapsed: number + geoLocation: { + country: string + } +} + +export interface AdminPagerSubmissionQueryData { + pager: { + entries: AdminPagerSubmissionEntryQueryData[] + + total: number + limit: number + start: number + } + + form: AdminPagerSubmissionFormQueryData +} + +export interface AdminPagerSubmissionQueryVariables { + form: string + start?: number + limit?: number +} + +export const ADMIN_PAGER_SUBMISSION_QUERY = gql` + query pager($form: ID!, $start: Int, $limit: Int){ + form: getFormById(id: $form) { + id + title + isLive + } + + pager: listSubmissions(form: $form, start: $start, limit: $limit) { + entries { + id + created + lastModified + percentageComplete + timeElapsed + geoLocation { + country + } + + fields { + id + value + type + } + } + total + limit + start + } + } +` diff --git a/pages/admin/forms/[id]/submissions.tsx b/pages/admin/forms/[id]/submissions.tsx new file mode 100644 index 0000000..53a86c1 --- /dev/null +++ b/pages/admin/forms/[id]/submissions.tsx @@ -0,0 +1,121 @@ +import {EditOutlined} from '@ant-design/icons/lib' +import {useQuery} from '@apollo/react-hooks' +import {Button, Space, Table} from 'antd' +import {PaginationProps} from 'antd/es/pagination' +import {DateTime} from 'components/date.time' +import Structure from 'components/structure' +import {TimeAgo} from 'components/time.ago' +import {withAuth} from 'components/with.auth' +import {NextPage} from 'next' +import Link from 'next/link' +import {useRouter} from 'next/router' +import React, {useState} from 'react' +import { + ADMIN_PAGER_SUBMISSION_QUERY, + AdminPagerSubmissionEntryQueryData, + AdminPagerSubmissionFormQueryData, + AdminPagerSubmissionQueryData, + AdminPagerSubmissionQueryVariables +} from '../../../../graphql/query/admin.pager.submission.query' + +const Submissions: NextPage = () => { + const router = useRouter() + const [pagination, setPagination] = useState({ + pageSize: 25, + }) + const [form, setForm] = useState() + const [entries, setEntries] = useState() + const {loading, refetch} = useQuery(ADMIN_PAGER_SUBMISSION_QUERY, { + variables: { + form: router.query.id as string, + limit: pagination.pageSize, + start: pagination.current * pagination.pageSize || 0 + }, + onCompleted: ({pager, form}) => { + setPagination({ + ...pagination, + total: pager.total, + }) + setForm(form) + setEntries(pager.entries) + } + }) + + const columns = [ + { + title: 'Values', + dataIndex: 'fields', + render: fields =>
{JSON.stringify(fields, null, 2)}
+ }, + { + title: 'Created', + dataIndex: 'created', + render: date => + }, + { + title: 'Last Change', + dataIndex: 'lastModified', + render: date => + }, + { + render: row => { + return ( + + + + + + + ) + } + }, + ] + + return ( + + + , + , + ]} + > + { + setPagination(pagination) + }} + /> + + ) +} + +export default withAuth(Submissions, ['admin']) diff --git a/pages/admin/forms/index.tsx b/pages/admin/forms/index.tsx index f2eae80..7070946 100644 --- a/pages/admin/forms/index.tsx +++ b/pages/admin/forms/index.tsx @@ -1,4 +1,4 @@ -import {DeleteOutlined, EditOutlined, GlobalOutlined} from '@ant-design/icons/lib' +import {DeleteOutlined, EditOutlined, GlobalOutlined, UnorderedListOutlined} from '@ant-design/icons/lib' import {useQuery} from '@apollo/react-hooks' import {Button, Popconfirm, Space, Table, Tooltip} from 'antd' import {PaginationProps} from 'antd/es/pagination' @@ -8,11 +8,11 @@ import Structure from 'components/structure' import {TimeAgo} from 'components/time.ago' import {withAuth} from 'components/with.auth' import { - PAGER_FORM_QUERY, - PagerFormEntryQueryData, - PagerFormQueryData, - PagerFormQueryVariables -} from 'graphql/query/pager.form.query' + ADMIN_PAGER_FORM_QUERY, + AdminPagerFormEntryQueryData, + AdminPagerFormQueryData, + AdminPagerFormQueryVariables +} from 'graphql/query/admin.pager.form.query' import {NextPage} from 'next' import Link from 'next/link' import React, {useState} from 'react' @@ -21,9 +21,8 @@ const Index: NextPage = () => { const [pagination, setPagination] = useState({ pageSize: 25, }) - const [entries, setEntries] = useState() - // TODO limit forms if user is only admin! - const {loading, refetch} = useQuery(PAGER_FORM_QUERY, { + const [entries, setEntries] = useState() + const {loading, refetch} = useQuery(ADMIN_PAGER_FORM_QUERY, { variables: { limit: pagination.pageSize, start: pagination.current * pagination.pageSize || 0 @@ -80,6 +79,15 @@ const Index: NextPage = () => { render: row => { return ( + + + + + + = ({id}) => { const windowSize = useWindowSize() const [swiper, setSwiper] = useState(null) + const submission = useSubmission(id) const {loading, data, error} = useQuery(FORM_QUERY, { variables: { @@ -70,11 +72,18 @@ const Index: NextPage = ({id}) => { next={goNext} prev={goPrev} /> : undefined, - ...data.form.fields.map(field => ( + ...data.form.fields.map((field, i) => ( { + submission.setField(field.id, values[field.id]) + + if (data.form.fields.length === i - 1) { + submission.finish() + } + }} next={goNext} prev={goPrev} /> @@ -84,7 +93,7 @@ const Index: NextPage = ({id}) => { type={'end'} page={data.form.endPage} design={design} - next={goNext} + next={submission.finish} prev={goPrev} /> : undefined ].filter(e => !!e)}