diff --git a/Core/API/Parameter/Parameter.class.php b/Core/API/Parameter/Parameter.class.php
index a85e93a..3b1afe7 100644
--- a/Core/API/Parameter/Parameter.class.php
+++ b/Core/API/Parameter/Parameter.class.php
@@ -100,14 +100,14 @@ class Parameter {
return $str;
}
- public static function parseType($value): int {
+ public static function parseType(mixed $value, bool $strict = false): int {
if (is_array($value))
return Parameter::TYPE_ARRAY;
- else if (is_numeric($value) && intval($value) == $value)
+ else if (is_int($value) || (!$strict && is_numeric($value) && intval($value) == $value))
return Parameter::TYPE_INT;
- else if (is_float($value) || (is_numeric($value) && floatval($value) == $value))
+ else if (is_float($value) || (!$strict && is_numeric($value) && floatval($value) == $value))
return Parameter::TYPE_FLOAT;
- else if (is_bool($value) || $value == "true" || $value == "false")
+ else if (is_bool($value) || (!$strict && ($value == "true" || $value == "false")))
return Parameter::TYPE_BOOLEAN;
else if (is_a($value, 'DateTime'))
return Parameter::TYPE_DATE_TIME;
diff --git a/Core/Driver/SQL/MySQL.class.php b/Core/Driver/SQL/MySQL.class.php
index 0ffa581..ba9fd71 100644
--- a/Core/Driver/SQL/MySQL.class.php
+++ b/Core/Driver/SQL/MySQL.class.php
@@ -91,7 +91,7 @@ class MySQL extends SQL {
private function getPreparedParams($values): array {
$sqlParams = array('');
foreach ($values as $value) {
- $paramType = Parameter::parseType($value);
+ $paramType = Parameter::parseType($value, true); // TODO: is strict type checking really correct here?
switch ($paramType) {
case Parameter::TYPE_BOOLEAN:
$value = $value ? 1 : 0;
diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php
index 8b4532f..9f31b92 100644
--- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php
+++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php
@@ -342,7 +342,9 @@ class DatabaseEntityHandler implements Persistable {
$value = $row[$columnName];
if ($column instanceof DateTimeColumn) {
- $value = new \DateTime($value);
+ if ($value !== null) {
+ $value = new \DateTime($value);
+ }
} else if ($column instanceof JsonColumn) {
$value = json_decode($value, true);
} else if (isset($this->relations[$propertyName])) {
diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php
index 051d8ed..f8b50ec 100644
--- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php
+++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php
@@ -138,7 +138,7 @@ class DatabaseEntityQuery extends Select {
}
}
- public function execute(): DatabaseEntity|array|null {
+ public function execute(): DatabaseEntity|array|null|false {
if ($this->logVerbose) {
$params = [];
@@ -148,7 +148,7 @@ class DatabaseEntityQuery extends Select {
$res = parent::execute();
if ($res === null || $res === false) {
- return null;
+ return $res;
}
if ($this->resultType === SQL::FETCH_ALL) {
diff --git a/react/shared/api.js b/react/shared/api.js
index 0088234..2e75552 100644
--- a/react/shared/api.js
+++ b/react/shared/api.js
@@ -281,7 +281,7 @@ export default class API {
/** ApiKeyAPI **/
async getApiKeys(showActiveOnly = false, page = 1, count = 25, orderBy = "validUntil", sortOrder = "desc") {
- return this.apiCall("apiKey/fetch", { showActiveOnly: showActiveOnly, pageNum: page, count: count, orderBy: orderBy, sortOrder: sortOrder });
+ return this.apiCall("apiKey/fetch", { showActiveOnly: showActiveOnly, page: page, count: count, orderBy: orderBy, sortOrder: sortOrder });
}
async createApiKey() {
diff --git a/react/shared/elements/data-table.js b/react/shared/elements/data-table.js
index ca52b5f..59673ef 100644
--- a/react/shared/elements/data-table.js
+++ b/react/shared/elements/data-table.js
@@ -6,7 +6,7 @@ import "./data-table.css";
import {LocaleContext} from "../locale";
import clsx from "clsx";
import {Box, IconButton} from "@mui/material";
-import {formatDateTime} from "../util";
+import {formatDate, formatDateTime} from "../util";
import CachedIcon from "@material-ui/icons/Cached";
@@ -23,31 +23,35 @@ export function DataTable(props) {
const [doFetchData, setFetchData] = useState(false);
const [sortAscending, setSortAscending] = useState(["asc","ascending"].includes(defaultSortOrder?.toLowerCase));
const [sortColumn, setSortColumn] = useState(defaultSortColumn || null);
- const sortable = props.hasOwnProperty("sortable") ? !!props.sortable : true;
+ const sortable = !!fetchData && (props.hasOwnProperty("sortable") ? !!props.sortable : true);
const onRowClick = onClick || (() => {});
const onFetchData = useCallback((force = false) => {
- if (doFetchData || force) {
- setFetchData(false);
- const orderBy = columns[sortColumn]?.field || null;
- const sortOrder = sortAscending ? "asc" : "desc";
- fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder);
+ if (fetchData) {
+ if (doFetchData || force) {
+ setFetchData(false);
+ const orderBy = columns[sortColumn]?.field || null;
+ const sortOrder = sortAscending ? "asc" : "desc";
+ fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder);
+ }
}
- }, [doFetchData, columns, sortColumn, sortAscending, pagination]);
+ }, [fetchData, doFetchData, columns, sortColumn, sortAscending, pagination]);
// pagination changed?
useEffect(() => {
- let forceFetch = false;
- if (pagination.getPageSize() < pagination.getTotal()) {
- // page size is smaller than the total count
- forceFetch = true;
- } else if (data?.length && pagination.getPageSize() >= data.length && data.length < pagination.getTotal()) {
- // page size is greater than the current visible count but there were hidden rows before
- forceFetch = true;
- }
+ if (pagination) {
+ let forceFetch = false;
+ if (pagination.getPageSize() < pagination.getTotal()) {
+ // page size is smaller than the total count
+ forceFetch = true;
+ } else if (data?.length && pagination.getPageSize() >= data.length && data.length < pagination.getTotal()) {
+ // page size is greater than the current visible count but there were hidden rows before
+ forceFetch = true;
+ }
- onFetchData(forceFetch);
- }, [pagination.data.pageSize, pagination.data.current]);
+ onFetchData(forceFetch);
+ }
+ }, [pagination?.data?.pageSize, pagination?.data?.current]);
// sorting changed
useEffect(() => {
@@ -112,12 +116,17 @@ export function DataTable(props) {
}
return
-
- onFetchData(true)}>
-
-
- {title}
-
+ {title ?
+
+ {fetchData ?
+ onFetchData(true)}>
+
+
+ : <>>
+ }
+ {title}
+
: <>>
+ }
@@ -128,7 +137,7 @@ export function DataTable(props) {
{ rows }
- {pagination.renderPagination(L, numRows)}
+ {pagination && pagination.renderPagination(L, numRows)}
}
@@ -210,6 +219,17 @@ export class DateTimeColumn extends DataColumn {
}
}
+export class DateColumn extends DataColumn {
+ constructor(label, field = null, params = {}) {
+ super(label, field, params);
+ }
+
+ renderData(L, entry, index) {
+ let date = super.renderData(L, entry);
+ return formatDate(L, date);
+ }
+}
+
export class BoolColumn extends DataColumn {
constructor(label, field = null, params = {}) {
super(label, field, params);
diff --git a/react/shared/elements/dialog.jsx b/react/shared/elements/dialog.jsx
index c126617..4144210 100644
--- a/react/shared/elements/dialog.jsx
+++ b/react/shared/elements/dialog.jsx
@@ -1,4 +1,4 @@
-import React, {useState} from "react";
+import React, {useEffect, useState} from "react";
import {
Box,
Button,
@@ -20,11 +20,21 @@ export default function Dialog(props) {
const [inputData, setInputData] = useState({});
+ useEffect(() => {
+ if (props.inputs) {
+ let initialData = {};
+ for (const input of props.inputs) {
+ initialData[input.name] = input.value || "";
+ }
+ setInputData(initialData);
+ }
+ }, [props.inputs]);
+
let buttons = [];
for (const [index, name] of options.entries()) {
buttons.push(
)
@@ -40,6 +50,7 @@ export default function Dialog(props) {
case 'text':
inputElements.push( {
+ if (!searchString || searchString.length < minLength) {
+ setResults([]);
+ return;
+ }
+
+ callback(searchString).then(results => {
+ setResults(results || null);
+ });
+ }, [searchString]);
+
+ return [searchString, setSearchString, results];
+}
\ No newline at end of file