api/frontend: fix report timezone bug causing empty results
Date strings formatted as YYYY-MM-DD were parsed by the API as UTC
midnight, so the query range ended at T00:00:00Z and excluded all
entries logged that calendar day. Fix by sending full local-timezone ISO
timestamps from the client (startOfDay/endOfDay) and updating the API
schema to accept them with z.iso.datetime({ offset: true })
Also bump the annotation truncation limit in the report PDFParents:
055fe404 file(s) changed
- api/src/reports/ReportDocument.tsx +1 -1
- api/src/reports/report.data.ts +2 -0
- api/src/reports/report.router.ts +4 -2
- frontend/app/report.tsx +3 -3
api/src/reports/ReportDocument.tsx
1 1
import React from 'react'
2 2
import {
3 3
Document,
4 4
Page,
5 5
View,
▸ 498 unchanged lines
504 504
format(entry.moment, 'd MMM', { locale: ptBR }),
505 505
T.moods[entry.mood] ?? entry.mood,
506 506
String(entry.anxietyLevel),
507 507
String(entry.stressLevel),
508 508
String(entry.energyLevel),
509 -
truncate(entry.annotation, 14),
509 +
truncate(entry.annotation?.trim(), 73),
510 510
]}
511 511
widths={[56, 78, 30, 30, 30, 75]}
512 512
colors={[
513 513
undefined,
514 514
MOOD_COLORS[entry.mood],
▸ 12 unchanged lines
527 527
)}
528 528
</Page>
529 529
</Document>
530 530
)
531 531
}
api/src/reports/report.data.ts
1 1
import { eachDayOfInterval, format } from 'date-fns'
2 2
import { prisma } from '@app/lib/prisma'
3 3
import {
4 4
BaseMoodOption,
5 5
MoodComponentOption,
▸ 51 unchanged lines
57 57
}),
58 58
])
59 59
60 60
const total = moods.length
61 61
62 +
console.log(`[report] found ${moods.length} mood entries from ${start} - ${end}`)
63 +
62 64
const moodCounts = Object.fromEntries(
63 65
MOOD_ORDER.map(m => [m, 0]),
64 66
) as Record<BaseMoodOption, number>
65 67
66 68
for (const m of moods) moodCounts[m.selectedMood]++
▸ 65 unchanged lines
132 134
dailyMoods,
133 135
topComponents,
134 136
checkinLog,
135 137
}
136 138
}
api/src/reports/report.router.ts
1 1
import { Router, Response, NextFunction } from 'express'
2 2
import React from 'react'
3 3
import { renderToBuffer } from '@react-pdf/renderer'
4 4
import { z } from 'zod'
5 5
import { requireAuth, AuthenticatedRequest } from '@app/middleware/auth'
▸ 2 unchanged lines
8 8
9 9
const router = Router()
10 10
11 11
const ReportSchema = z
12 12
.object({
13 -
periodStart: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
14 -
periodEnd: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
13 +
periodStart: z.iso.datetime({ offset: true }),
14 +
periodEnd: z.iso.datetime({ offset: true }),
15 15
})
16 16
.refine(
17 17
(d) => {
18 18
const start = new Date(d.periodStart)
19 19
const end = new Date(d.periodEnd)
▸ 6 unchanged lines
26 26
router.post(
27 27
'/',
28 28
requireAuth,
29 29
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
30 30
try {
31 +
req.log.info("Requirements: %s", req.body.periodStart, req.body.periodEnd)
32 +
31 33
const parsed = ReportSchema.safeParse(req.body)
32 34
if (!parsed.success) {
33 35
return res.status(400).json({ error: parsed.error.issues[0]?.message ?? 'Invalid request' })
34 36
}
35 37
▸ 20 unchanged lines
56 58
}
57 59
},
58 60
)
59 61
60 62
export default router
frontend/app/report.tsx
1 1
import React, { useState } from 'react'
2 2
import {
3 3
View,
4 4
Text,
5 5
TouchableOpacity,
6 6
StyleSheet,
7 7
ScrollView,
8 8
} from 'react-native'
9 9
import { Stack } from 'expo-router'
10 -
import { format, subDays } from 'date-fns'
10 +
import { format, subDays, startOfDay, endOfDay } from 'date-fns'
11 11
import { Paths, File } from 'expo-file-system'
12 12
import * as Sharing from 'expo-sharing'
13 13
import { SafeAreaView } from 'react-native-safe-area-context'
14 14
import { Button } from '@/components/ui/Button'
15 15
import { Section, SectionHeader } from '@/components/ui/Sections'
▸ 30 unchanged lines
46 46
47 47
async function handleGenerate() {
48 48
setLoading(true)
49 49
try {
50 50
const buffer = await apiClient.generateReport(
51 -
format(startDate, 'yyyy-MM-dd'),
52 -
format(endDate, 'yyyy-MM-dd'),
51 +
startOfDay(startDate).toISOString(),
52 +
endOfDay(endDate).toISOString(),
53 53
)
54 54
55 55
const fileName = `nexo-report-${format(startDate, 'yyyy-MM')}.pdf`
56 56
const file = new File(Paths.cache, fileName)
57 57
file.write(new Uint8Array(buffer))
▸ 137 unchanged lines
195 195
},
196 196
chipTextActive: {
197 197
color: Colors.light.background,
198 198
},
199 199
})