Skip to content

Commit

Permalink
Merge branch 'main' into refactor/user-invite
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko authored Jan 14, 2025
2 parents 06578d5 + 1d7b399 commit 1c9d6e9
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 48 deletions.
21 changes: 17 additions & 4 deletions app/components/assets/assets-index/advanced-asset-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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";

export function AdvancedIndexColumn({
column,
Expand Down Expand Up @@ -157,9 +158,20 @@ export function AdvancedIndexColumn({
);

case "id":
case "qrId":
return <TextColumn value={item[column]} />;

case "qrId":
return (
<QrPreviewDialog
asset={item}
trigger={
<Td className="w-full max-w-none !overflow-visible whitespace-nowrap">
<Button variant="link-gray">{item.qrId}</Button>
</Td>
}
/>
);

case "status":
return <StatusColumn status={item.status} />;

Expand Down Expand Up @@ -189,13 +201,14 @@ export function AdvancedIndexColumn({
<TextColumn
value={
item?.location?.name ? (
<Link
<Button
to={`/locations/${item.locationId}`}
className="block max-w-[220px] truncate font-medium underline hover:text-gray-600"
title={item.location.name}
target="_blank"
variant="link-gray"
>
{item.location.name}
</Link>
</Button>
) : (
""
)
Expand Down
35 changes: 22 additions & 13 deletions app/components/assets/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useActionData,
useLoaderData,
useNavigation,
useParams,
} from "@remix-run/react";
import { useAtom, useAtomValue } from "jotai";
import type { Tag } from "react-tag-autocomplete";
Expand Down Expand Up @@ -420,20 +421,28 @@ export const AssetForm = ({
);
};

const Actions = ({ disabled }: { disabled: boolean }) => (
<>
<ButtonGroup>
<Button to=".." variant="secondary" disabled={disabled}>
Cancel
</Button>
<AddAnother disabled={disabled} />
</ButtonGroup>
const Actions = ({ disabled }: { disabled: boolean }) => {
const { assetId } = useParams<{ assetId?: string }>();

<Button type="submit" disabled={disabled}>
Save
</Button>
</>
);
return (
<>
<ButtonGroup>
<Button
to={assetId ? `/assets/${assetId}/overview` : ".."}
variant="secondary"
disabled={disabled}
>
Cancel
</Button>
<AddAnother disabled={disabled} />
</ButtonGroup>

<Button type="submit" disabled={disabled}>
Save
</Button>
</>
);
};

const AddAnother = ({ disabled }: { disabled: boolean }) => (
<TooltipProvider delayDuration={100}>
Expand Down
91 changes: 91 additions & 0 deletions app/components/assets/qr-preview-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { cloneElement, useState } from "react";
import type { Asset } from "@prisma/client";
import useApiQuery from "~/hooks/use-api-query";
import { tw } from "~/utils/tw";
import { Dialog, DialogPortal } from "../layout/dialog";
import { QrPreview } from "../qr/qr-preview";
import { Button } from "../shared/button";
import When from "../when/when";

type QrPreviewDialogProps = {
className?: string;
asset: Pick<Asset, "id" | "title"> & {
qrId: string;
};
trigger: React.ReactElement<{ onClick: () => void }>;
};

export default function QrPreviewDialog({
className,
asset,
trigger,
}: QrPreviewDialogProps) {
const [isDialogOpen, setIsDialogOpen] = useState(false);

const { isLoading, data, error } = useApiQuery<{
qrObj: React.ComponentProps<typeof QrPreview>["qrObj"];
}>({
api: `/api/assets/${asset.id}/generate-qr-obj`,
enabled: isDialogOpen,
});

function openDialog() {
setIsDialogOpen(true);
}

function closeDialog() {
setIsDialogOpen(false);
}

return (
<>
{cloneElement(trigger, { onClick: openDialog })}

<DialogPortal>
<Dialog
open={isDialogOpen}
onClose={closeDialog}
className={tw(
"h-[90vh] w-full p-0 md:h-[calc(100vh-4rem)] md:w-1/2",
className
)}
title={`QR Id: ${asset.qrId}`}
>
<div
className={
"relative z-10 flex h-full flex-col bg-white shadow-lg md:rounded"
}
>
<div className="flex max-h-[calc(100%-4rem)] grow items-center justify-center border-y border-gray-200 bg-gray-50">
<When truthy={isLoading}>
<div className="relative size-full animate-pulse bg-gray-200">
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col gap-2">
<p>Fetching qr code...</p>
</div>
</div>
</When>
<When truthy={!!error}>
<p className="text-center text-error-500">{error}</p>
</When>
<When truthy={!isLoading}>
<QrPreview
className="mb-0 flex size-full flex-col items-center justify-center"
item={{
name: asset.title,
type: "asset",
}}
qrObj={data?.qrObj}
/>
</When>
</div>
<div className="flex w-full justify-center gap-3 px-6 py-3 md:justify-end">
<Button variant="secondary" onClick={closeDialog}>
Close
</Button>
</div>
</div>
</Dialog>
</DialogPortal>
</>
);
}
1 change: 1 addition & 0 deletions app/components/layout/header/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type ButtonVariant =
| "secondary"
| "tertiary"
| "link"
| "link-gray"
| "block-link"
| "block-link-gray"
| "danger";
Expand Down
60 changes: 33 additions & 27 deletions app/components/qr/qr-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import domtoimage from "dom-to-image";
import { useReactToPrint } from "react-to-print";
import { Button } from "~/components/shared/button";
import { slugify } from "~/utils/slugify";
import { tw } from "~/utils/tw";

type SizeKeys = "cable" | "small" | "medium" | "large";

interface ObjectType {
className?: string;
item: {
name: string;
type: "asset" | "kit";
Expand All @@ -20,7 +23,7 @@ interface ObjectType {
};
}

export const QrPreview = ({ qrObj, item }: ObjectType) => {
export const QrPreview = ({ className, qrObj, item }: ObjectType) => {
const captureDivRef = useRef<HTMLImageElement>(null);
const downloadQrBtnRef = useRef<HTMLAnchorElement>(null);

Expand Down Expand Up @@ -67,32 +70,35 @@ export const QrPreview = ({ qrObj, item }: ObjectType) => {
content: () => captureDivRef.current,
});
return (
<div className="">
<div className="mb-4 w-auto rounded border border-solid bg-white">
<div className="flex w-full justify-center pt-6">
<QrLabel ref={captureDivRef} data={qrObj} title={item.name} />
</div>
<div className="mt-8 flex items-center gap-3 border-t-[1.1px] border-[#E3E4E8] px-4 py-3">
<Button
icon="download"
onClick={downloadQr}
download={`${slugify(item.name)}-${qrObj?.qr
?.size}-shelf-qr-code-${qrObj?.qr?.id}.png`}
ref={downloadQrBtnRef}
variant="secondary"
className="w-full"
>
Download
</Button>
<Button
icon="print"
variant="secondary"
className="w-full"
onClick={printQr}
>
Print
</Button>
</div>
<div
className={tw(
"mb-4 w-auto rounded border border-solid bg-white",
className
)}
>
<div className="flex w-full justify-center pt-6">
<QrLabel ref={captureDivRef} data={qrObj} title={item.name} />
</div>
<div className="mt-8 flex items-center gap-3 border-t-[1.1px] border-[#E3E4E8] px-4 py-3">
<Button
icon="download"
onClick={downloadQr}
download={`${slugify(item.name)}-${qrObj?.qr
?.size}-shelf-qr-code-${qrObj?.qr?.id}.png`}
ref={downloadQrBtnRef}
variant="secondary"
className="w-full"
>
Download
</Button>
<Button
icon="print"
variant="secondary"
className="w-full"
onClick={printQr}
>
Print
</Button>
</div>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions app/components/shared/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const variants: Record<ButtonVariant, string> = {
link: tw(
`border-none p-0 text-text-sm font-semibold text-primary-700 hover:text-primary-800`
),
"link-gray": tw(
"text-gray border-none p-0 text-text-sm font-normal underline hover:text-gray-500 "
),
"block-link": tw(
"-mt-1 border-none px-2 py-1 text-[14px] font-normal hover:bg-primary-50 hover:text-primary-600"
),
Expand Down
47 changes: 47 additions & 0 deletions app/hooks/use-api-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useEffect, useState } from "react";

/**
* A simple hook which calls any of our API
*
*/
type UseApiQueryParams = {
/** Any API endpoint */
api: string;
/** Query will not execute until this is true */
enabled?: boolean;
};

export default function useApiQuery<TData>({
api,
enabled = true,
}: UseApiQueryParams) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | undefined>();
const [data, setData] = useState<TData | undefined>();

useEffect(
function handleQuery() {
if (enabled) {
setIsLoading(true);
fetch(api)
.then((response) => response.json())
.then((data: TData) => {
setData(data);
})
.catch((error: Error) => {
setError(error?.message ?? "Something went wrong.");
})
.finally(() => {
setIsLoading(false);
});
}
},
[api, enabled]
);

return {
isLoading,
error,
data,
};
}
4 changes: 2 additions & 2 deletions app/routes/_layout+/assets.$assetId_.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export default function AssetEditPage() {
);

return (
<>
<div className="relative">
<Header title={hasTitle ? title : asset.title} />
<div className=" items-top flex justify-between">
<AssetForm
Expand All @@ -242,6 +242,6 @@ export default function AssetEditPage() {
tags={tags}
/>
</div>
</>
</div>
);
}
5 changes: 3 additions & 2 deletions app/routes/_layout+/assets.new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,13 @@ export default function NewAssetPage() {

// Get category from URL params or use the default passed prop
const categoryFromUrl = searchParams.get("category");

return (
<>
<div className="relative">
<Header title={title ? title : "Untitled Asset"} />
<div>
<AssetForm qrId={qrId} category={categoryFromUrl || undefined} />
</div>
</>
</div>
);
}
Loading

0 comments on commit 1c9d6e9

Please sign in to comment.