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 all 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
10 changes: 9 additions & 1 deletion app/components/assets/assets-index/advanced-asset-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ import { resolveTeamMemberName } from "~/utils/user";
import { freezeColumnClassNames } from "./freeze-column-classes";
import { AssetImage } from "../asset-image";
import { AssetStatusBadge } from "../asset-status-badge";
import QrPreviewDialog from "../qr-preview-dialog";
import { QrPreviewDialog } from "../qr-preview-dialog";
import AssetQuickActions from "./asset-quick-actions";

export function AdvancedIndexColumn({
column,
Expand Down Expand Up @@ -240,6 +241,13 @@ export function AdvancedIndexColumn({

case "availableToBook":
return <TextColumn value={item.availableToBook ? "Yes" : "No"} />;

case "actions":
return (
<Td>
<AssetQuickActions asset={item} />
</Td>
);
}
}

Expand Down
15 changes: 14 additions & 1 deletion app/components/assets/assets-index/advanced-filters/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { CustomField } from "@prisma/client";
import type { SerializeFrom } from "@remix-run/node";
import { useSearchParams } from "~/hooks/search-params";
import type { Column } from "~/modules/asset-index-settings/helpers";
import type {
Column,
ColumnLabelKey,
} from "~/modules/asset-index-settings/helpers";
import type { Filter, FilterFieldType, FilterOperator } from "./schema";
import type { Sort } from "../advanced-asset-index-filters-and-sorting";

Expand Down Expand Up @@ -165,6 +168,8 @@ export function getDefaultValueForFieldType(
}
}

export const COLUMNS_WITHOUT_FILTER: ColumnLabelKey[] = ["actions"];

/**
* Determines what columns are available based on already used columns and operation type
* @param columns - All available columns
Expand Down Expand Up @@ -194,6 +199,14 @@ export function getAvailableColumns(
// Common exclusions for both operations
if (!column.visible) return false;

/**
* Some columns like `actions` does not support filtering,
* so we have to skip them to be availableColumns.
*/
if (COLUMNS_WITHOUT_FILTER.includes(column.name)) {
return false;
}

if (operation === "sort") {
// Columns that can't be sorted
const unsortableColumns = ["tags"];
Expand Down
39 changes: 22 additions & 17 deletions app/components/assets/assets-index/asset-index-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,23 +160,28 @@ export const SwitchToAdvancedMode = ({
</span>
</div>
<AlertDialogTitle>Advanced Mode (Beta)</AlertDialogTitle>
<AlertDialogDescription className="space-y-2 text-center text-gray-500 md:text-left">
<p>
You are about to switch to Advanced Mode. This feature is
currently in beta and includes:
</p>
<ul className="list-inside list-disc text-left">
<li>Advanced filtering capabilities</li>
<li>Custom column management</li>
<li>Enhanced sorting options</li>
<li>Custom field support</li>
</ul>
<p>
While we've thoroughly tested this feature, you may encounter
occasional issues. Your feedback helps us improve! If you face any
issues, please report them to our support team so we can resolve
them asap.
</p>
<AlertDialogDescription
className="space-y-2 text-center text-gray-500 md:text-left"
asChild
>
<div>
<p>
You are about to switch to Advanced Mode. This feature is
currently in beta and includes:
</p>
<ul className="list-inside list-disc text-left">
<li>Advanced filtering capabilities</li>
<li>Custom column management</li>
<li>Enhanced sorting options</li>
<li>Custom field support</li>
</ul>
<p>
While we've thoroughly tested this feature, you may encounter
occasional issues. Your feedback helps us improve! If you face
any issues, please report them to our support team so we can
resolve them asap.
</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
Expand Down
100 changes: 100 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,100 @@
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: Pick<AssetsFromViewItem, "id" | "title" | "mainImage"> & {
qrId: string;
};
};

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

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

<Tooltip>
<QrPreviewDialog
asset={{
id: asset.id,
title: asset.title,
qrId: asset.qrId,
}}
trigger={
<TooltipTrigger asChild>
<Button size="sm" variant="secondary" className={"p-2"}>
<QrCodeIcon className="size-4" />
</Button>
</TooltipTrigger>
}
/>

<TooltipContent align="center" side="top">
Show asset label
</TooltipContent>
</Tooltip>

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

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

<TooltipContent align="center" side="top">
Delete asset
</TooltipContent>
</Tooltip>
</div>
);
}
119 changes: 64 additions & 55 deletions app/components/assets/delete-asset.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { cloneElement, forwardRef } 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,75 @@ 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,
}: {
type DeleteAssetProps = {
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;
};

<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>
export const DeleteAsset = forwardRef<HTMLButtonElement, DeleteAssetProps>(
function ({ asset, trigger }, ref) {
const navigation = useNavigation();
const disabled = isFormProcessing(navigation.state);

<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>
return (
<AlertDialog>
<AlertDialogTrigger ref={ref} asChild>
{cloneElement(trigger)}
</AlertDialogTrigger>

<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>
);
}
);

DeleteAsset.displayName = "DeleteAsset";
Loading
Loading