Created
March 11, 2020 12:23
-
-
Save liamkernighan/25456b661556117ecb54c8da23ade905 to your computer and use it in GitHub Desktop.
React Select
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useTheme } from '@material-ui/core'; | |
import { observer } from 'mobx-react'; | |
import * as React from 'react'; | |
import Select from 'react-select'; | |
import { ValueType } from 'react-select/src/types'; | |
import { SelectedOption, SelectorProps } from './Types'; | |
type Props = SelectorProps & { | |
required?: boolean; | |
}; | |
export const AutocompleteSelector = observer((props: Props) => { | |
const theme = useTheme(); | |
const selectStyles = { | |
input: (base: React.CSSProperties) => ({ | |
...base, | |
color: theme.palette.text.primary, | |
'& input': { | |
font: 'inherit', | |
}, | |
}), | |
menu: (styles: React.CSSProperties) => ({ ...styles, zIndex: 1000 }) | |
}; | |
const handleSelected = (option: ValueType<SelectedOption>) => { | |
if (option !== null) { | |
props.onSelect(option); | |
} | |
else if (props.isMulti) { | |
props.onSelect([]); | |
} | |
else { | |
props.onSelect(null); | |
} | |
}; | |
return <> | |
<Select | |
options={props.options} | |
onChange={handleSelected} | |
value={props.selectedValues} | |
placeholder={props.placeholder || 'Выберите'} | |
styles={selectStyles} | |
isSearchable={true} | |
isClearable={true} | |
isLoading={props.isLoading} | |
loadingMessage={() => 'ищем...'} | |
isMulti={props.isMulti} | |
onMenuOpen={props.onMenuOpen} | |
onInputChange={props.onInputChange} | |
noOptionsMessage={() => 'Пусто'} /> | |
{props.required && <input | |
tabIndex={-1} | |
autoComplete='off' | |
style={{ opacity: 0, height: 0 }} | |
value={props.selectedValues ? 'Filled' : undefined} | |
required | |
/>} | |
</>; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Button } from '@material-ui/core'; | |
import { observer } from 'mobx-react'; | |
import * as React from 'react'; | |
import { AutocompleteSelector } from '../../Shared/AutocompleteSelector/AutocompleteSelector'; | |
import { MonthRangePicker } from '../../Shared/DatePicker/MonthRangePicker'; | |
import { AlignRight } from '../../SportsmanCard/StyledComponents'; | |
import { ButtonPanel, PanelButton, Paragraph } from '../StyledComponents'; | |
import { UmoStore } from './Store'; | |
type Props = { | |
store: UmoStore; | |
}; | |
@observer | |
export class UmoFilterContent extends React.Component<Props> { | |
render() { | |
const {store} = this.props; | |
return <> | |
<span style={{display: 'flex'}}> | |
<AlignRight> | |
<Button onClick={store.resetButtonOnClick}> | |
Сбросить поиск | |
</Button> | |
</AlignRight> | |
</span> | |
<Paragraph> | |
<label>Вид спорта / дисциплина: </label> | |
<AutocompleteSelector | |
onMenuOpen={store.fetchSportsAndDisciplines} | |
isLoading={store.sportsAndDisciplinesDropdownIsLoading} | |
options={store.sportsAndDisciplinesOptions} | |
onSelect={store.onSportsDisciplinesSelect} | |
selectedValues={store.selectedSportsAndDisciplinesWithFilter} | |
isMulti={true} /> | |
</Paragraph> | |
{/* undone important разобраться с проблемой типизации у селекта (мульти\не мульти) */} | |
<Paragraph> | |
<label>Место осмотра: </label> | |
<AutocompleteSelector | |
onMenuOpen={store.fetchPlaces} | |
isLoading={store.placesDropdownIsLoading} | |
options={store.placesOptions} | |
onSelect={store.onPlaceSelect} | |
selectedValues={store.selectedPlaces} | |
isMulti={true} | |
/> | |
</Paragraph> | |
<label>Период:</label> | |
<ButtonPanel> | |
<PanelButton onClick={store.setMinus3Plus6} variant='contained'>-6 +3 мес.</PanelButton> | |
<PanelButton onClick={store.setThisMonth} variant='contained'>Этот месяц</PanelButton> | |
<PanelButton onClick={store.setPreviousMonth} variant='contained'>Предыдущий месяц</PanelButton> | |
</ButtonPanel> | |
<MonthRangePicker store={store.rangePickerStore} /> | |
</>; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Axios from 'axios'; | |
import * as dayjs from 'dayjs'; | |
import groupBy from 'lodash-es/groupBy'; | |
import uniq from 'lodash-es/uniq'; | |
import { action, computed, observable, toJS } from 'mobx'; | |
import { ValueType } from 'react-select/src/types'; | |
import { DateHelpers } from '../../../Helpers/DateHelpers'; | |
import { Helpers } from '../../../Helpers/Helpers'; | |
import { ClientHttp } from '../../../Helpers/HttpService/ClientHttp'; | |
import { LocalStorageFacade } from '../../../Helpers/HttpService/LocalStorageFacade'; | |
import { getTeamDataRequest } from '../../../Server/ApiRequestBlueprints'; | |
import { GlobalContext } from '../../RootSSRComponent/GlobalContext'; | |
import { SelectedOption } from '../../Shared/AutocompleteSelector/Types'; | |
import { MonthRangePickerStore } from '../../Shared/DatePicker/Store'; | |
import { SingleCard, SportAndDisciplineDropdownValue, UmoFilterSettings, ValueAndGuid } from './Types'; | |
export class UmoStore { | |
constructor() { | |
if(Helpers.isClient()) { | |
GlobalContext.instance.store.clearCallback(); | |
} | |
} | |
@action | |
public setCurrentlyOpened = (id: string) => { | |
const element = this.data.find(r => r.data._id.$oid === id); | |
if (element) { | |
element.expanded = !element.expanded; | |
!!element.expanded | |
? LocalStorageFacade.setValue('expandedElementId', id) | |
: LocalStorageFacade.deleteValue('expandedElementId'); | |
} | |
} | |
public deletePlace = (id: string) => { | |
this.selectedPlaces = this.selectedPlaces.filter(r => r.value !== id); | |
this.startSearchingWithNewFilterSet(); | |
} | |
public deleteSportAndDiscipline = (id: string) => { | |
this.selectedSportsAndDisciplines = this.selectedSportsAndDisciplines.filter(r => r.value !== id); | |
this.startSearchingWithNewFilterSet(); | |
} | |
public loadInitialData = async () => { | |
this.isLoading = true; | |
await this.fetchSportsAndDisciplines(); | |
await Promise.all([this.fetchPlaces(), this.restoreFilterSettings()]); | |
await this.startSearchingWithNewFilterSet(false); | |
this.restoreOpenedCard(); | |
} | |
public restoreOpenedCard = () => { | |
const openedCardId = LocalStorageFacade.getValue('expandedElementId'); | |
// tslint:disable-next-line:no-unused-expression | |
openedCardId && this.setCurrentlyOpened(openedCardId); | |
//openedCardId && this.loadDetails(openedCardId); | |
} | |
private saveFilterSettings = () => { | |
const settings: UmoFilterSettings = { | |
selectedSportsAndDisciplines: this.selectedSportsAndDisciplines, | |
selectedPlaces: this.selectedPlaces, | |
startDate: this.startDate.format(), | |
endDate: this.endDate.format(), | |
}; | |
LocalStorageFacade.setValue('umoFilterSettings', settings); | |
} | |
public restoreFilterSettings = () => { | |
const settings = LocalStorageFacade.getValue('umoFilterSettings'); | |
if (!settings) { | |
return Promise.resolve(); | |
} | |
if (settings.selectedSportsAndDisciplines) { | |
this.selectedSportsAndDisciplines = settings.selectedSportsAndDisciplines; | |
} | |
if (settings.selectedPlaces) { | |
this.selectedPlaces = settings.selectedPlaces; | |
} | |
if (settings.startDate) { | |
this.startDate = dayjs(settings.startDate); | |
} | |
if (settings.endDate) { | |
this.endDate = dayjs(settings.endDate); | |
} | |
return Promise.resolve(); | |
} | |
//#region Настройки периода | |
@observable startDate = dayjs().add(-6, 'month').startOf('month'); | |
@observable endDate = dayjs().add(3, 'month').endOf('month'); | |
private defaultStartDate = dayjs().add(-20, 'year').startOf('month'); | |
private defaultEndDate = dayjs().add(20, 'year').endOf('month'); | |
public setEmptyDateInterval = () => { | |
this.startDate = this.defaultStartDate; | |
this.endDate = this.defaultEndDate; | |
this.startSearchingWithNewFilterSet(); | |
} | |
@computed get showDateChip() { | |
return !this.startDate.isSame(this.defaultStartDate) && !this.endDate.isSame(this.defaultEndDate); | |
} | |
public setMinus3Plus6 = () => { | |
this.startDate = dayjs().add(-6, 'month').startOf('month'); | |
this.endDate = dayjs().add(3, 'month').endOf('month'); | |
this.startSearchingWithNewFilterSet(); | |
} | |
public setThisMonth = () => { | |
this.startDate = dayjs().startOf('month'); | |
this.endDate = dayjs().endOf('month'); | |
this.startSearchingWithNewFilterSet(); | |
} | |
public setPreviousMonth = () => { | |
this.startDate = dayjs().startOf('month').add(-1, 'month').startOf('month'); | |
this.endDate = dayjs().endOf('month').add(-1, 'month').endOf('month'); | |
this.startSearchingWithNewFilterSet(); | |
} | |
//#endregion | |
@observable private _drawerIsOpened = false; | |
public drawerIsOpened = () => this._drawerIsOpened; | |
public setDrawerIsOpened = (value: boolean) => { | |
this._drawerIsOpened = value; | |
const globalStore = GlobalContext.instance.store; | |
if (value) { | |
globalStore.setCallback(() => this.setDrawerIsOpened(false)); | |
} | |
else { | |
globalStore.clearCallback(); | |
} | |
} | |
@observable public isLoading = true; | |
@computed private get currentFilters() { | |
return [this.selectedSportsAndDisciplines, this.selectedPlaces, this.startDate, this.endDate]; | |
} | |
@computed get filtersAreActive() { | |
return this.currentFilters.some(r => r !== null); | |
} | |
private clearAllFilters = () => { | |
this.selectedSportsAndDisciplines = []; | |
this.selectedPlaces = []; | |
this.setMinus3Plus6(); | |
} | |
public resetButtonOnClick = () => { | |
this.clearAllFilters(); | |
} | |
@observable public cardStates: SingleCard[] = []; | |
@observable public data: SingleCard[] = []; | |
@computed public get dates(): string[] { | |
const dates = this.data.map(r => r.data.approvalOrProposalDate['$date']); | |
return uniq(dates); | |
} | |
@observable public loadMoreIsLoading = false; | |
@action public loadMoreButtonOnClick = () => { | |
this.loadMoreIsLoading = true; | |
this.currentPage++; | |
this.startLoadingElementsFromApi(() => this.loadMoreIsLoading = false, 'append'); | |
} | |
@observable private currentPage = 1; | |
// места проведения осмотра | |
@observable public placesDropdownIsLoading = false; | |
@observable public placesList: ValueAndGuid[] | null = null; | |
@observable public selectedPlaces: SelectedOption[] = []; | |
@action public onPlaceSelect = (selectedOptions: ValueType<SelectedOption>) => { | |
this.selectedPlaces = selectedOptions as SelectedOption[]; | |
this.startSearchingWithNewFilterSet(); | |
} | |
@computed public get placesOptions(): SelectedOption[] { | |
if (this.placesList !== null) { | |
return this.placesList.map(val => ({ value: val.id, label: val.value })); | |
} | |
return []; | |
} | |
// комбинированный селектор видов спорта и дисциплин | |
@observable public sportsAndDisciplinesDropdownIsLoading = false; | |
@observable public sportsAndDisciplinesList: SportAndDisciplineDropdownValue[] | null = null; | |
@observable public selectedSportsAndDisciplines: SelectedOption[] = []; | |
@action public onSportsDisciplinesSelect = (selectedOptions: ValueType<SelectedOption>) => { | |
this.selectedSportsAndDisciplines = selectedOptions as SelectedOption[]; | |
this.startSearchingWithNewFilterSet(); | |
} | |
@computed public get sportsAndDisciplinesOptions(): SelectedOption[] { | |
return this.sportsAndDisciplinesListWithFilter.map(r => ({ value: r.id, label: this.createLabel(r) })); | |
} | |
@computed | |
public get selectedSportsAndDisciplinesWithFilter() { | |
const filterIds = this.sportsAndDisciplinesListWithFilter.map(r => r.id); | |
return this.selectedSportsAndDisciplines.filter(r => filterIds.includes(r.value)); | |
} | |
@computed | |
public get sportsAndDisciplinesListWithFilter() { | |
const currentDoctorSports = GlobalContext.instance.store.currentSports; | |
return this.sportsAndDisciplinesList | |
&& this.sportsAndDisciplinesList.filter(r => currentDoctorSports.includes(r.sport)) || []; | |
} | |
private createLabel = (value: SportAndDisciplineDropdownValue) => [value.sport, value.discipline].filter(r => r !== null).join('/'); | |
private getQueryModel = () => { | |
const requestModel = getTeamDataRequest(); | |
const filterModifiers: object[] = []; | |
if (this.selectedPlaces !== null && this.selectedPlaces.length > 0) { | |
filterModifiers.push({ 'facility.name': { '$in': this.selectedPlaces.map(x => x.label) } }); | |
} | |
filterModifiers.push({ approvalOrProposalDate: DateHelpers.getDateInterval(this.startDate, this.endDate) }); | |
filterModifiers.forEach(mod => { | |
requestModel.filter = { ...requestModel.filter, ...mod }; | |
}); | |
requestModel.page = this.currentPage; | |
return requestModel; | |
} | |
public startSearchingWithNewFilterSet = async (save = true) => { | |
this.currentPage = 1; | |
this.isLoading = true; | |
if (save) { | |
this.saveFilterSettings(); | |
} | |
await this.startLoadingElementsFromApi(() => this.isLoading = false, 'replace'); | |
this.rangePickerStore.recieveNewRange(this.startDate, this.endDate); | |
} | |
private startLoadingElementsFromApi = async (callback: () => void, mode: 'append' | 'replace') => { | |
const sportsAndDiscpilinesToLoad = this.selectedSportsAndDisciplinesWithFilter.length === 0 | |
? GlobalContext.instance.store.currentSports.map(s => `{"sport":"${s}","discipline":null}`) // Ну это просто пиздец | |
: this.selectedSportsAndDisciplinesWithFilter.map(s => s.value); | |
const selectedSportsAndDisciplines = | |
toJS(this.sportsAndDisciplinesListWithFilter) | |
.filter(r => sportsAndDiscpilinesToLoad.includes(r.id)); | |
const groupedDisciplines = Object.entries(groupBy(selectedSportsAndDisciplines, r => r.sport)); | |
const promises = groupedDisciplines | |
.map(r => { | |
const query = this.getQueryModel(); | |
const withoutNulls = r[1].filter(x => x.discipline !== null); | |
if (withoutNulls.length > 0) { | |
query.filter.discipline = { '$in': withoutNulls.map(r => r.discipline as string) }; | |
} | |
query.filter.sport = { '$in': [r[0]] }; | |
return Axios.post('/api/dashboard-survey', query); | |
}); | |
const results = await Promise.all<unknown>(promises); | |
const values: SingleCard[] = []; | |
results.forEach(r => { | |
const data = (r as any).data.result as object[]; | |
values.push(...data.map(val => ({ data: val, expanded: false, id: Helpers.generateGuid() }))); | |
}); | |
if (mode === 'replace') { | |
this.data = values; | |
} | |
else { | |
this.data.push(...values); | |
} | |
callback(); | |
} | |
public loadDetails = async (orderId: string) => { | |
const result = await Axios.post('/api/team-data-details', { orderId: orderId }); | |
const data = ((result as any).data as object); | |
return data; | |
} | |
public fetchSportsAndDisciplines = async () => { | |
if (this.sportsAndDisciplinesList !== null) { | |
return; | |
} | |
this.sportsAndDisciplinesDropdownIsLoading = true; | |
const values: SportAndDisciplineDropdownValue[] = []; | |
const result: any[] = await new ClientHttp('POST', '/api/sports-and-disciplines', {}).go(); | |
if (result) { | |
result.sort((a: any, b: any) => a.sport.toLowerCase().localeCompare(b.sport.toLowerCase())) | |
.forEach((group: any) => { | |
const value = { sport: group.sport, discipline: null }; | |
values.push({ ...value, id: JSON.stringify(value) }); | |
group.disciplines.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase())) | |
.forEach((discipline: any) => { | |
const value = { sport: group.sport, discipline: discipline }; | |
values.push({ ...value, id: JSON.stringify(value) }); | |
}); | |
}); | |
this.sportsAndDisciplinesList = values; | |
} | |
this.sportsAndDisciplinesDropdownIsLoading = false; | |
} | |
public fetchPlaces = () => { | |
if (this.placesList === null) { | |
this.placesDropdownIsLoading = true; | |
return new ClientHttp('POST', '/api/places', {}).go().then((result: any) => { | |
const data = ((result as string[]).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).filter(r => !!r.length)); | |
this.placesList = data.map(r => ({ value: r, id: r })); | |
this.placesDropdownIsLoading = false; | |
}); | |
} | |
return Promise.resolve(); | |
} | |
public rangePickerStore = new MonthRangePickerStore(this.startDate, | |
this.endDate, (start, end) => { | |
this.startDate = start; | |
this.endDate = end; | |
this.startSearchingWithNewFilterSet(); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment