From 08ed9a5aad26dcbadfe29680af086187782d8755 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 18 Mar 2025 10:18:28 -0500 Subject: [PATCH] Add style customizer to report action, with excel poi implementation for columnWidths, more cell styles, merged ranges --- .../reporting/ExportStreamerInterface.java | 8 ++ .../ExportStyleCustomizerInterface.java | 35 +++++++++ .../reporting/GenerateReportAction.java | 8 +- .../excel/poi/StreamedSheetWriter.java | 49 ++++++++++-- .../model/actions/reporting/ReportInput.java | 33 ++++++++ .../reporting/excel/TestExcelStyler.java | 76 +++++++++++++++++++ 6 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStyleCustomizerInterface.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/reporting/excel/TestExcelStyler.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java index 4e2f7e02..eebfe4e0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java @@ -54,6 +54,14 @@ public interface ExportStreamerInterface // noop in base class } + /*************************************************************************** + ** + ***************************************************************************/ + default void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer) + { + // noop in base class + } + /******************************************************************************* ** Called once per sheet, before any rows are available. Meant to write a ** header, for example. diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStyleCustomizerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStyleCustomizerInterface.java new file mode 100644 index 00000000..23d15ef3 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStyleCustomizerInterface.java @@ -0,0 +1,35 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.reporting; + + +/******************************************************************************* + ** interface for classes that can be used to customize visual style aspects of + ** exports/reports. + ** + ** Anticipates very different sub-interfaces based on the file type being generated, + ** and the capabilities of each. e.g., excel (bolds, fonts, cell merging) vs + ** json (different structure of objects). + *******************************************************************************/ +public interface ExportStyleCustomizerInterface +{ +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java index b1e28903..07863fd0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java @@ -163,6 +163,12 @@ public class GenerateReportAction extends AbstractQActionFunction - - """); + """); + if(styleCustomizerInterface != null && view != null) + { + List columnWidths = styleCustomizerInterface.getColumnWidthsForView(view); + if(CollectionUtils.nullSafeHasContents(columnWidths)) + { + writer.write(""); + for(int i = 0; i < columnWidths.size(); i++) + { + writer.write(""" + + """.formatted(i + 1, i + 1, columnWidths.get(i))); + } + writer.write(""); + } + } + + writer.write(""); } @@ -67,11 +86,25 @@ public class StreamedSheetWriter /******************************************************************************* ** *******************************************************************************/ - public void endSheet() throws IOException + public void endSheet(QReportView view, ExcelPoiStyleCustomizerInterface styleCustomizerInterface) throws IOException { - writer.write(""" - - """); + writer.write(""); + + if(styleCustomizerInterface != null && view != null) + { + List mergedRanges = styleCustomizerInterface.getMergedRangesForView(view); + if(CollectionUtils.nullSafeHasContents(mergedRanges)) + { + writer.write(String.format("", mergedRanges.size())); + for(String range : mergedRanges) + { + writer.write(String.format("", range)); + } + writer.write(""); + } + } + + writer.write(""); } @@ -151,7 +184,7 @@ public class StreamedSheetWriter { rs.append("""); } - else if (c < 32 && c != '\t' && c != '\n') + else if(c < 32 && c != '\t' && c != '\n') { rs.append(' '); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java index be901e94..6c26a7b9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.function.Supplier; import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; @@ -44,6 +45,7 @@ public class ReportInput extends AbstractTableActionInput private ReportDestination reportDestination; private Supplier overrideExportStreamerSupplier; + private QCodeReference exportStyleCustomizer; @@ -208,4 +210,35 @@ public class ReportInput extends AbstractTableActionInput return (this); } + + + /******************************************************************************* + ** Getter for exportStyleCustomizer + *******************************************************************************/ + public QCodeReference getExportStyleCustomizer() + { + return (this.exportStyleCustomizer); + } + + + + /******************************************************************************* + ** Setter for exportStyleCustomizer + *******************************************************************************/ + public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer) + { + this.exportStyleCustomizer = exportStyleCustomizer; + } + + + + /******************************************************************************* + ** Fluent setter for exportStyleCustomizer + *******************************************************************************/ + public ReportInput withExportStyleCustomizer(QCodeReference exportStyleCustomizer) + { + this.exportStyleCustomizer = exportStyleCustomizer; + return (this); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/reporting/excel/TestExcelStyler.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/reporting/excel/TestExcelStyler.java new file mode 100644 index 00000000..c45061db --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/reporting/excel/TestExcelStyler.java @@ -0,0 +1,76 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.reporting.excel; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiStyleCustomizerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestExcelStyler implements ExcelPoiStyleCustomizerInterface +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List getColumnWidthsForView(QReportView view) + { + return List.of(60, 50, 40); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List getMergedRangesForView(QReportView view) + { + return List.of("A1:B1"); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void customizeStyles(Map styles, XSSFWorkbook workbook, CreationHelper createHelper) + { + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 16); + font.setBold(true); + XSSFCellStyle cellStyle = workbook.createCellStyle(); + cellStyle.setFont(font); + styles.put("header", cellStyle); + } +}