Add trend-range gating, vehicle filter, sync cancel, moving time, and UI fixes
- Grey out trend ranges beyond available health history - Reject implausibly fast (vehicle) activities on upload with feedback - Add cancel button + cooperative cancellation for Garmin sync - Show daily steps prominently on the dashboard - Clear errors for malformed/empty upload ZIPs - Snap-target dot when drawing a segment on the map - Time-axis fallback for stationary/HIIT HR timelines; hide map when no GPS - Parse and display moving time (timer) vs elapsed; backfill task - Restyle SegmentsPanel like RouteLeaderboard; Laps/Routes/Segments on one row Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,10 +16,17 @@ function UploadZone({ title, description, accept, endpoint, icon }) {
|
||||
if (data.status === 'SUCCESS' || data.status === 'FAILURE') {
|
||||
clearInterval(intervalsRef.current[taskId])
|
||||
delete intervalsRef.current[taskId]
|
||||
// A successful task may still have skipped the file (e.g. a duplicate or
|
||||
// an activity that looks like vehicle travel) — surface the reason.
|
||||
const skipped = data.status === 'SUCCESS' && data.result?.status === 'skipped'
|
||||
setTasks(ts => ts.map(t =>
|
||||
t.task_id === taskId ? { ...t, status: data.status === 'SUCCESS' ? 'done' : 'failed' } : t
|
||||
t.task_id === taskId
|
||||
? { ...t,
|
||||
status: data.status === 'FAILURE' ? 'failed' : skipped ? 'skipped' : 'done',
|
||||
reason: skipped ? data.result?.reason : t.reason }
|
||||
: t
|
||||
))
|
||||
if (data.status === 'SUCCESS') {
|
||||
if (data.status === 'SUCCESS' && !skipped) {
|
||||
queryClient.invalidateQueries({ queryKey: ['activities'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['health-summary'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['health-metrics'] })
|
||||
@@ -50,6 +57,10 @@ function UploadZone({ title, description, accept, endpoint, icon }) {
|
||||
pollTask(data.task_id)
|
||||
}
|
||||
},
|
||||
onError: (err, file) => {
|
||||
const reason = err.response?.data?.detail || 'Upload failed'
|
||||
setTasks(t => [...t, { file: file?.name || String(file), status: 'failed', reason }])
|
||||
},
|
||||
})
|
||||
|
||||
const onDrop = useCallback((accepted) => {
|
||||
@@ -65,6 +76,7 @@ function UploadZone({ title, description, accept, endpoint, icon }) {
|
||||
function StatusBadge({ status }) {
|
||||
if (status === 'processing') return <span className="ml-2 text-blue-400 animate-pulse">⏳ Processing</span>
|
||||
if (status === 'done') return <span className="ml-2 text-green-400">✓ Done</span>
|
||||
if (status === 'skipped') return <span className="ml-2 text-amber-400">⚠ Skipped</span>
|
||||
if (status === 'failed') return <span className="ml-2 text-red-400">✗ Failed</span>
|
||||
return <span className="ml-2 text-green-400">✓ Queued</span>
|
||||
}
|
||||
@@ -107,12 +119,19 @@ function UploadZone({ title, description, accept, endpoint, icon }) {
|
||||
{tasks.length > 0 && (
|
||||
<div className="mt-4 space-y-2">
|
||||
{tasks.map((task, i) => (
|
||||
<div key={i} className="flex items-center justify-between text-xs bg-gray-800 rounded-lg px-3 py-2">
|
||||
<span className="text-gray-300 truncate flex-1">{task.file}</span>
|
||||
{task.activity_tasks !== undefined && (
|
||||
<span className="text-gray-500 ml-2">{task.activity_tasks} activities queued</span>
|
||||
<div key={i} className="bg-gray-800 rounded-lg px-3 py-2">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-300 truncate flex-1">{task.file}</span>
|
||||
{task.activity_tasks !== undefined && (
|
||||
<span className="text-gray-500 ml-2">{task.activity_tasks} activities queued</span>
|
||||
)}
|
||||
<StatusBadge status={task.status} />
|
||||
</div>
|
||||
{task.reason && (task.status === 'skipped' || task.status === 'failed') && (
|
||||
<p className={`text-xs mt-1 ${task.status === 'skipped' ? 'text-amber-400/80' : 'text-red-400/80'}`}>
|
||||
{task.reason}
|
||||
</p>
|
||||
)}
|
||||
<StatusBadge status={task.status} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user