From 8447fc4f9f48d399e3b239a76c0519d0307ce615 Mon Sep 17 00:00:00 2001 From: ngnathan Date: Mon, 19 Aug 2024 11:43:35 -0400 Subject: [PATCH] Add findLastRowInRecordSet and add additional JSDocs --- .changeset/ninety-phones-tickle.md | 5 + src/data.ts | 286 +++++++++++++++++++---------- src/workbook.ts | 36 ++-- 3 files changed, 213 insertions(+), 114 deletions(-) create mode 100644 .changeset/ninety-phones-tickle.md diff --git a/.changeset/ninety-phones-tickle.md b/.changeset/ninety-phones-tickle.md new file mode 100644 index 0000000..d506d40 --- /dev/null +++ b/.changeset/ninety-phones-tickle.md @@ -0,0 +1,5 @@ +--- +"@connectk12/exceljs": patch +--- + +Add findLastRowInRecordSet and add additional JSDocs diff --git a/src/data.ts b/src/data.ts index cd19df1..04e6f01 100644 --- a/src/data.ts +++ b/src/data.ts @@ -1,6 +1,16 @@ import ExcelJS from "@zurmokeeper/exceljs"; import { sanitizeText } from "./helpers"; +/* List of functions in this file: + * updateCell + * findMatchingRowById + * findMatchingRowByName + * findNextRecordSetRowById + * findNextRecordSetRowByName + * getRowsOfMatchingRecordSet + * createNewRowAfterRecordSet + */ + /** * Updates the cell with the provided value and options. * It can overwrite the cell value, update the cell style, fill color, number format, and note. @@ -301,118 +311,67 @@ export const findMatchingRowByName = ({ }; /** - * Retrieves a record set (contiguous group of rows) from the worksheet that match a cell value at the identifier column and optional conditions + * Finds the last row in the record set based on the starting row and the lookup value. + * It searches for the next row that does not match the lookup value. * - * @param worksheet - The worksheet to search for the matching rows. - * @param startRowNumber - The starting row number to search for the matching rows. - * @param identifierCol - The column to search for the identifier value in the worksheet. - * @param identifierValue - The value to match the identifier value. - * @param identifierCondition - The condition to match the identifier value. - * @param opts - Options to customize the search process. - * @param opts.findSimilarMatchWithLastDigits - Whether to find a similar match based on the last digits of the identifier value. - * - * @returns {ExcelJS.Row[]} - Returns the rows of the matching record set + * @param worksheet - The worksheet to search for the last row in the record set. + * @param startingRow - The starting row to search for the last row in the record set. + * @param lastRowNumber - The last row number to search for the last row in the record set. + * @param lookupCol - The column to search for the lookup value in the worksheet. + * @param lookupValue - The value to match the lookup value. * - * @example - * const rows = getRowsOfMatchingRecordSet({ - * worksheet, - * startRowNumber: 2, - * identifierCol: "A", - * identifierValue: "12345", - * opts: { findSimilarMatchWithLastDigits: true } - * }); + * @returns {ExcelJS.Row | undefined} - The last row in the record set if found, otherwise undefined. */ - -export const getRowsOfMatchingRecordSet = ({ +export const findLastRowInRecordSet = ({ worksheet, - startRowNumber, - identifierCol, - identifierValue, - identifierCondition, - opts, + startingRow, + lastRowNumber, + lookupCol, + lookupValue, }: { worksheet: ExcelJS.Worksheet; - startRowNumber: number; + startingRow: ExcelJS.Row; lastRowNumber: number; - identifierCol: string; - identifierValue: string; - identifierCondition?: (identifierValue: string) => boolean; - opts?: { - findSimilarMatchWithLastDigits?: boolean; - }; -}): ExcelJS.Row[] => { - let rowNumber = startRowNumber; - let currentRow = worksheet.getRow(rowNumber); - let currentIdentifierValue = currentRow - .getCell(identifierCol) - .value?.toString(); - const rows = []; - while ( - currentIdentifierValue !== null && - currentIdentifierValue !== "" && - (currentIdentifierValue === identifierValue || - (opts?.findSimilarMatchWithLastDigits && - currentIdentifierValue?.endsWith(identifierValue)) || - (currentIdentifierValue && identifierCondition?.(currentIdentifierValue))) - ) { - rows.push(currentRow); - rowNumber++; - currentRow = worksheet.getRow(rowNumber); - currentIdentifierValue = currentRow - .getCell(identifierCol) - .value?.toString(); + lookupCol: string; + lookupValue: ExcelJS.CellValue; +}): ExcelJS.Row | undefined => { + const rows = worksheet.getRows(startingRow.number, lastRowNumber); + if (!rows) { + // throw new Error("No matching rows found"); + console.log("No matching rows found"); + return undefined; } - return rows; + + let nextMatchingRow = rows.find((row) => { + let currentRowValue = sanitizeText( + row.getCell(lookupCol).value?.toString() + ); + if (!currentRowValue) { + return true; + } + return currentRowValue !== lookupValue; + }); + + const lastRowInRecordSet = nextMatchingRow + ? worksheet.getRow(nextMatchingRow.number - 1) + : worksheet.getRow(lastRowNumber); + + return lastRowInRecordSet; }; /** - * Retrieves a set of rows from the worksheet that match a cell value at the identifier column and optional conditions - * and returns the newly added row - * @param worksheet - The worksheet to search for the matching rows. - * @param startRowNumber - The starting row number to search for the matching rows. - * @param identifierCol - The column to search for the identifier value in the worksheet. + * Finds the next record set in the worksheet based on the starting row by id. + * It searches for the next row that does not match id. * - * @returns {ExcelJS.Row} - Returns the last row of the record set + * @param worksheet - The worksheet to search for the next record set. + * @param startingRow - The starting row to search for the next record set. + * @param lastRowNumber - The last row number to search for the next record set. + * @param id - The id to match the id value. + * @param lookupCol - The column to search for the id in the worksheet. + * @param opts - Options to customize the search process. * - * @example - * const newRow = createNewRowAfterRecordSet({ - * worksheet, - * startRowNumber: 2, - * identifierCol: "A" - * }); + * @returns {ExcelJS.Row | undefined} - The next record set if found, otherwise undefined. */ -export const createNewRowAfterRecordSet = ({ - worksheet, - startRowNumber, - identifierCol, -}: { - worksheet: ExcelJS.Worksheet; - startRowNumber: number; - identifierCol: string; -}): ExcelJS.Row => { - // Get rows for employee data - let prepRowNumber = startRowNumber; - let prepCellFieldValue = worksheet - .getRow(prepRowNumber) - .getCell(identifierCol).value; - while (prepCellFieldValue !== null && prepCellFieldValue !== "") { - prepRowNumber++; - prepCellFieldValue = worksheet - .getRow(prepRowNumber) - .getCell(identifierCol).value; - } - const previousRecordSet = prepRowNumber - 1; - - // Duplicate previous row - worksheet.insertRow(previousRecordSet + 1, [], "i+"); - const newRow = worksheet.getRow(previousRecordSet + 1); - newRow.eachCell({ includeEmpty: true }, (cell) => { - cell.value = ""; - }); - return newRow; -}; - -// Find next row with name that does not match current row name by id export const findNextRecordSetRowById = ({ worksheet, startingRow, @@ -429,7 +388,7 @@ export const findNextRecordSetRowById = ({ opts?: { findSimilarMatchWithLastDigits?: boolean; }; -}) => { +}): ExcelJS.Row | undefined => { const rows = worksheet.getRows(startingRow.number, lastRowNumber); if (!rows) { // throw new Error("No matching rows found"); @@ -451,7 +410,20 @@ export const findNextRecordSetRowById = ({ return nextMatchingRow; }; -// Find next record set that does not match current record by name +/** + * Finds the next record set in the worksheet based on the starting row by name. + * It searches for the next row that does not match the name. + * The name can be split into first name and last name or combined with a delimiter. + * It can also include conditions to match additional columns. + * + * @param worksheet - The worksheet to search for the next record set. + * @param startingRow - The starting row to search for the next record set. + * @param lastRowNumber - The last row number to search for the next record set. + * @param lookupCols - The columns to search for the name in the worksheet. + * @param conditions - The conditions to match additional columns. + * + * @returns {ExcelJS.Row | undefined} - The next record set if found, otherwise undefined. + */ export const findNextRecordSetRowByName = ({ worksheet, startingRow, @@ -476,7 +448,7 @@ export const findNextRecordSetRowByName = ({ col: string; condition: (cell: ExcelJS.Cell) => boolean; }[]; -}) => { +}): ExcelJS.Row | undefined => { let currentLastName = undefined; let currentFirstName = undefined; if ("lastName" in lookupCols && "firstName" in lookupCols) { @@ -563,3 +535,115 @@ export const findNextRecordSetRowByName = ({ return nextMatchingRow; }; + +/** + * Retrieves a record set (contiguous group of rows) from the worksheet that match a cell value at the identifier column and optional conditions + * + * @param worksheet - The worksheet to search for the matching rows. + * @param startRowNumber - The starting row number to search for the matching rows. + * @param identifierCol - The column to search for the identifier value in the worksheet. + * @param identifierValue - The value to match the identifier value. + * @param identifierCondition - The condition to match the identifier value. + * @param opts - Options to customize the search process. + * @param opts.findSimilarMatchWithLastDigits - Whether to find a similar match based on the last digits of the identifier value. + * + * @returns {ExcelJS.Row[]} - Returns the rows of the matching record set + * + * @example + * const rows = getRowsOfMatchingRecordSet({ + * worksheet, + * startRowNumber: 2, + * identifierCol: "A", + * identifierValue: "12345", + * opts: { findSimilarMatchWithLastDigits: true } + * }); + */ + +export const getRowsOfMatchingRecordSet = ({ + worksheet, + startRowNumber, + identifierCol, + identifierValue, + identifierCondition, + opts, +}: { + worksheet: ExcelJS.Worksheet; + startRowNumber: number; + lastRowNumber: number; + identifierCol: string; + identifierValue: string; + identifierCondition?: (identifierValue: string) => boolean; + opts?: { + findSimilarMatchWithLastDigits?: boolean; + }; +}): ExcelJS.Row[] => { + let rowNumber = startRowNumber; + let currentRow = worksheet.getRow(rowNumber); + let currentIdentifierValue = currentRow + .getCell(identifierCol) + .value?.toString(); + const rows = []; + while ( + currentIdentifierValue !== null && + currentIdentifierValue !== "" && + (currentIdentifierValue === identifierValue || + (opts?.findSimilarMatchWithLastDigits && + currentIdentifierValue?.endsWith(identifierValue)) || + (currentIdentifierValue && identifierCondition?.(currentIdentifierValue))) + ) { + rows.push(currentRow); + rowNumber++; + currentRow = worksheet.getRow(rowNumber); + currentIdentifierValue = currentRow + .getCell(identifierCol) + .value?.toString(); + } + return rows; +}; + +/** + * Retrieves a set of rows from the worksheet that match a cell value at the identifier column and optional conditions + * and returns the newly added row + * @param worksheet - The worksheet to search for the matching rows. + * @param startRowNumber - The starting row number to search for the matching rows. + * @param identifierCol - The column to search for the identifier value in the worksheet. + * + * @returns {ExcelJS.Row} - Returns the last row of the record set + * + * @example + * const newRow = createNewRowAfterRecordSet({ + * worksheet, + * startRowNumber: 2, + * identifierCol: "A" + * }); + */ +export const createNewRowAfterRecordSet = ({ + worksheet, + startRowNumber, + identifierCol, +}: { + worksheet: ExcelJS.Worksheet; + startRowNumber: number; + identifierCol: string; +}): ExcelJS.Row => { + // Get rows for employee data + let prepRowNumber = startRowNumber; + let prepCellFieldValue = worksheet + .getRow(prepRowNumber) + .getCell(identifierCol).value; + while (prepCellFieldValue !== null && prepCellFieldValue !== "") { + prepRowNumber++; + prepCellFieldValue = worksheet + .getRow(prepRowNumber) + .getCell(identifierCol).value; + } + const previousRecordSet = prepRowNumber - 1; + + // Duplicate previous row + worksheet.insertRow(previousRecordSet + 1, [], "i+"); + const newRow = worksheet.getRow(previousRecordSet + 1); + newRow.eachCell({ includeEmpty: true }, (cell) => { + cell.value = ""; + }); + return newRow; +}; diff --git a/src/workbook.ts b/src/workbook.ts index af374bb..1b3e7d4 100644 --- a/src/workbook.ts +++ b/src/workbook.ts @@ -63,22 +63,32 @@ export const exportWorkbook = async ({ password?: string; }; }) => { - if (opts?.currentWorksheet && opts.removeSharedFormulas) { - removeSharedFormulas(opts.currentWorksheet); - } - if (opts?.currentWorksheet && opts.setWorksheetViewId && opts.worksheetName) { - let worksheetId = -1; - workbook.worksheets.forEach((worksheet, index) => { - if (worksheet.name === opts.worksheetName) { - worksheetId = index + 1; + try { + if (opts?.currentWorksheet && opts.removeSharedFormulas) { + removeSharedFormulas(opts.currentWorksheet); + } + if ( + opts?.currentWorksheet && + opts.setWorksheetViewId && + opts.worksheetName + ) { + let worksheetId = -1; + workbook.worksheets.forEach((worksheet, index) => { + if (worksheet.name === opts.worksheetName) { + worksheetId = index + 1; + } + }); + if (worksheetId > 0) { + setWorksheetView(workbook, worksheetId); } - }); - if (worksheetId > 0) { - setWorksheetView(workbook, worksheetId); } + await workbook.xlsx.writeFile(outputPathname, { + password: opts?.password, + }); + console.log("Workbook exported", outputPathname); + } catch (error) { + console.log("Error exporting workbook", error); } - await workbook.xlsx.writeFile(outputPathname); - console.log("Workbook exported", outputPathname); }; export const removeSharedFormulas = (worksheet: ExcelJS.Worksheet) => {