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
|
|
@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||
import { useSessionsStore } from '@/stores/sessions'
|
||||
import type { ShiftPointAnalysis, TrackMapAnalysis } from '@/types'
|
||||
import { isShiftPointAnalysis, isTrackMapAnalysis } from '@/utils/analysis'
|
||||
import * as api from '@/api/client'
|
||||
|
||||
const props = defineProps<{
|
||||
id: string
|
||||
|
|
@ -100,8 +101,6 @@ function viewAnalysis() {
|
|||
if (!session.value) return
|
||||
if (session.value.use_case === 'shift_points') {
|
||||
router.push({ name: 'shift-points', params: { id: session.value.id } })
|
||||
} else if (session.value.use_case === 'track_map') {
|
||||
router.push({ name: 'track-map', params: { id: session.value.id } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +120,37 @@ async function handleDeleteRun(runId: string, runLabel: string) {
|
|||
async function handleReopen() {
|
||||
await store.reopenSession(sessionId.value)
|
||||
}
|
||||
|
||||
const buildingTrack = ref(false)
|
||||
const buildTrackError = ref<string | null>(null)
|
||||
const buildTrackSuccess = ref(false)
|
||||
|
||||
const isTrackMapping = computed(() => session.value?.use_case === 'track_mapping')
|
||||
|
||||
const hasLeftRun = computed(() =>
|
||||
session.value?.runs.some((r) => r.label?.toLowerCase() === 'left' && r.status === 'stopped') ?? false,
|
||||
)
|
||||
|
||||
const hasRightRun = computed(() =>
|
||||
session.value?.runs.some((r) => r.label?.toLowerCase() === 'right' && r.status === 'stopped') ?? false,
|
||||
)
|
||||
|
||||
const canBuildTrack = computed(() => isCompleted.value && hasLeftRun.value && hasRightRun.value)
|
||||
|
||||
async function handleBuildTrack() {
|
||||
buildingTrack.value = true
|
||||
buildTrackError.value = null
|
||||
buildTrackSuccess.value = false
|
||||
try {
|
||||
await store.reAnalyze(sessionId.value)
|
||||
await api.buildTrackModel({ session_id: sessionId.value })
|
||||
buildTrackSuccess.value = true
|
||||
} catch (e: unknown) {
|
||||
buildTrackError.value = e instanceof Error ? e.message : 'Failed to build track model'
|
||||
} finally {
|
||||
buildingTrack.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -310,7 +340,7 @@ async function handleReopen() {
|
|||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-primary" @click="viewAnalysis">
|
||||
<button v-if="session.use_case === 'shift_points'" class="btn btn-primary" @click="viewAnalysis">
|
||||
📊 View Full Analysis
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="handleReAnalyze">
|
||||
|
|
@ -329,6 +359,42 @@ async function handleReopen() {
|
|||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Track Mapping: Build Track Model -->
|
||||
<section v-if="isTrackMapping && isCompleted" class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h2>Build Track Model</h2>
|
||||
</div>
|
||||
<div v-if="canBuildTrack">
|
||||
<p class="text-secondary mb-2" style="font-size: 0.85rem">
|
||||
Both boundary runs (left + right) are recorded. Build the persistent track model to enable corner analysis and sector timing.
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="buildingTrack"
|
||||
@click="handleBuildTrack"
|
||||
>
|
||||
{{ buildingTrack ? 'Building...' : 'Build Track Model' }}
|
||||
</button>
|
||||
<div v-if="buildTrackSuccess" class="text-success mt-2" style="font-size: 0.85rem">
|
||||
Track model built successfully. <RouterLink to="/tracks">View in Tracks →</RouterLink>
|
||||
</div>
|
||||
<div v-if="buildTrackError" class="text-error mt-2" style="font-size: 0.85rem">
|
||||
{{ buildTrackError }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-secondary" style="font-size: 0.85rem">
|
||||
<p>Requires 2 completed runs labeled "left" and "right".</p>
|
||||
<div class="flex gap-2 mt-1">
|
||||
<span :class="hasLeftRun ? 'text-success' : 'text-muted'">
|
||||
{{ hasLeftRun ? '✓' : '○' }} Left boundary
|
||||
</span>
|
||||
<span :class="hasRightRun ? 'text-success' : 'text-muted'">
|
||||
{{ hasRightRun ? '✓' : '○' }} Right boundary
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue