Skip to content

Commit

Permalink
Merge branch 'main' into email-queue
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko authored Jan 23, 2025
2 parents 4a92abd + c16ac5f commit aa998d7
Show file tree
Hide file tree
Showing 36 changed files with 347 additions and 118 deletions.
32 changes: 32 additions & 0 deletions app/components/assets/assets-index/advanced-filters/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,35 @@ export function getAvailableColumns(
return true;
});
}

/**
* Extracts the QR ID from a URL or returns the original value if it's not a URL
* Removes any query parameters and returns the last path segment
*
* @example
* localhost:3000/qr/abc123?hello=world -> abc123
* https://example.com/abc123 -> abc123
* abc123 -> abc123
*
* @param value - The input value (URL or QR ID)
* @returns The extracted QR ID or original value
*/
export function extractQrIdFromValue(value: string): string {
try {
// Try to parse as URL first
const url = new URL(value);

// Remove leading and trailing slashes and split path
const pathParts = url.pathname.split("/").filter(Boolean);

// Get the last part of the path (if exists)
if (pathParts.length > 0) {
return pathParts[pathParts.length - 1];
}

return value;
} catch (e) {
// If URL parsing fails, return original value
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,25 @@ import DynamicDropdown from "~/components/dynamic-dropdown/dynamic-dropdown";
import DynamicSelect from "~/components/dynamic-select/dynamic-select";
import Input from "~/components/forms/input";

import { CheckIcon, ChevronRight, PlusIcon } from "~/components/icons/library";
import {
CheckIcon,
ChevronRight,
HelpIcon,
PlusIcon,
} from "~/components/icons/library";
import { Button } from "~/components/shared/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "~/components/shared/tooltip";
import type { AssetIndexLoaderData } from "~/routes/_layout+/assets._index";
import { useHints } from "~/utils/client-hints";
import { adjustDateToUTC, isDateString } from "~/utils/date-fns";
import { tw } from "~/utils/tw";
import { resolveTeamMemberName } from "~/utils/user";
import { extractQrIdFromValue } from "./helpers";
import type { Filter } from "./schema";
import { userFriendlyAssetStatus } from "../../asset-status-badge";

Expand Down Expand Up @@ -156,6 +168,63 @@ export function ValueField({
switch (filter.type) {
case "string":
case "text":
if (filter.name === "qrId") {
return (
<div className={tw("flex w-full md:w-auto")}>
<div className="relative flex-1">
<Input
{...commonInputProps}
type="text"
value={filter.value as string}
onChange={(e) => {
setFilter(e.target.value);
}}
placeholder={placeholder(filter.operator)}
onKeyUp={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
setTimeout(() => {
// Assert the target as HTMLInputElement to access value
const input = e.target as HTMLInputElement;
const cleanValue = extractQrIdFromValue(input.value);
setFilter(cleanValue);
// Create a new keyboard event for submitOnEnter
submitOnEnter(e as React.KeyboardEvent<HTMLInputElement>);
}, 10);
}
}}
error={error}
name={fieldName}
/>
{!["contains", "containsAny", "matchesAny"].includes(
filter.operator
) ? (
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<i className="absolute right-3.5 top-1/2 flex -translate-y-1/2 cursor-pointer text-gray-400 hover:text-gray-700">
<HelpIcon />
</i>
</TooltipTrigger>
<TooltipContent side="bottom" className="z-[9999999]">
<div className="max-w-[260px] sm:max-w-[320px]">
<h6 className="mb-1 text-xs font-semibold text-gray-700">
Barcode scanner ready
</h6>
<p className="text-xs font-medium text-gray-500">
This fields supports barcode scanners. Simply place
your cursor in the field and scan a Shelf QR code with
your barcode scanner. The value will be automatically
filled in for you.
</p>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : null}
</div>
</div>
);
}
return (
<Input
{...commonInputProps}
Expand Down
124 changes: 79 additions & 45 deletions app/components/assets/assets-index/asset-quick-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import {
TooltipContent,
TooltipTrigger,
} from "~/components/shared/tooltip";
import When from "~/components/when/when";
import { useUserRoleHelper } from "~/hooks/user-user-role-helper";
import type { AssetsFromViewItem } from "~/modules/asset/types";
import {
PermissionAction,
PermissionEntity,
} from "~/utils/permissions/permission.data";
import { userHasPermission } from "~/utils/permissions/permission.validator.client";
import { tw } from "~/utils/tw";
import { DeleteAsset } from "../delete-asset";
import { QrPreviewDialog } from "../qr-preview-dialog";
Expand All @@ -23,24 +30,34 @@ export default function AssetQuickActions({
style,
asset,
}: AssetQuickActionsProps) {
const { roles } = useUserRoleHelper();

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>
<When
truthy={userHasPermission({
roles,
entity: PermissionEntity.asset,
action: PermissionAction.update,
})}
>
<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>
<TooltipContent align="center" side="top">
Edit asset information
</TooltipContent>
</Tooltip>
</When>

<Tooltip>
<QrPreviewDialog
Expand All @@ -63,38 +80,55 @@ export default function AssetQuickActions({
</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>
<When
truthy={userHasPermission({
roles,
entity: PermissionEntity.asset,
action: PermissionAction.update,
})}
>
<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">
Duplicate asset
</TooltipContent>
</Tooltip>
</When>

<TooltipContent align="center" side="top">
Delete asset
</TooltipContent>
</Tooltip>
<When
truthy={userHasPermission({
roles,
entity: PermissionEntity.asset,
action: PermissionAction.delete,
})}
>
<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>
</When>
</div>
);
}
33 changes: 22 additions & 11 deletions app/modules/asset/service.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,11 +921,13 @@ export async function updateAssetMainImage({
assetId,
userId,
organizationId,
isNewAsset = false,
}: {
request: Request;
assetId: string;
userId: User["id"];
organizationId: Organization["id"];
isNewAsset?: boolean;
}) {
try {
const fileData = await parseFileFormData({
Expand Down Expand Up @@ -955,7 +957,14 @@ export async function updateAssetMainImage({
userId,
organizationId,
});
await deleteOtherImages({ userId, assetId, data: { path: image } });

/**
* If updateAssetMainImage is called from new asset route, then we don't have to delete other images
* bcause no others images for this assets exists yet.
*/
if (!isNewAsset) {
await deleteOtherImages({ userId, assetId, data: { path: image } });
}
} catch (cause) {
throw new ShelfError({
cause,
Expand Down Expand Up @@ -1093,6 +1102,7 @@ export function createCustomFieldsPayloadFromAsset(
) || {}
);
}

export async function duplicateAsset({
asset,
userId,
Expand Down Expand Up @@ -2019,14 +2029,15 @@ export async function createAssetsFromBackupImport({
}
}

export async function updateAssetBookingAvailability(
id: Asset["id"],
availability: Asset["availableToBook"]
) {
export async function updateAssetBookingAvailability({
id,
availableToBook,
organizationId,
}: Pick<Asset, "id" | "availableToBook" | "organizationId">) {
try {
return await db.asset.update({
where: { id },
data: { availableToBook: availability },
where: { id, organizationId },
data: { availableToBook },
});
} catch (cause) {
throw maybeUniqueConstraintViolation(cause, "Asset", {
Expand Down Expand Up @@ -2158,7 +2169,7 @@ export async function updateAssetQrCode({
// Disconnect all existing QR codes
await db.asset
.update({
where: { id: assetId },
where: { id: assetId, organizationId },
data: {
qrCodes: {
set: [],
Expand All @@ -2177,7 +2188,7 @@ export async function updateAssetQrCode({
// Connect the new QR code
return await db.asset
.update({
where: { id: assetId },
where: { id: assetId, organizationId },
data: {
qrCodes: {
connect: { id: newQrId },
Expand Down Expand Up @@ -2623,7 +2634,7 @@ export async function bulkAssignAssetTags({

const updatePromises = _assetIds.map((id) =>
db.asset.update({
where: { id },
where: { id, organizationId },
data: {
tags: {
[remove ? "disconnect" : "connect"]: tagsIds.map((id) => ({ id })), // IDs of tags you want to connect
Expand Down Expand Up @@ -2700,7 +2711,7 @@ export async function relinkQrCode({
getQr({ id: qrId }),
getUserByID(userId),
db.asset.findFirst({
where: { id: assetId },
where: { id: assetId, organizationId },
select: { qrCodes: { select: { id: true } } },
}),
]);
Expand Down
2 changes: 1 addition & 1 deletion app/modules/asset/utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export const importAssetsSchema = z
tags: z.string().array(),
location: z.string().optional(),
custodian: z.string().optional(),
bookable: z.enum(["yes", "no"]).optional(),
bookable: z.enum(["yes", "no"]).optional().nullable(),
imageUrl: z.string().url().optional(),
})
.and(z.record(z.string(), z.any()));
Loading

0 comments on commit aa998d7

Please sign in to comment.