Skip to content

Commit 2a53e9a

Browse files
Visual tweaks
1 parent 3df09e9 commit 2a53e9a

File tree

9 files changed

+198
-135
lines changed

9 files changed

+198
-135
lines changed

mockup/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@
221221
<body>
222222
<header>
223223
<h1>EV Price Tracker</h1>
224-
<p class="subtitle">Track used electric vehicle prices across multiple sources</p>
225224
</header>
226225

227226
<div class="container">

src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ function App() {
6969
return (
7070
<div className="app">
7171
<header>
72-
<h1>Used EV Price Tracker</h1>
73-
<p>Track used electric vehicle prices across multiple sources</p>
72+
<h1>Used EV Finder</h1>
73+
<p>Compare used electric vehicle prices from multiple dealers and track changes over time.</p>
7474
</header>
7575
<main className="container">
7676
{!selectedModel ? (

src/components/ListingsTable.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export default function ListingsTable({ listings, title = "Current Listings", em
1515

1616
return (
1717
<div className="listings-table">
18-
<h2>{title}</h2>
1918
<table>
2019
<thead>
2120
<tr>
Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,7 @@
11
.model-listings-view {
2+
background: white;
3+
border-radius: 8px;
4+
padding: 1.5rem;
5+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
26
margin-top: 2rem;
37
}
4-
5-
.tabs {
6-
display: flex;
7-
gap: 0.5rem;
8-
margin-bottom: 1rem;
9-
border-bottom: 2px solid #e0e0e0;
10-
}
11-
12-
.tab {
13-
background: none;
14-
border: none;
15-
padding: 0.75rem 1.5rem;
16-
font-size: 1rem;
17-
font-weight: 500;
18-
color: #666;
19-
cursor: pointer;
20-
border-bottom: 2px solid transparent;
21-
margin-bottom: -2px;
22-
transition: all 0.2s ease;
23-
}
24-
25-
.tab:hover {
26-
color: #667eea;
27-
background: #f8f9fa;
28-
}
29-
30-
.tab.active {
31-
color: #667eea;
32-
border-bottom-color: #667eea;
33-
}

src/components/ModelListingsView.jsx

Lines changed: 8 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,14 @@
1-
import React, { useState, useEffect } from 'react';
2-
import ListingsTable from './ListingsTable';
1+
import React from 'react';
2+
import VehicleListingTabs from './VehicleListingTabs';
33
import { findNewListings, findListingsWithPriceChanges } from '../services/dataLoader';
44
import './ModelListingsView.css';
55

66
export default function ModelListingsView({ data, model }) {
7-
const [activeTab, setActiveTab] = useState('all');
8-
9-
// Load tab from URL on mount
10-
useEffect(() => {
11-
const url = new URL(window.location);
12-
const tab = url.searchParams.get('tab');
13-
if (tab === 'new' || tab === 'all') {
14-
setActiveTab(tab);
15-
}
16-
}, []);
17-
18-
// Handle browser back/forward
19-
useEffect(() => {
20-
const handlePopState = () => {
21-
const url = new URL(window.location);
22-
const tab = url.searchParams.get('tab') || 'all';
23-
setActiveTab(tab);
24-
};
25-
26-
window.addEventListener('popstate', handlePopState);
27-
return () => window.removeEventListener('popstate', handlePopState);
28-
}, []);
29-
307
if (!data || data.length === 0 || !model) {
318
return null;
329
}
3310

34-
const handleTabChange = (tab) => {
35-
setActiveTab(tab);
36-
const url = new URL(window.location);
37-
if (tab === 'all') {
38-
url.searchParams.delete('tab');
39-
} else {
40-
url.searchParams.set('tab', tab);
41-
}
42-
window.history.pushState({}, '', url);
43-
};
44-
45-
// Get latest data only for "all" tab
11+
// Get latest data for "all" tab
4612
const latestDate = data
4713
.map(d => d.scraped_at)
4814
.sort()
@@ -73,32 +39,13 @@ export default function ModelListingsView({ data, model }) {
7339
listing => `${listing.make} ${listing.model}` === model
7440
);
7541

76-
// Combine new and price-changed listings
77-
const newAndChangedListings = [...newListings, ...priceChangedListings];
78-
79-
const listings = activeTab === 'all' ? allListings : newAndChangedListings;
80-
const title = activeTab === 'all' ? 'All Listings' : 'New & Changed Listings';
81-
8242
return (
8343
<div className="model-listings-view">
84-
<div className="tabs">
85-
<button
86-
className={`tab ${activeTab === 'all' ? 'active' : ''}`}
87-
onClick={() => handleTabChange('all')}
88-
>
89-
All Listings ({allListings.length})
90-
</button>
91-
<button
92-
className={`tab ${activeTab === 'new' ? 'active' : ''}`}
93-
onClick={() => handleTabChange('new')}
94-
>
95-
New & Changed ({newAndChangedListings.length})
96-
</button>
97-
</div>
98-
<ListingsTable
99-
listings={listings}
100-
title={title}
101-
showPriceChange={activeTab === 'new'}
44+
<VehicleListingTabs
45+
newListings={newListings}
46+
changedListings={priceChangedListings}
47+
allListings={allListings}
48+
showModel={false}
10249
/>
10350
</div>
10451
);

src/components/NewListings.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@
77
}
88

99
.new-listings-header {
10-
display: flex;
11-
justify-content: space-between;
12-
align-items: flex-start;
13-
margin-bottom: 1.5rem;
14-
gap: 2rem;
10+
margin-bottom: 0;
1511
}
1612

1713
.new-listings h2 {

src/components/NewListings.jsx

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react';
2-
import ListingsTable from './ListingsTable';
2+
import VehicleListingTabs from './VehicleListingTabs';
33
import { findNewListings, findListingsWithPriceChanges } from '../services/dataLoader';
44
import './NewListings.css';
55

@@ -34,7 +34,7 @@ export default function NewListings({ data }) {
3434
const newListings = findNewListings(data);
3535
const priceChangedListings = findListingsWithPriceChanges(data);
3636

37-
// Combine new and price-changed listings
37+
// Combine new and price-changed listings for source filtering
3838
const allListings = [...newListings, ...priceChangedListings];
3939

4040
if (allListings.length === 0) {
@@ -45,9 +45,13 @@ export default function NewListings({ data }) {
4545
const sources = [...new Set(allListings.map(l => l.source))].sort();
4646

4747
// Filter listings by source
48-
const filteredListings = selectedSource === 'all'
49-
? allListings
50-
: allListings.filter(l => l.source === selectedSource);
48+
const filteredNewListings = selectedSource === 'all'
49+
? newListings
50+
: newListings.filter(l => l.source === selectedSource);
51+
52+
const filteredChangedListings = selectedSource === 'all'
53+
? priceChangedListings
54+
: priceChangedListings.filter(l => l.source === selectedSource);
5155

5256
const handleSourceChange = (source) => {
5357
setSelectedSource(source);
@@ -60,39 +64,34 @@ export default function NewListings({ data }) {
6064
window.history.pushState({}, '', url);
6165
};
6266

67+
const sourceFilterElement = (
68+
<div className="source-filter">
69+
<select
70+
id="source-select"
71+
value={selectedSource}
72+
onChange={(e) => handleSourceChange(e.target.value)}
73+
>
74+
<option value="all">All Dealers ({allListings.length})</option>
75+
{sources.map(source => {
76+
const count = allListings.filter(l => l.source === source).length;
77+
const displayName = source.charAt(0).toUpperCase() + source.slice(1);
78+
return (
79+
<option key={source} value={source}>
80+
{displayName} ({count})
81+
</option>
82+
);
83+
})}
84+
</select>
85+
</div>
86+
);
87+
6388
return (
6489
<div className="new-listings">
65-
<div className="new-listings-header">
66-
<div>
67-
<h2>New & Changed Listings</h2>
68-
<p className="subtitle">Recently added vehicles and price changes from the latest scrape</p>
69-
</div>
70-
<div className="source-filter">
71-
<label htmlFor="source-select">Filter by source:</label>
72-
<select
73-
id="source-select"
74-
value={selectedSource}
75-
onChange={(e) => handleSourceChange(e.target.value)}
76-
>
77-
<option value="all">All Sources ({allListings.length})</option>
78-
{sources.map(source => {
79-
const count = allListings.filter(l => l.source === source).length;
80-
const displayName = source.charAt(0).toUpperCase() + source.slice(1);
81-
return (
82-
<option key={source} value={source}>
83-
{displayName} ({count})
84-
</option>
85-
);
86-
})}
87-
</select>
88-
</div>
89-
</div>
90-
<ListingsTable
91-
listings={filteredListings}
92-
title=""
93-
emptyMessage="No new or changed listings found"
90+
<VehicleListingTabs
91+
newListings={filteredNewListings}
92+
changedListings={filteredChangedListings}
9493
showModel={true}
95-
showPriceChange={true}
94+
sourceFilter={sourceFilterElement}
9695
/>
9796
</div>
9897
);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
.vehicle-listing-tabs {
2+
/* No top margin needed */
3+
}
4+
5+
.tabs-container {
6+
display: flex;
7+
justify-content: space-between;
8+
align-items: flex-end;
9+
margin-bottom: 1.5rem;
10+
border-bottom: 2px solid #e5e7eb;
11+
gap: 2rem;
12+
}
13+
14+
.tabs {
15+
display: flex;
16+
gap: 0.5rem;
17+
}
18+
19+
.tabs-filter {
20+
display: flex;
21+
align-items: center;
22+
padding-bottom: 0.75rem;
23+
}
24+
25+
.tab {
26+
padding: 0.75rem 1.5rem;
27+
background: none;
28+
border: none;
29+
border-bottom: 2px solid transparent;
30+
cursor: pointer;
31+
font-size: 1rem;
32+
font-weight: 500;
33+
color: #6b7280;
34+
transition: all 0.2s;
35+
margin-bottom: -2px;
36+
}
37+
38+
.tab:hover {
39+
color: #1f2937;
40+
background-color: #f9fafb;
41+
}
42+
43+
.tab.active {
44+
color: #2563eb;
45+
border-bottom-color: #2563eb;
46+
}

0 commit comments

Comments
 (0)