@griddo/ax 1.75.182 → 1.75.184

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": "1.75.182",
4
+ "version": "1.75.184",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -234,5 +234,5 @@
234
234
  "publishConfig": {
235
235
  "access": "public"
236
236
  },
237
- "gitHead": "888dd2eeef46c60b7f0e9139198eb57bb7253b5f"
237
+ "gitHead": "2200f9bc4190ae41967b895813666980c7a59909"
238
238
  }
@@ -120,6 +120,7 @@ const getDataContents = (params: IGetStructuredDataParams, siteID?: number | nul
120
120
  filterQuery,
121
121
  relatedFields = false,
122
122
  order,
123
+ lang,
123
124
  } = params;
124
125
 
125
126
  const url = siteID ? `${host}/site/${siteID}${endpoint}` : `${host}${endpoint}`;
@@ -130,7 +131,11 @@ const getDataContents = (params: IGetStructuredDataParams, siteID?: number | nul
130
131
  if (order) SERVICES.GET_DATA_CONTENTS.dynamicUrl += `&order=${order}`;
131
132
  if (filterQuery) SERVICES.GET_DATA_CONTENTS.dynamicUrl += filterQuery;
132
133
 
133
- return sendRequest(SERVICES.GET_DATA_CONTENTS);
134
+ const dataHeader = {
135
+ ...(lang && { lang }),
136
+ };
137
+
138
+ return sendRequest(SERVICES.GET_DATA_CONTENTS, null, dataHeader);
134
139
  };
135
140
 
136
141
  const createDataContent = (data: any) => {
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from "react";
2
2
 
3
3
  import { IDataSource, ISite, IStructuredData } from "@ax/types";
4
- import { Button, FloatingMenu, IconAction, RadioGroup, Select, FieldsBehavior } from "@ax/components";
4
+ import { Button, FloatingMenu, IconAction, RadioGroup, Select, FieldsBehavior, UniqueCheck } from "@ax/components";
5
5
 
6
6
  import { IReferenceState, useReference } from "../Context";
7
7
  import AutoItem from "./AutoItem";
@@ -102,6 +102,7 @@ const AutoPanel = (props: IProps): JSX.Element => {
102
102
  filter: state.filter,
103
103
  fullRelations: state.fullRelations,
104
104
  allLanguages: state.allLanguages,
105
+ preferenceLanguage: state.preferenceLanguage,
105
106
  };
106
107
 
107
108
  onChange(newValue);
@@ -171,6 +172,9 @@ const AutoPanel = (props: IProps): JSX.Element => {
171
172
  }
172
173
  };
173
174
 
175
+ const handleAllLanguagesChange = (value: any) =>
176
+ setState((state: IReferenceState) => ({ ...state, allLanguages: value }));
177
+
174
178
  return (
175
179
  <S.Wrapper data-testid="auto-panel-wrapper">
176
180
  <S.FormWrapper>
@@ -197,6 +201,18 @@ const AutoPanel = (props: IProps): JSX.Element => {
197
201
  );
198
202
  })}
199
203
  </S.SourcesWrapper>
204
+ <S.AllLanguagesWrapper>
205
+ <S.AllLanguagesText>
206
+ The content is displayed in the language of the page, but if the content you want is not available in that
207
+ language, you have the option to include content in another language.
208
+ </S.AllLanguagesText>
209
+ <UniqueCheck
210
+ name="AllLanguagesCheck"
211
+ options={[{ value: false, name: "AllLanguages", title: "Include content in another language" }]}
212
+ value={state.allLanguages}
213
+ onChange={handleAllLanguagesChange}
214
+ />
215
+ </S.AllLanguagesWrapper>
200
216
  <S.RadioWrapper>
201
217
  <S.FieldLabel>Sort</S.FieldLabel>
202
218
  <RadioGroup {...radioGroupProps} />
@@ -75,6 +75,21 @@ const SourceItem = styled.li`
75
75
  }
76
76
  `;
77
77
 
