Skip to content

Commit

Permalink
feat: edit flights and attach airline
Browse files Browse the repository at this point in the history
  • Loading branch information
johanohly committed Sep 10, 2024
1 parent 58c3212 commit a47bc74
Show file tree
Hide file tree
Showing 15 changed files with 325 additions and 144 deletions.
2 changes: 1 addition & 1 deletion src/lib/components/dock/DockTooltipItem.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { DockItem } from '$lib/components/dock/index.js';
import { DockItem } from '$lib/components/dock';
import * as Tooltip from '$lib/components/ui/tooltip';
export let item: {
Expand Down
29 changes: 22 additions & 7 deletions src/lib/components/form-fields/AircraftField.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import type { SuperForm } from 'sveltekit-superforms';
import { createCombobox, melt } from '@melt-ui/svelte';
import { fly } from 'svelte/transition';
import { ChevronDown, ChevronUp } from '@o7/icon/lucide';
import { CircleX, ChevronDown, ChevronUp } from '@o7/icon/lucide';
import { z } from 'zod';
import type { flightSchema } from '$lib/zod/flight';
import { writable } from 'svelte/store';
import { AIRCRAFT } from '$lib/data/aircraft';
import { type Aircraft, aircraftFromICAO, WTC_TO_LABEL } from '$lib/utils/data/aircraft';
import {
type Aircraft,
aircraftFromICAO,
WTC_TO_LABEL,
} from '$lib/utils/data/aircraft';
let {
form,
Expand All @@ -34,9 +38,7 @@
selected,
});
selected.subscribe((item) => {
if (item) {
$formData.aircraft = item.value;
}
$formData.aircraft = item?.value ?? null;
});
$effect(() => {
Expand All @@ -50,7 +52,7 @@
if ($touchedInput && $inputValue !== '') {
aircraft = AIRCRAFT.filter((a) =>
a.name.toLowerCase().includes($inputValue.toLowerCase()),
);
).slice(0, 20);
} else {
aircraft = [];
}
Expand All @@ -63,9 +65,22 @@
<div class="relative">
<input
use:melt={$input}
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 pr-12"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 pr-16"
placeholder="Select aircraft"
/>
{#if $open && $selected}
<button
type="button"
onclick={() => {
// @ts-expect-error - This is totally fine
$selected = undefined;
$inputValue = '';
}}
class="cursor-pointer absolute right-10 top-1/2 z-10 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<CircleX size="20" />
</button>
{/if}
<div
class="absolute right-2 top-1/2 z-10 -translate-y-1/2 text-muted-foreground"
>
Expand Down
124 changes: 124 additions & 0 deletions src/lib/components/form-fields/AirlineField.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,135 @@
<script lang="ts">
import * as Form from '$lib/components/ui/form';
import type { SuperForm } from 'sveltekit-superforms';
import { createCombobox, melt } from '@melt-ui/svelte';
import { fly } from 'svelte/transition';
import { CircleX, ChevronDown, ChevronUp } from '@o7/icon/lucide';
import { z } from 'zod';
import type { flightSchema } from '$lib/zod/flight';
import { writable } from 'svelte/store';
import { type Airline, airlineFromICAO } from '$lib/utils/data/airlines';
import { AIRLINES } from '$lib/data/airlines';
let {
form,
}: {
form: SuperForm<z.infer<typeof flightSchema>>;
} = $props();
const { form: formData } = form;
const selected = writable(
$formData.airline
? {
label: airlineFromICAO($formData.airline)?.name,
value: $formData.airline,
}
: undefined,
);
const {
elements: { menu, input, option },
states: { open, inputValue, touchedInput },
} = createCombobox<string>({
forceVisible: true,
selected,
});
selected.subscribe((item) => {
$formData.airline = item?.value ?? null;
});
$effect(() => {
if (!$open) {
$inputValue = $selected?.label ?? '';
}
});
let airlines: Airline[] = $state([]);
$effect(() => {
if ($touchedInput && $inputValue !== '') {
airlines = AIRLINES.filter((a) => {
const input = $inputValue.toLowerCase();
return (
a.icao.toLowerCase() === input || a.name.toLowerCase().includes(input)
);
}).slice(0, 20);
} else {
airlines = [];
}
});
</script>

<Form.Field {form} name="airline" class="flex flex-col">
<Form.Control let:attrs>
<Form.Label>Airline</Form.Label>
<div class="relative">
<input
use:melt={$input}
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 pr-12"
placeholder="Select airline"
/>
{#if $open && $selected}
<button
type="button"
onclick={() => {
// @ts-expect-error - This is totally fine
$selected = undefined;
$inputValue = '';
}}
class="cursor-pointer absolute right-10 top-1/2 z-10 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<CircleX size="20" />
</button>
{/if}
<div
class="absolute right-2 top-1/2 z-10 -translate-y-1/2 text-muted-foreground"
>
{#if $open}
<ChevronUp class="size-4" />
{:else}
<ChevronDown class="size-4" />
{/if}
</div>
</div>
<input hidden bind:value={$formData.airline} name={attrs.name} />
</Form.Control>
{#if $open}
<ul
class="z-[5000] flex max-h-[300px] flex-col overflow-hidden rounded-lg border"
use:melt={$menu}
transition:fly={{ duration: 150, y: -5 }}
>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="flex max-h-full flex-col gap-0 overflow-y-auto bg-card px-2 py-2 text-card-foreground dark:bg-dark-1"
tabindex="0"
>
{#each airlines as airline}
<li
use:melt={$option({
value: airline.icao,
label: airline.name,
})}
class="relative cursor-pointer scroll-my-2 rounded-md py-2 pl-4 pr-4
data-[highlighted]:bg-zinc-300 data-[highlighted]:dark:bg-dark-2"
>
<div class="flex flex-col">
<span class="text-lg truncate">{airline.name}</span>
<span class="text-sm opacity-75"
>{airline.icao}{airline.iata ? ` - ${airline.iata}` : ''}</span
>
</div>
</li>
{:else}
<li class="relative cursor-pointer rounded-md py-1 pl-8 pr-4">
{#if $inputValue}
No airlines found.
{:else}
Start typing to search...
{/if}
</li>
{/each}
</div>
</ul>
{/if}
<Form.FieldErrors />
</Form.Field>
1 change: 1 addition & 0 deletions src/lib/components/form-fields/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as AirportField } from './AirportField.svelte';
export { default as AircraftField } from './AircraftField.svelte';
export { default as AirlineField } from './AirlineField.svelte';
export { default as DateTimeField } from './DateTimeField.svelte';
132 changes: 68 additions & 64 deletions src/lib/components/modals/add-flight/FlightInformation.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import * as Accordion from '$lib/components/ui/accordion';
import * as Form from '$lib/components/ui/form';
import * as Select from '$lib/components/ui/select';
import type { SuperForm } from 'sveltekit-superforms';
import { z } from 'zod';
import { Input, Textarea } from '$lib/components/ui/input';
import { toTitleCase } from '$lib/utils';
import type { flightSchema } from '$lib/zod/flight';
import { AircraftField } from '$lib/components/form-fields';
import { AircraftField, AirlineField } from '$lib/components/form-fields';
import { Separator } from '$lib/components/ui/separator';
let {
form,
Expand All @@ -17,72 +17,76 @@
const { form: formData } = form;
</script>

<Accordion.Item value="flight">
<Accordion.Trigger>Flight Information</Accordion.Trigger>
<Accordion.Content>
<div class="grid gap-4">
<AircraftField {form} />
<div class="grid grid-cols-[1fr_1fr_1fr] gap-2">
<Form.Field {form} name="flightReason">
<Form.Control let:attrs>
<Form.Label>Flight Reason</Form.Label>
<Select.Root
selected={{
label: $formData.flightReason
? toTitleCase($formData.flightReason)
: undefined,
value: $formData.flightReason,
}}
onSelectedChange={(value) => {
if (value) {
<section>
<h3 class="font-medium">Flight Information</h3>
<Separator class="mt-2 mb-3" />
<div class="grid gap-4">
<AircraftField {form} />
<AirlineField {form} />
<div class="grid grid-cols-[1fr_1fr_1fr] gap-2">
<Form.Field {form} name="flightReason">
<Form.Control let:attrs>
<Form.Label>Flight Reason</Form.Label>
<Select.Root
selected={{
label: $formData.flightReason
? toTitleCase($formData.flightReason)
: undefined,
value: $formData.flightReason,
}}
onSelectedChange={(value) => {
if (value) {
if (value.value === $formData.flightReason) {
$formData.flightReason = null;
} else {
$formData.flightReason = value.value;
}
}}
>
<Select.Trigger {...attrs}>
<Select.Value placeholder="Select reason..." />
</Select.Trigger>
<Select.Content>
<Select.Item value="leisure" label="Leisure" />
<Select.Item value="business" label="Business" />
<Select.Item value="crew" label="Crew" />
<Select.Item value="other" label="Other" />
</Select.Content>
</Select.Root>
<input
type="hidden"
bind:value={$formData.flightReason}
name={attrs.name}
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="flightNumber">
<Form.Control let:attrs>
<Form.Label>Flight Number</Form.Label>
<Input bind:value={$formData.flightNumber} {...attrs} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="aircraftReg">
<Form.Control let:attrs>
<Form.Label>Aircraft Registration</Form.Label>
<Input bind:value={$formData.aircraftReg} {...attrs} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
</div>
<Form.Field {form} name="note">
<Form.Control let:attrs>
<Form.Label>Notes</Form.Label>
<Textarea
bind:value={$formData.note}
class="resize-y h-20 min-h-10 max-h-32"
{...attrs}
}
}}
>
<Select.Trigger {...attrs}>
<Select.Value placeholder="Select reason..." />
</Select.Trigger>
<Select.Content>
<Select.Item value="leisure" label="Leisure" />
<Select.Item value="business" label="Business" />
<Select.Item value="crew" label="Crew" />
<Select.Item value="other" label="Other" />
</Select.Content>
</Select.Root>
<input
type="hidden"
value={$formData.flightReason}
name={attrs.name}
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="flightNumber">
<Form.Control let:attrs>
<Form.Label>Flight Number</Form.Label>
<Input bind:value={$formData.flightNumber} {...attrs} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="aircraftReg">
<Form.Control let:attrs>
<Form.Label>Aircraft Registration</Form.Label>
<Input bind:value={$formData.aircraftReg} {...attrs} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
</div>
</Accordion.Content>
</Accordion.Item>
<Form.Field {form} name="note">
<Form.Control let:attrs>
<Form.Label>Notes</Form.Label>
<Textarea
bind:value={$formData.note}
class="resize-y h-20 min-h-10 max-h-32"
{...attrs}
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
</div>
</section>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import * as Accordion from '$lib/components/ui/accordion';
import SeatInformation from '$lib/components/modals/add-flight/SeatInformation.svelte';
import type { SuperForm } from 'sveltekit-superforms';
import { z } from 'zod';
Expand All @@ -13,7 +12,5 @@
} = $props();
</script>

<Accordion.Root>
<SeatInformation {form} />
<FlightInformation {form} />
</Accordion.Root>
<SeatInformation {form} />
<FlightInformation {form} />
Loading

0 comments on commit a47bc74

Please sign in to comment.