managed filter fixed + distance calc to table

This commit is contained in:
Winzlieb - 2022-03-17 15:00:02 +01:00
parent 83b6d1710f
commit 383847e7f3
3 changed files with 89 additions and 70 deletions

View File

@ -11,30 +11,34 @@ import StringFilter from '@inovua/reactdatagrid-community/StringFilter'
import BoolFilter from '@inovua/reactdatagrid-community/BoolFilter' import BoolFilter from '@inovua/reactdatagrid-community/BoolFilter'
import NumberFilter from "@inovua/reactdatagrid-community/NumberFilter" import NumberFilter from "@inovua/reactdatagrid-community/NumberFilter"
import BoolEditor from '@inovua/reactdatagrid-community/BoolEditor' import BoolEditor from '@inovua/reactdatagrid-community/BoolEditor'
import { GetOffersQuery, GetRwQuery} from "../../codegen/generates" import {GetOffersQuery, GetRwQuery} from "../../codegen/generates"
import moment from "moment" import moment from "moment"
import {useTranslation} from "react-i18next" import {useTranslation} from "react-i18next"
import {resources} from '../../i18n/config' import {resources} from '../../i18n/config'
import { fetcher } from '../../codegen/fetcher' import {fetcher} from '../../codegen/fetcher'
import { useAuthStore, AuthState } from '../Login' import {useAuthStore, AuthState} from '../Login'
import defaultColumnRawDefinition from "../config/defaultColumnRawDefinition"; import defaultColumnRawDefinition from "../config/defaultColumnRawDefinition";
import defaultColumnGroups from "../config/defaultColumnGroups"; import defaultColumnGroups from "../config/defaultColumnGroups";
import { ColumnRaw } from '../util/datagrid/columnRaw' import {ColumnRaw} from '../util/datagrid/columnRaw'
import columnsRaw from "../config/defaultColumnRawDefinition"; import columnsRaw from "../config/defaultColumnRawDefinition";
import {transformValue} from "../util/tableValueMapper"; import {transformValue} from "../util/tableValueMapper";
import {filterUndefOrNull} from "../util/notEmpty"; import {filterUndefOrNull} from "../util/notEmpty";
import extendedFilter from "../util/datagrid/extendedFilter"; import extendedFilter from "../util/datagrid/extendedFilter";
import {haversine_distance, LatLng} from "../util/distance";
global.moment = moment global.moment = moment
export type HostOfferLookupTableDataType = GetOffersQuery["get_offers"] & GetRwQuery["get_rw"]; export type HostOfferLookupTableDataType =
Omit<NonNullable<(GetOffersQuery["get_offers"] & GetRwQuery["get_rw"])>[number], '__typename'>
& { place_distance?: number };
export type HostOfferLookupTableProps = { export type HostOfferLookupTableProps = {
data_ro?: GetOffersQuery["get_offers"], data_ro?: GetOffersQuery["get_offers"],
data_rw?: GetRwQuery["get_rw"], // TODO data_rw?: GetRwQuery["get_rw"], // TODO
refetch_rw: any, refetch_rw: any,
onFilteredDataChange?: (data: HostOfferLookupTableDataType[]) => void onFilteredDataChange?: (data: HostOfferLookupTableDataType[]) => void
center?: LatLng
} }
const filterMappings = { const filterMappings = {
@ -61,12 +65,12 @@ type CustomRendererMatcher = {
render: (...args: any[]) => ReactNode render: (...args: any[]) => ReactNode
} }
function Email({value}: {value: string}) { function Email({value}: { value: string }) {
const href = `mailto:${value}` const href = `mailto:${value}`
return <a href={href}>{value}</a> return <a href={href}>{value}</a>
} }
function Phone({value}: {value: string}) { function Phone({value}: { value: string }) {
const href = `tel:${value}` const href = `tel:${value}`
return <a href={href}>{value}</a> return <a href={href}>{value}</a>
} }
@ -94,6 +98,12 @@ const findMatchingRenderer = (c: Partial<ColumnRaw>) => {
return customRenderer?.render return customRenderer?.render
} }
const floor = (v: number | undefined) => v && Math.floor(v);
const calculateDistance = (r: {place_lat?: number | null, place_lon?: number | null}, {lng, lat}: LatLng) =>
r.place_lat && r.place_lon && lng && lat
? floor(haversine_distance(lat, lng, r.place_lat, r.place_lon)) : r
const columns: TypeColumn[] = defaultColumnRawDefinition const columns: TypeColumn[] = defaultColumnRawDefinition
.map(c => ({ .map(c => ({
...c, ...c,
@ -113,15 +123,17 @@ const defaultFilterValue: TypeFilterValue = columns
} as unknown as TypeSingleFilterValue } as unknown as TypeSingleFilterValue
}) })
async function mutate(auth: AuthState, onEditComplete: {value: string, columnId: string, rowId: string}) { async function mutate(auth: AuthState, onEditComplete: { value: string, columnId: string, rowId: string }) {
const type = typeof(onEditComplete.value) const type = typeof (onEditComplete.value)
const onEditCompleteByType = {rowId: onEditComplete.rowId, const onEditCompleteByType = {
columnId: onEditComplete.columnId, rowId: onEditComplete.rowId,
value_boolean: type === 'boolean' && onEditComplete.value || null, columnId: onEditComplete.columnId,
value_string: type === 'string' && onEditComplete.value || null} value_boolean: type === 'boolean' && onEditComplete.value || null,
value_string: type === 'string' && onEditComplete.value || null
}
const result = await fetcher<any, any>(`mutation WriteRW($auth: Auth!, $onEditCompleteByType: Boolean) { const result = await fetcher<any, any>(`mutation WriteRW($auth: Auth!, $onEditCompleteByType: Boolean) {
write_rw(auth: $auth, onEditCompleteByType: $onEditCompleteByType) }`, write_rw(auth: $auth, onEditCompleteByType: $onEditCompleteByType) }`,
{auth, onEditCompleteByType})() {auth, onEditCompleteByType})()
return result?.write_rw return result?.write_rw
} }
@ -129,46 +141,57 @@ const rw_default = {
rw_contacted: false, rw_contacted: false,
rw_contact_replied: false, rw_contact_replied: false,
rw_offer_occupied: false, rw_offer_occupied: false,
rw_note: ''} // Required for filtering 'Not empty'. TODO: Should be fixed in StringFilter rw_note: ''
} // Required for filtering 'Not empty'. TODO: Should be fixed in StringFilter
const HostOfferLookupTable = ({data_ro, data_rw, refetch_rw, onFilteredDataChange}: HostOfferLookupTableProps) => { const HostOfferLookupTable = ({
data_ro,
data_rw,
refetch_rw,
onFilteredDataChange,
center
}: HostOfferLookupTableProps) => {
const [dataSource, setDataSource] = useState<HostOfferLookupTableDataType[]>([]); const [dataSource, setDataSource] = useState<HostOfferLookupTableDataType[]>([]);
const [filteredData, setFilteredData] = useState<HostOfferLookupTableDataType[]>([]); const [filteredData, setFilteredData] = useState<HostOfferLookupTableDataType[]>([]);
const [filterValue, setFilterValue] = useState<TypeFilterValue>(defaultFilterValue); const [filterValue, setFilterValue] = useState<TypeFilterValue | undefined>(defaultFilterValue);
const filterValueChangeHandler = useCallback((_filterValue?: TypeFilterValue) => { const filterValueChangeHandler = useCallback((_filterValue?: TypeFilterValue) => {
const data = !_filterValue setFilterValue(_filterValue);
? dataSource }, [setFilterValue])
: extendedFilter(
dataSource,
_filterValue,
columnsRaw
)
_filterValue && setFilterValue(_filterValue);
setFilteredData(data)
onFilteredDataChange && onFilteredDataChange(data)
}, [dataSource])
const filterAndSetData = useCallback(() => {
if (!filterValue) {
setFilteredData(dataSource)
onFilteredDataChange && onFilteredDataChange(dataSource)
return
}
const data = extendedFilter(dataSource, filterValue, columnsRaw)
setFilteredData(data)
}, [dataSource, onFilteredDataChange, setFilteredData, filterValue])
useEffect(() => { useEffect(() => {
const data = filterUndefOrNull( data_ro filterAndSetData()
?.map( e_ro => ({ }, [dataSource, filterValue, filterAndSetData]);
...((data_rw?.find((e_rw) => e_ro.id_tmp === e_rw.id || `rw_${e_ro.id}` === e_rw.id
) || rw_default)), useEffect(() => {
...e_ro const data = filterUndefOrNull(data_ro
}) ) || []).map(v => transformValue(v, columnsRaw)) ?.map(e_ro => ({
...((data_rw?.find((e_rw) => e_ro.id_tmp === e_rw.id || `rw_${e_ro.id}` === e_rw.id
) || rw_default)),
...e_ro
})) || [])
.map(v => transformValue(v, columnsRaw))
.map(v => center ? {...v, place_distance: calculateDistance(v, center)} : v)
// @ts-ignore // @ts-ignore
data && setDataSource(data) data && setDataSource(data)
filterValueChangeHandler() }, [data_ro, data_rw, center]);
//setDataSource((/*data_rw?.get_rw || */ data_ro?.get_offers || []).map(v => transformValue(v, columnsRaw)))
}, [data_ro, data_rw]);
const auth = useAuthStore() const auth = useAuthStore()
const onEditComplete = useCallback(async ({value, columnId, rowId}) => { const onEditComplete = useCallback(async ({value, columnId, rowId}) => {
/** For now the easiest way to ensure the user can see if data was updated in the db is by calling `refetch_rw()` /** For now the easiest way to ensure the user can see if data was updated in the db is by calling `refetch_rw()`
TODO: error handling **/ TODO: error handling **/
await mutate(auth, {value, columnId, rowId}) && refetch_rw() await mutate(auth, {value, columnId, rowId}) && refetch_rw()
}, [auth, refetch_rw]) }, [auth, refetch_rw])
@ -177,22 +200,22 @@ const HostOfferLookupTable = ({data_ro, data_rw, refetch_rw, onFilteredDataChang
const reactdatagridi18n = resources[language]?.translation?.reactdatagrid const reactdatagridi18n = resources[language]?.translation?.reactdatagrid
return <DataGrid return <DataGrid
idProperty="id" idProperty="id"
filterable filterable
showColumnMenuFilterOptions={true} showColumnMenuFilterOptions={true}
showFilteringMenuItems={true} showFilteringMenuItems={true}
filterValue={filterValue} filterValue={filterValue}
onFilterValueChange={filterValueChangeHandler} onFilterValueChange={filterValueChangeHandler}
rowIndexColumn rowIndexColumn
enableSelection enableSelection
enableColumnAutosize={false} enableColumnAutosize={false}
columns={columns} columns={columns}
dataSource={filteredData} dataSource={filteredData}
i18n={reactdatagridi18n || undefined} i18n={reactdatagridi18n || undefined}
style={{height: '100%'}} style={{height: '100%'}}
onEditComplete={onEditComplete} onEditComplete={onEditComplete}
groups={defaultColumnGroups} groups={defaultColumnGroups}
/> />
} }
export default HostOfferLookupTable export default HostOfferLookupTable

View File

@ -11,10 +11,6 @@ import {marker} from "leaflet";
type HostOfferLookupWrapperProps = Partial<HostOfferLookupTableProps> type HostOfferLookupWrapperProps = Partial<HostOfferLookupTableProps>
function floor(v: number|undefined) {
return v && Math.floor(v)
}
//type HostOfferLookupTableDataRowType = NonNullable<HostOfferLookupTableDataType>[number] //type HostOfferLookupTableDataRowType = NonNullable<HostOfferLookupTableDataType>[number]
const makeMarker: (row: { id?: string | null; place_lon?: number | null; place_lat?: number | null }) => Marker | undefined = const makeMarker: (row: { id?: string | null; place_lon?: number | null; place_lat?: number | null }) => Marker | undefined =
({ id, place_lon, place_lat }) => id && place_lon && place_lat && ({ ({ id, place_lon, place_lat }) => id && place_lon && place_lat && ({
@ -39,20 +35,14 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
const {setMarkers, center, setFilteredMarkers} = useLeafletStore() const {setMarkers, center, setFilteredMarkers} = useLeafletStore()
useEffect(() => { useEffect(() => {
const markers = filterUndefOrNull( data_ro?.get_offers?.map(makeMarker) ) const markers = filterUndefOrNull( data_ro?.get_offers?.map(makeMarker) )
setMarkers(filterUndefOrNull(markers)) setMarkers(filterUndefOrNull(markers))
}, [data_ro]) }, [data_ro, setMarkers])
const data_ro_withDistance = data_ro?.get_offers?.map(r => ({...r,
place_distance: floor(haversine_distance(center?.lat, center?.lng, r.place_lat, r.place_lon))}))
const handleFilteredDataChange = useCallback( const handleFilteredDataChange = useCallback(
(data: HostOfferLookupTableDataType[]) => { (data: HostOfferLookupTableDataType[]) => {
// @ts-ignore // @ts-ignore
const _filteredMarkers = filterUndefOrNull( data.map(d => d && makeMarker(d))) const _filteredMarkers = filterUndefOrNull( data.map(d => d && makeMarker(d)))
console.log({_filteredMarkers}) setFilteredMarkers(_filteredMarkers)
//setFilteredMarkers(_filteredMarkers)
}, },
[setFilteredMarkers], [setFilteredMarkers],
); );
@ -74,10 +64,11 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
style={{flex: '1 1', height: '100%'}}> style={{flex: '1 1', height: '100%'}}>
<HostOfferLookupTable <HostOfferLookupTable
{...props} {...props}
data_ro={data_ro_withDistance} data_ro={data_ro.get_offers}
data_rw={data_rw?.get_rw} data_rw={data_rw?.get_rw}
refetch_rw={queryResult_rw.refetch} refetch_rw={queryResult_rw.refetch}
onFilteredDataChange={handleFilteredDataChange} onFilteredDataChange={handleFilteredDataChange}
center={center || undefined}
/> />
</div>} </div>}
</Box> </Box>

View File

@ -1,8 +1,13 @@
export type lon=number|null|undefined export type Lng =number|null|undefined
export type lat=number|null|undefined export type Lat =number|null|undefined
export type LatLng = {
lat: Lat
lng: Lng
}
/** optimization by https://stackoverflow.com/questions/5260423/torad-javascript-function-throwing-error/21623256#21623256 **/ /** optimization by https://stackoverflow.com/questions/5260423/torad-javascript-function-throwing-error/21623256#21623256 **/
export function haversine_distance(lat1:lat, lon1: lon, lat2:lat, lon2:lon) { export function haversine_distance(lat1:Lat, lon1: Lng, lat2:Lat, lon2:Lng) {
if(lat1 && lon1 && lat2 && lon2) { if(lat1 && lon1 && lat2 && lon2) {
var R = 6371 // Radius of the earth in km var R = 6371 // Radius of the earth in km
var dLat = (lat2 - lat1) * Math.PI / 180 // deg2rad below var dLat = (lat2 - lat1) * Math.PI / 180 // deg2rad below