Compare commits

...

13 Commits

33 changed files with 555 additions and 1249 deletions

View File

@ -1,44 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './agency-table-entry';
import Head from './agency-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function AgencyTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
agencyId={item.agency_id}
agencyName={item.agency_name}
agencyUrl={item.agency_url}
agencyTimezone={item.agency_timezone}
agencyLang={item.agency_lang}
agencyPhone={item.agency_phone}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
AgencyTable.propTypes = {
aryData: PropTypes.array
};
export default AgencyTable;

View File

@ -3,22 +3,22 @@ import PropTypes from 'prop-types';
import FileSelect from '../components/file-select';
import TablePage from '../components/table-page.js';
const FileSelection = ({ options }) => {
const defaultGtfsFile = 'Select GTFS file';
const [gtfsFile, setGtfsFile] = useState(defaultGtfsFile);
const handleChangeGtfsFile = (event) => {
setGtfsFile((gtfsFile) => event.target.value);
const defaultFile = 'Select file';
const [fileName, setFileName] = useState(defaultFile);
const handleChangeFile = (event) => {
setFileName((fileName) => event.target.value);
};
if (options.length > 0) {
return (
<div>
<FileSelect
name="file"
onChange={handleChangeGtfsFile}
onChange={handleChangeFile}
options={options}
defaultValue={defaultGtfsFile}
title={defaultGtfsFile}
defaultValue={defaultFile}
title={defaultFile}
/>
<TablePage name={gtfsFile} />
<TablePage name={fileName} />
</div>
);
} else {

View File

@ -1,44 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './frequencies-table-entry';
import Head from './frequencies-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function FrequenciesTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
tripId={item.trip_id}
startTime={item.start_time}
endTime={item.end_time}
headwaySecs={item.headway_secs}
exactTimes={item.exact_times}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
FrequenciesTable.propTypes = {
aryData: PropTypes.array
};
export default FrequenciesTable;

View File

@ -20,7 +20,7 @@ const GtfsFile = ({ name }) => {
/*set state*/
setCount(count.data[0]['count']);
} catch (err) {
console.log('err.message: ' + err.message);
console.error('err.message: ' + err.message);
}
};

View File

@ -4,38 +4,14 @@ import GtfsFile from './gtfs-file';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import config from '../config';
const GtfsFiles = () => {
/*store and initialise data in function component state*/
const [tables, setTables] = useState([]);
/*fetch data in a JavaScript function*/
const getTables = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const tables = await axios.get(`${config.API}table-names`);
/*set state*/
setTables(tables.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
useEffect(() => {
/*effect goes here*/
/*hook need to be placed in body of the function component in which it is used*/
getTables();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, []);
if (tables) {
import PropTypes from 'prop-types';
const GtfsFiles = ({data}) => {
if (data.length>0) {
return (
<>
<Container>
<Row>
{tables.map((item, index) => {
{data.map((item, index) => {
return <GtfsFile key={index} name={item['table_name']} />;
})}
</Row>
@ -49,5 +25,7 @@ const GtfsFiles = () => {
return <p>List of GTFS files loading...</p>;
}
};
GtfsFiles.propTypes = {
data: PropTypes.array
};
export default GtfsFiles;

View File

@ -10,13 +10,8 @@ function NavigationBar () {
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<LinkContainer to="/agency">
<Nav.Link>Agency</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/frequencies">
<Nav.Link>Frequencies</Nav.Link>
<LinkContainer to="/files">
<Nav.Link>Files</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
@ -24,31 +19,11 @@ function NavigationBar () {
<Nav.Link>Overview</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/routes">
<Nav.Link>Routes</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/service">
<Nav.Link>Service</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/shapes">
<Nav.Link>Shapes</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/stops">
<Nav.Link>Stops</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/trips">
<Nav.Link>Trips</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/contact">
<Nav.Link>Contact</Nav.Link>

View File

@ -22,7 +22,7 @@ function OverviewTable ({ overview }) {
);
});
} else {
console.log('overview NOT available');
console.error('overview NOT available');
return null;
}
};

View File

@ -1,46 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './routes-table-entry';
import Head from './routes-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function RoutesTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
routeId={item.route_id}
agencyId={item.agency_id}
routeShortName={item.route_short_name}
routeLongName={item.route_long_name}
routeType={item.route_type}
routeColor={item.route_color}
routeTextColor={item.route_text_color}
routeDesc={item.route_desc}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
RoutesTable.propTypes = {
aryData: PropTypes.array
};
export default RoutesTable;

View File

@ -5,13 +5,12 @@ import Entry from './service-table-entry';
import Head from './service-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*accept a single property object argument*/
function ServiceTable (props) {
function ServiceTable ({service,render}) {
/*map over msgs array and return Standortmeldungen*/
const getService = () => {
//iterate over object
if (props.service) {
return Object.entries(props.service).map((trips, key) => {
if (service) {
return Object.entries(service).map((trips, key) => {
/*the strict equals operator does not converts operants of differnet type*/
//console.log('key: ' + key);
let objTrips = trips[1];
@ -24,11 +23,12 @@ function ServiceTable (props) {
return <Entry date={date.toDateString()} count={count} key={key} />;
});
} else {
console.log('service NOT available');
console.error('service NOT available');
return null;
}
};
if (props.render) {
if (render) {
/*return a React element*/
return (
<>

View File

@ -1,43 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './shapes-table-entry';
import Head from './shapes-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function ShapesTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
shapeId={item.shape_id}
shapePtLat={item.shape_pt_lat}
shapePtLon={item.shape_pt_lon}
shapePtSequence={item.shape_pt_sequence}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
ShapesTable.propTypes = {
aryData: PropTypes.array
};
export default ShapesTable;

View File

@ -1,50 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './stops-table-entry';
import Head from './stops-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function StopsTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
stopId={item.stop_id}
stopCode={item.stop_code}
stopName={item.stop_name}
stopDesc={item.stop_desc}
stopLat={item.stop_lat}
stopLon={item.stop_lon}
locationType={item.location_type}
parentStation={item.parent_station}
wheelchairBoarding={item.wheelchair_boarding}
platformCode={item.platform_code}
zoneId={item.zone_id}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
StopsTable.propTypes = {
aryData: PropTypes.array
};
export default StopsTable;

View File

@ -0,0 +1,206 @@
import React from 'react';
import PropTypes from 'prop-types';
import AgencyEntry from './agency-table-entry';
import CalendarEntry from './calendar-table-entry';
import CalendarDatesEntry from './calendar-dates-table-entry';
import FrequenciesEntry from './frequencies-table-entry';
import LevelsEntry from './levels-table-entry';
import RoutesEntry from './routes-table-entry';
import ShapesEntry from './shapes-table-entry';
import StopsEntry from './stops-table-entry';
import StopTimesEntry from './stop-times-table-entry';
import TransfersEntry from './transfers-table-entry';
import TripsEntry from './trips-table-entry';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function TableEntrySwitch ({ aryData, name }) {
/*TODO add table entry for pathways*/
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
switch (name) {
case 'agency':
return (
<AgencyEntry
agencyId={item.agency_id}
agencyName={item.agency_name}
agencyUrl={item.agency_url}
agencyTimezone={item.agency_timezone}
agencyLanguage={item.agency_language}
agencyPhone={item.agency_phone}
key={index}
/>
);
break;
case 'calendar':
return (
<CalendarEntry
serviceId={item.service_id}
monday={item.monday}
tuesday={item.tuesday}
wednesday={item.wednesday}
thursday={item.thursday}
friday={item.friday}
saturday={item.saturday}
sunday={item.sunday}
startDate={item.start_date}
endDate={item.end_date}
key={index}
/>
);
break;
case 'calendar_dates':
return (
<CalendarDatesEntry
serviceId={item.service_id}
date={item.date}
exceptionType={item.exception_type}
key={index}
/>
);
break;
case 'frequencies':
return (
<FrequenciesEntry
tripId={item.trip_id}
startTime={item.start_time}
endTime={item.end_time}
headwaySecs={item.headway_secs}
exactTimes={item.exact_times}
key={index}
/>
);
break;
case 'levels':
return (
<LevelsEntry
levelId={item.level_id}
levelIndex={item.level_index}
key={index}
/>
);
break;
case 'routes':
return (
<RoutesEntry
routeId={item.route_id}
agencyId={item.agency_id}
routeShortName={item.route_short_name}
routeLongName={item.route_long_name}
routeType={item.route_type}
routeColor={item.route_color}
routeTextColor={item.route_text_color}
routeDesc={item.route_desc}
key={index}
/>
);
break;
case 'shapes':
return (
<ShapesEntry
shapeId={item.shape_id}
shapePtLat={item.shape_pt_lat}
shapePtLon={item.shape_pt_lon}
shapePtSequence={item.shape_pt_sequence}
key={index}
/>
);
break;
case 'stops':
return (
<StopsEntry
stopId={item.stop_id}
stopCode={item.stop_code}
stopName={item.stop_name}
stopDesc={item.stop_desc}
stopLat={item.stop_lat}
stopLon={item.stop_lon}
locationType={item.location_type}
parentStation={item.parent_station}
wheelchairBoarding={item.wheelchair_boarding}
platformCode={item.platform_code}
zoneId={item.zone_id}
key={index}
/>
);
break;
case 'stop_times':
let arrivalTime = item.arrival_time;
/*TODO Why is this condition neccessary?*/
if (arrivalTime) {
return (
<StopTimesEntry
tripId={item.trip_id}
arrivalTimeHours={item.arrival_time['hours']}
arrivalTimeMinutes={item.arrival_time['minutes']}
departureTimeHours={item.departure_time['hours']}
departureTimeMinutes={item.departure_time['minutes']}
stopId={item.stop_id}
stopSequence={item.stop_sequence}
pickupType={item.pickup_type}
dropOffType={item.drop_off_type}
stopHeadsign={item.stop_headsign}
key={index}
/>
);
} else {
return (
<StopTimesEntry
tripId={item.trip_id}
stopId={item.stop_id}
stopSequence={item.stop_sequence}
pickupType={item.pickup_type}
dropOffType={item.drop_off_type}
stopHeadsign={item.stop_headsign}
key={index}
/>
);
}
break;
case 'transfers':
return (
<TransfersEntry
fromStopId={item.from_stop_id}
toStopId={item.to_stop_id}
fromRouteId={item.from_route_id}
toRouteId={item.to_route_id}
fromTripId={item.from_trip_id}
toTripId={item.to_trip_id}
transferType={item.transfer_type}
minTransferTime={item.min_transfer_time}
key={index}
/>
);
break;
case 'trips':
return (
<TripsEntry
routeId={item.route_id}
serviceId={item.service_id}
tripId={item.trip_id}
tripHeadsign={item.trip_headsign}
tripShortName={item.trip_short_name}
directionId={item.direction_id}
blockId={item.block_id}
shapeId={item.shape_id}
wheelchairAccessible={item.wheelchair_accessible}
bikesAllowed={item.bikes_allowed}
key={index}
/>
);
break;
default:
console.error(`${name} unknown`);
return null;
}
});
}else{
console.error('data is empty');
return null;
}
}
TableEntrySwitch.propTypes = {
aryData: PropTypes.array,
name: PropTypes.string
};
export default TableEntrySwitch;

View File

@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import AgencyHead from './agency-table-head';
import CalendarHead from './calendar-table-head';
import CalendarDatesHead from './calendar-dates-table-head';
import FrequenciesHead from './frequencies-table-head';
import LevelsHead from './levels-table-head';
import PathwaysHead from './pathways-table-head';
import RoutesHead from './routes-table-head';
import ShapesHead from './shapes-table-head';
import StopsHead from './stops-table-head';
import StopTimesHead from './stop-times-table-head';
import TransfersHead from './transfers-table-head';
import TripsHead from './trips-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function TableHeadSwitch ({ name }) {
switch (name) {
case 'agency':
return <AgencyHead />;
break;
case 'calendar':
return <CalendarHead />;
break;
case 'calendar_dates':
return <CalendarDatesHead />;
break;
case 'frequencies':
return <FrequenciesHead />;
break;
case 'levels':
return <LevelsHead />;
break;
case 'pathways':
return <PathwaysHead />;
break;
case 'routes':
return <RoutesHead />;
break;
case 'shapes':
return <ShapesHead />;
break;
case 'stops':
return <StopsHead />;
break;
case 'stop_times':
return <StopTimesHead />;
break;
case 'transfers':
return <TransfersHead />;
break;
case 'trips':
return <TripsHead />;
break;
default:
console.error(`${name} unknown`);
return null;
}
}
TableHeadSwitch.propTypes = {
name: PropTypes.string
};
export default TableHeadSwitch;

View File

@ -1,46 +1,16 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
const selectOptions = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000];
import TablePageSwitch from '../components/table-switch';
import Select from './select';
import {selectOptions} from '../utils/select-options';
import TableSwitch from './table-switch';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from './input';
import PropTypes from 'prop-types';
import config from '../config';
const TablePage = ({ name }) => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[0]);
const [ary, setAry] = useState([]);
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
//fetch data only if user selection is available
if (name.indexOf(' ') === -1) {
const address = `${config.API}${name}-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry((ary) => res.data);
}
} catch (err) {
console.error('err.message: ' + err.message);
//TODO handle error
setAry((ary) => []);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
useEffect(() => {
/*effect goes here*/
/*hook need to be placed in body of the function component in which it is used*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit, name]);
const [limit, setLimit] = useState(parseInt(selectOptions[0],10));
const [searchField, setSearchField] = useState('');
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
@ -48,9 +18,12 @@ const TablePage = ({ name }) => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
setLimit((limit) => parseInt(event.target.value,10));
};
if (ary.length > 0 && name.indexOf(' ') === -1) {
const handleSearch = (e) => {
setSearchField((searchField)=>e.target.value);
};
if (name!==null && name.indexOf(' ') === -1) {
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
@ -62,22 +35,35 @@ const TablePage = ({ name }) => {
</Button>
<Select
defaultValue={selectOptions[0]}
id="tabePageLimit"
id="tablePageLimit"
name="tablePageLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="tablePageSearch"
name="tablePageSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<TablePageSwitch aryData={ary} name={name} />
<TableSwitch
name={name}
isFetched={false}
oset={oset}
limit={limit}
filter={searchField}
/>
</>
);
} else {
return null;
}
};
TablePage.propTypes = {
name: PropTypes.string
};
export default TablePage;

View File

@ -1,269 +1,80 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import AgencyHead from './agency-table-head';
import AgencyEntry from './agency-table-entry';
import CalendarHead from './calendar-table-head';
import CalendarEntry from './calendar-table-entry';
import CalendarDatesHead from './calendar-dates-table-head';
import CalendarDatesEntry from './calendar-dates-table-entry';
import FrequenciesHead from './frequencies-table-head';
import FrequenciesEntry from './frequencies-table-entry';
import LevelsHead from './levels-table-head';
import LevelsEntry from './levels-table-entry';
import PathwaysHead from './pathways-table-head';
import RoutesHead from './routes-table-head';
import RoutesEntry from './routes-table-entry';
import ShapesHead from './shapes-table-head';
import ShapesEntry from './shapes-table-entry';
import StopsHead from './stops-table-head';
import StopsEntry from './stops-table-entry';
import StopTimesHead from './stop-times-table-head';
import StopTimesEntry from './stop-times-table-entry';
import TransfersHead from './transfers-table-head';
import TransfersEntry from './transfers-table-entry';
import TripsHead from './trips-table-head';
import TripsEntry from './trips-table-entry';
import TableHeadSwitch from './table-head-switch';
import TableEntrySwitch from './table-entry-switch';
import config from '../config';
import {filterData} from '../utils/filter-data';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function TableSwitch ({ aryData, name }) {
const handleTableHead = () => {
switch (name) {
case 'agency':
return <AgencyHead />;
break;
case 'calendar':
return <CalendarHead />;
break;
case 'calendar_dates':
return <CalendarDatesHead />;
break;
case 'frequencies':
return <FrequenciesHead />;
break;
case 'levels':
return <LevelsHead />;
break;
case 'pathways':
return <PathwaysHead />;
break;
case 'routes':
return <RoutesHead />;
break;
case 'shapes':
return <ShapesHead />;
break;
case 'stops':
return <StopsHead />;
break;
case 'stop_times':
return <StopTimesHead />;
break;
case 'transfers':
return <TransfersHead />;
break;
case 'trips':
return <TripsHead />;
break;
default:
console.error('file unknown');
function TableSwitch ({name, isFetched, oset, limit, filter}) {
//console.log('TableSwitch name: '+name);
//console.log('TableSwitch isFetched: '+isFetched);
//console.log('TableSwitch filter: '+filter);
const [ary, setAry] = useState([]);
const [aryFiltered, setAryFiltered] = useState([]);
const [fetchCompleted, setFetchCompleted] = useState(isFetched);
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
//fetch data only if user selection is unequal default value
if (name.length>0 && name.indexOf(' ') === -1) {
const address = `${config.API}${name}-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
setAry((ary) => res.data);
let data=filterData(res.data,name,filter);
setAryFiltered((aryFiltered) => data);
} else {
console.error(`name ${name} not valid`);
setAry((ary) => []);
}
} catch (err) {
console.error('err.message: ' + err.message);
setAry((ary) => []);
}
};
const handleTableEntry = () => {
/*TODO add table entry for pathways*/
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
switch (name) {
case 'agency':
return (
<AgencyEntry
agencyId={item.agency_id}
agencyName={item.agency_name}
agencyUrl={item.agency_url}
agencyTimezone={item.agency_timezone}
agencyLanguage={item.agency_language}
agencyPhone={item.agency_phone}
key={index}
/>
);
break;
case 'calendar':
return (
<CalendarEntry
serviceId={item.service_id}
monday={item.monday}
tuesday={item.tuesday}
wednesday={item.wednesday}
thursday={item.thursday}
friday={item.friday}
saturday={item.saturday}
sunday={item.sunday}
startDate={item.start_date}
endDate={item.end_date}
key={index}
/>
);
break;
case 'calendar_dates':
return (
<CalendarDatesEntry
serviceId={item.service_id}
date={item.date}
exceptionType={item.exception_type}
key={index}
/>
);
break;
case 'frequencies':
return (
<FrequenciesEntry
tripId={item.trip_id}
startTime={item.start_time}
endTime={item.end_time}
headwaySecs={item.headway_secs}
exactTimes={item.exact_times}
key={index}
/>
);
break;
case 'levels':
return (
<LevelsEntry
levelId={item.level_id}
levelIndex={item.level_index}
key={index}
/>
);
break;
case 'routes':
return (
<RoutesEntry
routeId={item.route_id}
agencyId={item.agency_id}
routeShortName={item.route_short_name}
routeLongName={item.route_long_name}
routeType={item.route_type}
routeColor={item.route_color}
routeTextColor={item.route_text_color}
routeDesc={item.route_desc}
key={index}
/>
);
break;
case 'shapes':
return (
<ShapesEntry
shapeId={item.shape_id}
shapePtLat={item.shape_pt_lat}
shapePtLon={item.shape_pt_lon}
shapePtSequence={item.shape_pt_sequence}
key={index}
/>
);
break;
case 'stops':
return (
<StopsEntry
stopId={item.stop_id}
stopCode={item.stop_code}
stopName={item.stop_name}
stopDesc={item.stop_desc}
stopLat={item.stop_lat}
stopLon={item.stop_lon}
locationType={item.location_type}
parentStation={item.parent_station}
wheelchairBoarding={item.wheelchair_boarding}
platformCode={item.platform_code}
zoneId={item.zone_id}
key={index}
/>
);
break;
case 'stop_times':
let arrivalTime = item.arrival_time;
/*TODO Why is this condition neccessary?*/
if (arrivalTime) {
return (
<StopTimesEntry
tripId={item.trip_id}
arrivalTimeHours={item.arrival_time['hours']}
arrivalTimeMinutes={item.arrival_time['minutes']}
departureTimeHours={item.departure_time['hours']}
departureTimeMinutes={item.departure_time['minutes']}
stopId={item.stop_id}
stopSequence={item.stop_sequence}
pickupType={item.pickup_type}
dropOffType={item.drop_off_type}
stopHeadsign={item.stop_headsign}
key={index}
/>
);
} else {
return (
<StopTimesEntry
tripId={item.trip_id}
stopId={item.stop_id}
stopSequence={item.stop_sequence}
pickupType={item.pickup_type}
dropOffType={item.drop_off_type}
stopHeadsign={item.stop_headsign}
key={index}
/>
);
}
break;
case 'transfers':
return (
<TransfersEntry
fromStopId={item.from_stop_id}
toStopId={item.to_stop_id}
fromRouteId={item.from_route_id}
toRouteId={item.to_route_id}
fromTripId={item.from_trip_id}
toTripId={item.to_trip_id}
transferType={item.transfer_type}
minTransferTime={item.min_transfer_time}
key={index}
/>
);
break;
case 'trips':
return (
<TripsEntry
routeId={item.route_id}
serviceId={item.service_id}
tripId={item.trip_id}
tripHeadsign={item.trip_headsign}
tripShortName={item.trip_short_name}
directionId={item.direction_id}
blockId={item.block_id}
shapeId={item.shape_id}
wheelchairAccessible={item.wheelchair_accessible}
bikesAllowed={item.bikes_allowed}
key={index}
/>
);
break;
default:
console.error('file unknown');
}
});
}
};
/*return a React element*/
return (
useEffect(()=>{
setAryFiltered(aryFiltered=>filterData(ary,name,filter));
},[filter]);
useEffect(() => {
/*effect goes here*/
fetch();
setFetchCompleted((fetchCompleted)=>true);
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [name,oset,limit]);
if(fetchCompleted && aryFiltered.length > 0){
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>{handleTableHead()}</thead>
<tbody>{handleTableEntry()}</tbody>
<Table
striped
bordered
hover
size="sm"
variant="dark"
responsive
>
<thead>
<TableHeadSwitch name={name}/>
</thead>
<tbody>
<TableEntrySwitch aryData={aryFiltered} name={name}/>
</tbody>
</Table>
</>
);
);
}else{
return null;
}
}
TableSwitch.propTypes = {
aryData: PropTypes.array,
name: PropTypes.string
name: PropTypes.string,
isFetched: PropTypes.bool,
offset: PropTypes.number,
limit: PropTypes.number,
filter: PropTypes.string
};
export default TableSwitch;

View File

@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import FileSelection from '../components/file-selection';
import FileSelection from './file-selection';
const Tables = ({ data }) => {
//console.log('Tables data.length: '+data.length);
if (data.length > 0) {
return (
<>
@ -9,10 +10,10 @@ const Tables = ({ data }) => {
</>
);
} else {
return <p>Files loading...</p>;
return <p>Selection loading...</p>;
}
};
export default Tables;
Tables.propTypes = {
data: PropTypes.array
};
export default Tables;

View File

@ -1,25 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const ToggleBtn = (props) => {
//destructuring
const { btnState, btnTrue, btnFalse, descTrue, descFalse, btnOnClick } =
props;
//render
return (
<>
<button onClick={btnOnClick}>
{btnState === false ? btnFalse : btnTrue}
</button>
{btnState === false ? descFalse : descTrue}
</>
);
};
export default ToggleBtn;
ToggleBtn.propTypes = {
btnState: PropTypes.bool,
btnTrue: PropTypes.string,
btnFalse: PropTypes.string,
descTrue: PropTypes.string,
descFalse: PropTypes.string,
btnOnClick: PropTypes.func
};

View File

@ -1,49 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/Table';
import Entry from './trips-table-entry';
import Head from './trips-table-head';
/*the simplest way to define a component is to write a JavaScript function*/
/*destructure props object*/
function TripsTable ({ aryData }) {
const [searchField, setSearchField] = useState('');
const handleAryData = () => {
if (aryData.length > 0) {
//iterate over array
return aryData.map((item, index) => {
//console.log('aryData index: ' + index);
return (
<Entry
routeId={item.route_id}
serviceId={item.service_id}
tripId={item.trip_id}
tripHeadsign={item.trip_headsign}
tripShortName={item.trip_short_name}
directionId={item.direction_id}
blockId={item.block_id}
shapeId={item.shape_id}
wheelchairAccessibel={item.wheechair_accessible}
bikesAllowed={item.bikes_allowed}
key={index}
/>
);
});
}
};
/*return a React element*/
return (
<>
<Table striped bordered hover size="sm" variant="dark" responsive>
<thead>
<Head />
</thead>
<tbody>{handleAryData()}</tbody>
</Table>
</>
);
}
TripsTable.propTypes = {
aryData: PropTypes.array
};
export default TripsTable;

View File

@ -1,4 +1,4 @@
export default {
API: 'https://v1gtfs.vbn.api.swingbe.de/',
GTFS_VALIDATOR_REPORT: 'https://www.v1gtfs.vbn.api.swingbe.de/gtfs-validator/report.html',
API: 'https://v1gtfs.delfi.api.swingbe.de/',
GTFS_VALIDATOR_REPORT: 'https://www.v1gtfs.delfi.api.swingbe.de/gtfs-validator/report.html',
};

View File

@ -6,14 +6,9 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import NavBar from './components/nav-bar';
import HomePage from './pages/homepage';
import Agency from './pages/agency';
import Frequencies from './pages/frequencies';
import GtfsRoutes from './pages/routes';
import Files from './pages/files';
import Overview from './pages/overview';
import Service from './pages/service';
import Shapes from './pages/shapes';
import Stops from './pages/stops';
import Trips from './pages/trips';
import Contact from './pages/contact';
const Main = () => {
@ -26,14 +21,9 @@ const Main = () => {
<NavBar />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/agency" element={<Agency />} />
<Route path="/frequencies" element={<Frequencies />} />
<Route path="/routes" element={<GtfsRoutes />} />
<Route path="/files" element={<Files />} />
<Route path="/overview" element={<Overview />} />
<Route path="/service" element={<Service />} />
<Route path="/shapes" element={<Shapes />} />
<Route path="/stops" element={<Stops />} />
<Route path="/trips" element={<Trips />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>

View File

@ -1,90 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import AgencyTable from '../components/agency-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Agency = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
(item.agency_id!==null && item.agency_id.toLowerCase().includes(searchField.toLowerCase())) ||
item.agency_name.toLowerCase().includes(searchField.toLowerCase()) ||
item.agency_url.toLowerCase().includes(searchField.toLowerCase()) ||
item.agency_timezone.toLowerCase().includes(searchField.toLowerCase()) ||
(item.agency_lang!==null && item.agency_lang.toLowerCase().includes(searchField.toLowerCase())) ||
(item.agency_phone!==null && item.agency_phone.toLowerCase().includes(searchField.toLowerCase()))
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}agency-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="agencyLimit"
name="agencyLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="agencySearch"
name="agencySearch"
onChange={handleSearch}
placeholder="Search table globally"
title="Enter search value"
type="search"
value={searchField}
/>
</Stack>
<AgencyTable aryData={filteredAry} />
</>
);
};
export default Agency;

43
app/pages/files.js Normal file
View File

@ -0,0 +1,43 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Tables from '../components/tables.js';
import config from '../config';
const Files = () => {
/*store and initialise data in function component state*/
const [tables, setTables] = useState([]);
/*fetch data in a JavaScript function*/
const getTables = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const tables = await axios.get(`${config.API}table-names`);
/*set state*/
setTables(tables.data);
} catch (err) {
console.error('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
getTables();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, []);
if(tables.length>0){
return (
<>
<Tables data={tables} />
</>
);
}else{
return (
<>
<p>Files loading...</p>
</>
);
}
};
export default Files;

View File

@ -1,89 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import FrequenciesTable from '../components/frequencies-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Frequencies = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
item.trip_id.toLowerCase().includes(searchField.toLowerCase()) ||
item.start_time.toLowerCase().includes(searchField.toLowerCase()) ||
item.end_time.toLowerCase().includes(searchField.toLowerCase()) ||
item.headway_secs.toString().includes(searchField) ||
(item.exact_times!==null && item.exact_times.toString().includes(searchField))
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}frequencies-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="frequenciesLimit"
name="frequenciesLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="frequenciesSearch"
name="frequenciesSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<FrequenciesTable aryData={filteredAry} />
</>
);
};
export default Frequencies;

View File

@ -12,7 +12,6 @@ const Homepage = () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const tables = await axios.get(`${config.API}table-names`);
/*set state*/
setTables(tables.data);
} catch (err) {
@ -30,12 +29,20 @@ const Homepage = () => {
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, []);
return (
<>
<GtfsValidatorReport />
<GtfsFiles />
<Tables data={tables} />
</>
);
if(tables.length>0){
return (
<>
<GtfsValidatorReport />
<GtfsFiles data={tables}/>
<Tables data={tables} />
</>
);
}else{
return (
<>
<GtfsValidatorReport />
</>
);
}
};
export default Homepage;

View File

@ -60,7 +60,6 @@ const Overview = () => {
/*get agencies*/
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const res = await axios.get(`${config.API}agency-all`);
let aryOv = res.data;
for (var i = 0; i < aryOv.length; i++) {
let agencyId = aryOv[i].agency_id;

View File

@ -1,92 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import RoutesTable from '../components/routes-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Routes = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
item.route_id.toLowerCase().includes(searchField.toLowerCase()) ||
(item.agency_id!==null && item.agency_id.toLowerCase().includes(searchField.toLowerCase())) ||
(item.route_short_name!==null && item.route_short_name.toLowerCase().includes(searchField.toLowerCase())) ||
(item.route_long_name!==null && item.route_long_name.toLowerCase().includes(searchField.toLowerCase())) ||
item.route_type.toString().includes(searchField.toLowerCase()) ||
(item.route_color!==null && item.route_color.toLowerCase().includes(searchField.toLowerCase())) ||
(item.route_text_color!==null && item.route_text_color.toLowerCase().includes(searchField.toLowerCase())) ||
(item.route_desc!==null &&item.route_desc.toLowerCase().includes(searchField.toLowerCase()))
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}routes-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="shapesLimit"
name="shapesLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="shapesSearch"
name="shapesSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<RoutesTable aryData={filteredAry} />
</>
);
};
export default Routes;

View File

@ -34,7 +34,7 @@ const Service = () => {
const aryDate = aryTime.map((time) => new Date(time).toDateString());
setTime(aryDate);
} catch (err) {
console.log('err.message: ' + err.message);
console.error('err.message: ' + err.message);
}
};

View File

@ -1,88 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import ShapesTable from '../components/shapes-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Shapes = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
item.shape_id.toLowerCase().includes(searchField.toLowerCase()) ||
item.shape_pt_lat.toString().includes(searchField) ||
item.shape_pt_lon.toString().includes(searchField) ||
item.shape_pt_sequence.toString().includes(searchField)
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}shapes-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="shapesLimit"
name="shapesLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="shapesSearch"
name="shapesSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<ShapesTable aryData={filteredAry} />
</>
);
};
export default Shapes;

