Compare commits
2 Commits
feature/sh
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
a14d15b531 | ||
|
20fa2205fa |
|
@ -51,6 +51,27 @@ To set config options at runtime, use `environment variables`, `java system prop
|
||||||
|
|
||||||
## Notes to developers
|
## Notes to developers
|
||||||
|
|
||||||
|
Login using credentials from DB_SEED. Hint: be careful with escaping ;-)
|
||||||
|
|
||||||
|
### debug
|
||||||
|
|
||||||
|
in case of failure you can add detail information by activating verbose-mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SET VERBOSE=true
|
||||||
|
lein run
|
||||||
|
```
|
||||||
|
|
||||||
|
to check using test-data use
|
||||||
|
|
||||||
|
```bash
|
||||||
|
set DB_SEED="src/beherbergung/db/seed/test.edn"
|
||||||
|
lein run
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### State management
|
### State management
|
||||||
|
|
||||||
Global state (e.g. instances of database and webserver) are managed by [mount](https://github.com/tolitius/mount).
|
Global state (e.g. instances of database and webserver) are managed by [mount](https://github.com/tolitius/mount).
|
||||||
|
@ -80,19 +101,3 @@ consider download, self-install and run via lein.bat
|
||||||
curl https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein.bat >lein.bat
|
curl https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein.bat >lein.bat
|
||||||
lein run
|
lein run
|
||||||
```
|
```
|
||||||
|
|
||||||
### debug
|
|
||||||
|
|
||||||
in case of failure you can add detail information by activating verbose-mode
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SET VERBOSE=true
|
|
||||||
lein run
|
|
||||||
```
|
|
||||||
|
|
||||||
to check using test-data use
|
|
||||||
|
|
||||||
```bash
|
|
||||||
set DB_SEED="src/beherbergung/db/seed/test.edn"
|
|
||||||
lein run
|
|
||||||
```
|
|
||||||
|
|
|
@ -37,12 +37,7 @@ const columnsRaw: ColumnRaw[] = [
|
||||||
"group": "distance",
|
"group": "distance",
|
||||||
"header": "km",
|
"header": "km",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"defaultWidth": 105,
|
"defaultWidth": 105
|
||||||
"options": {
|
|
||||||
"filter": {
|
|
||||||
"operator": "lte"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "place_country",
|
"name": "place_country",
|
||||||
|
@ -67,15 +62,7 @@ const columnsRaw: ColumnRaw[] = [
|
||||||
"group": "time",
|
"group": "time",
|
||||||
"header": "From",
|
"header": "From",
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"defaultWidth": 90,
|
"defaultWidth": 90
|
||||||
"options": {
|
|
||||||
"dateFormat": "MM/DD/YYYY",
|
|
||||||
"transform": {
|
|
||||||
"date2Iso": {
|
|
||||||
"inputDateFormat": "MM/DD/YYYY"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "time_duration_str",
|
"name": "time_duration_str",
|
||||||
|
@ -87,14 +74,7 @@ const columnsRaw: ColumnRaw[] = [
|
||||||
"name": "languages",
|
"name": "languages",
|
||||||
"header": "Languages",
|
"header": "Languages",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"defaultWidth": 200,
|
"defaultWidth": 200
|
||||||
"options": {
|
|
||||||
"transform": {
|
|
||||||
array2string: {
|
|
||||||
join: ","
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "accessible",
|
"name": "accessible",
|
||||||
|
|
|
@ -5,40 +5,36 @@ import {CheckBoxOutlineBlank, CheckBox} from '@mui/icons-material'
|
||||||
import '@inovua/reactdatagrid-community/index.css'
|
import '@inovua/reactdatagrid-community/index.css'
|
||||||
|
|
||||||
import DataGrid from '@inovua/reactdatagrid-community'
|
import DataGrid from '@inovua/reactdatagrid-community'
|
||||||
|
import filter from '@inovua/reactdatagrid-community/filter'
|
||||||
import {TypeColumn, TypeFilterValue, TypeSingleFilterValue} from "@inovua/reactdatagrid-community/types"
|
import {TypeColumn, TypeFilterValue, TypeSingleFilterValue} from "@inovua/reactdatagrid-community/types"
|
||||||
import DateFilter from '@inovua/reactdatagrid-community/DateFilter'
|
import DateFilter from '@inovua/reactdatagrid-community/DateFilter'
|
||||||
import StringFilter from '@inovua/reactdatagrid-community/StringFilter'
|
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 {haversine_distance, LatLng} from "../util/distance";
|
|
||||||
|
|
||||||
global.moment = moment
|
global.moment = moment
|
||||||
|
|
||||||
export type HostOfferLookupTableDataType =
|
export type HostOfferLookupTableDataType = GetOffersQuery["get_offers"] & GetRwQuery["get_rw"];
|
||||||
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 = {
|
||||||
|
@ -65,12 +61,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>
|
||||||
}
|
}
|
||||||
|
@ -98,12 +94,6 @@ 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,
|
||||||
|
@ -112,87 +102,63 @@ const columns: TypeColumn[] = defaultColumnRawDefinition
|
||||||
editor: editorMappings[c.type as 'string' | 'number' | 'boolean' | 'date']
|
editor: editorMappings[c.type as 'string' | 'number' | 'boolean' | 'date']
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const defaultFilterValue: TypeFilterValue = defaultColumnRawDefinition
|
const defaultFilterValue: TypeFilterValue = columns
|
||||||
.filter(({type}) => type && ['string', 'number', 'boolean', 'date'].includes(type))
|
.filter(({type}) => type && ['string', 'number', 'date', 'boolean'].includes(type))
|
||||||
.map(({name, type, options}) => {
|
.map(({name, type}) => {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
value: null,
|
value: null,
|
||||||
operator: options?.filter?.operator || operatorsForType[type as 'string' | 'number' | 'date' | 'boolean']
|
operator: operatorsForType[type as 'string' | 'number' | 'date' | 'boolean']
|
||||||
} 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 = {
|
const onEditCompleteByType = {rowId: onEditComplete.rowId,
|
||||||
rowId: onEditComplete.rowId,
|
columnId: onEditComplete.columnId,
|
||||||
columnId: onEditComplete.columnId,
|
value_boolean: type === 'boolean' && onEditComplete.value || null,
|
||||||
value_boolean: type === 'boolean' && onEditComplete.value || null,
|
value_string: type === 'string' && 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
|
||||||
}
|
}
|
||||||
|
|
||||||
const rw_default = {
|
const rw_default = {rw_note: ''} // Required for filtering 'Not empty'. TODO: Should be fixed in StringFilter
|
||||||
rw_contacted: false,
|
|
||||||
rw_contact_replied: false,
|
|
||||||
rw_offer_occupied: false,
|
|
||||||
rw_note: ''
|
|
||||||
} // Required for filtering 'Not empty'. TODO: Should be fixed in StringFilter
|
|
||||||
|
|
||||||
const HostOfferLookupTable = ({
|
const HostOfferLookupTable = ({data_ro, data_rw, refetch_rw, onFilteredDataChange}: HostOfferLookupTableProps) => {
|
||||||
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 | undefined>(defaultFilterValue);
|
const [filterValue, setFilterValue] = useState(defaultFilterValue);
|
||||||
|
|
||||||
const filterValueChangeHandler = useCallback((_filterValue?: TypeFilterValue) => {
|
const filterValueChangeHandler = useCallback((_filterValue) => {
|
||||||
|
const data = filter(dataSource, filterValue) as HostOfferLookupTableDataType[]
|
||||||
setFilterValue(_filterValue);
|
setFilterValue(_filterValue);
|
||||||
}, [setFilterValue])
|
|
||||||
|
|
||||||
const filterAndSetData = useCallback(() => {
|
|
||||||
if (!filterValue) {
|
|
||||||
setFilteredData(dataSource)
|
|
||||||
onFilteredDataChange && onFilteredDataChange(dataSource)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const data = extendedFilter(dataSource, filterValue, columnsRaw)
|
|
||||||
onFilteredDataChange && onFilteredDataChange(data)
|
|
||||||
setFilteredData(data)
|
setFilteredData(data)
|
||||||
}, [dataSource, onFilteredDataChange, setFilteredData, filterValue])
|
onFilteredDataChange && onFilteredDataChange(data)
|
||||||
|
}, [dataSource])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
filterAndSetData()
|
// @ts-ignore
|
||||||
}, [dataSource, filterValue, filterAndSetData]);
|
const data = filterUndefOrNull( data_ro
|
||||||
|
?.map( e_ro => ({
|
||||||
useEffect(() => {
|
...((data_rw?.find((e_rw) => e_ro.id_tmp === e_rw.id || `rw_${e_ro.id}` === e_rw.id
|
||||||
const data = filterUndefOrNull(data_ro
|
) || rw_default)),
|
||||||
?.map(e_ro => ({
|
...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)
|
||||||
}, [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])
|
||||||
|
|
||||||
|
@ -201,22 +167,21 @@ const HostOfferLookupTable = ({
|
||||||
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}
|
defaultFilterValue={defaultFilterValue}
|
||||||
onFilterValueChange={filterValueChangeHandler}
|
rowIndexColumn
|
||||||
rowIndexColumn
|
enableSelection
|
||||||
enableSelection
|
enableColumnAutosize={false}
|
||||||
enableColumnAutosize={false}
|
columns={columns}
|
||||||
columns={columns}
|
dataSource={dataSource}
|
||||||
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
|
||||||
|
|
|
@ -1,25 +1,18 @@
|
||||||
import React, {useCallback, useEffect} from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useGetOffersQuery, useGetRwQuery } from "../../codegen/generates"
|
import { useGetOffersQuery, useGetRwQuery } from "../../codegen/generates"
|
||||||
import HostOfferLookupTable, {HostOfferLookupTableDataType, HostOfferLookupTableProps} from "./HostOfferLookupTable"
|
import HostOfferLookupTable, {HostOfferLookupTableProps} from "./HostOfferLookupTable"
|
||||||
import { Box } from "@mui/material"
|
import { Box } from "@mui/material"
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Login, useAuthStore } from '../Login'
|
import { Login, useAuthStore } from '../Login'
|
||||||
import {Marker, useLeafletStore} from './LeafletStore'
|
import { useLeafletStore } from './LeafletStore'
|
||||||
import { filterUndefOrNull } from '../util/notEmpty'
|
import { filterUndefOrNull } from '../util/notEmpty'
|
||||||
import { haversine_distance } from '../util/distance'
|
import { haversine_distance } from '../util/distance'
|
||||||
import {marker} from "leaflet";
|
|
||||||
|
|
||||||
type HostOfferLookupWrapperProps = Partial<HostOfferLookupTableProps>
|
type HostOfferLookupWrapperProps = Partial<HostOfferLookupTableProps>
|
||||||
|
|
||||||
//type HostOfferLookupTableDataRowType = NonNullable<HostOfferLookupTableDataType>[number]
|
function floor(v: number|undefined) {
|
||||||
const makeMarker: (row: { id?: string | null; place_lon?: number | null; place_lat?: number | null }) => Marker | undefined =
|
return v && Math.floor(v)
|
||||||
({ id, place_lon, place_lat }) => id && place_lon && place_lat && ({
|
}
|
||||||
id: id,
|
|
||||||
lat: place_lat,
|
|
||||||
lng: place_lon,
|
|
||||||
radius: 1000, // TODO
|
|
||||||
content: 'TODO'
|
|
||||||
}) || undefined;
|
|
||||||
|
|
||||||
const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
|
const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -32,20 +25,19 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
|
||||||
const {data: data_ro} = queryResult_ro
|
const {data: data_ro} = queryResult_ro
|
||||||
const {data: data_rw} = queryResult_rw
|
const {data: data_rw} = queryResult_rw
|
||||||
|
|
||||||
const {setMarkers, center, setFilteredMarkers} = useLeafletStore()
|
const {setMarkers, center} = useLeafletStore()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const markers = filterUndefOrNull( data_ro?.get_offers?.map(makeMarker) )
|
const markers = data_ro?.get_offers?.map(row => (row.id && row.place_lon && row.place_lat
|
||||||
|
&& ({id: row.id,
|
||||||
|
lat: row.place_lat,
|
||||||
|
lng: row.place_lon,
|
||||||
|
radius: 1000, // TODO
|
||||||
|
content: 'TODO'}) || undefined))
|
||||||
setMarkers(filterUndefOrNull(markers))
|
setMarkers(filterUndefOrNull(markers))
|
||||||
}, [data_ro, setMarkers])
|
}, [data_ro])
|
||||||
|
|
||||||
const handleFilteredDataChange = useCallback(
|
const data_ro_withDistance = data_ro?.get_offers?.map(r => ({...r,
|
||||||
(data: HostOfferLookupTableDataType[]) => {
|
place_distance: floor(haversine_distance(center?.lat, center?.lng, r.place_lat, r.place_lon))}))
|
||||||
// @ts-ignore
|
|
||||||
const _filteredMarkers = filterUndefOrNull( data.map(d => d && makeMarker(d)))
|
|
||||||
setFilteredMarkers(_filteredMarkers)
|
|
||||||
},
|
|
||||||
[setFilteredMarkers],
|
|
||||||
);
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
@ -64,11 +56,9 @@ const HostOfferLookupWrapper = (props: HostOfferLookupWrapperProps) => {
|
||||||
style={{flex: '1 1', height: '100%'}}>
|
style={{flex: '1 1', height: '100%'}}>
|
||||||
<HostOfferLookupTable
|
<HostOfferLookupTable
|
||||||
{...props}
|
{...props}
|
||||||
data_ro={data_ro.get_offers}
|
data_ro={data_ro_withDistance}
|
||||||
data_rw={data_rw?.get_rw}
|
data_rw={data_rw?.get_rw}
|
||||||
refetch_rw={queryResult_rw.refetch}
|
refetch_rw={queryResult_rw.refetch}
|
||||||
onFilteredDataChange={handleFilteredDataChange}
|
|
||||||
center={center || undefined}
|
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -20,20 +20,20 @@ import { useLeafletStore } from './LeafletStore'
|
||||||
type LeafletMapProps = {onBoundsChange?: (bounds: L.LatLngBounds) => void}
|
type LeafletMapProps = {onBoundsChange?: (bounds: L.LatLngBounds) => void}
|
||||||
|
|
||||||
const BoundsChangeListener = ({onBoundsChange}: {onBoundsChange?: (bounds: L.LatLngBounds) => void}) => {
|
const BoundsChangeListener = ({onBoundsChange}: {onBoundsChange?: (bounds: L.LatLngBounds) => void}) => {
|
||||||
const { setCenter} = useLeafletStore()
|
const leafletStore = useLeafletStore()
|
||||||
|
|
||||||
const map = useMap()
|
const map = useMap()
|
||||||
|
|
||||||
const updateBounds = useCallback(
|
const updateBounds = useCallback(
|
||||||
() => {
|
() => {
|
||||||
setCenter(map.getCenter())
|
leafletStore.setCenter(map.getCenter())
|
||||||
|
|
||||||
// onBoundsChange is unused at the moment
|
// onBoundsChange is unused at the moment
|
||||||
onBoundsChange && onBoundsChange(
|
onBoundsChange && onBoundsChange(
|
||||||
map.getBounds()
|
map.getBounds()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[map, onBoundsChange, setCenter],
|
[map, onBoundsChange],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -92,28 +92,17 @@ const LeafletMap = ({onBoundsChange}: LeafletMapProps) => {
|
||||||
</LayersControl.BaseLayer>
|
</LayersControl.BaseLayer>
|
||||||
|
|
||||||
|
|
||||||
{leafletStore.markers.map((m, i) =>
|
{leafletStore.markers.map(m =>
|
||||||
/** TODO: Maybe a clustered marker would be helpfull, but we loose the possibility of showing the radius (display accuracy of the coordinate).
|
/** TODO: Maybe a clustered marker would be helpfull, but we loose the possibility of showing the radius (display accuracy of the coordinate).
|
||||||
* Probably the best solution is showing Circle and clustered marker.
|
* Probably the best solution is showing Circle and clustered marker.
|
||||||
**/
|
**/
|
||||||
<Circle key={m.id + i.toString() /* Till we have unique ids from the db */}
|
<Circle key={m.id + Math.random() /* Till we have unique ids from the db */}
|
||||||
center={[m.lat, m.lng]}
|
center={[m.lat, m.lng]}
|
||||||
radius={m.radius}
|
radius={m.radius}
|
||||||
pathOptions={{color: 'grey'}}>
|
pathOptions={{color: 'grey'}}>
|
||||||
<Popup><a href={`#${m.id}`}>{ m.content }</a></Popup>
|
<Popup><a href={`#${m.id}`}>{ m.content }</a></Popup>
|
||||||
</Circle>
|
</Circle>
|
||||||
)}
|
)}
|
||||||
{leafletStore.filteredMarkers.map((m, i) =>
|
|
||||||
/** TODO: Maybe a clustered marker would be helpfull, but we loose the possibility of showing the radius (display accuracy of the coordinate).
|
|
||||||
* Probably the best solution is showing Circle and clustered marker.
|
|
||||||
**/
|
|
||||||
<Circle key={m.id + i.toString() /* Till we have unique ids from the db */}
|
|
||||||
center={[m.lat, m.lng]}
|
|
||||||
radius={m.radius}
|
|
||||||
pathOptions={{color: 'blue'}}>
|
|
||||||
<Popup><a href={`#${m.id}`}>{ m.content }</a></Popup>
|
|
||||||
</Circle>
|
|
||||||
)}
|
|
||||||
</LayersControl>
|
</LayersControl>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
</>)
|
</>)
|
||||||
|
|
|
@ -14,15 +14,11 @@ export interface LeafletState {
|
||||||
setCenter: (center: L.LatLng) => void
|
setCenter: (center: L.LatLng) => void
|
||||||
markers: Marker[]
|
markers: Marker[]
|
||||||
setMarkers: (markers: Marker[]) => void
|
setMarkers: (markers: Marker[]) => void
|
||||||
filteredMarkers: Marker[]
|
|
||||||
setFilteredMarkers: (filteredMarkers: Marker[]) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLeafletStore = create<LeafletState>(set => ({
|
export const useLeafletStore = create<LeafletState>(set => ({
|
||||||
center: null,
|
center: null,
|
||||||
setCenter: center => set( _orig => ({center}) ),
|
setCenter: center => set( _orig => ({center}) ),
|
||||||
markers: [],
|
markers: [],
|
||||||
setMarkers: markers => set( _orig => ({markers}) ),
|
setMarkers: markers => set( _orig => ({markers}) )
|
||||||
filteredMarkers: [],
|
|
||||||
setFilteredMarkers: filteredMarkers => set( _orig => ({filteredMarkers}) )
|
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import {Array2StringTransformOptions, DateToISOTransformOptions} from "../tableValueMapper";
|
import {Array2StringTransformOptions} from "../tableValueMapper";
|
||||||
|
|
||||||
export type ColumnOptions = {
|
export type ColumnOptions = {
|
||||||
dateFormat?: string
|
|
||||||
filter?: {
|
|
||||||
operator?: string
|
|
||||||
}
|
|
||||||
transform?: {
|
transform?: {
|
||||||
array2string?: Array2StringTransformOptions
|
array2string?: Array2StringTransformOptions
|
||||||
date2Iso?: DateToISOTransformOptions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface ColumnRaw {
|
export interface ColumnRaw {
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import {TypeColumn, TypeSingleFilterValue} from "@inovua/reactdatagrid-community/types";
|
|
||||||
import filter from "@inovua/reactdatagrid-community/filter";
|
|
||||||
import moment from "moment";
|
|
||||||
import {ColumnRaw} from "./columnRaw";
|
|
||||||
|
|
||||||
const defaultDateFormat = "MM/DD/YYYY"
|
|
||||||
const extendedFilter: <T>(
|
|
||||||
data: T[],
|
|
||||||
filterValue: TypeSingleFilterValue[],
|
|
||||||
columnsRaw: ColumnRaw[]
|
|
||||||
) => T[] = <T>(data: T[], filterValue: TypeSingleFilterValue[], columnsRaw: ColumnRaw[] ) => {
|
|
||||||
const columns = columnsRaw
|
|
||||||
.filter(({ type }) => type === "date")
|
|
||||||
.reduce((prev, cur) =>
|
|
||||||
({...prev ,[cur.name]: { dateFormat: cur.options?.dateFormat || defaultDateFormat }}), {})
|
|
||||||
return filter(
|
|
||||||
data,
|
|
||||||
filterValue.map(fV => {
|
|
||||||
if (typeof fV.value == 'string' && fV.type === 'date') {
|
|
||||||
return {
|
|
||||||
...fV,
|
|
||||||
value: moment(fV.value).format(defaultDateFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fV
|
|
||||||
}),
|
|
||||||
undefined,
|
|
||||||
columns) as T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default extendedFilter
|
|
|
@ -1,13 +1,8 @@
|
||||||
export type Lng =number|null|undefined
|
export type lon=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: Lng, lat2:Lat, lon2:Lng) {
|
export function haversine_distance(lat1:lat, lon1: lon, lat2:lat, lon2:lon) {
|
||||||
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
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
* https://stackoverflow.com/a/46700791/2726641
|
* https://stackoverflow.com/a/46700791/2726641
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
const isUndefOrEmpty = <TValue>(value: TValue | null | undefined): value is TValue => !(value === null || value === undefined)
|
export default <TValue>(value: TValue | null | undefined): value is TValue => !(value === null || value === undefined)
|
||||||
|
|
||||||
export const filterUndefOrNull = <T>(ts?: (T | undefined | null)[] | null): T[] => ts?.filter((t: T | undefined | null): t is T => t !== undefined && t !== null) || []
|
export const filterUndefOrNull = <T>(ts?: (T | undefined | null)[] | null): T[] => ts?.filter((t: T | undefined | null): t is T => t !== undefined && t !== null) || []
|
||||||
|
|
||||||
export default isUndefOrEmpty
|
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
import {ColumnRaw} from "./datagrid/columnRaw";
|
import {ColumnRaw} from "./datagrid/columnRaw";
|
||||||
import moment from "moment"
|
|
||||||
|
|
||||||
export type Array2StringTransformOptions = {
|
export type Array2StringTransformOptions = {
|
||||||
join?: string
|
join?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DateToISOTransformOptions = {
|
|
||||||
inputDateFormat: string
|
|
||||||
outputDateFormat?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const array2string = (value: string[], options: Array2StringTransformOptions) => value.join(options.join || ',')
|
const array2string = (value: string[], options: Array2StringTransformOptions) => value.join(options.join || ',')
|
||||||
|
|
||||||
const callOneOrMany: <T, O>(els: T | T[], cb: (d: T, ...rest: any[]) => O, args?: any[]) => O | O [] = (els, cb, args = []) =>
|
|
||||||
Array.isArray(els) ? els.map(v => cb(v, ...args)) : cb(els, ...args)
|
|
||||||
|
|
||||||
const dateToIso: (date: string, options: DateToISOTransformOptions) => string =
|
|
||||||
(date, { inputDateFormat, outputDateFormat}) => moment(date, inputDateFormat).format(outputDateFormat)
|
|
||||||
|
|
||||||
export const transformValue: <T>(values: T, columnsRaw: ColumnRaw[]) => T = (values, columnsRaw) => {
|
export const transformValue: <T>(values: T, columnsRaw: ColumnRaw[]) => T = (values, columnsRaw) => {
|
||||||
const newValues = {...values}
|
const newValues = {...values}
|
||||||
columnsRaw
|
columnsRaw
|
||||||
|
@ -26,11 +14,8 @@ export const transformValue: <T>(values: T, columnsRaw: ColumnRaw[]) => T = (val
|
||||||
const transform = c.options?.transform
|
const transform = c.options?.transform
|
||||||
if (!transform) return
|
if (!transform) return
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let value = values[c.name]
|
const value = values[c.name]
|
||||||
if (!value) return
|
if (!value) return
|
||||||
if(transform.date2Iso) {
|
|
||||||
value = callOneOrMany(value, dateToIso, [ transform.date2Iso ])
|
|
||||||
}
|
|
||||||
if (transform.array2string) {
|
if (transform.array2string) {
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
Loading…
Reference in New Issue
Block a user