Skip to content

Commit 421c640

Browse files
authored
Merge pull request #980 from githru/feat-title/959
[new feature](view): StoryLine chart 타이틀/서브타이틀 구현
2 parents 60f26c2 + 1a77946 commit 421c640

2 files changed

Lines changed: 136 additions & 51 deletions

File tree

packages/view/src/components/FolderActivityFlow/FolderActivityFlow.scss

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,65 @@
1414
--chart-color-9: #3a4776; // mono-sec
1515
--chart-color-10: #aa4b72; // mono-fai
1616

17+
--title-left-offset: 30px;
18+
--title-icon-size: 34px;
19+
--title-gap: 4px;
20+
1721

1822
padding: 1rem;
1923
margin-bottom: 2rem;
2024
background: $color-background;
2125
border-radius: 8px;
2226
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2327

24-
&__header {
28+
&__head {
29+
margin-top: 40px;
30+
margin-left: var(--title-left-offset);
2531
display: flex;
26-
justify-content: space-between;
32+
flex-direction: column;
2733
align-items: flex-start;
28-
margin-bottom: 1rem;
34+
gap: 0.25rem;
2935
}
3036

3137
&__title {
32-
margin: 0 0 0.5rem 0;
33-
font-size: 1.2rem;
34-
font-weight: 600;
38+
display: flex;
39+
align-items: center;
40+
column-gap: var(--title-gap);
3541
color: $color-white;
42+
font-size: 34px;
43+
letter-spacing: 0.25px;
44+
line-height: 42px;
45+
font-weight: $font-weight-regular;
46+
white-space: nowrap;
3647
}
3748

38-
&__subtitle {
39-
margin: 0 0 0.5rem 0;
40-
font-size: 0.875rem;
41-
color: $color-light-gray;
42-
}
43-
44-
&__mode-toggle {
45-
transition: all 0.2s ease;
49+
&__title-icon {
50+
font-size: var(--title-icon-size);
51+
width: var(--title-icon-size);
52+
height: var(--title-icon-size);
53+
transform: translateY(-2px);
54+
color: $color-white;
4655

47-
&:hover {
48-
transform: translateY(-1px);
49-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
56+
&.MuiSvgIcon-root {
57+
font-size: var(--title-icon-size);
5058
}
59+
}
5160

52-
&:active {
53-
transform: translateY(0);
54-
}
61+
&__title-text {
62+
display: flex;
63+
align-items: center;
64+
}
65+
66+
&__subtitle {
67+
color: $color-white;
68+
font-size: 20px;
69+
font-weight: $font-weight-regular;
5570
}
5671

5772
&__breadcrumb {
58-
padding-left: 50px;
73+
display: flex;
74+
align-items: center;
75+
margin-bottom: 0.5rem;
5976

6077
.MuiBreadcrumbs-separator {
6178
color: $color-white;
@@ -72,6 +89,7 @@
7289
font-size: $font-size-body;
7390
font-weight: $font-weight-regular;
7491
text-decoration: none;
92+
cursor: pointer;
7593

7694
&:hover {
7795
color: $color-white;
@@ -196,4 +214,4 @@
196214
font-size: 11px;
197215
}
198216
}
199-
}
217+
}

packages/view/src/components/FolderActivityFlow/FolderActivityFlow.tsx

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Breadcrumbs from "@mui/material/Breadcrumbs";
55
import Link from "@mui/material/Link";
66
import Typography from "@mui/material/Typography";
77
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
8+
import WorkspacePremiumRoundedIcon from "@mui/icons-material/WorkspacePremiumRounded";
89

910
import { useDataStore } from "store";
1011

@@ -37,6 +38,68 @@ const FolderActivityFlow = () => {
3738

3839
const breadcrumbs = useMemo(() => getBreadcrumbs(), [getBreadcrumbs]);
3940

41+
const { topContributorName, releaseRangeLabel } = useMemo(() => {
42+
if (!totalData || totalData.length === 0 || releaseTopFolderPaths.length === 0) {
43+
return {
44+
topContributorName: null,
45+
releaseRangeLabel: "...",
46+
};
47+
}
48+
49+
const currentDepth = currentPath === "" ? 1 : currentPath.split("/").length + 1;
50+
const releaseContributorActivities = extractReleaseBasedContributorActivities(
51+
totalData,
52+
releaseTopFolderPaths,
53+
currentDepth
54+
);
55+
56+
if (releaseContributorActivities.length === 0) {
57+
return {
58+
topContributorName: null,
59+
releaseRangeLabel: "...",
60+
};
61+
}
62+
63+
const contributorClocs = new Map<string, number>();
64+
releaseContributorActivities.forEach((activity) => {
65+
const current = contributorClocs.get(activity.contributorName) || 0;
66+
contributorClocs.set(activity.contributorName, current + activity.changes);
67+
});
68+
69+
let maxCloc = 0;
70+
let mostActiveContributor = "";
71+
contributorClocs.forEach((cloc, name) => {
72+
if (cloc > maxCloc) {
73+
maxCloc = cloc;
74+
mostActiveContributor = name;
75+
}
76+
});
77+
78+
const releaseIndices = Array.from(
79+
new Set(releaseContributorActivities.map((activity) => activity.releaseIndex))
80+
).sort((a, b) => a - b);
81+
const releaseTagByIndex = new Map<number, string>();
82+
releaseContributorActivities.forEach((activity) => {
83+
if (!releaseTagByIndex.has(activity.releaseIndex)) {
84+
releaseTagByIndex.set(activity.releaseIndex, activity.releaseTag);
85+
}
86+
});
87+
88+
const resolvedTags = releaseIndices.map((index) => {
89+
const tag = releaseTagByIndex.get(index);
90+
return tag && tag.trim().length > 0 ? tag : `Release ${index}`;
91+
});
92+
93+
const firstReleaseLabel = resolvedTags[0] || "...";
94+
const lastReleaseLabel = resolvedTags[resolvedTags.length - 1] || firstReleaseLabel;
95+
const rangeLabel = resolvedTags.length <= 1 ? firstReleaseLabel : `${firstReleaseLabel} to ${lastReleaseLabel}`;
96+
97+
return {
98+
topContributorName: mostActiveContributor || null,
99+
releaseRangeLabel: rangeLabel || "...",
100+
};
101+
}, [totalData, releaseTopFolderPaths, currentPath]);
102+
40103
useEffect(() => {
41104
if (!totalData || totalData.length === 0) {
42105
return;
@@ -84,43 +147,47 @@ const FolderActivityFlow = () => {
84147
});
85148
}, [totalData, releaseGroups, releaseTopFolderPaths, navigateToFolder, currentPath]);
86149

150+
const topContributorLabel = topContributorName || "...";
151+
87152
return (
88153
<div
89154
className="folder-activity-flow"
90155
ref={containerRef}
91156
>
92-
<div className="folder-activity-flow__header">
93-
<div>
94-
<p className="folder-activity-flow__title">Contributors Folder Activity Flow</p>
95-
<div className="folder-activity-flow__subtitle">Contributors moving between folders across releases</div>
157+
<div className="folder-activity-flow__head">
158+
<Breadcrumbs
159+
separator={<NavigateNextIcon fontSize="small" />}
160+
aria-label="breadcrumb"
161+
className="folder-activity-flow__breadcrumb"
162+
>
163+
{breadcrumbs.map((crumb, index) => {
164+
const isLast = index === breadcrumbs.length - 1;
165+
166+
if (isLast) {
167+
return <Typography key={crumb}>{crumb}</Typography>;
168+
}
169+
170+
return (
171+
<Link
172+
key={crumb}
173+
underline="none"
174+
component="button"
175+
onClick={() => navigateToBreadcrumb(index, breadcrumbs.length)}
176+
>
177+
{crumb}
178+
</Link>
179+
);
180+
})}
181+
</Breadcrumbs>
182+
183+
<div className="folder-activity-flow__title">
184+
<WorkspacePremiumRoundedIcon className="folder-activity-flow__title-icon" />
185+
<span className="folder-activity-flow__title-text">Top contributor is {topContributorLabel}</span>
96186
</div>
187+
188+
<div className="folder-activity-flow__subtitle">{releaseRangeLabel}</div>
97189
</div>
98190

99-
<Breadcrumbs
100-
separator={<NavigateNextIcon fontSize="small" />}
101-
aria-label="breadcrumb"
102-
className="folder-activity-flow__breadcrumb"
103-
>
104-
{breadcrumbs.map((crumb, index) => {
105-
const isLast = index === breadcrumbs.length - 1;
106-
107-
if (isLast) {
108-
return <Typography key={crumb}>{crumb}</Typography>;
109-
}
110-
111-
return (
112-
<Link
113-
key={crumb}
114-
underline="none"
115-
component="button"
116-
onClick={() => navigateToBreadcrumb(index, breadcrumbs.length)}
117-
sx={{ cursor: "pointer" }}
118-
>
119-
{crumb}
120-
</Link>
121-
);
122-
})}
123-
</Breadcrumbs>
124191
<svg
125192
className="folder-activity-flow__chart"
126193
ref={svgRef}

0 commit comments

Comments
 (0)