Skip to content

Commit

Permalink
feat: add SurveyCategoryChart
Browse files Browse the repository at this point in the history
  • Loading branch information
moonrailgun committed Feb 8, 2025
1 parent 4c37eab commit ba281ac
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 12 deletions.
72 changes: 72 additions & 0 deletions src/client/components/chart/SimplePieChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { PieChart, Pie, Label } from 'recharts';
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '../ui/chart';
import { sumBy } from 'lodash-es';
import { useTranslation } from '@i18next-toolkit/react';

export const SimplePieChart: React.FC<{
className?: string;
data: { label: string; count: number }[];
chartConfig: ChartConfig;
}> = React.memo((props) => {
const { className, data, chartConfig } = props;
const { t } = useTranslation();

const totalCount = React.useMemo(() => {
return sumBy(data, 'count');
}, []);

return (
<ChartContainer className={className} config={chartConfig}>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={data}
dataKey="count"
nameKey="label"
innerRadius={'50%'}
strokeWidth={5}
>
<Label
content={({ viewBox }) => {
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-3xl font-bold"
>
{totalCount.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground"
>
{t('Count')}
</tspan>
</text>
);
}
}}
/>
</Pie>
</PieChart>
</ChartContainer>
);
});
SimplePieChart.displayName = 'SimplePieChart';
76 changes: 76 additions & 0 deletions src/client/components/survey/SurveyCategoryChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useMemo, useState } from 'react';
import { SimplePieChart } from '../chart/SimplePieChart';
import { trpc } from '@/api/trpc';
import { useCurrentWorkspaceId } from '@/store/user';
import { reverse, sortBy, sumBy, take } from 'lodash-es';
import { ChartConfig } from '../ui/chart';
import { Checkbox } from '../ui/checkbox';
import { useTranslation } from '@i18next-toolkit/react';

interface SurveyCategoryChartProps {
surveyId: string;
}
export const SurveyCategoryChart: React.FC<SurveyCategoryChartProps> =
React.memo((props) => {
const workspaceId = useCurrentWorkspaceId();
const { t } = useTranslation();
const [hideUncategorized, setHideUncategorized] = useState(false);

const { data: allCategory = [] } = trpc.survey.aiCategoryList.useQuery({
workspaceId,
surveyId: props.surveyId,
});

const { data, chartConfig } = useMemo(() => {
const ordered = reverse(
sortBy(
hideUncategorized
? allCategory.filter((item) => item.name)
: allCategory,
(d) => d.count
)
);
const category = take(ordered, 4);

if (ordered.length >= 5) {
category[4] = {
name: 'Other',
count: sumBy(ordered, 'count') - sumBy(category, 'count'),
};
}

const chartConfig: ChartConfig = {};
category.forEach((c, i) => {
const name = c.name ? c.name : 'Uncategorized';
chartConfig[`category-${i}`] = {
label: name,
color: `hsl(var(--chart-${i + 1}))`,
};
});

return {
data: category.map((c, i) => ({
label: c.name ? c.name : 'Uncategorized',
count: c.count,
fill: `var(--color-category-${i})`,
})),
chartConfig,
};
}, [allCategory, hideUncategorized]);

return (
<div>
<SimplePieChart data={data} chartConfig={chartConfig} />
<div className="flex items-center gap-1">
<Checkbox
checked={hideUncategorized}
onCheckedChange={(checked) =>
setHideUncategorized(checked === true)
}
/>
<span>{t('Hide Uncategorized')}</span>
</div>
</div>
);
});
SurveyCategoryChart.displayName = 'SurveyCategoryChart';
53 changes: 41 additions & 12 deletions src/client/routes/survey/$surveyId/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import React from 'react';
import { useGlobalConfig } from '@/hooks/useConfig';
import { DataRender } from '@/components/DataRender';
import { SurveyAIBtn } from '@/components/survey/SurveyAIBtn';
import { compact } from 'lodash-es';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { SurveyCategoryChart } from '@/components/survey/SurveyCategoryChart';

type SurveyResultItem =
AppRouterOutput['survey']['resultList']['items'][number];
Expand Down Expand Up @@ -125,7 +128,7 @@ function PageComponent() {
});

const columns = useMemo(() => {
return [
return compact([
columnHelper.accessor('id', {
header: 'ID',
size: 230,
Expand All @@ -147,12 +150,17 @@ function PageComponent() {
header: item.label,
})
) ?? []),
config.enableAI &&
columnHelper.accessor('aiCategory', {
header: t('AI Category'),
size: 200,
}),
columnHelper.accessor('createdAt', {
header: t('Created At'),
size: 200,
cell: (props) => dayjs(props.getValue()).format('YYYY-MM-DD HH:mm:ss'),
}),
];
]);
}, [t, info]);

return (
Expand Down Expand Up @@ -216,16 +224,37 @@ function PageComponent() {
</div>

<div className="mt-4">
<TimeEventChart
className="h-[240px] w-full"
data={surveyStats}
unit="day"
chartConfig={{
count: {
label: t('Count'),
},
}}
/>
<Tabs defaultValue="recent" className="w-full">
<TabsList>
<TabsTrigger value="recent">
{t('Recent Survey Count')}
</TabsTrigger>

{config.enableAI && (
<TabsTrigger value="category">
{t('Survey Category')}
</TabsTrigger>
)}
</TabsList>
<TabsContent value="recent">
<TimeEventChart
className="h-[240px] w-full"
data={surveyStats}
unit="day"
chartConfig={{
count: {
label: t('Count'),
},
}}
/>
</TabsContent>

{config.enableAI && (
<TabsContent value="category">
<SurveyCategoryChart surveyId={surveyId} />
</TabsContent>
)}
</Tabs>
</div>
</CardContent>
</Card>
Expand Down

0 comments on commit ba281ac

Please sign in to comment.