97 | Report is not found in Formio. Please make sure that you are
98 | using the Formio Reporting module and it is correctly included
99 | in your application.
100 |
101 | );
102 | }
103 |
104 | return (element = el)} />;
105 | };
106 |
107 | /**
108 | * @typedef {object} Options
109 | * @property {boolean} [readOnly]
110 | * @property {boolean} [noAlerts]
111 | * @property {object} [i18n]
112 | * @property {string} [template]
113 | * @property {string} [projectEndpoint]
114 | */
115 |
116 | /**
117 | * @typedef {object} ReportProps
118 | * @property {string} [src]
119 | * @property {string} [projectEndpoint]
120 | * @property {object} [report]
121 | * @property {Options} [options]
122 | * @property {function} [onFormLoad]
123 | * @property {function} [onError]
124 | * @property {function} [onRender]
125 | * @property {function} [onFocus]
126 | * @property {function} [onBlur]
127 | * @property {function} [onInitialized]
128 | * @property {function} [onReportReady]
129 | * @property {function} [onChange]
130 | * @property {function} [onRowClick]
131 | * @property {function} [onRowSelectChange]
132 | * @property {function} [onFetchDataError]
133 | * @property {function} [onChangeItemsPerPage]
134 | * @property {function} [onPage]
135 | u */
136 | Report.propTypes = {
137 | src: PropTypes.string,
138 | projectEndpoint: PropTypes.string,
139 | report: PropTypes.object,
140 | options: PropTypes.shape({
141 | readOnly: PropTypes.bool,
142 | noAlerts: PropTypes.bool,
143 | i18n: PropTypes.object,
144 | template: PropTypes.string,
145 | language: PropTypes.string,
146 | }),
147 | onRowClick: PropTypes.func,
148 | onRowSelectChange: PropTypes.func,
149 | onFetchDataError: PropTypes.func,
150 | onChangeItemsPerPage: PropTypes.func,
151 | onPage: PropTypes.func,
152 | onChange: PropTypes.func,
153 | onFormLoad: PropTypes.func,
154 | onError: PropTypes.func,
155 | onRender: PropTypes.func,
156 | onFocus: PropTypes.func,
157 | onBlur: PropTypes.func,
158 | onInitialized: PropTypes.func,
159 | onReportReady: PropTypes.func,
160 | };
161 |
162 | Report.getDefaultEmitter = () => {
163 | return new EventEmitter({
164 | wildcard: false,
165 | maxListeners: 0,
166 | });
167 | };
168 |
169 | export default Report;
170 |
--------------------------------------------------------------------------------
/src/components/SubmissionGrid.tsx:
--------------------------------------------------------------------------------
1 | import { Utils, Component } from '@formio/core';
2 | import { useState, useEffect, useCallback, ReactNode } from 'react';
3 | import { usePagination } from '../hooks/usePagination';
4 | import { useFormioContext } from '../hooks/useFormioContext';
5 | import { FormProps, FormType, JSON } from './Form';
6 | import { ComponentProp } from './FormGrid';
7 |
8 | type FormioPaginationResponse = FetchedSubmission[] & {
9 | serverCount: number;
10 | };
11 | export type FetchedSubmission = NonNullable
& {
12 | _id: string;
13 | };
14 |
15 | export type SubmissionTableProps = {
16 | submissions?: FetchedSubmission[];
17 | components?: {
18 | Container?: ComponentProp<{ children: ReactNode }>;
19 | TableContainer?: ComponentProp<{ children: ReactNode }>;
20 | TableHeadContainer?: ComponentProp<{ children: ReactNode }>;
21 | TableHeadCell?: ComponentProp<{ children: ReactNode }>;
22 | TableBodyRowContainer?: ComponentProp<{
23 | children: ReactNode;
24 | onClick?: () => void;
25 | }>;
26 | TableHeaderRowContainer?: ComponentProp<{ children: ReactNode }>;
27 | TableBodyContainer?: ComponentProp<{ children: ReactNode }>;
28 | TableCell?: ComponentProp<{ children: ReactNode }>;
29 | PaginationContainer?: ComponentProp<{ children: ReactNode }>;
30 | PaginationButton?: ComponentProp<{
31 | children: ReactNode;
32 | isActive?: boolean;
33 | disabled?: boolean;
34 | onClick: () => void;
35 | }>;
36 | };
37 | onSubmissionClick?: (id: string) => void;
38 | limit: number;
39 | submissionQuery?: {
40 | [key: string]: JSON;
41 | };
42 | formId?: string;
43 | };
44 | type Row = { data: JSON[]; id: string };
45 |
46 | const DEFAULT_COMPONENTS = {};
47 | const DEFAULT_QUERY = {};
48 |
49 | const isFormioPaginationResponse = (
50 | obj: unknown,
51 | ): obj is FormioPaginationResponse => {
52 | return !!obj && Object.prototype.hasOwnProperty.call(obj, 'serverCount');
53 | };
54 |
55 | const toString = (value: JSON) => {
56 | switch (typeof value) {
57 | case 'object':
58 | case 'number':
59 | return JSON.stringify(value);
60 | default:
61 | return value;
62 | }
63 | };
64 | const getColumnsAndCells = (
65 | form: FormType,
66 | submissions: FetchedSubmission[],
67 | ) => {
68 | const columnsSet = new Set<{ key: string; label: string }>();
69 | Utils.eachComponent(form.components, (component: Component) => {
70 | if (
71 | !Object.prototype.hasOwnProperty.call(component, 'tableView') ||
72 | component.tableView
73 | ) {
74 | columnsSet.add({
75 | key: component.key,
76 | label: component.label ?? component.key,
77 | });
78 | }
79 | });
80 | const columns = Array.from(columnsSet);
81 | const cells: Row[] = submissions.map((submission) => {
82 | const row: JSON[] = columns.map((column) => {
83 | return submission.data?.[column.key] ?? '';
84 | });
85 | return { data: row, id: submission._id };
86 | });
87 | return { columns, cells };
88 | };
89 |
90 | export const SubmissionTable = ({
91 | formId,
92 | limit,
93 | submissions,
94 | onSubmissionClick,
95 | components = DEFAULT_COMPONENTS,
96 | submissionQuery = DEFAULT_QUERY,
97 | }: SubmissionTableProps) => {
98 | const {
99 | Container = ({ children }) => {children}
,
100 | TableContainer = ({ children }) => ,
101 | TableHeadContainer = ({ children }) => {children},
102 | TableHeaderRowContainer = ({ children }) => {children}
,
103 | TableHeadCell = ({ children }) => {children} | ,
104 | TableBodyRowContainer = ({ children, onClick }) => (
105 | {children}
106 | ),
107 | TableBodyContainer = ({ children }) => {children},
108 | TableCell = ({ children }) => {children} | ,
109 | PaginationContainer = ({ children }) => ,
110 | PaginationButton = ({ children }) => {children},
111 | } = components;
112 | const [form, setForm] = useState();
113 | const { Formio } = useFormioContext();
114 | const fetchFunction = useCallback(
115 | (limit: number, skip: number) => {
116 | if (!formId) {
117 | console.warn(
118 | "You're trying to fetch submissions without a form ID, did you mean to pass a submissions prop instead?",
119 | );
120 | return Promise.resolve([]);
121 | }
122 | const formio = new Formio(
123 | `${Formio.projectUrl || Formio.baseUrl}/form/${formId}`,
124 | );
125 | return formio.loadSubmissions({
126 | params: { ...submissionQuery, limit, skip },
127 | });
128 | },
129 | [submissionQuery, Formio, formId],
130 | );
131 | const dataOrFnArg = submissions ? submissions : fetchFunction;
132 | const { data, total, page, nextPage, prevPage, setPage, hasMore } =
133 | usePagination(1, limit, dataOrFnArg);
134 | const { columns, cells } = form
135 | ? getColumnsAndCells(form, data)
136 | : { columns: [], cells: [] };
137 |
138 | useEffect(() => {
139 | const fetchForm = async () => {
140 | const formio = new Formio(
141 | `${Formio.projectUrl || Formio.baseUrl}/form/${formId}`,
142 | );
143 | setForm(await formio.loadForm());
144 | };
145 | fetchForm();
146 | }, [Formio, formId]);
147 |
148 | return (
149 |
150 |
151 |
152 |
153 | {form &&
154 | columns.map(({ key, label }) => {
155 | return (
156 |
157 | {label}
158 |
159 | );
160 | })}
161 |
162 |
163 |
164 | {cells.map(({ data, id }, index) => (
165 | {
168 | onSubmissionClick?.(id);
169 | }}
170 | >
171 | {form &&
172 | data.map((cell, index) => (
173 |
174 | {toString(cell)}
175 |
176 | ))}
177 |
178 | ))}
179 |
180 |
181 |
182 |
183 | Prev
184 |
185 | {isFormioPaginationResponse(data) &&
186 | !total &&
187 | Array.from(
188 | {
189 | length: Math.ceil(data.serverCount / limit),
190 | },
191 | (_, i) => i + 1,
192 | ).map((n) => (
193 | setPage(n)}
196 | isActive={n === page}
197 | >
198 | {n}
199 |
200 | ))}
201 | {data &&
202 | total &&
203 | Array.from(
204 | {
205 | length: Math.ceil(total / limit),
206 | },
207 | (_, i) => i + 1,
208 | ).map((n) => (
209 | setPage(n)}
212 | isActive={n === page}
213 | >
214 | {n}
215 |
216 | ))}
217 | {}
218 |
219 | Next
220 |
221 |
222 |
223 | );
224 | };
225 |
--------------------------------------------------------------------------------
/src/components/__tests__/Form.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import { screen } from '@testing-library/dom';
3 | import '@testing-library/jest-dom';
4 |
5 | import { Form } from '../Form';
6 |
7 | const simpleForm = {
8 | display: 'form' as const,
9 | components: [
10 | {
11 | label: 'First Name',
12 | key: 'firstName',
13 | type: 'textfield',
14 | input: true,
15 | },
16 | {
17 | label: 'Last Name',
18 | key: 'lastName',
19 | type: 'textfield',
20 | input: true,
21 | validate: {
22 | required: true,
23 | },
24 | },
25 | {
26 | label: 'Submit',
27 | type: 'button',
28 | key: 'submit',
29 | input: true,
30 | disableOnInvalid: true,
31 | },
32 | ],
33 | };
34 |
35 | test('loads and displays a simple form', async () => {
36 | const executeTests = async () => {
37 | expect(screen.getByText('First Name')).toBeInTheDocument();
38 | expect(screen.getByText('Last Name')).toBeInTheDocument();
39 | expect(screen.getByText('Submit')).toBeInTheDocument();
40 | expect(await screen.findByRole('button')).toBeDisabled();
41 | };
42 | render();
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Form';
2 | export * from './FormBuilder';
3 | export * from './FormEdit';
4 | export * from './FormGrid';
5 | export * from './SubmissionGrid';
6 | export { default as Errors } from './Errors';
7 | export { default as ReactComponent } from './ReactComponent';
8 | export { default as Report } from './Report';
9 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { AllItemsPerPage } from './types';
2 |
3 | export const defaultPageSizes = [10, 25, 50, 100, AllItemsPerPage];
4 |
--------------------------------------------------------------------------------
/src/contexts/FormioContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useState, useEffect } from 'react';
2 | import { Formio as ImportedFormio } from '@formio/js';
3 |
4 | type BaseConfigurationArgs = {
5 | baseUrl?: string;
6 | projectUrl?: string;
7 | Formio?: typeof ImportedFormio;
8 | };
9 |
10 | const useBaseConfiguration = ({
11 | baseUrl,
12 | projectUrl,
13 | Formio,
14 | }: BaseConfigurationArgs) => {
15 | if (!Formio) {
16 | if (baseUrl) {
17 | ImportedFormio.setBaseUrl(baseUrl);
18 | }
19 | if (projectUrl) {
20 | ImportedFormio.setProjectUrl(projectUrl);
21 | }
22 | return {
23 | Formio: ImportedFormio,
24 | baseUrl: ImportedFormio.baseUrl,
25 | projectUrl: ImportedFormio.projectUrl,
26 | };
27 | }
28 |
29 | if (baseUrl) {
30 | Formio.setBaseUrl(baseUrl);
31 | }
32 | if (projectUrl) {
33 | Formio.setProjectUrl(projectUrl);
34 | }
35 |
36 | return {
37 | Formio,
38 | baseUrl: Formio.baseUrl,
39 | projectUrl: Formio.projectUrl,
40 | };
41 | };
42 |
43 | const useAuthentication = ({ Formio }: { Formio: typeof ImportedFormio }) => {
44 | const [token, setToken] = useState(Formio.getToken() || null);
45 | const [isAuthenticated, setIsAuthenticated] = useState(!!token);
46 |
47 | useEffect(() => {
48 | const handleUserEvent = async (user: unknown) => {
49 | if (user) {
50 | setToken(Formio.getToken());
51 | setIsAuthenticated(true);
52 | } else if (isAuthenticated) {
53 | await Formio.logout();
54 | setToken(null);
55 | setIsAuthenticated(false);
56 | }
57 | };
58 |
59 | const handleStaleToken = async () => {
60 | if (isAuthenticated) {
61 | const user = await Formio.currentUser();
62 | if (!user) {
63 | setToken(null);
64 | setIsAuthenticated(false);
65 | }
66 | }
67 | };
68 |
69 | Formio.events.on('formio.user', handleUserEvent);
70 | handleStaleToken();
71 |
72 | return () => {
73 | Formio.events.off('formio.user', handleUserEvent);
74 | };
75 | }, [isAuthenticated, Formio]);
76 |
77 | const logout = async () => {
78 | await Formio.logout();
79 | setToken(null);
80 | setIsAuthenticated(false);
81 | };
82 |
83 | return { token, setToken, isAuthenticated, logout };
84 | };
85 |
86 | export const FormioContext = createContext<
87 | | (ReturnType &
88 | ReturnType)
89 | | null
90 | >(null);
91 |
92 | export function FormioProvider({
93 | children,
94 | baseUrl,
95 | projectUrl,
96 | Formio,
97 | }: { children: React.ReactNode } & BaseConfigurationArgs) {
98 | const baseConfig = useBaseConfiguration({ baseUrl, projectUrl, Formio });
99 | const auth = useAuthentication({ Formio: baseConfig.Formio });
100 | const formio = { ...baseConfig, ...auth };
101 | return (
102 |
103 | {children}
104 |
105 | );
106 | }
107 |
--------------------------------------------------------------------------------
/src/hooks/__tests__/usePagination.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { waitFor } from '@testing-library/dom';
3 | import { usePagination } from '../usePagination';
4 |
5 | it('should return correct paginated data when passed a static array', () => {
6 | const myData = Array.from(
7 | {
8 | length: 100,
9 | },
10 | (_, i) => i,
11 | );
12 | const { result } = renderHook(() => usePagination(1, 10, myData));
13 | const { data } = result.current;
14 | expect(data).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
15 | });
16 |
17 | it('should return correct paginated data when passed a fetch function', async () => {
18 | const fakeData = Array.from({ length: 100 }, (_, i) => i);
19 | const fetchFunction = async (limit: number, skip: number) => {
20 | return fakeData.slice(skip, skip + limit);
21 | };
22 | const { result } = renderHook(() => usePagination(1, 10, fetchFunction));
23 | waitFor(() => {
24 | const { data } = result.current;
25 | expect(data).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/hooks/useFormioContext.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { FormioContext } from '../contexts/FormioContext';
3 |
4 | export function useFormioContext() {
5 | const context = useContext(FormioContext);
6 |
7 | if (!context) {
8 | throw new Error(
9 | 'useFormioContext must be used within a FormioProvider component.',
10 | );
11 | }
12 | return context;
13 | }
14 |
--------------------------------------------------------------------------------
/src/hooks/usePagination.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from 'react';
2 |
3 | type PaginationResult = {
4 | data: T[];
5 | total: number | undefined;
6 | page: number;
7 | hasMore: boolean;
8 | nextPage: () => void;
9 | prevPage: () => void;
10 | setPage: (page: number) => void;
11 | fetchPage: (page: number, limit: number) => Promise;
12 | };
13 |
14 | type FetchFunction = (limit: number, skip: number) => Promise;
15 |
16 | export function usePagination(
17 | initialPage: number,
18 | limit: number,
19 | dataOrFetchFunction: T[] | FetchFunction,
20 | ): PaginationResult {
21 | const [data, setData] = useState([]);
22 | const [page, setPage] = useState(initialPage);
23 | const [hasMore, setHasMore] = useState(true);
24 | const total: number | undefined = Array.isArray(dataOrFetchFunction)
25 | ? dataOrFetchFunction.length
26 | : undefined;
27 | let serverCount: number | undefined;
28 |
29 | const fetchPage = useCallback(
30 | async (page: number): Promise => {
31 | const skip = (page - 1) * limit;
32 | let result;
33 | if (Array.isArray(dataOrFetchFunction)) {
34 | result = dataOrFetchFunction.slice(skip, skip + limit);
35 | serverCount = dataOrFetchFunction.length;
36 | setData(result);
37 | } else {
38 | result = await dataOrFetchFunction(limit, skip);
39 | serverCount = (result as any).serverCount;
40 | setData(result);
41 | }
42 | if (serverCount !== undefined) {
43 | setHasMore(page * limit < serverCount);
44 | } else {
45 | setHasMore(result.length >= limit);
46 | }
47 | },
48 | [limit, dataOrFetchFunction],
49 | );
50 |
51 | const nextPage = () => {
52 | if (hasMore) {
53 | const newPage = page + 1;
54 | fetchPage(newPage);
55 | setPage(newPage);
56 | }
57 | };
58 |
59 | const prevPage = () => {
60 | if (page > 1) {
61 | const newPage = page - 1;
62 | fetchPage(newPage);
63 | setPage(newPage);
64 | }
65 | };
66 |
67 | useEffect(() => {
68 | fetchPage(page);
69 | }, [fetchPage, page]);
70 |
71 | return {
72 | data,
73 | page,
74 | hasMore,
75 | nextPage,
76 | prevPage,
77 | total,
78 | setPage: (page: number) => {
79 | setPage(page);
80 | fetchPage(page);
81 | },
82 | fetchPage,
83 | };
84 | }
85 |
--------------------------------------------------------------------------------
/src/hooks/useTraceUpdate.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export function useTraceUpdate(props: any) {
4 | const prev = useRef(props);
5 | useEffect(() => {
6 | const changedProps = Object.entries(props).reduce((ps: any, [k, v]) => {
7 | if (prev.current[k] !== v) {
8 | ps[k] = [prev.current[k], v];
9 | }
10 | return ps;
11 | }, {});
12 | if (Object.keys(changedProps).length > 0) {
13 | console.log('Changed props:', changedProps);
14 | }
15 | prev.current = props;
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Formio } from '@formio/js';
2 | const Webform = Formio.Webform;
3 | const WebformBuilder = Formio.WebformBuilder;
4 | const Wizard = Formio.Wizard;
5 | const WizardBuilder = Formio.WizardBuilder;
6 |
7 | export { Webform, WebformBuilder, Wizard, WizardBuilder };
8 |
9 | export * from './components';
10 | export { useFormioContext } from './hooks/useFormioContext';
11 | export { usePagination } from './hooks/usePagination';
12 | export { FormioProvider } from './contexts/FormioContext';
13 | export * from './constants';
14 | export * from './modules';
15 | export * from './types';
16 | export * from './utils';
17 | export { Components, Utils, Templates } from '@formio/js';
18 |
--------------------------------------------------------------------------------
/src/modules/auth/actions.js:
--------------------------------------------------------------------------------
1 | import { Formio as formiojs } from '@formio/js';
2 | import * as type from './constants';
3 |
4 | const requestUser = () => ({
5 | type: type.USER_REQUEST,
6 | });
7 |
8 | const receiveUser = (user) => ({
9 | type: type.USER_REQUEST_SUCCESS,
10 | user,
11 | });
12 |
13 | const failUser = (error) => ({
14 | type: type.USER_REQUEST_FAILURE,
15 | error,
16 | });
17 |
18 | const logoutUser = () => ({
19 | type: type.USER_LOGOUT,
20 | });
21 |
22 | const submissionAccessUser = (submissionAccess) => ({
23 | type: type.USER_SUBMISSION_ACCESS,
24 | submissionAccess,
25 | });
26 |
27 | const formAccessUser = (formAccess) => ({
28 | type: type.USER_FORM_ACCESS,
29 | formAccess,
30 | });
31 |
32 | const projectAccessUser = (projectAccess) => ({
33 | type: type.USER_PROJECT_ACCESS,
34 | projectAccess,
35 | });
36 |
37 | const rolesUser = (roles) => ({
38 | type: type.USER_ROLES,
39 | roles,
40 | });
41 |
42 | function transformSubmissionAccess(forms) {
43 | return Object.values(forms).reduce(
44 | (result, form) => ({
45 | ...result,
46 | [form.name]: form.submissionAccess.reduce(
47 | (formSubmissionAccess, access) => ({
48 | ...formSubmissionAccess,
49 | [access.type]: access.roles,
50 | }),
51 | {},
52 | ),
53 | }),
54 | {},
55 | );
56 | }
57 |
58 | function transformFormAccess(forms) {
59 | return Object.values(forms).reduce(
60 | (result, form) => ({
61 | ...result,
62 | [form.name]: form.access.reduce(
63 | (formAccess, access) => ({
64 | ...formAccess,
65 | [access.type]: access.roles,
66 | }),
67 | {},
68 | ),
69 | }),
70 | {},
71 | );
72 | }
73 |
74 | function transformProjectAccess(projectAccess) {
75 | return projectAccess.reduce(
76 | (result, access) => ({
77 | ...result,
78 | [access.type]: access.roles,
79 | }),
80 | {},
81 | );
82 | }
83 |
84 | export const initAuth = () => (dispatch) => {
85 | const projectUrl = formiojs.getProjectUrl();
86 |
87 | dispatch(requestUser());
88 |
89 | Promise.all([
90 | formiojs.currentUser(),
91 | formiojs
92 | .makeStaticRequest(`${projectUrl}/access`)
93 | .then((result) => {
94 | const submissionAccess = transformSubmissionAccess(
95 | result.forms,
96 | );
97 | const formAccess = transformFormAccess(result.forms);
98 |
99 | dispatch(submissionAccessUser(submissionAccess));
100 | dispatch(formAccessUser(formAccess));
101 | dispatch(rolesUser(result.roles));
102 | })
103 | .catch(() => {}),
104 | formiojs
105 | .makeStaticRequest(projectUrl)
106 | .then((project) => {
107 | const projectAccess = transformProjectAccess(project.access);
108 | dispatch(projectAccessUser(projectAccess));
109 | })
110 | .catch(() => {}),
111 | ])
112 | .then(([user]) => {
113 | if (user) {
114 | dispatch(receiveUser(user));
115 | } else {
116 | dispatch(logoutUser());
117 | }
118 | })
119 | .catch((result) => {
120 | dispatch(failUser(result));
121 | });
122 | };
123 |
124 | export const setUser = (user) => (dispatch) => {
125 | formiojs.setUser(user);
126 | dispatch(receiveUser(user));
127 | };
128 |
129 | export const logout = () => (dispatch) => {
130 | formiojs.logout().then(() => {
131 | dispatch(logoutUser());
132 | });
133 | };
134 |
--------------------------------------------------------------------------------
/src/modules/auth/constants.js:
--------------------------------------------------------------------------------
1 | export const USER_REQUEST = 'USER_REQUEST';
2 | export const USER_REQUEST_SUCCESS = 'USER_REQUEST_SUCCESS';
3 | export const USER_REQUEST_FAILURE = 'USER_REQUEST_FAILURE';
4 | export const USER_LOGOUT = 'USER_LOGOUT';
5 | export const USER_SUBMISSION_ACCESS = 'USER_SUBMISSION_ACCESS';
6 | export const USER_FORM_ACCESS = 'USER_FORM_ACCESS';
7 | export const USER_PROJECT_ACCESS = 'USER_PROJECT_ACCESS';
8 | export const USER_ROLES = 'USER_ROLES';
9 |
--------------------------------------------------------------------------------
/src/modules/auth/index.js:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './constants';
3 | export * from './reducers';
4 | export * from './selectors';
5 |
--------------------------------------------------------------------------------
/src/modules/auth/reducers.js:
--------------------------------------------------------------------------------
1 | import * as type from './constants';
2 |
3 | const initialState = {
4 | init: false,
5 | isActive: false,
6 | user: null,
7 | authenticated: false,
8 | submissionAccess: {},
9 | formAccess: {},
10 | projectAccess: {},
11 | roles: {},
12 | is: {},
13 | error: '',
14 | };
15 |
16 | function mapProjectRolesToUserRoles(projectRoles, userRoles) {
17 | return Object.entries(projectRoles).reduce(
18 | (result, [name, role]) => ({
19 | ...result,
20 | [name]: userRoles.includes(role._id),
21 | }),
22 | {},
23 | );
24 | }
25 |
26 | function getUserRoles(projectRoles) {
27 | return Object.keys(projectRoles).reduce(
28 | (result, name) => ({
29 | ...result,
30 | [name]: name === 'anonymous',
31 | }),
32 | {},
33 | );
34 | }
35 |
36 | export const auth =
37 | () =>
38 | (state = initialState, action) => {
39 | switch (action.type) {
40 | case type.USER_REQUEST:
41 | return {
42 | ...state,
43 | init: true,
44 | submissionAccess: false,
45 | isActive: true,
46 | };
47 | case type.USER_REQUEST_SUCCESS:
48 | return {
49 | ...state,
50 | isActive: false,
51 | user: action.user,
52 | authenticated: true,
53 | is: mapProjectRolesToUserRoles(
54 | state.roles,
55 | action.user.roles,
56 | ),
57 | error: '',
58 | };
59 | case type.USER_REQUEST_FAILURE:
60 | return {
61 | ...state,
62 | isActive: false,
63 | is: getUserRoles(state.roles),
64 | error: action.error,
65 | };
66 | case type.USER_LOGOUT:
67 | return {
68 | ...state,
69 | user: null,
70 | isActive: false,
71 | authenticated: false,
72 | is: getUserRoles(state.roles),
73 | error: '',
74 | };
75 | case type.USER_SUBMISSION_ACCESS:
76 | return {
77 | ...state,
78 | submissionAccess: action.submissionAccess,
79 | };
80 | case type.USER_FORM_ACCESS:
81 | return {
82 | ...state,
83 | formAccess: action.formAccess,
84 | };
85 | case type.USER_PROJECT_ACCESS:
86 | return {
87 | ...state,
88 | projectAccess: action.projectAccess,
89 | };
90 | case type.USER_ROLES:
91 | return {
92 | ...state,
93 | roles: action.roles,
94 | };
95 | default:
96 | return state;
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/src/modules/auth/selectors.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/src/modules/form/actions.js:
--------------------------------------------------------------------------------
1 | import { Formio as Formiojs } from '@formio/js';
2 | import * as types from './constants';
3 | import { selectForm } from './selectors';
4 |
5 | export const clearFormError = (name) => ({
6 | type: types.FORM_CLEAR_ERROR,
7 | name,
8 | });
9 |
10 | const requestForm = (name, id, url) => ({
11 | type: types.FORM_REQUEST,
12 | name,
13 | id,
14 | url,
15 | });
16 |
17 | const receiveForm = (name, form, url) => ({
18 | type: types.FORM_SUCCESS,
19 | form,
20 | name,
21 | url,
22 | });
23 |
24 | const failForm = (name, err) => ({
25 | type: types.FORM_FAILURE,
26 | error: err,
27 | name,
28 | });
29 |
30 | export const resetForm = (name) => ({
31 | type: types.FORM_RESET,
32 | name,
33 | });
34 |
35 | const sendForm = (name, form) => ({
36 | type: types.FORM_SAVE,
37 | form,
38 | name,
39 | });
40 |
41 | export const getForm = (name, id = '', done = () => {}) => {
42 | return (dispatch, getState) => {
43 | // Check to see if the form is already loaded.
44 | const form = selectForm(name, getState());
45 | if (
46 | form.components &&
47 | Array.isArray(form.components) &&
48 | form.components.length &&
49 | form._id === id
50 | ) {
51 | return;
52 | }
53 |
54 | const path = `${Formiojs.getProjectUrl()}/${id ? `form/${id}` : name}`;
55 | const formio = new Formiojs(path);
56 |
57 | dispatch(requestForm(name, id, path));
58 |
59 | return formio
60 | .loadForm()
61 | .then((result) => {
62 | dispatch(receiveForm(name, result));
63 | done(null, result);
64 | })
65 | .catch((result) => {
66 | dispatch(failForm(name, result));
67 | done(result);
68 | });
69 | };
70 | };
71 |
72 | export const saveForm = (name, form, done = () => {}) => {
73 | return (dispatch) => {
74 | dispatch(sendForm(name, form));
75 |
76 | const id = form._id;
77 | const path = `${Formiojs.getProjectUrl()}/form${id ? `/${id}` : ''}`;
78 | const formio = new Formiojs(path);
79 |
80 | formio
81 | .saveForm(form)
82 | .then((result) => {
83 | const url = `${Formiojs.getProjectUrl()}/form/${result._id}`;
84 | dispatch(receiveForm(name, result, url));
85 | done(null, result);
86 | })
87 | .catch((result) => {
88 | dispatch(failForm(name, result));
89 | done(result);
90 | });
91 | };
92 | };
93 |
94 | export const deleteForm = (name, id, done = () => {}) => {
95 | return (dispatch) => {
96 | const path = `${Formiojs.getProjectUrl()}/form/${id}`;
97 | const formio = new Formiojs(path);
98 |
99 | return formio
100 | .deleteForm()
101 | .then(() => {
102 | dispatch(resetForm(name));
103 | done();
104 | })
105 | .catch((result) => {
106 | dispatch(failForm(name, result));
107 | done(result);
108 | });
109 | };
110 | };
111 |
--------------------------------------------------------------------------------
/src/modules/form/constants.js:
--------------------------------------------------------------------------------
1 | export const FORM_CLEAR_ERROR = 'FORM_CLEAR_ERROR';
2 | export const FORM_REQUEST = 'FORM_REQUEST';
3 | export const FORM_SUCCESS = 'FORM_SUCCESS';
4 | export const FORM_FAILURE = 'FORM_FAILURE';
5 | export const FORM_SAVE = 'FORM_SAVE';
6 | export const FORM_RESET = 'FORM_RESET';
7 |
--------------------------------------------------------------------------------
/src/modules/form/index.js:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './constants';
3 | export * from './reducers';
4 | export * from './selectors';
5 |
--------------------------------------------------------------------------------
/src/modules/form/reducers.js:
--------------------------------------------------------------------------------
1 | import * as types from './constants';
2 |
3 | export function form(config) {
4 | const initialState = {
5 | id: '',
6 | isActive: false,
7 | lastUpdated: 0,
8 | form: {},
9 | url: '',
10 | error: '',
11 | };
12 |
13 | return (state = initialState, action) => {
14 | // Only proceed for this form.
15 | if (action.name !== config.name) {
16 | return state;
17 | }
18 | switch (action.type) {
19 | case types.FORM_CLEAR_ERROR:
20 | return {
21 | ...state,
22 | error: '',
23 | };
24 | case types.FORM_REQUEST:
25 | return {
26 | ...state,
27 | isActive: true,
28 | id: action.id,
29 | form: {},
30 | url: action.url,
31 | error: '',
32 | };
33 | case types.FORM_SUCCESS:
34 | return {
35 | ...state,
36 | isActive: false,
37 | id: action.form._id,
38 | form: action.form,
39 | url: action.url || state.url,
40 | error: '',
41 | };
42 | case types.FORM_FAILURE:
43 | return {
44 | ...state,
45 | isActive: false,
46 | isInvalid: true,
47 | error: action.error,
48 | };
49 | case types.FORM_SAVE:
50 | return {
51 | ...state,
52 | isActive: true,
53 | };
54 | case types.FORM_RESET:
55 | return initialState;
56 | default:
57 | return state;
58 | }
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/modules/form/selectors.js:
--------------------------------------------------------------------------------
1 | import { selectRoot } from '../root';
2 |
3 | export const selectForm = (name, state) => selectRoot(name, state).form;
4 |
--------------------------------------------------------------------------------
/src/modules/forms/actions.js:
--------------------------------------------------------------------------------
1 | import { Formio as Formiojs } from '@formio/js';
2 |
3 | import { selectRoot } from '../root';
4 |
5 | import * as types from './constants';
6 |
7 | export const resetForms = (name) => ({
8 | type: types.FORMS_RESET,
9 | name,
10 | });
11 |
12 | const requestForms = (name, page, params) => ({
13 | type: types.FORMS_REQUEST,
14 | name,
15 | page,
16 | params,
17 | });
18 |
19 | const receiveForms = (name, forms) => ({
20 | type: types.FORMS_SUCCESS,
21 | name,
22 | forms,
23 | });
24 |
25 | const failForms = (name, error) => ({
26 | type: types.FORMS_FAILURE,
27 | name,
28 | error,
29 | });
30 |
31 | export const indexForms =
32 | (name, page = 1, params = {}, done = () => {}) =>
33 | (dispatch, getState) => {
34 | dispatch(requestForms(name, page, params));
35 |
36 | const { limit, query, select, sort } = selectRoot(name, getState());
37 | const formio = new Formiojs(`${Formiojs.getProjectUrl()}/form`);
38 | const requestParams = { ...query, ...params };
39 |
40 | // Ten is the default so if set to 10, don't send.
41 | if (limit !== 10) {
42 | requestParams.limit = limit;
43 | } else {
44 | delete requestParams.limit;
45 | }
46 |
47 | if (page !== 1) {
48 | requestParams.skip = (page - 1) * limit;
49 | } else {
50 | delete requestParams.skip;
51 | }
52 |
53 | if (select) {
54 | requestParams.select = select;
55 | } else {
56 | delete requestParams.select;
57 | }
58 |
59 | if (sort) {
60 | requestParams.sort = sort;
61 | } else {
62 | delete requestParams.sort;
63 | }
64 |
65 | return formio
66 | .loadForms({ params: requestParams })
67 | .then((result) => {
68 | dispatch(receiveForms(name, result));
69 | done(null, result);
70 | })
71 | .catch((error) => {
72 | dispatch(failForms(name, error));
73 | done(error);
74 | });
75 | };
76 |
--------------------------------------------------------------------------------
/src/modules/forms/constants.js:
--------------------------------------------------------------------------------
1 | export const FORMS_RESET = 'FORMS_RESET';
2 | export const FORMS_REQUEST = 'FORMS_REQUEST';
3 | export const FORMS_SUCCESS = 'FORMS_SUCCESS';
4 | export const FORMS_FAILURE = 'FORMS_FAILURE';
5 |
--------------------------------------------------------------------------------
/src/modules/forms/index.js:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './constants';
3 | export * from './reducers';
4 | export * from './selectors';
5 |
--------------------------------------------------------------------------------
/src/modules/forms/reducers.js:
--------------------------------------------------------------------------------
1 | import _pick from 'lodash/pick';
2 |
3 | import * as types from './constants';
4 |
5 | export function forms({
6 | name,
7 | limit = 10,
8 | query = {},
9 | select = '',
10 | sort = '',
11 | }) {
12 | const initialState = {
13 | error: '',
14 | forms: [],
15 | isActive: false,
16 | limit,
17 | pagination: {
18 | numPages: 0,
19 | page: 1,
20 | total: 0,
21 | },
22 | query,
23 | select,
24 | sort,
25 | };
26 |
27 | return (state = initialState, action) => {
28 | // Only proceed for this forms.
29 | if (action.name !== name) {
30 | return state;
31 | }
32 |
33 | switch (action.type) {
34 | case types.FORMS_RESET:
35 | return initialState;
36 | case types.FORMS_REQUEST:
37 | return {
38 | ...state,
39 | ..._pick(action.params, [
40 | 'limit',
41 | 'query',
42 | 'select',
43 | 'sort',
44 | ]),
45 | error: '',
46 | forms: [],
47 | isActive: true,
48 | pagination: {
49 | ...state.pagination,
50 | page: action.page,
51 | },
52 | };
53 | case types.FORMS_SUCCESS: {
54 | const total = action.forms.serverCount;
55 |
56 | return {
57 | ...state,
58 | forms: action.forms,
59 | isActive: false,
60 | pagination: {
61 | ...state.pagination,
62 | numPages: Math.ceil(total / state.limit),
63 | total,
64 | },
65 | };
66 | }
67 | case types.FORMS_FAILURE:
68 | return {
69 | ...state,
70 | error: action.error,
71 | isActive: false,
72 | };
73 | default:
74 | return state;
75 | }
76 | };
77 | }
78 |
--------------------------------------------------------------------------------
/src/modules/forms/selectors.js:
--------------------------------------------------------------------------------
1 | import { selectRoot } from '../root';
2 |
3 | export const selectForms = (name, state) => selectRoot(name, state).forms;
4 |
--------------------------------------------------------------------------------
/src/modules/index.js:
--------------------------------------------------------------------------------
1 | export * from './auth';
2 | export * from './form';
3 | export * from './forms';
4 | export * from './root';
5 | export * from './submission';
6 | export * from './submissions';
7 |
--------------------------------------------------------------------------------
/src/modules/root/index.js:
--------------------------------------------------------------------------------
1 | export * from './selectors';
2 |
--------------------------------------------------------------------------------
/src/modules/root/selectors.js:
--------------------------------------------------------------------------------
1 | export const selectRoot = (name, state) => state[name];
2 | export const selectError = (name, state) => selectRoot(name, state).error;
3 | export const selectIsActive = (name, state) => selectRoot(name, state).isActive;
4 |
--------------------------------------------------------------------------------
/src/modules/submission/actions.js:
--------------------------------------------------------------------------------
1 | import { Formio as Formiojs } from '@formio/js';
2 |
3 | import * as types from './constants';
4 |
5 | export const clearSubmissionError = (name) => ({
6 | type: types.SUBMISSION_CLEAR_ERROR,
7 | name,
8 | });
9 |
10 | const requestSubmission = (name, id, formId, url) => ({
11 | type: types.SUBMISSION_REQUEST,
12 | name,
13 | id,
14 | formId,
15 | url,
16 | });
17 |
18 | const sendSubmission = (name) => ({
19 | type: types.SUBMISSION_SAVE,
20 | name,
21 | });
22 |
23 | const receiveSubmission = (name, submission, url) => ({
24 | type: types.SUBMISSION_SUCCESS,
25 | name,
26 | submission,
27 | url,
28 | });
29 |
30 | const failSubmission = (name, error) => ({
31 | type: types.SUBMISSION_FAILURE,
32 | name,
33 | error,
34 | });
35 |
36 | export const resetSubmission = (name) => ({
37 | type: types.SUBMISSION_RESET,
38 | name,
39 | });
40 |
41 | export const getSubmission =
42 | (name, id, formId, done = () => {}) =>
43 | (dispatch, getState) => {
44 | // Check to see if the submission is already loaded.
45 | if (getState().id === id) {
46 | return;
47 | }
48 |
49 | const url = `${Formiojs.getProjectUrl()}/${formId ? `form/${formId}` : name}/submission/${id}`;
50 | const formio = new Formiojs(url);
51 |
52 | dispatch(requestSubmission(name, id, formId, url));
53 |
54 | formio
55 | .loadSubmission()
56 | .then((result) => {
57 | dispatch(receiveSubmission(name, result));
58 | done(null, result);
59 | })
60 | .catch((error) => {
61 | dispatch(failSubmission(name, error));
62 | done(error);
63 | });
64 | };
65 |
66 | export const saveSubmission =
67 | (name, data, formId, done = () => {}) =>
68 | (dispatch) => {
69 | dispatch(sendSubmission(name, data));
70 |
71 | const id = data._id;
72 |
73 | const formio = new Formiojs(
74 | `${Formiojs.getProjectUrl()}/${formId ? `form/${formId}` : name}/submission${id ? `/${id}` : ''}`,
75 | );
76 |
77 | formio
78 | .saveSubmission(data)
79 | .then((result) => {
80 | const url = `${Formiojs.getProjectUrl()}/${formId ? `form/${formId}` : name}/submission/${result._id}`;
81 | dispatch(receiveSubmission(name, result, url));
82 | done(null, result);
83 | })
84 | .catch((error) => {
85 | dispatch(failSubmission(name, error));
86 | done(error);
87 | });
88 | };
89 |
90 | export const deleteSubmission =
91 | (name, id, formId, done = () => {}) =>
92 | (dispatch) => {
93 | const formio = new Formiojs(
94 | `${Formiojs.getProjectUrl()}/${formId ? `form/${formId}` : name}/submission/${id}`,
95 | );
96 |
97 | return formio
98 | .deleteSubmission()
99 | .then(() => {
100 | dispatch(resetSubmission(name));
101 | done(null, true);
102 | })
103 | .catch((error) => {
104 | dispatch(failSubmission(name, error));
105 | done(error);
106 | });
107 | };
108 |
--------------------------------------------------------------------------------
/src/modules/submission/constants.js:
--------------------------------------------------------------------------------
1 | export const SUBMISSION_CLEAR_ERROR = 'SUBMISSION_CLEAR_ERROR';
2 | export const SUBMISSION_REQUEST = 'SUBMISSION_REQUEST';
3 | export const SUBMISSION_SAVE = 'SUBMISSION_SAVE';
4 | export const SUBMISSION_SUCCESS = 'SUBMISSION_SUCCESS';
5 | export const SUBMISSION_FAILURE = 'SUBMISSION_FAILURE';
6 | export const SUBMISSION_RESET = 'SUBMISSION_RESET';
7 |
--------------------------------------------------------------------------------
/src/modules/submission/index.js:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './constants';
3 | export * from './reducers';
4 | export * from './selectors';
5 |
--------------------------------------------------------------------------------
/src/modules/submission/reducers.js:
--------------------------------------------------------------------------------
1 | import * as types from './constants';
2 |
3 | export function submission(config) {
4 | const initialState = {
5 | formId: '',
6 | id: '',
7 | isActive: false,
8 | lastUpdated: 0,
9 | submission: {},
10 | url: '',
11 | error: '',
12 | };
13 |
14 | return (state = initialState, action) => {
15 | // Only proceed for this form.
16 | if (action.name !== config.name) {
17 | return state;
18 | }
19 | switch (action.type) {
20 | case types.SUBMISSION_CLEAR_ERROR:
21 | return {
22 | ...state,
23 | error: '',
24 | };
25 | case types.SUBMISSION_REQUEST:
26 | return {
27 | ...state,
28 | formId: action.formId,
29 | id: action.id,
30 | url: action.url,
31 | submission: {},
32 | isActive: true,
33 | };
34 | case types.SUBMISSION_SAVE:
35 | return {
36 | ...state,
37 | formId: action.formId,
38 | id: action.id,
39 | url: action.url || state.url,
40 | submission: {},
41 | isActive: true,
42 | };
43 | case types.SUBMISSION_SUCCESS:
44 | return {
45 | ...state,
46 | id: action.submission._id,
47 | submission: action.submission,
48 | isActive: false,
49 | error: '',
50 | };
51 | case types.SUBMISSION_FAILURE:
52 | return {
53 | ...state,
54 | isActive: false,
55 | isInvalid: true,
56 | error: action.error,
57 | };
58 | case types.SUBMISSION_RESET:
59 | return initialState;
60 | default:
61 | return state;
62 | }
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/src/modules/submission/selectors.js:
--------------------------------------------------------------------------------
1 | import { selectRoot } from '../root';
2 |
3 | export const selectSubmission = (name, state) =>
4 | selectRoot(name, state).submission;
5 |
--------------------------------------------------------------------------------
/src/modules/submissions/actions.js:
--------------------------------------------------------------------------------
1 | import { Formio as Formiojs } from '@formio/js';
2 |
3 | import { selectRoot } from '../root';
4 |
5 | import * as types from './constants';
6 |
7 | export const resetSubmissions = (name) => ({
8 | type: types.SUBMISSIONS_RESET,
9 | name,
10 | });
11 |
12 | const requestSubmissions = (name, page, params, formId) => ({
13 | type: types.SUBMISSIONS_REQUEST,
14 | name,
15 | page,
16 | params,
17 | formId,
18 | });
19 |
20 | const receiveSubmissions = (name, submissions) => ({
21 | type: types.SUBMISSIONS_SUCCESS,
22 | name,
23 | submissions,
24 | });
25 |
26 | const failSubmissions = (name, error) => ({
27 | type: types.SUBMISSIONS_FAILURE,
28 | name,
29 | error,
30 | });
31 |
32 | export const getSubmissions =
33 | (name, page = 0, params = {}, formId, done = () => {}) =>
34 | (dispatch, getState) => {
35 | dispatch(requestSubmissions(name, page, params, formId));
36 |
37 | const { limit, query, select, sort } = selectRoot(name, getState());
38 | const formio = new Formiojs(
39 | `${Formiojs.getProjectUrl()}/${formId ? `form/${formId}` : name}/submission`,
40 | );
41 | const requestParams = { ...query, ...params };
42 |
43 | // Ten is the default so if set to 10, don't send.
44 | if (limit !== 10) {
45 | requestParams.limit = limit;
46 | } else {
47 | delete requestParams.limit;
48 | }
49 |
50 | if (page !== 1) {
51 | requestParams.skip = (page - 1) * limit;
52 | } else {
53 | delete requestParams.skip;
54 | }
55 |
56 | if (select) {
57 | requestParams.select = select;
58 | } else {
59 | delete requestParams.select;
60 | }
61 |
62 | if (sort) {
63 | requestParams.sort = sort;
64 | } else {
65 | delete requestParams.sort;
66 | }
67 |
68 | return formio
69 | .loadSubmissions({ params: requestParams })
70 | .then((result) => {
71 | dispatch(receiveSubmissions(name, result));
72 | done(null, result);
73 | })
74 | .catch((error) => {
75 | dispatch(failSubmissions(name, error));
76 | done(error);
77 | });
78 | };
79 |
--------------------------------------------------------------------------------
/src/modules/submissions/constants.js:
--------------------------------------------------------------------------------
1 | export const SUBMISSIONS_RESET = 'SUBMISSIONS_RESET';
2 | export const SUBMISSIONS_REQUEST = 'SUBMISSIONS_REQUEST';
3 | export const SUBMISSIONS_SUCCESS = 'SUBMISSIONS_SUCCESS';
4 | export const SUBMISSIONS_FAILURE = 'SUBMISSIONS_FAILURE';
5 |
--------------------------------------------------------------------------------
/src/modules/submissions/index.js:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './constants';
3 | export * from './reducers';
4 | export * from './selectors';
5 |
--------------------------------------------------------------------------------
/src/modules/submissions/reducers.js:
--------------------------------------------------------------------------------
1 | import _pick from 'lodash/pick';
2 |
3 | import * as types from './constants';
4 |
5 | export function submissions({
6 | name,
7 | limit = 10,
8 | query = {},
9 | select = '',
10 | sort = '',
11 | }) {
12 | const initialState = {
13 | error: '',
14 | formId: '',
15 | isActive: false,
16 | limit,
17 | pagination: {
18 | numPages: 0,
19 | page: 1,
20 | total: 0,
21 | },
22 | query,
23 | select,
24 | sort,
25 | submissions: [],
26 | };
27 |
28 | return (state = initialState, action) => {
29 | // Only proceed for this submissions.
30 | if (action.name !== name) {
31 | return state;
32 | }
33 |
34 | switch (action.type) {
35 | case types.SUBMISSIONS_RESET:
36 | return initialState;
37 | case types.SUBMISSIONS_REQUEST:
38 | return {
39 | ...state,
40 | ..._pick(action.params, [
41 | 'limit',
42 | 'query',
43 | 'select',
44 | 'sort',
45 | ]),
46 | error: '',
47 | formId: action.formId,
48 | isActive: true,
49 | pagination: {
50 | ...state.pagination,
51 | page: action.page,
52 | },
53 | submissions: [],
54 | };
55 | case types.SUBMISSIONS_SUCCESS: {
56 | const total = action.submissions.serverCount;
57 |
58 | return {
59 | ...state,
60 | isActive: false,
61 | pagination: {
62 | ...state.pagination,
63 | numPages: Math.ceil(total / state.limit),
64 | total,
65 | },
66 | submissions: action.submissions,
67 | };
68 | }
69 | case types.SUBMISSIONS_FAILURE:
70 | return {
71 | ...state,
72 | error: action.error,
73 | isActive: false,
74 | };
75 | default:
76 | return state;
77 | }
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/src/modules/submissions/selectors.js:
--------------------------------------------------------------------------------
1 | import { selectRoot } from '../root';
2 |
3 | export const selectSubmissions = (name, state) =>
4 | selectRoot(name, state).submissions;
5 |
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | export const AllItemsPerPage = 'all';
4 |
5 | /**
6 | * @typedef Column
7 | * @type {object}
8 | * @property {string} key
9 | * @property {(boolean|string|Function)} sort
10 | * @property {string} title
11 | * @property {Function} value
12 | * @property {number} width
13 | */
14 |
15 | /**
16 | * @constant
17 | * @type {Column}
18 | */
19 | export const Column = PropTypes.shape({
20 | key: PropTypes.string.isRequired,
21 | sort: PropTypes.oneOfType([
22 | PropTypes.bool,
23 | PropTypes.string,
24 | PropTypes.func,
25 | ]),
26 | title: PropTypes.string,
27 | value: PropTypes.func,
28 | width: PropTypes.number,
29 | });
30 |
31 | /**
32 | * @constant
33 | * @type {Column[]}
34 | */
35 | export const Columns = PropTypes.arrayOf(Column);
36 |
37 | /**
38 | * @typedef Operation
39 | * @type {object}
40 | * @property {string} [action]
41 | * @property {string} [buttonType]
42 | * @property {string} [icon]
43 | * @property {Function} [permissionsResolver]
44 | * @property {string} [title]
45 | */
46 |
47 | /**
48 | * @constant
49 | * @type {Operation}
50 | */
51 | export const Operation = PropTypes.shape({
52 | action: PropTypes.string.isRequired,
53 | buttonType: PropTypes.string,
54 | icon: PropTypes.string,
55 | permissionsResolver: PropTypes.func,
56 | title: PropTypes.string,
57 | });
58 |
59 | /**
60 | * @constant
61 | * @type {Operation[]}
62 | */
63 | export const Operations = PropTypes.arrayOf(Operation);
64 |
65 | /**
66 | * @typedef LabelValue
67 | * @type {object}
68 | * @property {string} label
69 | * @property {number} value
70 | */
71 |
72 | /**
73 | * @constant
74 | * @type {(number|LabelValue)}
75 | */
76 | export const PageSize = PropTypes.oneOfType([
77 | PropTypes.number,
78 | PropTypes.shape({
79 | label: PropTypes.string,
80 | value: PropTypes.number,
81 | }),
82 | PropTypes.oneOf([AllItemsPerPage]),
83 | ]);
84 |
85 | /**
86 | * @constant
87 | * @type {PageSize[]}
88 | */
89 | export const PageSizes = PropTypes.arrayOf(PageSize);
90 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { Components } from '@formio/js';
2 | import _get from 'lodash/get';
3 |
4 | export const getComponentDefaultColumn = (component) => ({
5 | component: Components.create(component, null, null, true),
6 | key: `data.${component.key}`,
7 | sort: true,
8 | title: component.label || component.title || component.key,
9 | value(submission) {
10 | const cellValue = _get(submission, this.key, null);
11 |
12 | if (cellValue === null) {
13 | return '';
14 | }
15 |
16 | const rendered = this.component.asString(cellValue);
17 | if (cellValue !== rendered) {
18 | return {
19 | content: rendered,
20 | isHtml: true,
21 | };
22 | }
23 |
24 | return cellValue;
25 | },
26 | });
27 |
28 | /**
29 | * @param {import('./types').Column[]} columns
30 | */
31 | export function setColumnsWidth(columns) {
32 | if (columns.length > 6) {
33 | columns.forEach((column) => {
34 | column.width = 2;
35 | });
36 | } else {
37 | const columnsAmount = columns.length;
38 | const rowWidth = 12;
39 | const basewidth = Math.floor(rowWidth / columnsAmount);
40 | const remainingWidth = rowWidth - basewidth * columnsAmount;
41 |
42 | columns.forEach((column, index) => {
43 | column.width = index < remainingWidth ? basewidth + 1 : basewidth;
44 | });
45 | }
46 | }
47 |
48 | /**
49 | * @param {Function} fn
50 | * @returns {(function(*): void)|*}
51 | */
52 | export const stopPropagationWrapper = (fn) => (event) => {
53 | event.stopPropagation();
54 | fn();
55 | };
56 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "NodeNext",
4 | "outDir": "lib",
5 | "allowJs": true,
6 | "jsx": "react-jsx",
7 | "declaration": true,
8 | "declarationMap": true,
9 | "target": "es2015",
10 | "baseUrl": "src",
11 | "strict": true,
12 | "skipLibCheck": true,
13 | "esModuleInterop": true,
14 | },
15 | "include": ["src/**/*"],
16 | "types": [
17 | "node",
18 | "jest",
19 | "@testing-library/jest-dom",
20 | "react",
21 | "react-dom",
22 | ],
23 | "exclude": ["node_modules", "lib", "test"],
24 | }
25 |
--------------------------------------------------------------------------------
/webpack.test.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // mode: 'development',
3 | module: {
4 | rules: [
5 | {
6 | test: /\.jsx?$/,
7 | exclude: /node_modules/,
8 | loader: 'babel-loader',
9 | options: {
10 | presets: [
11 | ['es2015', { modules: false }],
12 | 'react',
13 | 'stage-2',
14 | ],
15 | },
16 | },
17 | ],
18 | },
19 | };
20 |
--------------------------------------------------------------------------------