Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/quick actions #1573

Merged
merged 17 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cf888b2
feat(quick-actions): create quick actions on asset index
rockingrohit9639 Jan 13, 2025
39c4d58
feat(quick-actions): make delete asset work on asset index page
rockingrohit9639 Jan 14, 2025
162e6e4
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/quic…
rockingrohit9639 Jan 14, 2025
534d4e2
feat(quick-actions): add QR code preview in quick actions
rockingrohit9639 Jan 14, 2025
055c48b
Merge branch 'main' into feature/quick-actions
DonKoko Jan 14, 2025
9d6baee
Merge branch 'main' into feature/quick-actions
DonKoko Jan 15, 2025
4a7c571
feat(asset-reminders): change position of quick actions in simple mode
rockingrohit9639 Jan 15, 2025
002c534
feat(quick-actions): add quick actions in advanced asset list
rockingrohit9639 Jan 16, 2025
1b2c9d7
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/quic…
rockingrohit9639 Jan 16, 2025
479734a
feat(quick-actions): hide actions column from filters
rockingrohit9639 Jan 16, 2025
905d46d
feat(quick-actions): fix qr and delete popover not working
rockingrohit9639 Jan 16, 2025
ac7ac6e
Merge branch 'main' into feature/quick-actions
DonKoko Jan 16, 2025
b52d2d9
feat(quick-actions): fix dom validation issues and making tooltip work
rockingrohit9639 Jan 16, 2025
0906550
feat(quick-actions): fix dom issue on advanced index
rockingrohit9639 Jan 16, 2025
5bb109c
Merge branch 'main' into feature/quick-actions
rockingrohit9639 Jan 17, 2025
5223433
Merge branch 'main' into feature/quick-actions
DonKoko Jan 17, 2025
2006982
styling updates
DonKoko Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion app/components/assets/actions-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,20 @@ const ConditionalActionsDropdown = () => {
}}
disabled={assetIsCheckedOut || assetIsPartOfUnavailableKit}
>
<DeleteAsset asset={asset} />
<DeleteAsset
asset={asset}
trigger={
<Button
variant="link"
data-test-id="deleteAssetButton"
icon="trash"
className="justify-start rounded-sm px-4 py-3 text-sm font-semibold text-gray-700 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-slate-100 hover:text-gray-700"
width="full"
>
Delete
</Button>
}
/>
</DropdownMenuItem>
<DropdownMenuItem className="border-t p-4 md:hidden md:p-0">
<Button
Expand Down
94 changes: 94 additions & 0 deletions app/components/assets/assets-index/asset-quick-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { CopyIcon, PencilIcon, QrCodeIcon, Trash2Icon } from "lucide-react";
import { Button } from "~/components/shared/button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/shared/tooltip";
import type { AssetsFromViewItem } from "~/modules/asset/types";
import { tw } from "~/utils/tw";
import { DeleteAsset } from "../delete-asset";
import QrPreviewDialog from "../qr-preview-dialog";

type AssetQuickActionsProps = {
className?: string;
style?: React.CSSProperties;
asset: AssetsFromViewItem;
};

export default function AssetQuickActions({
className,
style,
asset,
}: AssetQuickActionsProps) {
return (
<div className={tw("flex items-center", className)} style={style}>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="block-link-gray"
to={`/assets/${asset.id}/edit`}
>
<PencilIcon className="size-4" />
</Button>
</TooltipTrigger>

<TooltipContent align="center" side="top">
Edit asset information
</TooltipContent>
</Tooltip>

<Tooltip>
<TooltipTrigger asChild>
<QrPreviewDialog
asset={{
id: asset.id,
title: asset.title,
qrId: asset.qrCodes[0].id,
}}
trigger={
<Button size="sm" variant="block-link-gray">
<QrCodeIcon className="size-4" />
</Button>
}
/>
</TooltipTrigger>

<TooltipContent>Show asset label</TooltipContent>
</Tooltip>

<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="block-link-gray"
to={`/assets/${asset.id}/overview/duplicate`}
>
<CopyIcon className="size-4" />
</Button>
</TooltipTrigger>

<TooltipContent align="center" side="top">
Duplicate asset
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<DeleteAsset
asset={asset}
trigger={
<Button size="sm" variant="block-link-gray">
<Trash2Icon className="size-4" />
</Button>
}
/>
</TooltipTrigger>

<TooltipContent align="center" side="top">
Delete asset
</TooltipContent>
</Tooltip>
</div>
);
}
110 changes: 57 additions & 53 deletions app/components/assets/delete-asset.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { cloneElement } from "react";
import type { Asset } from "@prisma/client";
import { useNavigation } from "@remix-run/react";
import { Button } from "~/components/shared/button";

