@griddo/ax 11.1.15 → 11.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.1.15",
4
+ "version": "11.1.16",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -225,5 +225,5 @@
225
225
  "publishConfig": {
226
226
  "access": "public"
227
227
  },
228
- "gitHead": "0a37b975b12080895e7705a99f338d3543ba14a8"
228
+ "gitHead": "46a62886326c447820b5198ecc2da2c3d6246bb8"
229
229
  }
package/src/api/forms.tsx CHANGED
@@ -85,6 +85,16 @@ const SERVICES: { [key: string]: IServiceConfig } = {
85
85
  endpoint: "/translations/form/",
86
86
  method: "POST",
87
87
  },
88
+ DUPLICATE_FORM: {
89
+ ...template,
90
+ endpoint: ["/form/", "/duplicate"],
91
+ method: "POST",
92
+ },
93
+ COPY_FORM: {
94
+ ...template,
95
+ endpoint: ["/form/", "/duplicate/site/"],
96
+ method: "POST",
97
+ },
88
98
  };
89
99
 
90
100
  const getForms = async (siteID: number | "global", params: GetFormsParams) => {
@@ -188,6 +198,26 @@ const deleteFormCategoryBulk = async (categoryIds: number[]) => {
188
198
 
189
199
  const orderFormCategory = (data: FormCategoriesOrderParams) => sendRequest(SERVICES.ORDER_FORM_CATEGORY, { ...data });
190
200
 
201
+ const duplicateForm = async (formID: number, title: string) => {
202
+ const {
203
+ host,
204
+ endpoint: [prefix, suffix],
205
+ } = SERVICES.DUPLICATE_FORM;
206
+
207
+ SERVICES.DUPLICATE_FORM.dynamicUrl = `${host}${prefix}${formID}${suffix}`;
208
+ return sendRequest(SERVICES.DUPLICATE_FORM, { title });
209
+ };
210
+
211
+ const copyForm = async (formID: number, siteID: number) => {
212
+ const {
213
+ host,
214
+ endpoint: [prefix, suffix],
215
+ } = SERVICES.COPY_FORM;
216
+
217
+ SERVICES.COPY_FORM.dynamicUrl = `${host}${prefix}${formID}${suffix}${siteID}`;
218
+ return sendRequest(SERVICES.COPY_FORM);
219
+ };
220
+
191
221
  export default {
192
222
  getForms,
193
223
  getForm,
@@ -204,4 +234,6 @@ export default {
204
234
  orderFormCategory,
205
235
  updateFormState,
206
236
  getFormTranslation,
237
+ duplicateForm,
238
+ copyForm,
207
239
  };
@@ -17,12 +17,23 @@ const TableList = (props: ITableListProps): JSX.Element => {
17
17
  <S.TableHeader hasFixedHeader={hasFixedHeader} data-testid="table-list-header">
18
18
  {tableHeader}
19
19
  </S.TableHeader>
20
- {isLoading ? <Loading /> : <S.TableBody data-testid="table-body" className={className}>{children}</S.TableBody>}
20
+ {isLoading ? (
21
+ <Loading />
22
+ ) : (
23
+ <S.TableBody data-testid="table-body" className={className}>
24
+ {children}
25
+ </S.TableBody>
26
+ )}
21
27
  </>
22
28
  </S.Table>
23
29
  <S.PaginationWrapper>
24
- {showPagination && (
25
- <Pagination totalItems={pagination.totalItems} setPage={pagination.setPage} itemsPerPage={pagination.itemsPerPage} currPage={pagination.currPage} />
30
+ {showPagination && !isLoading && (
31
+ <Pagination
32
+ totalItems={pagination.totalItems}
33
+ setPage={pagination.setPage}
34
+ itemsPerPage={pagination.itemsPerPage}
35
+ currPage={pagination.currPage}
36
+ />
26
37
  )}
27
38
  </S.PaginationWrapper>
28
39
  </S.TableList>
@@ -2,15 +2,17 @@ import styled from "styled-components";
2
2
 
3
3
  const TableList = styled.div<{ overflow?: string }>`
4
4
  width: 100%;
5
- height: 100%;
6
5
  overflow: ${(p) => p.overflow};
6
+ display: flex;
7
+ flex-direction: column;
8
+ flex-grow: 1;
7
9
  `;
8
10
 
9
11
  const Table = styled.div`
10
12
  width: 100%;
11
13
  display: flex;
12
- flex-flow: column;
13
- height: 100%;
14
+ flex-direction: column;
15
+ flex-grow: 1;
14
16
  `;
15
17
 
16
18
  const TableHeader = styled.div<{ hasFixedHeader?: boolean }>`
@@ -84,6 +86,7 @@ const PaginationWrapper = styled.div`
84
86
  justify-content: flex-end;
85
87
  margin-right: ${(p) => p.theme.spacing.m};
86
88
  margin-bottom: ${(p) => p.theme.spacing.m};
89
+ flex-shrink: 0;
87
90
  `;
88
91
 
89
92
  export {
@@ -816,7 +816,7 @@ function getFormTranslation(langID: number): (dispatch: Dispatch, getState: any)
816
816
  generateFormContent(data)(dispatch, getState);
817
817
  dispatch(setIsIATranslated(true));
818
818
  },
819
- handleError: () => console.log("Error en GetFormTranslation"),
819
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
820
820
  };
821
821
 
822
822
  const callback = async () => formContent && forms.getFormTranslation(formContent, langID);
@@ -829,6 +829,44 @@ function getFormTranslation(langID: number): (dispatch: Dispatch, getState: any)
829
829
  };
830
830
  }
831
831
 
832
+ function duplicateForm(formID: number, title: string): (dispatch: Dispatch, getState: any) => Promise<boolean> {
833
+ return async (dispatch) => {
834
+ try {
835
+ const responseActions = {
836
+ handleSuccess: (data: FormContent) => {
837
+ dispatch(setCurrentFormID(data.id));
838
+ },
839
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
840
+ };
841
+
842
+ const callback = async () => forms.duplicateForm(formID, title);
843
+
844
+ return await handleRequest(callback, responseActions, [])(dispatch);
845
+ } catch (e) {
846
+ console.log(e);
847
+ return false;
848
+ }
849
+ };
850
+ }
851
+
852
+ function copyForm(formID: number, siteID: number): (dispatch: Dispatch, getState: any) => Promise<boolean> {
853
+ return async (dispatch) => {
854
+ try {
855
+ const responseActions = {
856
+ handleSuccess: () => {},
857
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
858
+ };
859
+
860
+ const callback = async () => forms.copyForm(formID, siteID);
861
+
862
+ return await handleRequest(callback, responseActions, [])(dispatch);
863
+ } catch (e) {
864
+ console.log(e);
865
+ return false;
866
+ }
867
+ };
868
+ }
869
+
832
870
  export {
833
871
  setSelectedTab,
834
872
  setFormContent,
@@ -856,4 +894,6 @@ export {
856
894
  createNewTranslation,
857
895
  getFormTranslation,
858
896
  setIsIATranslated,
897
+ duplicateForm,
898
+ copyForm,
859
899
  };
@@ -2,12 +2,12 @@ import React, { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { CategoryCell, CheckField, Flag, FloatingMenu, Icon, LanguageMenu, Tooltip } from "@ax/components";
5
- import { FormContent, FormLanguage, FormState, ICheck, ILanguage } from "@ax/types";
5
+ import { FormContent, FormLanguage, FormState, ICheck, ILanguage, IRootState, ISite } from "@ax/types";
6
6
  import { formsActions } from "@ax/containers/Forms";
7
7
  import { appActions } from "@ax/containers/App";
8
8
  import { useModal } from "@ax/hooks";
9
9
  import { findObjectValue, getHumanLastModifiedDate } from "@ax/helpers";
10
- import { DeleteModal, UnPublishModal } from "../../atoms";
10
+ import { CopyModal, DeleteModal, DuplicateModal, UnPublishModal } from "../../atoms";
11
11
 
12
12
  import * as S from "./style";
13
13
 
@@ -19,6 +19,7 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
19
19
  lang,
20
20
  featuredCategory,
21
21
  categoryColors,
22
+ currentSiteInfo,
22
23
  addCategoryColors,
23
24
  onChange,
24
25
  onClick,
@@ -27,6 +28,9 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
27
28
  updateFormState,
28
29
  createNewTranslation,
29
30
  setLanguage,
31
+ duplicateForm,
32
+ copyForm,
33
+ goToEditor,
30
34
  } = props;
31
35
 
32
36
  const { id, title, modified, state, dataLanguages, template } = form;
@@ -38,10 +42,15 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
38
42
  const categories = categoryObject?.map((cat) => cat.title) || [];
39
43
 
40
44
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
45
+ const [duplicateTitle, setDuplicateTitle] = useState("");
46
+ const [site, setSite] = useState<number | null>(null);
41
47
  const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
42
48
  const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
49
+ const { isOpen: isDuplicateOpen, toggleModal: toggleDuplicateModal } = useModal();
50
+ const { isOpen: isCopyOpen, toggleModal: toggleCopyModal } = useModal();
43
51
 
44
52
  const isTranslated = dataLanguages.length > 1;
53
+ const isSiteView = !!currentSiteInfo;
45
54
 
46
55
  const handleClick = () => onClick(form.id);
47
56
 
@@ -111,6 +120,27 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
111
120
  </S.FlagsWrapper>
112
121
  );
113
122
 
123
+ const handleDuplicateForm = async () => {
124
+ const duplicated = await duplicateForm(form.id, duplicateTitle);
125
+ if (duplicated) {
126
+ goToEditor();
127
+ }
128
+ isDuplicateOpen && toggleDuplicateModal();
129
+ };
130
+
131
+ const handleCopyForm = async () => {
132
+ const copied = site ? await copyForm(form.id, site) : false;
133
+ if (copied) {
134
+ toggleToast("1 form copied to another site");
135
+ handleCloseCopyModal();
136
+ }
137
+ };
138
+
139
+ const handleCloseCopyModal = () => {
140
+ setSite(null);
141
+ isCopyOpen && toggleCopyModal();
142
+ };
143
+
114
144
  const unpublishForm = async () => {
115
145
  const isUpdated = await updateFormState(id, "inactive");
116
146
  if (isUpdated) {
@@ -140,6 +170,18 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
140
170
  };
141
171
 
142
172
  const menuOptions = [
173
+ {
174
+ label: "duplicate",
175
+ icon: "duplicate",
176
+ action: () => toggleDuplicateModal(),
177
+ },
178
+ isSiteView
179
+ ? {
180
+ label: "Copy form in another site",
181
+ icon: "copy",
182
+ action: toggleCopyModal,
183
+ }
184
+ : undefined,
143
185
  publishButton,
144
186
  {
145
187
  label: "delete",
@@ -162,6 +204,21 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
162
204
 
163
205
  const secondaryUnpublishModalAction = { title: "Cancel", onClick: toggleUnpublishModal };
164
206
 
207
+ const mainDuplicateModalAction = {
208
+ title: "Duplicate form",
209
+ onClick: handleDuplicateForm,
210
+ };
211
+
212
+ const secondaryDuplicateModalAction = { title: "Cancel", onClick: toggleDuplicateModal };
213
+
214
+ const mainCopyModalAction = {
215
+ title: "Copy form",
216
+ onClick: handleCopyForm,
217
+ disabled: !site,
218
+ };
219
+
220
+ const secondaryCopyModalAction = { title: "Cancel", onClick: handleCloseCopyModal };
221
+
165
222
  const checkStatus = () => (state === "active" ? "active" : "offline");
166
223
  const getTooltip = () => (state === "active" ? "Live" : "Offline");
167
224
 
@@ -202,6 +259,22 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
202
259
  secondaryModalAction={secondaryUnpublishModalAction}
203
260
  title={title}
204
261
  />
262
+ <DuplicateModal
263
+ isOpen={isDuplicateOpen}
264
+ toggleModal={toggleDuplicateModal}
265
+ mainModalAction={mainDuplicateModalAction}
266
+ secondaryModalAction={secondaryDuplicateModalAction}
267
+ title={duplicateTitle}
268
+ handleChange={setDuplicateTitle}
269
+ />
270
+ <CopyModal
271
+ isOpen={isCopyOpen}
272
+ toggleModal={handleCloseCopyModal}
273
+ mainModalAction={mainCopyModalAction}
274
+ secondaryModalAction={secondaryCopyModalAction}
275
+ site={site}
276
+ setSite={setSite}
277
+ />
205
278
  </>
206
279
  );
207
280
  };
@@ -213,6 +286,7 @@ interface IFormItemProps {
213
286
  lang: { locale: string; id: number };
214
287
  featuredCategory?: { label: string; value: string };
215
288
  categoryColors: Record<string, string>;
289
+ currentSiteInfo: ISite | null;
216
290
  addCategoryColors(cats: string[]): void;
217
291
  onChange: (value: ICheck) => void;
218
292
  onClick: (id: number) => void;
@@ -221,13 +295,22 @@ interface IFormItemProps {
221
295
  updateFormState(formID: number | number[], state: FormState): Promise<boolean>;
222
296
  createNewTranslation(isNewTranslation: boolean): void;
223
297
  setLanguage(lang: { locale: string; id: number | null }): void;
298
+ duplicateForm(formID: number, title: string): Promise<boolean>;
299
+ copyForm(formID: number, siteID: number): Promise<boolean>;
300
+ goToEditor(): void;
224
301
  }
225
302
 
303
+ const mapStateToProps = (state: IRootState) => ({
304
+ currentSiteInfo: state.sites.currentSiteInfo,
305
+ });
306
+
226
307
  const mapDispatchToProps = {
227
308
  deleteForm: formsActions.deleteForm,
228
309
  updateFormState: formsActions.updateFormState,
229
310
  createNewTranslation: formsActions.createNewTranslation,
230
311
  setLanguage: appActions.setLanguage,
312
+ duplicateForm: formsActions.duplicateForm,
313
+ copyForm: formsActions.copyForm,
231
314
  };
232
315
 
233
- export default connect(null, mapDispatchToProps)(FormItem);
316
+ export default connect(mapStateToProps, mapDispatchToProps)(FormItem);
@@ -118,6 +118,8 @@ const FormList = (props: IUserListProps): JSX.Element => {
118
118
  setFiltersSelection(filterPointer, filtersSelected);
119
119
  };
120
120
 
121
+ const goToEditor = () => setHistoryPush(`${BASE_URL}/editor`, true);
122
+
121
123
  const createNewForm = () => {
122
124
  const templateSchema = selectedTemplate ? selectedTemplate : Object.values(formTemplates)[0];
123
125
  addTemplate(templateSchema.component);
@@ -127,7 +129,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
127
129
 
128
130
  const handleClick = (formID: number) => {
129
131
  setCurrentFormID(formID);
130
- setHistoryPush(`${BASE_URL}/editor`, true);
132
+ goToEditor();
131
133
  };
132
134
 
133
135
  const addNewAction = () => (Object.keys(formTemplates).length > 1 ? toggleNewModal() : createNewForm());
@@ -308,6 +310,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
308
310
  featuredCategory={featuredCategory}
309
311
  categoryColors={categoryColors}
310
312
  addCategoryColors={addCategoryColors}
313
+ goToEditor={goToEditor}
311
314
  />
312
315
  );
313
316
  })
@@ -1,7 +1,7 @@
1
- import React from "react";
1
+ import React, { Dispatch, SetStateAction } from "react";
2
2
 
3
3
  import { IModal } from "@ax/types";
4
- import { Modal, FieldsBehavior } from "@ax/components";
4
+ import { Modal, FieldsBehavior, AsyncSelect } from "@ax/components";
5
5
 
6
6
  import * as S from "./style";
7
7
 
@@ -97,10 +97,71 @@ const UnPublishModal = (props: IUnpublishModal): JSX.Element => {
97
97
  );
98
98
  };
99
99
 
100
+ const DuplicateModal = (props: IDuplicateModal): JSX.Element => {
101
+ const { isOpen, toggleModal, mainModalAction, secondaryModalAction, title, handleChange } = props;
102
+
103
+ return (
104
+ <Modal
105
+ isOpen={isOpen}
106
+ hide={toggleModal}
107
+ title="Duplicate form"
108
+ secondaryAction={secondaryModalAction}
109
+ mainAction={mainModalAction}
110
+ size="S"
111
+ height={284}
112
+ >
113
+ <S.ModalContent>
114
+ <p>Please enter a new name for the duplicated form.</p>
115
+ <FieldsBehavior
116
+ fieldType="TextField"
117
+ name="title"
118
+ title="Form name"
119
+ mandatory={true}
120
+ value={title}
121
+ onChange={handleChange}
122
+ placeholder="Type a name for this form"
123
+ />
124
+ </S.ModalContent>
125
+ </Modal>
126
+ );
127
+ };
128
+
129
+ const CopyModal = (props: ICopyModal): JSX.Element => {
130
+ const { isOpen, toggleModal, mainModalAction, secondaryModalAction, setSite, site } = props;
131
+
132
+ const handleSelectChange = (value: string | number | null) => {
133
+ if (typeof value === "number") setSite(value);
134
+ };
135
+
136
+ return (
137
+ <Modal
138
+ isOpen={isOpen}
139
+ hide={toggleModal}
140
+ size="S"
141
+ title="Copy form in another site"
142
+ mainAction={mainModalAction}
143
+ secondaryAction={secondaryModalAction}
144
+ overflow="visible"
145
+ height={278}
146
+ >
147
+ <S.ModalContent>
148
+ <p>
149
+ <strong>Select a site to copy this form. </strong>
150
+ <br />
151
+ You can only select sites with the same language as this form.
152
+ </p>
153
+ <S.SelectWrapper>
154
+ <AsyncSelect name="select" entity="sites" onChange={handleSelectChange} value={site} mandatory={true} />
155
+ </S.SelectWrapper>
156
+ </S.ModalContent>
157
+ </Modal>
158
+ );
159
+ };
160
+
100
161
  interface IDeleteModal extends IModal {
101
162
  isTranslated: boolean;
102
163
  deleteAllVersions: boolean;
103
- setDeleteAllVersions: React.Dispatch<React.SetStateAction<boolean>>;
164
+ setDeleteAllVersions: Dispatch<SetStateAction<boolean>>;
104
165
  title?: string;
105
166
  }
106
167
 
@@ -108,4 +169,14 @@ interface IUnpublishModal extends IModal {
108
169
  title?: string;
109
170
  }
110
171
 
111
- export { DeleteModal, UnPublishModal };
172
+ interface IDuplicateModal extends IModal {
173
+ title: string;
174
+ handleChange(title: string): void;
175
+ }
176
+
177
+ interface ICopyModal extends IModal {
178
+ setSite: Dispatch<SetStateAction<number | null>>;
179
+ site: number | null;
180
+ }
181
+
182
+ export { DeleteModal, UnPublishModal, DuplicateModal, CopyModal };
@@ -8,4 +8,11 @@ const ModalContent = styled.div`
8
8
  }
9
9
  `;
10
10
 
11
- export { ModalContent };
11
+ const SelectWrapper = styled.div`
12
+ & .react-select__control,
13
+ .react-select__menu {
14
+ min-width: 100%;
15
+ }
16
+ `;
17
+
18
+ export { ModalContent, SelectWrapper };
@@ -2,6 +2,7 @@ import styled from "styled-components";
2
2
 
3
3
  const StructuredDataWrapper = styled.div`
4
4
  display: flex;
5
+ flex-grow: 1;
5
6
  `;
6
7
 
7
8
  const TableListWrapper = styled.div`
@@ -9,6 +10,8 @@ const TableListWrapper = styled.div`
9
10
  width: 100%;
10
11
  height: calc(100vh - ${(p) => p.theme.spacing.xl});
11
12
  overflow: auto;
13
+ display: flex;
14
+ flex-direction: column;
12
15
  `;
13
16
 
14
17
  const NotificationWrapper = styled.div`