Skip to content

Commit

Permalink
delete bulk cost surfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
anamontiaga committed Aug 31, 2023
1 parent 3950c58 commit 1b032b0
Show file tree
Hide file tree
Showing 9 changed files with 1,060 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const InventoryTable = ({
<th
key={column.name}
className={cn({
'flex-1 border pl-2': true,
'flex-1 pl-2': true,
[column.className]: !!column.className,
})}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NavigationInventoryTabs } from 'layout/project/navigation/types';

import CostSurfaceTable from './cost-surface';
import CostSurfaceInfo from './cost-surface/info';
import CostSurfaceTable from './cost-surfaces';
import CostSurfaceInfo from './cost-surfaces/info';
import FeaturesTable from './features';
import FeaturesInfo from './features/info';
import FeatureUploadModal from './features/modals/upload';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useCallback, useState } from 'react';

import Button from 'components/button';
import Icon from 'components/icon';
import Modal from 'components/modal/component';
import DeleteModal from 'layout/project/sidebar/project/inventory-panel/cost-surfaces/modals/delete/index';
import { CostSurface } from 'types/api/cost-surface';

import EDIT_SVG from 'svgs/ui/edit.svg?sprite';
import DELETE_SVG from 'svgs/ui/new-layout/delete.svg?sprite';

const BUTTON_CLASSES =
'col-span-1 flex items-center space-x-2 rounded-lg bg-gray-700 px-4 text-xs text-gray-50';
const ICON_CLASSES = 'h-5 w-5 transition-colors text-gray-400 group-hover:text-gray-50';

const CostSurfaceBulkActionMenu = ({
selectedCostSurfacesIds,
}: {
selectedCostSurfacesIds: CostSurface['id'][];
}): JSX.Element => {
const [modalState, setModalState] = useState<{ edit: boolean; delete: boolean }>({
edit: false,
delete: false,
});

const handleModal = useCallback((modalKey: keyof typeof modalState, isVisible: boolean) => {
setModalState((prevState) => ({ ...prevState, [modalKey]: isVisible }));
}, []);

return (
<>
<div className="grid w-full grid-cols-2 items-center space-x-2 rounded-xl bg-gray-500 p-1">
<span className="col-span-1 flex items-center justify-center space-x-2">
<span className="block w-[20px] rounded-[4px] bg-blue-400/25 px-1 text-center text-xs font-semibold text-blue-400">
{selectedCostSurfacesIds.length}
</span>
<span className="text-xs text-gray-50">Selected</span>
</span>

<Button
theme="secondary"
size="base"
className={BUTTON_CLASSES}
onClick={() => handleModal('delete', true)}
>
<Icon icon={DELETE_SVG} className={ICON_CLASSES} />
<span>Delete</span>
</Button>
</div>

<Modal
id="delete-cost-surfaces-modal"
open={modalState.delete}
size="narrow"
dismissable
onDismiss={() => handleModal('delete', false)}
>
<DeleteModal selectedCostSurfacesIds={selectedCostSurfacesIds} />
</Modal>
</>
);
};

export default CostSurfaceBulkActionMenu;
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Session } from 'next-auth';

import { Feature } from 'types/api/feature';
import { Project } from 'types/api/project';

import PROJECTS from 'services/projects';

