added listings
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user