Skip to content

Commit

Permalink
Merge pull request #115 from TechMaarten/main
Browse files Browse the repository at this point in the history
All strudel example app updates
  • Loading branch information
TechMaarten authored Aug 5, 2024
2 parents 1af50ee + 5854afb commit add092f
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 105 deletions.
96 changes: 61 additions & 35 deletions strudel_ex/gbif-app/src/pages/science-info/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import { Box, Container, Paper, Stack, Typography } from '@mui/material';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PageHeader } from '../../components/PageHeader';
import { useExploreData } from './_context/ContextProvider';
import axios from 'axios';

/**
* Work in Progress:
*
* Detail view for a selected row from the` <DataExplorer>` in the explore-data Task Flow.
*/
const DataDetailPage: React.FC = () => {
const {state, dispatch} = useExploreData();
const { state } = useExploreData();
const params = useParams();
const entity = state.data?.find((d) => {
if (params.id) {
return d[state.dataIdField].toString() === params.id.toString();
}
});
console.log(state);
console.log(entity);

// State to hold the fetched entity data
const [entity, setEntity] = useState<any>(null);
// State to manage loading status
const [loading, setLoading] = useState(true);
// State to manage error messages
const [error, setError] = useState<string | null>(null);

// Fetch the entity data when the component mounts or when the id parameter changes
useEffect(() => {
const fetchEntity = async () => {
if (params.id) {
try {
// Make a GET request to the GBIF API to fetch the occurrence data
const response = await axios.get(`https://api.gbif.org/v1/occurrence/${params.id}`);
setEntity(response.data); // Set the fetched data to the entity state
} catch (err) {
setError('Failed to fetch data'); // Set error message if the request fails
} finally {
setLoading(false); // Set loading to false after the request completes
}
}
};
fetchEntity();
}, [params.id]); // Dependency array ensures this effect runs when the id parameter changes

// Determine the title to display based on the fetched entity data
const entityTitle = entity ? entity[state.columns[0].field] : 'Not Found';

/**
Expand All @@ -35,32 +56,37 @@ const DataDetailPage: React.FC = () => {
}}
/>
<Container maxWidth="xl">
<Stack>
<Paper
sx={{
padding: 2
}}
>
<Stack>
<Typography fontWeight="bold">
{state.columns[1].field}
</Typography>
<Typography>
{entity && entity[state.columns[1].field]}
</Typography>
</Stack>
</Paper>
<Paper
sx={{
padding: 2
}}
>
More coming soon!
</Paper>
</Stack>
{/* Display loading message while data is being fetched */}
{loading ? (
<Typography>Loading...</Typography>
) : error ? (
// Display error message if there is an error
<Typography>{error}</Typography>
) : (
// Display the fetched entity data
<Stack>
<Paper
sx={{
padding: 2,
}}
>
<Stack>
<Typography fontWeight="bold">{state.columns[1].field}</Typography>
<Typography>{entity && entity[state.columns[1].field]}</Typography>
</Stack>
</Paper>
<Paper
sx={{
padding: 2,
}}
>
More coming soon!
</Paper>
</Stack>
)}
</Container>
</Box>
)
}
);
};

export default DataDetailPage;
export default DataDetailPage;
Original file line number Diff line number Diff line change
@@ -1,36 +1,83 @@
import FilterListIcon from '@mui/icons-material/FilterList';
import { Button, Paper, Stack, TextField, Typography } from '@mui/material';
import { GridEventListener } from '@mui/x-data-grid';
import React from 'react';
import ViewListIcon from '@mui/icons-material/ViewList';
import ViewModuleIcon from '@mui/icons-material/ViewModule';
import { Button, Paper, Stack, TextField, Typography, Grid, Card, CardContent, CardMedia, Link, Pagination, Select, MenuItem, FormControl, InputLabel } from '@mui/material';
import { GridEventListener, GridPaginationModel } from '@mui/x-data-grid';
import React, { useEffect, useState } from 'react';
import { DataGrid } from '@mui/x-data-grid';
import { useExploreData } from '../_context/ContextProvider';
import { setPreviewItem, setSearch } from '../_context/actions';
import { PreviewPanel } from './PreviewPanel';

// Define an interface for the data items
interface DataItem {
scientificName: string;
eventDate?: string;
country?: string;
media?: { identifier: string }[];
[key: string]: any; // This allows for additional fields not explicitly defined
}

interface DataTablePanelProps {
onToggleFiltersPanel: () => any
onToggleFiltersPanel: () => any;
}

/**
* Main data table and its header section in the explore-data Task Flow.
* Columns are configured based on definitions.columns.main.
* Data in this table is filtered by the inputs in the filters panel.
*/
export const DataTablePanel: React.FC<DataTablePanelProps> = (props) => {
const {state, dispatch} = useExploreData();
export const DataTablePanel: React.FC<DataTablePanelProps> = (props) => {
const { state, dispatch } = useExploreData();

// State to hold the rows of data
const [rows, setRows] = useState<DataItem[]>([]);

// State to manage pagination settings
const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 10 });

