diff --git a/frontend/search/components/ngo/HostOfferLookupTable.tsx b/frontend/search/components/ngo/HostOfferLookupTable.tsx index cdb8f34..2ac2fea 100644 --- a/frontend/search/components/ngo/HostOfferLookupTable.tsx +++ b/frontend/search/components/ngo/HostOfferLookupTable.tsx @@ -11,30 +11,34 @@ import StringFilter from '@inovua/reactdatagrid-community/StringFilter' import BoolFilter from '@inovua/reactdatagrid-community/BoolFilter' import NumberFilter from "@inovua/reactdatagrid-community/NumberFilter" import BoolEditor from '@inovua/reactdatagrid-community/BoolEditor' -import { GetOffersQuery, GetRwQuery} from "../../codegen/generates" +import {GetOffersQuery, GetRwQuery} from "../../codegen/generates" import moment from "moment" import {useTranslation} from "react-i18next" import {resources} from '../../i18n/config' -import { fetcher } from '../../codegen/fetcher' -import { useAuthStore, AuthState } from '../Login' +import {fetcher} from '../../codegen/fetcher' +import {useAuthStore, AuthState} from '../Login' import defaultColumnRawDefinition from "../config/defaultColumnRawDefinition"; import defaultColumnGroups from "../config/defaultColumnGroups"; -import { ColumnRaw } from '../util/datagrid/columnRaw' +import {ColumnRaw} from '../util/datagrid/columnRaw' import columnsRaw from "../config/defaultColumnRawDefinition"; import {transformValue} from "../util/tableValueMapper"; import {filterUndefOrNull} from "../util/notEmpty"; import extendedFilter from "../util/datagrid/extendedFilter"; +import {haversine_distance, LatLng} from "../util/distance"; global.moment = moment -export type HostOfferLookupTableDataType = GetOffersQuery["get_offers"] & GetRwQuery["get_rw"]; +export type HostOfferLookupTableDataType = + Omit[number], '__typename'> + & { place_distance?: number }; export type HostOfferLookupTableProps = { data_ro?: GetOffersQuery["get_offers"], data_rw?: GetRwQuery["get_rw"], // TODO refetch_rw: any, onFilteredDataChange?: (data: HostOfferLookupTableDataType[]) => void + center?: LatLng } const filterMappings = { @@ -61,12 +65,12 @@ type CustomRendererMatcher = { render: (...args: any[]) => ReactNode } -function Email({value}: {value: string}) { +function Email({value}: { value: string }) { const href = `mailto:${value}` return {value} } -function Phone({value}: {value: string}) { +function Phone({value}: { value: string }) { const href = `tel:${value}` return {value} } @@ -94,6 +98,12 @@ const findMatchingRenderer = (c: Partial) => { 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 .map(c => ({ ...c, @@ -113,15 +123,17 @@ const defaultFilterValue: TypeFilterValue = columns } as unknown as TypeSingleFilterValue }) -async function mutate(auth: AuthState, onEditComplete: {value: string, columnId: string, rowId: string}) { - const type = typeof(onEditComplete.value) - const onEditCompleteByType = {rowId: onEditComplete.rowId, - columnId: onEditComplete.columnId, - value_boolean: type === 'boolean' && onEditComplete.value || null, - value_string: type === 'string' && onEditComplete.value || null} +async function mutate(auth: AuthState, onEditComplete: { value: string, columnId: string, rowId: string }) { + const type = typeof (onEditComplete.value) + const onEditCompleteByType = { + rowId: onEditComplete.rowId, + columnId: onEditComplete.columnId, + value_boolean: type === 'boolean' && onEditComplete.value || null, + value_string: type === 'string' && onEditComplete.value || null + } const result = await fetcher(`mutation WriteRW($auth: Auth!, $onEditCompleteByType: Boolean) { write_rw(auth: $auth, onEditCompleteByType: $onEditCompleteByType) }`, - {auth, onEditCompleteByType})() + {auth, onEditCompleteByType})() return result?.write_rw } @@ -129,46 +141,57 @@ const rw_default = { rw_contacted: false, rw_contact_replied: 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([]); const [filteredData, setFilteredData] = useState([]); - const [filterValue, setFilterValue] = useState(defaultFilterValue); + const [filterValue, setFilterValue] = useState(defaultFilterValue); const filterValueChangeHandler = useCallback((_filterValue?: TypeFilterValue) => { - const data = !_filterValue - ? dataSource - : extendedFilter( - dataSource, - _filterValue, - columnsRaw - ) - _filterValue && setFilterValue(_filterValue); - setFilteredData(data) - onFilteredDataChange && onFilteredDataChange(data) - }, [dataSource]) + setFilterValue(_filterValue); + }, [setFilterValue]) + const filterAndSetData = useCallback(() => { + if (!filterValue) { + setFilteredData(dataSource) + onFilteredDataChange && onFilteredDataChange(dataSource) + return + } + const data = extendedFilter(dataSource, filterValue, columnsRaw) + setFilteredData(data) + }, [dataSource, onFilteredDataChange, setFilteredData, filterValue]) useEffect(() => { - const data = filterUndefOrNull( data_ro - ?.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)) + filterAndSetData() + }, [dataSource, filterValue, filterAndSetData]); + + useEffect(() => { + const data = filterUndefOrNull(data_ro + ?.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 data && setDataSource(data) - filterValueChangeHandler() - //setDataSource((/*data_rw?.get_rw || */ data_ro?.get_offers || []).map(v => transformValue(v, columnsRaw))) - }, [data_ro, data_rw]); + }, [data_ro, data_rw, center]); const auth = useAuthStore() 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()` - TODO: error handling **/ + TODO: error handling **/ await mutate(auth, {value, columnId, rowId}) && refetch_rw() }, [auth, refetch_rw]) @@ -177,22 +200,22 @@ const HostOfferLookupTable = ({data_ro, data_rw, refetch_rw, onFilteredDataChang const reactdatagridi18n = resources[language]?.translation?.reactdatagrid return + idProperty="id" + filterable + showColumnMenuFilterOptions={true} + showFilteringMenuItems={true} + filterValue={filterValue} + onFilterValueChange={filterValueChangeHandler} + rowIndexColumn + enableSelection + enableColumnAutosize={false} + columns={columns} + dataSource={filteredData} + i18n={reactdatagridi18n || undefined} + style={{height: '100%'}} + onEditComplete={onEditComplete} + groups={defaultColumnGroups} + /> } export default HostOfferLookupTable diff --git a/frontend/search/components/ngo/HostOfferLookupWrapper.tsx b/frontend/search/components/ngo/HostOfferLookupWrapper.tsx index 23af1fa..f18d672 100644 --- a/frontend/search/components/ngo/HostOfferLookupWrapper.tsx +++ b/frontend/search/components/ngo/HostOfferLookupWrapper.tsx @@ -11,10 +11,6 @@ import {marker} from "leaflet"; type HostOfferLookupWrapperProps = Partial -function floor(v: number|undefined) { - return v && Math.floor(v) -} - //type HostOfferLookupTableDataRowType = NonNullable[number] 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 && ({ @@ -39,20 +35,14 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => { const {setMarkers, center, setFilteredMarkers} = useLeafletStore() useEffect(() => { const markers = filterUndefOrNull( data_ro?.get_offers?.map(makeMarker) ) - - setMarkers(filterUndefOrNull(markers)) - }, [data_ro]) - - 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))})) + }, [data_ro, setMarkers]) const handleFilteredDataChange = useCallback( (data: HostOfferLookupTableDataType[]) => { // @ts-ignore const _filteredMarkers = filterUndefOrNull( data.map(d => d && makeMarker(d))) - console.log({_filteredMarkers}) - //setFilteredMarkers(_filteredMarkers) + setFilteredMarkers(_filteredMarkers) }, [setFilteredMarkers], ); @@ -74,10 +64,11 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => { style={{flex: '1 1', height: '100%'}}> } diff --git a/frontend/search/components/util/distance.ts b/frontend/search/components/util/distance.ts index 64d8958..7e62cb7 100644 --- a/frontend/search/components/util/distance.ts +++ b/frontend/search/components/util/distance.ts @@ -1,8 +1,13 @@ -export type lon=number|null|undefined -export type lat=number|null|undefined +export type Lng =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 **/ -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) { var R = 6371 // Radius of the earth in km var dLat = (lat2 - lat1) * Math.PI / 180 // deg2rad below