Skip to content

Commit 4ef2d3e

Browse files
committed
Format date-only labels without timezone shift
Use formatDateOnlyLabel across dashboard tables to avoid calendar-day shifts when the API returns date-only strings. Updated ProposicoesTable, TaquigraficasTable and VotacoesTable to use the helper and added the import in VotacoesTable. Reworked formatDateOnlyLabel to detect YYYY-MM-DD via a regex and construct a local Date(year, month-1, day) so the displayed day isn't affected by timezone parsing; it still falls back to parsing full ISO-like values and returns invalid inputs unchanged. Added unit tests (filterUtils.test.ts) covering date-only values, ISO-like values, and invalid inputs.
1 parent caf283e commit 4ef2d3e

5 files changed

Lines changed: 43 additions & 4 deletions

File tree

ui/src/components/dashboard/ProposicoesTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ export function ProposicoesTable({ limit = 10, parliamentarianId }: ProposicoesT
375375
>
376376
<TableCell className="text-sm text-muted-foreground">
377377
{proposicao.dataApresentacao
378-
? new Date(proposicao.dataApresentacao).toLocaleDateString('pt-BR')
378+
? formatDateOnlyLabel(proposicao.dataApresentacao)
379379
: '—'}
380380
</TableCell>
381381
<TableCell className="font-medium">

ui/src/components/dashboard/TaquigraficasTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ export function TaquigraficasTable({ limit = 20, parliamentarianId }: Taquigrafi
333333
].join(' ')}
334334
>
335335
<TableCell className="text-sm text-muted-foreground">
336-
{speech.date ? new Date(speech.date).toLocaleDateString('pt-BR') : '—'}
336+
{speech.date ? formatDateOnlyLabel(speech.date) : '—'}
337337
</TableCell>
338338
{/* <TableCell className="text-sm">{speech.session_number ?? '—'}</TableCell> */}
339339
<TableCell className="text-sm">{speech.type ?? '—'}</TableCell>

ui/src/components/dashboard/VotacoesTable.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { listRollCallVotes, type RollCallVoteSortBy, type SortOrder } from '@/ap
44
import { mapRollCallVoteOutToVotacao } from '@/api/mappers';
55
import {
66
buildFilterChips,
7+
formatDateOnlyLabel,
78
formatDateTimeLabel,
89
toIsoOrUndefined,
910
useDraftAppliedFilters,
@@ -316,7 +317,7 @@ export function VotacoesTable({ limit = 10, parliamentarianId }: VotacoesTablePr
316317
{votacao.proposicao}
317318
</TableCell>
318319
<TableCell className="text-sm text-muted-foreground">
319-
{votacao.data ? new Date(votacao.data).toLocaleDateString('pt-BR') : '—'}
320+
{votacao.data ? formatDateOnlyLabel(votacao.data) : '—'}
320321
</TableCell>
321322
<TableCell>
322323
<div className="flex items-center gap-2 whitespace-nowrap">
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { formatDateOnlyLabel } from './filterUtils';
4+
5+
describe('formatDateOnlyLabel', () => {
6+
it('formats date-only API values without shifting the calendar day', () => {
7+
expect(formatDateOnlyLabel('2026-05-25')).toBe('25/05/2026');
8+
expect(formatDateOnlyLabel('2026-05-18')).toBe('18/05/2026');
9+
});
10+
11+
it('uses the calendar date portion from ISO-like values', () => {
12+
expect(formatDateOnlyLabel('2026-05-25T23:30:00Z')).toBe('25/05/2026');
13+
});
14+
15+
it('returns invalid values unchanged', () => {
16+
expect(formatDateOnlyLabel('not-a-date')).toBe('not-a-date');
17+
});
18+
});

ui/src/components/dashboard/filterUtils.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useState } from 'react';
22

3+
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})/;
4+
35
type StringFilterShape<T> = {
46
[K in keyof T]: string;
57
};
@@ -26,7 +28,25 @@ export function formatDateTimeLabel(value: string): string {
2628

2729
export function formatDateOnlyLabel(value: string): string {
2830
if (!value) return '';
29-
const date = new Date(`${value}T00:00:00`);
31+
32+
const dateOnlyMatch = DATE_ONLY_PATTERN.exec(value);
33+
if (dateOnlyMatch) {
34+
const [, year, month, day] = dateOnlyMatch;
35+
const parsedYear = Number(year);
36+
const parsedMonth = Number(month) - 1;
37+
const parsedDay = Number(day);
38+
const localDate = new Date(parsedYear, parsedMonth, parsedDay);
39+
40+
if (
41+
localDate.getFullYear() === parsedYear &&
42+
localDate.getMonth() === parsedMonth &&
43+
localDate.getDate() === parsedDay
44+
) {
45+
return localDate.toLocaleDateString('pt-BR');
46+
}
47+
}
48+
49+
const date = new Date(value);
3050
if (Number.isNaN(date.getTime())) return value;
3151
return date.toLocaleDateString('pt-BR');
3252
}

0 commit comments

Comments
 (0)