diff --git a/components/layout/LayoutFormBasic.tsx b/components/layout/LayoutFormBasic.tsx index 1bb3c5e2d4..a397c499c1 100644 --- a/components/layout/LayoutFormBasic.tsx +++ b/components/layout/LayoutFormBasic.tsx @@ -39,11 +39,7 @@ export default function LayoutShare({ title, formId, currentStep, children }) { {/* Main content */}
-
- {/* Replace with your content */} - {children} - {/* /End replace */} -
+
{children}
diff --git a/components/results/ResultsDashboard.tsx b/components/results/ResultsDashboard.tsx index 82fb0af17c..8313f14368 100644 --- a/components/results/ResultsDashboard.tsx +++ b/components/results/ResultsDashboard.tsx @@ -1,3 +1,78 @@ -export default function ResultsDashboard({}) { - return

Dashboard

; +import { ClockIcon, InboxIcon, UsersIcon } from "@heroicons/react/outline"; +import { useMemo } from "react"; +import { + getSubmissionAnalytics, + useSubmissionSessions, +} from "../../lib/submissionSessions"; +import { timeSince } from "../../lib/utils"; + +export default function ResultsDashboard({ formId }) { + const { submissionSessions, isLoadingSubmissionSessions } = + useSubmissionSessions(formId); + + const analytics = useMemo(() => { + if (!isLoadingSubmissionSessions) { + return getSubmissionAnalytics(submissionSessions); + } + }, [isLoadingSubmissionSessions]); + + const stats = useMemo(() => { + if (analytics) { + return [ + { + id: "uniqueUsers", + name: "Unique Users", + stat: analytics.uniqueUsers, + icon: UsersIcon, + }, + { + id: "totalSubmissions", + name: "Total Submissions", + stat: analytics.totalSubmissions, + icon: InboxIcon, + }, + { + id: "uniqueUsers", + name: "Last Submission", + stat: timeSince(analytics.lastSubmissionAt) || "-", + icon: ClockIcon, + }, + ]; + } + }, [analytics]); + return ( +
+
+
+ {stats ? ( +
+ {stats.map((item) => ( +
+
+
+
+

+ {item.name} +

+
+
+

+ {item.stat} +

+
+
+ ))} +
+ ) : null} +
+
+
+ ); } diff --git a/components/results/ResultsResponses.tsx b/components/results/ResultsResponses.tsx index fd71bfcc55..4cc7ef1bb8 100644 --- a/components/results/ResultsResponses.tsx +++ b/components/results/ResultsResponses.tsx @@ -38,9 +38,18 @@ export default function ResultsResponses({ formId }: ResultsResponseProps) {
-
- {activeSubmissionSession && ( -
+
+ {!activeSubmissionSession ? ( + + ) : ( +

@@ -105,53 +114,60 @@ export default function ResultsResponses({ formId }: ResultsResponseProps) { )}

-
diff --git a/lib/submissionSessions.ts b/lib/submissionSessions.ts index c0c0630432..b4f011ff99 100644 --- a/lib/submissionSessions.ts +++ b/lib/submissionSessions.ts @@ -1,4 +1,5 @@ import useSWR from "swr"; +import { SubmissionSession } from "./types"; import { fetcher } from "./utils"; export const useSubmissionSessions = (formId: string) => { @@ -56,3 +57,34 @@ export const getSubmission = (submissionSession, schema) => { } return submission; }; + +export const getSubmissionAnalytics = ( + submissionSessions: SubmissionSession[] +) => { + const uniqueUsers = []; + let totalSubmissions = 0; + let lastSubmissionAt = null; + for (const submissionSession of submissionSessions) { + // collect unique users + if (!uniqueUsers.includes(submissionSession.userFingerprint)) { + uniqueUsers.push(submissionSession.userFingerprint); + } + if (submissionSession.events.length > 0) { + totalSubmissions += 1; + const lastSubmission = + submissionSession.events[submissionSession.events.length - 1]; + if (!lastSubmissionAt) { + lastSubmissionAt = lastSubmission.createdAt; + } else if ( + Date.parse(lastSubmission.createdAt) > Date.parse(lastSubmissionAt) + ) { + lastSubmissionAt = lastSubmission.createdAt; + } + } + } + return { + lastSubmissionAt, + uniqueUsers: uniqueUsers.length, + totalSubmissions: totalSubmissions, + }; +}; diff --git a/lib/utils.ts b/lib/utils.ts index 66ccc1211e..ea1b0417bb 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,4 +1,5 @@ import intlFormat from "date-fns/intlFormat"; +import { formatDistance } from "date-fns"; export const fetcher = (url) => fetch(url).then((res) => res.json()); @@ -94,3 +95,10 @@ export const convertTimeString = (dateString: string) => { } ); }; + +export const timeSince = (dateString: string) => { + const date = new Date(dateString); + return formatDistance(date, new Date(), { + addSuffix: true, + }); +}; diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx index 8cb0ff8778..f24d440a0f 100644 --- a/pages/auth/signin.tsx +++ b/pages/auth/signin.tsx @@ -120,7 +120,6 @@ export default function SignIn({ csrfToken }: props) { export const getServerSideProps: GetServerSideProps = async (context) => { const csrfToken = await getCsrfToken(context); - console.log("csrfToken", JSON.stringify(csrfToken)); return { props: { csrfToken }, }; diff --git a/pages/forms/[id]/results.tsx b/pages/forms/[id]/results.tsx index 8946b6bd37..bf8817cb09 100644 --- a/pages/forms/[id]/results.tsx +++ b/pages/forms/[id]/results.tsx @@ -27,7 +27,7 @@ export default function Share() { resultMode={resultMode} setResultMode={setResultMode} > - {resultMode === "dashboard" && } + {resultMode === "dashboard" && } {resultMode === "responses" && ( <>