mysql param types, async search, bugfix
This commit is contained in:
		
							parent
							
								
									92c78356ed
								
							
						
					
					
						commit
						424a945fa6
					
				| @ -100,14 +100,14 @@ class Parameter { | |||||||
|     return $str; |     return $str; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public static function parseType($value): int { |   public static function parseType(mixed $value, bool $strict = false): int { | ||||||
|     if (is_array($value)) |     if (is_array($value)) | ||||||
|       return Parameter::TYPE_ARRAY; |       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; |       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; |       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; |       return Parameter::TYPE_BOOLEAN; | ||||||
|     else if (is_a($value, 'DateTime')) |     else if (is_a($value, 'DateTime')) | ||||||
|       return Parameter::TYPE_DATE_TIME; |       return Parameter::TYPE_DATE_TIME; | ||||||
|  | |||||||
| @ -91,7 +91,7 @@ class MySQL extends SQL { | |||||||
|   private function getPreparedParams($values): array { |   private function getPreparedParams($values): array { | ||||||
|     $sqlParams = array(''); |     $sqlParams = array(''); | ||||||
|     foreach ($values as $value) { |     foreach ($values as $value) { | ||||||
|       $paramType = Parameter::parseType($value); |       $paramType = Parameter::parseType($value, true);  // TODO: is strict type checking really correct here?
 | ||||||
|       switch ($paramType) { |       switch ($paramType) { | ||||||
|         case Parameter::TYPE_BOOLEAN: |         case Parameter::TYPE_BOOLEAN: | ||||||
|           $value = $value ? 1 : 0; |           $value = $value ? 1 : 0; | ||||||
|  | |||||||
| @ -342,7 +342,9 @@ class DatabaseEntityHandler implements Persistable { | |||||||
| 
 | 
 | ||||||
|     $value = $row[$columnName]; |     $value = $row[$columnName]; | ||||||
|     if ($column instanceof DateTimeColumn) { |     if ($column instanceof DateTimeColumn) { | ||||||
|  |       if ($value !== null) { | ||||||
|         $value = new \DateTime($value); |         $value = new \DateTime($value); | ||||||
|  |       } | ||||||
|     } else if ($column instanceof JsonColumn) { |     } else if ($column instanceof JsonColumn) { | ||||||
|       $value = json_decode($value, true); |       $value = json_decode($value, true); | ||||||
|     } else if (isset($this->relations[$propertyName])) { |     } 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) { |     if ($this->logVerbose) { | ||||||
|       $params = []; |       $params = []; | ||||||
| @ -148,7 +148,7 @@ class DatabaseEntityQuery extends Select { | |||||||
| 
 | 
 | ||||||
|     $res = parent::execute(); |     $res = parent::execute(); | ||||||
|     if ($res === null || $res === false) { |     if ($res === null || $res === false) { | ||||||
|       return null; |       return $res; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if ($this->resultType === SQL::FETCH_ALL) { |     if ($this->resultType === SQL::FETCH_ALL) { | ||||||
|  | |||||||
| @ -281,7 +281,7 @@ export default class API { | |||||||
| 
 | 
 | ||||||
|     /** ApiKeyAPI **/ |     /** ApiKeyAPI **/ | ||||||
|     async getApiKeys(showActiveOnly = false, page = 1, count = 25, orderBy = "validUntil", sortOrder = "desc") { |     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() { |     async createApiKey() { | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import "./data-table.css"; | |||||||
| import {LocaleContext} from "../locale"; | import {LocaleContext} from "../locale"; | ||||||
| import clsx from "clsx"; | import clsx from "clsx"; | ||||||
| import {Box, IconButton} from "@mui/material"; | import {Box, IconButton} from "@mui/material"; | ||||||
| import {formatDateTime} from "../util"; | import {formatDate, formatDateTime} from "../util"; | ||||||
| import CachedIcon from "@material-ui/icons/Cached"; | import CachedIcon from "@material-ui/icons/Cached"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -23,20 +23,23 @@ export function DataTable(props) { | |||||||
|     const [doFetchData, setFetchData] = useState(false); |     const [doFetchData, setFetchData] = useState(false); | ||||||
|     const [sortAscending, setSortAscending] = useState(["asc","ascending"].includes(defaultSortOrder?.toLowerCase)); |     const [sortAscending, setSortAscending] = useState(["asc","ascending"].includes(defaultSortOrder?.toLowerCase)); | ||||||
|     const [sortColumn, setSortColumn] = useState(defaultSortColumn || null); |     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 onRowClick = onClick || (() => {}); | ||||||
| 
 | 
 | ||||||
|     const onFetchData = useCallback((force = false) => { |     const onFetchData = useCallback((force = false) => { | ||||||
|  |         if (fetchData) { | ||||||
|             if (doFetchData || force) { |             if (doFetchData || force) { | ||||||
|                 setFetchData(false); |                 setFetchData(false); | ||||||
|                 const orderBy = columns[sortColumn]?.field || null; |                 const orderBy = columns[sortColumn]?.field || null; | ||||||
|                 const sortOrder = sortAscending ? "asc" : "desc"; |                 const sortOrder = sortAscending ? "asc" : "desc"; | ||||||
|                 fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder); |                 fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder); | ||||||
|             } |             } | ||||||
|     }, [doFetchData, columns, sortColumn, sortAscending, pagination]); |         } | ||||||
|  |     }, [fetchData, doFetchData, columns, sortColumn, sortAscending, pagination]); | ||||||
| 
 | 
 | ||||||
|     // pagination changed?
 |     // pagination changed?
 | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|  |         if (pagination) { | ||||||
|             let forceFetch = false; |             let forceFetch = false; | ||||||
|             if (pagination.getPageSize() < pagination.getTotal()) { |             if (pagination.getPageSize() < pagination.getTotal()) { | ||||||
|                 // page size is smaller than the total count
 |                 // page size is smaller than the total count
 | ||||||
| @ -47,7 +50,8 @@ export function DataTable(props) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             onFetchData(forceFetch); |             onFetchData(forceFetch); | ||||||
|     }, [pagination.data.pageSize, pagination.data.current]); |         } | ||||||
|  |     }, [pagination?.data?.pageSize, pagination?.data?.current]); | ||||||
| 
 | 
 | ||||||
|     // sorting changed
 |     // sorting changed
 | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
| @ -112,12 +116,17 @@ export function DataTable(props) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return <Box position={"relative"}> |     return <Box position={"relative"}> | ||||||
|  |             {title ? | ||||||
|                 <h3> |                 <h3> | ||||||
|  |                     {fetchData ? | ||||||
|                         <IconButton onClick={() => onFetchData(true)}> |                         <IconButton onClick={() => onFetchData(true)}> | ||||||
|                             <CachedIcon/> |                             <CachedIcon/> | ||||||
|                         </IconButton> |                         </IconButton> | ||||||
|  |                         : <></> | ||||||
|  |                     } | ||||||
|                     {title} |                     {title} | ||||||
|             </h3> |                 </h3> : <></> | ||||||
|  |             } | ||||||
|             <Table className={clsx("data-table", className)} size="small" {...other}> |             <Table className={clsx("data-table", className)} size="small" {...other}> | ||||||
|                 <TableHead> |                 <TableHead> | ||||||
|                     <TableRow> |                     <TableRow> | ||||||
| @ -128,7 +137,7 @@ export function DataTable(props) { | |||||||
|                     { rows } |                     { rows } | ||||||
|                 </TableBody> |                 </TableBody> | ||||||
|             </Table> |             </Table> | ||||||
|         {pagination.renderPagination(L, numRows)} |         {pagination && pagination.renderPagination(L, numRows)} | ||||||
|     </Box> |     </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 { | export class BoolColumn extends DataColumn { | ||||||
|     constructor(label, field = null, params = {}) { |     constructor(label, field = null, params = {}) { | ||||||
|         super(label, field, params); |         super(label, field, params); | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import React, {useState} from "react"; | import React, {useEffect, useState} from "react"; | ||||||
| import { | import { | ||||||
|     Box, |     Box, | ||||||
|     Button, |     Button, | ||||||
| @ -20,11 +20,21 @@ export default function Dialog(props) { | |||||||
| 
 | 
 | ||||||
|     const [inputData, setInputData] = useState({}); |     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 = []; |     let buttons = []; | ||||||
|     for (const [index, name] of options.entries()) { |     for (const [index, name] of options.entries()) { | ||||||
|         buttons.push( |         buttons.push( | ||||||
|             <Button variant={"outlined"} size={"small"} key={"button-" + name} |             <Button variant={"outlined"} size={"small"} key={"button-" + name} | ||||||
|                     onClick={() => { onClose(); onOption(index, inputData); }}> |                     onClick={() => { onClose(); onOption(index, inputData); setInputData({}); }}> | ||||||
|                 {name} |                 {name} | ||||||
|             </Button> |             </Button> | ||||||
|         ) |         ) | ||||||
| @ -40,6 +50,7 @@ export default function Dialog(props) { | |||||||
|             case 'text': |             case 'text': | ||||||
|                 inputElements.push(<TextField |                 inputElements.push(<TextField | ||||||
|                     {...inputProps} |                     {...inputProps} | ||||||
|  |                     sx={{marginTop: 1}} | ||||||
|                     size={"small"} fullWidth={true} |                     size={"small"} fullWidth={true} | ||||||
|                     key={"input-" + input.name} |                     key={"input-" + input.name} | ||||||
|                     value={inputData[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