diff --git a/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts b/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts index be877a536..312356413 100644 --- a/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts +++ b/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts @@ -22,6 +22,7 @@ import { updateMetric, updateMetricShare } from './requests'; +import type { BusterCollection } from '@/api/asset_interfaces/collection'; /** * This is a mutation that saves a metric to the server. @@ -157,16 +158,39 @@ export const useSaveMetricToCollections = () => { return useMutation({ mutationFn: saveMetricToCollection, - onSuccess: (_, { collectionIds }) => { + onMutate: ({ metricIds, collectionIds }) => { + metricIds.forEach((id) => { + queryClient.setQueryData( + metricsQueryKeys.metricsGetMetric(id, null).queryKey, + (oldData) => { + if (!oldData) return oldData; + const newData: BusterMetric = create(oldData, (draft) => { + draft.collections = [ + ...(draft.collections || []), + ...collectionIds.map((id) => ({ id, name: '' })) + ]; + }); + return newData; + } + ); + }); + }, + onSuccess: (_, { collectionIds, metricIds }) => { const collectionIsInFavorites = userFavorites.some((f) => { return collectionIds.includes(f.id); }); if (collectionIsInFavorites) refreshFavoritesList(); - queryClient.invalidateQueries({ - queryKey: collectionIds.map( - (id) => collectionQueryKeys.collectionsGetCollection(id).queryKey - ), - refetchType: 'all' + + collectionIds.forEach((id) => { + queryClient.invalidateQueries({ + queryKey: collectionQueryKeys.collectionsGetCollection(id).queryKey + }); + }); + + metricIds.forEach((id) => { + queryClient.invalidateQueries({ + queryKey: metricsQueryKeys.metricsGetMetric(id, null).queryKey + }); }); } }); @@ -192,17 +216,48 @@ export const useRemoveMetricFromCollection = () => { return useMutation({ mutationFn: removeMetricFromCollection, + onMutate: ({ metricIds, collectionIds }) => { + metricIds.forEach((id) => { + queryClient.setQueryData( + metricsQueryKeys.metricsGetMetric(id, null).queryKey, + (oldData) => { + if (!oldData) return oldData; + const newData: BusterMetric = create(oldData, (draft) => { + draft.collections = draft.collections?.filter((c) => !collectionIds.includes(c.id)); + }); + return newData; + } + ); + }); + collectionIds.forEach((id) => { + queryClient.setQueryData( + collectionQueryKeys.collectionsGetCollection(id).queryKey, + (oldData) => { + if (!oldData) return oldData; + const newData: BusterCollection = create(oldData, (draft) => { + draft.assets = draft.assets?.filter((a) => !metricIds.includes(a.id)) || []; + }); + return newData; + } + ); + }); + }, onSuccess: (_, { collectionIds, metricIds }) => { const collectionIsInFavorites = userFavorites.some((f) => { return collectionIds.includes(f.id); }); if (collectionIsInFavorites) refreshFavoritesList(); - queryClient.invalidateQueries({ - queryKey: collectionIds.map( - (id) => collectionQueryKeys.collectionsGetCollection(id).queryKey - ), - refetchType: 'all' + collectionIds.forEach((id) => { + queryClient.invalidateQueries({ + queryKey: collectionQueryKeys.collectionsGetCollection(id).queryKey + }); + }); + + metricIds.forEach((id) => { + queryClient.invalidateQueries({ + queryKey: metricsQueryKeys.metricsGetMetric(id, null).queryKey + }); }); } }); diff --git a/apps/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx b/apps/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx index c7dc35f01..072507554 100644 --- a/apps/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx +++ b/apps/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx @@ -1,7 +1,6 @@ -import uniq from 'lodash/uniq'; import type React from 'react'; -import { useState } from 'react'; import { + useGetMetric, useRemoveMetricFromCollection, useSaveMetricToCollections } from '@/api/buster_rest/metrics'; @@ -11,38 +10,29 @@ import { SaveToCollectionsDropdown } from '../dropdowns/SaveToCollectionsDropdow import { CollectionButton } from './CollectionsButton'; export const SaveMetricToCollectionButton: React.FC<{ - metricIds: string[]; - selectedCollections: string[]; + metricId: string; buttonType?: 'ghost' | 'default'; useText?: boolean; -}> = ({ - metricIds, - selectedCollections: selectedCollectionsProp, - buttonType = 'ghost', - useText = false -}) => { +}> = ({ metricId, buttonType = 'ghost', useText = false }) => { const { openInfoMessage } = useBusterNotifications(); const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); - - const [selectedCollections, setSelectedCollections] = - useState[0]['selectedCollections']>( - selectedCollectionsProp - ); + const { data: selectedCollections } = useGetMetric( + { id: metricId }, + { select: (x) => x.collections?.map((x) => x.id) } + ); const onSaveToCollection = useMemoizedFn(async (collectionIds: string[]) => { - setSelectedCollections((prev) => uniq([...prev, ...collectionIds])); await saveMetricToCollection({ - metricIds, + metricIds: [metricId], collectionIds }); openInfoMessage('Metrics saved to collections'); }); const onRemoveFromCollection = useMemoizedFn(async (collectionId: string) => { - setSelectedCollections((prev) => prev.filter((x) => x !== collectionId)); await removeMetricFromCollection({ - metricIds, + metricIds: [metricId], collectionIds: [collectionId] }); openInfoMessage('Metrics removed from collections'); @@ -52,7 +42,7 @@ export const SaveMetricToCollectionButton: React.FC<{ + selectedCollections={selectedCollections || []}> ); diff --git a/apps/web/src/components/features/dropdowns/SaveToCollectionsDropdown.tsx b/apps/web/src/components/features/dropdowns/SaveToCollectionsDropdown.tsx index 6afec38f4..b90bbad6a 100644 --- a/apps/web/src/components/features/dropdowns/SaveToCollectionsDropdown.tsx +++ b/apps/web/src/components/features/dropdowns/SaveToCollectionsDropdown.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import type { BusterCollectionListItem } from '@/api/asset_interfaces/collection'; import { useGetCollectionsList } from '@/api/buster_rest/collections'; import { Button } from '@/components/ui/buttons'; @@ -56,6 +56,8 @@ export const useSaveToCollectionsDropdownContent = ({ > & { ModalComponent: React.ReactNode; } => { + const [openCollectionModal, setOpenCollectionModal] = useState(false); + const { data: collectionsList, isPending: isCreatingCollection } = useGetCollectionsList({}); const onChangePage = useAppLayoutContextSelector((s) => s.onChangePage); @@ -75,8 +77,6 @@ export const useSaveToCollectionsDropdownContent = ({ return collectionsItems; }, [collectionsList, selectedCollections]); - const [openCollectionModal, setOpenCollectionModal] = React.useState(false); - const menuHeader = useMemo(() => { return items.length > 0 ? 'Save to a collection' : undefined; }, [items.length]); diff --git a/apps/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx b/apps/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx index b3ef206b2..c7d7aa7b9 100644 --- a/apps/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx +++ b/apps/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useState } from 'react'; -import { ShareAssetType } from '@buster/server-shared/share'; import { useDeleteChat, useRemoveChatFromCollections, diff --git a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx index c3cb0a845..e6abf37a4 100644 --- a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx +++ b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx @@ -145,16 +145,12 @@ const useVersionHistorySelectMenu = ({ dashboardId }: { dashboardId: string }) = const useCollectionSelectMenu = ({ dashboardId }: { dashboardId: string }) => { const { mutateAsync: saveDashboardToCollection } = useAddDashboardToCollection(); const { mutateAsync: removeDashboardFromCollection } = useRemoveDashboardFromCollection(); - const { data: collections } = useGetDashboard( + const { data: selectedCollections } = useGetDashboard( { id: dashboardId }, - { select: (x) => x.collections } + { select: (x) => x.collections?.map((x) => x.id) } ); const { openInfoMessage } = useBusterNotifications(); - const selectedCollections = useMemo(() => { - return collections?.map((x) => x.id) || []; - }, [collections]); - const onSaveToCollection = useMemoizedFn(async (collectionIds: string[]) => { await saveDashboardToCollection({ dashboardIds: [dashboardId], collectionIds }); openInfoMessage('Dashboard saved to collections'); @@ -171,7 +167,7 @@ const useCollectionSelectMenu = ({ dashboardId }: { dashboardId: string }) => { const { ModalComponent, ...dropdownProps } = useSaveToCollectionsDropdownContent({ onSaveToCollection, onRemoveFromCollection, - selectedCollections + selectedCollections: selectedCollections || [] }); const collectionSubMenu = useMemo(() => { diff --git a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx index 4bd342067..17d9247ec 100644 --- a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx +++ b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx @@ -126,14 +126,7 @@ const EditChartButton = React.memo(({ metricId }: { metricId: string }) => { EditChartButton.displayName = 'EditChartButton'; const SaveToCollectionButton = React.memo(({ metricId }: { metricId: string }) => { - const { data: collections } = useGetMetric( - { id: metricId }, - { select: (x) => x.collections?.map((x) => x.id) } - ); - - return ( - - ); + return ; }); SaveToCollectionButton.displayName = 'SaveToCollectionButton'; diff --git a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx index 32114cbfa..38c973552 100644 --- a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx +++ b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx @@ -202,13 +202,12 @@ const useDashboardSelectMenu = ({ metricId }: { metricId: string }) => { const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => { const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); - const { data: collections } = useGetMetric({ id: metricId }, { select: (x) => x.collections }); + const { data: selectedCollections } = useGetMetric( + { id: metricId }, + { select: (x) => x.collections?.map((x) => x.id) } + ); const { openInfoMessage } = useBusterNotifications(); - const selectedCollections = useMemo(() => { - return collections?.map((x) => x.id) || []; - }, [collections]); - const onSaveToCollection = useMemoizedFn(async (collectionIds: string[]) => { await saveMetricToCollection({ metricIds: [metricId], @@ -228,7 +227,7 @@ const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => { const { ModalComponent, ...dropdownProps } = useSaveToCollectionsDropdownContent({ onSaveToCollection, onRemoveFromCollection, - selectedCollections + selectedCollections: selectedCollections || [] }); const CollectionSubMenu = useMemo(() => {