View File

@ -1,95 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import StopsTable from '../components/stops-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Stops = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
item.stop_id.toLowerCase().includes(searchField.toLowerCase()) ||
(item.stop_code!==null && item.stop_code.toLowerCase().includes(searchField.toLowerCase())) ||
(item.stop_name!==null && item.stop_name.toLowerCase().includes(searchField.toLowerCase())) ||
(item.stop_desc!==null && item.stop_descr.toLowerCase().includes(searchField.toLowerCase())) ||
(item.stop_lat!==null && item.stop_lat.toString().includes(searchField)) ||
(item.stop_lon!==null && item.stop_lon.toString().includes(searchField)) ||
(item.location_type!==null && item.location_type.toString().includes(searchField)) ||
(item.parent_station!==null && item.parent_station.toString().includes(searchField)) ||
(item.wheelchair_boarding!==null && item.wheelchair_boarding.toString().includes(searchField)) ||
(item.platform_code!==null && item.platform_code.toLowerCase().includes(searchField.toLowerCase())) ||
(item.zone_id!==null && item.zone_id.toLowerCase().includes(searchField.toLowerCase()))
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}stops-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="stopsLimit"
name="stopsLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="stopsSearch"
name="stopsSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<StopsTable aryData={filteredAry} />
</>
);
};
export default Stops;

