Layout tweaks and add custom other responses
This commit is contained in:
@@ -113,29 +113,9 @@ const ResponseFeed = ({ responses, title, renderSummary }) => (
|
|||||||
<ScrollArea className="h-[400px]">
|
<ScrollArea className="h-[400px]">
|
||||||
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
{responses.items.map((response) => (
|
{responses.items.map((response) => (
|
||||||
<div key={response.token} className="p-4 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
<div key={response.token} className="p-4">
|
||||||
<div className="space-y-1.5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{response.hidden?.name || 'Anonymous'}
|
|
||||||
</span>
|
|
||||||
{response.hidden?.email && (
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
({response.hidden.email})
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<time
|
|
||||||
className="text-xs text-muted-foreground"
|
|
||||||
dateTime={response.submitted_at}
|
|
||||||
>
|
|
||||||
{format(new Date(response.submitted_at), "MMM d, h:mm a")}
|
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
{renderSummary(response)}
|
{renderSummary(response)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
@@ -152,7 +132,21 @@ const ProductRelevanceFeed = ({ responses }) => (
|
|||||||
const textAnswer = response.answers?.find(a => a.type === 'text')?.text;
|
const textAnswer = response.answers?.find(a => a.type === 'text')?.text;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
{response.hidden?.email ? (
|
||||||
|
<a
|
||||||
|
href={`https://backend.acherryontop.com/search/?search_for=customers&search=${response.hidden.email}`}
|
||||||
|
className="text-sm font-medium text-gray-900 dark:text-gray-100 hover:underline"
|
||||||
|
>
|
||||||
|
{response.hidden?.name || 'Anonymous'}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{response.hidden?.name || 'Anonymous'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Badge
|
<Badge
|
||||||
className={
|
className={
|
||||||
answer?.boolean ? "bg-green-200 text-green-700" : "bg-red-200 text-red-700"
|
answer?.boolean ? "bg-green-200 text-green-700" : "bg-red-200 text-red-700"
|
||||||
@@ -160,10 +154,18 @@ const ProductRelevanceFeed = ({ responses }) => (
|
|||||||
>
|
>
|
||||||
{answer?.boolean ? "Yes" : "No"}
|
{answer?.boolean ? "Yes" : "No"}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<time
|
||||||
|
className="text-xs text-muted-foreground"
|
||||||
|
dateTime={response.submitted_at}
|
||||||
|
>
|
||||||
|
{format(new Date(response.submitted_at), "MMM d")}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
{textAnswer && (
|
{textAnswer && (
|
||||||
<span className="text-sm text-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
"{textAnswer}"
|
"{textAnswer}"
|
||||||
</span>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -178,12 +180,24 @@ const WinbackFeed = ({ responses }) => (
|
|||||||
renderSummary={(response) => {
|
renderSummary={(response) => {
|
||||||
const likelihoodAnswer = response.answers?.find(a => a.type === 'number');
|
const likelihoodAnswer = response.answers?.find(a => a.type === 'number');
|
||||||
const reasonsAnswer = response.answers?.find(a => a.type === 'choices');
|
const reasonsAnswer = response.answers?.find(a => a.type === 'choices');
|
||||||
const otherAnswer = response.answers?.find(a => a.type === 'text' && a.field.ref.includes('other'));
|
const feedbackAnswer = response.answers?.find(a => a.type === 'text' && a.field.type === 'long_text');
|
||||||
const feedbackAnswer = response.answers?.find(a => a.type === 'text' && !a.field.ref.includes('other'));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
{response.hidden?.email ? (
|
||||||
|
<a
|
||||||
|
href={`https://backend.acherryontop.com/search/?search_for=customers&search=${response.hidden.email}`}
|
||||||
|
className="text-sm font-medium text-gray-900 dark:text-gray-100 hover:underline"
|
||||||
|
>
|
||||||
|
{response.hidden?.name || 'Anonymous'}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{response.hidden?.name || 'Anonymous'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Badge
|
<Badge
|
||||||
className={
|
className={
|
||||||
likelihoodAnswer?.number === 1 ? "bg-red-200 text-red-700" :
|
likelihoodAnswer?.number === 1 ? "bg-red-200 text-red-700" :
|
||||||
@@ -196,20 +210,28 @@ const WinbackFeed = ({ responses }) => (
|
|||||||
>
|
>
|
||||||
{likelihoodAnswer?.number}/5
|
{likelihoodAnswer?.number}/5
|
||||||
</Badge>
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<time
|
||||||
|
className="text-xs text-muted-foreground"
|
||||||
|
dateTime={response.submitted_at}
|
||||||
|
>
|
||||||
|
{format(new Date(response.submitted_at), "MMM d")}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
{(reasonsAnswer?.choices?.labels || []).map((label, idx) => (
|
{(reasonsAnswer?.choices?.labels || []).map((label, idx) => (
|
||||||
<Badge key={idx} variant="secondary" className="text-xs">
|
<Badge key={idx} variant="secondary" className="text-xs">
|
||||||
{label}
|
{label}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
{reasonsAnswer?.choices?.other && (
|
||||||
{otherAnswer?.text && (
|
<Badge variant="outline" className="text-xs">
|
||||||
<div className="text-sm">
|
{reasonsAnswer.choices.other}
|
||||||
<span className="font-medium">Other:</span>{" "}
|
</Badge>
|
||||||
{otherAnswer.text}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{feedbackAnswer?.text && (
|
{feedbackAnswer?.text && (
|
||||||
<div className="text-sm">
|
<div className="text-sm text-muted-foreground">
|
||||||
{feedbackAnswer.text}
|
{feedbackAnswer.text}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -293,7 +315,7 @@ const TypeformDashboard = () => {
|
|||||||
? Math.round((likelihoodAnswers.reduce((a, b) => a + b, 0) / likelihoodAnswers.length) * 10) / 10
|
? Math.round((likelihoodAnswers.reduce((a, b) => a + b, 0) / likelihoodAnswers.length) * 10) / 10
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Get reasons for not ordering
|
// Get reasons for not ordering (only predefined choices)
|
||||||
const reasonsMap = new Map();
|
const reasonsMap = new Map();
|
||||||
form2Responses.forEach(response => {
|
form2Responses.forEach(response => {
|
||||||
const reasonsAnswer = response.answers?.find(a => a.type === 'choices');
|
const reasonsAnswer = response.answers?.find(a => a.type === 'choices');
|
||||||
@@ -388,7 +410,7 @@ const TypeformDashboard = () => {
|
|||||||
|
|
||||||
|
|
||||||
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||||
<CardHeader>
|
<CardHeader className="p-6">
|
||||||
<div className="flex items-baseline justify-between">
|
<div className="flex items-baseline justify-between">
|
||||||
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">How likely are you to place another order with us?</CardTitle>
|
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">How likely are you to place another order with us?</CardTitle>
|
||||||
<span className={`text-2xl font-bold ${
|
<span className={`text-2xl font-bold ${
|
||||||
@@ -408,20 +430,20 @@ const TypeformDashboard = () => {
|
|||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart
|
<BarChart
|
||||||
data={likelihoodCounts}
|
data={likelihoodCounts}
|
||||||
margin={{ top: 0, right: 0, left: -20, bottom: 0 }}
|
margin={{ top: 0, right: 10, left: -20, bottom: -25 }}
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="rating"
|
dataKey="rating"
|
||||||
tickFormatter={(value) => {
|
tickFormatter={(value) => {
|
||||||
return value === "1" ? "Not at all likely" : value === "5" ? "Extremely likely" : "";
|
return value === "1" ? "Not at all" : value === "5" ? "Extremely" : "";
|
||||||
}}
|
}}
|
||||||
textAnchor="middle"
|
textAnchor="middle"
|
||||||
interval={0}
|
interval={0}
|
||||||
height={50}
|
height={50}
|
||||||
className="text-muted-foreground"
|
className="text-muted-foreground text-xs md:text-sm"
|
||||||
/>
|
/>
|
||||||
<YAxis className="text-muted-foreground" />
|
<YAxis className="text-muted-foreground text-xs md:text-sm" />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={({ payload }) => {
|
content={({ payload }) => {
|
||||||
if (payload && payload.length) {
|
if (payload && payload.length) {
|
||||||
@@ -459,12 +481,12 @@ const TypeformDashboard = () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||||
<CardHeader>
|
<CardHeader className="p-6">
|
||||||
<div className="flex items-baseline justify-between">
|
<div className="flex items-baseline justify-between gap-2">
|
||||||
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">Were the suggested products in this email relevant to you?</CardTitle>
|
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">Were the suggested products in this email relevant to you?</CardTitle>
|
||||||
<div className="flex flex-col items-end">
|
<div className="flex flex-col items-end">
|
||||||
<span className="text-2xl font-bold text-green-600 dark:text-green-500">
|
<span className="text-2xl font-bold text-green-600 dark:text-green-500">
|
||||||
{metrics.productRelevance.yesPercentage}% Positive
|
{metrics.productRelevance.yesPercentage}% Relevant
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -513,7 +535,7 @@ const TypeformDashboard = () => {
|
|||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="yes" stackId="stack" fill="#10b981" radius={[4, 4, 0, 0]}>
|
<Bar dataKey="yes" stackId="stack" fill="#10b981" radius={[0, 0, 0, 0]}>
|
||||||
<text
|
<text
|
||||||
x="50%"
|
x="50%"
|
||||||
y="50%"
|
y="50%"
|
||||||
@@ -525,7 +547,7 @@ const TypeformDashboard = () => {
|
|||||||
{metrics.productRelevance.yesPercentage}%
|
{metrics.productRelevance.yesPercentage}%
|
||||||
</text>
|
</text>
|
||||||
</Bar>
|
</Bar>
|
||||||
<Bar dataKey="no" stackId="stack" fill="#ef4444" radius={[0, 0, 4, 4]} />
|
<Bar dataKey="no" stackId="stack" fill="#ef4444" radius={[0, 0, 0, 0]} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
@@ -537,8 +559,8 @@ const TypeformDashboard = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 xl:grid-cols-12 gap-6">
|
<div className="grid grid-cols-2 lg:grid-cols-12 gap-6">
|
||||||
<div className="col-span-4">
|
<div className="col-span-4 lg:col-span-12 xl:col-span-4">
|
||||||
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">Reasons for Not Ordering</CardTitle>
|
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-gray-100">Reasons for Not Ordering</CardTitle>
|
||||||
@@ -568,10 +590,10 @@ const TypeformDashboard = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 lg:col-span-1 xl:col-span-4">
|
<div className="col-span-4 lg:col-span-6 xl:col-span-4">
|
||||||
<WinbackFeed responses={formData.form2.responses} />
|
<WinbackFeed responses={formData.form2.responses} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 lg:col-span-1 xl:col-span-4">
|
<div className="col-span-4 lg:col-span-6 xl:col-span-4">
|
||||||
<ProductRelevanceFeed responses={formData.form1.responses} />
|
<ProductRelevanceFeed responses={formData.form1.responses} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user