mysql param types, async search, bugfix
This commit is contained in:
		
							parent
							
								
									92c78356ed
								
							
						
					
					
						commit
						424a945fa6
					
				@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -342,7 +342,9 @@ class DatabaseEntityHandler implements Persistable {
 | 
			
		||||
 | 
			
		||||
    $value = $row[$columnName];
 | 
			
		||||
    if ($column instanceof DateTimeColumn) {
 | 
			
		||||
      if ($value !== null) {
 | 
			
		||||
        $value = new \DateTime($value);
 | 
			
		||||
      }
 | 
			
		||||
    } else if ($column instanceof JsonColumn) {
 | 
			
		||||
      $value = json_decode($value, true);
 | 
			
		||||
    } else if (isset($this->relations[$propertyName])) {
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
 | 
			
		||||
@ -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,20 +23,23 @@ 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 (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(() => {
 | 
			
		||||
        if (pagination) {
 | 
			
		||||
            let forceFetch = false;
 | 
			
		||||
            if (pagination.getPageSize() < pagination.getTotal()) {
 | 
			
		||||
                // page size is smaller than the total count
 | 
			
		||||
@ -47,7 +50,8 @@ export function DataTable(props) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            onFetchData(forceFetch);
 | 
			
		||||
    }, [pagination.data.pageSize, pagination.data.current]);
 | 
			
		||||
        }
 | 
			
		||||
    }, [pagination?.data?.pageSize, pagination?.data?.current]);
 | 
			
		||||
 | 
			
		||||
    // sorting changed
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
@ -112,12 +116,17 @@ export function DataTable(props) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return <Box position={"relative"}>
 | 
			
		||||
            {title ?
 | 
			
		||||
                <h3>
 | 
			
		||||
                    {fetchData ?
 | 
			
		||||
                        <IconButton onClick={() => onFetchData(true)}>
 | 
			
		||||
                            <CachedIcon/>
 | 
			
		||||
                        </IconButton>
 | 
			
		||||
                        : <></>
 | 
			
		||||
                    }
 | 
			
		||||
                    {title}
 | 
			
		||||
            </h3>
 | 
			
		||||
                </h3> : <></>
 | 
			
		||||
            }
 | 
			
		||||
            <Table className={clsx("data-table", className)} size="small" {...other}>
 | 
			
		||||
                <TableHead>
 | 
			
		||||
                    <TableRow>
 | 
			
		||||
@ -128,7 +137,7 @@ export function DataTable(props) {
 | 
			
		||||
                    { rows }
 | 
			
		||||
                </TableBody>
 | 
			
		||||
            </Table>
 | 
			
		||||
        {pagination.renderPagination(L, numRows)}
 | 
			
		||||
        {pagination && pagination.renderPagination(L, numRows)}
 | 
			
		||||
    </Box>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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(
 | 
			
		||||
            <Button variant={"outlined"} size={"small"} key={"button-" + name}
 | 
			
		||||
                    onClick={() => { onClose(); onOption(index, inputData); }}>
 | 
			
		||||
                    onClick={() => { onClose(); onOption(index, inputData); setInputData({}); }}>
 | 
			
		||||
                {name}
 | 
			
		||||
            </Button>
 | 
			
		||||
        )
 | 
			
		||||
@ -40,6 +50,7 @@ export default function Dialog(props) {
 | 
			
		||||
            case 'text':
 | 
			
		||||
                inputElements.push(<TextField
 | 
			
		||||
                    {...inputProps}
 | 
			
		||||
                    sx={{marginTop: 1}}
 | 
			
		||||
                    size={"small"} fullWidth={true}
 | 
			
		||||
                    key={"input-" + input.name}
 | 
			
		||||
                    value={inputData[input.name] || ""}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								react/shared/hooks/async-search.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										21
									
								
								react/shared/hooks/async-search.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
import {useEffect, useState} from "react";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default function useAsyncSearch(callback, minLength = 1) {
 | 
			
		||||
 | 
			
		||||
    const [searchString, setSearchString] = useState("");
 | 
			
		||||
    const [results, setResults] = useState(null);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!searchString || searchString.length < minLength) {
 | 
			
		||||
            setResults([]);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        callback(searchString).then(results => {
 | 
			
		||||
            setResults(results || null);
 | 
			
		||||
        });
 | 
			
		||||
    }, [searchString]);
 | 
			
		||||
 | 
			
		||||
    return [searchString, setSearchString, results];
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user