Skip to content

Commit 0db770f

Browse files
committed
fix(journey): clamp limits and localize status
Cap journey list limits at 100 with safe defaults and tests. Localize status labels and inherit theme-aware icon colors.
1 parent 6e5f05e commit 0db770f

4 files changed

Lines changed: 66 additions & 6 deletions

File tree

internal/api/journey/handler.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
111111
limit := 20
112112
if s := r.URL.Query().Get("limit"); s != "" {
113113
if n, err := strconv.Atoi(s); err == nil {
114-
limit = n
114+
limit = clampListLimit(n)
115115
}
116116
}
117117
sessions, err := h.store.List(r.Context(), vehicleID, status, limit)
@@ -265,6 +265,16 @@ func (h *Handler) SavePlan(w http.ResponseWriter, r *http.Request) {
265265
httpx.WriteJSON(w, http.StatusCreated, pv)
266266
}
267267

268+
func clampListLimit(n int) int {
269+
if n <= 0 {
270+
return 20
271+
}
272+
if n > 100 {
273+
return 100
274+
}
275+
return n
276+
}
277+
268278
func sessionIDParam(r *http.Request) (int64, error) {
269279
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
270280
if err != nil || id <= 0 {

internal/api/journey/handler_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,26 @@ func TestListFiltersByVehicleAndStatus(t *testing.T) {
197197
}
198198
}
199199

200+
func TestClampListLimit(t *testing.T) {
201+
t.Parallel()
202+
cases := []struct {
203+
in, want int
204+
}{
205+
{0, 20},
206+
{-5, 20},
207+
{1, 1},
208+
{20, 20},
209+
{100, 100},
210+
{101, 100},
211+
{10_000, 100},
212+
}
213+
for _, tc := range cases {
214+
if got := clampListLimit(tc.in); got != tc.want {
215+
t.Fatalf("clampListLimit(%d) = %d, want %d", tc.in, got, tc.want)
216+
}
217+
}
218+
}
219+
200220
func TestListValidation(t *testing.T) {
201221
h := NewHandler(newFakeStore())
202222
for _, url := range []string{

web/src/features/trips/components/JourneyPanel.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ import { formatDateTime } from '@/lib/dateFormat';
1818

1919
const STATUS_FILTERS = ['', 'planned', 'active', 'paused', 'completed', 'aborted'] as const;
2020

21+
const STATUS_LABEL_KEYS: Record<Exclude<(typeof STATUS_FILTERS)[number], ''>, string> = {
22+
planned: 'journey.status.planned',
23+
active: 'journey.status.active',
24+
paused: 'journey.status.paused',
25+
completed: 'journey.status.completed',
26+
aborted: 'journey.status.aborted',
27+
};
28+
29+
const STATUS_LABEL_DEFAULTS: Record<Exclude<(typeof STATUS_FILTERS)[number], ''>, string> = {
30+
planned: 'Planned',
31+
active: 'Active',
32+
paused: 'Paused',
33+
completed: 'Completed',
34+
aborted: 'Aborted',
35+
};
36+
2137
function statusVariant(status: JourneyStatus) {
2238
switch (status) {
2339
case 'active':
@@ -94,6 +110,9 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
94110
const create = useCreateJourney();
95111
const transition = useTransitionJourney();
96112

113+
const statusLabel = (status: JourneyStatus) =>
114+
t(STATUS_LABEL_KEYS[status], STATUS_LABEL_DEFAULTS[status]);
115+
97116
const submitCreate = (event: FormEvent) => {
98117
event.preventDefault();
99118
if (vehicleId == null || !draft.name.trim()) return;
@@ -134,7 +153,9 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
134153
{
135154
key: 'status',
136155
header: t('journey.col.status', 'Status'),
137-
render: (row) => <Badge variant={statusVariant(row.status)}>{row.status}</Badge>,
156+
render: (row) => (
157+
<Badge variant={statusVariant(row.status)}>{statusLabel(row.status)}</Badge>
158+
),
138159
},
139160
{
140161
key: 'plan',
@@ -184,7 +205,7 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
184205
value={statusFilter}
185206
options={STATUS_FILTERS.map((s) => ({
186207
value: s,
187-
label: s === '' ? t('journey.filter.all', 'All') : s,
208+
label: s === '' ? t('journey.filter.all', 'All') : statusLabel(s),
188209
}))}
189210
onChange={(event) => setStatusFilter(event.target.value)}
190211
/>
@@ -259,7 +280,7 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
259280

260281
<GlassPanel className="p-4 sm:p-5">
261282
<PanelTitle className="mb-1 flex items-center gap-2">
262-
<Icons.flag className="h-4 w-4 text-cyan-300" aria-hidden="true" />
283+
<Icons.flag className="h-4 w-4" aria-hidden="true" />
263284
{detail ? detail.session.name : t('journey.detail.title', 'Journey detail')}
264285
</PanelTitle>
265286
{detailQuery.isLoading || detail == null ? (
@@ -276,7 +297,9 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
276297
) : (
277298
<div className="space-y-4">
278299
<div className="flex flex-wrap items-center gap-2">
279-
<Badge variant={statusVariant(detail.session.status)}>{detail.session.status}</Badge>
300+
<Badge variant={statusVariant(detail.session.status)}>
301+
{statusLabel(detail.session.status)}
302+
</Badge>
280303
{detail.next_statuses.map((next) => {
281304
const action = transitionAction(next, detail.session.status);
282305
return (
@@ -295,7 +318,7 @@ export function JourneyPanel({ vehicleId }: { vehicleId: number | null }) {
295318
</div>
296319
<div>
297320
<Text as="p" variant="label" className="mb-2 flex items-center gap-2">
298-
<Icons.package className="h-4 w-4 text-[var(--text-muted)]" aria-hidden="true" />
321+
<Icons.package className="h-4 w-4" aria-hidden="true" />
299322
{t('journey.plans.title', 'Plan versions')}
300323
</Text>
301324
{detail.plans.length === 0 ? (

web/src/i18n/en/locale-trips.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@
131131
"loading": "Loading journeys…",
132132
"empty": "No journeys yet. Plan one above and it will live here from planning to debrief."
133133
},
134+
"status": {
135+
"planned": "Planned",
136+
"active": "Active",
137+
"paused": "Paused",
138+
"completed": "Completed",
139+
"aborted": "Aborted"
140+
},
134141
"filter": {
135142
"status": "Filter by status",
136143
"all": "All"

0 commit comments

Comments
 (0)