add and remove from collection works 📖

This commit is contained in:
Nate Kelley 2025-04-09 14:46:45 -06:00
parent 77d9203383
commit 4e5ac6f752
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 80 additions and 20 deletions

View File

@ -106,7 +106,7 @@ export const updateCollectionShare = async ({
export const addAssetToCollection = async ({ export const addAssetToCollection = async ({
id, id,
...params assets
}: { }: {
id: string; id: string;
assets: { assets: {
@ -114,13 +114,14 @@ export const addAssetToCollection = async ({
id: string; id: string;
}[]; }[];
}) => { }) => {
return mainApi.post<null>(`/collections/${id}/assets`, params).then((res) => res.data); return mainApi.post<null>(`/collections/${id}/assets`, { assets }).then((res) => res.data);
}; };
export const removeAssetFromCollection = async ( export const removeAssetFromCollection = async ({
params: Parameters<typeof addAssetToCollection>[0] id,
) => { assets
}: Parameters<typeof addAssetToCollection>[0]) => {
return mainApi return mainApi
.delete<null>(`/collections/${params.id}/assets`, { data: params }) .delete<null>(`/collections/${id}/assets`, { data: { assets } })
.then((res) => res.data); .then((res) => res.data);
}; };

View File

@ -1,19 +1,15 @@
import { useGetMetricsList } from '@/api/buster_rest/metrics';
import { useDebounce, useMemoizedFn } from '@/hooks'; import { useDebounce, useMemoizedFn } from '@/hooks';
import React, { useLayoutEffect, useMemo, useState } from 'react'; import React, { useLayoutEffect, useMemo, useState } from 'react';
import { InputSelectModal, InputSelectModalProps } from '@/components/ui/modal/InputSelectModal'; import { InputSelectModal, InputSelectModalProps } from '@/components/ui/modal/InputSelectModal';
import { formatDate } from '@/lib'; import { formatDate } from '@/lib';
import { Button } from '@/components/ui/buttons'; import { Button } from '@/components/ui/buttons';
import { useGetDashboardsList } from '@/api/buster_rest/dashboards';
import { import {
useAddAndRemoveAssetsFromCollection, useAddAndRemoveAssetsFromCollection,
useGetCollection useGetCollection
} from '@/api/buster_rest/collections'; } from '@/api/buster_rest/collections';
import { Text } from '@/components/ui/typography'; import { Text } from '@/components/ui/typography';
import { ASSET_ICONS } from '../config/assetIcons'; import { ASSET_ICONS } from '../config/assetIcons';
import pluralize from 'pluralize';
import { useSearch } from '@/api/buster_rest/search'; import { useSearch } from '@/api/buster_rest/search';
import { ShareAssetType } from '@/api/asset_interfaces/share';
export const AddToCollectionModal: React.FC<{ export const AddToCollectionModal: React.FC<{
open: boolean; open: boolean;
@ -103,6 +99,61 @@ export const AddToCollectionModal: React.FC<{
return originalIds.length !== newIds.length || originalIds.some((id) => !newIds.includes(id)); return originalIds.length !== newIds.length || originalIds.some((id) => !newIds.includes(id));
}, [originalIds, selectedAssets]); }, [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(() => { const emptyState = useMemo(() => {
if (rows.length === 0) { if (rows.length === 0) {
return 'No assets found'; return 'No assets found';
@ -123,18 +174,19 @@ export const AddToCollectionModal: React.FC<{
onClick: onClose onClick: onClose
}, },
primaryButton: { primaryButton: {
text: text: primaryButtonText,
selectedAssets.length === 0
? 'Update collection'
: `Add ${selectedAssets.length} ${pluralize('asset', selectedAssets.length)} to collection`,
onClick: handleAddAndRemoveMetrics, onClick: handleAddAndRemoveMetrics,
disabled: !isSelectedChanged, disabled: !isSelectedChanged,
tooltip: isSelectedChanged tooltip: primaryButtonTooltipText
? `Adding ${selectedAssets.length} assets`
: 'No changes to update'
} }
}; };
}, [selectedAssets.length, isSelectedChanged, handleAddAndRemoveMetrics]); }, [
selectedAssets.length,
isSelectedChanged,
handleAddAndRemoveMetrics,
primaryButtonText,
primaryButtonTooltipText
]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (isFetchedCollection) { if (isFetchedCollection) {

View File

@ -189,7 +189,6 @@ export const AddToDashboardModal: React.FC<{
emptyState={emptyState} emptyState={emptyState}
searchText={searchTerm} searchText={searchTerm}
handleSearchChange={setSearchTerm} handleSearchChange={setSearchTerm}
className="data-[state=closed]:slide-out-to-top-[5%]! data-[state=open]:slide-in-from-top-[5%]! top-28 translate-y-0"
/> />
); );
}); });

View File

@ -6,6 +6,7 @@ import { useDebounceSearch } from '@/hooks';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog'; import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog';
import { Text } from '../typography'; import { Text } from '../typography';
import { cn } from '@/lib/classMerge';
export interface InputSelectModalProps extends Omit<BorderedModalProps, 'children'> { export interface InputSelectModalProps extends Omit<BorderedModalProps, 'children'> {
inputPlaceholder?: string; inputPlaceholder?: string;
@ -25,6 +26,7 @@ export const InputSelectModal = React.memo(
columns, columns,
rows, rows,
emptyState, emptyState,
className,
onSelectChange, onSelectChange,
selectedRowKeys, selectedRowKeys,
searchText, searchText,
@ -51,7 +53,13 @@ export const InputSelectModal = React.memo(
}, [searchText, handleSearchChange, inputPlaceholder, rows.length]); }, [searchText, handleSearchChange, inputPlaceholder, rows.length]);
return ( return (
<BorderedModal header={memoizedHeader} {...props}> <BorderedModal
header={memoizedHeader}
className={cn(
'data-[state=closed]:slide-out-to-top-[5%]! data-[state=open]:slide-in-from-top-[5%]! top-28 translate-y-0',
className
)}
{...props}>
<div <div
className="max-h-[65vh]" className="max-h-[65vh]"
style={{ style={{