printinggoogle-sheets-apispreadsheetgoogle-workspace

Page Totals and Running Totals on Export in Google Sheets


I'm looking to export a logbook of mine to paper. The logbook consists of entries of hours based on dates that continue row by row.

I need to export these entries through printing with each page containing page totals as well as totals carried forward from previous pages on the last row of each page. Each row of hours should have sum of the hours per page. [Mockup of Rough Export formatting][1]

I understand I could split the sheet up manually and sum the inputs that way. Is it possible to automatically format the hours with the rest of my logbook so that I am able to have this continue on as I update my logbook.

Thank you [1]: https://i.sstatic.net/Tpgb48mJ.png


Solution

  • This script will create a new sheet in your spreadsheet called "Print Logbook" (or clear it if it already exists) and populate it with data from your original logbook, automatically adding total rows and formatting them

    /**
     * @fileoverview This script generates a printable version of the logbook
     * with page totals and carried-over totals.
     * It reads data from a source sheet and creates a new formatted sheet for printing.
     */
    
    /**
     * Main function to generate the printable formatted logbook.
     * Reads data from the source sheet, calculates totals, and writes them to a new sheet.
     */
    function generatePrintableLogbook() {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      // Name of the sheet where your original logbook data is located.
      // Modify this value if your sheet name is different.
      const sourceSheetName = "Logbook"; // Changed
      // Name of the sheet that will be created or updated with the printable version.
      const printSheetName = "Printable Logbook"; // Changed
    
      const sourceSheet = ss.getSheetByName(sourceSheetName);
      if (!sourceSheet) {
        SpreadsheetApp.getUi().alert("Error: Source sheet '" + sourceSheetName + "' not found. Please ensure the name is correct."); // Translated message
        return;
      }
    
      // Create or get the print sheet. If it exists, clear its content.
      let printSheet = ss.getSheetByName(printSheetName);
      if (printSheet) {
        printSheet.clearContents(); // Clear existing content
      } else {
        printSheet = ss.insertSheet(printSheetName); // Insert a new sheet
      }
    
      // --- Configurations ---
      // Number of header rows in your source sheet (e.g., "Date", "Description", "Hours").
      const HEADER_ROWS = 1;
      // Index of the hours column (0-indexed). If hours are in column C, the index is 2.
      const HOURS_COLUMN_INDEX = 2;
      // Number of data rows for each printed "page".
      // Adjust this value based on how many rows you want on each physical page.
      const ROWS_PER_PAGE = 25;
      // Number of empty rows to insert between page blocks for visual separation.
      const EMPTY_ROWS_BETWEEN_PAGES = 3;
    
      // Get all data from the source sheet
      const sourceDataRange = sourceSheet.getDataRange();
      const allSourceValues = sourceDataRange.getValues();
    
      // Extract header rows
      const headers = allSourceValues.slice(0, HEADER_ROWS);
      // Extract actual logbook entries
      const logEntries = allSourceValues.slice(HEADER_ROWS);
    
      let outputRows = []; // Array that will hold all rows for the print sheet
      let cumulativeHours = 0; // Cumulative total of hours from previous pages
      let currentPageData = []; // Data for the current page
      let currentPageHours = 0; // Total hours for the current page
      let rowsOnCurrentPage = 0; // Counter for data rows on the current page
    
      // Iterate through the logbook entries
      for (let i = 0; i < logEntries.length; i++) {
        const row = logEntries[i];
        // Attempt to convert hours to a number, handling non-numeric values
        const hours = parseFloat(row[HOURS_COLUMN_INDEX]);
    
        // If starting a new page block (or it's the very first block)
        if (rowsOnCurrentPage === 0) {
          if (outputRows.length > 0) { // If not the very first block, add empty rows for separation
            for (let j = 0; j < EMPTY_ROWS_BETWEEN_PAGES; j++) {
              outputRows.push(new Array(sourceDataRange.getNumColumns()).fill(''));
            }
          }
          outputRows.push(...headers); // Add headers for the new page block
        }
    
        // Add the row to the current page's data
        currentPageData.push(row);
        // Update the total hours for the current page
        currentPageHours += isNaN(hours) ? 0 : hours;
        rowsOnCurrentPage++; // Increment the counter for rows on the current page
    
        const isLastEntry = (i === logEntries.length - 1); // Check if it's the last entry
    
        // If the page is full or it's the last entry, finalize the current page block
        if (rowsOnCurrentPage === ROWS_PER_PAGE || isLastEntry) {
          // Add the data rows for the current page to the final output
          outputRows.push(...currentPageData);
    
          // Add the "Page Total" row
          const pageTotalRow = new Array(sourceDataRange.getNumColumns()).fill('');
          pageTotalRow[0] = 'Page Total:'; // Translated text
          pageTotalRow[HOURS_COLUMN_INDEX] = currentPageHours;
          outputRows.push(pageTotalRow);
    
          // Add the "Carried Over Total" row
          const carriedOverTotalRow = new Array(sourceDataRange.getNumColumns()).fill('');
          carriedOverTotalRow[0] = 'Carried Over Total:'; // Translated text
          // The "Carried Over Total" is the cumulative total of hours *before* this page's hours
          carriedOverTotalRow[HOURS_COLUMN_INDEX] = cumulativeHours;
          outputRows.push(carriedOverTotalRow);
    
          // Update the cumulative total for the next page
          cumulativeHours += currentPageHours;
    
          // Reset counters and data for the next page
          currentPageData = [];
          currentPageHours = 0;
          rowsOnCurrentPage = 0;
        }
      }
    
      // Write all generated rows to the print sheet
      if (outputRows.length > 0) {
        printSheet.getRange(1, 1, outputRows.length, outputRows[0].length).setValues(outputRows);
      } else {
        SpreadsheetApp.getUi().alert("No data found in the source sheet for printing. Please ensure the sheet '" + sourceSheetName + "' contains data."); // Translated message
        return;
      }
    
      // --- Apply Formatting ---
      applyFormatting(printSheet, outputRows.length, sourceDataRange.getNumColumns(), HEADER_ROWS, ROWS_PER_PAGE, EMPTY_ROWS_BETWEEN_PAGES);
    
      SpreadsheetApp.getUi().alert("Logbook formatted for printing in sheet '" + printSheetName + "'."); // Translated message
    }
    
    /**
     * Applies formatting to the print sheet.
     * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet The sheet to apply formatting to.
     * @param {number} totalRows The total number of rows in the print sheet.
     * @param {number} totalCols The total number of columns in the print sheet.
     * @param {number} headerRows The number of header rows for each page block.
     * @param {number} rowsPerPage The number of data rows per page.
     * @param {number} emptyRowsBetweenPages The number of empty rows between page blocks.
     */
    function applyFormatting(sheet, totalRows, totalCols, headerRows, rowsPerPage, emptyRowsBetweenPages) {
      // Set column widths (adjust these values if necessary)
      sheet.setColumnWidth(1, 100); // Column A: Date
      sheet.setColumnWidth(2, 300); // Column B: Description
      sheet.setColumnWidth(3, 80);  // Column C: Hours
    
      // Apply bold and borders to header and total rows
      let currentOutputRow = 1; // Start from the first row of the output sheet
    
      // Iterate through page blocks to apply formatting
      while (currentOutputRow <= totalRows) {
        // Apply bold to headers
        sheet.getRange(currentOutputRow, 1, headerRows, totalCols).setFontWeight('bold');
        currentOutputRow += headerRows; // Move pointer past headers
    
        // Skip data rows (they will be formatted as normal text)
        currentOutputRow += rowsPerPage;
    
        // Apply formatting to "Page Total" and "Carried Over Total" rows
        if (currentOutputRow <= totalRows) { // Check if "Page Total" row exists
          sheet.getRange(currentOutputRow, 1, 1, totalCols).setFontWeight('bold').setBorder(true, true, true, true, false, false);
          currentOutputRow++; // Move pointer to the next row
    
          if (currentOutputRow <= totalRows) { // Check if "Carried Over Total" row exists
            sheet.getRange(currentOutputRow, 1, 1, totalCols).setFontWeight('bold').setBorder(true, true, true, true, false, false);
            currentOutputRow++; // Move pointer to the next row
          }
        }
    
        // Skip empty separator rows to move to the next page block
        currentOutputRow += emptyRowsBetweenPages;
      }
    }
    
    /**
     * Creates a custom menu in Google Sheets to easily run the script.
     * This function runs automatically when the Google Sheet is opened.
     */
    function onOpen() {
      const ui = SpreadsheetApp.getUi();
      ui.createMenu('Logbook') // Changed from "Diario di Bordo"
          .addItem('Generate for Print', 'generatePrintableLogbook') // Translated item name
          .addToUi();
    }
    

    Code Explanation:

    Now, every time you update your original logbook, you just need to run the script to get a print-ready version with all totals calculated and formatted automatically.