added listings

This commit is contained in:
2025-12-26 20:01:28 -05:00
parent 06a5ff98d7
commit f08432e417
17 changed files with 2200 additions and 101 deletions

View File

@@ -1,21 +1,55 @@
<!-- src/routes/(app)/[stateSlug]/[countySlug]/+page.svelte -->
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/stores
';
import { onMount } from 'svelte';
import { newEnglandStates } from '$lib/states';
interface Listing {
id: number;
company_name: string;
is_active: boolean;
price_per_gallon: number;
price_per_gallon_cash: number | null;
note: string | null;
minimum_order: number | null;
service: boolean;
bio_percent: number;
phone: string | null;
online_ordering: string;
county_id: number;
user_id: number;
last_edited: string;
}
const { stateSlug, countySlug } = $page.params as { stateSlug: string; countySlug: string };
let countyData: { id: number; name: string; state: string } | null = null;
let listings: Listing[] = [];
let loading = true;
let listingsLoading = false;
let error: string | null = null;
let listingsError: string | null = null;
let sortColumn = 'price_per_gallon';
let sortDirection = 'asc'; // 'asc' or 'desc' - lowest price first
onMount(async () => {
try {
// Ensure API URL matches the Docker port forwarding for API (9552)
const response = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}/${countySlug}`);
const token = localStorage.getItem('auth_token');
const headers: Record<string, string> = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}/${countySlug}`, {
headers
});
if (!response.ok) {
throw new Error(`Failed to fetch county data: ${response.statusText}`);
}
countyData = await response.json();
// Fetch listings for this county
await fetchListings();
} catch (err) {
error = err instanceof Error ? err.message : 'An error occurred while fetching county data.';
countyData = null;
@@ -23,17 +57,190 @@
loading = false;
}
});
</script>3
async function fetchListings() {
if (!countyData) return;
listingsLoading = true;
listingsError = null;
try {
const response = await fetch(`http://localhost:9552/listings/county/${countyData.id}`);
if (response.ok) {
listings = await response.json();
sortListings();
} else {
listingsError = 'Failed to load listings';
}
} catch (err) {
listingsError = 'Network error loading listings';
} finally {
listingsLoading = false;
}
}
function sortListings() {
listings = [...listings].sort((a, b) => {
let aValue = a[sortColumn as keyof Listing];
let bValue = b[sortColumn as keyof Listing];
// Handle string sorting
if (typeof aValue === 'string' && typeof bValue === 'string') {
const comparison = aValue.localeCompare(bValue);
return sortDirection === 'asc' ? comparison : -comparison;
}
// Handle number sorting
if (typeof aValue === 'number' && typeof bValue === 'number') {
return sortDirection === 'asc' ? aValue - bValue : bValue - aValue;
}
// Handle boolean sorting
if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {
const aNum = aValue ? 1 : 0;
const bNum = bValue ? 1 : 0;
return sortDirection === 'asc' ? aNum - bNum : bNum - aNum;
}
return 0;
});
}
function handleSort(column: string) {
if (sortColumn === column) {
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
} else {
sortColumn = column;
sortDirection = 'asc';
}
sortListings();
}
function getStateName(stateAbbr: string): string {
const state = newEnglandStates.find(s => s.id === stateAbbr);
return state ? state.name : stateAbbr;
}
</script>
{#if loading}
<div class="text-center py-10">
<p>Loading county data...</p>
</div>
{:else if countyData}
<!-- Breadcrumbs -->
<nav class="breadcrumb mb-6" aria-label="Breadcrumb">
<ul class="flex space-x-2 text-sm">
<li><a href="/" class="text-blue-500 hover:underline">Home</a></li>
<li class="text-gray-500">/</li>
<li><a href="/{stateSlug}" class="text-blue-500 hover:underline">{getStateName(countyData.state)}</a></li>
<li class="text-gray-500">/</li>
<li class="text-gray-700 font-medium">{countyData.name}</li>
</ul>
</nav>
<div class="text-center py-10">
<h1 class="text-3xl font-bold">{countyData.name}</h1>
<p class="text-xl mt-4">County ID: {countyData.id}</p>
<a href="/{stateSlug}" class="btn btn-primary mt-6">Back to State</a>
<a href="/{stateSlug}" class="btn btn-primary mt-6">Back to {getStateName(countyData.state)}</a>
</div>
<!-- Listings Table -->
<div class="mt-8">
{#if listingsLoading}
<div class="flex justify-center">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else if listingsError}
<div class="alert alert-error">
<span>{listingsError}</span>
</div>
{:else if listings.length === 0}
<div class="text-center py-8">
<p class="text-lg text-gray-600">No active listings found for this county.</p>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra w-full">
<thead>
<tr>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('company_name')}>
Company
{#if sortColumn === 'company_name'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('price_per_gallon')}>
Price per Gallon Card
{#if sortColumn === 'price_per_gallon'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('price_per_gallon_cash')}>
Price per Gallon Cash
{#if sortColumn === 'price_per_gallon_cash'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('bio_percent')}>
Bio %
{#if sortColumn === 'bio_percent'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('online_ordering')}>
Online Ordering
{#if sortColumn === 'online_ordering'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('service')}>
Service
{#if sortColumn === 'service'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
<th class="cursor-pointer hover:bg-base-200" on:click={() => handleSort('phone')}>
Phone
{#if sortColumn === 'phone'}
{sortDirection === 'asc' ? '↑' : '↓'}
{/if}
</th>
</tr>
</thead>
<tbody>
{#each listings as listing}
<tr>
<td>{listing.company_name}</td>
<td>${listing.price_per_gallon.toFixed(2)}</td>
<td>{listing.price_per_gallon_cash ? `$${listing.price_per_gallon_cash.toFixed(2)}` : 'N/A'}</td>
<td>{listing.bio_percent}%</td>
<td>
{#if listing.online_ordering === 'none'}
No
{:else if listing.online_ordering === 'online_only'}
Online Only
{:else if listing.online_ordering === 'both'}
Both
{/if}
</td>
<td>
{#if listing.service}
<span class="badge badge-success">Yes</span>
{:else}
<span class="badge badge-neutral">No</span>
{/if}
</td>
<td>
{#if listing.phone}
<a href="tel:{listing.phone}" class="text-blue-600 hover:underline">{listing.phone}</a>
{:else}
N/A
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
{:else if error}
<div class="text-center py-10">