diff --git a/dashboard/src/components/dashboard/TypeformDashboard.jsx b/dashboard/src/components/dashboard/TypeformDashboard.jsx index 9cec777..993223a 100644 --- a/dashboard/src/components/dashboard/TypeformDashboard.jsx +++ b/dashboard/src/components/dashboard/TypeformDashboard.jsx @@ -46,7 +46,7 @@ const FORM_NAMES = { // Loading skeleton components const SkeletonChart = () => ( -
+
{[...Array(5)].map((_, i) => (
( /> ); -const renderProductRelevanceBar = (metrics) => ( - - -
- Were the suggested products in this email relevant to you? -
- - {metrics.productRelevance.yesPercentage}% Positive - -
-
-
- -
- - - - - { - if (payload && payload.length) { - const yesCount = payload[0].payload.yes; - const noCount = payload[0].payload.no; - const total = yesCount + noCount; - const yesPercent = Math.round((yesCount / total) * 100); - const noPercent = Math.round((noCount / total) * 100); - return ( - - -
-
- Yes: - {yesCount} ({yesPercent}%) -
-
- No: - {noCount} ({noPercent}%) -
-
-
-
- ); - } - return null; - }} - /> - - - {metrics.productRelevance.yesPercentage}% - - - -
-
-
-
-
Yes: {metrics.productRelevance.yesCount}
-
No: {metrics.productRelevance.noCount}
-
-
-
-); - -const renderLikelihoodChart = (metrics, responses) => { - const likelihoodCounts = [1, 2, 3, 4, 5].map(rating => ({ - rating: rating.toString(), - count: responses.items - .filter(r => r.answers?.find(a => a.type === 'number')?.number === rating) - .length - })); - - return ( - - -
- How likely are you to place another order with us? - - {metrics.winback.averageRating} - /5 avg - -
-
- -
- - - - { - return value === "1" ? "Not at all likely" : value === "5" ? "Extremely likely" : ""; - }} - textAnchor="middle" - interval={0} - height={50} - /> - - { - if (payload && payload.length) { - const { rating, count } = payload[0].payload; - return ( -
-
- {rating} Rating: {count} responses -
-
- ); - } - return null; - }} - /> - - {likelihoodCounts.map((_, index) => ( - - ))} - -
-
-
-
-
- ); -}; - const TypeformDashboard = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -487,18 +327,55 @@ const TypeformDashboard = () => { const metrics = loading ? null : calculateMetrics(); + // Find the newest response across both forms + const getNewestResponse = () => { + if (!formData.form1.responses?.items?.length && !formData.form2.responses?.items?.length) return null; + + const form1Latest = formData.form1.responses?.items[0]?.submitted_at; + const form2Latest = formData.form2.responses?.items[0]?.submitted_at; + + if (!form1Latest) return form2Latest; + if (!form2Latest) return form1Latest; + + return new Date(form1Latest) > new Date(form2Latest) ? form1Latest : form2Latest; + }; + + const newestResponse = getNewestResponse(); + if (error) { return ( - - - Error - {error} - + + +
+ {error} +
+
+
); } + // Calculate likelihood counts for the chart + const likelihoodCounts = !loading && formData.form2.responses ? [1, 2, 3, 4, 5].map(rating => ({ + rating: rating.toString(), + count: formData.form2.responses.items + .filter(r => r.answers?.find(a => a.type === 'number')?.number === rating) + .length + })) : []; + return ( - + + +
+ + Customer Surveys + + {newestResponse && ( +

+ Newest response: {format(new Date(newestResponse), "MMM d, h:mm a")} +

+ )} +
+
{loading ? (
@@ -508,41 +385,195 @@ const TypeformDashboard = () => { ) : ( <>
- {renderProductRelevanceBar(metrics)} - {renderLikelihoodChart(metrics, formData.form2.responses)} -
+ -
-
- - - Reasons for Not Ordering + + +
+ How likely are you to place another order with us? + + {metrics.winback.averageRating} + /5 avg + +
- - - - Reason - Count - % - - - - {metrics.winback.reasons.map((reason, index) => ( - - {reason.reason} - {reason.count} - {reason.percentage}% - - ))} - -
+
+ + + + { + return value === "1" ? "Not at all likely" : value === "5" ? "Extremely likely" : ""; + }} + textAnchor="middle" + interval={0} + height={50} + className="text-muted-foreground" + /> + + { + if (payload && payload.length) { + const { rating, count } = payload[0].payload; + return ( + + +
+ {rating} Rating: {count} responses +
+
+
+ ); + } + return null; + }} + /> + + {likelihoodCounts.map((_, index) => ( + + ))} + +
+
+
+
+
+ + +
+ Were the suggested products in this email relevant to you? +
+ + {metrics.productRelevance.yesPercentage}% Positive + +
+
+
+ +
+ + + + + { + if (payload && payload.length) { + const yesCount = payload[0].payload.yes; + const noCount = payload[0].payload.no; + const total = yesCount + noCount; + const yesPercent = Math.round((yesCount / total) * 100); + const noPercent = Math.round((noCount / total) * 100); + return ( + + +
+
+ Yes: + {yesCount} ({yesPercent}%) +
+
+ No: + {noCount} ({noPercent}%) +
+
+
+
+ ); + } + return null; + }} + /> + + + {metrics.productRelevance.yesPercentage}% + + + +
+
+
+
+
Yes: {metrics.productRelevance.yesCount}
+
No: {metrics.productRelevance.noCount}
+
-
-
- + +
+
+ + + Reasons for Not Ordering + + +
+ + + + Reason + Count + % + + + + {metrics.winback.reasons.map((reason, index) => ( + + {reason.reason} + {reason.count} + {reason.percentage}% + + ))} + +
+
+
+
+
+ +
+ +
+
+ +
)}