Skip to content

Commit

Permalink
Merge branch 'main' into refactor/sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko authored Nov 26, 2024
2 parents 9be30ee + bd2ae24 commit 0fcf89f
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,12 @@ function CustodyEnumField({
className="w-full justify-start font-normal [&_span]:w-full [&_span]:max-w-full [&_span]:truncate"
>
<div className="flex items-center justify-between">
<span className=" text-left">
<span
className={tw(
"text-left",
selectedIds.length <= 0 && "text-gray-500"
)}
>
{value === "without-custody"
? "Without custody"
: selectedIds.length > 0
Expand Down Expand Up @@ -675,7 +680,12 @@ function CategoryEnumField({
className="w-full justify-start font-normal [&_span]:w-full [&_span]:max-w-full [&_span]:truncate"
>
<div className="flex items-center justify-between">
<span className="text-left">
<span
className={tw(
"text-left",
selectedIds.length <= 0 && "text-gray-500"
)}
>
{selectedIds.length > 0
? selectedIds
.map((id) => {
Expand Down Expand Up @@ -778,7 +788,12 @@ function LocationEnumField({
className="w-full justify-start font-normal [&_span]:w-full [&_span]:max-w-full [&_span]:truncate"
>
<div className="flex items-center justify-between">
<span className="text-left">
<span
className={tw(
"text-left",
selectedIds.length <= 0 && "text-gray-500"
)}
>
{selectedIds.length > 0
? selectedIds
.map((id) => {
Expand Down Expand Up @@ -881,7 +896,12 @@ function KitEnumField({
className="w-full justify-start font-normal [&_span]:w-full [&_span]:max-w-full [&_span]:truncate"
>
<div className="flex items-center justify-between">
<span className="text-left">
<span
className={tw(
"text-left",
selectedIds.length <= 0 && "text-gray-500"
)}
>
{selectedIds.length > 0 && data.kits && data.kits.length > 0
? selectedIds
.map((id) => {
Expand Down
59 changes: 49 additions & 10 deletions app/components/assets/assets-index/export-assets-button.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { useState } from "react";
import { useLoaderData } from "@remix-run/react";
import { useAtomValue } from "jotai";
import { selectedBulkItemsAtom } from "~/atoms/list";
import { UpgradeMessage } from "~/components/marketing/upgrade-message";
import { Button } from "~/components/shared/button";
import { Spinner } from "~/components/shared/spinner";
import type { AssetIndexLoaderData } from "~/routes/_layout+/assets._index";
import { isSelectingAllItems } from "~/utils/list";

export function ExportAssetsButton() {
const selectedAssets = useAtomValue(selectedBulkItemsAtom);
const { canImportAssets } = useLoaderData<AssetIndexLoaderData>();
const disabled = selectedAssets.length === 0;
const [isDownloading, setIsDownloading] = useState(false);

const allSelected = isSelectingAllItems(selectedAssets);
const title = `Export selection ${
Expand All @@ -14,27 +21,59 @@ export function ExportAssetsButton() {

/** Get the assetIds from the atom and add them to assetIds search param */
const assetIds = selectedAssets.map((asset) => asset.id);
let url = `/assets/export/assets-${new Date()
const url = `/assets/export/assets-${new Date()
.toISOString()
.slice(0, 10)}.csv`;
if (assetIds.length > 0) {
url += `?assetIds=${assetIds.join(",")}`;
}
const searchParams =
assetIds.length > 0 ? `?assetIds=${assetIds.join(",")}` : "";

/** Handle the download via fetcher and track state */
const handleExport = async () => {
setIsDownloading(true);
try {
const response = await fetch(`${url}${searchParams}`);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.setAttribute("download", url.split("/").pop() || "export.csv");
document.body.appendChild(link);
link.click();
link.remove();
} finally {
setIsDownloading(false);
}
};

return (
<Button
to={url}
onClick={handleExport}
variant="secondary"
className="font-medium"
download
reloadDocument
title={title}
disabled={
disabled
!canImportAssets
? {
reason: (
<>
Exporting is not available on the free tier of shelf.{" "}
<UpgradeMessage />
</>
),
}
: disabled
? { reason: "You must select at least 1 asset to export" }
: false
: isDownloading
}
>
{title}
<div className="flex items-center gap-1">
{isDownloading ? (
<span>
<Spinner />
</span>
) : null}{" "}
<span>{title}</span>
</div>
</Button>
);
}
2 changes: 1 addition & 1 deletion app/components/assets/custom-fields-inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function AssetCustomFields({
DATE: (field) => (
<div className="flex w-full items-end">
<Input
className="w-full"
className="w-full placeholder:text-gray-500"
label={field.name}
hideLabel
type="date"
Expand Down
7 changes: 6 additions & 1 deletion app/components/dynamic-select/dynamic-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,12 @@ export default function DynamicSelect({
ref={triggerRef}
className="flex w-full items-center justify-between whitespace-nowrap rounded border border-gray-300 px-[14px] py-2 text-[14px] hover:cursor-pointer disabled:opacity-50"
>
<span className="truncate whitespace-nowrap pr-2">
<span
className={tw(
"truncate whitespace-nowrap pr-2",
selectedValue === undefined && "text-gray-500"
)}
>
{triggerValue}
</span>
<ChevronDownIcon />
Expand Down
6 changes: 5 additions & 1 deletion app/components/marketing/upgrade-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ export function UpgradeMessage() {
return (
<>
Please consider{" "}
<Button to="/account-details/subscription" variant="link">
<Button
to="/account-details/subscription"
variant="link"
className="!m-0 !p-0"
>
upgrading
</Button>{" "}
your subscription to access this feature.
Expand Down
133 changes: 74 additions & 59 deletions app/utils/csv.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ import type {
AdvancedIndexAsset,
ShelfAssetCustomFieldValueType,
} from "~/modules/asset/types";
import type { Column } from "~/modules/asset-index-settings/helpers";
import type {
Column,
FixedField,
} from "~/modules/asset-index-settings/helpers";
import { parseColumnName } from "~/modules/asset-index-settings/helpers";
import { checkExhaustiveSwitch } from "./check-exhaustive-switch";
import { getAdvancedFiltersFromRequest } from "./cookies.server";
import { isLikeShelfError, ShelfError } from "./error";
import { ALL_SELECTED_KEY } from "./list";
Expand Down Expand Up @@ -265,7 +269,10 @@ export async function exportAssetsFromIndexToCsv({
// Pass both assets and columns to the build function
const csvData = buildCsvExportDataFromAssets({
assets,
columns: settings.columns as Column[],
columns: [
{ name: "name", visible: true, position: 0 },
...(settings.columns as Column[]),
],
});

// Join rows with CRLF as per CSV spec
Expand Down Expand Up @@ -303,63 +310,71 @@ export const buildCsvExportDataFromAssets = ({
// Handle different column types
let value: any;

switch (column.name) {
case "id":
value = asset.id;
break;
case "name":
value = asset.title;
break;
case "description":
value = asset.description ?? "";
break;
case "category":
value = asset.category?.name ?? "Uncategorized";
break;
case "location":
value = asset.location?.name;
break;
case "kit":
value = asset.kit?.name;
break;
case "custody":
value = asset.custody
? resolveTeamMemberName(asset.custody.custodian)
: "";
break;
case "tags":
value = asset.tags?.map((t) => t.name).join(", ") ?? "";
break;
case "status":
value = asset.status;
break;
case "createdAt":
value = asset.createdAt
? new Date(asset.createdAt).toISOString()
: "";
break;
case "valuation":
value = asset.valuation;
break;
case "availableToBook":
value = asset.availableToBook ? "Yes" : "No";
break;
default:
// Handle custom fields
if (column.name.startsWith("cf_")) {
const fieldName = column.name.replace("cf_", "");
const customField = asset.customFields?.find(
(cf) => cf.customField.name === fieldName
);

if (!customField) {
value = "";
} else {
const fieldValue =
customField.value as unknown as ShelfAssetCustomFieldValueType["value"];
value = formatCustomFieldForCsv(fieldValue, column.cfType);
}
}
// If it's not a custom field, it must be a fixed field or 'name'
if (!column.name.startsWith("cf_")) {
const fieldName = column.name as FixedField | "name";

switch (fieldName) {
case "id":
value = asset.id;
break;
case "qrId":
value = asset.qrId;
break;
case "name":
value = asset.title;
break;
case "description":
value = asset.description ?? "";
break;
case "category":
value = asset.category?.name ?? "Uncategorized";
break;
case "location":
value = asset.location?.name;
break;
case "kit":
value = asset.kit?.name;
break;
case "custody":
value = asset.custody
? resolveTeamMemberName(asset.custody.custodian)
: "";
break;
case "tags":
value = asset.tags?.map((t) => t.name).join(", ") ?? "";
break;
case "status":
value = asset.status;
break;
case "createdAt":
value = asset.createdAt
? new Date(asset.createdAt).toISOString()
: "";
break;
case "valuation":
value = asset.valuation;
break;
case "availableToBook":
value = asset.availableToBook ? "Yes" : "No";
break;
default:
checkExhaustiveSwitch(fieldName);
value = "";
}
} else {
// Handle custom fields
const fieldName = column.name.replace("cf_", "");
const customField = asset.customFields?.find(
(cf) => cf.customField.name === fieldName
);
if (!customField) {
value = "";
} else {
const fieldValue =
customField.value as unknown as ShelfAssetCustomFieldValueType["value"];
value = formatCustomFieldForCsv(fieldValue, column.cfType);
}
}

return formatValueForCsv(value);
Expand Down

0 comments on commit 0fcf89f

Please sign in to comment.