improve(dashboard): stacked area chart and better dashed stroke (minor fix to bar chart)
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||||
import { api } from '@/trpc/client';
|
|
||||||
import type { IChartData } from '@/trpc/client';
|
import type { IChartData } from '@/trpc/client';
|
||||||
|
import { api } from '@/trpc/client';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { getChartColor } from '@/utils/theme';
|
import { getChartColor } from '@/utils/theme';
|
||||||
import { isSameDay, isSameHour, isSameMonth } from 'date-fns';
|
import { isSameDay, isSameHour, isSameMonth } from 'date-fns';
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
Legend,
|
Legend,
|
||||||
Line,
|
|
||||||
ReferenceLine,
|
ReferenceLine,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -23,6 +22,7 @@ import {
|
|||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
||||||
|
import { SolidToDashedGradient } from '../common/linear-gradient';
|
||||||
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
||||||
import { ReportTable } from '../common/report-table';
|
import { ReportTable } from '../common/report-table';
|
||||||
import { SerieIcon } from '../common/serie-icon';
|
import { SerieIcon } from '../common/serie-icon';
|
||||||
@@ -47,7 +47,6 @@ export function Chart({ data }: Props) {
|
|||||||
isEditMode,
|
isEditMode,
|
||||||
options: { hideXAxis, hideYAxis },
|
options: { hideXAxis, hideYAxis },
|
||||||
} = useReportChartContext();
|
} = useReportChartContext();
|
||||||
const dataLength = data.series[0]?.data?.length || 0;
|
|
||||||
const references = api.reference.getChartReferences.useQuery(
|
const references = api.reference.getChartReferences.useQuery(
|
||||||
{
|
{
|
||||||
projectId,
|
projectId,
|
||||||
@@ -69,20 +68,6 @@ export function Chart({ data }: Props) {
|
|||||||
const lastIntervalPercent =
|
const lastIntervalPercent =
|
||||||
((rechartData.length - 2) * 100) / (rechartData.length - 1);
|
((rechartData.length - 2) * 100) / (rechartData.length - 1);
|
||||||
|
|
||||||
const gradientTwoColors = (
|
|
||||||
id: string,
|
|
||||||
col1: string,
|
|
||||||
col2: string,
|
|
||||||
percentChange: number,
|
|
||||||
) => (
|
|
||||||
<linearGradient id={id} x1="0" y1="0" x2="100%" y2="0">
|
|
||||||
<stop offset="0%" stopColor={col1} />
|
|
||||||
<stop offset={`${percentChange}%`} stopColor={col1} />
|
|
||||||
<stop offset={`${percentChange}%`} stopColor={`${col2}`} />
|
|
||||||
<stop offset="100%" stopColor={col2} />
|
|
||||||
</linearGradient>
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastSerieDataItem = last(series[0]?.data || [])?.date || new Date();
|
const lastSerieDataItem = last(series[0]?.data || [])?.date || new Date();
|
||||||
const useDashedLastLine = (() => {
|
const useDashedLastLine = (() => {
|
||||||
if (interval === 'hour') {
|
if (interval === 'hour') {
|
||||||
@@ -102,7 +87,7 @@ export function Chart({ data }: Props) {
|
|||||||
|
|
||||||
const CustomLegend = useCallback(() => {
|
const CustomLegend = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1">
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs mt-4 -mb-2">
|
||||||
{series.map((serie) => (
|
{series.map((serie) => (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
@@ -128,7 +113,6 @@ export function Chart({ data }: Props) {
|
|||||||
interval,
|
interval,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAreaStyle = series.length === 1;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
||||||
@@ -157,104 +141,56 @@ export function Chart({ data }: Props) {
|
|||||||
))}
|
))}
|
||||||
<YAxis {...yAxisProps} />
|
<YAxis {...yAxisProps} />
|
||||||
<XAxis {...xAxisProps} />
|
<XAxis {...xAxisProps} />
|
||||||
{series.length > 1 && (
|
<Legend content={<CustomLegend />} />
|
||||||
<Legend
|
|
||||||
wrapperStyle={{ fontSize: '10px' }}
|
|
||||||
content={<CustomLegend />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Tooltip content={<ReportChartTooltip />} />
|
<Tooltip content={<ReportChartTooltip />} />
|
||||||
{series.map((serie) => {
|
{series.map((serie) => {
|
||||||
const color = getChartColor(serie.index);
|
const color = getChartColor(serie.index);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={serie.id}>
|
<React.Fragment key={serie.id}>
|
||||||
<defs>
|
<defs>
|
||||||
{isAreaStyle && (
|
<linearGradient
|
||||||
<linearGradient
|
id={`color${color}`}
|
||||||
id={`color${color}`}
|
x1="0"
|
||||||
x1="0"
|
y1="0"
|
||||||
y1="0"
|
x2="0"
|
||||||
x2="0"
|
y2="1"
|
||||||
y2="1"
|
>
|
||||||
>
|
<stop offset="0%" stopColor={color} stopOpacity={0.8} />
|
||||||
<stop offset="0%" stopColor={color} stopOpacity={0.8} />
|
<stop
|
||||||
<stop
|
offset={'100%'}
|
||||||
offset="100%"
|
stopColor={color}
|
||||||
stopColor={color}
|
stopOpacity={0.1}
|
||||||
stopOpacity={0.1}
|
/>
|
||||||
/>
|
</linearGradient>
|
||||||
</linearGradient>
|
{useDashedLastLine && (
|
||||||
)}
|
<SolidToDashedGradient
|
||||||
{gradientTwoColors(
|
percentage={lastIntervalPercent}
|
||||||
`hideAllButLastInterval_${serie.id}`,
|
baseColor={color}
|
||||||
'rgba(0,0,0,0)',
|
id={`stroke${color}`}
|
||||||
color,
|
/>
|
||||||
lastIntervalPercent,
|
|
||||||
)}
|
|
||||||
{gradientTwoColors(
|
|
||||||
`hideJustLastInterval_${serie.id}`,
|
|
||||||
color,
|
|
||||||
'rgba(0,0,0,0)',
|
|
||||||
lastIntervalPercent,
|
|
||||||
)}
|
)}
|
||||||
</defs>
|
</defs>
|
||||||
<Line
|
<Area
|
||||||
dot={isAreaStyle && dataLength <= 8}
|
stackId="1"
|
||||||
type={lineType}
|
type={lineType}
|
||||||
name={serie.id}
|
name={serie.id}
|
||||||
isAnimationActive={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
dataKey={`${serie.id}:count`}
|
dataKey={`${serie.id}:count`}
|
||||||
stroke={useDashedLastLine ? 'transparent' : color}
|
stroke={useDashedLastLine ? `url(#stroke${color})` : color}
|
||||||
// Use for legend
|
fill={`url(#color${color})`}
|
||||||
fill={color}
|
isAnimationActive={false}
|
||||||
|
fillOpacity={0.7}
|
||||||
/>
|
/>
|
||||||
{isAreaStyle && (
|
|
||||||
<Area
|
|
||||||
dot={false}
|
|
||||||
name={`${serie.id}:area:noTooltip`}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
fill={`url(#color${color})`}
|
|
||||||
type={lineType}
|
|
||||||
isAnimationActive={false}
|
|
||||||
fillOpacity={0.1}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{useDashedLastLine && (
|
|
||||||
<>
|
|
||||||
<Line
|
|
||||||
dot={false}
|
|
||||||
type={lineType}
|
|
||||||
name={`${serie.id}:dashed:noTooltip`}
|
|
||||||
isAnimationActive={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
stroke={`url('#hideAllButLastInterval_${serie.id}')`}
|
|
||||||
strokeDasharray="4 2"
|
|
||||||
strokeOpacity={0.7}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
dot={false}
|
|
||||||
type={lineType}
|
|
||||||
name={`${serie.id}:solid:noTooltip`}
|
|
||||||
isAnimationActive={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
stroke={`url('#hideJustLastInterval_${serie.id}')`}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{previous && (
|
{previous && (
|
||||||
<Line
|
<Area
|
||||||
|
stackId="2"
|
||||||
type={lineType}
|
type={lineType}
|
||||||
name={`${serie.id}:prev`}
|
name={`${serie.id}:prev`}
|
||||||
isAnimationActive
|
|
||||||
dot={false}
|
|
||||||
strokeOpacity={0.3}
|
|
||||||
dataKey={`${serie.id}:prev:count`}
|
dataKey={`${serie.id}:prev:count`}
|
||||||
stroke={color}
|
stroke={color}
|
||||||
// Use for legend
|
|
||||||
fill={color}
|
fill={color}
|
||||||
|
fillOpacity={0.3}
|
||||||
|
strokeOpacity={0.3}
|
||||||
|
isAnimationActive={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
interface GradientProps {
|
||||||
|
percentage: number;
|
||||||
|
baseColor: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SolidToDashedGradient: React.FC<GradientProps> = ({
|
||||||
|
percentage,
|
||||||
|
baseColor,
|
||||||
|
id,
|
||||||
|
}) => {
|
||||||
|
const stops = generateSolidToDashedLinearGradient(percentage, baseColor);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<linearGradient id={id} x1="0" y1="0" x2="1" y2="0">
|
||||||
|
{stops.map((stop, index) => (
|
||||||
|
<stop
|
||||||
|
key={index as any}
|
||||||
|
offset={stop.offset}
|
||||||
|
stopColor={stop.color}
|
||||||
|
stopOpacity={stop.opacity}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</linearGradient>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function moved to the same file
|
||||||
|
const generateSolidToDashedLinearGradient = (
|
||||||
|
percentage: number,
|
||||||
|
baseColor: string,
|
||||||
|
) => {
|
||||||
|
// Start with solid baseColor up to percentage
|
||||||
|
const stops = [
|
||||||
|
{ offset: '0%', color: baseColor, opacity: 1 },
|
||||||
|
{ offset: `${percentage}%`, color: baseColor, opacity: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Calculate the remaining space for dashes
|
||||||
|
const remainingSpace = 100 - percentage;
|
||||||
|
const dashWidth = remainingSpace / 20; // 10 dashes = 20 segments (dash + gap)
|
||||||
|
|
||||||
|
// Generate 10 dashes
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const startOffset = percentage + i * 2 * dashWidth;
|
||||||
|
|
||||||
|
// Add dash and gap with sharp transitions
|
||||||
|
stops.push(
|
||||||
|
// Start of dash
|
||||||
|
{ offset: `${startOffset}%`, color: baseColor, opacity: 1 },
|
||||||
|
// End of dash
|
||||||
|
{ offset: `${startOffset + dashWidth}%`, color: baseColor, opacity: 1 },
|
||||||
|
// Start of gap (immediate transition)
|
||||||
|
{
|
||||||
|
offset: `${startOffset + dashWidth}%`,
|
||||||
|
color: 'transparent',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// End of gap
|
||||||
|
{
|
||||||
|
offset: `${startOffset + 2 * dashWidth}%`,
|
||||||
|
color: 'transparent',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stops;
|
||||||
|
};
|
||||||
@@ -60,7 +60,7 @@ export function Chart({ data }: Props) {
|
|||||||
<>
|
<>
|
||||||
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
||||||
<ResponsiveContainer>
|
<ResponsiveContainer>
|
||||||
<BarChart data={rechartData} barCategoryGap={10}>
|
<BarChart data={rechartData}>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
vertical={false}
|
vertical={false}
|
||||||
@@ -68,30 +68,10 @@ export function Chart({ data }: Props) {
|
|||||||
/>
|
/>
|
||||||
<Tooltip content={<ReportChartTooltip />} cursor={<BarHover />} />
|
<Tooltip content={<ReportChartTooltip />} cursor={<BarHover />} />
|
||||||
<YAxis {...yAxisProps} />
|
<YAxis {...yAxisProps} />
|
||||||
<XAxis {...xAxisProps} />
|
<XAxis {...xAxisProps} scale={'auto'} type="category" />
|
||||||
{series.map((serie) => {
|
{series.map((serie) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={serie.id}>
|
<React.Fragment key={serie.id}>
|
||||||
<defs>
|
|
||||||
<linearGradient
|
|
||||||
id="colorGradient"
|
|
||||||
x1="0"
|
|
||||||
y1="1"
|
|
||||||
x2="0"
|
|
||||||
y2="0"
|
|
||||||
>
|
|
||||||
<stop
|
|
||||||
offset="0%"
|
|
||||||
stopColor={getChartColor(serie.index)}
|
|
||||||
stopOpacity={0.7}
|
|
||||||
/>
|
|
||||||
<stop
|
|
||||||
offset="100%"
|
|
||||||
stopColor={getChartColor(serie.index)}
|
|
||||||
stopOpacity={1}
|
|
||||||
/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
{previous && (
|
{previous && (
|
||||||
<Bar
|
<Bar
|
||||||
key={`${serie.id}:prev`}
|
key={`${serie.id}:prev`}
|
||||||
@@ -100,17 +80,17 @@ export function Chart({ data }: Props) {
|
|||||||
fill={getChartColor(serie.index)}
|
fill={getChartColor(serie.index)}
|
||||||
fillOpacity={0.1}
|
fillOpacity={0.1}
|
||||||
radius={3}
|
radius={3}
|
||||||
barSize={20} // Adjust the bar width here
|
barSize={5} // Adjust the bar width here
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Bar
|
<Bar
|
||||||
key={serie.id}
|
key={serie.id}
|
||||||
name={serie.id}
|
name={serie.id}
|
||||||
dataKey={`${serie.id}:count`}
|
dataKey={`${serie.id}:count`}
|
||||||
fill="url(#colorGradient)"
|
fill={getChartColor(serie.index)}
|
||||||
radius={3}
|
radius={3}
|
||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
barSize={20} // Adjust the bar width here
|
barSize={5} // Adjust the bar width here
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
||||||
|
import { SolidToDashedGradient } from '../common/linear-gradient';
|
||||||
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
||||||
import { ReportTable } from '../common/report-table';
|
import { ReportTable } from '../common/report-table';
|
||||||
import { SerieIcon } from '../common/serie-icon';
|
import { SerieIcon } from '../common/serie-icon';
|
||||||
@@ -69,20 +70,6 @@ export function Chart({ data }: Props) {
|
|||||||
const lastIntervalPercent =
|
const lastIntervalPercent =
|
||||||
((rechartData.length - 2) * 100) / (rechartData.length - 1);
|
((rechartData.length - 2) * 100) / (rechartData.length - 1);
|
||||||
|
|
||||||
const gradientTwoColors = (
|
|
||||||
id: string,
|
|
||||||
col1: string,
|
|
||||||
col2: string,
|
|
||||||
percentChange: number,
|
|
||||||
) => (
|
|
||||||
<linearGradient id={id} x1="0" y1="0" x2="100%" y2="0">
|
|
||||||
<stop offset="0%" stopColor={col1} />
|
|
||||||
<stop offset={`${percentChange}%`} stopColor={col1} />
|
|
||||||
<stop offset={`${percentChange}%`} stopColor={`${col2}`} />
|
|
||||||
<stop offset="100%" stopColor={col2} />
|
|
||||||
</linearGradient>
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastSerieDataItem = last(series[0]?.data || [])?.date || new Date();
|
const lastSerieDataItem = last(series[0]?.data || [])?.date || new Date();
|
||||||
const useDashedLastLine = (() => {
|
const useDashedLastLine = (() => {
|
||||||
if (interval === 'hour') {
|
if (interval === 'hour') {
|
||||||
@@ -102,7 +89,7 @@ export function Chart({ data }: Props) {
|
|||||||
|
|
||||||
const CustomLegend = useCallback(() => {
|
const CustomLegend = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1">
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs mt-4 -mb-2">
|
||||||
{series.map((serie) => (
|
{series.map((serie) => (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
@@ -157,12 +144,7 @@ export function Chart({ data }: Props) {
|
|||||||
domain={maxDomain ? [0, maxDomain] : undefined}
|
domain={maxDomain ? [0, maxDomain] : undefined}
|
||||||
/>
|
/>
|
||||||
<XAxis {...xAxisProps} />
|
<XAxis {...xAxisProps} />
|
||||||
{series.length > 1 && (
|
{series.length > 1 && <Legend content={<CustomLegend />} />}
|
||||||
<Legend
|
|
||||||
wrapperStyle={{ fontSize: '10px' }}
|
|
||||||
content={<CustomLegend />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Tooltip content={<ReportChartTooltip />} />
|
<Tooltip content={<ReportChartTooltip />} />
|
||||||
{series.map((serie) => {
|
{series.map((serie) => {
|
||||||
const color = getChartColor(serie.index);
|
const color = getChartColor(serie.index);
|
||||||
@@ -185,17 +167,12 @@ export function Chart({ data }: Props) {
|
|||||||
/>
|
/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
)}
|
)}
|
||||||
{gradientTwoColors(
|
{useDashedLastLine && (
|
||||||
`hideAllButLastInterval_${serie.id}`,
|
<SolidToDashedGradient
|
||||||
'rgba(0,0,0,0)',
|
percentage={lastIntervalPercent}
|
||||||
color,
|
baseColor={color}
|
||||||
lastIntervalPercent,
|
id={`stroke${color}`}
|
||||||
)}
|
/>
|
||||||
{gradientTwoColors(
|
|
||||||
`hideJustLastInterval_${serie.id}`,
|
|
||||||
color,
|
|
||||||
'rgba(0,0,0,0)',
|
|
||||||
lastIntervalPercent,
|
|
||||||
)}
|
)}
|
||||||
</defs>
|
</defs>
|
||||||
<Line
|
<Line
|
||||||
@@ -205,7 +182,7 @@ export function Chart({ data }: Props) {
|
|||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dataKey={`${serie.id}:count`}
|
dataKey={`${serie.id}:count`}
|
||||||
stroke={useDashedLastLine ? 'transparent' : color}
|
stroke={useDashedLastLine ? `url(#stroke${color})` : color}
|
||||||
// Use for legend
|
// Use for legend
|
||||||
fill={color}
|
fill={color}
|
||||||
/>
|
/>
|
||||||
@@ -221,31 +198,6 @@ export function Chart({ data }: Props) {
|
|||||||
fillOpacity={0.1}
|
fillOpacity={0.1}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{useDashedLastLine && (
|
|
||||||
<>
|
|
||||||
<Line
|
|
||||||
dot={false}
|
|
||||||
type={lineType}
|
|
||||||
name={`${serie.id}:dashed:noTooltip`}
|
|
||||||
isAnimationActive={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
stroke={`url('#hideAllButLastInterval_${serie.id}')`}
|
|
||||||
strokeDasharray="2 4"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeOpacity={0.7}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
dot={false}
|
|
||||||
type={lineType}
|
|
||||||
name={`${serie.id}:solid:noTooltip`}
|
|
||||||
isAnimationActive={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
stroke={`url('#hideJustLastInterval_${serie.id}')`}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{previous && (
|
{previous && (
|
||||||
<Line
|
<Line
|
||||||
type={lineType}
|
type={lineType}
|
||||||
|
|||||||
Reference in New Issue
Block a user