vom laptop
This commit is contained in:
95
src/App.css
95
src/App.css
@ -36,3 +36,98 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.sort-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-size: 15px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
|
||||
transition: transform 0.05s ease-out;
|
||||
}
|
||||
|
||||
.sort-reverse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: thin solid;
|
||||
}
|
||||
|
||||
table{
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Sortable tables */
|
||||
table.sortable thead {
|
||||
background-color:rgb(171, 71, 13);
|
||||
color:#144514;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: rgba(243, 97, 12, 0.31);
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td
|
||||
{
|
||||
background-color: rgba(243, 97, 12, 0.836);
|
||||
border: 1px solid rgba(0, 0, 0, 0.8);
|
||||
padding: 5px;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h1,h2,h4,p
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
.toindex
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.flexbox
|
||||
{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right
|
||||
{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.header
|
||||
{
|
||||
border: 0px;
|
||||
font-weight: 900;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.pilot .header
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.link
|
||||
{
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
45
src/App.tsx
45
src/App.tsx
@ -1,24 +1,27 @@
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
import "./App.css";
|
||||
import FlightsTable from "./components/FlightsTable";
|
||||
import SeasonTable from "./components/SeasonTable";
|
||||
import {groupByMap} from './global/tools';
|
||||
import flights from './data/HoPe_all_flights.json';
|
||||
import {IFlight} from './interfaces/IFlights';
|
||||
import {CSeason} from './classes/CSeason';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React XXXX
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
const data = flights.data;
|
||||
const seasondataraw = groupByMap(data, i => i.FKSeason);
|
||||
const seasondata: CSeason[] = [];
|
||||
|
||||
for (let [key, value] of seasondataraw)
|
||||
{
|
||||
seasondata.push(new CSeason(key, value));
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default function App()
|
||||
{
|
||||
return(
|
||||
<div className='App'>
|
||||
{/* <SeasonTable seasondata = {seasondata} /> */}
|
||||
<FlightsTable flightdata = {data}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
28
src/classes/CHour.ts
Normal file
28
src/classes/CHour.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export class CHour
|
||||
{
|
||||
public minutes_sum: number;
|
||||
private _hours: number;
|
||||
private _minutes: number;
|
||||
|
||||
constructor(secs: number)
|
||||
{
|
||||
this.minutes_sum = Math.round(secs / 60);
|
||||
this._hours = Math.floor(this.minutes_sum / 60);
|
||||
this._minutes = this.minutes_sum % 60;
|
||||
}
|
||||
|
||||
Add(hour: CHour)
|
||||
{
|
||||
this.minutes_sum += hour.minutes_sum;
|
||||
this._hours = Math.floor(this.minutes_sum / 60);
|
||||
this._minutes = this.minutes_sum % 60;
|
||||
}
|
||||
|
||||
Print()
|
||||
{
|
||||
// const nbspc = String.fromCharCode(160);
|
||||
// return (nbspc + nbspc + nbspc + this._hours).slice(-4) + ":" + ("0" + this._minutes).slice(-2);
|
||||
return this._hours + ":" +
|
||||
("0" + this._minutes).slice(-2);
|
||||
}
|
||||
};
|
||||
31
src/classes/CSeason.ts
Normal file
31
src/classes/CSeason.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {IFlight} from '../interfaces/IFlights';
|
||||
import {CHour} from '../classes/CHour';
|
||||
|
||||
export class CSeason
|
||||
{
|
||||
id: string;
|
||||
flighttime: CHour;
|
||||
flights: IFlight[];
|
||||
|
||||
constructor(id: string, flights: IFlight[])
|
||||
{
|
||||
this.id = id;
|
||||
this.flights = flights;
|
||||
this.flighttime = new CHour(0)
|
||||
this.calcFlighttime();
|
||||
}
|
||||
|
||||
calcFlighttime()
|
||||
{
|
||||
for (let f of this.flights)
|
||||
{
|
||||
const ftime: number = +f.FlightDuration;
|
||||
this.flighttime.Add(new CHour(ftime));
|
||||
}
|
||||
}
|
||||
|
||||
get flightCount() : number
|
||||
{
|
||||
return this.flights.length;
|
||||
}
|
||||
}
|
||||
42
src/components/FlightsTable.tsx
Normal file
42
src/components/FlightsTable.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import SortableTable from "./SortableTable";
|
||||
import {THeader} from "./SortableTable";
|
||||
import {IFlight} from '../interfaces/IFlights';
|
||||
|
||||
function Link2Flight(key: string, row: any, data: string | null)
|
||||
{
|
||||
var val: JSX.Element = <></>;
|
||||
const link: string = 'https://de.dhv-xc.de/flight/' + row['IDFlight'];
|
||||
val = <td><a href={link} target="_blank" rel="noopener noreferrer" >{data}</a></td>;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
function Meter2Km(key: string, row: any, data: string | null)
|
||||
{
|
||||
var val: JSX.Element = <></>;
|
||||
const numdata: number = +data! / 1000;
|
||||
val = <td>{numdata}</td>;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export default function FlightsTable(flightdata: IFlight[])
|
||||
//export default function FlightsTable(flightdata: any)
|
||||
{
|
||||
const headers: THeader[] = [
|
||||
{ key: "IDFlight", label: "ID", visible: false },
|
||||
{ key: "FlightDate", label: "Datum", callback: Link2Flight },
|
||||
{ key: "TakeoffWaypointName", label: "Start" },
|
||||
{ key: "Glider", label: "Gleitschirm" },
|
||||
{ key: "BestTaskDistance", label: "Strecke", callback: Meter2Km },
|
||||
{ key: "BestTaskType", label: "Streckentyp" },
|
||||
{ key: "BestTaskPoints", label: "Punkte" }
|
||||
];
|
||||
|
||||
return(
|
||||
<div className='App'>
|
||||
<SortableTable<IFlight> headers={headers} dataTbl={flightdata} ></SortableTable>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
src/components/SeasonTable.tsx
Normal file
9
src/components/SeasonTable.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
import {CSeason} from '../classes/CSeason';
|
||||
|
||||
//export default function SeasonTable(seasondata: CSeason[])
|
||||
export default function SeasonTable(seasondata: CSeason[])
|
||||
{
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
157
src/components/SortableTable.tsx
Normal file
157
src/components/SortableTable.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import React from "react";
|
||||
import { orderBy } from 'natural-orderby';
|
||||
import { MouseEventHandler, useState } from "react";
|
||||
|
||||
export interface ITableTdCallback {(key: string, row: any, data: string | null): JSX.Element};
|
||||
export type THeader = { key: string; label: string; visible?: boolean; callback?: ITableTdCallback };
|
||||
|
||||
type TSortOrder = "ascn" | "desc";
|
||||
|
||||
|
||||
function SortButton({
|
||||
sortOrder,
|
||||
columnKey,
|
||||
sortKey,
|
||||
onClick,
|
||||
}: {
|
||||
sortOrder: TSortOrder;
|
||||
columnKey: string;
|
||||
sortKey: string;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`${
|
||||
sortKey === columnKey && sortOrder === "desc"
|
||||
? "sort-button sort-reverse"
|
||||
: "sort-button"
|
||||
}`}
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SortableTable<T>({ headers, dataTbl }:
|
||||
{ headers: THeader[], dataTbl: T[]})
|
||||
{
|
||||
const [sortKey, setSortKey] = useState<string>(headers[0].key);
|
||||
const [sortOrder, setSortOrder] = useState<TSortOrder>("ascn");
|
||||
|
||||
const sortedData: T[] = sortData(
|
||||
{
|
||||
tableData: dataTbl,
|
||||
sortKey: sortKey,
|
||||
reverse: sortOrder === "desc"
|
||||
});
|
||||
|
||||
function changeSort(key: string) {
|
||||
setSortOrder(sortOrder === "ascn" ? "desc" : "ascn");
|
||||
|
||||
setSortKey(key);
|
||||
}
|
||||
|
||||
function sortData({
|
||||
tableData,
|
||||
sortKey,
|
||||
reverse,
|
||||
}: {
|
||||
tableData: T[];
|
||||
sortKey: string;
|
||||
reverse: boolean;
|
||||
})
|
||||
{
|
||||
if (!sortKey) return tableData;
|
||||
|
||||
const order = reverse ? 'asc': 'desc';
|
||||
// const reval: T[] = orderBy(tableData, [sortKey], [order]);
|
||||
const reval: T[] = tableData;
|
||||
return reval;
|
||||
}
|
||||
|
||||
function TableTd({ item, row_item, data } : {item: THeader, row_item: T, data: string | null})
|
||||
{
|
||||
var val_complete: JSX.Element = <></>;
|
||||
if(item.visible === undefined || item.visible === true)
|
||||
{
|
||||
if(item.callback)
|
||||
{
|
||||
val_complete = item.callback(item.key, row_item, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
val_complete = (<td>{data}</td>);
|
||||
}
|
||||
}
|
||||
return val_complete;
|
||||
}
|
||||
|
||||
function TableTr(
|
||||
{headers,row_item}:
|
||||
{
|
||||
headers: THeader[],
|
||||
row_item: T
|
||||
})
|
||||
{
|
||||
return(
|
||||
<tr>
|
||||
{
|
||||
headers.map((h_item) =>
|
||||
{
|
||||
var c: any = row_item; // workaround TS2322 TS7053
|
||||
return ( <TableTd item={h_item} row_item={row_item} data={c[h_item.key]}></TableTd> );
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function tableHeader(headers: THeader[])
|
||||
{
|
||||
return(
|
||||
headers.map(
|
||||
(row) =>
|
||||
{
|
||||
if(row.visible === undefined || row.visible === true)
|
||||
{
|
||||
return (
|
||||
<td key={row.key}>
|
||||
{row.label}{" "}
|
||||
<SortButton
|
||||
columnKey={row.key}
|
||||
onClick={() => changeSort(row.key)}
|
||||
{...{
|
||||
sortOrder,
|
||||
sortKey,
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (<></>);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<table className='sortable'>
|
||||
<thead>
|
||||
<tr>
|
||||
{tableHeader(headers)}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{sortedData.map((row_item) => {
|
||||
return (
|
||||
<TableTr headers={headers} row_item = {row_item} />
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
1
src/data/HoPe_all_flights.json
Executable file
1
src/data/HoPe_all_flights.json
Executable file
File diff suppressed because one or more lines are too long
8002
src/data/dataX.json
Normal file
8002
src/data/dataX.json
Normal file
File diff suppressed because it is too large
Load Diff
21
src/global/tools.ts
Normal file
21
src/global/tools.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
|
||||
arr.reduce((groups, item) =>
|
||||
{
|
||||
(groups[key(item)] ||= []).push(item);
|
||||
return groups;
|
||||
}, {} as Record<K, T[]>);
|
||||
|
||||
export function groupByMap<K, V>(list: Array<V>, keyGetter: (input: V) => K): Map<K, Array<V>>
|
||||
{
|
||||
const map = new Map<K, Array<V>>();
|
||||
list.forEach((item) => {
|
||||
const key = keyGetter(item);
|
||||
const collection = map.get(key);
|
||||
if (!collection) {
|
||||
map.set(key, [item]);
|
||||
} else {
|
||||
collection.push(item);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
107
src/interfaces/IFlights.ts
Normal file
107
src/interfaces/IFlights.ts
Normal file
@ -0,0 +1,107 @@
|
||||
export interface IFlights {
|
||||
success: boolean;
|
||||
message: string;
|
||||
meta: Meta;
|
||||
data?: (IFlight)[] | null;
|
||||
}
|
||||
export interface Meta {
|
||||
totalCount: number;
|
||||
}
|
||||
export interface IFlight {
|
||||
IDFlight: string;
|
||||
FKGliderCategory: string;
|
||||
Category: string;
|
||||
FKCompetitionClass: string;
|
||||
FKCompetitionClassDesired?: null;
|
||||
CompetitionClass: string;
|
||||
FKLaunchtype: string;
|
||||
Launchtype: string;
|
||||
FKPilot: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
Nationality: string;
|
||||
FKFederation?: string | null;
|
||||
ClubID?: string | null;
|
||||
ClubName?: string | null;
|
||||
Glider: string;
|
||||
FKGlider?: string | null;
|
||||
FKGliderBrand?: string | null;
|
||||
GliderBrand?: string | null;
|
||||
FKGliderClassification: string;
|
||||
GliderClassification: string;
|
||||
FKSeason: string;
|
||||
FlightDate: string;
|
||||
UtcOffset: string;
|
||||
FlightStartTime: string;
|
||||
FlightEndTime: string;
|
||||
FlightDuration: string;
|
||||
FirstLat: string;
|
||||
FirstLng: string;
|
||||
LastLat: string;
|
||||
LastLng: string;
|
||||
FlightMinLat: string;
|
||||
FlightMaxLat: string;
|
||||
FlightMinLng: string;
|
||||
FlightMaxLng: string;
|
||||
TakeoffCountry: string;
|
||||
FKTakeoffWaypoint: string;
|
||||
TakeoffWaypointOffset: string;
|
||||
TakeoffLocation?: string | null;
|
||||
TakeoffWaypointName: string;
|
||||
FKClosestWaypoint?: string | null;
|
||||
ClosestWaypointOffset?: string | null;
|
||||
LandingCountry: string;
|
||||
FKLandingWaypoint?: string | null;
|
||||
LandingWaypointOffset?: string | null;
|
||||
LandingWaypointName?: string | null;
|
||||
LandingLocation?: string | null;
|
||||
LinearDistance: string;
|
||||
MaxLinearDistance: string;
|
||||
ArcDistance?: string | null;
|
||||
FKBestTaskType: string;
|
||||
BestTaskType: string;
|
||||
BestTaskTypeKey: string;
|
||||
BestTaskDistance: string;
|
||||
BestTaskPoints: string;
|
||||
BestTaskDuration: string;
|
||||
MaxSpeed?: string | null;
|
||||
GroundSpeed?: string | null;
|
||||
BestTaskSpeed: string;
|
||||
TakeoffAltitude: string;
|
||||
MaxAltitude: string;
|
||||
MinAltitude: string;
|
||||
ElevationGain?: string | null;
|
||||
MeanAltitudeDiff: string;
|
||||
MaxClimb: string;
|
||||
MinClimb: string;
|
||||
Dataversion: string;
|
||||
ValidBRecordsCount?: string | null;
|
||||
AirspaceViolationLevel?: string | null;
|
||||
UserReviewComment: string;
|
||||
UserReviewStatus: string;
|
||||
ReviewRequired: string;
|
||||
ReviewReason: string;
|
||||
ReviewStatus: string;
|
||||
ReviewComment: string;
|
||||
ReviewBy: string;
|
||||
ReviewTime?: null;
|
||||
CommentsEnabled: string;
|
||||
CountComments: string;
|
||||
HasPhotos: string;
|
||||
IsBigSmileCandidate: string;
|
||||
WxcCivlID?: string | null;
|
||||
WxcNacStatus?: string | null;
|
||||
WxcSync?: string | null;
|
||||
WxcSyncTS?: string | null;
|
||||
IgcFilename: string;
|
||||
IgcFileHash: string;
|
||||
GRecordStatus: string;
|
||||
GValidationMessage: string;
|
||||
IsNew: string;
|
||||
CanRetract: string;
|
||||
StatisticsValid: string;
|
||||
UC?: string | null;
|
||||
TC: string;
|
||||
US: string;
|
||||
TS: string;
|
||||
}
|
||||
Reference in New Issue
Block a user