From 27d22aefd4b662df9fb062b94d4228922dbd2828 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Mon, 9 Feb 2026 18:31:55 -0500 Subject: [PATCH] feat(CRIT-010): show market prices on county pages alongside premium dealers Add OilPrice type and oilPrices.getByCounty() API client method. County page now fetches both premium listings and scraped oil prices in parallel, displaying them in separate sections with appropriate table/card views. Co-Authored-By: Claude Opus 4.6 --- src/lib/api/client.ts | 14 + src/lib/api/index.ts | 2 +- src/lib/api/types.ts | 12 + .../[stateSlug]/[countySlug]/+page.svelte | 424 ++++++++++-------- 4 files changed, 271 insertions(+), 181 deletions(-) diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index 7cdef85..7480d13 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -12,6 +12,7 @@ import type { Listing, CreateListingRequest, UpdateListingRequest, + OilPrice, County, ServiceCategory } from './types'; @@ -265,6 +266,18 @@ export const listingsApi = { } }; +/** + * Oil Prices API methods (scraped market data, public) + */ +export const oilPricesApi = { + /** + * Get oil prices for a county (public) + */ + async getByCounty(countyId: number): Promise> { + return request(`/oil-prices/county/${countyId}`); + } +}; + /** * State/County API methods */ @@ -304,6 +317,7 @@ export const api = { company: companyApi, listing: listingApi, listings: listingsApi, + oilPrices: oilPricesApi, state: stateApi, categories: categoriesApi }; diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 2e2c872..710342a 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,3 +1,3 @@ // API Client exports -export { api, authApi, companyApi, listingApi, listingsApi, stateApi, categoriesApi } from './client'; +export { api, authApi, companyApi, listingApi, listingsApi, oilPricesApi, stateApi, categoriesApi } from './client'; export * from './types'; diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts index 57a9265..20cb421 100644 --- a/src/lib/api/types.ts +++ b/src/lib/api/types.ts @@ -99,6 +99,18 @@ export interface CreateListingRequest { export type UpdateListingRequest = Partial; +// Oil Price Types (scraped market data) +export interface OilPrice { + id: number; + state: string | null; + zone: number | null; + name: string | null; + price: number | null; + date: string | null; + scrapetimestamp: string | null; + county_id: number | null; +} + // State/County Types export interface County { id: number; diff --git a/src/routes/(app)/[stateSlug]/[countySlug]/+page.svelte b/src/routes/(app)/[stateSlug]/[countySlug]/+page.svelte index a5e6df7..38bc59c 100644 --- a/src/routes/(app)/[stateSlug]/[countySlug]/+page.svelte +++ b/src/routes/(app)/[stateSlug]/[countySlug]/+page.svelte @@ -4,11 +4,12 @@ import { onMount } from 'svelte'; import { newEnglandStates } from '../../../../lib/states'; import { api } from '$lib/api'; - import type { Listing, County } from '$lib/api'; + import type { Listing, OilPrice, County } from '$lib/api'; const { stateSlug, countySlug } = $page.params as { stateSlug: string; countySlug: string }; let countyData: County | null = null; let listings: Listing[] = []; + let oilPrices: OilPrice[] = []; let loading = true; let listingsLoading = false; let error: string | null = null; @@ -18,34 +19,40 @@ onMount(async () => { const result = await api.state.getCounty(stateSlug, countySlug); - + if (result.error) { error = result.error; countyData = null; } else { countyData = result.data; - // Fetch listings for this county - await fetchListings(); + // Fetch both listings and oil prices in parallel + await fetchAllPrices(); } - + loading = false; }); - async function fetchListings() { + async function fetchAllPrices() { if (!countyData) return; listingsLoading = true; listingsError = null; - - const result = await api.listings.getByCounty(countyData.id); - - if (result.error) { + + const [listingsResult, oilPricesResult] = await Promise.all([ + api.listings.getByCounty(countyData.id), + api.oilPrices.getByCounty(countyData.id) + ]); + + if (listingsResult.error) { listingsError = 'Failed to load listings'; } else { - listings = result.data || []; + listings = listingsResult.data || []; sortListings(); } - + + // Oil prices failure is non-critical - just show empty + oilPrices = oilPricesResult.data || []; + listingsLoading = false; } @@ -113,114 +120,175 @@ Back to {getStateName(countyData.state)} - -
- {#if listingsLoading} -
- -
- {:else if listingsError} -
- {listingsError} -
- {:else if listings.length === 0} -
-

No active listings found for this county.

-
- {:else} - -
- - -
+ {#if listingsLoading} +
+ +
+ {:else if listingsError} +
+ {listingsError} +
+ {:else} + +
+

Premium Dealers

- - + + +
+ {#each listings as listing} +
+
+

+ {listing.company_name} + {#if listing.town} +
{listing.town} + {/if} +

+
+
+ Card Price:
+ ${listing.price_per_gallon.toFixed(2)} +
+
+ Cash Price:
+ {listing.price_per_gallon_cash ? `$${listing.price_per_gallon_cash.toFixed(2)}` : 'N/A'} +
+
+ Bio %:
+ {listing.bio_percent}% +
+
+ Service:
+ {#if listing.service} + Yes + {:else} + No + {/if} +
+
+ Contact:
+ {#if listing.phone} + {listing.phone}
+ {:else} + N/A
+ {/if} + + Online: {#if listing.online_ordering === 'none'} No {:else if listing.online_ordering === 'online_only'} @@ -228,77 +296,73 @@ {:else if listing.online_ordering === 'both'} Both {/if} -
+ +
+
+ Last Updated:
+ {listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'}
- - {listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'} - - {/each} - - -
- - -
- {#each listings as listing} -
-
-

- {listing.company_name} - {#if listing.town} -
{listing.town} - {/if} -

-
-
- Card Price:
- ${listing.price_per_gallon.toFixed(2)} -
-
- Cash Price:
- {listing.price_per_gallon_cash ? `$${listing.price_per_gallon_cash.toFixed(2)}` : 'N/A'} -
-
- Bio %:
- {listing.bio_percent}% -
-
- Service:
- {#if listing.service} - Yes - {:else} - No - {/if} -
-
- Contact:
- {#if listing.phone} - {listing.phone}
- {:else} - N/A
- {/if} - - Online: - {#if listing.online_ordering === 'none'} - No - {:else if listing.online_ordering === 'online_only'} - Online Only - {:else if listing.online_ordering === 'both'} - Both - {/if} - -
-
- Last Updated:
- {listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'}
-
- {/each} + {/each} +
+ {/if} +
+ + + {#if oilPrices.length > 0} +
+

Market Prices

+ + + + + +
+ {#each oilPrices as op} +
+
+
+ {op.name || 'Unknown'} + {op.price != null ? `$${op.price.toFixed(2)}` : 'N/A'} +
+
+ Posted: {op.date || 'N/A'} +
+
+
+ {/each} +
{/if} -
+ + + {#if listings.length === 0 && oilPrices.length === 0} +
+

No pricing data available for this county yet.

+
+ {/if} + {/if} {:else if error}

County Not Found