View File

@ -1,94 +0,0 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Select from '../components/select';
import {selectOptions} from '../utils/select-options';
import TripsTable from '../components/trips-table';
import Stack from 'react-bootstrap/Stack';
import Button from 'react-bootstrap/Button';
import Input from '../components/input';
import config from '../config';
const Trips = () => {
/*store and initialise data in function component state*/
const [oset, setOset] = useState(1);
const [limit, setLimit] = useState(selectOptions[2]);
const [ary, setAry] = useState([]);
const [searchField, setSearchField] = useState('');
const filteredAry = ary.filter((item, index) => {
return (
item.route_id.toLowerCase().includes(searchField.toLowerCase()) ||
item.service_id.toLowerCase().includes(searchField.toLowerCase()) ||
item.trip_id.toLowerCase().includes(searchField.toLowerCase()) ||
(item.trip_headsign!==null && item.trip_headsign.toLowerCase().includes(searchField.toLowerCase())) ||
(item.trip_short_name!==null && item.trip_short_name.toLowerCase().includes(searchField.toLowerCase())) ||
(item.direction_id!==null && item.direction_id.toString().includes(searchField)) ||
(item.block_id!==null && item.block_id.toLowerCase().includes(searchField.toLowerCase())) ||
(item.shape_id!==null && item.shape_id.toLowerCase().includes(searchField.toLowerCase())) ||
(item.wheelchair_accessible!==null && item.wheelchair_accessible.toString().includes(searchField)) ||
(item.bikes_allowed!==null && item.bikes_allowed.toString().includes(searchField))
);
});
/*fetch ary in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const address = `${config.API}trips-oset-limit?oset=${oset}&limit=${limit}`;
const res = await axios.get(address);
/*set state*/
setAry(res.data);
} catch (err) {
console.log('err.message: ' + err.message);
}
};
/*this hook is run after a DOM update. Changing state migh result in an infinite loop*/
/*hook need to be placed in body of the function component in which it is used*/
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, [oset, limit]);
const handleClickPrev = () => {
setOset((oset) => (oset > 1 ? --oset : oset));
};
const handleClickNext = () => {
setOset((oset) => ++oset);
};
const handleChangeLimit = (event) => {
setLimit((limit) => event.target.value);
};
const handleSearch = (e) => {
setSearchField(e.target.value);
};
return (
<>
<Stack direction="horizontal" gap={1} className="m-1">
<Button variant="secondary" onClick={handleClickPrev} autoFocus>
prev
</Button>
<Button variant="secondary" onClick={handleClickNext}>
next
</Button>
<Select
defaultValue={selectOptions[2]}
id="tripsLimit"
name="tripsLimit"
onChange={handleChangeLimit}
options={selectOptions}
/>
<Input
id="tripsSearch"
name="tripsSearch"
onChange={handleSearch}
placeholder="Search table globally"
type="search"
title="Enter search value"
value={searchField}
/>
</Stack>
<TripsTable aryData={filteredAry} />
</>
);
};
export default Trips;

