From 4e5ac6f752e927f486ff2a5ce56a0f788018eb7b Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Wed, 9 Apr 2025 14:46:45 -0600 Subject: [PATCH] =?UTF-8?q?add=20and=20remove=20from=20collection=20works?= =?UTF-8?q?=20=F0=9F=93=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/buster_rest/collections/requests.ts | 13 ++-- .../features/modal/AddToCollectionModal.tsx | 76 ++++++++++++++++--- .../features/modal/AddToDashboardModal.tsx | 1 - .../components/ui/modal/InputSelectModal.tsx | 10 ++- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/web/src/api/buster_rest/collections/requests.ts b/web/src/api/buster_rest/collections/requests.ts index 9bb0da619..b907a76f9 100644 --- a/web/src/api/buster_rest/collections/requests.ts +++ b/web/src/api/buster_rest/collections/requests.ts @@ -106,7 +106,7 @@ export const updateCollectionShare = async ({ export const addAssetToCollection = async ({ id, - ...params + assets }: { id: string; assets: { @@ -114,13 +114,14 @@ export const addAssetToCollection = async ({ id: string; }[]; }) => { - return mainApi.post(`/collections/${id}/assets`, params).then((res) => res.data); + return mainApi.post(`/collections/${id}/assets`, { assets }).then((res) => res.data); }; -export const removeAssetFromCollection = async ( - params: Parameters[0] -) => { +export const removeAssetFromCollection = async ({ + id, + assets +}: Parameters[0]) => { return mainApi - .delete(`/collections/${params.id}/assets`, { data: params }) + .delete(`/collections/${id}/assets`, { data: { assets } }) .then((res) => res.data); }; diff --git a/web/src/components/features/modal/AddToCollectionModal.tsx b/web/src/components/features/modal/AddToCollectionModal.tsx index f45ba6ef5..45be7c758 100644 --- a/web/src/components/features/modal/AddToCollectionModal.tsx +++ b/web/src/components/features/modal/AddToCollectionModal.tsx @@ -1,19 +1,15 @@ -import { useGetMetricsList } from '@/api/buster_rest/metrics'; import { useDebounce, useMemoizedFn } from '@/hooks'; import React, { useLayoutEffect, useMemo, useState } from 'react'; import { InputSelectModal, InputSelectModalProps } from '@/components/ui/modal/InputSelectModal'; import { formatDate } from '@/lib'; import { Button } from '@/components/ui/buttons'; -import { useGetDashboardsList } from '@/api/buster_rest/dashboards'; import { useAddAndRemoveAssetsFromCollection, useGetCollection } from '@/api/buster_rest/collections'; import { Text } from '@/components/ui/typography'; import { ASSET_ICONS } from '../config/assetIcons'; -import pluralize from 'pluralize'; import { useSearch } from '@/api/buster_rest/search'; -import { ShareAssetType } from '@/api/asset_interfaces/share'; export const AddToCollectionModal: React.FC<{ open: boolean; @@ -103,6 +99,61 @@ export const AddToCollectionModal: React.FC<{ return originalIds.length !== newIds.length || originalIds.some((id) => !newIds.includes(id)); }, [originalIds, selectedAssets]); + const removedAssetCount = useMemo(() => { + return originalIds.filter((id) => !selectedAssets.includes(id)).length; + }, [originalIds, selectedAssets]); + + const addedAssetCount = useMemo(() => { + return selectedAssets.filter((id) => !originalIds.includes(id)).length; + }, [originalIds, selectedAssets]); + + const primaryButtonText = useMemo(() => { + if (!isFetchedCollection) { + return 'Loading assets...'; + } + + const hasRemovedItems = removedAssetCount > 0; + const hasAddedItems = addedAssetCount > 0; + + if (hasRemovedItems && hasAddedItems) { + return `Update collection`; + } + + if (hasRemovedItems) { + return `Remove assets`; + } + + if (hasAddedItems) { + return `Add assets`; + } + + return `Update collection`; + }, [isFetchedCollection, removedAssetCount, addedAssetCount]); + + const primaryButtonTooltipText = useMemo(() => { + if (!isFetchedCollection) { + return ''; + } + + const hasRemovedItems = removedAssetCount > 0; + const hasAddedItems = addedAssetCount > 0; + const returnText: string[] = []; + + if (!hasRemovedItems && !hasAddedItems) { + return 'No changes to update'; + } + + if (hasRemovedItems) { + returnText.push(`Removing ${removedAssetCount}`); + } + + if (hasAddedItems) { + returnText.push(`Adding ${addedAssetCount}`); + } + + return returnText.join(', '); + }, [isFetchedCollection, addedAssetCount, removedAssetCount]); + const emptyState = useMemo(() => { if (rows.length === 0) { return 'No assets found'; @@ -123,18 +174,19 @@ export const AddToCollectionModal: React.FC<{ onClick: onClose }, primaryButton: { - text: - selectedAssets.length === 0 - ? 'Update collection' - : `Add ${selectedAssets.length} ${pluralize('asset', selectedAssets.length)} to collection`, + text: primaryButtonText, onClick: handleAddAndRemoveMetrics, disabled: !isSelectedChanged, - tooltip: isSelectedChanged - ? `Adding ${selectedAssets.length} assets` - : 'No changes to update' + tooltip: primaryButtonTooltipText } }; - }, [selectedAssets.length, isSelectedChanged, handleAddAndRemoveMetrics]); + }, [ + selectedAssets.length, + isSelectedChanged, + handleAddAndRemoveMetrics, + primaryButtonText, + primaryButtonTooltipText + ]); useLayoutEffect(() => { if (isFetchedCollection) { diff --git a/web/src/components/features/modal/AddToDashboardModal.tsx b/web/src/components/features/modal/AddToDashboardModal.tsx index ab259e506..e10b1820e 100644 --- a/web/src/components/features/modal/AddToDashboardModal.tsx +++ b/web/src/components/features/modal/AddToDashboardModal.tsx @@ -189,7 +189,6 @@ export const AddToDashboardModal: React.FC<{ emptyState={emptyState} searchText={searchTerm} handleSearchChange={setSearchTerm} - className="data-[state=closed]:slide-out-to-top-[5%]! data-[state=open]:slide-in-from-top-[5%]! top-28 translate-y-0" /> ); }); diff --git a/web/src/components/ui/modal/InputSelectModal.tsx b/web/src/components/ui/modal/InputSelectModal.tsx index 3e7ea0938..bf7ca929e 100644 --- a/web/src/components/ui/modal/InputSelectModal.tsx +++ b/web/src/components/ui/modal/InputSelectModal.tsx @@ -6,6 +6,7 @@ import { useDebounceSearch } from '@/hooks'; import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog'; import { Text } from '../typography'; +import { cn } from '@/lib/classMerge'; export interface InputSelectModalProps extends Omit { inputPlaceholder?: string; @@ -25,6 +26,7 @@ export const InputSelectModal = React.memo( columns, rows, emptyState, + className, onSelectChange, selectedRowKeys, searchText, @@ -51,7 +53,13 @@ export const InputSelectModal = React.memo( }, [searchText, handleSearchChange, inputPlaceholder, rows.length]); return ( - +