Skip to content

Commit

Permalink
feat: make excel multi-sheet possible
Browse files Browse the repository at this point in the history
  • Loading branch information
mfts committed May 31, 2024
1 parent 087872d commit 1852831
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 58 deletions.
15 changes: 9 additions & 6 deletions components/view/dataroom/dataroom-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ const ExcelViewer = dynamic(
{ ssr: false },
);

type RowData = { [key: string]: any };
type SheetData = {
sheetName: string;
columnData: string[];
rowData: RowData[];
};

export type TDocumentData = {
id: string;
name: string;
Expand All @@ -44,10 +51,7 @@ export type DEFAULT_DOCUMENT_VIEW_TYPE = {
pages?:
| { file: string; pageNumber: string; embeddedLinks: string[] }[]
| null;
sheetData?: {
rowData: { [key: string]: any }[];
columnData: string[];
} | null;
sheetData?: SheetData[] | null;
notionData?: { recordMap: ExtendedRecordMap | null };
};

Expand Down Expand Up @@ -268,8 +272,7 @@ export default function DataroomView({
documentId={documentData.id}
documentName={documentData.name}
versionNumber={documentData.documentVersionNumber}
columns={viewData.sheetData.columnData!}
data={viewData.sheetData.rowData!}
sheetData={viewData.sheetData}
brand={brand}
dataroomId={dataroom.id}
setDocumentData={setDocumentData}
Expand Down
12 changes: 8 additions & 4 deletions components/view/document-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ import { LinkWithDocument } from "@/lib/types";
import EmailVerificationMessage from "./email-verification-form";
import ViewData from "./view-data";

type RowData = { [key: string]: any };
type SheetData = {
sheetName: string;
columnData: string[];
rowData: RowData[];
};

export type DEFAULT_DOCUMENT_VIEW_TYPE = {
viewId: string;
file?: string | null;
pages?:
| { file: string; pageNumber: string; embeddedLinks: string[] }[]
| null;
sheetData?: {
rowData: { [key: string]: any }[];
columnData: string[];
} | null;
sheetData?: SheetData[] | null;
};

export default function DocumentView({
Expand Down
3 changes: 1 addition & 2 deletions components/view/view-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ export default function ViewData({
documentId={document.id}
documentName={document.name}
versionNumber={document.versions[0].versionNumber}
columns={viewData.sheetData.columnData!}
data={viewData.sheetData.rowData!}
sheetData={viewData.sheetData}
brand={brand}
/>
) : viewData.pages ? (
Expand Down
74 changes: 55 additions & 19 deletions components/view/viewer/excel-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ import React from "react";
import "@/public/vendor/handsontable/handsontable.full.min.css";
import { Brand, DataroomBrand } from "@prisma/client";

import { Button } from "@/components/ui/button";

import { cn } from "@/lib/utils";

import { TDocumentData } from "../dataroom/dataroom-view";
import Nav from "../nav";

// Define the type for the JSON data
type SheetData = { [key: string]: any };
type RowData = { [key: string]: any };
type SheetData = {
sheetName: string;
columnData: string[];
rowData: RowData[];
};

const trackPageView = async (data: {
linkId: string;
Expand All @@ -34,8 +43,7 @@ export default function ExcelViewer({
documentId,
documentName,
versionNumber,
columns,
data,
sheetData,
brand,
dataroomId,
setDocumentData,
Expand All @@ -45,15 +53,15 @@ export default function ExcelViewer({
documentId: string;
documentName: string;
versionNumber: number;
columns: string[];
data: SheetData[];
sheetData: SheetData[];
brand?: Partial<Brand> | Partial<DataroomBrand> | null;
dataroomId?: string;
setDocumentData?: React.Dispatch<React.SetStateAction<TDocumentData | null>>;
}) {
const [availableWidth, setAvailableWidth] = useState<number>(200);
const [availableHeight, setAvailableHeight] = useState<number>(200);
const [handsontableLoaded, setHandsontableLoaded] = useState<boolean>(false);
const [selectedSheetIndex, setSelectedSheetIndex] = useState<number>(0);

useEffect(() => {
const script = document.createElement("script");
Expand Down Expand Up @@ -84,8 +92,8 @@ export default function ExcelViewer({
const calculateSize = () => {
if (containerRef.current) {
const offset = containerRef.current.getBoundingClientRect();
setAvailableWidth(Math.max(offset.width - 60, 200));
setAvailableHeight(Math.max(offset.height - 10, 200));
setAvailableWidth(Math.max(offset.width, 200));
setAvailableHeight(Math.max(offset.height - 50, 200));
}
};

Expand Down Expand Up @@ -113,7 +121,7 @@ export default function ExcelViewer({
documentId,
viewId,
duration,
pageNumber: 1,
pageNumber: selectedSheetIndex + 1,
versionNumber,
dataroomId,
});
Expand All @@ -130,24 +138,27 @@ export default function ExcelViewer({
documentId,
viewId,
duration,
pageNumber: 1,
pageNumber: selectedSheetIndex + 1,
versionNumber,
dataroomId,
}); // Also capture duration if component unmounts while visible
startTimeRef.current = Date.now();
}
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
}, [selectedSheetIndex]);

useEffect(() => {
const handleBeforeUnload = () => {
if (!visibilityRef.current) return;

const duration = Date.now() - startTimeRef.current;
trackPageView({
linkId,
documentId,
viewId,
duration,
pageNumber: 1,
pageNumber: selectedSheetIndex + 1,
versionNumber,
dataroomId,
});
Expand All @@ -158,22 +169,24 @@ export default function ExcelViewer({
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, []);
}, [selectedSheetIndex]);

useEffect(() => {
if (handsontableLoaded && data.length && columns.length) {
if (handsontableLoaded && sheetData.length) {
if (hotInstanceRef.current) {
hotInstanceRef.current.destroy();
}

const { columnData, rowData } = sheetData[selectedSheetIndex];

// @ts-ignore - Handsontable import has not types
hotInstanceRef.current = new Handsontable(hotRef.current!, {
data: data,
data: rowData,
readOnly: true,
disableVisualSelection: true,
comments: false,
contextMenu: false,
colHeaders: columns,
colHeaders: columnData,
rowHeaders: true,
manualColumnResize: true,
width: availableWidth,
Expand All @@ -190,7 +203,13 @@ export default function ExcelViewer({
// },
});
}
}, [handsontableLoaded, data, columns, availableHeight, availableWidth]);
}, [
handsontableLoaded,
sheetData,
selectedSheetIndex,
availableHeight,
availableWidth,
]);

return (
<>
Expand All @@ -202,11 +221,28 @@ export default function ExcelViewer({
type="sheet"
/>
<div
style={{ height: "calc(100vh - 64px)" }}
className="flex h-screen items-center justify-center"
style={{ height: "calc(100dvh - 64px)" }}
className="mx-2 flex h-screen flex-col sm:mx-6 lg:mx-8"
ref={containerRef}
>
<div ref={hotRef}></div>
<div className="" ref={hotRef}></div>
<div className="flex max-w-fit divide-x divide-gray-200 overflow-x-scroll whitespace-nowrap rounded-b-sm bg-[#f0f0f0] px-1 ">
{sheetData.map((sheet, index) => (
<div className="px-1">
<Button
key={sheet.sheetName}
onClick={() => setSelectedSheetIndex(index)}
className={cn(
"mb-1 rounded-none rounded-b-sm bg-[#f0f0f0] font-normal text-gray-950 hover:bg-gray-50",
index === selectedSheetIndex &&
"bg-white font-medium text-black ring-1 ring-gray-500 hover:bg-white",
)}
>
{sheet.sheetName}
</Button>
</div>
))}
</div>
</div>
</>
);
Expand Down
51 changes: 32 additions & 19 deletions lib/sheet/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as XLSX from "xlsx";

type RowData = { [key: string]: any };
type SheetData = {
sheetName: string;
columnData: string[];
rowData: RowData[];
};

// Custom sort function to sort keys A, B, .. Z, AA, AB, ..
const customSort = (a: string, b: string) => {
Expand All @@ -18,30 +23,38 @@ export const parseSheet = async ({ fileUrl }: { fileUrl: string }) => {
const arrayBuffer = await response.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
const workbook = XLSX.read(data, { type: "array" });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];

const json: RowData[] = XLSX.utils.sheet_to_json(worksheet, {
header: "A",
});
const result: SheetData[] = [];

// Iterate through all sheets in the workbook
workbook.SheetNames.forEach((sheetName) => {
const worksheet = workbook.Sheets[sheetName];
const json: RowData[] = XLSX.utils.sheet_to_json(worksheet, {
header: "A",
});

// Collect all unique keys from the JSON data
const allKeys = Array.from(new Set(json.flatMap(Object.keys)));
// Collect all unique keys from the JSON data
const allKeys = Array.from(new Set(json.flatMap(Object.keys)));

// Sort the keys alphabetically
allKeys.sort(customSort);
// Sort the keys alphabetically
allKeys.sort(customSort);

// Ensure each row has the same set of keys
const normalizedData = json.map((row) => {
const normalizedRow: RowData = {};
allKeys.forEach((key) => {
normalizedRow[key] = row[key] || "";
// Ensure each row has the same set of keys
const normalizedData = json.map((row) => {
const normalizedRow: RowData = {};
allKeys.forEach((key) => {
normalizedRow[key] = row[key] || "";
});
return normalizedRow;
});
return normalizedRow;
});

columnData = allKeys;
rowData = normalizedData;
// Store column and row data for the current sheet
result.push({
sheetName,
columnData: allKeys,
rowData: normalizedData,
});
});

return { columnData, rowData };
return result;
};
7 changes: 3 additions & 4 deletions pages/api/views-dataroom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export default async function handle(
// otherwise, return file from document version
let documentPages, documentVersion;
let recordMap;
let columnData, rowData;
let sheetData;

if (hasPages) {
// get pages from document version
Expand Down Expand Up @@ -379,8 +379,7 @@ export default async function handle(
});

const data = await parseSheet({ fileUrl });
columnData = data.columnData;
rowData = data.rowData;
sheetData = data;
}
console.timeEnd("get-file");
}
Expand All @@ -396,7 +395,7 @@ export default async function handle(
notionData: recordMap ? { recordMap } : undefined,
sheetData:
documentVersion && documentVersion.type === "sheet"
? { columnData, rowData }
? sheetData
: undefined,
};

Expand Down
9 changes: 5 additions & 4 deletions pages/api/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export default async function handle(
// if document version has pages, then return pages
// otherwise, return file from document version
let documentPages, documentVersion;
let columnData, rowData;
let sheetData;
// let documentPagesPromise, documentVersionPromise;
if (hasPages) {
// get pages from document version
Expand Down Expand Up @@ -279,8 +279,9 @@ export default async function handle(
});

const data = await parseSheet({ fileUrl });
columnData = data.columnData;
rowData = data.rowData;
sheetData = data;
// columnData = data.columnData;
// rowData = data.rowData;
}
console.timeEnd("get-file");
}
Expand All @@ -301,7 +302,7 @@ export default async function handle(
pages: documentPages ? documentPages : undefined,
sheetData:
documentVersion && documentVersion.type === "sheet"
? { columnData, rowData }
? sheetData
: undefined,
};

Expand Down

0 comments on commit 1852831

Please sign in to comment.