diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx
index e8dff57b..f53387af 100644
--- a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx
+++ b/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx
@@ -12,6 +12,7 @@ import {
import { cn } from '@/utils/cn';
import { createProjectTitle } from '@/utils/title';
import {
+ CopyIcon,
LayoutPanelTopIcon,
MoreHorizontal,
PlusIcon,
@@ -122,6 +123,7 @@ function ReportItem({
endDate,
interval,
onDelete,
+ onDuplicate,
}: {
report: any;
organizationId: string;
@@ -131,6 +133,7 @@ function ReportItem({
endDate: any;
interval: any;
onDelete: (reportId: string) => void;
+ onDuplicate: (reportId: string) => void;
}) {
const router = useRouter();
const chartRange = report.range;
@@ -218,6 +221,15 @@ function ReportItem({
+ {
+ event.stopPropagation();
+ onDuplicate(report.id);
+ }}
+ >
+
+ Duplicate
+
{
reportDeletion.mutate({ reportId });
}}
+ onDuplicate={(reportId) => {
+ reportDuplicate.mutate({ reportId });
+ }}
/>
))}
diff --git a/packages/trpc/src/routers/report.ts b/packages/trpc/src/routers/report.ts
index 7d5f75b3..723803ea 100644
--- a/packages/trpc/src/routers/report.ts
+++ b/packages/trpc/src/routers/report.ts
@@ -135,6 +135,49 @@ export const reportRouter = createTRPCRouter({
},
});
}),
+ duplicate: protectedProcedure
+ .input(
+ z.object({
+ reportId: z.string(),
+ }),
+ )
+ .mutation(async ({ input: { reportId }, ctx }) => {
+ const report = await db.report.findUniqueOrThrow({
+ where: {
+ id: reportId,
+ },
+ });
+
+ const access = await getProjectAccess({
+ userId: ctx.session.userId,
+ projectId: report.projectId,
+ });
+
+ if (!access) {
+ throw TRPCAccessError('You do not have access to this project');
+ }
+
+ return db.report.create({
+ data: {
+ projectId: report.projectId,
+ dashboardId: report.dashboardId,
+ name: `Copy of ${report.name}`,
+ events: report.events!,
+ interval: report.interval,
+ breakdowns: report.breakdowns!,
+ chartType: report.chartType,
+ lineType: report.lineType,
+ range: report.range,
+ formula: report.formula,
+ previous: report.previous,
+ unit: report.unit,
+ criteria: report.criteria,
+ metric: report.metric,
+ funnelGroup: report.funnelGroup,
+ funnelWindow: report.funnelWindow,
+ },
+ });
+ }),
get: protectedProcedure
.input(
z.object({