Compare commits

..

2 Commits

Author SHA1 Message Date
Johannes Lötzsch a14d15b531
Merge pull request #13 from PluggPreagar/main
Update README.md
2022-03-17 14:17:55 +01:00
PluggPreagar 20fa2205fa
Update README.md
move to generic debug
2022-03-17 14:15:02 +01:00
11 changed files with 105 additions and 238 deletions

View File

@ -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
```

View File

@ -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",

View File

@ -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

View File

@ -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>

View File

@ -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>
</>) </>)

View File

@ -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}) )
})) }))

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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