78
+ const AllLanguagesWrapper = styled.div`
79
+ ${(p) => p.theme.textStyle.fieldContent};
80
+ color: ${(p) => p.theme.color.textHighEmphasis};
81
+ background-color: ${(p) => p.theme.color.uiBackground03};
82
+ padding: ${(p) => p.theme.spacing.s};
83
+ border-radius: ${(p) => p.theme.radii.s};
84
+ margin-bottom: ${(p) => p.theme.spacing.s};
85
+ `;
86
+
87
+ const AllLanguagesText = styled.div`
88
+ ${(p) => p.theme.textStyle.uiXS};
89
+ color: ${(p) => p.theme.color.textMediumEmphasis};
90
+ margin-bottom: ${(p) => p.theme.spacing.xs};
91
+ `;
92
+
78
93
  export {
79
94
  Wrapper,
80
95
  FormWrapper,
@@ -87,5 +102,7 @@ export {
87
102
  IconWrapper,
88
103
  SourceMenu,
89
104
  SourceItem,
90
- SelectWrapper
105
+ SelectWrapper,
106
+ AllLanguagesWrapper,
107
+ AllLanguagesText,
91
108
  };
@@ -18,6 +18,7 @@ const initState = {
18
18
  sourceTitles: [],
19
19
  fullRelations: false,
20
20
  allLanguages: false,
21
+ preferenceLanguage: false,
21
22
  };
22
23
 
23
24
  const ReferenceContext = createContext({ state: { ...initState } });
@@ -86,6 +87,7 @@ export interface IReferenceState {
86
87
  sourceTitles: any;
87
88
  fullRelations: boolean;
88
89
  allLanguages: boolean;
90
+ preferenceLanguage: boolean;
89
91
  }
90
92
 
91
93
  export { ReferenceProvider, useReference };
@@ -1,13 +1,13 @@
1
1
  import React from "react";
2
2
 
3
- import { IDataSource } from "@ax/types";
3
+ import { IDataSource, IStructuredDataContent } from "@ax/types";
4
4
  import { getFormattedDateWithTimezone } from "@ax/helpers";
5
- import { CheckField } from "@ax/components";
5
+ import { CheckField, Tooltip } from "@ax/components";
6
6
 
7
7
  import * as S from "./style";
8
8
 
9
- const Item = (props: IProps) => {
10
- const { handleOnClick, item, isChecked, source, disabled } = props;
9
+ const Item = (props: IProps): JSX.Element => {
10
+ const { handleOnClick, item, isChecked, source, disabled, tooltip } = props;
11
11
 
12
12
  const handleItemClick = (e: any) => {
13
13
  e.preventDefault();
@@ -15,25 +15,28 @@ const Item = (props: IProps) => {
15
15
  };
16
16
 
17
17
  return (
18
- <S.Item onClick={handleItemClick} data-testid="manual-panel-item">
19
- <CheckField name="check" value={item.id} checked={isChecked} disabled={disabled} />
20
- <S.TextWrapper>
21
- <S.Header>
22
- <S.Type>{source.title.toUpperCase()}</S.Type>
23
- {item.modified && <S.Date>{getFormattedDateWithTimezone(item.modified, "d MMM Y")}</S.Date>}
24
- </S.Header>
25
- <S.Title>{item.content.title}</S.Title>
26
- </S.TextWrapper>
27
- </S.Item>
18
+ <Tooltip content={tooltip} bottom>
19
+ <S.Item onClick={handleItemClick} data-testid="manual-panel-item">
20
+ <CheckField name="check" value={item.id} checked={isChecked} disabled={disabled} />
21
+ <S.TextWrapper>
22
+ <S.Header>
23
+ <S.Type>{source.title.toUpperCase()}</S.Type>
24
+ {item.modified && <S.Date>{getFormattedDateWithTimezone(item.modified, "d MMM Y")}</S.Date>}
25
+ </S.Header>
26
+ <S.Title>{item.content.title}</S.Title>
27
+ </S.TextWrapper>
28
+ </S.Item>
29
+ </Tooltip>
28
30
  );
29
31
  };
30
32
 
31
33
  interface IProps {
32
- handleOnClick: (item: any) => void;
33
- item: any;
34
+ handleOnClick: (item: IStructuredDataContent) => void;
35
+ item: IStructuredDataContent;
34
36
  isChecked: boolean;
35
37
  source: IDataSource;
36
38
  disabled: boolean;
39
+ tooltip: string;
37
40
  }
38
41
 
39
42
  export default Item;
@@ -1,26 +1,36 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { IDataSource, IGetStructuredDataParams, IRootState, IStructuredDataContent } from "@ax/types";
4
+ import {
5
+ IDataLanguage,
6
+ IDataSource,
7
+ IGetStructuredDataParams,
8
+ ILanguage,
9
+ IRootState,
10
+ IStructuredDataContent,
11
+ } from "@ax/types";
5
12
  import { isReqOk } from "@ax/helpers";
6
13
  import { structuredData } from "@ax/api";
7
14
  import { Button, Loader, Select, TextField } from "@ax/components";
8
-
9
15
  import { useDebounce } from "@ax/hooks";
10
-
11
16
  import { IReferenceState, useReference } from "../Context";
12
17
  import Item from "./Item";
18
+
13
19
  import * as S from "./style";
14
20
 
15
21
  const ManualPanel = (props: IProps) => {
16
- const { onChange, currentSite, hasMaxItems, handleValidation, validators } = props;
22
+ const { onChange, currentSite, hasMaxItems, handleValidation, validators, lang, siteLangs, globalLangs } = props;
17
23
 
18
24
  const { state, setState } = useReference();
19
25
 
20
26
  const [isLoading, setIsLoading] = useState(false);
21
27
  const [selectedSource, setSelectedSource] = useState(state.sourceTitles[0].id);
28
+ const [selectedLang, setSelectedLang] = useState(lang.id);
22
29
  const debouncedSearch = useDebounce(state.search);
23
30
 
31
+ const availableLangs = currentSite ? siteLangs : globalLangs;
32
+ const langOptions = availableLangs.map((lang) => ({ label: lang.label, value: lang.id.toString() }));
33
+
24
34
  useEffect(() => {
25
35
  let isMounted = true;
26
36
  const params: IGetStructuredDataParams = {
@@ -31,6 +41,7 @@ const ManualPanel = (props: IProps) => {
31
41
  deleted: false,
32
42
  include_draft: false,
33
43
  query: debouncedSearch,
44
+ lang: selectedLang,
34
45
  };
35
46
 
36
47
  const getAndSetItems = async () => {
@@ -53,14 +64,16 @@ const ManualPanel = (props: IProps) => {
53
64
  isMounted = false;
54
65
  };
55
66
  // eslint-disable-next-line react-hooks/exhaustive-deps
56
- }, [currentSite, selectedSource, debouncedSearch, state.mode]);
67
+ }, [currentSite, selectedSource, debouncedSearch, state.mode, selectedLang]);
57
68
 
58
69
  const handleOnClick = (item: IStructuredDataContent) => {
59
70
  let newSelIds: number[];
60
71
  let selItems: number[];
61
- if (state.fixed.includes(item.id)) {
62
- newSelIds = state.fixed.filter((a: any) => a !== item.id);
63
- selItems = state.selectedItems.filter((b: any) => b.id !== item.id);
72
+ const itemSelected = item.dataLanguages.find((dataLang: IDataLanguage) => state.fixed.includes(dataLang.id));
73
+
74
+ if (itemSelected) {
75
+ newSelIds = state.fixed.filter((a: number) => a !== itemSelected.id);
76
+ selItems = state.selectedItems.filter((b: IStructuredDataContent) => b.id !== itemSelected.id);
64
77
  } else {
65
78
  newSelIds = [...state.fixed, item.id];
66
79
  selItems = [...state.selectedItems, item];
@@ -69,9 +82,8 @@ const ManualPanel = (props: IProps) => {
69
82
  setState((state: IReferenceState) => ({ ...state, fixed: newSelIds, selectedItems: selItems, showSelected }));
70
83
  };
71
84
 
72
- const handleSelectedClick = () => {
85
+ const handleSelectedClick = () =>
73
86
  setState((state: IReferenceState) => ({ ...state, showSelected: !state.showSelected }));
74
- };
75
87
 
76
88
  const handleAdd = () => {
77
89
  const { mode, fixed, fullRelations } = state;
@@ -96,9 +108,9 @@ const ManualPanel = (props: IProps) => {
96
108
  return { label: singleSource.title, value: singleSource.id };
97
109
  });
98
110
 
99
- const handleSource = (newSource: string) => {
100
- setSelectedSource(newSource);
101
- };
111
+ const handleSource = (newSource: string) => setSelectedSource(newSource);
112
+
113
+ const handleLang = (newLang: string) => setSelectedLang(parseInt(newLang));
102
114
 
103
115
  return (
104
116
  <S.Wrapper>
@@ -111,9 +123,26 @@ const ManualPanel = (props: IProps) => {
111
123
  )}
112
124
  {!state.showSelected && (
113
125
  <>
114
- {state.sourceTitles.length > 1 && (
115
- <Select name="select" options={options} onChange={handleSource} value={selectedSource} type="inline" />
116
- )}
126
+ <S.SelectsWrapper>
127
+ {availableLangs.length > 1 && (
128
+ <Select
129
+ name="selectLang"
130
+ options={langOptions}
131
+ onChange={handleLang}
132
+ value={selectedLang.toString()}
133
+ type="inline"
134
+ />
135
+ )}
136
+ {state.sourceTitles.length > 1 && (
137
+ <Select
138
+ name="selectSource"
139
+ options={options}
140
+ onChange={handleSource}
141
+ value={selectedSource}
142
+ type="inline"
143
+ />
144
+ )}
145
+ </S.SelectsWrapper>
117
146
  <S.SearchWrapper>
118
147
  <TextField {...textFieldProps} />
119
148
  </S.SearchWrapper>
@@ -129,16 +158,23 @@ const ManualPanel = (props: IProps) => {
129
158
  {showedItems &&
130
159
  showedItems.map((item: IStructuredDataContent) => {
131
160
  const isChecked = state.fixed.includes(item.id);
161
+ const hasVersionInPageLanguage = item.dataLanguages.find(
162
+ (dataLang: IDataLanguage) => dataLang.language === lang.id && item.language !== lang.id
163
+ );
164
+ const isSelectedOtherLanguage = item.dataLanguages.find(
165
+ (dataLang: IDataLanguage) => state.fixed.includes(dataLang.id) && dataLang.language !== selectedLang
166
+ );
132
167
  const source = state.sourceTitles.find((el: IDataSource) => el.id === item.structuredData);
133
- const disabled = hasMaxItems && !isChecked;
168
+ const disabled = (hasMaxItems && !isChecked) || !!hasVersionInPageLanguage;
134
169
  return (
135
170
  <Item
136
- key={item.id}
137
- isChecked={isChecked}
171
+ key={`${item.content.title}-${item.id}`}
172
+ isChecked={isChecked || !!isSelectedOtherLanguage}
138
173
  handleOnClick={handleOnClick}
139
174
  item={item}
140
175
  source={source}
141
176
  disabled={disabled}
177
+ tooltip={hasVersionInPageLanguage ? "Content has version in page language" : ""}
142
178
  />
143
179
  );
144
180
  })}
@@ -162,11 +198,16 @@ interface IProps {
162
198
  hasMaxItems: boolean;
163
199
  handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
164
200
  validators?: Record<string, unknown>;
201
+ lang: { locale: string; id: number };
202
+ siteLangs: ILanguage[];
203
+ globalLangs: ILanguage[];
165
204
  }
166
205
 
167
206
  const mapStateToProps = (state: IRootState) => ({
168
207
  lang: state.app.lang,
169
208
  currentSite: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
209
+ siteLangs: state.sites.currentSiteLanguages,
210
+ globalLangs: state.app.globalLangs,
170
211
  });
171
212
 
172
213
  export default connect(mapStateToProps)(ManualPanel);
@@ -40,12 +40,11 @@ const ActionsWrapper = styled.div`
40
40
  padding-top: ${(p) => p.theme.spacing.s};
41
41
  `;
42
42
 
43
- export {
44
- Wrapper,
45
- LoadingWrapper,
46
- ListWrapper,
47
- ItemList,
48
- SearchWrapper,
49
- ButtonWrapper,
50
- ActionsWrapper
51
- }
43
+ const SelectsWrapper = styled.div`
44
+ display: flex;
45
+ & > div {
46
+ margin-right: ${(p) => p.theme.spacing.s};
47
+ }
48
+ `;
49
+
50
+ export { Wrapper, LoadingWrapper, ListWrapper, ItemList, SearchWrapper, ButtonWrapper, ActionsWrapper, SelectsWrapper };
@@ -9,7 +9,7 @@ import { IconAction, FloatingPanel, Select, Tag } from "@ax/components";
9
9
  import AutoPanel from "./AutoPanel";
10
10
  import ItemList from "./ItemList";
11
11
  import ManualPanel from "./ManualPanel";
12
- import { useReference, ReferenceProvider } from "./Context";
12
+ import { useReference, ReferenceProvider, IReferenceState } from "./Context";
13
13
 
14
14
  import * as S from "./style";
15
15
 
@@ -45,7 +45,7 @@ const ReferenceField = (props: IReferenceFieldProps) => {
45
45
  const hasMaxItems = !!(value && value.fixed && maxItems && !isAuto && value.fixed.length >= maxItems);
46
46
 
47
47
  const handleMode = (mode: string) => {
48
- const { fixed, order, quantity, filter, allLanguages, fullRelations = false } = state;
48
+ const { fixed, order, quantity, filter, allLanguages, preferenceLanguage, fullRelations = false } = state;
49
49
 
50
50
  const manualSources: string[] = state.selectedItems.reduce(
51
51
  (unique: string[], selItem: IStructuredDataContent) =>
@@ -65,6 +65,7 @@ const ReferenceField = (props: IReferenceFieldProps) => {
65
65
  filter,
66
66
  fullRelations,
67
67
  allLanguages,
68
+ preferenceLanguage,
68
69
  }
69
70
  : {
70
71
  mode,
@@ -107,6 +108,7 @@ const ReferenceField = (props: IReferenceFieldProps) => {
107
108
  }, [editorID]);
108
109
 
109
110
  const handleAddItems = () => {
111
+ setState((state: IReferenceState) => ({ ...state, showSelected: false }));
110
112
  toggleModal();
111
113
  };
112
114
 
@@ -6,7 +6,7 @@ import { Loading, Pagination } from "@ax/components";
6
6
  import * as S from "./style";
7
7
 
8
8
  const TableList = (props: ITableListProps): JSX.Element => {
9
- const { tableHeader, children, pagination, isLoading, onScroll, hasFixedHeader, tableRef, overflow = "auto" } = props;
9
+ const { tableHeader, children, pagination, isLoading, onScroll, hasFixedHeader, tableRef } = props;
10
10
 
11
11
  const { totalItems, setPage, itemsPerPage, currPage } = pagination;
12
12
 
@@ -16,7 +16,9 @@ const TableList = (props: ITableListProps): JSX.Element => {
16
16
  <S.TableList onScroll={onScroll} ref={tableRef} data-testid="table-list">
17
17
  <S.Table>
18
18
  <>
19
- <S.TableHeader hasFixedHeader={hasFixedHeader} data-testid="table-list-header">{tableHeader}</S.TableHeader>
19
+ <S.TableHeader hasFixedHeader={hasFixedHeader} data-testid="table-list-header">
20
+ {tableHeader}
21
+ </S.TableHeader>
20
22
  {isLoading ? <Loading /> : <S.TableBody data-testid="table-body">{children}</S.TableBody>}
21
23
  </>
22
24
  </S.Table>
@@ -46,7 +48,6 @@ export interface ITableListProps {
46
48
  onScroll?: any;
47
49
  hasFixedHeader?: boolean;
48
50
  tableRef?: any;
49
- overflow?: string;
50
51
  }
51
52
 
52
53
  export default connect(mapStateToProps, null)(TableList);
@@ -21,7 +21,7 @@ function getAnalytics(siteId: number | string = "global"): (dispatch: any) => Pr
21
21
  handleError: (response: any) => appActions.handleError(response)(dispatch),
22
22
  };
23
23
 
24
- await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
24
+ await handleRequest(callback, responseActions, [])(dispatch);
25
25
  } catch (e) {
26
26
  console.log(e);
27
27
  }
@@ -955,7 +955,7 @@ function generatePageContent(editorContent: IPage, dispatch: Dispatch, getState:
955
955
  } = getState();
956
956
 
957
957
  const { header, footer, isGlobal } = editorContent;
958
- const { defaultHeader, defaultFooter } = (configFormData.templates && configFormData.templates[template]) || {};
958
+ const { defaultHeader, defaultFooter } = (configFormData?.templates && configFormData?.templates[template]) || {};
959
959
 
960
960
  const headerID =
961
961
  header === null || header === undefined ? defaultHeader : typeof header === "object" ? header.id : header;
@@ -366,12 +366,12 @@ function getSiteLanguages(siteID: number): (dispatch: any) => Promise<void> {
366
366
  };
367
367
  }
368
368
 
369
- function deleteSite(siteID: number): (dispatch: Dispatch, getState: any) => Promise<void> {
370
- return async (dispatch, getState) => {
369
+ function deleteSite(siteID: number, params?: IGetSitesParams): (dispatch: Dispatch) => Promise<void> {
370
+ return async (dispatch) => {
371
371
  try {
372
372
  const responseActions = {
373
373
  handleSuccess: () => {
374
- getSites()(dispatch);
374
+ getSites(params)(dispatch);
375
375
  },
376
376
  handleError: (response: any) => appActions.handleError(response)(dispatch),
377
377
  };
@@ -384,12 +384,12 @@ function deleteSite(siteID: number): (dispatch: Dispatch, getState: any) => Prom
384
384
  };
385
385
  }
386
386
 
387
- function publishSite(siteID: number): (dispatch: Dispatch, getState: any) => Promise<void> {
388
- return async (dispatch, getState) => {
387
+ function publishSite(siteID: number, params?: IGetSitesParams): (dispatch: Dispatch) => Promise<void> {
388
+ return async (dispatch) => {
389
389
  try {
390
390
  const response = await sites.publishSite(siteID);
391
391
  if (isReqOk(response.status)) {
392
- getSites()(dispatch);
392
+ getSites(params)(dispatch);
393
393
  }
394
394
  } catch (e) {
395
395
  console.log(e); // TODO: capturar error bien
@@ -397,8 +397,8 @@ function publishSite(siteID: number): (dispatch: Dispatch, getState: any) => Pro
397
397
  };
398
398
  }
399
399
 
400
- function publishSitesBulk(ids: number[]): (dispatch: Dispatch, getState: any) => Promise<void> {
401
- return async (dispatch, getState) => {
400
+ function publishSitesBulk(ids: number[]): (dispatch: Dispatch) => Promise<void> {
401
+ return async (dispatch) => {
402
402
  try {
403
403
  const responseActions = {
404
404
  handleSuccess: () => {
@@ -415,12 +415,12 @@ function publishSitesBulk(ids: number[]): (dispatch: Dispatch, getState: any) =>
415
415
  };
416
416
  }
417
417
 
418
- function unpublishSite(siteID: number): (dispatch: Dispatch, getState: any) => Promise<void> {
419
- return async (dispatch, getState) => {
418
+ function unpublishSite(siteID: number, params?: IGetSitesParams): (dispatch: Dispatch) => Promise<void> {
419
+ return async (dispatch) => {
420
420
  try {
421
421
  const response = await sites.unpublishSite(siteID);
422
422
  if (isReqOk(response.status)) {
423
- getSites()(dispatch);
423
+ getSites(params)(dispatch);
424
424
  }
425
425
  } catch (e) {
426
426
  console.log(e); // TODO: capturar error bien
@@ -428,8 +428,8 @@ function unpublishSite(siteID: number): (dispatch: Dispatch, getState: any) => P
428
428
  };
429
429
  }
430
430
 
431
- function unpublishSitesBulk(ids: number[]): (dispatch: Dispatch, getState: any) => Promise<void> {
432
- return async (dispatch, getState) => {
431
+ function unpublishSitesBulk(ids: number[]): (dispatch: Dispatch) => Promise<void> {
432
+ return async (dispatch) => {
433
433
  try {
434
434
  const responseActions = {
435
435
  handleSuccess: () => {
@@ -115,15 +115,14 @@ const GlobalEditor = (props: IProps) => {
115
115
  const setRoute = (path: string) => props.setHistoryPush(path, true);
116
116
 
117
117
  const removePage = async () => {
118
- console.log(props, "props");
119
118
  const { deletePage } = props;
120
- const path = "/sites/pages";
119
+ const path = "/data";
121
120
 
122
- deletePage().then((deleted: boolean) => {
123
- if (deleted) {
124
- setRoute(path);
125
- }
126
- });
121
+ const deleted = await deletePage();
122
+
123
+ if (deleted) {
124
+ setRoute(path);
125
+ }
127
126
  };
128
127
 
129
128
  const publishPage = async () => {
@@ -38,15 +38,19 @@ const DataPacks = (props: IProps): JSX.Element => {
38
38
  const { isDirty } = useShouldBeSaved(selected, form);
39
39
  const { isOpen, toggleModal } = useModal();
40
40
  const { isVisible, toggleToast, setIsVisible } = useToast();
41
+ const errorRef = React.useRef<HTMLDivElement>(null);
41
42
 
42
43
  const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
43
44
  const [deactivatedPack, setDeactivatedPack] = useState("");
44
45
 
45
46
  const toggleDeleteModal = () => setIsDeleteModalOpen(!isDeleteModalOpen);
46
47
 
47
- const saveConfig = () => {
48
+ const saveConfig = async () => {
48
49
  setIsSavedData(true);
49
- updateDataPack(selected.id);
50
+ const saved = await updateDataPack(selected.id);
51
+ if (!saved && errorRef.current) {
52
+ errorRef.current.scrollIntoView();
53
+ }
50
54
  };
51
55
 
52
56
  const getActivatedPacks = useCallback((): void => {
@@ -191,7 +195,7 @@ const DataPacks = (props: IProps): JSX.Element => {
191
195
  {isVisible && <Toast {...toastProps} />}
192
196
  <Nav isDirty={isDirty} />
193
197
  <S.ContentWrapper>
194
- <ErrorToast />
198
+ <ErrorToast ref={errorRef} />
195
199
  {isLoading ? (
196
200
  <S.LoadingWrapper>
197
201
  <Loading />
@@ -217,7 +221,7 @@ interface IProps {
217
221
  routerAction: string;
218
222
  getSiteDataPacks: (queryParams?: string) => void;
219
223
  setSelected: (dataPack?: any) => void;
220
- updateDataPack: (dataPack?: any) => void;
224
+ updateDataPack: (dataPack?: any) => Promise<boolean>;
221
225
  setHistoryPush(path: string): any;
222
226
  getDataPacksCategories: () => void;
223
227
  addDataPacks: (dataPack?: any, fromConfig?: boolean) => void;
@@ -3,7 +3,7 @@ import { connect } from "react-redux";
3
3
 
4
4
  import { useModal } from "@ax/hooks";
5
5
  import { isDevelopment } from "@ax/helpers";
6
- import { ISite } from "@ax/types";
6
+ import { IGetSitesParams, ISite } from "@ax/types";
7
7
  import { appActions } from "@ax/containers/App";
8
8
  import { sitesActions } from "@ax/containers/Sites";
9
9
  import { FieldsBehavior, Icon, Modal, Tooltip } from "@ax/components";
@@ -13,7 +13,7 @@ import { ItemName, ItemThumbnail } from "./../../atoms";
13
13
  import * as S from "./style";
14
14
 
15
15
  const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
16
- const { site, setSiteInfo, setHistoryPush, deleteSite, publishSite, unpublishSite } = props;
16
+ const { site, setSiteInfo, setHistoryPush, deleteSite, publishSite, unpublishSite, getParams } = props;
17
17
  const { isOpen: isOpenDelete, toggleModal: toggleDeleteModal } = useModal();
18
18
  const { isOpen: isOpenPublish, toggleModal: togglePublishModal } = useModal();
19
19
  const [inputValue, setInputValue] = useState("");
@@ -62,7 +62,8 @@ const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
62
62
  ];
63
63
 
64
64
  const handleDeleteSite = () => {
65
- deleteSite(site.id);
65
+ const params = getParams();
66
+ deleteSite(site.id, params);
66
67
  toggleDeleteModal();
67
68
  };
68
69
 
@@ -74,12 +75,14 @@ const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
74
75
  const secondaryDeleteAction = { title: "Cancel", onClick: toggleDeleteModal };
75
76
 
76
77
  const handlePublishSite = () => {
77
- publishSite(site.id);
78
+ const params = getParams();
79
+ publishSite(site.id, params);
78
80
  togglePublishModal();
79
81
  };
80
82
 
81
83
  const handleUnpublishSite = () => {
82
- unpublishSite(site.id);
84
+ const params = getParams();
85
+ unpublishSite(site.id, params);
83
86
  togglePublishModal();
84
87
  };
85
88
 
@@ -117,7 +120,7 @@ const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
117
120
  return (
118
121
  <>
119
122
  <S.SiteWrapper key={site.id} onClick={setSite} data-testid="grid-site-item">
120
- <Tooltip content={siteTooltip} >
123
+ <Tooltip content={siteTooltip}>
121
124
  <ItemThumbnail src={site.thumbnail} width={280} height={199} />
122
125
  </Tooltip>
123
126
  <S.Wrapper>
@@ -170,9 +173,10 @@ interface IGridSiteItemProps {
170
173
  site: ISite;
171
174
  setSiteInfo(currentSiteInfo: any): void;
172
175
  setHistoryPush(page: string, isEditor: boolean): void;
173
- deleteSite(siteID: number): void;
174
- publishSite(siteID: number): void;
175
- unpublishSite(siteID: number): void;
176
+ deleteSite(siteID: number, params?: IGetSitesParams): void;
177
+ publishSite(siteID: number, params?: IGetSitesParams): void;
178
+ unpublishSite(siteID: number, params?: IGetSitesParams): void;
179
+ getParams: () => IGetSitesParams;
176
180
  }
177
181
 
178
182
  const mapDispatchToProps = {
@@ -3,7 +3,7 @@ import { connect } from "react-redux";
3
3
 
4
4
  import { useModal } from "@ax/hooks";
5
5
  import { getFormattedDateWithTimezone } from "@ax/helpers";
6
- import { ICheck, ISite } from "@ax/types";
6
+ import { ICheck, IGetSitesParams, ISite } from "@ax/types";
7
7
  import { appActions } from "@ax/containers/App";
8
8
  import { sitesActions } from "@ax/containers/Sites";
9
9
  import { FieldsBehavior, Icon, Modal, Tooltip, CheckField } from "@ax/components";
@@ -13,7 +13,8 @@ import { ItemName, ItemThumbnail } from "./../../atoms";
13
13
  import * as S from "./style";
14
14
 
15
15
  const ListSiteItem = (props: IListSiteItemProps): JSX.Element => {
16
- const { site, setSiteInfo, setHistoryPush, deleteSite, publishSite, unpublishSite, isSelected, onCheck } = props;
16
+ const { site, setSiteInfo, setHistoryPush, deleteSite, publishSite, unpublishSite, isSelected, onCheck, getParams } =
17
+ props;
17
18
 
18
19
  const { isOpen: isOpenDelete, toggleModal: toggleDeleteModal } = useModal();
19
20
  const { isOpen: isOpenPublish, toggleModal: togglePublishModal } = useModal();
@@ -63,7 +64,8 @@ const ListSiteItem = (props: IListSiteItemProps): JSX.Element => {
63
64
  ];
64
65
 
65
66
  const handleDeleteSite = () => {
66
- deleteSite(site.id);
67
+ const params = getParams();
68
+ deleteSite(site.id, params);
67
69
  toggleDeleteModal();
68
70
  };
69
71
 
@@ -75,12 +77,14 @@ const ListSiteItem = (props: IListSiteItemProps): JSX.Element => {
75
77
  const secondaryDeleteAction = { title: "Cancel", onClick: toggleDeleteModal };
76
78
 
77
79
  const handlePublishSite = () => {
78
- publishSite(site.id);
80
+ const params = getParams();
81
+ publishSite(site.id, params);
79
82
  togglePublishModal();
80
83
  };
81
84
 
82
85
  const handleUnpublishSite = () => {
83
- unpublishSite(site.id);
86
+ const params = getParams();
87
+ unpublishSite(site.id, params);
84
88
  togglePublishModal();
85
89
  };
86
90
 
@@ -183,10 +187,11 @@ interface IListSiteItemProps {
183
187
  isSelected: boolean;
184
188
  setSiteInfo(currentSiteInfo: any): void;
185
189
  setHistoryPush(site: string, isEditor: boolean): void;
186
- deleteSite(siteID: number): void;
187
- publishSite(siteID: number): void;
188
- unpublishSite(siteID: number): void;
190
+ deleteSite(siteID: number, params?: IGetSitesParams): void;
191
+ publishSite(siteID: number, params?: IGetSitesParams): void;
192
+ unpublishSite(siteID: number, params?: IGetSitesParams): void;
189
193
  onCheck: (e: any) => void;
194
+ getParams: () => IGetSitesParams;
190
195
  }
191
196
 
192
197
  const mapDispatchToProps = {
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { appActions } from "@ax/containers/App";
@@ -6,7 +6,7 @@ import { sitesActions } from "@ax/containers/Sites";
6
6
  import { ICheck, IGetSitesParams, IRootState, ISettingsForm, ISite, ISiteListConfig, IUser } from "@ax/types";
7
7
  import { IError } from "@ax/containers/App/reducer";
8
8
  import { useBulkSelection, useModal } from "@ax/hooks";
9
- import { MainWrapper, Modal, ErrorToast, Icon, IconAction, TableList, EmptyState } from "@ax/components";
9
+ import { MainWrapper, Modal, ErrorToast, Icon, IconAction, TableList, EmptyState, Pagination } from "@ax/components";
10
10
 
11
11
  import { useFilterQuery, useSortedListStatus, useIsMount } from "./hooks";
12
12
  import { filterByStatus, getSortedListStatus } from "./utils";
@@ -47,7 +47,7 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
47
47
 
48
48
  const itemsPerPage = 30;
49
49
  const firstPage = 1;
50
- const [sitesIds, setsitesIds] = useState<any[]>([]);
50
+ const [sitesIds, setsitesIds] = useState<number[]>([]);
51
51
 
52
52
  const [page, setPage] = useState(firstPage);
53
53
  const [form, setForm] = useState(initialState);
@@ -81,19 +81,22 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
81
81
  const { setSortedListStatus } = useSortedListStatus();
82
82
  const { setFiltersSelection, setFilterQuery, filterValues } = useFilterQuery(currentConfig.filterValues);
83
83
 
84
+ const getParams = useCallback((): IGetSitesParams => {
85
+ const query = searchQuery ? `&query=${searchQuery}` : "";
86
+ return {
87
+ pagination: true,
88
+ language: undefined,
89
+ recentSitesNumber: 7,
90
+ page,
91
+ itemsPerPage,
92
+ filterQuery: currentFilterQuery,
93
+ searchQuery: query,
94
+ };
95
+ }, [currentFilterQuery, searchQuery, page]);
96
+
84
97
  useEffect(() => {
85
98
  if (token) {
86
- const query = searchQuery ? `&query=${searchQuery}` : "";
87
- const params = {
88
- pagination: true,
89
- language: undefined,
90
- recentSitesNumber: 7,
91
- page,
92
- itemsPerPage,
93
- filterQuery: currentFilterQuery,
94
- searchQuery: query,
95
- };
96
-
99
+ const params = getParams();
97
100
  if (isMount) {
98
101
  getSites({ ...params, token });
99
102
  } else {
@@ -101,11 +104,11 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
101
104
  }
102
105
  }
103
106
  // eslint-disable-next-line react-hooks/exhaustive-deps
104
- }, [token, currentFilterQuery, searchQuery, page]);
107
+ }, [token, getParams]);
105
108
 
106
109
  useEffect(() => {
107
110
  if (sites.length > 0) {
108
- const currentSitesId = sites?.map((site: any) => site.id);
111
+ const currentSitesId = sites?.map((site: ISite) => site.id);
109
112
  setsitesIds(currentSitesId);
110
113
  }
111
114
  }, [sites]);
@@ -259,16 +262,7 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
259
262
  unpublishSitesBulk(published);
260
263
  }
261
264
 
262
- const query = searchQuery ? `&query=${searchQuery}` : "";
263
- const params = {
264
- pagination: true,
265
- language: undefined,
266
- recentSitesNumber: 7,
267
- page,
268
- itemsPerPage,
269
- filterQuery: currentFilterQuery,
270
- searchQuery: query,
271
- };
265
+ const params = getParams();
272
266
  getSites(params);
273
267
  unselectAllItems();
274
268
  };
@@ -299,9 +293,15 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
299
293
  currentSites.map((site: ISite) => {
300
294
  const isItemSelected = isSelected(site.id);
301
295
  return displayMode === "grid" ? (
302
- <GridSiteItem key={site.id} site={site} />
296
+ <GridSiteItem key={site.id} site={site} getParams={getParams} />
303
297
  ) : (
304
- <ListSiteItem key={site.id} site={site} isSelected={isItemSelected} onCheck={handleAddToBulk} />
298
+ <ListSiteItem
299
+ key={site.id}
300
+ site={site}
301
+ isSelected={isItemSelected}
302
+ onCheck={handleAddToBulk}
303
+ getParams={getParams}
304
+ />
305
305
  );
306
306
  })
307
307
  ) : (
@@ -310,19 +310,28 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
310
310
  </S.EmptyStateWrapper>
311
311
  );
312
312
 
313
- const GridList = () => (
314
- <S.GridList data-testid="sites-grid-list" isEmpty={currentSites.length === 0} fullWidth={currentSites.length >= 5}>
315
- {mappedSites}
316
- </S.GridList>
317
- );
313
+ const GridList = () => {
314
+ const showPagination = totalItems > itemsPerPage;
315
+ return (
316
+ <>
317
+ <S.GridList
318
+ data-testid="sites-grid-list"
319
+ isEmpty={currentSites.length === 0}
320
+ fullWidth={currentSites.length >= 5}
321
+ >
322
+ {mappedSites}
323
+ </S.GridList>
324
+ {showPagination && (
325
+ <S.PaginationWrapper>
326
+ <Pagination totalItems={totalItems} setPage={setPage} itemsPerPage={itemsPerPage} currPage={page} />
327
+ </S.PaginationWrapper>
328
+ )}
329
+ </>
330
+ );
331
+ };
332
+
318
333
  const ListTable = () => (
319
- <TableList
320
- tableHeader={Header}
321
- pagination={pagination}
322
- hasFixedHeader={true}
323
- tableRef={tableRef}
324
- overflow="visible"
325
- >
334
+ <TableList tableHeader={Header} pagination={pagination} hasFixedHeader={true} tableRef={tableRef}>
326
335
  {mappedSites}
327
336
  </TableList>
328
337
  );
@@ -151,6 +151,13 @@ const Thumbnail = styled.div<{ backgroundUrl: string; height: number; width: num
151
151
 
152
152
  const ItemName = styled.span``;
153
153
 
154
+ const PaginationWrapper = styled.div`
155
+ display: flex;
156
+ justify-content: flex-end;
157
+ padding-bottom: ${(p) => p.theme.spacing.m};
158
+ padding-right: ${(p) => p.theme.spacing.m};
159
+ `;
160
+
154
161
  export {
155
162
  SectionHeader,
156
163
  CollapseButton,
@@ -167,4 +174,5 @@ export {
167
174
  ImageWrapper,
168
175
  Thumbnail,
169
176
  ItemName,
177
+ PaginationWrapper,
170
178
  };
@@ -181,7 +181,6 @@ const StructuredDataList = (props: IProps): JSX.Element => {
181
181
  const params = {
182
182
  page,
183
183
  itemsPerPage,
184
- lang: lang.locale,
185
184
  pagination: true,
186
185
  deleted: false,
187
186
  include_draft: true,
@@ -191,7 +190,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
191
190
  };
192
191
 
193
192
  return params;
194
- }, [lang.locale, page, searchQuery]);
193
+ }, [page, searchQuery]);
195
194
 
196
195
  const getStructuredData = useCallback(
197
196
  (id: string, filterQuery?: string) => {
@@ -8,6 +8,7 @@ const TableListWrapper = styled.div`
8
8
  position: relative;
9
9
  width: 100%;
10
10
  height: calc(100vh - ${(p) => p.theme.spacing.xl});
11
+ overflow: auto;
11
12
  `;
12
13
 
13
14
  const NotificationWrapper = styled.div`
@@ -333,6 +333,7 @@ export interface IGetStructuredDataParams {
333
333
  filterQuery?: string;
334
334
  relatedFields?: boolean;
335
335
  order?: string;
336
+ lang?: number;
336
337
  }
337
338
 
338
339
  export interface IMenu {