2022-03-14 13:27:20 +01:00
|
|
|
import React, {ReactNode, useCallback, useEffect, useState} from 'react'
|
2022-03-09 18:23:27 +01:00
|
|
|
|
2022-03-13 23:37:29 +01:00
|
|
|
import {CheckBoxOutlineBlank, CheckBox} from '@mui/icons-material'
|
2022-03-09 18:23:27 +01:00
|
|
|
|
2022-03-09 17:01:05 +01:00
|
|
|
import '@inovua/reactdatagrid-community/index.css'
|
|
|
|
|
|
|
|
import DataGrid from '@inovua/reactdatagrid-community'
|
2022-03-13 23:37:29 +01:00
|
|
|
import {TypeColumn, TypeFilterValue, TypeSingleFilterValue} from "@inovua/reactdatagrid-community/types"
|
2022-03-09 17:01:05 +01:00
|
|
|
import DateFilter from '@inovua/reactdatagrid-community/DateFilter'
|
|
|
|
import StringFilter from '@inovua/reactdatagrid-community/StringFilter'
|
|
|
|
import BoolFilter from '@inovua/reactdatagrid-community/BoolFilter'
|
2022-03-13 23:37:29 +01:00
|
|
|
import NumberFilter from "@inovua/reactdatagrid-community/NumberFilter"
|
|
|
|
import BoolEditor from '@inovua/reactdatagrid-community/BoolEditor'
|
2022-03-17 15:00:02 +01:00
|
|
|
import {GetOffersQuery, GetRwQuery} from "../../codegen/generates"
|
2022-03-13 23:37:29 +01:00
|
|
|
import moment from "moment"
|
2022-03-09 17:19:45 +01:00
|
|
|
|
2022-03-13 23:37:29 +01:00
|
|
|
import {useTranslation} from "react-i18next"
|
|
|
|
import {resources} from '../../i18n/config'
|
2022-03-09 22:51:30 +01:00
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
import {fetcher} from '../../codegen/fetcher'
|
|
|
|
import {useAuthStore, AuthState} from '../Login'
|
2022-03-14 12:00:17 +01:00
|
|
|
import defaultColumnRawDefinition from "../config/defaultColumnRawDefinition";
|
|
|
|
import defaultColumnGroups from "../config/defaultColumnGroups";
|
2022-03-17 15:00:02 +01:00
|
|
|
import {ColumnRaw} from '../util/datagrid/columnRaw'
|
2022-03-14 13:27:20 +01:00
|
|
|
import columnsRaw from "../config/defaultColumnRawDefinition";
|
|
|
|
import {transformValue} from "../util/tableValueMapper";
|
2022-03-14 15:32:57 +01:00
|
|
|
import {filterUndefOrNull} from "../util/notEmpty";
|
2022-03-14 22:04:11 +01:00
|
|
|
import extendedFilter from "../util/datagrid/extendedFilter";
|
2022-03-17 15:00:02 +01:00
|
|
|
import {haversine_distance, LatLng} from "../util/distance";
|
2022-03-13 01:06:16 +01:00
|
|
|
|
2022-03-09 17:19:45 +01:00
|
|
|
global.moment = moment
|
2022-03-09 15:39:15 +01:00
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
export type HostOfferLookupTableDataType =
|
|
|
|
Omit<NonNullable<(GetOffersQuery["get_offers"] & GetRwQuery["get_rw"])>[number], '__typename'>
|
|
|
|
& { place_distance?: number };
|
2022-03-14 14:22:03 +01:00
|
|
|
export type HostOfferLookupTableProps = {
|
2022-03-14 15:32:57 +01:00
|
|
|
data_ro?: GetOffersQuery["get_offers"],
|
|
|
|
data_rw?: GetRwQuery["get_rw"], // TODO
|
2022-03-13 01:06:16 +01:00
|
|
|
refetch_rw: any,
|
2022-03-14 15:32:57 +01:00
|
|
|
onFilteredDataChange?: (data: HostOfferLookupTableDataType[]) => void
|
2022-03-17 15:00:02 +01:00
|
|
|
center?: LatLng
|
2022-03-09 15:39:15 +01:00
|
|
|
}
|
|
|
|
|
2022-03-09 17:01:05 +01:00
|
|
|
const filterMappings = {
|
|
|
|
string: StringFilter,
|
|
|
|
boolean: BoolFilter,
|
|
|
|
number: NumberFilter,
|
2022-03-12 18:12:14 +01:00
|
|
|
date: DateFilter,
|
2022-03-09 17:01:05 +01:00
|
|
|
}
|
2022-03-13 23:37:29 +01:00
|
|
|
const editorMappings = {
|
|
|
|
string: null,
|
|
|
|
boolean: BoolEditor,
|
|
|
|
number: null,
|
|
|
|
date: null
|
|
|
|
}
|
2022-03-09 17:01:05 +01:00
|
|
|
const operatorsForType = {
|
|
|
|
number: 'gte',
|
|
|
|
string: 'contains',
|
2022-03-12 18:12:14 +01:00
|
|
|
date: 'beforeOrOn',
|
|
|
|
boolean: 'eq',
|
2022-03-09 18:23:27 +01:00
|
|
|
}
|
2022-03-09 17:01:05 +01:00
|
|
|
|
2022-03-09 21:58:51 +01:00
|
|
|
type CustomRendererMatcher = {
|
|
|
|
match: { [key: string]: any }
|
|
|
|
render: (...args: any[]) => ReactNode
|
|
|
|
}
|
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
function Email({value}: { value: string }) {
|
2022-03-13 23:37:29 +01:00
|
|
|
const href = `mailto:${value}`
|
|
|
|
return <a href={href}>{value}</a>
|
|
|
|
}
|
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
function Phone({value}: { value: string }) {
|
2022-03-13 23:37:29 +01:00
|
|
|
const href = `tel:${value}`
|
|
|
|
return <a href={href}>{value}</a>
|
|
|
|
}
|
|
|
|
|
2022-03-09 21:58:51 +01:00
|
|
|
const customRendererForType: CustomRendererMatcher[] = [
|
2022-03-09 18:23:27 +01:00
|
|
|
{
|
|
|
|
match: {type: 'boolean'},
|
2022-03-10 12:41:20 +01:00
|
|
|
render: ({value}) => !!value ? <CheckBox/> : <CheckBoxOutlineBlank/>
|
2022-03-09 18:23:27 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
match: {type: 'string', name: 'contact_email'},
|
2022-03-13 23:37:29 +01:00
|
|
|
render: Email
|
|
|
|
},
|
|
|
|
{
|
|
|
|
match: {type: 'string', name: 'contact_phone'},
|
|
|
|
render: Phone
|
2022-03-09 18:23:27 +01:00
|
|
|
}
|
|
|
|
]
|
|
|
|
|
2022-03-12 18:12:14 +01:00
|
|
|
const findMatchingRenderer = (c: Partial<ColumnRaw>) => {
|
2022-03-09 18:23:27 +01:00
|
|
|
const customRenderer = customRendererForType.find(d => {
|
|
|
|
// @ts-ignore
|
2022-03-10 12:41:20 +01:00
|
|
|
return Object.keys(d.match).reduce((prev, cur) => prev && c[cur] === d.match[cur], true)
|
2022-03-09 18:23:27 +01:00
|
|
|
})
|
|
|
|
return customRenderer?.render
|
|
|
|
}
|
2022-03-09 17:01:05 +01:00
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
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
|
|
|
|
|
2022-03-14 12:00:17 +01:00
|
|
|
const columns: TypeColumn[] = defaultColumnRawDefinition
|
2022-03-09 17:01:05 +01:00
|
|
|
.map(c => ({
|
|
|
|
...c,
|
2022-03-09 21:58:51 +01:00
|
|
|
render: findMatchingRenderer(c) || undefined,
|
2022-03-13 23:37:29 +01:00
|
|
|
filterEditor: filterMappings[c.type as 'string' | 'number' | 'boolean' | 'date'],
|
|
|
|
editor: editorMappings[c.type as 'string' | 'number' | 'boolean' | 'date']
|
2022-03-09 17:01:05 +01:00
|
|
|
}))
|
|
|
|
|
|
|
|
const defaultFilterValue: TypeFilterValue = columns
|
2022-03-14 22:04:11 +01:00
|
|
|
.filter(({type}) => type && ['string', 'number', 'boolean', 'date'].includes(type))
|
2022-03-10 12:41:20 +01:00
|
|
|
.map(({name, type}) => {
|
2022-03-09 17:01:05 +01:00
|
|
|
return {
|
|
|
|
name,
|
|
|
|
type,
|
2022-03-09 18:23:27 +01:00
|
|
|
value: null,
|
|
|
|
operator: operatorsForType[type as 'string' | 'number' | 'date' | 'boolean']
|
2022-03-09 17:01:05 +01:00
|
|
|
} as unknown as TypeSingleFilterValue
|
2022-03-10 12:41:20 +01:00
|
|
|
})
|
2022-03-09 17:01:05 +01:00
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
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
|
|
|
|
}
|
2022-03-13 23:37:29 +01:00
|
|
|
const result = await fetcher<any, any>(`mutation WriteRW($auth: Auth!, $onEditCompleteByType: Boolean) {
|
|
|
|
write_rw(auth: $auth, onEditCompleteByType: $onEditCompleteByType) }`,
|
2022-03-17 15:00:02 +01:00
|
|
|
{auth, onEditCompleteByType})()
|
2022-03-13 01:06:16 +01:00
|
|
|
return result?.write_rw
|
|
|
|
}
|
|
|
|
|
2022-03-14 22:04:11 +01:00
|
|
|
const rw_default = {
|
|
|
|
rw_contacted: false,
|
|
|
|
rw_contact_replied: false,
|
|
|
|
rw_offer_occupied: false,
|
2022-03-17 15:00:02 +01:00
|
|
|
rw_note: ''
|
|
|
|
} // Required for filtering 'Not empty'. TODO: Should be fixed in StringFilter
|
|
|
|
|
|
|
|
const HostOfferLookupTable = ({
|
|
|
|
data_ro,
|
|
|
|
data_rw,
|
|
|
|
refetch_rw,
|
|
|
|
onFilteredDataChange,
|
|
|
|
center
|
|
|
|
}: HostOfferLookupTableProps) => {
|
2022-03-14 15:32:57 +01:00
|
|
|
const [dataSource, setDataSource] = useState<HostOfferLookupTableDataType[]>([]);
|
|
|
|
const [filteredData, setFilteredData] = useState<HostOfferLookupTableDataType[]>([]);
|
2022-03-17 15:00:02 +01:00
|
|
|
const [filterValue, setFilterValue] = useState<TypeFilterValue | undefined>(defaultFilterValue);
|
2022-03-14 22:04:11 +01:00
|
|
|
|
2022-03-14 23:34:12 +01:00
|
|
|
const filterValueChangeHandler = useCallback((_filterValue?: TypeFilterValue) => {
|
2022-03-17 15:00:02 +01:00
|
|
|
setFilterValue(_filterValue);
|
|
|
|
}, [setFilterValue])
|
|
|
|
|
|
|
|
const filterAndSetData = useCallback(() => {
|
|
|
|
if (!filterValue) {
|
|
|
|
setFilteredData(dataSource)
|
|
|
|
onFilteredDataChange && onFilteredDataChange(dataSource)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const data = extendedFilter(dataSource, filterValue, columnsRaw)
|
2022-03-14 14:16:43 +01:00
|
|
|
setFilteredData(data)
|
2022-03-17 15:00:02 +01:00
|
|
|
}, [dataSource, onFilteredDataChange, setFilteredData, filterValue])
|
2022-03-14 13:27:20 +01:00
|
|
|
|
2022-03-17 15:00:02 +01:00
|
|
|
useEffect(() => {
|
|
|
|
filterAndSetData()
|
|
|
|
}, [dataSource, filterValue, filterAndSetData]);
|
2022-03-14 13:27:20 +01:00
|
|
|
|
|
|
|
useEffect(() => {
|
2022-03-17 15:00:02 +01:00
|
|
|
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)
|
2022-03-14 15:32:57 +01:00
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
data && setDataSource(data)
|
2022-03-17 15:00:02 +01:00
|
|
|
}, [data_ro, data_rw, center]);
|
2022-03-13 01:06:16 +01:00
|
|
|
|
|
|
|
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()`
|
2022-03-17 15:00:02 +01:00
|
|
|
TODO: error handling **/
|
2022-03-13 23:37:29 +01:00
|
|
|
await mutate(auth, {value, columnId, rowId}) && refetch_rw()
|
2022-03-13 23:53:05 +01:00
|
|
|
}, [auth, refetch_rw])
|
2022-03-09 22:51:30 +01:00
|
|
|
|
2022-03-10 12:41:20 +01:00
|
|
|
const {i18n: {language}} = useTranslation()
|
2022-03-09 22:51:30 +01:00
|
|
|
// @ts-ignore
|
|
|
|
const reactdatagridi18n = resources[language]?.translation?.reactdatagrid
|
|
|
|
|
2022-03-14 13:27:20 +01:00
|
|
|
return <DataGrid
|
2022-03-17 15:00:02 +01:00
|
|
|
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}
|
|
|
|
/>
|
2022-03-09 15:39:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export default HostOfferLookupTable
|