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