1
|
import {
|
2
|
Link,
|
3
|
Table,
|
4
|
TableBody,
|
5
|
TableCell,
|
6
|
TableContainer,
|
7
|
TableHead,
|
8
|
TablePagination,
|
9
|
TableRow,
|
10
|
Typography,
|
11
|
} from '@mui/material'
|
12
|
import { Fragment, useEffect, useState } from 'react'
|
13
|
import { Link as RouterLink } from 'react-router-dom'
|
14
|
import { CatalogItemDto } from '../../swagger/data-contracts'
|
15
|
import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
|
16
|
import ContentLoading from '../Reusables/ContentLoading'
|
17
|
import { RootState } from '../redux/store'
|
18
|
import { useDispatch, useSelector } from 'react-redux'
|
19
|
import {
|
20
|
consumeError,
|
21
|
setLoading,
|
22
|
setRowsPerPage,
|
23
|
ShowAllItemsOption,
|
24
|
} from './catalogSlice'
|
25
|
import { fetchItems } from './catalogThunks'
|
26
|
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
|
27
|
|
28
|
// Catalog table component
|
29
|
const CatalogTable = () => {
|
30
|
const [page, setPage] = useState(0) // currently shown page
|
31
|
|
32
|
const dispatch = useDispatch()
|
33
|
|
34
|
// Rows per page
|
35
|
const rowsPerPageOptions = useSelector(
|
36
|
(state: RootState) => state.catalog.rowsPerPageOptions
|
37
|
)
|
38
|
const rowsPerPage = useSelector(
|
39
|
(state: RootState) => state.catalog.rowsPerPage
|
40
|
)
|
41
|
|
42
|
// Items, loading and error from api
|
43
|
const items = useSelector((state: RootState) => state.catalog.items)
|
44
|
const loading = useSelector((state: RootState) => state.catalog.loading)
|
45
|
const apiError = useSelector((state: RootState) => state.catalog.error)
|
46
|
|
47
|
// Local state to display any error relevant error
|
48
|
const [displayError, setDisplayError] = useState<string | undefined>(
|
49
|
undefined
|
50
|
)
|
51
|
const [unload, setUnload] = useState(true)
|
52
|
|
53
|
// When changing rows per page set the selected number and reset to the first page
|
54
|
const onRowsPerPageChange = (
|
55
|
event: React.ChangeEvent<HTMLInputElement>
|
56
|
) => {
|
57
|
dispatch(setRowsPerPage(Number(event.target.value)))
|
58
|
setPage(0)
|
59
|
}
|
60
|
|
61
|
useEffect(() => {
|
62
|
// Fetch items when the component is mounted
|
63
|
// This will automatically search whenever the filter changes
|
64
|
dispatch(fetchItems())
|
65
|
|
66
|
return () => {
|
67
|
// Invalidate the state when unmounting so that the old list is not rerendered when the user returns to the page
|
68
|
dispatch(setLoading())
|
69
|
}
|
70
|
}, [dispatch])
|
71
|
|
72
|
// Use effect to read the error and consume it
|
73
|
useEffect(() => {
|
74
|
if (apiError) {
|
75
|
setDisplayError(apiError)
|
76
|
dispatch(consumeError())
|
77
|
}
|
78
|
}, [apiError, dispatch])
|
79
|
|
80
|
// Name of columns in the header
|
81
|
const columns = [
|
82
|
'Name',
|
83
|
'Alternative Names',
|
84
|
'Written form',
|
85
|
'Type',
|
86
|
'State or Territory',
|
87
|
'Coordinates',
|
88
|
'Certainty',
|
89
|
]
|
90
|
|
91
|
const mapValueOrDefault = (value?: string, textStyle?: any) => (
|
92
|
<TableCell align="center">
|
93
|
<Typography
|
94
|
sx={{
|
95
|
...textStyle,
|
96
|
}}
|
97
|
>
|
98
|
{formatHtmlStringToReactDom(value as string)}
|
99
|
</Typography>
|
100
|
</TableCell>
|
101
|
)
|
102
|
|
103
|
// Maps catalogItem to corresponding table row
|
104
|
const mapItemColumnValues = (item: CatalogItemDto) => (
|
105
|
<Fragment>
|
106
|
{/* {mapValueOrDefault(item.name)} */}
|
107
|
<TableCell align="center">
|
108
|
<Link
|
109
|
component={RouterLink}
|
110
|
to={`/catalog/${item.id as string}`}
|
111
|
onClick={() => setUnload(false)}
|
112
|
>
|
113
|
{item.name}
|
114
|
</Link>
|
115
|
</TableCell>
|
116
|
{mapValueOrDefault(item.allNames?.join(', '), {
|
117
|
display: '-webkit-box',
|
118
|
overflow: 'hidden',
|
119
|
WebkitBoxOrient: 'vertical',
|
120
|
wordBreak: 'break-all',
|
121
|
WebkitLineClamp: 2,
|
122
|
})}
|
123
|
{mapValueOrDefault(item.writtenForms?.join(', '))}
|
124
|
{mapValueOrDefault(item.types?.join(', '))}
|
125
|
{mapValueOrDefault(item.countries?.join(', '))}
|
126
|
{mapValueOrDefault(
|
127
|
item.latitude && item.longitude
|
128
|
? `${item.latitude}, ${item.longitude}`
|
129
|
: undefined
|
130
|
)}
|
131
|
{mapValueOrDefault(
|
132
|
item.certainty ? `${item.certainty}` : undefined
|
133
|
)}
|
134
|
</Fragment>
|
135
|
)
|
136
|
|
137
|
return (
|
138
|
<Fragment>
|
139
|
<ShowErrorIfPresent err={displayError} />
|
140
|
{loading && !displayError ? <ContentLoading /> : null}
|
141
|
{!loading && !displayError ? (
|
142
|
<Fragment>
|
143
|
<TableContainer sx={{ minHeight: '60vh', maxHeight: '60vh' }}>
|
144
|
<Table
|
145
|
stickyHeader
|
146
|
sx={{ minWidth: 400 }}
|
147
|
aria-label="catalogTable"
|
148
|
>
|
149
|
<TableHead>
|
150
|
<TableRow>
|
151
|
{columns.map((col, idx) => (
|
152
|
<TableCell key={idx} align="center">
|
153
|
{col}
|
154
|
</TableCell>
|
155
|
))}
|
156
|
</TableRow>
|
157
|
</TableHead>
|
158
|
<TableBody>
|
159
|
{items
|
160
|
.slice(
|
161
|
page * rowsPerPage,
|
162
|
page * rowsPerPage + rowsPerPage
|
163
|
)
|
164
|
.map((row, idx) => (
|
165
|
<TableRow hover tabIndex={-1} key={idx}>
|
166
|
{mapItemColumnValues(row)}
|
167
|
</TableRow>
|
168
|
))}
|
169
|
</TableBody>
|
170
|
</Table>
|
171
|
</TableContainer>
|
172
|
<TablePagination
|
173
|
rowsPerPageOptions={rowsPerPageOptions.map((item) => ({
|
174
|
value:
|
175
|
item === ShowAllItemsOption
|
176
|
? items.length
|
177
|
: item,
|
178
|
label: item as string,
|
179
|
}))}
|
180
|
component="div"
|
181
|
count={items.length}
|
182
|
rowsPerPage={rowsPerPage}
|
183
|
page={page}
|
184
|
onPageChange={(_, newPage) => setPage(newPage)}
|
185
|
onRowsPerPageChange={onRowsPerPageChange}
|
186
|
/>
|
187
|
</Fragment>
|
188
|
) : null}
|
189
|
</Fragment>
|
190
|
)
|
191
|
}
|
192
|
|
193
|
export default CatalogTable
|