feat: improve activity chart on profile
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
format,
|
format,
|
||||||
formatISO,
|
formatISO,
|
||||||
isSameMonth,
|
isSameMonth,
|
||||||
|
isToday,
|
||||||
startOfMonth,
|
startOfMonth,
|
||||||
subMonths,
|
subMonths,
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
@@ -18,15 +19,26 @@ import {
|
|||||||
WidgetTitle,
|
WidgetTitle,
|
||||||
} from '../overview/overview-widget';
|
} from '../overview/overview-widget';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
|
import { Tooltiper } from '../ui/tooltip';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: { count: number; date: string }[];
|
data: { count: number; date: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getOpacityLevel(count: number, maxCount: number): number {
|
||||||
|
if (count === 0 || maxCount === 0) return 0;
|
||||||
|
const ratio = count / maxCount;
|
||||||
|
if (ratio <= 0.25) return 0.25;
|
||||||
|
if (ratio <= 0.5) return 0.5;
|
||||||
|
if (ratio <= 0.75) return 0.75;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
const MonthCalendar = ({
|
const MonthCalendar = ({
|
||||||
month,
|
month,
|
||||||
data,
|
data,
|
||||||
}: { month: Date; data: Props['data'] }) => (
|
maxCount,
|
||||||
|
}: { month: Date; data: Props['data']; maxCount: number }) => (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 text-sm">{format(month, 'MMMM yyyy')}</div>
|
<div className="mb-2 text-sm">{format(month, 'MMMM yyyy')}</div>
|
||||||
<div className="-m-1 grid grid-cols-7 gap-1 p-1">
|
<div className="-m-1 grid grid-cols-7 gap-1 p-1">
|
||||||
@@ -37,14 +49,42 @@ const MonthCalendar = ({
|
|||||||
const hit = data.find((item) =>
|
const hit = data.find((item) =>
|
||||||
item.date.includes(formatISO(date, { representation: 'date' })),
|
item.date.includes(formatISO(date, { representation: 'date' })),
|
||||||
);
|
);
|
||||||
|
const opacity = hit ? getOpacityLevel(hit.count, maxCount) : 0;
|
||||||
return (
|
return (
|
||||||
<div
|
<Tooltiper
|
||||||
key={date.toISOString()}
|
key={date.toISOString()}
|
||||||
className={cn(
|
asChild
|
||||||
'aspect-square w-full rounded',
|
content={
|
||||||
hit ? 'bg-highlight' : 'bg-def-200',
|
<div className="text-sm col gap-1">
|
||||||
|
<div className="font-medium">{format(date, 'EEEE, MMM d')}</div>
|
||||||
|
{hit ? (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
{hit.count} {hit.count === 1 ? 'event' : 'events'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-muted-foreground">No activity</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'aspect-square w-full rounded cursor-default group hover:ring-1 hover:ring-foreground overflow-hidden',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'size-full group-hover:shadow-[inset_0_0_0_2px_var(--background)] rounded',
|
||||||
|
isToday(date)
|
||||||
|
? 'bg-highlight'
|
||||||
|
: hit
|
||||||
|
? 'bg-foreground'
|
||||||
|
: 'bg-def-200',
|
||||||
|
)}
|
||||||
|
style={hit && !isToday(date) ? { opacity } : undefined}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltiper>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +93,7 @@ const MonthCalendar = ({
|
|||||||
|
|
||||||
export const ProfileActivity = ({ data }: Props) => {
|
export const ProfileActivity = ({ data }: Props) => {
|
||||||
const [startDate, setStartDate] = useState(startOfMonth(new Date()));
|
const [startDate, setStartDate] = useState(startOfMonth(new Date()));
|
||||||
|
const maxCount = Math.max(...data.map((item) => item.count), 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget className="w-full">
|
<Widget className="w-full">
|
||||||
@@ -83,6 +124,7 @@ export const ProfileActivity = ({ data }: Props) => {
|
|||||||
key={offset}
|
key={offset}
|
||||||
month={subMonths(startDate, offset)}
|
month={subMonths(startDate, offset)}
|
||||||
data={data}
|
data={data}
|
||||||
|
maxCount={maxCount}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user