export function bulkDeleteFeatureFromProject(
pid: Project['id'],
fids: Feature['id'][],
session: Session
) {
const deleteFeatureFromProject = ({ pid, fid }: { pid: Project['id']; fid: Feature['id'] }) => {
return PROJECTS.delete(`/${pid}/features/${fid}`, {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
};

return Promise.all(fids.map((fid) => deleteFeatureFromProject({ pid, fid })));
}

export function editFeaturesTagsBulk(
projectId: Project['id'],
featureIds: Feature['id'][],
session: Session,
data: {
tagName: string;
}
) {
const editFeatureTag = ({
featureId,
projectId,
data,
}: {
featureId: Feature['id'];
projectId: Project['id'];
data: {
tagName: string;
};
}) => {
return PROJECTS.request({
method: 'PATCH',
url: `/${projectId}/features/${featureId}/tags`,
data,
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
};
return Promise.all(featureIds.map((featureId) => editFeatureTag({ projectId, featureId, data })));
}

export function deleteFeaturesTagsBulk(
projectId: Project['id'],
featureIds: Feature['id'][],
session: Session
) {
const deleteFeatureTags = ({
projectId,
featureId,
}: {
projectId: Project['id'];
featureId: Feature['id'];
}) => {
return PROJECTS.delete(`/${projectId}/features/${featureId}/tags`, {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
};

return Promise.all(featureIds.map((featureId) => deleteFeatureTags({ projectId, featureId })));
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import { setSelectedCostSurfaces as setVisibleCostSurfaces } from 'store/slices/

import { useProjectCostSurfaces } from 'hooks/cost-surface';

import CostSurfacesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/cost-surfaces/bulk-action-menu';
import ActionsMenu from 'layout/project/sidebar/project/inventory-panel/features/actions-menu';
import FeaturesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu';
import { CostSurface } from 'types/api/cost-surface';

import InventoryTable, { type DataItem } from '../components/inventory-table';

const COST_SURFACE_TABLE_COLUMNS = {
name: 'Name',
};
const COST_SURFACE_TABLE_COLUMNS = [
{
name: 'name',
text: 'Name',
},
];

const InventoryPanelCostSurface = ({ noData: noDataMessage }: { noData: string }): JSX.Element => {
const dispatch = useAppDispatch();
Expand All @@ -28,7 +31,7 @@ const InventoryPanelCostSurface = ({ noData: noDataMessage }: { noData: string }

const [selectedCostSurfaceIds, setSelectedCostSurfaceIds] = useState<CostSurface['id'][]>([]);
const [filters, setFilters] = useState<Parameters<typeof useProjectCostSurfaces>[1]>({
sort: COST_SURFACE_TABLE_COLUMNS.name,
sort: COST_SURFACE_TABLE_COLUMNS[0].name,
});

const allProjectCostSurfacesQuery = useProjectCostSurfaces(
Expand Down Expand Up @@ -126,7 +129,7 @@ const InventoryPanelCostSurface = ({ noData: noDataMessage }: { noData: string }
ActionsComponent={ActionsMenu}
/>
{displayBulkActions && (
<FeaturesBulkActionMenu selectedFeaturesIds={selectedCostSurfaceIds} />
<CostSurfacesBulkActionMenu selectedCostSurfacesIds={selectedCostSurfaceIds} />
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { useCallback, useMemo } from 'react';

import { useQueryClient } from 'react-query';

import { useRouter } from 'next/router';

import { useSession } from 'next-auth/react';

import { useProjectCostSurfaces } from 'hooks/cost-surface';
import { useToasts } from 'hooks/toast';

import { Button } from 'components/button/component';
import Icon from 'components/icon/component';
import { ModalProps } from 'components/modal';
import { bulkDeleteFeatureFromProject } from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu/utils';
import { CostSurface } from 'types/api/cost-surface';
import { Pagination } from 'types/api/meta';

import ALERT_SVG from 'svgs/ui/new-layout/alert.svg?sprite';

const DeleteModal = ({
selectedCostSurfacesIds,
onDismiss,
}: {
selectedCostSurfacesIds: CostSurface['id'][];
onDismiss?: ModalProps['onDismiss'];
}): JSX.Element => {
const { data: session } = useSession();
const queryClient = useQueryClient();
const { query } = useRouter();
const { pid } = query as { pid: string };
const { addToast } = useToasts();

const allProjectCostSurfacesQuery = useProjectCostSurfaces(pid, {});

const selectedCostSurfaces = useMemo(() => {
return allProjectCostSurfacesQuery.data?.filter(({ id }) =>
selectedCostSurfacesIds.includes(id)
);
}, [allProjectCostSurfacesQuery.data, selectedCostSurfacesIds]);

const costSurfaceNames = selectedCostSurfaces.map(({ name }) => name);
// ? the user will be able to delete the features only if they are not being used by any scenario.
const haveScenarioAssociated = selectedCostSurfaces.some(({ scenarioUsageCount }) =>
Boolean(scenarioUsageCount)
);

const handleBulkDelete = useCallback(() => {
const deletableFeatureIds = selectedCostSurfaces.map(({ id }) => id);

bulkDeleteFeatureFromProject(pid, deletableFeatureIds, session)
.then(async () => {
await queryClient.invalidateQueries(['cost-surfaces', pid]);

onDismiss();

addToast(
'delete-bulk-project-cost-surfaces',
<>
<h2 className="font-medium">Success</h2>
<p className="text-sm">The features were deleted successfully.</p>
</>,
{
level: 'success',
}
);
})
.catch(() => {
addToast(
'delete-bulk-project-cost-surfaces',
<>
<h2 className="font-medium">Error!</h2>
<p className="text-sm">Something went wrong deleting the cost surfaces.</p>
</>,
{
level: 'error',
}
);
});
}, [selectedCostSurfaces, addToast, onDismiss, pid, queryClient, session]);

return (
<div className="flex flex-col space-y-5 px-8 py-1">
<h2 className="font-heading font-bold text-black">{`Delete cost surface${
selectedCostSurfacesIds.length > 1 ? 's' : ''
}`}</h2>
<p className="font-heading text-sm font-medium text-black">
{selectedCostSurfacesIds.length > 1 ? (
<div className="space-y-2">
<span>
Are you sure you want to delete the following cost surfaces? <br />
This action cannot be undone.
</span>
<ul>
{costSurfaceNames.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</div>
) : (
<span>
Are you sure you want to delete &quot;{costSurfaceNames[0]}&quot; cost surface? <br />
This action cannot be undone.
</span>
)}
</p>
<div className="flex items-center space-x-1.5 rounded border-l-[5px] border-red-600 bg-red-50/50 px-1.5 py-4">
<Icon className="h-10 w-10 text-red-600" icon={ALERT_SVG} />
<p className="font-sans text-xs font-medium text-black">
A cost surface can be deleted ONLY if it&apos;s not being used by any scenario
</p>
</div>
<div className="flex w-full justify-between space-x-3 px-10 py-2">
<Button theme="secondary" size="lg" className="w-full" onClick={onDismiss}>
Cancel
</Button>
<Button
theme="danger-alt"
size="lg"
className="w-full"
disabled={haveScenarioAssociated}
onClick={handleBulkDelete}
>
Delete
</Button>
</div>
</div>
);
};

export default DeleteModal;
Loading

0 comments on commit 1b032b0

Please sign in to comment.