// State to hold the total count of rows in the dataset
const [rowCount, setRowCount] = useState(0);

// State to manage view mode (table or gallery)
const [isGalleryView, setIsGalleryView] = useState(false);

// State to manage the visibility of the preview panel
const [showPreviewPanel, setShowPreviewPanel] = useState(false);

// Handler for row click event
const handleRowClick: GridEventListener<'rowClick'> = (rowData) => {
dispatch(setPreviewItem(rowData.row));
setShowPreviewPanel(true); // Show the preview panel when a row is clicked
};

// Handler for search input change event
const handleSearch: React.ChangeEventHandler<HTMLInputElement> = (evt) => {
dispatch(setSearch(evt.target.value));
};

/**
* Content to render on the page for this component
*/

// Function to fetch data from the GBIF API based on the current page and page size
const fetchData = async (page: number, pageSize: number) => {
const offset = page * pageSize;
const response = await fetch(`https://api.gbif.org/v1/occurrence/search?limit=${pageSize}&offset=${offset}`);
const data = await response.json();
setRows(data.results);
setRowCount(data.count);
};

// Effect hook to fetch data whenever the pagination model changes
useEffect(() => {
fetchData(paginationModel.page, paginationModel.pageSize);
}, [paginationModel]);

// Handler for pagination model change event
const handlePaginationModelChange = (model: GridPaginationModel) => {
setPaginationModel(model);
};

// Handler to toggle between table and gallery views
const toggleView = () => {
setIsGalleryView(!isGalleryView);
};

return (
<Paper>
{/* Header section with filters, search, and view toggle */}
<Stack
direction="row"
spacing={2}
Expand All @@ -52,26 +99,95 @@ export const DataTablePanel: React.FC<DataTablePanelProps> = (props) => {
size="small"
onChange={handleSearch}
/>
<Button
startIcon={isGalleryView ? <ViewListIcon /> : <ViewModuleIcon />}
onClick={toggleView}
>
{isGalleryView ? 'Table View' : 'Gallery View'}
</Button>
</Stack>
<DataGrid
rows={state.filteredData || []}
getRowId={(row) => row[state.dataIdField]}
columns={state.columns}
disableColumnSelector
initialState={{
pagination: { paginationModel: { page: state.tablePage, pageSize: state.tablePageSize } }
}}
{...props}
onRowClick={handleRowClick}
sx={{
'& .MuiDataGrid-cell:focus-within': {
outline: 'none'
},
'& .MuiDataGrid-overlayWrapper': {
minHeight: '4rem'
}
}}
/>
</Paper>
)
}

{isGalleryView ? (
<>
{/* Gallery view */}
<Grid container spacing={2} sx={{ padding: 2 }}>
{rows.map((row) => (
<Grid item key={row[state.dataIdField]} xs={12} sm={6} md={4} lg={3}>
<Card>
{row.media && row.media.length > 0 && (
<CardMedia
component="img"
height="140"
image={row.media[0].identifier}
alt={row.scientificName}
/>
)}
<CardContent>
<Typography variant="h6">
<Link onClick={() => handleRowClick({ row })} style={{ cursor: 'pointer' }}>
{row.scientificName}
</Link>
</Typography>
<Typography variant="body2">{row.eventDate}</Typography>
<Typography variant="body2">{row.country}</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
{/* Pagination and page size selector for gallery view */}
<Stack direction="row" spacing={2} sx={{ padding: 2, alignItems: 'center', justifyContent: 'center' }}>
<FormControl variant="outlined" size="small">
<InputLabel>Rows</InputLabel>
<Select
value={paginationModel.pageSize}
onChange={(e) => setPaginationModel({ ...paginationModel, pageSize: parseInt(e.target.value as string, 10) })}
label="Rows per page"
>
<MenuItem value={25}>25</MenuItem>
<MenuItem value={50}>50</MenuItem>
<MenuItem value={100}>100</MenuItem>
</Select>
</FormControl>
<Pagination
count={Math.ceil(rowCount / paginationModel.pageSize)}
page={paginationModel.page + 1}
onChange={(event, value) => setPaginationModel({ page: value - 1, pageSize: paginationModel.pageSize })}
variant="outlined"
shape="rounded"
/>
</Stack>
</>
) : (
/* Table view */
<DataGrid
rows={rows}
rowCount={rowCount}
pagination
paginationMode="server"
onPaginationModelChange={handlePaginationModelChange}
getRowId={(row) => row[state.dataIdField]}
columns={state.columns}
disableColumnSelector
initialState={{
pagination: { paginationModel: { page: state.tablePage, pageSize: state.tablePageSize } }
}}
{...props}
onRowClick={handleRowClick}
sx={{
'& .MuiDataGrid-cell:focus-within': {
outline: 'none'
},
'& .MuiDataGrid-overlayWrapper': {
minHeight: '4rem'
}
}}
/>
)}
{/* Preview panel */}
{showPreviewPanel && (
<PreviewPanel onClose={() => setShowPreviewPanel(false)} />
)}
</Paper>
);
};
Loading

0 comments on commit add092f

Please sign in to comment.