diff --git a/frontend/src/components/Summary.jsx b/frontend/src/components/Summary.jsx index e72c462..18d8c5a 100644 --- a/frontend/src/components/Summary.jsx +++ b/frontend/src/components/Summary.jsx @@ -26,6 +26,7 @@ function Summary({ startDate, endDate }) { const params = {} if (startDate) params.start = startDate.toISOString() if (endDate) params.end = endDate.toISOString() + params.tz_offset = -new Date().getTimezoneOffset() const response = await axios.get(`${API_BASE}/analytics`, { params }) setAnalytics(response.data) @@ -110,6 +111,8 @@ function Summary({ startDate, endDate }) { displayName: c.contact_name || formatPhoneNumber(c.address) || c.address })) + const truncate = (str, max) => str.length > max ? str.slice(0, max) + '…' : str + return (
@@ -201,14 +204,15 @@ function Summary({ startDate, endDate }) {
{topContactsData.length > 0 ? ( - + truncate(val, 22)} /> [value.toLocaleString(), 'Messages']} diff --git a/internal/database.go b/internal/database.go index 251b908..567cc8b 100644 --- a/internal/database.go +++ b/internal/database.go @@ -1046,7 +1046,7 @@ func SearchMessages(userDB *sql.DB, query string, limit int) ([]SearchResult, er } // GetAnalytics retrieves analytics data for the Summary tab -func GetAnalytics(userDB *sql.DB, startDate, endDate *time.Time, topN int) (*AnalyticsResponse, error) { +func GetAnalytics(userDB *sql.DB, startDate, endDate *time.Time, topN int, tzOffsetMinutes int) (*AnalyticsResponse, error) { analytics := &AnalyticsResponse{} // Build date filter @@ -1074,7 +1074,7 @@ func GetAnalytics(userDB *sql.DB, startDate, endDate *time.Time, topN int) (*Ana analytics.TopContacts = topContacts // 3. Get hourly distribution - hourly, err := getHourlyDistribution(userDB, dateFilter, args) + hourly, err := getHourlyDistribution(userDB, dateFilter, args, tzOffsetMinutes) if err != nil { return nil, err } @@ -1154,10 +1154,11 @@ func getTopContacts(userDB *sql.DB, dateFilter string, args []interface{}, limit return contacts, nil } -func getHourlyDistribution(userDB *sql.DB, dateFilter string, args []interface{}) ([]HourlyDistribution, error) { +func getHourlyDistribution(userDB *sql.DB, dateFilter string, args []interface{}, tzOffsetMinutes int) ([]HourlyDistribution, error) { + tzModifier := fmt.Sprintf("%+d minutes", tzOffsetMinutes) query := ` SELECT - CAST(strftime('%H', date, 'unixepoch', 'localtime') AS INTEGER) as hour, + CAST(strftime('%H', date, 'unixepoch', '` + tzModifier + `') AS INTEGER) as hour, COUNT(*) as count FROM messages WHERE record_type IN (1, 2) ` + dateFilter + ` diff --git a/internal/handlers.go b/internal/handlers.go index d7d89e8..8b39cad 100644 --- a/internal/handlers.go +++ b/internal/handlers.go @@ -586,7 +586,15 @@ func HandleAnalytics(c echo.Context) error { } } - analytics, err := GetAnalytics(userDB, startDate, endDate, topN) + // Timezone offset in minutes from UTC (e.g. -300 for UTC-5, 330 for UTC+5:30) + tzOffsetMinutes := 0 + if tzStr := c.QueryParam("tz_offset"); tzStr != "" { + if val, err := strconv.Atoi(tzStr); err == nil && val >= -840 && val <= 840 { + tzOffsetMinutes = val + } + } + + analytics, err := GetAnalytics(userDB, startDate, endDate, topN, tzOffsetMinutes) if err != nil { slog.Error("Error getting analytics", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{