import {
Expand All @@ -11,68 +13,70 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from "~/components/shared/modal";
import { isFormProcessing } from "~/utils/form";
import { Form } from "../custom-form";
import { TrashIcon } from "../icons/library";

export const DeleteAsset = ({
asset,
trigger,
}: {
asset: {
id: Asset["id"];
title: Asset["title"];
mainImage: Asset["mainImage"];
};
}) => (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="link"
data-test-id="deleteAssetButton"
icon="trash"
className="justify-start rounded-sm px-4 py-3 text-sm font-semibold text-gray-700 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-slate-100 hover:text-gray-700"
width="full"
>
Delete
</Button>
</AlertDialogTrigger>
trigger: React.ReactElement;
}) => {
const navigation = useNavigation();
const disabled = isFormProcessing(navigation.state);

<AlertDialogContent>
<AlertDialogHeader>
<div className="mx-auto md:m-0">
<span className="flex size-12 items-center justify-center rounded-full bg-error-50 p-2 text-error-600">
<TrashIcon />
</span>
</div>
<AlertDialogTitle>Delete {asset.title}</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this asset? This action cannot be
undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<div className="flex justify-center gap-2">
<AlertDialogCancel asChild>
<Button variant="secondary">Cancel</Button>
</AlertDialogCancel>
return (
<AlertDialog>
<AlertDialogTrigger asChild>{cloneElement(trigger)}</AlertDialogTrigger>

<Form method="delete">
{asset.mainImage && (
<input
type="hidden"
value={asset.mainImage}
name="mainImageUrl"
/>
)}
<input type="hidden" value="delete" name="intent" />
<Button
className="border-error-600 bg-error-600 hover:border-error-800 hover:bg-error-800"
type="submit"
data-test-id="confirmdeleteAssetButton"
>
Delete
</Button>
</Form>
</div>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
<AlertDialogContent>
<AlertDialogHeader>
<div className="mx-auto md:m-0">
<span className="flex size-12 items-center justify-center rounded-full bg-error-50 p-2 text-error-600">
<TrashIcon />
</span>
</div>
<AlertDialogTitle>Delete {asset.title}</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this asset? This action cannot be
undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<div className="flex justify-center gap-2">
<AlertDialogCancel asChild>
<Button variant="secondary" disabled={disabled}>
Cancel
</Button>
</AlertDialogCancel>

<Form method="delete" action={`/assets/${asset.id}`}>
{asset.mainImage && (
<input
type="hidden"
value={asset.mainImage}
name="mainImageUrl"
/>
)}
<input type="hidden" value="delete" name="intent" />
<Button
className="border-error-600 bg-error-600 hover:border-error-800 hover:!bg-error-800"
type="submit"
data-test-id="confirmdeleteAssetButton"
disabled={disabled}
>
Delete
</Button>
</Form>
</div>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
4 changes: 4 additions & 0 deletions app/modules/asset/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ export const assetIndexFields = ({
},
},
},
qrCodes: {
select: { id: true },
take: 1,
},
} satisfies Prisma.AssetInclude;

// Conditionally add bookings if date range is provided
Expand Down
29 changes: 12 additions & 17 deletions app/routes/_layout+/assets._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import type {
} from "@remix-run/node";
import { json } from "@remix-run/node";
import type { ShouldRevalidateFunctionArgs } from "@remix-run/react";
import {
useFetcher,
useFetchers,
useLoaderData,
useNavigate,
} from "@remix-run/react";
import { Link, useFetcher, useFetchers, useLoaderData } from "@remix-run/react";
import { motion } from "framer-motion";
import { z } from "zod";
import { AssetImage } from "~/components/assets/asset-image";
Expand All @@ -22,6 +17,7 @@ import { AdvancedAssetRow } from "~/components/assets/assets-index/advanced-asse
import { AdvancedTableHeader } from "~/components/assets/assets-index/advanced-table-header";
import { AssetIndexPagination } from "~/components/assets/assets-index/asset-index-pagination";
// eslint-disable-next-line import/no-cycle
import AssetQuickActions from "~/components/assets/assets-index/asset-quick-actions";
import { AssetIndexFilters } from "~/components/assets/assets-index/filters";
import BulkActionsDropdown from "~/components/assets/bulk-actions-dropdown";
import { ImportButton } from "~/components/assets/import-button";
Expand Down Expand Up @@ -284,7 +280,6 @@ export const AssetsList = ({
disableTeamMemberFilter?: boolean;
disableBulkActions?: boolean;
}) => {
const navigate = useNavigate();
// We use the hook because it handles optimistic UI
const { modeIsSimple } = useAssetIndexViewState();
const { isMd } = useViewportHeight();
Expand All @@ -309,6 +304,7 @@ export const AssetsList = ({
<>
<Th>Category</Th>
<Th>Tags</Th>
<Th>Actions</Th>
<When
truthy={userHasPermission({
roles,
Expand Down Expand Up @@ -355,14 +351,6 @@ export const AssetsList = ({
title="Assets"
ItemComponent={modeIsSimple ? ListAssetContent : AdvancedAssetRow}
customPagination={<AssetIndexPagination />}
/**
* Using remix's navigate is the default behaviour, however it can also receive a custom function
*/
navigate={
modeIsSimple
? (itemId) => navigate(`/assets/${itemId}`)
: undefined
}
bulkActions={
disableBulkActions || isBase ? undefined : <BulkActionsDropdown />
}
Expand All @@ -384,7 +372,10 @@ const ListAssetContent = ({ item }: { item: AssetsFromViewItem }) => {
<>
{/* Item */}
<Td className="w-full whitespace-normal p-0 md:p-0">
<div className="flex justify-between gap-3 p-4 md:justify-normal md:px-6">
<Link
className="flex justify-between gap-3 p-4 md:justify-normal md:px-6"
to={`/assets/${item.id}`}
>
<div className="flex items-center gap-3">
<div className="relative flex size-12 shrink-0 items-center justify-center">
<AssetImage
Expand Down Expand Up @@ -425,7 +416,7 @@ const ListAssetContent = ({ item }: { item: AssetsFromViewItem }) => {
</div>
</div>
</div>
</div>
</Link>
</Td>

{/* Category */}
Expand All @@ -446,6 +437,10 @@ const ListAssetContent = ({ item }: { item: AssetsFromViewItem }) => {
<ListItemTagsColumn tags={tags} />
</Td>

<Td>
<AssetQuickActions asset={item} />
</Td>

{/* Custodian */}
<When
truthy={userHasPermission({
Expand Down