109 lines
4.5 KiB
TypeScript
109 lines
4.5 KiB
TypeScript
/**
|
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* See the LICENSE file for details.
|
|
*/
|
|
|
|
import { useTranslation } from "@plane/i18n";
|
|
import type { IUser, TActivityEntityData, TProjectAnalyticsCount } from "@plane/types";
|
|
import { useCurrentTime } from "@/hooks/use-current-time";
|
|
import { getActivityProjectId, getCompletionRate, type THomeProjectData } from "@/components/home/home.utils";
|
|
|
|
type HomePageHeaderProps = {
|
|
currentUser?: IUser;
|
|
selectedProject?: THomeProjectData;
|
|
selectedProjectAnalytics?: TProjectAnalyticsCount;
|
|
recents?: TActivityEntityData[];
|
|
workspaceName?: string;
|
|
};
|
|
|
|
export function HomePageHeader(props: HomePageHeaderProps) {
|
|
const { currentUser, selectedProject, selectedProjectAnalytics, recents, workspaceName } = props;
|
|
const { currentLocale } = useTranslation();
|
|
const { currentTime } = useCurrentTime();
|
|
|
|
const timeString = new Intl.DateTimeFormat(currentLocale, {
|
|
timeZone: currentUser?.user_timezone,
|
|
hour12: false,
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
}).format(currentTime);
|
|
const dateString = new Intl.DateTimeFormat(currentLocale, {
|
|
weekday: "long",
|
|
day: "numeric",
|
|
month: "long",
|
|
}).format(currentTime);
|
|
const heroDateLabel = dateString.toLocaleUpperCase(currentLocale || "ru-RU");
|
|
|
|
const totalIssues = selectedProjectAnalytics?.total_issues ?? 0;
|
|
const completedIssues = selectedProjectAnalytics?.completed_issues ?? 0;
|
|
const openIssues = Math.max(totalIssues - completedIssues, 0);
|
|
const completionRate = getCompletionRate(selectedProjectAnalytics);
|
|
const recentTouchpoints = (recents ?? []).filter((activity) => {
|
|
if (!selectedProject) return true;
|
|
return getActivityProjectId(activity) === selectedProject.id;
|
|
}).length;
|
|
|
|
const marketMetrics = [
|
|
{ label: "Готовность", value: `${completionRate}%`, caption: `${completedIssues}/${totalIssues || 0}` },
|
|
{ label: "Открытые задачи", value: openIssues.toString(), caption: "в работе" },
|
|
{ label: "Касания 7 дней", value: recentTouchpoints.toString(), caption: "recent" },
|
|
];
|
|
const workspaceDisplayName = workspaceName?.trim() || "Workspace";
|
|
|
|
return (
|
|
<section className="nodedc-home-hero">
|
|
<div className="nodedc-home-hero-time">
|
|
<div className="text-[11px] font-semibold tracking-[0.16em] text-placeholder uppercase">{heroDateLabel}</div>
|
|
<div className="text-13 font-semibold text-primary">{timeString}</div>
|
|
</div>
|
|
|
|
<div className="nodedc-home-hero-grid">
|
|
<div className="nodedc-home-market-band">
|
|
<div className="min-w-0">
|
|
<div className="text-12 font-semibold text-black/[0.58]">Фокус</div>
|
|
<div className="mt-1 flex min-w-0 items-center gap-3">
|
|
<div className="min-w-0">
|
|
<div className="truncate text-24 leading-none font-semibold text-black nodedc-home-market-focus-title">
|
|
{selectedProject?.name ?? "Workspace"}
|
|
</div>
|
|
{selectedProject?.identifier && (
|
|
<div className="mt-1 truncate text-12 font-medium text-black/[0.54]">
|
|
{selectedProject.identifier}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid min-w-0 flex-1 gap-3 sm:grid-cols-3">
|
|
{marketMetrics.map((metric) => (
|
|
<div key={metric.label} className="min-w-0">
|
|
<div className="truncate text-12 font-medium text-black/[0.58]">{metric.label}</div>
|
|
<div className="mt-1 text-[26px] leading-none font-semibold text-black">{metric.value}</div>
|
|
<div className="nodedc-home-market-progress">
|
|
<div
|
|
className="h-full rounded-full bg-black"
|
|
style={{
|
|
width:
|
|
metric.label === "Готовность"
|
|
? `${Math.min(completionRate, 100)}%`
|
|
: `${Math.min(Number(metric.value) * 8 + 18, 100)}%`,
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="mt-1 text-11 text-black/[0.48]">{metric.caption}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="nodedc-home-hero-title-cell">
|
|
<div className="nodedc-home-hero-title-label">Workspace</div>
|
|
<h1>{workspaceDisplayName}</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|