diff --git a/README.md b/README.md index 54b94978..fd47bf36 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,14 @@ Mixan is a simple analytics tool for logging events on web and react-native. My ## Whats left? +> Currently storing events on postgres but will probably move it to [clickhouse](https://clickhouse.com/) to speed up queries. Don't have any performance issues yet so will wait and see how well postgres can handle it. + +### GUI + +* [ ] Rename event label * [ ] Real time data (mostly screen_views stats) * [ ] Active users (5min, 10min, 30min) -* [ ] Save report to a specific dashboard +* [X] Save report to a specific dashboard * [ ] View events in a list * [ ] View profiles in a list * [ ] Invite users @@ -17,11 +22,17 @@ Mixan is a simple analytics tool for logging events on web and react-native. My * [ ] Pie * [ ] Area * [ ] Support funnels -* [ ] Create native sdk -* [ ] Create web sdk * [ ] Support multiple breakdowns * [ ] Aggregations (sum, average...) +### SDK + +* [ ] Store duration on screen view events (can be done in backend as well) +* [ ] Create native sdk + * [ ] Handle sessions +* [ ] Create web sdk + * [ ] Screen view function should take in title, path and parse query string (especially utm tags) + ## @mixan/sdk For pushing events diff --git a/apps/web/package.json b/apps/web/package.json index 8d8fb493..ce204fb3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -52,6 +52,7 @@ "react-syntax-highlighter": "^15.5.0", "react-virtualized-auto-sizer": "^1.0.20", "recharts": "^2.8.0", + "slugify": "^1.6.6", "superjson": "^1.13.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", diff --git a/apps/web/src/components/report/sidebar/ReportSave.tsx b/apps/web/src/components/report/sidebar/ReportSaveButton.tsx similarity index 75% rename from apps/web/src/components/report/sidebar/ReportSave.tsx rename to apps/web/src/components/report/sidebar/ReportSaveButton.tsx index c1431a81..c7cbdada 100644 --- a/apps/web/src/components/report/sidebar/ReportSave.tsx +++ b/apps/web/src/components/report/sidebar/ReportSaveButton.tsx @@ -2,10 +2,10 @@ import { Button } from "@/components/ui/button"; import { useReportId } from "../hooks/useReportId"; import { api } from "@/utils/api"; import { useSelector } from "@/redux"; +import { pushModal } from "@/modals"; -export function ReportSave() { +export function ReportSaveButton() { const { reportId } = useReportId(); - const save = api.report.save.useMutation(); const update = api.report.update.useMutation(); const report = useSelector((state) => state.report); @@ -22,11 +22,9 @@ export function ReportSave() { return ( } + {children ?? ( + + )} - - Nothing selected + + {typeof onCreate === "function" && search ? ( + + + + ) : ( + Nothing selected + )} {items.map((item) => ( >(({ className, ...props }, ref) => ( - + )) TableHeader.displayName = "TableHeader" @@ -55,7 +55,7 @@ const TableRow = React.forwardRef< { return (
- + { diff --git a/apps/web/src/modals/Modal/Container.tsx b/apps/web/src/modals/Modal/Container.tsx index 1c8cb33e..bde38921 100644 --- a/apps/web/src/modals/Modal/Container.tsx +++ b/apps/web/src/modals/Modal/Container.tsx @@ -20,7 +20,7 @@ type ModalHeaderProps = { export function ModalHeader({ title }: ModalHeaderProps) { return ( -
+
{title}
+ + + + + ); +} diff --git a/apps/web/src/modals/index.tsx b/apps/web/src/modals/index.tsx index 7f4170cf..c3fbeb84 100644 --- a/apps/web/src/modals/index.tsx +++ b/apps/web/src/modals/index.tsx @@ -28,6 +28,9 @@ const modals = { Confirm: dynamic(() => import('./Confirm'), { loading: Loading, }), + SaveReport: dynamic(() => import('./SaveReport'), { + loading: Loading, + }), }; const emitter = mitt<{ diff --git a/apps/web/src/server/api/routers/dashboard.ts b/apps/web/src/server/api/routers/dashboard.ts index 092bdbfa..f974deba 100644 --- a/apps/web/src/server/api/routers/dashboard.ts +++ b/apps/web/src/server/api/routers/dashboard.ts @@ -3,22 +3,48 @@ import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { db } from "@/server/db"; import { getProjectBySlug } from "@/server/services/project.service"; - - +import { slug } from "@/utils/slug"; export const dashboardRouter = createTRPCRouter({ list: protectedProcedure .input( - z.object({ - organizationSlug: z.string(), - projectSlug: z.string(), - }), + z + .object({ + projectSlug: z.string(), + }) + .or( + z.object({ + projectId: z.string(), + }), + ), ) - .query(async ({ input: { projectSlug } }) => { - const project = await getProjectBySlug(projectSlug) + .query(async ({ input }) => { + let projectId = null; + if ("projectId" in input) { + projectId = input.projectId; + } else { + projectId = (await getProjectBySlug(input.projectSlug)).id; + } + return db.dashboard.findMany({ where: { - project_id: project.id, + project_id: projectId, + }, + }); + }), + create: protectedProcedure + .input( + z.object({ + name: z.string(), + projectId: z.string(), + }), + ) + .mutation(async ({ input: { projectId, name } }) => { + return db.dashboard.create({ + data: { + slug: slug(name), + project_id: projectId, + name, }, }); }), diff --git a/apps/web/src/server/api/routers/project.ts b/apps/web/src/server/api/routers/project.ts index a6c98d77..b46cac49 100644 --- a/apps/web/src/server/api/routers/project.ts +++ b/apps/web/src/server/api/routers/project.ts @@ -6,17 +6,19 @@ import { getOrganizationBySlug } from "@/server/services/organization.service"; export const projectRouter = createTRPCRouter({ list: protectedProcedure - .input(z.object({ - organizationSlug: z.string() - })) - .query(async ({ input }) => { - const organization = await getOrganizationBySlug(input.organizationSlug) - return db.project.findMany({ - where: { - organization_id: organization.id, - }, - }); - }), + .input( + z.object({ + organizationSlug: z.string(), + }), + ) + .query(async ({ input }) => { + const organization = await getOrganizationBySlug(input.organizationSlug); + return db.project.findMany({ + where: { + organization_id: organization.id, + }, + }); + }), get: protectedProcedure .input( z.object({ @@ -27,7 +29,6 @@ export const projectRouter = createTRPCRouter({ return db.project.findUniqueOrThrow({ where: { id: input.id, - organization_id: "d433c614-69f9-443a-8961-92a662869929", }, }); }), @@ -42,7 +43,6 @@ export const projectRouter = createTRPCRouter({ return db.project.update({ where: { id: input.id, - organization_id: "d433c614-69f9-443a-8961-92a662869929", }, data: { name: input.name, @@ -53,17 +53,19 @@ export const projectRouter = createTRPCRouter({ .input( z.object({ name: z.string(), + organizationSlug: z.string(), }), ) - .mutation(({ input }) => { + .mutation(async ({ input }) => { + const organization = await getOrganizationBySlug(input.organizationSlug); return db.project.create({ data: { - organization_id: "d433c614-69f9-443a-8961-92a662869929", + organization_id: organization.id, name: input.name, }, }); }), - remove: protectedProcedure + remove: protectedProcedure .input( z.object({ id: z.string(), @@ -73,9 +75,8 @@ export const projectRouter = createTRPCRouter({ await db.project.delete({ where: { id: input.id, - organization_id: "d433c614-69f9-443a-8961-92a662869929", }, }); - return true + return true; }), }); diff --git a/apps/web/src/utils/slug.ts b/apps/web/src/utils/slug.ts new file mode 100644 index 00000000..59625e54 --- /dev/null +++ b/apps/web/src/utils/slug.ts @@ -0,0 +1,18 @@ +import _slugify from 'slugify' + +const slugify = (str: string) => { + return _slugify( + str + .replace('å', 'a') + .replace('ä', 'a') + .replace('ö', 'o') + .replace('Å', 'A') + .replace('Ä', 'A') + .replace('Ö', 'O'), + { lower: true, strict: true, trim: true }, + ) +} + +export function slug(str: string): string { + return slugify(str) +} diff --git a/bun.lockb b/bun.lockb index b2bea5c7..268e2458 100755 Binary files a/bun.lockb and b/bun.lockb differ