Layout tweaks and add custom other responses

This commit is contained in:
2024-12-30 13:34:24 -05:00
parent 271d2f0f6c
commit 8a05bf5c41

View File

@@ -113,28 +113,8 @@ 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"> {renderSummary(response)}
<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)}
</div>
</div> </div>
))} ))}
</div> </div>
@@ -152,18 +132,40 @@ 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="flex items-center gap-2"> <div className="space-y-1.5">
<Badge <div className="flex items-center justify-between">
className={ <div className="flex items-center gap-2">
answer?.boolean ? "bg-green-200 text-green-700" : "bg-red-200 text-red-700" {response.hidden?.email ? (
} <a
> href={`https://backend.acherryontop.com/search/?search_for=customers&search=${response.hidden.email}`}
{answer?.boolean ? "Yes" : "No"} className="text-sm font-medium text-gray-900 dark:text-gray-100 hover:underline"
</Badge> >
{response.hidden?.name || 'Anonymous'}
</a>
) : (
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{response.hidden?.name || 'Anonymous'}
</span>
)}
<Badge
className={
answer?.boolean ? "bg-green-200 text-green-700" : "bg-red-200 text-red-700"
}
>
{answer?.boolean ? "Yes" : "No"}
</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,38 +180,58 @@ 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 gap-2"> <div className="flex items-center justify-between">
<Badge <div className="flex items-center gap-2">
className={ {response.hidden?.email ? (
likelihoodAnswer?.number === 1 ? "bg-red-200 text-red-700" : <a
likelihoodAnswer?.number === 2 ? "bg-orange-200 text-orange-700" : href={`https://backend.acherryontop.com/search/?search_for=customers&search=${response.hidden.email}`}
likelihoodAnswer?.number === 3 ? "bg-yellow-200 text-yellow-700" : className="text-sm font-medium text-gray-900 dark:text-gray-100 hover:underline"
likelihoodAnswer?.number === 4 ? "bg-lime-200 text-lime-700" : >
likelihoodAnswer?.number === 5 ? "bg-green-200 text-green-700" : {response.hidden?.name || 'Anonymous'}
"bg-gray-200 text-gray-700" </a>
} ) : (
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{response.hidden?.name || 'Anonymous'}
</span>
)}
<Badge
className={
likelihoodAnswer?.number === 1 ? "bg-red-200 text-red-700" :
likelihoodAnswer?.number === 2 ? "bg-orange-200 text-orange-700" :
likelihoodAnswer?.number === 3 ? "bg-yellow-200 text-yellow-700" :
likelihoodAnswer?.number === 4 ? "bg-lime-200 text-lime-700" :
likelihoodAnswer?.number === 5 ? "bg-green-200 text-green-700" :
"bg-gray-200 text-gray-700"
}
>
{likelihoodAnswer?.number}/5
</Badge>
</div>
<time
className="text-xs text-muted-foreground"
dateTime={response.submitted_at}
> >
{likelihoodAnswer?.number}/5 {format(new Date(response.submitted_at), "MMM d")}
</Badge> </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>
))} ))}
{reasonsAnswer?.choices?.other && (
<Badge variant="outline" className="text-xs">
{reasonsAnswer.choices.other}
</Badge>
)}
</div> </div>
{otherAnswer?.text && (
<div className="text-sm">
<span className="font-medium">Other:</span>{" "}
{otherAnswer.text}
</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>