Skip to content

Commit

Permalink
Date and numeric filter in AG grid.
Browse files Browse the repository at this point in the history
  • Loading branch information
kreinhard committed Feb 16, 2025
1 parent f026175 commit b8c7cc6
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 46 deletions.
2 changes: 1 addition & 1 deletion ToDo.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
==== Aktuell:
- I18n for AI grid.
- Doppelter E-Mailversand bei Urlaubseinträgen (deutsch und englisch).
- JCR: Tool for removing or recovering orphaned nodes.
- Favoriten bei Scriptausführung für Parameter.
- Viewpage für user für non-admins.
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ com-google-zxing-javase = "3.5.3"
com-googlecode-ez-vcard = "0.12.1"
com-googlecode-gson = "2.11.0"
com-googlecode-json-simple = "1.1.1"
com-googlecode-lanterna = "3.1.2"
com-googlecode-lanterna = "3.1.3"
com-itextpdf = "5.5.13.4"
com-thoughtworks-xstream = "1.4.21"
com-webauthn4j-core = "0.28.2.RELEASE"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
6 changes: 3 additions & 3 deletions projectforge-application/src/main/resources/i18nKeys.json
Original file line number Diff line number Diff line change
Expand Up @@ -1725,12 +1725,12 @@
{"i18nKey":"ok","bundleName":"I18nResources","translation":"OK","translationDE":"OK","usedInClasses":["org.projectforge.framework.utils.ResultHolderStatus"],"usedInFiles":[]},
{"i18nKey":"onlyDeleted","bundleName":"I18nResources","translation":"only deleted","translationDE":"nur gelöschte","usedInClasses":["org.projectforge.web.humanresources.HRPlanningEditForm","org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]},
{"i18nKey":"onlyDeleted.tooltip","bundleName":"I18nResources","translation":"Only deleted entries will be displayed (in general independent of any other filter settings).","translationDE":"Es werden nur gelöschte Datensätze angezeigt (i. d. R. unabhängig von anderen Filterangaben).","usedInClasses":["org.projectforge.web.wicket.AbstractListForm"],"usedInFiles":[]},
{"i18nKey":"operation.deleted","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.inserted","bundleName":"I18nResources","translation":"inserted","translationDE":"angelegt","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.deleted","bundleName":"I18nResources","translation":"deleted","translationDE":"gelöscht","usedInClasses":["org.projectforge.framework.persistence.history.DisplayHistoryEntry","org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.inserted","bundleName":"I18nResources","translation":"inserted","translationDE":"angelegt","usedInClasses":["org.projectforge.framework.persistence.history.DisplayHistoryEntry","org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.markAsDeleted","bundleName":"I18nResources","translation":"marked as deleted","translationDE":"als gelöscht markiert","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.undefined","bundleName":"I18nResources","translation":"undefined","translationDE":"undefiniert","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.undeleted","bundleName":"I18nResources","translation":"undeleted","translationDE":"wiederhergestellt","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService"],"usedInFiles":[]},
{"i18nKey":"operation.updated","bundleName":"I18nResources","translation":"updated","translationDE":"geändert","usedInClasses":["org.projectforge.framework.persistence.history.HistoryFormatService","org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]},
{"i18nKey":"operation.updated","bundleName":"I18nResources","translation":"updated","translationDE":"geändert","usedInClasses":["org.projectforge.framework.persistence.history.DisplayHistoryEntry","org.projectforge.framework.persistence.history.HistoryFormatService","org.projectforge.rest.my2fa.My2FASetupPageRest"],"usedInFiles":[]},
{"i18nKey":"orga.post.inhalt","bundleName":"I18nResources","translation":"Content","translationDE":"Inhalt","usedInClasses":["org.projectforge.business.orga.PostausgangDO","org.projectforge.business.orga.PosteingangDO"],"usedInFiles":[]},
{"i18nKey":"orga.post.type","bundleName":"I18nResources","translation":"Type","translationDE":"Art","usedInClasses":["org.projectforge.business.orga.PostausgangDO","org.projectforge.business.orga.PosteingangDO"],"usedInFiles":[]},
{"i18nKey":"orga.post.type.brief","bundleName":"I18nResources","translation":"Letter","translationDE":"Brief","usedInClasses":["org.projectforge.business.orga.PostType"],"usedInFiles":[]},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.common.props;
package org.projectforge.common.props

/**
* If the type of a field isn't represented by the Java type it may be defined in more detail by this enum. For example a BigDecimal may
* represent a currency value.
* @author Kai Reinhard ([email protected])
*/
public enum PropertyType
{
CURRENCY, DATE, DATE_TIME, DATE_TIME_SECONDS, DATE_TIME_MILLIS,
/**
* Use INPUT for long text fields if you wish to use input fields instead of text areas.
*/
INPUT, TIME, TIME_SECONDS, TIME_MILLIS, UNSPECIFIED;
enum class PropertyType {
CURRENCY, DATE, DATE_TIME, DATE_TIME_SECONDS, DATE_TIME_MILLIS,

/**
* Use INPUT for long text fields if you wish to use input fields instead of text areas.
*/
INPUT, TIME, TIME_SECONDS, TIME_MILLIS, UNSPECIFIED;

fun isIn(vararg types: PropertyType): Boolean {
return types.any { it == this }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class HistoryEntryUserCommentModalRest {
val historyEntry = item.entry ?: return ResponseEntity(HttpStatus.NOT_FOUND)
val dto = HistoryData(historyFormatService.convert(entity, historyEntry, HistoryLoadContext(item.baseDao)))
val titleKey = "history.entry"
val ui = UILayout(titleKey, RestResolver.getRestUrl(this::class.java, withoutPrefix = true))
val ui = UILayout(titleKey)
ui.userAccess.update = item.writeAccess
ui.userAccess.history = item.readAccess
ui.add(UIReadOnlyField("timeAgo", label = "modified"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class VisitorbookPagesRest : AbstractDTOPagesRest<VisitorbookDO, Visitorbook, Vi
.add("lastDateOfVisit", headerName = "orga.visitorbook.lastVisit")
.add("latestArrived", headerName = "orga.visitorbook.lastVisit.arrived")
.add("latestDeparted", headerName = "orga.visitorbook.lastVisit.departed")
.add("numberOfVisits", headerName = "orga.visitorbook.numberOfVisits")
.add("numberOfVisits", headerName = "orga.visitorbook.numberOfVisits", type = UIAgGridColumnDef.Type.NUMBER)
.add("contactPersonsAsString", headerName = "orga.visitorbook.contactPersons")
.add(lc, "comment")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ open class UIAgGrid(
wrapText: Boolean? = null,
autoHeight: Boolean? = wrapText,
tooltipField: String? = null,
type: UIAgGridColumnDef.Type? = null,
): UIAgGrid {
add(
UIAgGridColumnDef.createCol(
Expand All @@ -179,6 +180,7 @@ open class UIAgGrid(
wrapText = wrapText,
autoHeight = autoHeight,
tooltipField = tooltipField,
type = type,
)
)
return this
Expand All @@ -201,6 +203,7 @@ open class UIAgGrid(
lcField: String? = null,
wrapText: Boolean? = null,
autoHeight: Boolean? = wrapText,
type: UIAgGridColumnDef.Type? = null,
): UIAgGrid {
columnIds.forEach {
add(
Expand All @@ -215,6 +218,7 @@ open class UIAgGrid(
lcField = lcField ?: it,
wrapText = wrapText,
autoHeight = autoHeight,
type = type
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ open class UIAgGridColumnDef(
var headerTooltip: String? = null,
var sortable: Boolean = false,
/**
* If false, no auto-filter is shown.
* If true, auto-filter is shown, if false, no auto-filter is shown. AG grid supports also string values
* such as "agNumberColumnFilter" or "agSetColumnFilter".
*/
var filter: Boolean = true,
var filter: Any? = true,
var valueGetter: String? = null,
var type: String? = null,
var minWidth: Int? = null,
Expand All @@ -67,6 +68,12 @@ open class UIAgGridColumnDef(
var wrapText: Boolean? = null,
var autoHeight: Boolean? = wrapText,
) {
enum class Type {
NUMBER
}
class FilterParams {
var buttons: Array<String>? = null
}

var pinned: String? = null

Expand All @@ -81,6 +88,8 @@ open class UIAgGridColumnDef(

var suppressSizeToFit: Boolean? = null

var filterParams: FilterParams? = null

/**
* https://www.ag-grid.com/react-data-grid/column-definitions/#right-aligned-and-numeric-columns
*/
Expand Down Expand Up @@ -149,6 +158,13 @@ open class UIAgGridColumnDef(
return this
}

fun setApplyAndResetButton(): FilterParams {
filterParams = filterParams ?: FilterParams()
return filterParams!!.also {
it.buttons = arrayOf("apply", "reset")
}
}

companion object {
fun createCol(
property: KProperty<*>,
Expand Down Expand Up @@ -265,6 +281,7 @@ open class UIAgGridColumnDef(
autoHeight: Boolean? = wrapText,
valueIconMap: Map<Any, UIIconType?>? = null,
tooltipField: String? = null,
type: Type? = null,
): UIAgGridColumnDef {
val col = UIAgGridColumnDef(field, sortable = sortable, wrapText = wrapText, autoHeight = autoHeight)
lc?.idPrefix?.let {
Expand All @@ -285,7 +302,7 @@ open class UIAgGridColumnDef(
if (col.headerName == null) {
col.headerName = translate(elementInfo.i18nKey)
}
if (useFormatter == null) {
if (type == null && useFormatter == null) {
// Try to determine formatter by type and propertyInfo (defined on DO-field):
if (Number::class.java.isAssignableFrom(elementInfo.propertyClass)) {
if (elementInfo.propertyType == PropertyType.CURRENCY) {
Expand All @@ -294,7 +311,11 @@ open class UIAgGridColumnDef(
useFormatter = Formatter.NUMBER
}
} else if (elementInfo.propertyClass == LocalDate::class.java) {
useFormatter = Formatter.DATE
if (width == null) {
col.width = DATE_WIDTH
}
col.filter = "agDateColumnFilter"
col.setApplyAndResetButton()
} else if (java.util.Date::class.java == elementInfo.propertyClass) {
if (field in arrayOf("created", "lastUpdate")) {
useFormatter = Formatter.DATE
Expand Down Expand Up @@ -326,6 +347,18 @@ open class UIAgGridColumnDef(
col.valueGetter = "data?.${col.field}?.displayName"
}
}
if (type != null) {
when (type) {
Type.NUMBER -> {
if (width == null) {
col.width = NUMBER_WIDTH
}
col.type = AG_TYPE.NUMERIC_COLUMN.agType
col.filter = "agNumberColumnFilter"
col.setApplyAndResetButton()
}
}
}
if (width != null) {
col.width = width
}
Expand All @@ -334,28 +367,39 @@ open class UIAgGridColumnDef(
Formatter.CURRENCY -> {
if (width == null) {
col.width = CURRENCY_WIDTH
col.type = AG_TYPE.NUMERIC_COLUMN.agType
}
col.type = AG_TYPE.NUMERIC_COLUMN.agType
col.filter = "agNumberColumnFilter"
}

Formatter.NUMBER -> {
if (width == null) {
col.width = NUMBER_WIDTH
col.type = AG_TYPE.NUMERIC_COLUMN.agType
}
}

Formatter.DATE -> {
col.width = DATE_WIDTH
col.type = AG_TYPE.NUMERIC_COLUMN.agType
col.filter = "agNumberColumnFilter"
col.setApplyAndResetButton()
}

Formatter.CONSUMPTION -> {
col.width = 80
if (width == null) {
col.width = 80
} else {
}
}

else -> {}
else -> {
}
}
}
if (useFormatter == null
&& elementInfo?.propertyType?.isIn(PropertyType.INPUT, PropertyType.UNSPECIFIED) == true
&& (elementInfo.maxLength ?: 0) > 256
) {
// Use text filter for long texts.
col.filter = "agTextColumnFilter"
col.setApplyAndResetButton()
}
valueGetter?.let { col.valueGetter = it }
var myParams: MutableMap<String, Any>? = null
useFormatter?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,14 @@ import kotlin.reflect.KProperty
class UILayout(
title: String,
/** restBaseUrl is needed, if [UIAttachmentList] is used. */
@Suppress("unused") // Needed by React frontend.
var restBaseUrl: String? = null,
) : IUIContainer {
class UserAccess(
/**
* The user has access to the object's history, if given.
*/
var history: Boolean? = null,
/**
* Is the edit user-comment button visible?
* @see [org.projectforge.framework.persistence.api.BaseDao.supportsHistoryUserComments]
*/
var editHistoryComments: Boolean? = null,
/**
* The user has access to insert new objects.
*/
Expand All @@ -54,6 +50,11 @@ class UILayout(
* Cancel button is visible for all users at default.
*/
var cancel: Boolean? = true,
/**
* Is the edit user-comment button visible?
* @see [org.projectforge.framework.persistence.api.BaseDao.supportsHistoryUserComments]
*/
var editHistoryComments: Boolean? = null,
) {
fun copyFrom(userAccess: UserAccess?) {
this.history = userAccess?.history
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import AwesomeDebouncePromise from 'awesome-debounce-promise';
import PropTypes from 'prop-types';
import React, { useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef, useEffect, useState } from 'react';
import { AgGridReact } from 'ag-grid-react';
import { LicenseManager, ModuleRegistry, AllEnterpriseModule, themeBalham } from 'ag-grid-enterprise';
import { connect } from 'react-redux';
Expand Down Expand Up @@ -64,6 +64,7 @@ function DynamicAgGrid(props) {
const [gridApi, setGridApi] = useState();
const gridRef = useRef();
// const gridStyle = React.useMemo(() => ({ width: '100%' }), []);
const [processedColumnDefs, setProcessedColumnDefs] = useState([]);
const rowData = entries || Object.getByString(data, id) || Object.getByString(variables, id) || '';
const { selectedEntityIds } = data;
/*
Expand Down Expand Up @@ -116,6 +117,35 @@ function DynamicAgGrid(props) {
}
}, [gridApi, data.highlightRowId, highlightId]);

useEffect(() => {
// Gehe durch alle columnDefs und setze den comparator für agDateColumnFilter
const updatedColumnDefs = columnDefs.map((col) => {
if (col.filter === 'agDateColumnFilter') {
return {
...col,
filterParams: {
...col.filterParams,
comparator: (filterLocalDateAtMidnight, cellValue) => {
if (!cellValue) return -1;

// Wandelt "YYYY-MM-DD" in ein JS Date-Objekt um
const [year, month, day] = cellValue.split('-');
const cellDate = new Date(Number(year), Number(month) - 1, Number(day));

if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
return 0;
}
return cellDate < filterLocalDateAtMidnight ? -1 : 1;
},
},
};
}
return col;
});

setProcessedColumnDefs(updatedColumnDefs);
}, [columnDefs]);

/*
React.useEffect(() => {
showHighlightedRow();
Expand Down Expand Up @@ -157,10 +187,6 @@ function DynamicAgGrid(props) {
}
const redirectUrl = modifyRedirectUrl(rowClickRedirectUrl, event.data.id);
if (rowClickOpenModal) {
// const historyState = { serverData: action.variables };
// TODO: Fin, wie bekomme ich action.variables hier? Wenn das Modal geschlossen wird,
// kann ich nicht mehr das Formular ändern.
// Ich wollte das von hier kopieren: form.js:121
const historyState = { };

historyState.background = history.location;
Expand All @@ -171,11 +197,6 @@ function DynamicAgGrid(props) {
}
};

/*
const onFirstDataRendered = () => {
showHighlightedRow();
}; */

const onSelectionChanged = () => {
if (!rowClickRedirectUrl) {
// Do nothing
Expand Down Expand Up @@ -293,7 +314,7 @@ function DynamicAgGrid(props) {
ref={gridRef}
rowData={rowData}
components={allComponents}
columnDefs={columnDefs}
columnDefs={processedColumnDefs}
selectionColumnDef={selectionColumnDef}
rowSelection={rowSelection}
onGridReady={onGridReady}
Expand Down Expand Up @@ -342,7 +363,7 @@ function DynamicAgGrid(props) {
</div>
),
[
columnDefs,
processedColumnDefs,
data,
selectionColumnDef,
sortModel,
Expand All @@ -362,7 +383,6 @@ DynamicAgGrid.propTypes = {
pinned: PropTypes.string,
resizable: PropTypes.bool,
sortable: PropTypes.bool,
filter: PropTypes.bool,
})),
id: PropTypes.string,
entries: PropTypes.arrayOf(PropTypes.shape()),
Expand Down

0 comments on commit b8c7cc6

Please sign in to comment.