UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: быстрый выбор проектов на home

This commit is contained in:
DCCONSTRUCTIONS
2026-04-29 22:37:30 +03:00
parent d9a63f5f74
commit d28f83fe5e
2 changed files with 113 additions and 87 deletions
@@ -43,11 +43,16 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
} = props;
const activeProject = projects.find((project: THomeProjectData) => project.id === selectedProjectId);
const orderedProjects = activeProject
const selectedProject = activeProject ?? projects[0];
const selectedProjectIndex = Math.max(
projects.findIndex((project: THomeProjectData) => project.id === selectedProject?.id),
0
);
const stackProjects = activeProject
? [activeProject, ...projects.filter((project: THomeProjectData) => project.id !== activeProject.id)]
: projects;
const visibleProjects = orderedProjects.slice(0, STACK_VISIBLE_LIMIT);
const visibleProjects = stackProjects.slice(0, STACK_VISIBLE_LIMIT);
const activityCountByProject = (recents ?? []).reduce<Record<string, number>>((acc, activity) => {
const projectId = getActivityProjectId(activity);
if (!projectId) return acc;
@@ -56,8 +61,6 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
return acc;
}, {});
const selectedProject =
orderedProjects.find((project: THomeProjectData) => project.id === selectedProjectId) ?? orderedProjects[0];
const stackHeight =
visibleProjects.length > 0 ? ACTIVE_CARD_HEIGHT + (visibleProjects.length - 1) * STACK_OFFSET : 228;
const isHorizontal = orientation === "horizontal";
@@ -191,19 +194,7 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
</button>
</div>
{isHorizontal ? (
<div className="nodedc-home-project-deck-scroller">
<div className="nodedc-home-project-deck-row flex items-start px-1 py-2">
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, true))}
</div>
</div>
) : (
<div className="relative" style={{ height: `${stackHeight}px` }}>
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, false))}
</div>
)}
<div className="nodedc-home-project-quick-section mt-4 rounded-[24px] bg-black/10 p-4 xl:mt-auto">
<div className="nodedc-home-project-quick-section mb-4 rounded-[24px] bg-black/10 p-4">
<div className="mb-3 flex items-center justify-between gap-3">
<div>
<div className="text-13 font-semibold text-primary">Быстрый выбор</div>
@@ -216,9 +207,11 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
</div>
<div className="nodedc-home-project-quick-list">
{orderedProjects.map((project: THomeProjectData) => {
{projects.map((project: THomeProjectData, index: number) => {
const analytics = analyticsMap[project.id];
const isActive = project.id === selectedProject?.id;
const completionRate = getCompletionRate(analytics);
const distanceFromActive = Math.min(Math.abs(index - selectedProjectIndex), 2);
return (
<button
@@ -226,6 +219,7 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
type="button"
className="nodedc-home-project-quick-button"
data-active={isActive}
data-distance={distanceFromActive}
aria-label={
isActive ? `Открыть рабочую область проекта ${project.name}` : `Выбрать проект ${project.name}`
}
@@ -240,45 +234,54 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
>
<span className="nodedc-home-project-quick-main">
<span className="nodedc-home-project-quick-logo">
<Logo logo={project.logo_props} size={14} />
<FolderOpenDot className="size-4" />
</span>
<span className="truncate">{project.identifier}</span>
</span>
<span className="nodedc-home-project-quick-metric">
<span className="nodedc-home-project-quick-dot" aria-hidden="true" />
<span>{getCompletionRate(analytics)}%</span>
<span className="nodedc-home-project-quick-name truncate">{project.name}</span>
</span>
<span className="nodedc-home-project-quick-rate">{completionRate}%</span>
</button>
);
})}
</div>
</div>
{selectedProject && (
<div className="nodedc-home-project-focus-grid mt-4 grid grid-cols-2 gap-3 md:grid-cols-3">
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Фокус</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{selectedProject.identifier}
</div>
</div>
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="flex items-center gap-1 text-[11px] tracking-[0.18em] text-placeholder uppercase">
<UsersRound className="size-3.5" />
<span>Команда</span>
</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{analyticsMap[selectedProject.id]?.total_members ?? 0}
</div>
</div>
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Контур</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{activityCountByProject[selectedProject.id] ?? 0} касаний
</div>
{isHorizontal ? (
<div className="nodedc-home-project-deck-scroller">
<div className="nodedc-home-project-deck-row flex items-start px-1 py-2">
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, true))}
</div>
</div>
) : (
<div className="relative" style={{ height: `${stackHeight}px` }}>
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, false))}
</div>
)}
{selectedProject && (
<div className="nodedc-home-project-focus-grid mt-4 grid grid-cols-2 gap-3 md:grid-cols-3">
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Фокус</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{selectedProject.identifier}
</div>
</div>
)}
</div>
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="flex items-center gap-1 text-[11px] tracking-[0.18em] text-placeholder uppercase">
<UsersRound className="size-3.5" />
<span>Команда</span>
</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{analyticsMap[selectedProject.id]?.total_members ?? 0}
</div>
</div>
<div className="nodedc-home-project-focus-item px-3 py-2">
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Контур</div>
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
{activityCountByProject[selectedProject.id] ?? 0} касаний
</div>
</div>
</div>
)}
</section>
);
}