feat: persistent sessions support, remove track_map, add characteristics + tracks views
- Remove obsolete track_map use case from types, views, and router - Delete TrackMapView.vue (superseded by persistent TrackDetailView) - Auto-analyze in handleBuildTrack before building track model - Add CharacteristicsView: expandable car cards with aggregated shift-point profiles (gear RPM/speed ranges, recommended shift RPM) - Add TracksView + TrackDetailView + tracks Pinia store (Canvas 2D geometry) - Add characteristics API client functions (listCarProfiles) - Add tracks and characteristics nav items and routes - Update Game type values to match backend snake_case (project_cars_1/2) - Update use_case types and labels (remove track_map, keep track_mapping)
This commit is contained in:
parent
a6af6e4b98
commit
9e0b46596e
13 changed files with 1098 additions and 345 deletions
116
src/views/TracksView.vue
Normal file
116
src/views/TracksView.vue
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { useTracksStore } from '@/stores/tracks'
|
||||
|
||||
const store = useTracksStore()
|
||||
|
||||
onMounted(() => {
|
||||
store.fetchTracks()
|
||||
})
|
||||
|
||||
function formatTime(iso: string): string {
|
||||
return new Date(iso).toLocaleString()
|
||||
}
|
||||
|
||||
function gameLabel(game: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
assetto_corsa: 'Assetto Corsa',
|
||||
assetto_corsa_competizione: 'ACC',
|
||||
project_cars_1: 'Project CARS 1',
|
||||
project_cars_2: 'Project CARS 2',
|
||||
}
|
||||
return labels[game] ?? game
|
||||
}
|
||||
|
||||
async function handleDelete(game: string, trackName: string) {
|
||||
if (confirm(`Delete track model "${trackName}" (${gameLabel(game)})?`)) {
|
||||
await store.deleteTrack(game, trackName)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tracks-view">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="page-title">Tracks</h2>
|
||||
<RouterLink to="/sessions" class="btn btn-secondary">
|
||||
+ Map a Track
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div v-if="store.error" class="card mb-3 text-error">{{ store.error }}</div>
|
||||
<div v-if="store.loading && store.tracks.length === 0" class="card text-secondary">Loading tracks...</div>
|
||||
|
||||
<div v-if="store.tracks.length === 0 && !store.loading" class="card text-secondary">
|
||||
<p class="mb-2">No track models stored yet.</p>
|
||||
<p style="font-size: 0.85rem; color: var(--color-text-muted)">
|
||||
Create a Track Mapping session, record left and right boundary laps, then build the track model.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="track-cards">
|
||||
<div v-for="track in store.tracks" :key="`${track.game}-${track.track_name}`" class="card track-card">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="badge badge-primary">{{ gameLabel(track.game) }}</span>
|
||||
<span class="track-name">{{ track.track_name }}</span>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<RouterLink
|
||||
:to="`/tracks/${track.game}/${track.track_name}`"
|
||||
class="btn btn-secondary btn-sm"
|
||||
>
|
||||
View
|
||||
</RouterLink>
|
||||
<button
|
||||
class="btn btn-danger btn-sm"
|
||||
@click="handleDelete(track.game, track.track_name)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="track-meta">
|
||||
<span class="text-secondary">{{ track.corner_count }} corners</span>
|
||||
<span class="text-muted">·</span>
|
||||
<span class="text-muted" style="font-size: 0.8rem">{{ formatTime(track.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.track-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.track-card {
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.track-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.track-name {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.track-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue