Skip to content

Commit

Permalink
feat: export flights (#46)
Browse files Browse the repository at this point in the history
* feat: export flights

* fix: export to csv and json

* fix: import from AirTrail export

* docs: add guide to importing from AirTrail export
  • Loading branch information
johanohly authored Sep 26, 2024
1 parent 2bc373b commit 06060f3
Show file tree
Hide file tree
Showing 11 changed files with 387 additions and 15 deletions.
93 changes: 93 additions & 0 deletions docs/docs/features/export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
sidebar_position: 2
---

# Export

The export feature allows you to export your flight data from AirTrail.

## Export flights

To export your flights, follow these steps:

1. Go to the AirTrail application.
2. Go to the settings page.
3. Click on the "Export" tab.
4. Choose your desired export format (CSV or JSON).

## Export formats

### CSV

The CSV export option allows you to export your flights as a CSV file, which can be opened in any spreadsheet
application like Microsoft Excel or Google Sheets. It is a simple and easy-to-use format that can be used to
analyze your flight data.

#### Format

The CSV file contains the following columns:

- `date`: The date of the flight (YYYY-MM-DD format).
- `from`: The IATA code of the departure airport.
- `to`: The IATA code of the arrival airport.
- `departure`: The departure time in ISO 8601 format (if available).
- `arrival`: The arrival time in ISO 8601 format (if available).
- `duration`: The duration of the flight in seconds.
- `flightNumber`: The flight number (if available).
- `flightReason`: The reason for the flight (if provided).
- `airline`: The airline operating the flight (if available).
- `aircraft`: The type of aircraft used (if available).
- `aircraftReg`: The registration number of the aircraft (if available).
- `note`: Any additional notes about the flight.
- `seat`: The type of seat (e.g., window, aisle, etc.).
- `seatNumber`: The seat number (if available).
- `seatClass`: The class of the seat (e.g., economy, business).

### JSON

:::tip
The JSON format can be reimported into AirTrail using the import feature.
:::

The JSON export option provides a more structured format that is ideal for developers or when integrating the data into
other systems. It contains nested objects for each flight and detailed data for each user and their seat information.

#### Format

The JSON file follows this structure:

```json
{
"users": [
{
"id": "user_id",
"displayName": "User Name",
"username": "username"
}
],
"flights": [
{
"date": "YYYY-MM-DD",
"from": "ICAO_CODE",
"to": "ICAO_CODE",
"departure": "ISO_8601_DATETIME",
"arrival": "ISO_8601_DATETIME",
"duration": flight_duration_in_seconds,
"flightNumber": "FLIGHT_NUMBER",
"flightReason": "FLIGHT_REASON",
"airline": "ICAO_AIRLINE_CODE",
"aircraft": "ICAO_AIRCRAFT_TYPE",
"aircraftReg": "AIRCRAFT_REGISTRATION",
"note": "FLIGHT_NOTE",
"seats": [
{
"userId": "USER_ID",
"guestName": "GUEST_NAME",
"seat": "SEAT_TYPE",
"seatNumber": "SEAT_NUMBER",
"seatClass": "SEAT_CLASS"
}
]
}
]
}
26 changes: 22 additions & 4 deletions docs/docs/features/import.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ sidebar_position: 1

The import feature allows you to import flight data from other sources into AirTrail.
Currently, AirTrail supports importing flights from [MyFlightradar24](https://my.flightradar24.com)
, [App in the Air](https://appintheair.com) and [JetLog](https://github.com/pbogre/jetlog).
, [App in the Air](https://appintheair.com), [JetLog](https://github.com/pbogre/jetlog)
and [AirTrail JSON files](/docs/features/export).

## Import flights from MyFlightradar24

Expand All @@ -22,7 +23,7 @@ Once you have the CSV file, you can import it into AirTrail by following these s
1. Go to the AirTrail application.
2. Go to the settings page.
3. Click on the "Import" tab.
4. Click on the "Choose File" button and select the CSV file you downloaded from MyFlightradar24.
4. Click on the "Select file" button and select the CSV file you downloaded from MyFlightradar24.
5. Click on the "Import" button to start the import process.

After the import process is complete, you will see your flights on the map.
Expand All @@ -41,7 +42,7 @@ Once you have the text file, you can import it into AirTrail by following these
1. Go to the AirTrail application.
2. Go to the settings page.
3. Click on the "Import" tab.
4. Click on the "Choose File" button and select the text file you received from App in the Air.
4. Click on the "Select file" button and select the text file you received from App in the Air.
5. Click on the "Import" button to start the import process.

After the import process is complete, you will see your flights on the map.
Expand All @@ -63,7 +64,24 @@ Once you have the CSV file, you can import it into AirTrail by following these s
1. Go to the AirTrail application.
2. Go to the settings page.
3. Click on the "Import" tab.
4. Click on the "Choose File" button and select the CSV file you downloaded from JetLog.
4. Click on the "Select file" button and select the CSV file you downloaded from JetLog.
5. Click on the "Import" button to start the import process.

After the import process is complete, you will see your flights on the map.

## Import flights from AirTrail JSON files

:::tip
Make sure the file you are importing is called `airtrail.json`. If it is not, rename it to `airtrail.json` before
importing.
:::

Once you have the JSON file, you can import it into AirTrail by following these steps:

1. Go to the AirTrail application.
2. Go to the settings page.
3. Click on the "Import" tab.
4. Click on the "Select file" button and select the JSON file you want to import.
5. Click on the "Import" button to start the import process.

After the import process is complete, you will see your flights on the map.
4 changes: 4 additions & 0 deletions src/lib/components/modals/settings/SettingsModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
GeneralPage,
AppearancePage,
UsersPage,
ExportPage,
OAuthPage,
SecurityPage,
} from './pages';
Expand All @@ -22,6 +23,7 @@
{ title: 'Security', id: 'security' },
{ title: 'Appearance', id: 'appearance' },
{ title: 'Import', id: 'import' },
{ title: 'Export', id: 'export' },
] as const;
const ADMIN_SETTINGS = [
{ title: 'Users', id: 'users' },
Expand Down Expand Up @@ -131,6 +133,8 @@
<AppearancePage />
{:else if activeTab === 'import'}
<ImportPage {invalidator} />
{:else if activeTab === 'export'}
<ExportPage />
{:else if activeTab === 'users'}
<UsersPage />
{:else if activeTab === 'oauth'}
Expand Down
64 changes: 64 additions & 0 deletions src/lib/components/modals/settings/pages/ExportPage.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts">
import { PageHeader } from '.';
import { api } from '$lib/trpc';
import { cn } from '$lib/utils';
import { FileSpreadsheet, FileJson } from '@o7/icon/lucide';
import { Card } from '$lib/components/ui/card';
import { toast } from 'svelte-sonner';
const downloadBlob = (blob: Blob, filename: string) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
toast.info('Your download should start shortly');
URL.revokeObjectURL(url);
};
const downloadCsv = async () => {
const csv = await api.flight.exportCsv.query();
const blob = new Blob([csv], { type: 'text/csv' });
downloadBlob(blob, 'airtrail.csv');
};
const downloadJson = async () => {
const json = await api.flight.exportJson.query();
const blob = new Blob([json], { type: 'application/json' });
downloadBlob(blob, 'airtrail.json');
};
</script>

<PageHeader title="Export">
{#snippet subtitleHtml()}
<p class="text-muted-foreground text-sm">
Export your data. Learn more about the data formats <a
href="https://johanohly.github.io/AirTrail/docs/features/export#export-formats"
target="_blank"
class="text-blue-500 underline">in the documentation</a
>.
</p>
{/snippet}
<div class="flex">
<button onclick={downloadCsv} class="w-full">
<Card
class={cn(
'cursor-pointer py-12 border-2 border-dashed border-r-0 rounded-r-none flex flex-col items-center hover:bg-card-hover dark:hover:bg-dark-2',
)}
>
<FileSpreadsheet size={64} />
<span class="text-muted-foreground">.csv</span>
</Card>
</button>
<button onclick={downloadJson} class="w-full">
<Card
class={cn(
'cursor-pointer py-12 border-2 border-dashed rounded-l-none flex flex-col items-center hover:bg-card-hover dark:hover:bg-dark-2',
)}
>
<FileJson size={64} />
<span class="text-muted-foreground">.json</span>
</Card>
</button>
</div>
</PageHeader>
6 changes: 3 additions & 3 deletions src/lib/components/modals/settings/pages/ImportPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
const file = files?.[0];
if (!file) return;
if (!file.name.endsWith('.csv') && !file.name.endsWith('.txt')) {
if (!file.name.endsWith('.csv') && !file.name.endsWith('.txt') && !file.name.endsWith('.json')) {
fileError = 'File type not supported';
} else if (file.size > 5 * 1024 * 1024) {
fileError = 'File must be less than 5MB';
Expand Down Expand Up @@ -53,7 +53,7 @@

<PageHeader
title="Import"
subtitle="Supported platforms: FlightRadar24, App in the Air, JetLog"
subtitle="Supported platforms: FlightRadar24, App in the Air, JetLog and AirTrail backups"
>
<label for="file" class="block">
<Card
Expand All @@ -75,7 +75,7 @@
id="file"
name="file"
type="file"
accept=".csv,.txt"
accept=".csv,.txt,.json"
bind:files
class="hidden"
/>
Expand Down
19 changes: 13 additions & 6 deletions src/lib/components/modals/settings/pages/PageHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
<script lang="ts">
/* eslint svelte/no-at-html-tags: 0 */
import { Separator } from '$lib/components/ui/separator';
import type { Snippet } from 'svelte';
let {
title,
subtitle,
children,
subtitleHtml,
headerRight,
children,
}: {
title: string;
subtitle: string;
children: Snippet;
subtitle?: string;
subtitleHtml?: Snippet;
headerRight?: Snippet;
children: Snippet;
} = $props();
</script>

<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-medium">{title}</h3>
<p class="text-muted-foreground text-sm">
{subtitle}
</p>
{#if subtitleHtml}
{@render subtitleHtml()}
{:else if subtitle}
<p class="text-muted-foreground text-sm">
{@html subtitle}
</p>
{/if}
</div>
{#if headerRight}
{@render headerRight()}
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/modals/settings/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { default as UsersPage } from './users-page/UsersPage.svelte';
export { default as GeneralPage } from './general-page/GeneralPage.svelte';
export { default as SecurityPage } from './security-page/SecurityPage.svelte';
export { default as ImportPage } from './ImportPage.svelte';
export { default as ExportPage } from './ExportPage.svelte';
export { default as OAuthPage } from './OAuthPage.svelte';
Loading

0 comments on commit 06060f3

Please sign in to comment.