Skip to content

Commit

Permalink
A little more hacking on the restrictions table
Browse files Browse the repository at this point in the history
  • Loading branch information
cr0wst committed Dec 30, 2024
1 parent 61114bd commit 8d3fb7e
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 61 deletions.
2 changes: 2 additions & 0 deletions src/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type RestrictionFilters = {
areas: string[];
airport: string;
includeIncoming: boolean;
};
7 changes: 7 additions & 0 deletions src/lib/Badge.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
const { classes, children } = $props<{classes: string[]; children: any }>();
</script>

<div class="rounded-md px-2 py-1 text-xs font-medium {classes.join(' ')}">
{@render children()}
</div>
3 changes: 2 additions & 1 deletion src/lib/server/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { numeric, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';

export const restriction = pgTable('restrictions', {
id: uuid('id').primaryKey().defaultRandom(),
Expand All @@ -8,6 +8,7 @@ export const restriction = pgTable('restrictions', {
to: text('to'),
restriction: text('restriction'),
notes: text('notes'),
priority: numeric('priority').default('0'),
validAt: timestamp('valid_at').defaultNow(),
validUntil: timestamp('valid_until'),
createdAt: timestamp('created_at').defaultNow()
Expand Down
5 changes: 4 additions & 1 deletion src/lib/state.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export const restrictionFilters: RestrictionFilters = $state({
areas: []
areas: [],
airport: '',
includeIncoming: false,
incomingLessEmphasis: false
});
14 changes: 7 additions & 7 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@
<div class="flex min-h-screen flex-col">
<!-- Navbar -->
<nav
class="flex h-16 w-full items-center justify-between bg-gray-700 px-4 shadow-md dark:bg-gray-900"
class="flex h-16 w-full items-center justify-between bg-zinc-700 px-4 shadow-md dark:bg-zinc-900"
>
<a href="/">
<div class="flex gap-x-2 items-center text-xl font-medium text-white hover:text-gray-200">
<div class="flex gap-x-2 items-center text-xl font-medium text-white hover:text-zinc-200">
<div class="w-32"><IndyLogo/></div><div>Controller Tools</div>
</div>
</a>
<ul
class="flex list-none flex-row space-x-2 text-white hover:text-gray-200"
class="flex list-none flex-row space-x-2 text-white hover:text-zinc-200"
>
<li><a href="/restrictions">Restrictions</a></li>
</ul>
</nav>

<!-- Main Content Area -->
<div class="flex-grow bg-gray-50 p-6 dark:bg-gray-800">
<div class="flex-grow bg-zinc-50 p-6 dark:bg-zinc-800">
{@render children()}
</div>

<!-- Footer -->
<footer
class="flex w-full flex-col items-center justify-center bg-gray-700 py-4 dark:bg-gray-900"
class="flex w-full flex-col items-center justify-center bg-zinc-700 py-4 dark:bg-zinc-900"
>
<span class="p-2 text-center text-sm text-gray-300 dark:text-gray-400">
<span class="p-2 text-center text-sm text-zinc-300 dark:text-zinc-400">
This site is not affiliated with the Federal Aviation Administration or
any governing aviation body. All content is approved only for use on the
VATSIM network.
Expand All @@ -41,6 +41,6 @@

<style>
:global(body) {
@apply bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200;
@apply bg-zinc-100 text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200;
}
</style>
7 changes: 6 additions & 1 deletion src/routes/api/restrictions/+server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { db } from '$lib/server/db';
import { restriction } from '$lib/server/db/schema';
import { json } from '@sveltejs/kit';
import { asc, desc, eq } from 'drizzle-orm';

export async function GET() {
const restrictions = await db.select().from(restriction).orderBy(restriction.airport, 'asc');
const restrictions = await db
.select()
.from(restriction)
// .where(eq(restriction.airport, 'AGC'))
.orderBy(asc(restriction.airport), desc(restriction.priority));

return json(restrictions);
}
57 changes: 13 additions & 44 deletions src/routes/restrictions/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import RestrictionRow from './RestrictionRow.svelte';
import { restrictionFilters } from '$lib/state.svelte';
import RestrictionSection from './RestrictionSection.svelte';
import FilterPanel from './FilterPanel.svelte';
const { data } = $props();
Expand All @@ -13,9 +15,15 @@
map.set(airport, []);
}
if (restrictionFilters.areas.length === 0
|| (restrictionFilters.areas.includes(restriction.to)
|| restrictionFilters.areas.includes(restriction.from))) {
const noAreaSelected = restrictionFilters.areas.length === 0;
const searchIsEmpty = restrictionFilters.airport === '';
const areaMatches = restrictionFilters.areas.includes(restriction.from) || (restrictionFilters.includeIncoming && restrictionFilters.areas.includes(restriction.to));
const searchMatches = restriction.airport.toLowerCase().includes(restrictionFilters.airport.toLowerCase());
const isAreaMatch = noAreaSelected || areaMatches;
const isSearchMatch = searchMatches || searchIsEmpty;
if (isAreaMatch && isSearchMatch) {
map.get(airport).push(restriction);
}
});
Expand All @@ -29,53 +37,14 @@
return map;
});
const areas = [
'Area 1',
'Area 2',
'Area 3',
'Area 4',
'Area 5',
'Area 6',
'Area 7'
];
function toggleAreaFilter(area: string) {
if (restrictionFilters.areas.includes(area)) {
restrictionFilters.areas = restrictionFilters.areas.filter(a => a !== area);
} else {
restrictionFilters.areas.push(area);
}
}
</script>

<svelte:head>
<title>ICCT - Restrictions</title>
</svelte:head>
<h2>Filters</h2>
<div class="flex gap-2">
{#each areas.filter((a) => a && a.includes('Area')) as area}
<button class="px-2 py-1 rounded-md border border-b-zinc-200"
class:bg-zinc-200={restrictionFilters.areas.includes(area)}
onclick="{() => toggleAreaFilter(area)}">{area}</button>
{/each}
</div>
<FilterPanel />
<div class="w-full">
{#each restrictions as [airport, r]}
<div class="w-full flex mb-2 p-2">
<div class="w-32 text-left mr-4 font-medium border rounded-md p-2 bg-gray-700 text-white">{airport}</div>
<div class="flex-grow flex flex-col">
<div class="flex border-b border-b-zinc-400 font-medium">
<div class="w-5/12">Route</div>
<div class="w-1/12">From</div>
<div class="w-1/12">To</div>
<div class="w-3/12">Restriction</div>
<div class="w-2/12">Notes</div>
</div>
{#each r as restriction}
<RestrictionRow {restriction} />
{/each}
</div>
</div>
<RestrictionSection {airport} restrictions={r} />
{/each}
</div>
20 changes: 20 additions & 0 deletions src/routes/restrictions/AreaBadge.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import Badge from '$lib/Badge.svelte';
let { label } = $props<{label: string}>()
const areaClassesMap: Record<string, string[]> = {
'Area 1': ['text-red-500', 'bg-red-100', 'border-red-500', 'dark:text-red-300', 'dark:bg-red-700', 'dark:border-red-600'],
'Area 2': ['text-yellow-500', 'bg-yellow-100', 'border-yellow-500', 'dark:text-yellow-300', 'dark:bg-yellow-700', 'dark:border-yellow-600'],
'Area 3': ['text-green-500', 'bg-green-100', 'border-green-500', 'dark:text-green-300', 'dark:bg-green-700', 'dark:border-green-600'],
'Area 4': ['text-blue-500', 'bg-blue-100', 'border-blue-500', 'dark:text-blue-300', 'dark:bg-blue-700', 'dark:border-blue-600'],
'Area 5': ['text-indigo-500', 'bg-indigo-100', 'border-indigo-500', 'dark:text-indigo-300', 'dark:bg-indigo-700', 'dark:border-indigo-600'],
'Area 6': ['text-purple-500', 'bg-purple-100', 'border-purple-500', 'dark:text-purple-300', 'dark:bg-purple-700', 'dark:border-purple-600'],
'Area 7': ['text-pink-500', 'bg-pink-100', 'border-pink-500', 'dark:text-pink-300', 'dark:bg-pink-700', 'dark:border-pink-600'],
'Area 8': ['text-gray-500', 'bg-gray-100', 'border-gray-500', 'dark:text-gray-300', 'dark:bg-gray-700', 'dark:border-gray-600'],
}
const defaultAreaClasses = ['text-zinc-500','bg-zinc-100', 'border-zinc-500'];
</script>

<Badge classes={areaClassesMap[label] || defaultAreaClasses}>{label}</Badge>
45 changes: 45 additions & 0 deletions src/routes/restrictions/FilterPanel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script lang="ts">
import { restrictionFilters } from '$lib/state.svelte.js';
const areas = [
'Area 1',
'Area 2',
'Area 3',
'Area 4',
'Area 5',
'Area 6',
'Area 7'
];
function toggleAreaFilter(area: string) {
if (restrictionFilters.areas.includes(area)) {
restrictionFilters.areas = restrictionFilters.areas.filter(a => a !== area);
} else {
restrictionFilters.areas.push(area);
}
}
</script>

<div class="rounded-md lg:border lg:p-4 lg:dark:border-zinc-700 flex flex-col">
<div class="py-2 lg:px-2">
<input type="text" id="filter"
class="w-full rounded-md border border-zinc-300 p-3 text-zinc-700 dark:bg-zinc-700 dark:text-zinc-200"
placeholder="Search for Airport..." bind:value={restrictionFilters.airport}>
</div>
<div class="flex flex-col lg:p-2">
<h2 class="text-lg font-medium">Area Filters</h2>
<div class="flex gap-2">
{#each areas as area}
<button class="px-1 lg:px-2 py-1 text-sm lg:text-base rounded-md border dark:border-zinc-700 dark:text-zinc-200"
class:bg-zinc-200={restrictionFilters.areas.includes(area)}
class:dark:bg-zinc-700={restrictionFilters.areas.includes(area)}
onclick="{() => toggleAreaFilter(area)}">{area}</button>
{/each}
</div>
</div>
<div class="flex py-2 lg:px-2">
<input type="checkbox" id="includeIncoming" bind:checked={restrictionFilters.includeIncoming}>
<label for="includeIncoming" class="text-sm pl-2">Include Incoming Restrictions</label>
</div>
</div>
56 changes: 49 additions & 7 deletions src/routes/restrictions/RestrictionRow.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
<script lang="ts">
const { restriction }: { restriction: any} = $props()
import Badge from '$lib/Badge.svelte';
import AreaBadge from './AreaBadge.svelte';
const { route, restrictions } = $props();
const defaultTextColor = 'text-zinc-100';
const defaultBackgroundColor = 'bg-zinc-900';
</script>

<div class="flex border-b border-b-zinc-200 last:border-0 text-sm">
<div class="w-5/12">{restriction.route}</div>
<div class="w-1/12">{restriction.from}</div>
<div class="w-1/12">{restriction.to}</div>
<div class="w-3/12">{restriction.restriction}</div>
<div class="w-2/12">{restriction.notes}</div>
<div class="flex flex-col lg:flex-row border-b border-b-zinc-300 dark:border-b-zinc-500 lg:py-1 last:border-0 text-sm">
<!-- Route Header -->
<div class="w-full lg:w-4/12 mb-2 lg:mb-0 text-zinc-700 dark:text-white lg:text-black flex">
<span class="block lg:hidden font-bold">Route</span>
{route}
</div>

<!-- Restrictions Data -->
<div class="flex flex-col flex-grow space-y-2">
{#each restrictions as restriction}
<div class="flex flex-col lg:flex-row lg:gap-x-2 space-y-2 lg:space-y-0">
<!-- From Header -->

<div class="w-full lg:w-1/12">
{#if restriction.from}
<AreaBadge label={restriction.from} />
{/if}
</div>

<!-- To Header -->

<div class="w-full lg:w-1/12">
{#if restriction.to}
<AreaBadge label={restriction.to} />
{/if}
</div>


<!-- Restriction Header -->
<div class="w-full lg:w-3/12 flex flex-col justify-center">
<span class="block lg:hidden font-bold">Restriction</span>
{restriction.restriction}
</div>

<!-- Notes Header -->
<div class="w-full lg:w-3/12 flex font-light flex-col justify-center">
<span class="block lg:hidden font-bold">Notes</span>
{restriction.notes}
</div>
</div>
{/each}
</div>
</div>
37 changes: 37 additions & 0 deletions src/routes/restrictions/RestrictionSection.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import RestrictionRow from './RestrictionRow.svelte';
const { airport, restrictions } = $props<{ airport: string; restrictions: any }>();
let routes = $derived.by(() => {
const map = new Map();
restrictions.forEach((restriction: any) => {
const route = restriction.route;
if (!map.has(route)) {
map.set(route, []);
}
map.get(route).push(restriction);
});
return map;
});
</script>

<div class="w-full flex flex-col lg:flex-row mb-2 py-2">
<div class="w-full lg:w-32 text-left mr-4 font-medium border rounded-md p-2 bg-zinc-700 text-white">
{airport}
</div>
<div class="flex flex-col flex-grow">
<div class="hidden lg:flex font-medium border-b dark:border-b-zinc-300 border-b-zinc-500 mb-2">
<div class="w-4/12">Route</div>
<div class="flex flex-grow lg:gap-x-2">
<div class="w-1/12">From</div>
<div class="w-1/12">To</div>
<div class="w-3/12">Restriction</div>
<div class="w-3/12">Notes</div>
</div>
</div>
{#each routes as [route, restrictions]}
<RestrictionRow {route} {restrictions} />
{/each}
</div>
</div>

0 comments on commit 8d3fb7e

Please sign in to comment.