forked from github/plane
chore: info icon for activity graph (#1353)
This commit is contained in:
parent
c2caed8c42
commit
62392be5a3
@ -1,34 +1,141 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { CalendarGraph } from "components/ui";
|
import { Tooltip } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { renderDateFormat, renderShortNumericDateFormat } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { IUserActivity } from "types";
|
import { IUserActivity } from "types";
|
||||||
|
// constants
|
||||||
|
import { DAYS, MONTHS } from "constants/project";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
activities: IUserActivity[] | undefined;
|
activities: IUserActivity[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityGraph: React.FC<Props> = ({ activities }) => (
|
export const ActivityGraph: React.FC<Props> = ({ activities }) => {
|
||||||
<CalendarGraph
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
data={
|
|
||||||
activities?.map((activity) => ({
|
const [width, setWidth] = useState(0);
|
||||||
day: activity.created_date,
|
|
||||||
value: activity.activity_count,
|
const today = new Date();
|
||||||
})) ?? []
|
const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||||
|
const twoMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 2, 1);
|
||||||
|
const threeMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 3, 1);
|
||||||
|
const fourMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 4, 1);
|
||||||
|
const fiveMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 5, 1);
|
||||||
|
|
||||||
|
const recentMonths = [
|
||||||
|
fiveMonthsAgo,
|
||||||
|
fourMonthsAgo,
|
||||||
|
threeMonthsAgo,
|
||||||
|
twoMonthsAgo,
|
||||||
|
lastMonth,
|
||||||
|
today,
|
||||||
|
];
|
||||||
|
|
||||||
|
const getDatesOfMonth = (dateOfMonth: Date) => {
|
||||||
|
const month = dateOfMonth.getMonth();
|
||||||
|
const year = dateOfMonth.getFullYear();
|
||||||
|
|
||||||
|
const dates = [];
|
||||||
|
const date = new Date(year, month, 1);
|
||||||
|
|
||||||
|
while (date.getMonth() === month && date < new Date()) {
|
||||||
|
dates.push(renderDateFormat(new Date(date)));
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
}
|
}
|
||||||
from={activities?.length ? activities[0].created_date : new Date()}
|
|
||||||
to={activities?.length ? activities[activities.length - 1].created_date : new Date()}
|
return dates;
|
||||||
height="200px"
|
};
|
||||||
margin={{ bottom: 0, left: 10, right: 10, top: 0 }}
|
|
||||||
tooltip={(datum) => (
|
const recentDates = [
|
||||||
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
|
...getDatesOfMonth(recentMonths[0]),
|
||||||
<span className="text-brand-secondary">{renderShortDateWithYearFormat(datum.day)}:</span>{" "}
|
...getDatesOfMonth(recentMonths[1]),
|
||||||
{datum.value}
|
...getDatesOfMonth(recentMonths[2]),
|
||||||
|
...getDatesOfMonth(recentMonths[3]),
|
||||||
|
...getDatesOfMonth(recentMonths[4]),
|
||||||
|
...getDatesOfMonth(recentMonths[5]),
|
||||||
|
];
|
||||||
|
|
||||||
|
const activitiesIntensity = (activityCount: number) => {
|
||||||
|
if (activityCount <= 3) return "opacity-20";
|
||||||
|
else if (activityCount > 3 && activityCount <= 6) return "opacity-40";
|
||||||
|
else if (activityCount > 6 && activityCount <= 9) return "opacity-80";
|
||||||
|
else return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const addPaddingTiles = () => {
|
||||||
|
const firstDateDay = new Date(recentDates[0]).getDay();
|
||||||
|
|
||||||
|
for (let i = 0; i < firstDateDay; i++) recentDates.unshift("");
|
||||||
|
};
|
||||||
|
addPaddingTiles();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
setWidth(ref.current.offsetWidth);
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid place-items-center overflow-x-scroll">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="flex flex-col gap-2 pt-6">
|
||||||
|
{DAYS.map((day, index) => (
|
||||||
|
<h6 key={day} className="h-4 text-xs">
|
||||||
|
{index % 2 === 0 && day.substring(0, 3)}
|
||||||
|
</h6>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between" style={{ width: `${width}px` }}>
|
||||||
|
{recentMonths.map((month, index) => (
|
||||||
|
<h6 key={index} className="w-full text-xs">
|
||||||
|
{MONTHS[month.getMonth()].substring(0, 3)}
|
||||||
|
</h6>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-2 grid w-full grid-flow-col gap-2"
|
||||||
|
style={{ gridTemplateRows: "repeat(7, minmax(0, 1fr))" }}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
{recentDates.map((date, index) => {
|
||||||
|
const isActive = activities?.find((a) => a.created_date === date);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={`${date}-${index}`}
|
||||||
|
tooltipContent={`${
|
||||||
|
isActive ? isActive.activity_count : 0
|
||||||
|
} activities on ${renderShortNumericDateFormat(date)}`}
|
||||||
|
theme="dark"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
date === "" ? "pointer-events-none opacity-0" : ""
|
||||||
|
} h-4 w-4 rounded ${
|
||||||
|
isActive
|
||||||
|
? `bg-brand-accent ${activitiesIntensity(isActive.activity_count)}`
|
||||||
|
: "bg-brand-surface-2"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 flex items-center gap-2 text-xs">
|
||||||
|
<span>Less</span>
|
||||||
|
<span className="h-4 w-4 rounded bg-brand-surface-2" />
|
||||||
|
<span className="h-4 w-4 rounded bg-brand-accent opacity-20" />
|
||||||
|
<span className="h-4 w-4 rounded bg-brand-accent opacity-40" />
|
||||||
|
<span className="h-4 w-4 rounded bg-brand-accent opacity-80" />
|
||||||
|
<span className="h-4 w-4 rounded bg-brand-accent" />
|
||||||
|
<span>More</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
theme={{
|
);
|
||||||
background: "rgb(var(--color-bg-base))",
|
};
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// components
|
// components
|
||||||
import { ActivityGraph } from "components/workspace";
|
import { ActivityGraph } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader, Tooltip } from "components/ui";
|
||||||
|
// icons
|
||||||
|
import { InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IUserWorkspaceDashboard } from "types";
|
import { IUserWorkspaceDashboard } from "types";
|
||||||
|
|
||||||
@ -66,7 +68,15 @@ export const IssuesStats: React.FC<Props> = ({ data }) => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 lg:col-span-2">
|
<div className="p-4 lg:col-span-2">
|
||||||
<h3 className="mb-2 font-semibold capitalize">Activity Graph</h3>
|
<h3 className="mb-2 font-semibold capitalize flex items-center gap-2">
|
||||||
|
Activity Graph
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
|
||||||
|
className="w-72 border border-brand-base"
|
||||||
|
>
|
||||||
|
<InformationCircleIcon className="h-3 w-3" />
|
||||||
|
</Tooltip>
|
||||||
|
</h3>
|
||||||
<ActivityGraph activities={data?.issue_activities} />
|
<ActivityGraph activities={data?.issue_activities} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user