96
app/utils/filter-data.js Normal file
View File

@ -0,0 +1,96 @@
function filterData(data, name,filter){
if(data.length>0){
//console.log('filterData() data.length: '+data.length);
//console.log('filterData() name: '+name);
//console.log('filterData() filter:'+filter);
switch(name){
case 'agency':
return data.filter((item, index) => {
return (
(item.agency_id!==null && item.agency_id.toLowerCase().includes(filter.toLowerCase())) ||
item.agency_name.toLowerCase().includes(filter.toLowerCase()) ||
item.agency_url.toLowerCase().includes(filter.toLowerCase()) ||
item.agency_timezone.toLowerCase().includes(filter.toLowerCase()) ||
(item.agency_lang!==null && item.agency_lang.toLowerCase().includes(filter.toLowerCase())) ||
(item.agency_phone!==null && item.agency_phone.toLowerCase().includes(filter.toLowerCase()))
);
});
break;
case 'frequencies':
return data.filter((item, index) => {
return (
item.trip_id.toLowerCase().includes(filter.toLowerCase()) ||
item.start_time.toLowerCase().includes(filter.toLowerCase()) ||
item.end_time.toLowerCase().includes(filter.toLowerCase()) ||
item.headway_secs.toString().includes(filter) ||
(item.exact_times!==null && item.exact_times.toString().includes(filter))
);
});
break;
case 'routes':
return data.filter((item, index) => {
return (
item.route_id.toLowerCase().includes(filter.toLowerCase()) ||
(item.agency_id!==null && item.agency_id.toLowerCase().includes(filter.toLowerCase())) ||
(item.route_short_name!==null && item.route_short_name.toLowerCase().includes(filter.toLowerCase())) ||
(item.route_long_name!==null && item.route_long_name.toLowerCase().includes(filter.toLowerCase())) ||
item.route_type.toString().includes(filter.toLowerCase()) ||
(item.route_color!==null && item.route_color.toLowerCase().includes(filter.toLowerCase())) ||
(item.route_text_color!==null && item.route_text_color.toLowerCase().includes(filter.toLowerCase())) ||
(item.route_desc!==null &&item.route_desc.toLowerCase().includes(filter.toLowerCase()))
);
});
break;
case 'shapes':
return data.filter((item, index) => {
return (
item.shape_id.toLowerCase().includes(filter.toLowerCase()) ||
item.shape_pt_lat.toString().includes(filter) ||
item.shape_pt_lon.toString().includes(filter) ||
item.shape_pt_sequence.toString().includes(filter)
);
});
break;
case 'stops':
return data.filter((item, index) => {
return (
item.stop_id.toLowerCase().includes(filter.toLowerCase()) ||
(item.stop_code!==null && item.stop_code.toLowerCase().includes(filter.toLowerCase())) ||
(item.stop_name!==null && item.stop_name.toLowerCase().includes(filter.toLowerCase())) ||
(item.stop_desc!==null && item.stop_descr.toLowerCase().includes(filter.toLowerCase())) ||
(item.stop_lat!==null && item.stop_lat.toString().includes(filter)) ||
(item.stop_lon!==null && item.stop_lon.toString().includes(filter)) ||
(item.location_type!==null && item.location_type.toString().includes(filter)) ||
(item.parent_station!==null && item.parent_station.toString().includes(filter)) ||
(item.wheelchair_boarding!==null && item.wheelchair_boarding.toString().includes(filter)) ||
(item.platform_code!==null && item.platform_code.toLowerCase().includes(filter.toLowerCase())) ||
(item.zone_id!==null && item.zone_id.toLowerCase().includes(filter.toLowerCase()))
);
});
break;
case 'trips':
return data.filter((item, index) => {
return (
item.route_id.toLowerCase().includes(filter.toLowerCase()) ||
item.service_id.toLowerCase().includes(filter.toLowerCase()) ||
item.trip_id.toLowerCase().includes(filter.toLowerCase()) ||
(item.trip_headsign!==null && item.trip_headsign.toLowerCase().includes(filter.toLowerCase())) ||
(item.trip_short_name!==null && item.trip_short_name.toLowerCase().includes(filter.toLowerCase())) ||
(item.direction_id!==null && item.direction_id.toString().includes(filter)) ||
(item.block_id!==null && item.block_id.toLowerCase().includes(filter.toLowerCase())) ||
(item.shape_id!==null && item.shape_id.toLowerCase().includes(filter.toLowerCase())) ||
(item.wheelchair_accessible!==null && item.wheelchair_accessible.toString().includes(filter)) ||
(item.bikes_allowed!==null && item.bikes_allowed.toString().includes(filter))
);
});
break;
default:
return data;
}
}else{
return data;
}
};
module.exports = {
filterData
};

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "gtfs-display",
"version": "0.1.0",
"version": "0.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gtfs-display",
"version": "0.1.0",
"version": "0.3.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"axios": "0.27.2",

View File

@ -2,7 +2,7 @@
"private": true,
"name": "gtfs-display",
"description": "display data from GTFS files",
"version": "0.1.1",
"version": "0.3.0",
"main": "index.js",
"keywords": [
"public",