|
@@ -1,33 +1,41 @@
|
|
|
-import { useTrainingTypesQuery, TrainingType } from '../../gql'
|
|
|
import AddTrainingType from './AddTrainingType'
|
|
|
-import { useState, useEffect } from 'react'
|
|
|
+import { useState, useEffect, FunctionComponent, useRef } from 'react'
|
|
|
import { Modal } from '../../modal'
|
|
|
-import Dropdown from '../../dropdown/components/Dropdown'
|
|
|
import { QueryHookOptions, QueryResult } from '@apollo/client'
|
|
|
-import { TextInput } from '../../form'
|
|
|
+import { customEvent } from '../../lib/customEvent'
|
|
|
+import { useOnClickOutside } from '../../lib/onClickOutside'
|
|
|
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
|
+import {
|
|
|
+ faCogs,
|
|
|
+ faChevronCircleRight,
|
|
|
+ faChevronCircleDown,
|
|
|
+ faPlusCircle,
|
|
|
+ faEdit,
|
|
|
+ faTrash,
|
|
|
+} from '@fortawesome/free-solid-svg-icons'
|
|
|
+import theme from '../../styles/theme'
|
|
|
|
|
|
-interface ISelector<TQueryData, TQueryVariables> {
|
|
|
+interface ISelector<TQueryData, TQueryVariables extends { where?: any }> {
|
|
|
name: string
|
|
|
value: any
|
|
|
onChange: GenericEventHandler
|
|
|
- callback: (selectedIndex: number) => void
|
|
|
- query: (
|
|
|
- baseOptions?: QueryHookOptions<TQueryData, { where: { OR: { [key: string]: string }[] } }>
|
|
|
- ) => QueryResult<TQueryData, TQueryVariables>
|
|
|
- searchKeys?: (keyof TQueryData)[]
|
|
|
- dataKey: keyof TQueryData
|
|
|
- selectedKey: keyof TQueryData
|
|
|
+ query: (baseOptions?: QueryHookOptions) => QueryResult<TQueryData, TQueryVariables>
|
|
|
+ dataKey?: keyof TQueryData
|
|
|
+ searchKeys?: (keyof (TQueryVariables & { where: Pick<TQueryVariables, 'where'> })['where'])[]
|
|
|
className?: string
|
|
|
+ SelectorItem: FunctionComponent<ISelectorItem>
|
|
|
+ multiple?: boolean
|
|
|
+}
|
|
|
+
|
|
|
+export interface ISelectorItem {
|
|
|
+ data: any
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The selector allows to select an item from a list and offers tools to add, edit or remove items.
|
|
|
* @param param0
|
|
|
*/
|
|
|
-const Selector = <
|
|
|
- TQueryData extends { [dataKey: string]: any[] },
|
|
|
- TQueryVariables extends Record<string, any>
|
|
|
->({
|
|
|
+const Selector = <TQueryData, TQueryVariables extends { where?: any }>({
|
|
|
name,
|
|
|
value,
|
|
|
onChange,
|
|
@@ -35,58 +43,113 @@ const Selector = <
|
|
|
searchKeys,
|
|
|
dataKey,
|
|
|
className,
|
|
|
+ SelectorItem,
|
|
|
+ multiple = false,
|
|
|
}: ISelector<TQueryData, TQueryVariables>) => {
|
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
|
- const searchVariables =
|
|
|
- searchKeys && searchTerm !== ''
|
|
|
- ? { variables: { where: { OR: searchKeys?.map((key) => ({ [key]: searchTerm })) } } }
|
|
|
- : {}
|
|
|
+ const [modalState, setModalState] = useState(false)
|
|
|
+ const searchVariables = searchKeys
|
|
|
+ ? { variables: { where: { OR: searchKeys?.map((key) => ({ [key]: searchTerm })) } } }
|
|
|
+ : {}
|
|
|
const { data, error, loading } = query(searchVariables)
|
|
|
+ const debounce = useRef<undefined | NodeJS.Timeout>()
|
|
|
+ const ref = useRef<HTMLDivElement>(null)
|
|
|
+
|
|
|
+ const _dataKey: keyof TQueryData | undefined =
|
|
|
+ dataKey ?? (data && (Object.keys(data)[0] as keyof TQueryData))
|
|
|
+
|
|
|
+ const queryData = data && _dataKey && ((data[_dataKey] as unknown) as any[])
|
|
|
+ console.log({ queryData, value, name, searchVariables })
|
|
|
|
|
|
useEffect(() => {
|
|
|
- setTimeout(() => console.log('debouonced.'), 300)
|
|
|
+ if (debounce.current) clearTimeout(debounce.current)
|
|
|
+ debounce.current = setTimeout(() => console.log('debouonced', searchTerm), 400)
|
|
|
}, [searchTerm])
|
|
|
|
|
|
+ const [showDropdownList, setShowDropdownList] = useState(false)
|
|
|
+ const [showTools, setShowTools] = useState(false)
|
|
|
+ useOnClickOutside(ref, () => setShowDropdownList(false))
|
|
|
+
|
|
|
return (
|
|
|
- <div className={className}>
|
|
|
- <TextInput
|
|
|
- name='search'
|
|
|
- type='search'
|
|
|
- value={searchTerm}
|
|
|
- onChange={(event) => setSearchTerm(event.target.vale)}
|
|
|
- />
|
|
|
- <select
|
|
|
- id={name}
|
|
|
- name={name}
|
|
|
- value={value}
|
|
|
- onChange={(event) => {
|
|
|
- const changeEvent: CustomChangeEvent = {
|
|
|
- target: {
|
|
|
- type: 'custom',
|
|
|
- value: { id: event.target.value },
|
|
|
- name,
|
|
|
- },
|
|
|
- }
|
|
|
- onChange(changeEvent)
|
|
|
+ <div id='dd-container' ref={ref} className={[className, showDropdownList && 'open'].join(' ')}>
|
|
|
+ {multiple &&
|
|
|
+ queryData?.filter((item: any) => value.includes(item.id)).map((item: any) => item.id)}
|
|
|
+ <div
|
|
|
+ id='dd-header'
|
|
|
+ onClick={() => {
|
|
|
+ console.log('click')
|
|
|
+ setShowDropdownList(!showDropdownList)
|
|
|
}}
|
|
|
+ className={showDropdownList ? 'open' : undefined}
|
|
|
>
|
|
|
- {loading && 'loading training types...'}
|
|
|
- {error && 'error loading training types'}
|
|
|
- {data &&
|
|
|
- data[dataKey].map((item) => (
|
|
|
- <option key={item.id} value={item.id}>
|
|
|
- {item.name}
|
|
|
- </option>
|
|
|
+ <FontAwesomeIcon
|
|
|
+ icon={showDropdownList ? faChevronCircleDown : faChevronCircleRight}
|
|
|
+ height={16}
|
|
|
+ />
|
|
|
+ <span>{value.name}</span>
|
|
|
+ <button
|
|
|
+ onClick={(event) => {
|
|
|
+ event.stopPropagation()
|
|
|
+ if (!showTools) setShowDropdownList(true)
|
|
|
+ setShowTools(!showTools)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <FontAwesomeIcon icon={faCogs} height={16} />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showDropdownList && (
|
|
|
+ <ul id='dd-items'>
|
|
|
+ {showTools && (
|
|
|
+ <div id='dd-tools'>
|
|
|
+ <input
|
|
|
+ type='search'
|
|
|
+ name='search'
|
|
|
+ value={searchTerm}
|
|
|
+ placeholder='Search'
|
|
|
+ onChange={(event) => setSearchTerm(event.target.value)}
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ type='button'
|
|
|
+ onClick={(event) => {
|
|
|
+ setModalState(true)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <FontAwesomeIcon icon={faPlusCircle} height={16} />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {queryData?.map((item: any) => (
|
|
|
+ <li
|
|
|
+ key={item.id}
|
|
|
+ onClick={(event) => {
|
|
|
+ setShowDropdownList(false)
|
|
|
+ onChange(customEvent(name, item))
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <SelectorItem data={item} />
|
|
|
+ <div className='item-tools'>
|
|
|
+ <button
|
|
|
+ type='button'
|
|
|
+ onClick={(event) => {
|
|
|
+ setModalState(true)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <FontAwesomeIcon icon={faEdit} height={16} />
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type='button'
|
|
|
+ onClick={(event) => {
|
|
|
+ setModalState(true)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <FontAwesomeIcon icon={faTrash} height={16} />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
))}
|
|
|
- </select>
|
|
|
- <button
|
|
|
- type='button'
|
|
|
- onClick={(event) => {
|
|
|
- setModalState(true)
|
|
|
- }}
|
|
|
- >
|
|
|
- Add type
|
|
|
- </button>
|
|
|
+ </ul>
|
|
|
+ )}
|
|
|
<Modal state={[modalState, setModalState]}>
|
|
|
<AddTrainingType
|
|
|
onSuccess={(result) => {
|
|
@@ -105,20 +168,97 @@ const Selector = <
|
|
|
</Modal>
|
|
|
|
|
|
<style jsx>{`
|
|
|
- div {
|
|
|
+ #dd-container {
|
|
|
+ position: relative;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-header {
|
|
|
+ margin: 0.3em 0;
|
|
|
+ padding: 0.3em;
|
|
|
+ display: flex;
|
|
|
+ color: ${theme.colors.formColor};
|
|
|
+ background-color: ${theme.colors.formBackground}22;
|
|
|
+ border-bottom: 2px solid ${theme.colors.formHighlightBackground}00;
|
|
|
+ transition: all 250ms ease-in-out;
|
|
|
+ cursor: pointer;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-header.open {
|
|
|
+ color: ${theme.colors.formHighlight};
|
|
|
+ background-color: ${theme.colors.formHighlightBackground}22;
|
|
|
+ border-bottom: 2px solid ${theme.colors.formHighlightBackground}22;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-header.error {
|
|
|
+ color: ${theme.colors.formError};
|
|
|
+ background-color: ${theme.colors.formErrorBackground}22;
|
|
|
+ border-bottom: 2px solid ${theme.colors.formErrorBackground}22;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-items {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ top: 95%;
|
|
|
+ max-height: 300px;
|
|
|
+ z-index: 5;
|
|
|
+ overflow-y: scroll;
|
|
|
+ background-color: ${theme.colors.background};
|
|
|
+ list-style: none;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ box-shadow: ${theme.bsSmall};
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-items.open {
|
|
|
+ background-color: ${theme.colors.white}ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-items li {
|
|
|
+ background-color: ${theme.colors.formHighlightBackground}00;
|
|
|
display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
align-items: center;
|
|
|
+ transition: all 250ms ease-in-out;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ border-bottom: 1px solid ${theme.colors.highlight}11;
|
|
|
+ }
|
|
|
+ #dd-items li:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-items li:hover {
|
|
|
+ background-color: ${theme.colors.formHighlightBackground}22;
|
|
|
}
|
|
|
|
|
|
- label,
|
|
|
- select,
|
|
|
- button {
|
|
|
- display: inline-block;
|
|
|
+ #dd-header span {
|
|
|
+ flex-grow: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-header button {
|
|
|
+ margin: 0.2em;
|
|
|
+ padding: 0.2em;
|
|
|
width: auto;
|
|
|
+ color: inherit;
|
|
|
+ background-color: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-tools button,
|
|
|
+ #dd-items button {
|
|
|
+ color: ${theme.colors.links};
|
|
|
+ background-color: transparent;
|
|
|
+ margin: 0.3em;
|
|
|
+ padding: 0.3em;
|
|
|
}
|
|
|
- select {
|
|
|
+
|
|
|
+ #dd-tools {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ #dd-tools input {
|
|
|
flex-grow: 1;
|
|
|
- margin: 0 0.6em;
|
|
|
}
|
|
|
`}</style>
|
|
|
</div>
|