Merge pull request #213 from SirTenzin/fix/event-timezone
fix: 🐛 timezones not localised
This commit is contained in:
@@ -1,195 +1,213 @@
|
|||||||
|
import type { AgChartOptions, FormatterParams } from "ag-charts-community";
|
||||||
|
import { AgCharts } from "ag-charts-react";
|
||||||
import {
|
import {
|
||||||
AllCommunityModule,
|
AllCommunityModule,
|
||||||
ColDef,
|
type ColDef,
|
||||||
ModuleRegistry,
|
ModuleRegistry,
|
||||||
|
type PaginationChangedEvent,
|
||||||
|
type RowDataUpdatedEvent,
|
||||||
ValidationModule,
|
ValidationModule,
|
||||||
ValueFormatterParams,
|
type ValueFormatterParams,
|
||||||
RowDataUpdatedEvent,
|
|
||||||
PaginationChangedEvent,
|
|
||||||
} from "ag-grid-community";
|
} from "ag-grid-community";
|
||||||
import { AgChartOptions, FormatterParams } from "ag-charts-community";
|
|
||||||
import { AgCharts } from "ag-charts-react";
|
|
||||||
|
|
||||||
// Register all Community features
|
// Register all Community features
|
||||||
|
|
||||||
import { AgGridReact } from "ag-grid-react";
|
import { AgGridReact } from "ag-grid-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { IRow, Row, autumnTheme, paginationOptions } from "./components/AGGrid";
|
|
||||||
import { useAnalyticsContext } from "./AnalyticsContext";
|
import { useAnalyticsContext } from "./AnalyticsContext";
|
||||||
|
import {
|
||||||
|
autumnTheme,
|
||||||
|
type IRow,
|
||||||
|
paginationOptions,
|
||||||
|
type Row,
|
||||||
|
} from "./components/AGGrid";
|
||||||
import { RowClickDialog } from "./components/RowClickDialog";
|
import { RowClickDialog } from "./components/RowClickDialog";
|
||||||
|
|
||||||
|
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
// Helper function to parse UTC timestamps from the backend
|
||||||
|
const parseUTCTimestamp = (timestamp: string): Date => {
|
||||||
|
// If the timestamp doesn't end with 'Z' or have timezone info, assume it's UTC
|
||||||
|
if (!timestamp.includes('Z') && !timestamp.includes('+') && !timestamp.includes('-', 10)) {
|
||||||
|
// Add 'Z' to indicate UTC if it's missing
|
||||||
|
return new Date(timestamp + (timestamp.includes('T') ? 'Z' : ' UTC'));
|
||||||
|
}
|
||||||
|
return new Date(timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
const dateFormatter = new Intl.DateTimeFormat(navigator.language || "en-US", {
|
const dateFormatter = new Intl.DateTimeFormat(navigator.language || "en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
|
timeZone: userTimeZone,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hourFormatter = new Intl.DateTimeFormat(navigator.language || "en-US", {
|
const hourFormatter = new Intl.DateTimeFormat(navigator.language || "en-US", {
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
|
timeZone: userTimeZone,
|
||||||
});
|
});
|
||||||
|
|
||||||
const timestampFormatter = new Intl.DateTimeFormat(
|
const timestampFormatter = new Intl.DateTimeFormat(
|
||||||
navigator.language || "en-US",
|
navigator.language || "en-US",
|
||||||
{
|
{
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
second: "numeric",
|
second: "numeric",
|
||||||
}
|
timeZone: userTimeZone,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export function EventsBarChart({
|
export function EventsBarChart({
|
||||||
data,
|
data,
|
||||||
chartConfig,
|
chartConfig,
|
||||||
}: {
|
}: {
|
||||||
data: {
|
data: {
|
||||||
meta: any[];
|
meta: any[];
|
||||||
rows: number;
|
rows: number;
|
||||||
data: Row[];
|
data: Row[];
|
||||||
};
|
};
|
||||||
chartConfig: any;
|
chartConfig: any;
|
||||||
}) {
|
}) {
|
||||||
const { selectedInterval } = useAnalyticsContext();
|
const { selectedInterval } = useAnalyticsContext();
|
||||||
const [options, setOptions] = useState<AgChartOptions>({
|
const [options, setOptions] = useState<AgChartOptions>({
|
||||||
data: data.data,
|
data: data.data,
|
||||||
series: chartConfig,
|
series: chartConfig,
|
||||||
theme: {
|
theme: {
|
||||||
params: {
|
params: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
googleFont: "Inter",
|
googleFont: "Inter",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
palette: {
|
palette: {
|
||||||
fills: [
|
fills: [
|
||||||
"#9c5aff",
|
"#9c5aff",
|
||||||
"#a97eff",
|
"#a97eff",
|
||||||
"#8268ff",
|
"#8268ff",
|
||||||
"#7571ff",
|
"#7571ff",
|
||||||
"#687aff",
|
"#687aff",
|
||||||
"#5b83ff",
|
"#5b83ff",
|
||||||
"#4e8cff",
|
"#4e8cff",
|
||||||
"#4195ff",
|
"#4195ff",
|
||||||
"#349eff",
|
"#349eff",
|
||||||
"#27a7ff",
|
"#27a7ff",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
fill: "#fafaf9",
|
fill: "#fafaf9",
|
||||||
},
|
},
|
||||||
axes: [
|
axes: [
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
position: "bottom",
|
position: "bottom",
|
||||||
label: {
|
label: {
|
||||||
color: "#52525b",
|
color: "#52525b",
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "number",
|
type: "number",
|
||||||
position: "left",
|
position: "left",
|
||||||
label: {
|
label: {
|
||||||
color: "#52525b",
|
color: "#52525b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
formatter: {
|
formatter: {
|
||||||
x: (params: FormatterParams<any, unknown>) => {
|
x: (params: FormatterParams<any, unknown>) => {
|
||||||
if (params.type !== "category") return;
|
if (params.type !== "category") return;
|
||||||
return selectedInterval === "24h"
|
return selectedInterval === "24h"
|
||||||
? hourFormatter.format(new Date(params.value as string))
|
? hourFormatter.format(parseUTCTimestamp(params.value as string))
|
||||||
: dateFormatter.format(new Date(params.value as string));
|
: dateFormatter.format(parseUTCTimestamp(params.value as string));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = data.data;
|
useEffect(() => {
|
||||||
|
setOptions((prevOptions) => ({
|
||||||
|
...prevOptions,
|
||||||
|
data: data.data,
|
||||||
|
series: chartConfig,
|
||||||
|
}));
|
||||||
|
}, [chartConfig, data]);
|
||||||
|
|
||||||
useEffect(() => {
|
return <AgCharts options={options} className="h-full w-full" />;
|
||||||
setOptions({
|
|
||||||
...options,
|
|
||||||
data: data.data,
|
|
||||||
series: chartConfig,
|
|
||||||
});
|
|
||||||
}, [chartConfig, data]);
|
|
||||||
|
|
||||||
return <AgCharts options={options} className="h-full w-full" />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EventsAGGrid({ data }: { data: any }) {
|
export function EventsAGGrid({ data }: { data: any }) {
|
||||||
const [rowData, setRowData] = useState<IRow[]>([]);
|
const [rowData, setRowData] = useState<IRow[]>([]);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [event, setEvent] = useState<IRow | null>(null);
|
const [event, setEvent] = useState<IRow | null>(null);
|
||||||
const [colDefs] = useState<ColDef<IRow>[]>([
|
const [colDefs] = useState<ColDef<IRow>[]>([
|
||||||
{
|
{
|
||||||
field: "timestamp",
|
field: "timestamp",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
valueFormatter: (params: ValueFormatterParams<any, unknown>) => {
|
valueFormatter: (params: ValueFormatterParams<any, unknown>) => {
|
||||||
return timestampFormatter.format(new Date(params.value as string));
|
return timestampFormatter.format(parseUTCTimestamp(params.value as string));
|
||||||
},
|
},
|
||||||
cellStyle: {
|
cellStyle: {
|
||||||
paddingLeft: "2.5rem",
|
paddingLeft: "2.5rem",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
},
|
},
|
||||||
headerStyle: {
|
headerStyle: {
|
||||||
paddingLeft: "2.5rem",
|
paddingLeft: "2.5rem",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ field: "event_name", flex: 1, cellStyle: { fontWeight: "normal" } },
|
{ field: "event_name", flex: 1, cellStyle: { fontWeight: "normal" } },
|
||||||
{ field: "value", flex: 0, cellStyle: { fontWeight: "normal" } },
|
{ field: "value", flex: 0, cellStyle: { fontWeight: "normal" } },
|
||||||
{ field: "properties", flex: 1, cellStyle: { fontWeight: "normal" } },
|
{ field: "properties", flex: 1, cellStyle: { fontWeight: "normal" } },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ModuleRegistry.registerModules([AllCommunityModule, ValidationModule]);
|
ModuleRegistry.registerModules([AllCommunityModule, ValidationModule]);
|
||||||
|
|
||||||
const { gridRef, pageSize, setTotalRows, setTotalPages, setCurrentPage } =
|
const { gridRef, pageSize, setTotalRows, setTotalPages, setCurrentPage } =
|
||||||
useAnalyticsContext();
|
useAnalyticsContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRowData(data.data);
|
setRowData(data.data);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full overflow-hidden">
|
<div className="w-full h-full overflow-hidden">
|
||||||
<AgGridReact
|
<AgGridReact
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
rowData={rowData}
|
rowData={rowData}
|
||||||
columnDefs={colDefs as any}
|
columnDefs={colDefs as any}
|
||||||
domLayout="normal"
|
domLayout="normal"
|
||||||
pagination={true}
|
pagination={true}
|
||||||
paginationPageSize={pageSize}
|
paginationPageSize={pageSize}
|
||||||
paginationPageSizeSelector={paginationOptions}
|
paginationPageSizeSelector={paginationOptions}
|
||||||
suppressPaginationPanel={true}
|
suppressPaginationPanel={true}
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
theme={autumnTheme}
|
theme={autumnTheme}
|
||||||
defaultColDef={{
|
defaultColDef={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filter: true,
|
filter: true,
|
||||||
}}
|
}}
|
||||||
onRowClicked={(event) => {
|
onRowClicked={(event) => {
|
||||||
setEvent(event.data as IRow);
|
setEvent(event.data as IRow);
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}}
|
}}
|
||||||
onRowDataUpdated={(event: RowDataUpdatedEvent) => {
|
onRowDataUpdated={(event: RowDataUpdatedEvent) => {
|
||||||
setTotalRows(event.api.paginationGetRowCount());
|
setTotalRows(event.api.paginationGetRowCount());
|
||||||
}}
|
}}
|
||||||
onPaginationChanged={(event: PaginationChangedEvent) => {
|
onPaginationChanged={(event: PaginationChangedEvent) => {
|
||||||
setTotalPages(event.api.paginationGetTotalPages());
|
setTotalPages(event.api.paginationGetTotalPages());
|
||||||
setCurrentPage(event.api.paginationGetCurrentPage() + 1);
|
setCurrentPage(event.api.paginationGetCurrentPage() + 1);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{event && (
|
{event && (
|
||||||
<RowClickDialog event={event} isOpen={isOpen} setIsOpen={setIsOpen} />
|
<RowClickDialog event={event} isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user