@griddo/ax 10.4.16 → 10.4.18

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": "10.4.16",
4
+ "version": "10.4.18",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -231,5 +231,5 @@
231
231
  "publishConfig": {
232
232
  "access": "public"
233
233
  },
234
- "gitHead": "5eeddf8b9b7b05ffc7a61bc2b9f0f4da572a35cf"
234
+ "gitHead": "d7f921348096eb2997376ff2a12ca16001fc9a49"
235
235
  }
@@ -58,6 +58,7 @@ const CheckMark = styled.span<{
58
58
  color: white;
59
59
  border-width: ${(p) => (p.indeterminate ? "0 2px 0 0" : "0 2px 2px 0")};
60
60
  transform: ${(p) => (p.indeterminate ? "rotate(90deg)" : "rotate(45deg)")};
61
+ box-sizing: border-box;
61
62
  }
62
63
 
63
64
  &.light {
@@ -8,7 +8,7 @@ import { useBulkSelection, useModal, useToast } from "@ax/hooks";
8
8
  import { getComponentProps, containerToComponentArray, getTypefromKey, getModulesToPaste } from "../helpers";
9
9
  import AddItemButton from "./AddItemButton";
10
10
  import PasteModuleButton from "./PasteModuleButton";
11
- import BulkHeader from "./BulkHeader";
11
+ import BulkHeader from "../BulkHeader";
12
12
 
13
13
  import * as S from "./style";
14
14
 
@@ -1,11 +1,13 @@
1
- import React from "react";
2
- import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
1
+ import React, { useEffect, useState } from "react";
2
+ import { DragDropContext, Droppable, Draggable, DropResult, BeforeCapture } from "react-beautiful-dnd";
3
3
 
4
- import { IModule } from "@ax/types";
4
+ import { IModule, INotification, ISchemaField } from "@ax/types";
5
5
  import { ComponentContainer } from "@ax/components";
6
+ import { useBulkSelection } from "@ax/hooks";
6
7
 
7
8
  import AddItemButton from "./AddItemButton";
8
9
  import { getComponentProps, getTypefromKey } from "../helpers";
10
+ import BulkHeader from "../BulkHeader";
9
11
 
10
12
  import * as S from "./style";
11
13
 
@@ -13,7 +15,7 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
13
15
  const {
14
16
  whiteList,
15
17
  title,
16
- value,
18
+ value = [],
17
19
  goTo,
18
20
  selectedContent,
19
21
  editorID,
@@ -30,27 +32,35 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
30
32
 
31
33
  const type = getTypefromKey(objKey);
32
34
  const { contentType = type } = field;
35
+ const componentIDs: number[] = value && value.length ? value.map((element) => element.editorID) : [];
33
36
 
34
- let addModuleAction: any;
35
- let addComponentAction: any;
37
+ const { addModuleAction, addComponentAction, setNotificationAction, duplicateModuleAction, deleteModuleAction } =
38
+ actions || {};
36
39
 
37
- if (actions) {
38
- addModuleAction = actions.addModuleAction;
39
- addComponentAction = actions.addComponentAction;
40
- }
40
+ const [isBulkOpen, setIsBulkOpen] = useState(false);
41
+ const [draggingId, setDraggingId] = useState<number | null>(null);
42
+ const { resetBulkSelection, selectedItems, isSelected, checkState, addToBulkSelection, selectAllItems } =
43
+ useBulkSelection(componentIDs);
44
+
45
+ useEffect(() => {
46
+ if (selectedItems.all.length) {
47
+ setIsBulkOpen(true);
48
+ }
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ }, [selectedItems.all]);
41
51
 
42
52
  const getText = (name: string, index: number) => {
43
53
  return value && value.length > 1 ? `#${index + 1} ${name}` : name;
44
54
  };
45
55
 
46
- const { kind } = selectedContent;
56
+ const componentType = field.reference ? selectedContent[field.reference] : selectedContent["kind"];
47
57
 
48
58
  const isModuleArr = contentType === "modules";
49
59
  const isComponentModule = contentType === "components";
50
60
 
51
61
  const handleAddModule = (moduleType: string) => addModuleAction(moduleType, objKey, editorID, isComponentModule);
52
62
 
53
- const handleAddComponent = () => addComponentAction && addComponentAction(kind, objKey);
63
+ const handleAddComponent = () => addComponentAction && addComponentAction(componentType, objKey);
54
64
 
55
65
  const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;
56
66
 
@@ -59,19 +69,24 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
59
69
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
60
70
 
61
71
  const onDragEnd = (result: DropResult) => {
62
- const { moveModuleAction } = actions;
72
+ const { moveModuleAction } = actions || {};
63
73
 
64
- if (!result.destination) {
74
+ if (!moveModuleAction || !result.destination || result.destination.index === result.source.index) {
75
+ setDraggingId(null);
65
76
  return;
66
77
  }
67
78
 
68
- if (result.destination.index === result.source.index) {
69
- return;
79
+ if (selectedItems.all.length > 0 && selectedItems.all.includes(parseInt(result.draggableId))) {
80
+ moveModuleAction(selectedItems.all, selectedContent, result.destination.index, objKey);
81
+ } else {
82
+ moveModuleAction([parseInt(result.draggableId)], selectedContent, result.destination.index, objKey);
70
83
  }
71
84
 
72
- moveModuleAction([parseInt(result.draggableId)], selectedContent, result.destination.index, objKey);
85
+ setDraggingId(null);
73
86
  };
74
87
 
88
+ const onBeforeCapture = (start: BeforeCapture) => setDraggingId(parseInt(start.draggableId));
89
+
75
90
  const ComponentList = React.memo(function ComponentList({ components }: any) {
76
91
  return components.map((element: any, i: number) => {
77
92
  const { editorID } = element;
@@ -81,9 +96,14 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
81
96
  isModuleArr
82
97
  );
83
98
  const text = getText(componentTitle || displayName, i);
99
+ const isItemSelected = isSelected(editorID);
100
+ const isDraggingSelected = selectedItems.all.includes(draggingId);
101
+ const isGhosting = isItemSelected && !!draggingId && draggingId !== editorID && isDraggingSelected;
102
+ const isMultiDragging = selectedItems.all.length > 1 && draggingId === editorID;
103
+
84
104
  return (
85
105
  <Draggable draggableId={`${editorID}`} index={i} key={editorID}>
86
- {(provided) => (
106
+ {(provided, snapshot) => (
87
107
  <ComponentContainer
88
108
  isArray={true}
89
109
  key={editorID}
@@ -103,6 +123,12 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
103
123
  innerRef={provided.innerRef}
104
124
  provided={provided}
105
125
  isModule={isModule}
126
+ isSelected={isItemSelected}
127
+ onChange={addToBulkSelection}
128
+ hasMenu={selectedItems.all.length > 0}
129
+ isMultiDragging={snapshot.isDragging && isMultiDragging}
130
+ draggingCount={selectedItems.all.length}
131
+ className={`${isGhosting ? "ghosting" : ""} ${snapshot.isDragging && isMultiDragging ? "dragging" : ""}`}
106
132
  />
107
133
  )}
108
134
  </Draggable>
@@ -110,19 +136,67 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
110
136
  });
111
137
  });
112
138
 
139
+ const handleDuplicate = () => {
140
+ if (maxItems && maxItems - value.length < selectedItems.all.length) {
141
+ const notification: INotification = {
142
+ type: "error",
143
+ text: "Unable to duplicate modules: The destination area has a limit on the number of modules allowed. Please adjust the selection accordingly.",
144
+ };
145
+ setNotificationAction && setNotificationAction(notification);
146
+ } else {
147
+ duplicateModuleAction && duplicateModuleAction(selectedItems.all, objKey);
148
+ resetBulkSelection();
149
+ }
150
+ };
151
+
152
+ const handleDelete = () => {
153
+ deleteModuleAction && deleteModuleAction(selectedItems.all, objKey);
154
+ resetBulkSelection();
155
+ };
156
+
157
+ const bulkActions = [
158
+ {
159
+ icon: "duplicate",
160
+ text: "duplicate",
161
+ action: handleDuplicate,
162
+ },
163
+ {
164
+ icon: "delete",
165
+ text: "delete",
166
+ action: handleDelete,
167
+ },
168
+ ];
169
+
170
+ const selectItems = () => (checkState.isAllSelected ? resetBulkSelection() : selectAllItems());
171
+
172
+ const toggleBulk = () => {
173
+ setIsBulkOpen(false);
174
+ resetBulkSelection();
175
+ };
176
+
113
177
  return (
114
178
  <S.Wrapper data-testid="sameComponentWrapper">
115
179
  <S.Title>
116
180
  {title} <Asterisk />
117
181
  </S.Title>
118
182
  <S.ItemRow>
119
- <S.Subtitle>{value && value.length} items</S.Subtitle>
183
+ {isBulkOpen ? (
184
+ <BulkHeader
185
+ selectItems={selectItems}
186
+ checkState={checkState}
187
+ totalItems={selectedItems.all.length}
188
+ toggleBulk={toggleBulk}
189
+ actions={bulkActions}
190
+ />
191
+ ) : (
192
+ <S.Subtitle>{value?.length || 0} items</S.Subtitle>
193
+ )}
120
194
  {showAddItemButton && !disabled && (
121
195
  <AddItemButton handleClick={handleAdd} tooltipText={isModuleArr ? "Add module" : "Add component"} />
122
196
  )}
123
197
  </S.ItemRow>
124
198
  {value && Array.isArray(value) && (
125
- <DragDropContext onDragEnd={onDragEnd}>
199
+ <DragDropContext onDragEnd={onDragEnd} onBeforeCapture={onBeforeCapture}>
126
200
  <Droppable droppableId="list">
127
201
  {(provided) => (
128
202
  <div ref={provided.innerRef} {...provided.droppableProps}>
@@ -146,12 +220,23 @@ export interface ISameComponentArrayProps {
146
220
  selectedContent: any;
147
221
  editorID: number;
148
222
  goTo: (editorID: string) => void;
149
- actions: any;
223
+ actions: {
224
+ addComponentAction: (componentType: any, key?: string) => void;
225
+ addModuleAction: (moduleType: string, key: string, selectedID: number, isComponentModule?: boolean) => void;
226
+ deleteModuleAction: (editorID: number[], key?: string) => void;
227
+ duplicateModuleAction: (editorID: number[], key?: string) => number;
228
+ copyModuleAction: (editorID: number[]) => boolean | number;
229
+ replaceElementsInCollectionAction: (newValue: string, reference?: string) => void;
230
+ moveModuleAction: (moduleID: number[], selectedContent: any, newIndex: number, key: string) => void;
231
+ pasteModuleAction: (editorID: number, key: string, modulesToPaste: IModule[]) => Promise<{ error?: INotification }>;
232
+ setNotificationAction: (notification: INotification) => void;
233
+ replaceModuleAction: (module: any, parent: any, objKey: string) => void;
234
+ };
150
235
  categories?: any;
151
236
  disabled?: boolean;
152
237
  activatedModules: string[];
153
238
  objKey: string;
154
- field: any;
239
+ field: ISchemaField;
155
240
  mandatory?: boolean;
156
241
  theme: string;
157
242
  }
@@ -19,7 +19,7 @@ import Item from "./Item";
19
19
  import * as S from "./style";
20
20
 
21
21
  const ManualPanel = (props: IProps) => {
22
- const { onChange, currentSite, hasMaxItems, handleValidation, validators, lang, globalLangs } = props;
22
+ const { onChange, currentSite, hasMaxItems, handleValidation, validators, lang, globalLangs, maxItems } = props;
23
23
 
24
24
  const { state, setState } = useReference();
25
25
 
@@ -83,7 +83,7 @@ const ManualPanel = (props: IProps) => {
83
83
  if (itemSelectedID) {
84
84
  newSelIds = state.fixed.filter((a: number) => a !== itemSelectedID);
85
85
  selItems = state.selectedItems.filter((b: IStructuredDataContent) => b.id !== itemSelectedID);
86
- if(selItems.length === 0) {
86
+ if (selItems.length === 0) {
87
87
  setItemSite(form.site);
88
88
  }
89
89
  } else {
@@ -213,7 +213,11 @@ const ManualPanel = (props: IProps) => {
213
213
  (dataLang: IDataLanguage) => state.fixed.includes(dataLang.id) && dataLang.language !== form.lang
214
214
  );
215
215
  const source = state.sourceTitles.find((el: IDataSource) => el.id === item.structuredData);
216
- const disabled = (hasMaxItems && !isChecked) || !!hasVersionInPageLanguage || hasContentFromOtherSite;
216
+ const disabled =
217
+ (hasMaxItems && !isChecked) ||
218
+ !!hasVersionInPageLanguage ||
219
+ (maxItems && state.fixed.length >= maxItems && !isChecked) ||
220
+ hasContentFromOtherSite;
217
221
  return (
218
222
  <React.Fragment key={`${item.content.title}-${item.id}`}>
219
223
  {source && (
@@ -251,6 +255,7 @@ interface IProps {
251
255
  validators?: Record<string, unknown>;
252
256
  lang: { locale: string; id: number };
253
257
  globalLangs: ILanguage[];
258
+ maxItems?: number;
254
259
  }
255
260
 
256
261
  interface IFormState {
@@ -203,6 +203,7 @@ const ReferenceField = (props: IReferenceFieldProps) => {
203
203
  hasMaxItems={hasMaxItems}
204
204
  handleValidation={handleValidation}
205
205
  validators={validators}
206
+ maxItems={maxItems}
206
207
  />
207
208
  );
208
209
 
@@ -1,5 +1,5 @@
1
1
  import { Icon } from "@ax/components";
2
- import React, { useEffect, useRef, useState } from "react";
2
+ import React, { useEffect, useState } from "react";
3
3
  import { Select } from "../Fields";
4
4
 
5
5
  import * as S from "./style";
@@ -19,7 +19,7 @@ const SearchField = (props: ISearchFieldProps): JSX.Element => {
19
19
  value,
20
20
  } = props;
21
21
 
22
- const [isOpen, setIsOpen] = useState(false);
22
+ const [isOpen, setIsOpen] = useState(value && value.trim() !== "" ? true : false);
23
23
  const [inputValue, setInputValue] = useState(value || "");
24
24
  const [selectValue, setSelectValue] = useState<string>("");
25
25
 
@@ -14,6 +14,7 @@ import {
14
14
  SET_CURRENT_SITE_ERROR_PAGES,
15
15
  SET_CONTENT_FILTERS,
16
16
  SET_CONFIG,
17
+ SET_CURRENT_SEARCH,
17
18
  } from "./constants";
18
19
 
19
20
  import {
@@ -30,6 +31,7 @@ import {
30
31
  ISetContentFilters,
31
32
  ISetSitesTotalItems,
32
33
  ISetConfig,
34
+ ISetCurrentSearch,
33
35
  } from "./interfaces";
34
36
 
35
37
  import {
@@ -113,6 +115,10 @@ function setConfig(config: ISiteListConfig): ISetConfig {
113
115
  return { type: SET_CONFIG, payload: { config } };
114
116
  }
115
117
 
118
+ function setCurrentSearch(currentSearch: string): ISetCurrentSearch {
119
+ return { type: SET_CURRENT_SEARCH, payload: { currentSearch } };
120
+ }
121
+
116
122
  // TODO: hay que controlar que cuando da error la API borrar los sites ya guardados y sacar el error (ver los siguientes FIXME)
117
123
  function getSites(params: IGetSitesParams = { recentSitesNumber: 7 }): (dispatch: Dispatch) => Promise<void> {
118
124
  return async (dispatch) => {
@@ -479,6 +485,7 @@ function resetSiteValues(siteID: number): (dispatch: Dispatch) => void {
479
485
  dispatch(setCurrentSitePages([]));
480
486
  dispatch(setTotalItems(0));
481
487
  getAnalytics(siteID)(dispatch);
488
+ dispatch(setCurrentSearch(""));
482
489
  };
483
490
  }
484
491
 
@@ -695,6 +702,16 @@ function setListConfig(config: ISiteListConfig): (dispatch: Dispatch) => Promise
695
702
  };
696
703
  }
697
704
 
705
+ function updateCurrentSearch(query: string): (dispatch: Dispatch) => Promise<void> {
706
+ return async (dispatch) => {
707
+ try {
708
+ dispatch(setCurrentSearch(query));
709
+ } catch (e) {
710
+ console.log(e);
711
+ }
712
+ };
713
+ }
714
+
698
715
  export {
699
716
  setCurrentSiteInfo,
700
717
  setCurrentSitePages,
@@ -725,4 +742,5 @@ export {
725
742
  deleteAndRemoveFromSiteBulk,
726
743
  setListConfig,
727
744
  getSite,
745
+ updateCurrentSearch,
728
746
  };
@@ -14,6 +14,7 @@ export const SET_SAVED_SITE_INFO = `${NAME}/SET_SAVED_SITE_INFO`;
14
14
  export const SET_CURRENT_SITE_ERROR_PAGES = `${NAME}/SET_CURRENT_SITE_ERROR_PAGES`;
15
15
  export const SET_CONTENT_FILTERS = `${NAME}/SET_CONTENT_FILTERS`;
16
16
  export const SET_CONFIG = `${NAME}/SET_CONFIG`;
17
+ export const SET_CURRENT_SEARCH = `${NAME}/SET_CURRENT_SEARCH`;
17
18
 
18
19
  export const ITEMS_PER_PAGE = 50;
19
20
 
@@ -13,6 +13,7 @@ import {
13
13
  SET_CONTENT_FILTERS,
14
14
  SET_SITES_TOTAL_ITEMS,
15
15
  SET_CONFIG,
16
+ SET_CURRENT_SEARCH,
16
17
  } from "./constants";
17
18
  import { IQueryValue, ISite, ISiteListConfig } from "@ax/types";
18
19
 
@@ -86,4 +87,9 @@ export interface ISetConfig {
86
87
  payload: { config: ISiteListConfig };
87
88
  }
88
89
 
90
+ export interface ISetCurrentSearch {
91
+ type: typeof SET_CURRENT_SEARCH;
92
+ payload: { currentSearch: string };
93
+ }
94
+
89
95
  export type SitesActionsCreators = ISetSitesAction & ISetCurrentSiteInfoAction;
@@ -13,6 +13,7 @@ import {
13
13
  SET_CURRENT_SITE_ERROR_PAGES,
14
14
  SET_CONTENT_FILTERS,
15
15
  SET_CONFIG,
16
+ SET_CURRENT_SEARCH,
16
17
  } from "./constants";
17
18
 
18
19
  import { ISite, IPage, ILanguage, ISiteListConfig, IQueryValue } from "@ax/types";
@@ -34,6 +35,7 @@ export interface ISitesState {
34
35
  currentSiteErrorPages: number[];
35
36
  contentFilters: Record<string, IQueryValue[]> | null;
36
37
  config: ISiteListConfig;
38
+ currentSearch: string;
37
39
  }
38
40
 
39
41
  const config = {
@@ -64,6 +66,7 @@ export const initialState = {
64
66
  currentSiteErrorPages: [],
65
67
  contentFilters: null,
66
68
  config,
69
+ currentSearch: "",
67
70
  };
68
71
 
69
72
  export function reducer(state = initialState, action: any): ISitesState {
@@ -82,6 +85,7 @@ export function reducer(state = initialState, action: any): ISitesState {
82
85
  case SET_CURRENT_SITE_ERROR_PAGES:
83
86
  case SET_CONTENT_FILTERS:
84
87
  case SET_CONFIG:
88
+ case SET_CURRENT_SEARCH:
85
89
  return { ...state, ...action.payload };
86
90
  default:
87
91
  return state;
@@ -18,6 +18,7 @@ import {
18
18
  SET_VALIDATED,
19
19
  SET_CONTENT_FILTERS,
20
20
  SET_IS_IA_TRANSLATED,
21
+ SET_CURRENT_SEARCH,
21
22
  } from "./constants";
22
23
 
23
24
  import {
@@ -34,6 +35,7 @@ import {
34
35
  ISetValidated,
35
36
  ISetContentFilters,
36
37
  ISetIsIATranslated,
38
+ ISetCurrentSearch,
37
39
  } from "./interfaces";
38
40
  import {
39
41
  prepareStructuredDataContent,
@@ -127,6 +129,10 @@ function setIsIATranslated(isIATranslated: boolean): ISetIsIATranslated {
127
129
  return { type: SET_IS_IA_TRANSLATED, payload: { isIATranslated } };
128
130
  }
129
131
 
132
+ function setCurrentSearch(currentSearch: string): ISetCurrentSearch {
133
+ return { type: SET_CURRENT_SEARCH, payload: { currentSearch } };
134
+ }
135
+
130
136
  function updateFormValue(valueObj: any): (dispatch: Dispatch, getState: any) => void {
131
137
  return (dispatch, getState) => {
132
138
  const {
@@ -651,12 +657,23 @@ function resetCurrentData(): (dispatch: Dispatch) => Promise<void> {
651
657
  dispatch(setCurrentData(null));
652
658
  dispatch(setCurrentDataID(null));
653
659
  dispatch(setCurrentDataContent([]));
660
+ //dispatch(setCurrentSearch(""));
654
661
  } catch (e) {
655
662
  console.log("Error", e);
656
663
  }
657
664
  };
658
665
  }
659
666
 
667
+ function updateCurrentSearch(query: string): (dispatch: Dispatch) => Promise<void> {
668
+ return async (dispatch) => {
669
+ try {
670
+ dispatch(setCurrentSearch(query));
671
+ } catch (e) {
672
+ console.log(e);
673
+ }
674
+ };
675
+ }
676
+
660
677
  export {
661
678
  setIsActive,
662
679
  setCategories,
@@ -694,4 +711,5 @@ export {
694
711
  setFormValues,
695
712
  setIsTranslated,
696
713
  resetCurrentData,
714
+ updateCurrentSearch,
697
715
  };
@@ -17,6 +17,7 @@ const SET_ERRORS = `${NAME}/SET_ERRORS`;
17
17
  const SET_VALIDATED = `${NAME}/SET_VALIDATED`;
18
18
  const SET_CONTENT_FILTERS = `${NAME}/SET_CONTENT_FILTERS`;
19
19
  const SET_IS_IA_TRANSLATED = `${NAME}/SET_IS_IA_TRANSLATED`;
20
+ const SET_CURRENT_SEARCH = `${NAME}/SET_CURRENT_SEARCH`;
20
21
 
21
22
  const ITEMS_PER_PAGE = 50;
22
23
 
@@ -49,4 +50,5 @@ export {
49
50
  SET_VALIDATED,
50
51
  SET_CONTENT_FILTERS,
51
52
  SET_IS_IA_TRANSLATED,
53
+ SET_CURRENT_SEARCH,
52
54
  };
@@ -14,6 +14,7 @@ import {
14
14
  SET_VALIDATED,
15
15
  SET_CONTENT_FILTERS,
16
16
  SET_IS_IA_TRANSLATED,
17
+ SET_CURRENT_SEARCH,
17
18
  } from "./constants";
18
19
 
19
20
  import { IStructuredData, IStructuredDataContent, ICategory, IErrorItem, IStructuredDataQueryValues } from "@ax/types";
@@ -92,6 +93,11 @@ export interface ISetIsIATranslated {
92
93
  payload: { isIATranslated: boolean };
93
94
  }
94
95
 
96
+ export interface ISetCurrentSearch {
97
+ type: typeof SET_CURRENT_SEARCH;
98
+ payload: { currentSearch: string };
99
+ }
100
+
95
101
  export type CategoryActionsCreators = ISetCategories & ISetCurrentData;
96
102
 
97
103
  export type StructuredDataActionsCreators = CategoryActionsCreators &
@@ -17,6 +17,7 @@ import {
17
17
  SET_VALIDATED,
18
18
  SET_CONTENT_FILTERS,
19
19
  SET_IS_IA_TRANSLATED,
20
+ SET_CURRENT_SEARCH,
20
21
  } from "./constants";
21
22
 
22
23
  import { StructuredDataActionsCreators } from "./interfaces";
@@ -38,6 +39,7 @@ export interface IStructuredDataState {
38
39
  validated: boolean;
39
40
  contentFilters: Record<string, IStructuredDataQueryValues> | null;
40
41
  isIATranslated: boolean;
42
+ currentSearch: string;
41
43
  }
42
44
 
43
45
  export const initialState = {
@@ -57,6 +59,7 @@ export const initialState = {
57
59
  validated: false,
58
60
  contentFilters: null,
59
61
  isIATranslated: false,
62
+ currentSearch: "",
60
63
  };
61
64
 
62
65
  export function reducer(state = initialState, action: any): IStructuredDataState {
@@ -78,6 +81,7 @@ export function reducer(state = initialState, action: any): IStructuredDataState
78
81
  case SET_VALIDATED:
79
82
  case SET_CONTENT_FILTERS:
80
83
  case SET_IS_IA_TRANSLATED:
84
+ case SET_CURRENT_SEARCH:
81
85
  return { ...state, ...action.payload };
82
86
  default:
83
87
  return state;
@@ -74,7 +74,7 @@ const getTemplateDisplayName = (template: string) => {
74
74
  return schema ? schema.displayName : undefined;
75
75
  };
76
76
 
77
- const getMenuItems = (type: string): ({ fields: any[] } | undefined) => schemas.menuItems[type];
77
+ const getMenuItems = (type: string): { fields: any[] } | undefined => schemas.menuItems[type];
78
78
 
79
79
  const filterByCategory = (options: any, category: string) =>
80
80
  options.filter((option: any) => allSchemas[option].category === category);
@@ -10,6 +10,7 @@ const Column = styled.div`
10
10
  width: 100%;
11
11
  padding: 0 ${(p) => p.theme.spacing.m};
12
12
  height: calc(100% - ${(p) => p.theme.spacing.xs} * 4);
13
+ padding-bottom: ${(p) => p.theme.spacing.l};
13
14
 
14
15
  &:first-child {
15
16
  width: calc(${(p) => p.theme.spacing.m} * 18);
@@ -38,15 +39,18 @@ const Header = styled.div`
38
39
  `;
39
40
 
40
41
  const Check = styled.div`
41
- padding-right: 0;
42
+ padding-right: ${(p) => p.theme.spacing.xs};
42
43
  padding-left: ${(p) => p.theme.spacing.xs};
44
+ padding-top: ${(p) => p.theme.spacing.xs};
43
45
  `;
44
46
 
45
47
  const CheckAll = styled.div`
46
- margin: ${(p) => p.theme.spacing.xxs} calc(${(p) => p.theme.spacing.xxs} * 3.5);
48
+ display: flex;
49
+ align-items: center;
50
+ margin: ${(p) => p.theme.spacing.xs} calc(${(p) => p.theme.spacing.xxs} * 3.5);
47
51
 
48
52
  & > span {
49
- margin-left: ${(p) => p.theme.spacing.m};
53
+ margin-left: ${(p) => p.theme.spacing.xs};
50
54
  ${(p) => p.theme.textStyle.fieldContent};
51
55
  color: ${(p) => p.theme.color.textHighEmphasis};
52
56
  }
@@ -49,6 +49,7 @@ import {
49
49
  pageStatus,
50
50
  } from "@ax/containers/PageEditor/interfaces";
51
51
  import { integrationsActions } from "@ax/containers/Integrations";
52
+ import { IError } from "@ax/containers/App/reducer";
52
53
 
53
54
  import { getOptionValues, getOptionFilters, getCurrentFilter, filterByStatus, getSortedListStatus } from "./utils";
54
55
  import { useSortedListStatus, useFilterQuery } from "./hooks";
@@ -61,7 +62,6 @@ import PageImporter from "./PageImporter";
61
62
  import { DeleteModal } from "./atoms";
62
63
 
63
64
  import * as S from "./style";
64
- import { IError } from "@ax/containers/App/reducer";
65
65
 
66
66
  // TODO: Make this monster manageable
67
67
  const Content = (props: IProps): JSX.Element => {
@@ -122,6 +122,8 @@ const Content = (props: IProps): JSX.Element => {
122
122
  getDefaults,
123
123
  getAvailableSiteDataPacks,
124
124
  error,
125
+ currentSearch,
126
+ updateCurrentSearch,
125
127
  } = props;
126
128
 
127
129
  if (!currentSiteInfo) {
@@ -190,7 +192,6 @@ const Content = (props: IProps): JSX.Element => {
190
192
 
191
193
  const [isScrolling, setIsScrolling] = useState(false);
192
194
  const [deletedItem, setDeletedItem] = useState<number | number[] | null>(null);
193
- const [searchQuery, setSearchQuery] = useState<string>("");
194
195
  const [pagesToImport, setPagesToImport] = useState([]);
195
196
  const [isEmpty, setIsEmpty] = useState(false);
196
197
  const [emptyStateProps, setEmptyStateProps] = useState<IEmptyStateProps>({});
@@ -245,7 +246,7 @@ const Content = (props: IProps): JSX.Element => {
245
246
  pagination: true,
246
247
  deleted: false,
247
248
  include_draft: true,
248
- query: searchQuery,
249
+ query: currentSearch,
249
250
  format: "list",
250
251
  }
251
252
  : {
@@ -253,12 +254,12 @@ const Content = (props: IProps): JSX.Element => {
253
254
  deleted: false,
254
255
  page,
255
256
  itemsPerPage,
256
- query: searchQuery,
257
+ query: currentSearch,
257
258
  format: "list",
258
259
  };
259
260
 
260
261
  return params;
261
- }, [filter, currentSiteInfo, isStructuredData, page, searchQuery, lang]);
262
+ }, [filter, currentSiteInfo, isStructuredData, page, currentSearch, lang]);
262
263
 
263
264
  const getPages = async (params: any, filterQuery?: any) => {
264
265
  const isStructuredDataPage = filter !== "unique-pages";
@@ -284,7 +285,7 @@ const Content = (props: IProps): JSX.Element => {
284
285
  tableRef.current.scrollTo(0, 0);
285
286
  }
286
287
  // eslint-disable-next-line
287
- }, [page, filter, currentFilterQuery, searchQuery]);
288
+ }, [page, filter, currentFilterQuery, currentSearch]);
288
289
 
289
290
  useLayoutEffect(() => {
290
291
  setPage(firstPage);
@@ -317,7 +318,7 @@ const Content = (props: IProps): JSX.Element => {
317
318
  const emptyState: IEmptyStateProps = {};
318
319
  const { liveStatus, translated, type } = filterValues;
319
320
  const isSearching =
320
- searchQuery.length > 0 ||
321
+ currentSearch.length > 0 ||
321
322
  (liveStatus.length && liveStatus[0].value !== "all") ||
322
323
  (translated.length && translated[0].value !== "all") ||
323
324
  (type.length && type[0].value !== "all");
@@ -831,9 +832,9 @@ const Content = (props: IProps): JSX.Element => {
831
832
  languageActions={languageActions}
832
833
  availableLanguages={siteLanguages}
833
834
  rightButton={rightButtonProps}
834
- searchAction={setSearchQuery}
835
+ searchAction={updateCurrentSearch}
835
836
  errors={errors}
836
- searchValue={searchQuery}
837
+ searchValue={currentSearch}
837
838
  >
838
839
  <S.ContentListWrapper>
839
840
  <ContentFilters current={filter} dynamicValues={structuredData} resetFilter={resetFilter} />
@@ -867,7 +868,7 @@ const Content = (props: IProps): JSX.Element => {
867
868
  tableRef={tableRef}
868
869
  >
869
870
  <S.SearchTags>
870
- <SearchTagsBar query={searchQuery} setQuery={setSearchQuery} />
871
+ <SearchTagsBar query={currentSearch} setQuery={updateCurrentSearch} />
871
872
  <FilterTagsBar
872
873
  filters={filterValues}
873
874
  setFilters={setFiltersSelection}
@@ -946,6 +947,7 @@ const mapStateToProps = (state: IRootState) => ({
946
947
  skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
947
948
  contentFilters: state.sites.contentFilters,
948
949
  error: state.app.error,
950
+ currentSearch: state.sites.currentSearch,
949
951
  });
950
952
 
951
953
  interface IDispatchProps {
@@ -985,6 +987,7 @@ interface IDispatchProps {
985
987
  checkUserSession(): Promise<void>;
986
988
  getDefaults(): Promise<void>;
987
989
  getAvailableSiteDataPacks(queryParams: string | null, loading?: boolean): Promise<void>;
990
+ updateCurrentSearch(query: string): Promise<void>;
988
991
  }
989
992
 
990
993
  const mapDispatchToProps = {
@@ -1023,6 +1026,7 @@ const mapDispatchToProps = {
1023
1026
  checkUserSession: appActions.checkUserSession,
1024
1027
  getDefaults: navigationActions.getDefaults,
1025
1028
  getAvailableSiteDataPacks: dataPacksActions.getAvailableSiteDataPacks,
1029
+ updateCurrentSearch: sitesActions.updateCurrentSearch,
1026
1030
  };
1027
1031
 
1028
1032
  interface IPagesProps {
@@ -1052,6 +1056,7 @@ interface IPagesProps {
1052
1056
  skipReviewOnPublish?: boolean;
1053
1057
  contentFilters: Record<string, IQueryValue[]> | null;
1054
1058
  error: IError;
1059
+ currentSearch: string;
1055
1060
  }
1056
1061
 
1057
1062
  type IProps = IPagesProps & IDispatchProps;
@@ -59,7 +59,6 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
59
59
 
60
60
  const itemsPerPage = 30;
61
61
  const firstPage = 1;
62
- const [sitesIds, setsitesIds] = useState<number[]>([]);
63
62
 
64
63
  const [page, setPage] = useState(firstPage);
65
64
  const [form, setForm] = useState(initialState);
@@ -74,6 +73,8 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
74
73
  const tableRef = useRef<HTMLDivElement>(null);
75
74
  const errorRef = useRef<HTMLDivElement>(null);
76
75
 
76
+ const sitesIds = sites?.map((site: ISite) => site.id);
77
+
77
78
  const allowedToCreateSite = usePermission("general.createSite");
78
79
 
79
80
  const pagination = {
@@ -123,13 +124,6 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
123
124
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
125
  }, [token, getParams]);
125
126
 
126
- useEffect(() => {
127
- if (sites.length > 0) {
128
- const currentSitesId = sites?.map((site: ISite) => site.id);
129
- setsitesIds(currentSitesId);
130
- }
131
- }, [sites]);
132
-
133
127
  useEffect(() => {
134
128
  if (tableRef.current) {
135
129
  tableRef.current.scrollTo(0, 0);
@@ -316,30 +310,29 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
316
310
  </S.EmptyStateWrapper>
317
311
  );
318
312
 
319
- const GridList = () => {
320
- const showPagination = totalItems > itemsPerPage;
321
- return (
322
- <>
323
- <S.SearchTagsGrid>
324
- <SearchTagsBar query={searchQuery} setQuery={setSearchQuery} />
325
- </S.SearchTagsGrid>
326
- <S.GridList data-testid="sites-grid-list" isEmpty={sites.length === 0} fullWidth={sites.length >= 5}>
327
- {mappedSites}
328
- </S.GridList>
329
- {showPagination && (
330
- <S.PaginationWrapper>
331
- <Pagination totalItems={totalItems} setPage={setPage} itemsPerPage={itemsPerPage} currPage={page} />
332
- </S.PaginationWrapper>
333
- )}
334
- </>
335
- );
336
- };
313
+ const showPagination = totalItems > itemsPerPage;
314
+
315
+ const gridList = (
316
+ <>
317
+ <S.SearchTagsGrid>
318
+ <SearchTagsBar query={searchQuery} setQuery={setSearchQuery} />
319
+ </S.SearchTagsGrid>
320
+ <S.GridList data-testid="sites-grid-list" isEmpty={sites.length === 0} fullWidth={sites.length >= 5}>
321
+ {mappedSites}
322
+ </S.GridList>
323
+ {showPagination && (
324
+ <S.PaginationWrapper>
325
+ <Pagination totalItems={totalItems} setPage={setPage} itemsPerPage={itemsPerPage} currPage={page} />
326
+ </S.PaginationWrapper>
327
+ )}
328
+ </>
329
+ );
337
330
 
338
331
  const filterLabels = {
339
332
  liveStatus: "Live",
340
333
  };
341
334
 
342
- const ListTable = () => (
335
+ const listTable = (
343
336
  <TableList tableHeader={Header} pagination={pagination} hasFixedHeader={true} tableRef={tableRef}>
344
337
  <>
345
338
  <S.SearchTags>
@@ -356,12 +349,6 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
356
349
  </TableList>
357
350
  );
358
351
 
359
- const AllSitesList = () => (
360
- <S.AllSitesListWrapper className={hasAnimation ? "animate" : ""} onAnimationEnd={handleAnimationEnd}>
361
- {displayMode === "grid" ? <GridList /> : <ListTable />}
362
- </S.AllSitesListWrapper>
363
- );
364
-
365
352
  const handleAnimationEnd = () => setHasAnimation(false);
366
353
 
367
354
  return (
@@ -376,7 +363,9 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
376
363
  <S.SitesListWrapper className={hasAnimation ? "animate" : ""}>
377
364
  {recentSites?.length > 0 ? <RecentSitesList /> : null}
378
365
  <AllSitesHeader />
379
- <AllSitesList />
366
+ <S.AllSitesListWrapper className={hasAnimation ? "animate" : ""} onAnimationEnd={handleAnimationEnd}>
367
+ {displayMode === "grid" ? <>{gridList}</> : <>{listTable}</>}
368
+ </S.AllSitesListWrapper>
380
369
  </S.SitesListWrapper>
381
370
  <Modal
382
371
  isOpen={isOpen}
@@ -25,11 +25,13 @@ const Sites = (props: ISitesProps): JSX.Element => {
25
25
  globalLangs,
26
26
  getRoles,
27
27
  hasAnimation,
28
+ updateCurrentSearch,
28
29
  } = props;
29
30
 
30
31
  useLayoutEffect(() => {
31
32
  const fetchInitialData = async () => {
32
33
  setCurrentSiteInfo(null);
34
+ updateCurrentSearch("");
33
35
  await getRoles({ siteId: "global" }, token);
34
36
  await getUser("me", token);
35
37
  await getStructuredData(token);
@@ -75,6 +77,7 @@ interface IDispatchProps {
75
77
  getAllDataPacks: () => Promise<void>;
76
78
  getUser: (id: string, token?: string) => Promise<void>;
77
79
  getRoles: (params: IGetRoles, token?: string) => Promise<void>;
80
+ updateCurrentSearch(query: string): Promise<void>;
78
81
  }
79
82
 
80
83
  export type ISitesProps = IStateProps & IDispatchProps & RouteComponentProps;
@@ -86,6 +89,7 @@ const mapDispatchToProps = {
86
89
  getAllDataPacks: dataPacksActions.getAllDataPacks,
87
90
  getUser: usersActions.getUser,
88
91
  getRoles: usersActions.getRoles,
92
+ updateCurrentSearch: structuredDataActions.updateCurrentSearch,
89
93
  };
90
94
 
91
95
  export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Sites));
@@ -17,7 +17,6 @@ import {
17
17
  ICheck,
18
18
  IColumn,
19
19
  IStructuredDataQueryValues,
20
- ISite,
21
20
  IQueryValue,
22
21
  } from "@ax/types";
23
22
  import {
@@ -102,7 +101,8 @@ const StructuredDataList = (props: IProps): JSX.Element => {
102
101
  setContentFilters,
103
102
  contentFilters,
104
103
  checkUserSession,
105
- sites,
104
+ currentSearch,
105
+ updateCurrentSearch,
106
106
  } = props;
107
107
 
108
108
  const itemsPerPage = 50;
@@ -119,7 +119,6 @@ const StructuredDataList = (props: IProps): JSX.Element => {
119
119
  const { isVisible: isPageToast, toggleToast: togglePageToast, setIsVisible: setIsPageToast } = useToast();
120
120
  const tableRef = useRef<HTMLDivElement>(null);
121
121
  const wrapperRef = useRef<HTMLDivElement>(null);
122
- const [searchQuery, setSearchQuery] = useState<string>("");
123
122
  const [isEmpty, setIsEmpty] = useState(false);
124
123
  const [emptyStateProps, setEmptyStateProps] = useState<IEmptyStateProps>({});
125
124
  const { isOpen: isNewOpen, toggleModal: toggleNewModal } = useModal();
@@ -199,13 +198,13 @@ const StructuredDataList = (props: IProps): JSX.Element => {
199
198
  pagination: true,
200
199
  deleted: false,
201
200
  include_draft: true,
202
- query: searchQuery,
201
+ query: currentSearch,
203
202
  format: "list",
204
203
  relatedFields: true,
205
204
  };
206
205
 
207
206
  return params;
208
- }, [page, searchQuery]);
207
+ }, [page, currentSearch]);
209
208
 
210
209
  const getStructuredData = useCallback(
211
210
  async (id: string, filterQuery?: string) => {
@@ -280,7 +279,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
280
279
  tableRef.current.scrollTo(0, 0);
281
280
  }
282
281
  // eslint-disable-next-line react-hooks/exhaustive-deps
283
- }, [lang.locale, page, searchQuery, filterValues]);
282
+ }, [lang.locale, page, currentSearch, filterValues]);
284
283
 
285
284
  useEffect(() => {
286
285
  if (wrapperRef.current) {
@@ -294,7 +293,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
294
293
  useEffect(() => {
295
294
  if (!isFirstRender && !isLoading) {
296
295
  const emptyState: IEmptyStateProps = {};
297
- const isSearching = searchQuery.length > 0;
296
+ const isSearching = currentSearch.length > 0;
298
297
  if (isSearching) {
299
298
  emptyState.icon = "search";
300
299
  emptyState.title = "Oh! No Results Found";
@@ -597,7 +596,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
597
596
 
598
597
  const labelCategory = isStructuredDataFromPage ? { categories: "Category" } : { related: "Category " };
599
598
 
600
- let filterLabels = {
599
+ const filterLabels = {
601
600
  types: "Type",
602
601
  liveStatus: "Live",
603
602
  translated: "Translated",
@@ -612,8 +611,8 @@ const StructuredDataList = (props: IProps): JSX.Element => {
612
611
  availableLanguages={languageProps.globalLangs}
613
612
  language={languageProps.lang}
614
613
  languageActions={languageProps.languageActions}
615
- searchAction={setSearchQuery}
616
- searchValue={searchQuery}
614
+ searchAction={updateCurrentSearch}
615
+ searchValue={currentSearch}
617
616
  >
618
617
  <S.StructuredDataWrapper ref={wrapperRef}>
619
618
  <ContentFilters
@@ -648,7 +647,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
648
647
  >
649
648
  <>
650
649
  <S.SearchTags>
651
- <SearchTagsBar query={searchQuery} setQuery={setSearchQuery} />
650
+ <SearchTagsBar query={currentSearch} setQuery={updateCurrentSearch} />
652
651
  <FilterTagsBar
653
652
  filters={filterValues[currentStructuredData?.id || "all"]}
654
653
  setFilters={setFiltersSelection}
@@ -712,7 +711,7 @@ const mapStateToProps = (state: IRootState) => ({
712
711
  currentSiteErrorPages: state.sites.currentSiteErrorPages,
713
712
  skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
714
713
  contentFilters: state.structuredData.contentFilters,
715
- sites: state.sites.sites,
714
+ currentSearch: state.structuredData.currentSearch,
716
715
  });
717
716
 
718
717
  const mapDispatchToProps = {
@@ -739,6 +738,7 @@ const mapDispatchToProps = {
739
738
  resetCurrentSiteErrorPages: sitesActions.resetCurrentSiteErrorPages,
740
739
  setContentFilters: structuredDataActions.setContentFilters,
741
740
  checkUserSession: appActions.checkUserSession,
741
+ updateCurrentSearch: structuredDataActions.updateCurrentSearch,
742
742
  };
743
743
 
744
744
  interface IDispatchProps {
@@ -765,6 +765,7 @@ interface IDispatchProps {
765
765
  resetCurrentSiteErrorPages: () => Promise<void>;
766
766
  setContentFilters(contentFilters: Record<string, IStructuredDataQueryValues> | null): void;
767
767
  checkUserSession(): Promise<void>;
768
+ updateCurrentSearch(query: string): Promise<void>;
768
769
  }
769
770
 
770
771
  interface ICategoriesProps {
@@ -783,7 +784,7 @@ interface ICategoriesProps {
783
784
  currentSiteErrorPages: number[];
784
785
  skipReviewOnPublish?: boolean;
785
786
  contentFilters: Record<string, IStructuredDataQueryValues> | null;
786
- sites: ISite[];
787
+ currentSearch: string;
787
788
  }
788
789
 
789
790
  type IProps = ICategoriesProps & IDispatchProps;
@@ -174,6 +174,7 @@ export interface ISchemaField {
174
174
  whiteList?: string[];
175
175
  readonly?: boolean;
176
176
  slugTo?: string;
177
+ reference?: string;
177
178
  [key: string]: any; // TODO: set type
178
179
  }
179
180