From 608bd0077bf8d8ca54eb325f447ccb6c9cca1c5d Mon Sep 17 00:00:00 2001 From: Mikhail-Kolpakov Date: Thu, 12 Sep 2024 00:52:39 +0300 Subject: [PATCH 1/3] feat: added loading spinner while the data is being loaded from the server (admin page) --- src/features/AdminPage/AdminPage.styles.scss | 15 +++++++ .../CategoriesPage.component.tsx | 20 +++++++-- .../ContextPage/ContextMainPage.component.tsx | 16 +++++++- .../JobsTable/JobsTable.component.tsx | 26 ++++++++---- .../AdminPage/NewsPage/News.component.tsx | 15 +++++-- .../PartnerModal/PartnerModal.component.tsx | 22 ---------- .../PartnersPage/Partners.component.tsx | 41 +++++++++++++++++-- .../StreetcodesTable.component.tsx | 16 +++++++- .../TagsPage/TagsMainPage.component.tsx | 16 +++++++- .../AdminPage/TeamPage/TeamPage.component.tsx | 20 +++++++-- .../TeamPositionsMainPage.component.tsx | 16 +++++++- 11 files changed, 172 insertions(+), 51 deletions(-) diff --git a/src/features/AdminPage/AdminPage.styles.scss b/src/features/AdminPage/AdminPage.styles.scss index eef5c080d..06f1c2fe5 100644 --- a/src/features/AdminPage/AdminPage.styles.scss +++ b/src/features/AdminPage/AdminPage.styles.scss @@ -3,6 +3,8 @@ @use "@sass/mixins/_utils.mixins.scss" as mut; @use "@sass/_utils.functions.scss" as f; +$loadImg: '@assets/images/catalog/loading.webp'; + .adminPageContainer { @include mut.admin-page-base-layout(); @@ -28,3 +30,16 @@ min-height: 78vh; overflow: hidden; } + +.loadingWrapper { + background-color: c.$pure-white-color; + width: 100%; + height: 100%; + @include mut.flex-centered(); + + #loadingGif { + @include mut.bg-image($loadImg, contain, no-repeat); + @include mut.sized(60px, 60px); + margin: pxToRem(84px) 0; + } +} diff --git a/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx b/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx index dd008d123..c22d510ce 100644 --- a/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx +++ b/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx @@ -6,7 +6,7 @@ import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import ImageStore from '@stores/image-store'; import useMobx, { useModalContext } from '@stores/root-store'; -import { Button } from 'antd'; +import {Button, Empty} from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; import base64ToUrl from '@/app/common/utils/base64ToUrl.utility'; @@ -21,8 +21,10 @@ const CategoriesMainPage: React.FC = observer(() => { const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [categoryToEdit, setSourcesToEdit] = useState(); + const [isLoading, setIsLoading] = useState(false); const updatedCategories = () => { + setIsLoading(true); Promise.all([ sourcesStore.fetchSrcCategoriesAll(), ]).then(() => { @@ -36,7 +38,10 @@ const CategoriesMainPage: React.FC = observer(() => { }); } }); - }).then(() => sourcesStore.setInternalCategoriesMap(sourcesStore.getSrcCategoriesArray)); + }).then(() => { + sourcesStore.setInternalCategoriesMap(sourcesStore.getSrcCategoriesArray); + setIsLoading(false); + }); }; useEffect(() => { @@ -129,8 +134,17 @@ const CategoriesMainPage: React.FC = observer(() => { pagination={{ pageSize: 10 }} className="categories-table" columns={columns} - dataSource={sourcesStore?.getSrcCategoriesArray} + dataSource={isLoading ? [] : sourcesStore?.getSrcCategoriesArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx index f25bbbaf2..8ce516da7 100644 --- a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import useMobx, { useModalContext } from '@stores/root-store'; -import { Button } from 'antd'; +import { Button, Empty } from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; import ContextAdminModalComponent from '@features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component'; @@ -17,9 +17,12 @@ const ContextMainPage: React.FC = observer(() => { const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [contextToEdit, setContextToEdit] = useState(); + const [isLoading, setIsLoading] = useState(false); const updatedContexts = () => { + setIsLoading(true); contextStore.fetchContexts(); + setIsLoading(false); }; useEffect(() => { @@ -93,8 +96,17 @@ const ContextMainPage: React.FC = observer(() => { pagination={{ pageSize: 10 }} className="partners-table" columns={columns} - dataSource={contextStore.getContextArray} + dataSource={isLoading ? [] : contextStore.getContextArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx b/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx index 5469d6dd9..954300401 100644 --- a/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx +++ b/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { DeleteOutlined, DownOutlined, EditOutlined } from '@ant-design/icons'; import { - Button, Dropdown, MenuProps, Space, Table, + Button, Dropdown, Empty, MenuProps, Space, Table, } from 'antd'; import JobApi from '@/app/api/job/Job.api'; @@ -16,6 +16,7 @@ const JobsTable = () => { const [currentId, setCurrentId] = useState(0); const { modalStore } = useModalContext(); const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); const DeleteJob = (id: number) => { modalStore.setConfirmationModal( @@ -132,13 +133,11 @@ const JobsTable = () => { ]; const fetchJobsData = () => { - JobApi.getAllShort() - .then((response) => { - setMappedJobsShort(response); - }) - .catch((error) => { - console.log(error); - }); + setIsLoading(true); + JobApi.getAllShort().then((response) => { + setMappedJobsShort(response); + setIsLoading(false); + }); }; useEffect(() => { @@ -169,9 +168,18 @@ const JobsTable = () => { /> +
+
+ ) : ( + + ), + }} /> ); diff --git a/src/features/AdminPage/NewsPage/News.component.tsx b/src/features/AdminPage/NewsPage/News.component.tsx index 9cd243c87..b61061819 100644 --- a/src/features/AdminPage/NewsPage/News.component.tsx +++ b/src/features/AdminPage/NewsPage/News.component.tsx @@ -9,7 +9,7 @@ import useMobx, { useModalContext } from '@stores/root-store'; import { useQuery } from '@tanstack/react-query'; import dayjs from 'dayjs'; -import { Button, Pagination } from 'antd'; +import { Button, Empty, Pagination } from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; import FRONTEND_ROUTES from '@/app/common/constants/frontend-routes.constants'; @@ -24,7 +24,7 @@ const Newss: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [newsToEdit, setNewsToEdit] = useState(); - useQuery({ + const { isLoading } = useQuery({ queryKey: ['news', newsStore.CurrentPage], queryFn: () => newsStore.getAll(10), }); @@ -128,8 +128,17 @@ const Newss: React.FC = observer(() => { pagination={false} className="partners-table" columns={columns} - dataSource={newsStore.NewsArray} + dataSource={newsStore.NewsArray || []} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />

diff --git a/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.tsx b/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.tsx index fe89f5c48..bf96daa17 100644 --- a/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.tsx +++ b/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.tsx @@ -29,8 +29,6 @@ import Partner, { PartnerSourceLinkCreateUpdate, } from '@/models/partners/partners.model'; import { StreetcodeShort } from '@/models/streetcode/streetcode-types.model'; -import ImageStore from '@/app/stores/image-store'; -import { runInAction } from 'mobx'; import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; const PartnerModal: React.FC< { @@ -66,26 +64,6 @@ const PartnerModal: React.FC< { const [actionSuccess, setActionSuccess] = useState(false); const [waitingForApiResponse, setWaitingForApiResponse] = useState(false); - const updatedPartners = () => { - Promise.all([ - partnersStore?.fetchPartnersAll(), - ]).then(() => { - partnersStore?.PartnerMap.forEach((val, key) => { - ImageStore.getImageById(val.logoId).then((logo) => { - runInAction(() => { - partnersStore.PartnerMap.set( - val.id, - { ...val, logo }, - ); - }) - }); - }); - }).then(() => partnersStore.setInternalMap(partnersStore.getPartnerArray)); - }; - useEffect(() => { - updatedPartners(); - }, []); - message.config({ top: 100, duration: 2, diff --git a/src/features/AdminPage/PartnersPage/Partners.component.tsx b/src/features/AdminPage/PartnersPage/Partners.component.tsx index 19054030b..223cfa342 100644 --- a/src/features/AdminPage/PartnersPage/Partners.component.tsx +++ b/src/features/AdminPage/PartnersPage/Partners.component.tsx @@ -9,9 +9,11 @@ import twitter from '@assets/images/partners/twitterNew.svg'; import youtube from '@assets/images/partners/youtube.svg'; import useMobx, { useModalContext } from '@stores/root-store'; -import { Button } from 'antd'; +import { Button, Empty } from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; +import ImageStore from '@stores/image-store'; +import { runInAction } from 'mobx'; import PartnersApi from '@/app/api/partners/partners.api'; import base64ToUrl from '@/app/common/utils/base64ToUrl.utility'; import Image from '@/models/media/image.model'; @@ -25,11 +27,35 @@ const LogoType = [twitter, instagram, facebook, youtube]; const Partners:React.FC = observer(() => { const { partnersStore } = useMobx(); - const { modalStore } = useModalContext(); const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [partnerToEdit, setPartnerToedit] = useState(); + const [isLoading, setIsLoading] = useState(false); + + const updatedPartners = () => { + setIsLoading(true); + Promise.all([ + partnersStore?.fetchPartnersAll(), + ]).then(() => { + partnersStore?.PartnerMap.forEach((val, key) => { + ImageStore.getImageById(val.logoId).then((logo) => { + runInAction(() => { + partnersStore.PartnerMap.set( + val.id, + { ...val, logo }, + ); + }); + }); + }); + }).then(() => { + partnersStore.setInternalMap(partnersStore.getPartnerArray); + setIsLoading(false); + }); + }; + useEffect(() => { + updatedPartners(); + }, [modalAddOpened, modalEditOpened]); const columns: ColumnsType = [ { @@ -151,8 +177,17 @@ const Partners:React.FC = observer(() => { pagination={{ pageSize: 10 }} className="partners-table" columns={columns} - dataSource={partnersStore?.getPartnerArray} + dataSource={isLoading ? [] : partnersStore?.getPartnerArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx b/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx index 35e9b57a2..7880da109 100644 --- a/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx +++ b/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx @@ -11,7 +11,7 @@ import { format } from 'date-fns'; import { uk } from 'date-fns/locale'; import { - Button, Dropdown, InputNumber, MenuProps, Modal, Pagination, Space, + Button, Dropdown, Empty, InputNumber, MenuProps, Modal, Pagination, Space, } from 'antd'; import Table from 'antd/es/table/Table'; @@ -43,6 +43,7 @@ const StreetcodesTable = () => { const [currentStreetcodeOption, setCurrentStreetcodeOption] = useState(0); const [isConfirmationModalVisible, setIsConfirmationModalVisible] = useState(false); const [deleteStreetcode, deleteFormDB] = useState(0); + const [isLoading, setIsLoading] = useState(false); const amountRequest = 10; const requestDefault: GetAllStreetcodesRequest = { @@ -263,6 +264,7 @@ const StreetcodesTable = () => { } const fetchPaginatedData = async () => { + setIsLoading(true); requestGetAll.Page = pageRequest; requestGetAll.Amount = amountRequest; const getAllStreetcodesResponse = await StreetcodesApi.getAll(requestGetAll); @@ -294,6 +296,7 @@ const StreetcodesTable = () => { setMapedStreetCodes(mapedStreetCodesBuffer); setTotalItems(response[0].pages * amountRequest); + setIsLoading(false); }; @@ -308,8 +311,17 @@ const StreetcodesTable = () => {
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx index 912390fce..6b84338ec 100644 --- a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx +++ b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import useMobx, { useModalContext } from '@stores/root-store'; -import { Button } from 'antd'; +import { Button, Empty } from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; import TagAdminModal from './TagsPage/TagAdminModal'; @@ -17,9 +17,12 @@ const TagsMainPage: React.FC = observer(() => { const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [tagToEdit, setTagToEdit] = useState(); + const [isLoading, setIsLoading] = useState(false); const updatedTags = () => { + setIsLoading(true); tagsStore.fetchTags(); + setIsLoading(false); }; useEffect(() => { @@ -93,8 +96,17 @@ const TagsMainPage: React.FC = observer(() => { pagination={{ pageSize: 10 }} className="tags-table" columns={columns} - dataSource={tagsStore.getTagArray} + dataSource={isLoading ? [] : tagsStore.getTagArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/TeamPage/TeamPage.component.tsx b/src/features/AdminPage/TeamPage/TeamPage.component.tsx index 1569f050d..f57a01a27 100644 --- a/src/features/AdminPage/TeamPage/TeamPage.component.tsx +++ b/src/features/AdminPage/TeamPage/TeamPage.component.tsx @@ -12,7 +12,7 @@ import tiktok from '@assets/images/partners/tiktok.svg'; import twitter from '@assets/images/partners/twitterNew.svg'; import youtube from '@assets/images/partners/youtube.svg'; -import { Button, Table } from 'antd'; +import { Button, Empty, Table } from 'antd'; import { ColumnsType } from 'antd/es/table'; import Image from '@/models/media/image.model'; @@ -32,15 +32,20 @@ const TeamPage = () => { const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [teamToEdit, setTeamToedit] = useState(); + const [isLoading, setIsLoading] = useState(false); const updatedTeam = () => { + setIsLoading(true); Promise.all([teamStore?.fetchTeamAll()]).then(() => { teamStore?.TeamMap.forEach((val, key) => { ImageStore.getImageById(val.imageId).then((image) => { teamStore.TeamMap.set(val.id, { ...val, image }); }); }); - }).then(() => teamStore.setInternalMap(teamStore.getTeamArray)); + }).then(() => { + teamStore.setInternalMap(teamStore.getTeamArray); + setIsLoading(false); + }); }; useEffect(() => { @@ -185,8 +190,17 @@ const TeamPage = () => { pagination={{ pageSize: 10 }} className="team-table" columns={columns} - dataSource={teamStore?.getTeamArray} + dataSource={isLoading ? [] : teamStore?.getTeamArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx index 6870983f1..99aae9873 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import useMobx, { useModalContext } from '@stores/root-store'; -import { Button } from 'antd'; +import { Button, Empty } from 'antd'; import Table, { ColumnsType } from 'antd/es/table'; import TeamPositionsAdminModal from './TeamPositionsModal/TeamPositionsAdminModal.component'; @@ -17,10 +17,13 @@ const TeamPositionsMainPage: React.FC = observer(() => { const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); const [positionToEdit, setPositionToEdit] = useState(); + const [isLoading, setIsLoading] = useState(false); const updatedPositions = () => { + setIsLoading(true); const data = teamPositionsStore.fetchPositions(); console.log(data); + setIsLoading(false); }; useEffect(() => { @@ -94,8 +97,17 @@ const TeamPositionsMainPage: React.FC = observer(() => { pagination={{ pageSize: 10 }} className="positions-table" columns={columns} - dataSource={teamPositionsStore.getPositionsArray} + dataSource={isLoading ? [] : teamPositionsStore.getPositionsArray} rowKey="id" + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} />
From dd2747f6f01873f63e92c0d0b98b96753d0da379 Mon Sep 17 00:00:00 2001 From: Mikhail-Kolpakov Date: Mon, 21 Oct 2024 00:36:17 +0300 Subject: [PATCH 2/3] feat: made some improvements that was suggested by code reviewer --- .../AdminPage/ContextPage/ContextMainPage.component.tsx | 2 +- src/features/AdminPage/TagsPage/TagsMainPage.component.tsx | 2 +- .../TeamPositionsPage/TeamPositionsMainPage.component.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx index 4758b10de..f7a97399e 100644 --- a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx @@ -27,7 +27,7 @@ const ContextMainPage: React.FC = observer(() => { const updatedContexts = () => { setIsLoading(true); - contextStore.fetchContexts(); + contextStore.fetchContexts().then(); setIsLoading(false); }; diff --git a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx index 83890228b..72896bc0f 100644 --- a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx +++ b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx @@ -26,7 +26,7 @@ const TagsMainPage: React.FC = observer(() => { }); const updatedTags = () => { - tagsStore.fetchAllTags(); + tagsStore.fetchAllTags().then(); }; useEffect(() => { diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx index e838da947..64efaecfd 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx @@ -26,7 +26,7 @@ const TeamPositionsMainPage: React.FC = observer(() => { }); const updatedPositions = () => { - const data = teamPositionsStore.fetchPositions(); + teamPositionsStore.fetchPositions().then(); }; useEffect(() => { From f472206a13060d6d2417ac9ff8bf5335d8f07ab2 Mon Sep 17 00:00:00 2001 From: Mikhail-Kolpakov Date: Mon, 21 Oct 2024 00:58:32 +0300 Subject: [PATCH 3/3] feat: fixed tests --- .../AdminPage/ContextPage/ContextMainPage.component.tsx | 2 +- src/features/AdminPage/TagsPage/TagsMainPage.component.tsx | 2 +- .../TeamPositionsPage/TeamPositionsMainPage.component.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx index f7a97399e..4758b10de 100644 --- a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx @@ -27,7 +27,7 @@ const ContextMainPage: React.FC = observer(() => { const updatedContexts = () => { setIsLoading(true); - contextStore.fetchContexts().then(); + contextStore.fetchContexts(); setIsLoading(false); }; diff --git a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx index 72896bc0f..83890228b 100644 --- a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx +++ b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx @@ -26,7 +26,7 @@ const TagsMainPage: React.FC = observer(() => { }); const updatedTags = () => { - tagsStore.fetchAllTags().then(); + tagsStore.fetchAllTags(); }; useEffect(() => { diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx index 64efaecfd..0912aa3d4 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx @@ -26,7 +26,7 @@ const TeamPositionsMainPage: React.FC = observer(() => { }); const updatedPositions = () => { - teamPositionsStore.fetchPositions().then(); + teamPositionsStore.fetchPositions(); }; useEffect(() => {