diff --git a/resources/scripts/api/admin/databases/searchDatabases.ts b/resources/scripts/api/admin/databases/searchDatabases.ts new file mode 100644 index 000000000..cff92ee82 --- /dev/null +++ b/resources/scripts/api/admin/databases/searchDatabases.ts @@ -0,0 +1,32 @@ +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; +import http from '@/api/http'; + +interface Filters { + name?: string; + host?: string; +} + +interface Wow { + [index: string]: string; +} + +export default (filters?: Filters): Promise => { + let params = {}; + if (filters !== undefined) { + params = Object.keys(filters).map((key) => { + const a: Wow = {}; + a[`filter[${key}]`] = (filters as unknown as Wow)[key]; + return a; + }); + + console.log(params); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/databases', { params: { ...params } }) + .then(response => resolve( + (response.data.data || []).map(rawDataToDatabase) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/admin/nodes/DatabaseSelect.tsx b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx new file mode 100644 index 000000000..30f7212de --- /dev/null +++ b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import SearchableSelect from '@/components/elements/SearchableSelect'; +import searchDatabases from '@/api/admin/databases/searchDatabases'; +import { Database } from '@/api/admin/databases/getDatabases'; +import tw from 'twin.macro'; + +export default () => { + const [ database, setDatabase ] = useState(null); + const [ databases, setDatabases ] = useState([]); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchDatabases({ name: query }).then((databases) => { + setDatabases(databases); + return resolve(); + }).catch(reject); + }); + }; + + const onSelect = (database: Database) => { + setDatabase(database); + }; + + return ( + + {databases.map(d => ( + d.id === database?.id ? +
  • { + e.stopPropagation(); + // selectItem(d); + }} + > +
    + + {d.name} + +
    + + + + +
  • + : +
  • { + e.stopPropagation(); + // selectItem(d); + }} + > +
    + + {d.name} + +
    +
  • + ))} +
    + ); +}; diff --git a/resources/scripts/components/elements/SearchableSelect.tsx b/resources/scripts/components/elements/SearchableSelect.tsx new file mode 100644 index 000000000..5cbc87414 --- /dev/null +++ b/resources/scripts/components/elements/SearchableSelect.tsx @@ -0,0 +1,120 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import InputSpinner from '@/components/elements/InputSpinner'; +import { debounce } from 'debounce'; + +const Dropdown = styled.div<{ expanded: boolean }>` + ${tw`absolute mt-1 w-full rounded-md bg-neutral-900 shadow-lg z-10`}; + ${props => !props.expanded && tw`hidden`}; +`; + +interface Props { + id: string; + name: string; + nullable: boolean; + + items: T[]; + setItems: (items: T[]) => void; + + onSearch: (query: string) => Promise; + onSelect: (item: T) => void; + + children: React.ReactNode; +} + +function SearchableSelect ({ id, name, items, setItems, onSearch, children }: Props) { + const [ loading, setLoading ] = useState(false); + const [ expanded, setExpanded ] = useState(false); + + const [ inputText, setInputText ] = useState(''); + + const onFocus = () => { + setInputText(''); + setItems([]); + setExpanded(true); + }; + + const search = debounce((query: string) => { + if (!expanded) { + return; + } + + if (query === '' || query.length < 2) { + setItems([]); + return; + } + + setLoading(true); + onSearch(query).then(() => setLoading(false)); + }, 250); + + /* const selectItem = (item: any) => { + onSelect(item); + }; */ + + useEffect(() => { + // setInputText(location.short); + setExpanded(false); + }, [ ]); + // }, [ location ]); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key !== 'Escape') { + return; + } + + // setInputText(location.short); + setExpanded(false); + }; + + window.addEventListener('keydown', handler); + return () => { + window.removeEventListener('keydown', handler); + }; + }, [ expanded ]); + + return ( +
    + + +
    + + { + setInputText(e.currentTarget.value); + search(e.currentTarget.value); + }} + /> + + +
    + +
    + + + { items.length < 1 ? + inputText.length < 2 ? +
    +

    Please type 2 or more characters.

    +
    + : +
    +

    No results found.

    +
    + : +
      + {children} +
    + } +
    +
    +
    + ); +} + +export default SearchableSelect;