use range instead of dates across the web
This commit is contained in:
@@ -4,27 +4,29 @@ import { changeDateRanges, changeInterval } from "./reportSlice";
|
|||||||
import { Combobox } from "../ui/combobox";
|
import { Combobox } from "../ui/combobox";
|
||||||
import { type IInterval } from "@/types";
|
import { type IInterval } from "@/types";
|
||||||
import { timeRanges } from "@/utils/constants";
|
import { timeRanges } from "@/utils/constants";
|
||||||
import { entries } from "@/utils/object";
|
|
||||||
|
|
||||||
export function ReportDateRange() {
|
export function ReportDateRange() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const range = useSelector((state) => state.report.range);
|
||||||
const interval = useSelector((state) => state.report.interval);
|
const interval = useSelector((state) => state.report.interval);
|
||||||
const chartType = useSelector((state) => state.report.chartType);
|
const chartType = useSelector((state) => state.report.chartType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RadioGroup>
|
<RadioGroup>
|
||||||
{entries(timeRanges).map(([range, title]) => (
|
{timeRanges.map(item => {
|
||||||
|
return (
|
||||||
<RadioGroupItem
|
<RadioGroupItem
|
||||||
key={range}
|
key={item.range}
|
||||||
// active={range === interval}
|
active={item.range === range}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(changeDateRanges(range));
|
dispatch(changeDateRanges(item.range));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{item.title}
|
||||||
</RadioGroupItem>
|
</RadioGroupItem>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
{chartType === "linear" && (
|
{chartType === "linear" && (
|
||||||
<div className="w-full max-w-[200px]">
|
<div className="w-full max-w-[200px]">
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ type ReportLineChartProps = IChartInput
|
|||||||
|
|
||||||
export const Chart = withChartProivder(({
|
export const Chart = withChartProivder(({
|
||||||
interval,
|
interval,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
events,
|
events,
|
||||||
breakdowns,
|
breakdowns,
|
||||||
chartType,
|
chartType,
|
||||||
name,
|
name,
|
||||||
|
range,
|
||||||
}: ReportLineChartProps) => {
|
}: ReportLineChartProps) => {
|
||||||
const hasEmptyFilters = events.some((event) => event.filters.some((filter) => filter.value.length === 0));
|
const hasEmptyFilters = events.some((event) => event.filters.some((filter) => filter.value.length === 0));
|
||||||
const chart = api.chart.chart.useQuery(
|
const chart = api.chart.chart.useQuery(
|
||||||
{
|
{
|
||||||
interval,
|
interval,
|
||||||
chartType,
|
chartType,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
events,
|
events,
|
||||||
breakdowns,
|
breakdowns,
|
||||||
name,
|
name,
|
||||||
|
range,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
|
|||||||
@@ -4,22 +4,26 @@ import {
|
|||||||
type IChartEvent,
|
type IChartEvent,
|
||||||
type IInterval,
|
type IInterval,
|
||||||
type IChartType,
|
type IChartType,
|
||||||
|
type IChartRange,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { alphabetIds } from "@/utils/constants";
|
import { alphabetIds } from "@/utils/constants";
|
||||||
import { getDaysOldDate } from "@/utils/date";
|
|
||||||
import { type PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { type PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
type InitialState = IChartInput;
|
type InitialState = IChartInput & {
|
||||||
|
startDate: string | null;
|
||||||
|
endDate: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
// First approach: define the initial state using that type
|
// First approach: define the initial state using that type
|
||||||
const initialState: InitialState = {
|
const initialState: InitialState = {
|
||||||
name: "screen_view",
|
name: "screen_view",
|
||||||
chartType: "linear",
|
chartType: "linear",
|
||||||
startDate: getDaysOldDate(7).toISOString(),
|
|
||||||
endDate: new Date().toISOString(),
|
|
||||||
interval: "day",
|
interval: "day",
|
||||||
breakdowns: [],
|
breakdowns: [],
|
||||||
events: [],
|
events: [],
|
||||||
|
range: 30,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reportSlice = createSlice({
|
export const reportSlice = createSlice({
|
||||||
@@ -30,7 +34,11 @@ export const reportSlice = createSlice({
|
|||||||
return initialState
|
return initialState
|
||||||
},
|
},
|
||||||
setReport(state, action: PayloadAction<IChartInput>) {
|
setReport(state, action: PayloadAction<IChartInput>) {
|
||||||
return action.payload
|
return {
|
||||||
|
...action.payload,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// Events
|
// Events
|
||||||
addEvent: (state, action: PayloadAction<Omit<IChartEvent, "id">>) => {
|
addEvent: (state, action: PayloadAction<Omit<IChartEvent, "id">>) => {
|
||||||
@@ -107,21 +115,9 @@ export const reportSlice = createSlice({
|
|||||||
state.endDate = action.payload;
|
state.endDate = action.payload;
|
||||||
},
|
},
|
||||||
|
|
||||||
changeDateRanges: (state, action: PayloadAction<number | 'today'>) => {
|
changeDateRanges: (state, action: PayloadAction<IChartRange>) => {
|
||||||
if(action.payload === 'today') {
|
state.range = action.payload
|
||||||
const startDate = new Date()
|
if (action.payload === 0 || action.payload === 1) {
|
||||||
startDate.setHours(0,0,0,0)
|
|
||||||
|
|
||||||
state.startDate = startDate.toISOString();
|
|
||||||
state.endDate = new Date().toISOString();
|
|
||||||
state.interval = 'hour'
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
state.startDate = getDaysOldDate(action.payload).toISOString();
|
|
||||||
state.endDate = new Date().toISOString()
|
|
||||||
|
|
||||||
if (action.payload === 1) {
|
|
||||||
state.interval = "hour";
|
state.interval = "hour";
|
||||||
} else if (action.payload <= 30) {
|
} else if (action.payload <= 30) {
|
||||||
state.interval = "day";
|
state.interval = "day";
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { popModal } from ".";
|
|||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||||
|
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||||
|
|
||||||
const validator = z.object({
|
const validator = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
@@ -17,6 +18,7 @@ const validator = z.object({
|
|||||||
type IForm = z.infer<typeof validator>;
|
type IForm = z.infer<typeof validator>;
|
||||||
|
|
||||||
export default function AddProject() {
|
export default function AddProject() {
|
||||||
|
const params = useOrganizationParams()
|
||||||
const refetch = useRefetchActive()
|
const refetch = useRefetchActive()
|
||||||
const mutation = api.project.create.useMutation({
|
const mutation = api.project.create.useMutation({
|
||||||
onError: handleError,
|
onError: handleError,
|
||||||
@@ -41,7 +43,10 @@ export default function AddProject() {
|
|||||||
<ModalHeader title="Create project" />
|
<ModalHeader title="Create project" />
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit((values) => {
|
onSubmit={handleSubmit((values) => {
|
||||||
mutation.mutate(values);
|
mutation.mutate({
|
||||||
|
...values,
|
||||||
|
organizationSlug: params.organization,
|
||||||
|
});
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<InputWithLabel label="Name" placeholder="Name" {...register('name')} />
|
<InputWithLabel label="Name" placeholder="Name" {...register('name')} />
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const getServerSideProps = createServerSideProps()
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const params = useOrganizationParams();
|
const params = useOrganizationParams();
|
||||||
const query = api.dashboard.list.useQuery({
|
const query = api.dashboard.list.useQuery({
|
||||||
organizationSlug: params.organization,
|
|
||||||
projectSlug: params.project,
|
projectSlug: params.project,
|
||||||
}, {
|
}, {
|
||||||
enabled: Boolean(params.organization && params.project),
|
enabled: Boolean(params.organization && params.project),
|
||||||
|
|||||||
@@ -3,8 +3,13 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { last, pipe, sort, uniq } from "ramda";
|
import { last, pipe, sort, uniq } from "ramda";
|
||||||
import { toDots } from "@/utils/object";
|
import { toDots } from "@/utils/object";
|
||||||
import { zChartInput } from "@/utils/validation";
|
import { zChartInputWithDates } from "@/utils/validation";
|
||||||
import { type IChartInput, type IChartEvent } from "@/types";
|
import {
|
||||||
|
type IChartInputWithDates,
|
||||||
|
type IChartEvent,
|
||||||
|
type IChartRange,
|
||||||
|
} from "@/types";
|
||||||
|
import { getDaysOldDate } from "@/utils/date";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
@@ -89,18 +94,14 @@ export const chartRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
chart: protectedProcedure
|
chart: protectedProcedure
|
||||||
.input(zChartInput)
|
.input(zChartInputWithDates)
|
||||||
.query(async ({ input: { events, ...input } }) => {
|
.query(async ({ input: { events, ...input } }) => {
|
||||||
const startDate = input.startDate ?? new Date();
|
|
||||||
const endDate = input.endDate ?? new Date();
|
|
||||||
const series: Awaited<ReturnType<typeof getChartData>> = [];
|
const series: Awaited<ReturnType<typeof getChartData>> = [];
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
series.push(
|
series.push(
|
||||||
...(await getChartData({
|
...(await getChartData({
|
||||||
...input,
|
...input,
|
||||||
event,
|
event,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,14 +122,19 @@ export const chartRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events: Object.entries(series.reduce((acc, item) => {
|
events: Object.entries(
|
||||||
if(acc[item.event.id]) {
|
series.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
if (acc[item.event.id]) {
|
||||||
acc[item.event.id] += item.totalCount;
|
acc[item.event.id] += item.totalCount;
|
||||||
} else {
|
} else {
|
||||||
acc[item.event.id] = item.totalCount;
|
acc[item.event.id] = item.totalCount;
|
||||||
}
|
}
|
||||||
return acc
|
return acc;
|
||||||
}, {} as Record<typeof series[number]['event']['id'], number>)).map(([id, count]) => ({
|
},
|
||||||
|
{} as Record<(typeof series)[number]["event"]["id"], number>,
|
||||||
|
),
|
||||||
|
).map(([id, count]) => ({
|
||||||
count,
|
count,
|
||||||
...events.find((event) => event.id === id)!,
|
...events.find((event) => event.id === id)!,
|
||||||
})),
|
})),
|
||||||
@@ -184,16 +190,45 @@ function getTotalCount(arr: ResultItem[]) {
|
|||||||
return arr.reduce((acc, item) => acc + item.count, 0);
|
return arr.reduce((acc, item) => acc + item.count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDatesFromRange(range: IChartRange) {
|
||||||
|
if (range === 0) {
|
||||||
|
const startDate = new Date();
|
||||||
|
const endDate = new Date().toISOString();
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate: startDate.toISOString(),
|
||||||
|
endDate: endDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = getDaysOldDate(range).toISOString();
|
||||||
|
const endDate = new Date().toISOString();
|
||||||
|
return {
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function getChartData({
|
async function getChartData({
|
||||||
chartType,
|
chartType,
|
||||||
event,
|
event,
|
||||||
breakdowns,
|
breakdowns,
|
||||||
interval,
|
interval,
|
||||||
startDate,
|
range,
|
||||||
endDate,
|
startDate: _startDate,
|
||||||
|
endDate: _endDate,
|
||||||
}: {
|
}: {
|
||||||
event: IChartEvent;
|
event: IChartEvent;
|
||||||
} & Omit<IChartInput, "events">) {
|
} & Omit<IChartInputWithDates, "events">) {
|
||||||
|
const { startDate, endDate } =
|
||||||
|
_startDate && _endDate
|
||||||
|
? {
|
||||||
|
startDate: _startDate,
|
||||||
|
endDate: _endDate,
|
||||||
|
}
|
||||||
|
: getDatesFromRange(range);
|
||||||
|
|
||||||
const select = [];
|
const select = [];
|
||||||
const where = [];
|
const where = [];
|
||||||
const groupBy = [];
|
const groupBy = [];
|
||||||
@@ -362,6 +397,8 @@ function fillEmptySpotsInTimeline(
|
|||||||
const result = [];
|
const result = [];
|
||||||
const clonedStartDate = new Date(startDate);
|
const clonedStartDate = new Date(startDate);
|
||||||
const clonedEndDate = new Date(endDate);
|
const clonedEndDate = new Date(endDate);
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
if (interval === "hour") {
|
if (interval === "hour") {
|
||||||
clonedStartDate.setMinutes(0, 0, 0);
|
clonedStartDate.setMinutes(0, 0, 0);
|
||||||
clonedEndDate.setMinutes(0, 0, 0);
|
clonedEndDate.setMinutes(0, 0, 0);
|
||||||
@@ -370,7 +407,16 @@ function fillEmptySpotsInTimeline(
|
|||||||
clonedEndDate.setHours(2, 0, 0, 0);
|
clonedEndDate.setHours(2, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (clonedStartDate.getTime() <= clonedEndDate.getTime()) {
|
// Force if interval is month and the start date is the same month as today
|
||||||
|
const shouldForce = () =>
|
||||||
|
interval === "month" &&
|
||||||
|
clonedStartDate.getFullYear() === today.getFullYear() &&
|
||||||
|
clonedStartDate.getMonth() === today.getMonth();
|
||||||
|
|
||||||
|
while (
|
||||||
|
shouldForce() ||
|
||||||
|
clonedStartDate.getTime() <= clonedEndDate.getTime()
|
||||||
|
) {
|
||||||
const getYear = (date: Date) => date.getFullYear();
|
const getYear = (date: Date) => date.getFullYear();
|
||||||
const getMonth = (date: Date) => date.getMonth();
|
const getMonth = (date: Date) => date.getMonth();
|
||||||
const getDay = (date: Date) => date.getDate();
|
const getDay = (date: Date) => date.getDate();
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||||
import { zChartInput } from "@/utils/validation";
|
import { zChartInput } from "@/utils/validation";
|
||||||
import { dateDifferanceInDays, getDaysOldDate } from "@/utils/date";
|
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
type IChartInput,
|
type IChartInput,
|
||||||
type IChartBreakdown,
|
type IChartBreakdown,
|
||||||
type IChartEvent,
|
type IChartEvent,
|
||||||
type IChartEventFilter,
|
type IChartEventFilter,
|
||||||
|
type IChartRange,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { type Report as DbReport } from "@prisma/client";
|
import { type Report as DbReport } from "@prisma/client";
|
||||||
import { getProjectBySlug } from "@/server/services/project.service";
|
import { getProjectBySlug } from "@/server/services/project.service";
|
||||||
@@ -39,11 +39,10 @@ function transformReport(report: DbReport): IChartInput & { id: string } {
|
|||||||
id: report.id,
|
id: report.id,
|
||||||
events: (report.events as IChartEvent[]).map(transformEvent),
|
events: (report.events as IChartEvent[]).map(transformEvent),
|
||||||
breakdowns: report.breakdowns as IChartBreakdown[],
|
breakdowns: report.breakdowns as IChartBreakdown[],
|
||||||
startDate: getDaysOldDate(report.range).toISOString(),
|
|
||||||
endDate: new Date().toISOString(),
|
|
||||||
chartType: report.chart_type,
|
chartType: report.chart_type,
|
||||||
interval: report.interval,
|
interval: report.interval,
|
||||||
name: report.name || 'Untitled',
|
name: report.name || 'Untitled',
|
||||||
|
range: report.range as IChartRange ?? 30,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ export const reportRouter = createTRPCRouter({
|
|||||||
interval: report.interval,
|
interval: report.interval,
|
||||||
breakdowns: report.breakdowns,
|
breakdowns: report.breakdowns,
|
||||||
chart_type: report.chartType,
|
chart_type: report.chartType,
|
||||||
range: dateDifferanceInDays(new Date(report.endDate), new Date(report.startDate)),
|
range: report.range,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,7 +129,7 @@ export const reportRouter = createTRPCRouter({
|
|||||||
interval: report.interval,
|
interval: report.interval,
|
||||||
breakdowns: report.breakdowns,
|
breakdowns: report.breakdowns,
|
||||||
chart_type: report.chartType,
|
chart_type: report.chartType,
|
||||||
range: dateDifferanceInDays(new Date(report.endDate), new Date(report.startDate)),
|
range: report.range,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type RouterOutputs } from "@/utils/api";
|
import { type RouterOutputs } from "@/utils/api";
|
||||||
import { type zTimeInterval, type zChartBreakdown, type zChartEvent, type zChartInput, type zChartType } from "@/utils/validation";
|
import { type timeRanges } from "@/utils/constants";
|
||||||
|
import { type zTimeInterval, type zChartBreakdown, type zChartEvent, type zChartInput, type zChartType, type zChartInputWithDates } from "@/utils/validation";
|
||||||
import { type Client, type Project } from "@prisma/client";
|
import { type Client, type Project } from "@prisma/client";
|
||||||
import { type TooltipProps } from "recharts";
|
import { type TooltipProps } from "recharts";
|
||||||
import { type z } from "zod";
|
import { type z } from "zod";
|
||||||
@@ -7,6 +8,7 @@ import { type z } from "zod";
|
|||||||
export type HtmlProps<T> = React.DetailedHTMLProps<React.HTMLAttributes<T>, T>;
|
export type HtmlProps<T> = React.DetailedHTMLProps<React.HTMLAttributes<T>, T>;
|
||||||
|
|
||||||
export type IChartInput = z.infer<typeof zChartInput>
|
export type IChartInput = z.infer<typeof zChartInput>
|
||||||
|
export type IChartInputWithDates = z.infer<typeof zChartInputWithDates>
|
||||||
export type IChartEvent = z.infer<typeof zChartEvent>
|
export type IChartEvent = z.infer<typeof zChartEvent>
|
||||||
export type IChartEventFilter = IChartEvent['filters'][number]
|
export type IChartEventFilter = IChartEvent['filters'][number]
|
||||||
export type IChartEventFilterValue = IChartEvent['filters'][number]['value'][number]
|
export type IChartEventFilterValue = IChartEvent['filters'][number]['value'][number]
|
||||||
@@ -14,7 +16,7 @@ export type IChartBreakdown = z.infer<typeof zChartBreakdown>
|
|||||||
export type IInterval = z.infer<typeof zTimeInterval>
|
export type IInterval = z.infer<typeof zTimeInterval>
|
||||||
export type IChartType = z.infer<typeof zChartType>
|
export type IChartType = z.infer<typeof zChartType>
|
||||||
export type IChartData = RouterOutputs["chart"]["chart"];
|
export type IChartData = RouterOutputs["chart"]["chart"];
|
||||||
|
export type IChartRange = typeof timeRanges[number]['range'];
|
||||||
export type IToolTipProps<T> = Omit<TooltipProps<number, string>, 'payload'> & {
|
export type IToolTipProps<T> = Omit<TooltipProps<number, string>, 'payload'> & {
|
||||||
payload?: Array<T>
|
payload?: Array<T>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,26 @@ export const intervals = {
|
|||||||
month: "Month",
|
month: "Month",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const alphabetIds = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] as const;
|
export const alphabetIds = [
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"G",
|
||||||
|
"H",
|
||||||
|
"I",
|
||||||
|
"J",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const timeRanges = {
|
export const timeRanges = [
|
||||||
'today': 'Today',
|
{ range: 0, title: "Today" },
|
||||||
1: '24 hours',
|
{ range: 1, title: "24 hours" },
|
||||||
7: '7 days',
|
{ range: 7, title: "7 days" },
|
||||||
14: '14 days',
|
{ range: 14, title: "14 days" },
|
||||||
30: '30 days',
|
{ range: 30, title: "30 days" },
|
||||||
90: '3 months',
|
{ range: 90, title: "3 months" },
|
||||||
180: '6 months',
|
{ range: 180, title: "6 months" },
|
||||||
365: '1 year',
|
{ range: 365, title: "1 year" },
|
||||||
}
|
] as const
|
||||||
|
|||||||
@@ -16,9 +16,3 @@ export function toDots(
|
|||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function entries<K extends string | number | symbol, V>(
|
|
||||||
obj: Record<K, V>,
|
|
||||||
): [K, V][] {
|
|
||||||
return Object.entries(obj) as [K, V][];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { operators, chartTypes, intervals } from "./constants";
|
import { operators, chartTypes, intervals } from "./constants";
|
||||||
|
|
||||||
function objectToZodEnums<K extends string> ( obj: Record<K, any> ): [ K, ...K[] ] {
|
function objectToZodEnums<K extends string>(obj: Record<K, any>): [K, ...K[]] {
|
||||||
const [ firstKey, ...otherKeys ] = Object.keys( obj ) as K[]
|
const [firstKey, ...otherKeys] = Object.keys(obj) as K[];
|
||||||
return [ firstKey!, ...otherKeys ]
|
return [firstKey!, ...otherKeys];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const zChartEvent = z.object({
|
export const zChartEvent = z.object({
|
||||||
@@ -15,13 +15,7 @@ export const zChartEvent = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
operator: z.enum(objectToZodEnums(operators)),
|
operator: z.enum(objectToZodEnums(operators)),
|
||||||
value: z.array(
|
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
|
||||||
z
|
|
||||||
.string()
|
|
||||||
.or(z.number())
|
|
||||||
.or(z.boolean())
|
|
||||||
.or(z.null())
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -39,10 +33,22 @@ export const zTimeInterval = z.enum(objectToZodEnums(intervals));
|
|||||||
|
|
||||||
export const zChartInput = z.object({
|
export const zChartInput = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
startDate: z.string(),
|
|
||||||
endDate: z.string(),
|
|
||||||
chartType: zChartType,
|
chartType: zChartType,
|
||||||
interval: zTimeInterval,
|
interval: zTimeInterval,
|
||||||
events: zChartEvents,
|
events: zChartEvents,
|
||||||
breakdowns: zChartBreakdowns,
|
breakdowns: zChartBreakdowns,
|
||||||
|
range: z
|
||||||
|
.literal(0)
|
||||||
|
.or(z.literal(1))
|
||||||
|
.or(z.literal(7))
|
||||||
|
.or(z.literal(14))
|
||||||
|
.or(z.literal(30))
|
||||||
|
.or(z.literal(90))
|
||||||
|
.or(z.literal(180))
|
||||||
|
.or(z.literal(365)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zChartInputWithDates = zChartInput.extend({
|
||||||
|
startDate: z.string().nullish(),
|
||||||
|
endDate: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user