@karimov-labs/backstage-plugin-devxp 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.esm.js +69 -0
- package/dist/api.esm.js.map +1 -1
- package/dist/components/AnalyticsDashboard.esm.js +249 -0
- package/dist/components/AnalyticsDashboard.esm.js.map +1 -0
- package/dist/components/DeveloperComparison.esm.js +258 -0
- package/dist/components/DeveloperComparison.esm.js.map +1 -0
- package/dist/components/DeveloperDetails.esm.js +129 -0
- package/dist/components/DeveloperDetails.esm.js.map +1 -0
- package/dist/components/DeveloperLeaderboard.esm.js +340 -0
- package/dist/components/DeveloperLeaderboard.esm.js.map +1 -0
- package/dist/components/DevxpPage.esm.js +81 -3
- package/dist/components/DevxpPage.esm.js.map +1 -1
- package/dist/components/RepositoriesContent.esm.js +251 -0
- package/dist/components/RepositoriesContent.esm.js.map +1 -0
- package/dist/components/SpiderChart.esm.js +174 -0
- package/dist/components/SpiderChart.esm.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import React__default, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { Box, TextField, IconButton, Chip, FormControl, InputLabel, Select, MenuItem, CircularProgress, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Typography, LinearProgress, Tooltip, Button } from '@material-ui/core';
|
|
3
|
+
import Autocomplete from '@material-ui/lab/Autocomplete';
|
|
4
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
5
|
+
import SearchIcon from '@material-ui/icons/Search';
|
|
6
|
+
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
|
7
|
+
import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
|
|
8
|
+
import CompareArrowsIcon from '@material-ui/icons/CompareArrows';
|
|
9
|
+
import PersonIcon from '@material-ui/icons/Person';
|
|
10
|
+
import { InfoCard, EmptyState } from '@backstage/core-components';
|
|
11
|
+
|
|
12
|
+
const CATEGORIES = [
|
|
13
|
+
"Frontend Engineering",
|
|
14
|
+
"Backend Systems",
|
|
15
|
+
"Database & Data",
|
|
16
|
+
"Infrastructure (DevOps)",
|
|
17
|
+
"Mobile Development",
|
|
18
|
+
"AI & Machine Learning",
|
|
19
|
+
"Testing & QA",
|
|
20
|
+
"Security & Auth",
|
|
21
|
+
"Distributed Systems",
|
|
22
|
+
"Documentation"
|
|
23
|
+
];
|
|
24
|
+
const CATEGORY_COLORS = {
|
|
25
|
+
"Frontend Engineering": "#e91e63",
|
|
26
|
+
"Backend Systems": "#3f51b5",
|
|
27
|
+
"Database & Data": "#9c27b0",
|
|
28
|
+
"Infrastructure (DevOps)": "#ff9800",
|
|
29
|
+
"Mobile Development": "#4caf50",
|
|
30
|
+
"AI & Machine Learning": "#607d8b",
|
|
31
|
+
"Testing & QA": "#00bcd4",
|
|
32
|
+
"Security & Auth": "#f44336",
|
|
33
|
+
"Distributed Systems": "#795548",
|
|
34
|
+
"Documentation": "#9e9e9e"
|
|
35
|
+
};
|
|
36
|
+
const useStyles = makeStyles((theme) => ({
|
|
37
|
+
filters: {
|
|
38
|
+
display: "flex",
|
|
39
|
+
flexWrap: "wrap",
|
|
40
|
+
gap: theme.spacing(2),
|
|
41
|
+
marginBottom: theme.spacing(2),
|
|
42
|
+
alignItems: "flex-end"
|
|
43
|
+
},
|
|
44
|
+
searchField: {
|
|
45
|
+
minWidth: 200,
|
|
46
|
+
flex: 1
|
|
47
|
+
},
|
|
48
|
+
repoField: {
|
|
49
|
+
minWidth: 240,
|
|
50
|
+
flex: 2
|
|
51
|
+
},
|
|
52
|
+
selectField: {
|
|
53
|
+
minWidth: 160
|
|
54
|
+
},
|
|
55
|
+
categorySelect: {
|
|
56
|
+
minWidth: 200,
|
|
57
|
+
flex: 1
|
|
58
|
+
},
|
|
59
|
+
rankCell: {
|
|
60
|
+
fontWeight: 700,
|
|
61
|
+
color: theme.palette.text.secondary,
|
|
62
|
+
width: 48
|
|
63
|
+
},
|
|
64
|
+
userIdCell: {
|
|
65
|
+
fontFamily: "monospace",
|
|
66
|
+
fontWeight: 600
|
|
67
|
+
},
|
|
68
|
+
scoreBar: {
|
|
69
|
+
height: 6,
|
|
70
|
+
borderRadius: 3,
|
|
71
|
+
marginTop: 4
|
|
72
|
+
},
|
|
73
|
+
categoryChip: {
|
|
74
|
+
margin: theme.spacing(0.25),
|
|
75
|
+
fontSize: "0.65rem",
|
|
76
|
+
height: 18
|
|
77
|
+
},
|
|
78
|
+
clickableRow: {
|
|
79
|
+
cursor: "pointer",
|
|
80
|
+
"&:hover": {
|
|
81
|
+
backgroundColor: theme.palette.action.hover
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
pagination: {
|
|
85
|
+
display: "flex",
|
|
86
|
+
justifyContent: "space-between",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
padding: theme.spacing(1, 0),
|
|
89
|
+
marginTop: theme.spacing(1)
|
|
90
|
+
},
|
|
91
|
+
medalGold: { color: "#FFD700", fontWeight: 700 },
|
|
92
|
+
medalSilver: { color: "#C0C0C0", fontWeight: 700 },
|
|
93
|
+
medalBronze: { color: "#CD7F32", fontWeight: 700 },
|
|
94
|
+
actionButtons: {
|
|
95
|
+
display: "flex",
|
|
96
|
+
gap: theme.spacing(0.5)
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
const DeveloperLeaderboard = ({
|
|
100
|
+
api,
|
|
101
|
+
onViewDeveloper,
|
|
102
|
+
onCompareDeveloper,
|
|
103
|
+
compareList,
|
|
104
|
+
repoOptions: repoOptionsProp
|
|
105
|
+
}) => {
|
|
106
|
+
const classes = useStyles();
|
|
107
|
+
const [developers, setDevelopers] = useState([]);
|
|
108
|
+
const [totalCount, setTotalCount] = useState(0);
|
|
109
|
+
const [totalPages, setTotalPages] = useState(1);
|
|
110
|
+
const [loading, setLoading] = useState(true);
|
|
111
|
+
const [error, setError] = useState(null);
|
|
112
|
+
const [ownRepoOptions, setOwnRepoOptions] = useState([]);
|
|
113
|
+
const repoOptions = repoOptionsProp ?? ownRepoOptions;
|
|
114
|
+
const [filters, setFilters] = useState({
|
|
115
|
+
page: 1,
|
|
116
|
+
pageSize: 20,
|
|
117
|
+
categories: [],
|
|
118
|
+
repoNames: [],
|
|
119
|
+
sortBy: "avgProficiency",
|
|
120
|
+
sortOrder: "desc",
|
|
121
|
+
searchQuery: ""
|
|
122
|
+
});
|
|
123
|
+
const [searchInput, setSearchInput] = useState("");
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (repoOptionsProp !== void 0) return;
|
|
126
|
+
api.getRepositories({ pageSize: 200 }).then((data) => setOwnRepoOptions(data.repositories.map((r) => r.repoName))).catch(() => {
|
|
127
|
+
});
|
|
128
|
+
}, [api, repoOptionsProp]);
|
|
129
|
+
const load = useCallback(
|
|
130
|
+
async (f) => {
|
|
131
|
+
setLoading(true);
|
|
132
|
+
setError(null);
|
|
133
|
+
try {
|
|
134
|
+
const data = await api.getDeveloperLeaderboard(f);
|
|
135
|
+
setDevelopers(data.developers);
|
|
136
|
+
setTotalCount(data.totalCount);
|
|
137
|
+
setTotalPages(data.totalPages);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
setError(e.message ?? "Failed to load leaderboard");
|
|
140
|
+
} finally {
|
|
141
|
+
setLoading(false);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[api]
|
|
145
|
+
);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
load(filters);
|
|
148
|
+
}, [load, filters]);
|
|
149
|
+
const applySearch = () => {
|
|
150
|
+
setFilters((f) => ({ ...f, page: 1, searchQuery: searchInput }));
|
|
151
|
+
};
|
|
152
|
+
const handleKeyDown = (e) => {
|
|
153
|
+
if (e.key === "Enter") applySearch();
|
|
154
|
+
};
|
|
155
|
+
const toggleSort = (field) => {
|
|
156
|
+
setFilters((f) => ({
|
|
157
|
+
...f,
|
|
158
|
+
page: 1,
|
|
159
|
+
sortBy: field,
|
|
160
|
+
sortOrder: f.sortBy === field && f.sortOrder === "desc" ? "asc" : "desc"
|
|
161
|
+
}));
|
|
162
|
+
};
|
|
163
|
+
const SortIcon = ({ field }) => {
|
|
164
|
+
if (filters.sortBy !== field) return null;
|
|
165
|
+
return filters.sortOrder === "desc" ? /* @__PURE__ */ React__default.createElement(ArrowDownwardIcon, { style: { fontSize: 14, verticalAlign: "middle" } }) : /* @__PURE__ */ React__default.createElement(ArrowUpwardIcon, { style: { fontSize: 14, verticalAlign: "middle" } });
|
|
166
|
+
};
|
|
167
|
+
const rankLabel = (rank) => {
|
|
168
|
+
if (rank === 1) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalGold }, "\u{1F947} 1");
|
|
169
|
+
if (rank === 2) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalSilver }, "\u{1F948} 2");
|
|
170
|
+
if (rank === 3) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalBronze }, "\u{1F949} 3");
|
|
171
|
+
return /* @__PURE__ */ React__default.createElement("span", null, rank);
|
|
172
|
+
};
|
|
173
|
+
const globalRank = (idx) => (filters.page - 1) * filters.pageSize + idx + 1;
|
|
174
|
+
return /* @__PURE__ */ React__default.createElement(InfoCard, { title: `Developer Leaderboard (${totalCount})`, noPadding: false }, /* @__PURE__ */ React__default.createElement(Box, { className: classes.filters }, /* @__PURE__ */ React__default.createElement(
|
|
175
|
+
TextField,
|
|
176
|
+
{
|
|
177
|
+
className: classes.searchField,
|
|
178
|
+
size: "small",
|
|
179
|
+
label: "Search developer",
|
|
180
|
+
variant: "outlined",
|
|
181
|
+
value: searchInput,
|
|
182
|
+
onChange: (e) => setSearchInput(e.target.value),
|
|
183
|
+
onKeyDown: handleKeyDown,
|
|
184
|
+
InputProps: {
|
|
185
|
+
endAdornment: /* @__PURE__ */ React__default.createElement(IconButton, { size: "small", onClick: applySearch }, /* @__PURE__ */ React__default.createElement(SearchIcon, { fontSize: "small" }))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
), /* @__PURE__ */ React__default.createElement(
|
|
189
|
+
Autocomplete,
|
|
190
|
+
{
|
|
191
|
+
className: classes.repoField,
|
|
192
|
+
multiple: true,
|
|
193
|
+
options: repoOptions,
|
|
194
|
+
value: filters.repoNames,
|
|
195
|
+
onChange: (_e, value) => setFilters((f) => ({ ...f, page: 1, repoNames: value })),
|
|
196
|
+
renderTags: (value, getTagProps) => value.map((option, index) => /* @__PURE__ */ React__default.createElement(
|
|
197
|
+
Chip,
|
|
198
|
+
{
|
|
199
|
+
key: option,
|
|
200
|
+
label: option.split("/").pop() ?? option,
|
|
201
|
+
size: "small",
|
|
202
|
+
...getTagProps({ index })
|
|
203
|
+
}
|
|
204
|
+
)),
|
|
205
|
+
renderInput: (params) => /* @__PURE__ */ React__default.createElement(
|
|
206
|
+
TextField,
|
|
207
|
+
{
|
|
208
|
+
...params,
|
|
209
|
+
variant: "outlined",
|
|
210
|
+
size: "small",
|
|
211
|
+
label: "Repositories",
|
|
212
|
+
placeholder: filters.repoNames.length === 0 ? "Filter by repo\u2026" : ""
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
), /* @__PURE__ */ React__default.createElement(FormControl, { variant: "outlined", size: "small", className: classes.categorySelect }, /* @__PURE__ */ React__default.createElement(InputLabel, null, "Expertise domain"), /* @__PURE__ */ React__default.createElement(
|
|
217
|
+
Select,
|
|
218
|
+
{
|
|
219
|
+
multiple: true,
|
|
220
|
+
value: filters.categories,
|
|
221
|
+
onChange: (e) => setFilters((f) => ({ ...f, page: 1, categories: e.target.value })),
|
|
222
|
+
label: "Expertise domain",
|
|
223
|
+
renderValue: (selected) => selected.map((s) => s.split(" ")[0]).join(", ")
|
|
224
|
+
},
|
|
225
|
+
CATEGORIES.map((cat) => /* @__PURE__ */ React__default.createElement(MenuItem, { key: cat, value: cat }, cat))
|
|
226
|
+
)), /* @__PURE__ */ React__default.createElement(FormControl, { variant: "outlined", size: "small", className: classes.selectField }, /* @__PURE__ */ React__default.createElement(InputLabel, null, "Sort by"), /* @__PURE__ */ React__default.createElement(
|
|
227
|
+
Select,
|
|
228
|
+
{
|
|
229
|
+
value: filters.sortBy,
|
|
230
|
+
onChange: (e) => setFilters((f) => ({ ...f, page: 1, sortBy: e.target.value })),
|
|
231
|
+
label: "Sort by"
|
|
232
|
+
},
|
|
233
|
+
/* @__PURE__ */ React__default.createElement(MenuItem, { value: "avgProficiency" }, "Avg Proficiency"),
|
|
234
|
+
/* @__PURE__ */ React__default.createElement(MenuItem, { value: "domainSkillsScore" }, "Domain Score")
|
|
235
|
+
))), loading ? /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "center", py: 4 }, /* @__PURE__ */ React__default.createElement(CircularProgress, null)) : error ? /* @__PURE__ */ React__default.createElement(
|
|
236
|
+
EmptyState,
|
|
237
|
+
{
|
|
238
|
+
missing: "data",
|
|
239
|
+
title: "Leaderboard unavailable",
|
|
240
|
+
description: error
|
|
241
|
+
}
|
|
242
|
+
) : developers.length === 0 ? /* @__PURE__ */ React__default.createElement(
|
|
243
|
+
EmptyState,
|
|
244
|
+
{
|
|
245
|
+
missing: "data",
|
|
246
|
+
title: "No developers found",
|
|
247
|
+
description: "Try adjusting the filters."
|
|
248
|
+
}
|
|
249
|
+
) : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(TableContainer, { component: Paper, variant: "outlined" }, /* @__PURE__ */ React__default.createElement(Table, { size: "small" }, /* @__PURE__ */ React__default.createElement(TableHead, null, /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.rankCell }, "#"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Developer"), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
|
|
250
|
+
"span",
|
|
251
|
+
{
|
|
252
|
+
style: { cursor: "pointer" },
|
|
253
|
+
onClick: () => toggleSort("avgProficiency")
|
|
254
|
+
},
|
|
255
|
+
"Avg Proficiency ",
|
|
256
|
+
/* @__PURE__ */ React__default.createElement(SortIcon, { field: "avgProficiency" })
|
|
257
|
+
)), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
|
|
258
|
+
"span",
|
|
259
|
+
{
|
|
260
|
+
style: { cursor: "pointer" },
|
|
261
|
+
onClick: () => toggleSort("domainSkillsScore")
|
|
262
|
+
},
|
|
263
|
+
"Domain Score ",
|
|
264
|
+
/* @__PURE__ */ React__default.createElement(SortIcon, { field: "domainSkillsScore" })
|
|
265
|
+
)), /* @__PURE__ */ React__default.createElement(TableCell, null, "Skills"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Top Domains"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React__default.createElement(TableBody, null, developers.map((dev, idx) => /* @__PURE__ */ React__default.createElement(
|
|
266
|
+
TableRow,
|
|
267
|
+
{
|
|
268
|
+
key: dev.userId,
|
|
269
|
+
className: classes.clickableRow,
|
|
270
|
+
onClick: () => onViewDeveloper(dev.userId)
|
|
271
|
+
},
|
|
272
|
+
/* @__PURE__ */ React__default.createElement(TableCell, { className: classes.rankCell }, rankLabel(globalRank(idx))),
|
|
273
|
+
/* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", className: classes.userIdCell }, dev.userId)),
|
|
274
|
+
/* @__PURE__ */ React__default.createElement(TableCell, { style: { minWidth: 120 } }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", style: { fontWeight: 600 } }, (dev.averageProficiency * 10).toFixed(1), "%"), /* @__PURE__ */ React__default.createElement(
|
|
275
|
+
LinearProgress,
|
|
276
|
+
{
|
|
277
|
+
variant: "determinate",
|
|
278
|
+
value: dev.averageProficiency * 10,
|
|
279
|
+
className: classes.scoreBar,
|
|
280
|
+
style: { color: "#3f51b5" }
|
|
281
|
+
}
|
|
282
|
+
)),
|
|
283
|
+
/* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2" }, dev.sumOfAveragedSkillsProficiency.toFixed(2))),
|
|
284
|
+
/* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2" }, dev.skillCount)),
|
|
285
|
+
/* @__PURE__ */ React__default.createElement(TableCell, { style: { maxWidth: 200 } }, dev.categoryBreakdown.sort((a, b) => b.averageProficiency - a.averageProficiency).slice(0, 3).map((cat) => /* @__PURE__ */ React__default.createElement(
|
|
286
|
+
Chip,
|
|
287
|
+
{
|
|
288
|
+
key: cat.category,
|
|
289
|
+
label: cat.category.split(" ")[0],
|
|
290
|
+
size: "small",
|
|
291
|
+
className: classes.categoryChip,
|
|
292
|
+
style: {
|
|
293
|
+
backgroundColor: `${CATEGORY_COLORS[cat.category] ?? "#9e9e9e"}20`,
|
|
294
|
+
color: CATEGORY_COLORS[cat.category] ?? "#9e9e9e"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
))),
|
|
298
|
+
/* @__PURE__ */ React__default.createElement(TableCell, { align: "right", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React__default.createElement(Box, { className: classes.actionButtons }, /* @__PURE__ */ React__default.createElement(Tooltip, { title: "View details" }, /* @__PURE__ */ React__default.createElement(
|
|
299
|
+
IconButton,
|
|
300
|
+
{
|
|
301
|
+
size: "small",
|
|
302
|
+
onClick: () => onViewDeveloper(dev.userId)
|
|
303
|
+
},
|
|
304
|
+
/* @__PURE__ */ React__default.createElement(PersonIcon, { fontSize: "small" })
|
|
305
|
+
)), /* @__PURE__ */ React__default.createElement(
|
|
306
|
+
Tooltip,
|
|
307
|
+
{
|
|
308
|
+
title: compareList.includes(dev.userId) ? "Added to comparison" : "Add to comparison"
|
|
309
|
+
},
|
|
310
|
+
/* @__PURE__ */ React__default.createElement(
|
|
311
|
+
IconButton,
|
|
312
|
+
{
|
|
313
|
+
size: "small",
|
|
314
|
+
color: compareList.includes(dev.userId) ? "primary" : "default",
|
|
315
|
+
onClick: () => onCompareDeveloper(dev.userId)
|
|
316
|
+
},
|
|
317
|
+
/* @__PURE__ */ React__default.createElement(CompareArrowsIcon, { fontSize: "small" })
|
|
318
|
+
)
|
|
319
|
+
)))
|
|
320
|
+
))))), /* @__PURE__ */ React__default.createElement(Box, { className: classes.pagination }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Showing ", (filters.page - 1) * filters.pageSize + 1, "\u2013", Math.min(filters.page * filters.pageSize, totalCount), " of ", totalCount), /* @__PURE__ */ React__default.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React__default.createElement(
|
|
321
|
+
Button,
|
|
322
|
+
{
|
|
323
|
+
size: "small",
|
|
324
|
+
disabled: filters.page <= 1,
|
|
325
|
+
onClick: () => setFilters((f) => ({ ...f, page: f.page - 1 }))
|
|
326
|
+
},
|
|
327
|
+
"Prev"
|
|
328
|
+
), /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption" }, filters.page, " / ", totalPages), /* @__PURE__ */ React__default.createElement(
|
|
329
|
+
Button,
|
|
330
|
+
{
|
|
331
|
+
size: "small",
|
|
332
|
+
disabled: filters.page >= totalPages,
|
|
333
|
+
onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 }))
|
|
334
|
+
},
|
|
335
|
+
"Next"
|
|
336
|
+
)))));
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export { DeveloperLeaderboard };
|
|
340
|
+
//# sourceMappingURL=DeveloperLeaderboard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeveloperLeaderboard.esm.js","sources":["../../src/components/DeveloperLeaderboard.tsx"],"sourcesContent":["import React, { useState, useCallback, useEffect } from 'react';\nimport {\n Box,\n Typography,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n TextField,\n Select,\n MenuItem,\n FormControl,\n InputLabel,\n Chip,\n IconButton,\n Button,\n CircularProgress,\n LinearProgress,\n Tooltip,\n} from '@material-ui/core';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport { makeStyles } from '@material-ui/core/styles';\nimport SearchIcon from '@material-ui/icons/Search';\nimport ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';\nimport ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';\nimport CompareArrowsIcon from '@material-ui/icons/CompareArrows';\nimport PersonIcon from '@material-ui/icons/Person';\nimport { InfoCard, EmptyState } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { LeaderboardDeveloper, LeaderboardFilters } from '../types';\n\nconst CATEGORIES = [\n 'Frontend Engineering',\n 'Backend Systems',\n 'Database & Data',\n 'Infrastructure (DevOps)',\n 'Mobile Development',\n 'AI & Machine Learning',\n 'Testing & QA',\n 'Security & Auth',\n 'Distributed Systems',\n 'Documentation',\n];\n\nconst CATEGORY_COLORS: Record<string, string> = {\n 'Frontend Engineering': '#e91e63',\n 'Backend Systems': '#3f51b5',\n 'Database & Data': '#9c27b0',\n 'Infrastructure (DevOps)': '#ff9800',\n 'Mobile Development': '#4caf50',\n 'AI & Machine Learning': '#607d8b',\n 'Testing & QA': '#00bcd4',\n 'Security & Auth': '#f44336',\n 'Distributed Systems': '#795548',\n 'Documentation': '#9e9e9e',\n};\n\nconst useStyles = makeStyles(theme => ({\n filters: {\n display: 'flex',\n flexWrap: 'wrap',\n gap: theme.spacing(2),\n marginBottom: theme.spacing(2),\n alignItems: 'flex-end',\n },\n searchField: {\n minWidth: 200,\n flex: 1,\n },\n repoField: {\n minWidth: 240,\n flex: 2,\n },\n selectField: {\n minWidth: 160,\n },\n categorySelect: {\n minWidth: 200,\n flex: 1,\n },\n rankCell: {\n fontWeight: 700,\n color: theme.palette.text.secondary,\n width: 48,\n },\n userIdCell: {\n fontFamily: 'monospace',\n fontWeight: 600,\n },\n scoreBar: {\n height: 6,\n borderRadius: 3,\n marginTop: 4,\n },\n categoryChip: {\n margin: theme.spacing(0.25),\n fontSize: '0.65rem',\n height: 18,\n },\n clickableRow: {\n cursor: 'pointer',\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n },\n pagination: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: theme.spacing(1, 0),\n marginTop: theme.spacing(1),\n },\n medalGold: { color: '#FFD700', fontWeight: 700 },\n medalSilver: { color: '#C0C0C0', fontWeight: 700 },\n medalBronze: { color: '#CD7F32', fontWeight: 700 },\n actionButtons: {\n display: 'flex',\n gap: theme.spacing(0.5),\n },\n}));\n\ninterface DeveloperLeaderboardProps {\n api: DevxpApi;\n onViewDeveloper: (userId: string) => void;\n onCompareDeveloper: (userId: string) => void;\n compareList: string[];\n /** Pre-loaded repo names from the parent. When provided the component skips its own fetch. */\n repoOptions?: string[];\n}\n\nexport const DeveloperLeaderboard = ({\n api,\n onViewDeveloper,\n onCompareDeveloper,\n compareList,\n repoOptions: repoOptionsProp,\n}: DeveloperLeaderboardProps) => {\n const classes = useStyles();\n\n const [developers, setDevelopers] = useState<LeaderboardDeveloper[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [totalPages, setTotalPages] = useState(1);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n // Use parent-provided options when available, otherwise fetch own\n const [ownRepoOptions, setOwnRepoOptions] = useState<string[]>([]);\n const repoOptions = repoOptionsProp ?? ownRepoOptions;\n\n const [filters, setFilters] = useState<LeaderboardFilters>({\n page: 1,\n pageSize: 20,\n categories: [],\n repoNames: [],\n sortBy: 'avgProficiency',\n sortOrder: 'desc',\n searchQuery: '',\n });\n const [searchInput, setSearchInput] = useState('');\n\n // Only fetch repo names ourselves if the parent didn't supply them\n useEffect(() => {\n if (repoOptionsProp !== undefined) return;\n api\n .getRepositories({ pageSize: 200 })\n .then(data => setOwnRepoOptions(data.repositories.map(r => r.repoName)))\n .catch(() => {});\n }, [api, repoOptionsProp]);\n\n const load = useCallback(\n async (f: LeaderboardFilters) => {\n setLoading(true);\n setError(null);\n try {\n const data = await api.getDeveloperLeaderboard(f);\n setDevelopers(data.developers);\n setTotalCount(data.totalCount);\n setTotalPages(data.totalPages);\n } catch (e: any) {\n setError(e.message ?? 'Failed to load leaderboard');\n } finally {\n setLoading(false);\n }\n },\n [api],\n );\n\n useEffect(() => {\n load(filters);\n }, [load, filters]);\n\n const applySearch = () => {\n setFilters(f => ({ ...f, page: 1, searchQuery: searchInput }));\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') applySearch();\n };\n\n const toggleSort = (field: 'avgProficiency' | 'domainSkillsScore') => {\n setFilters(f => ({\n ...f,\n page: 1,\n sortBy: field,\n sortOrder: f.sortBy === field && f.sortOrder === 'desc' ? 'asc' : 'desc',\n }));\n };\n\n const SortIcon = ({ field }: { field: 'avgProficiency' | 'domainSkillsScore' }) => {\n if (filters.sortBy !== field) return null;\n return filters.sortOrder === 'desc' ? (\n <ArrowDownwardIcon style={{ fontSize: 14, verticalAlign: 'middle' }} />\n ) : (\n <ArrowUpwardIcon style={{ fontSize: 14, verticalAlign: 'middle' }} />\n );\n };\n\n const rankLabel = (rank: number) => {\n if (rank === 1) return <span className={classes.medalGold}>🥇 1</span>;\n if (rank === 2) return <span className={classes.medalSilver}>🥈 2</span>;\n if (rank === 3) return <span className={classes.medalBronze}>🥉 3</span>;\n return <span>{rank}</span>;\n };\n\n const globalRank = (idx: number) => (filters.page - 1) * filters.pageSize + idx + 1;\n\n return (\n <InfoCard title={`Developer Leaderboard (${totalCount})`} noPadding={false}>\n {/* Filters */}\n <Box className={classes.filters}>\n <TextField\n className={classes.searchField}\n size=\"small\"\n label=\"Search developer\"\n variant=\"outlined\"\n value={searchInput}\n onChange={e => setSearchInput(e.target.value)}\n onKeyDown={handleKeyDown}\n InputProps={{\n endAdornment: (\n <IconButton size=\"small\" onClick={applySearch}>\n <SearchIcon fontSize=\"small\" />\n </IconButton>\n ),\n }}\n />\n <Autocomplete\n className={classes.repoField}\n multiple\n options={repoOptions}\n value={filters.repoNames}\n onChange={(_e, value) =>\n setFilters(f => ({ ...f, page: 1, repoNames: value }))\n }\n renderTags={(value, getTagProps) =>\n value.map((option, index) => (\n <Chip\n key={option}\n label={option.split('/').pop() ?? option}\n size=\"small\"\n {...getTagProps({ index })}\n />\n ))\n }\n renderInput={params => (\n <TextField\n {...params}\n variant=\"outlined\"\n size=\"small\"\n label=\"Repositories\"\n placeholder={filters.repoNames.length === 0 ? 'Filter by repo…' : ''}\n />\n )}\n />\n <FormControl variant=\"outlined\" size=\"small\" className={classes.categorySelect}>\n <InputLabel>Expertise domain</InputLabel>\n <Select\n multiple\n value={filters.categories}\n onChange={e =>\n setFilters(f => ({ ...f, page: 1, categories: e.target.value as string[] }))\n }\n label=\"Expertise domain\"\n renderValue={(selected: any) =>\n (selected as string[])\n .map(s => s.split(' ')[0])\n .join(', ')\n }\n >\n {CATEGORIES.map(cat => (\n <MenuItem key={cat} value={cat}>\n {cat}\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n <FormControl variant=\"outlined\" size=\"small\" className={classes.selectField}>\n <InputLabel>Sort by</InputLabel>\n <Select\n value={filters.sortBy}\n onChange={e =>\n setFilters(f => ({ ...f, page: 1, sortBy: e.target.value as any }))\n }\n label=\"Sort by\"\n >\n <MenuItem value=\"avgProficiency\">Avg Proficiency</MenuItem>\n <MenuItem value=\"domainSkillsScore\">Domain Score</MenuItem>\n </Select>\n </FormControl>\n </Box>\n\n {loading ? (\n <Box display=\"flex\" justifyContent=\"center\" py={4}>\n <CircularProgress />\n </Box>\n ) : error ? (\n <EmptyState\n missing=\"data\"\n title=\"Leaderboard unavailable\"\n description={error}\n />\n ) : developers.length === 0 ? (\n <EmptyState\n missing=\"data\"\n title=\"No developers found\"\n description=\"Try adjusting the filters.\"\n />\n ) : (\n <React.Fragment>\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell className={classes.rankCell}>#</TableCell>\n <TableCell>Developer</TableCell>\n <TableCell>\n <span\n style={{ cursor: 'pointer' }}\n onClick={() => toggleSort('avgProficiency')}\n >\n Avg Proficiency <SortIcon field=\"avgProficiency\" />\n </span>\n </TableCell>\n <TableCell>\n <span\n style={{ cursor: 'pointer' }}\n onClick={() => toggleSort('domainSkillsScore')}\n >\n Domain Score <SortIcon field=\"domainSkillsScore\" />\n </span>\n </TableCell>\n <TableCell>Skills</TableCell>\n <TableCell>Top Domains</TableCell>\n <TableCell align=\"right\">Actions</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {developers.map((dev, idx) => (\n <TableRow\n key={dev.userId}\n className={classes.clickableRow}\n onClick={() => onViewDeveloper(dev.userId)}\n >\n <TableCell className={classes.rankCell}>\n {rankLabel(globalRank(idx))}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" className={classes.userIdCell}>\n {dev.userId}\n </Typography>\n </TableCell>\n <TableCell style={{ minWidth: 120 }}>\n <Typography variant=\"body2\" style={{ fontWeight: 600 }}>\n {(dev.averageProficiency * 10).toFixed(1)}%\n </Typography>\n <LinearProgress\n variant=\"determinate\"\n value={dev.averageProficiency * 10}\n className={classes.scoreBar}\n style={{ color: '#3f51b5' }}\n />\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">\n {dev.sumOfAveragedSkillsProficiency.toFixed(2)}\n </Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{dev.skillCount}</Typography>\n </TableCell>\n <TableCell style={{ maxWidth: 200 }}>\n {dev.categoryBreakdown\n .sort((a, b) => b.averageProficiency - a.averageProficiency)\n .slice(0, 3)\n .map(cat => (\n <Chip\n key={cat.category}\n label={cat.category.split(' ')[0]}\n size=\"small\"\n className={classes.categoryChip}\n style={{\n backgroundColor: `${CATEGORY_COLORS[cat.category] ?? '#9e9e9e'}20`,\n color: CATEGORY_COLORS[cat.category] ?? '#9e9e9e',\n }}\n />\n ))}\n </TableCell>\n <TableCell align=\"right\" onClick={e => e.stopPropagation()}>\n <Box className={classes.actionButtons}>\n <Tooltip title=\"View details\">\n <IconButton\n size=\"small\"\n onClick={() => onViewDeveloper(dev.userId)}\n >\n <PersonIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n <Tooltip\n title={\n compareList.includes(dev.userId)\n ? 'Added to comparison'\n : 'Add to comparison'\n }\n >\n <IconButton\n size=\"small\"\n color={compareList.includes(dev.userId) ? 'primary' : 'default'}\n onClick={() => onCompareDeveloper(dev.userId)}\n >\n <CompareArrowsIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </Box>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n\n {/* Pagination */}\n <Box className={classes.pagination}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Showing {(filters.page - 1) * filters.pageSize + 1}–\n {Math.min(filters.page * filters.pageSize, totalCount)} of {totalCount}\n </Typography>\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }}>\n <Button\n size=\"small\"\n disabled={filters.page <= 1}\n onClick={() => setFilters(f => ({ ...f, page: f.page - 1 }))}\n >\n Prev\n </Button>\n <Typography variant=\"caption\">\n {filters.page} / {totalPages}\n </Typography>\n <Button\n size=\"small\"\n disabled={filters.page >= totalPages}\n onClick={() => setFilters(f => ({ ...f, page: f.page + 1 }))}\n >\n Next\n </Button>\n </Box>\n </Box>\n </React.Fragment>\n )}\n </InfoCard>\n );\n};\n"],"names":["React"],"mappings":"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"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Page, Header, Content } from '@backstage/core-components';
|
|
3
3
|
import { useApi, fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
|
|
4
|
-
import { Tabs, Tab, Box } from '@material-ui/core';
|
|
4
|
+
import { Tabs, Tab, Box, Button } from '@material-ui/core';
|
|
5
|
+
import CompareArrowsIcon from '@material-ui/icons/CompareArrows';
|
|
5
6
|
import { DevxpClient } from '../api.esm.js';
|
|
7
|
+
import { AnalyticsDashboard } from './AnalyticsDashboard.esm.js';
|
|
6
8
|
import { DashboardContent } from './DashboardContent.esm.js';
|
|
7
9
|
import { SettingsContent } from './SettingsContent.esm.js';
|
|
10
|
+
import { DeveloperLeaderboard } from './DeveloperLeaderboard.esm.js';
|
|
11
|
+
import { DeveloperDetails } from './DeveloperDetails.esm.js';
|
|
12
|
+
import { DeveloperComparison } from './DeveloperComparison.esm.js';
|
|
13
|
+
import { RepositoriesContent } from './RepositoriesContent.esm.js';
|
|
8
14
|
|
|
9
15
|
const DevxpPage = () => {
|
|
10
16
|
const [tab, setTab] = React.useState(0);
|
|
@@ -14,6 +20,29 @@ const DevxpPage = () => {
|
|
|
14
20
|
() => new DevxpClient({ fetchApi, discoveryApi }),
|
|
15
21
|
[fetchApi, discoveryApi]
|
|
16
22
|
);
|
|
23
|
+
const [repoOptions, setRepoOptions] = React.useState([]);
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
api.getRepositories({ pageSize: 200 }).then((data) => setRepoOptions(data.repositories.map((r) => r.repoName))).catch(() => {
|
|
26
|
+
});
|
|
27
|
+
}, [api]);
|
|
28
|
+
const [devView, setDevView] = React.useState("leaderboard");
|
|
29
|
+
const [selectedUserId, setSelectedUserId] = React.useState("");
|
|
30
|
+
const [compareList, setCompareList] = React.useState([]);
|
|
31
|
+
const handleViewDeveloper = (userId) => {
|
|
32
|
+
setSelectedUserId(userId);
|
|
33
|
+
setDevView("details");
|
|
34
|
+
};
|
|
35
|
+
const handleToggleCompare = (userId) => {
|
|
36
|
+
setCompareList(
|
|
37
|
+
(prev) => prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev.slice(-1), userId]
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
const handleOpenComparison = () => {
|
|
41
|
+
setDevView("comparison");
|
|
42
|
+
};
|
|
43
|
+
const handleBackToLeaderboard = () => {
|
|
44
|
+
setDevView("leaderboard");
|
|
45
|
+
};
|
|
17
46
|
return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(
|
|
18
47
|
Header,
|
|
19
48
|
{
|
|
@@ -28,9 +57,58 @@ const DevxpPage = () => {
|
|
|
28
57
|
indicatorColor: "primary",
|
|
29
58
|
textColor: "primary"
|
|
30
59
|
},
|
|
31
|
-
/* @__PURE__ */ React.createElement(Tab, { label: "
|
|
60
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Analytics" }),
|
|
61
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Developers" }),
|
|
62
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Repositories" }),
|
|
63
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Identity" }),
|
|
32
64
|
/* @__PURE__ */ React.createElement(Tab, { label: "Settings" })
|
|
33
|
-
), /* @__PURE__ */ React.createElement(Box, { mt: 3 }, tab === 0 && /* @__PURE__ */ React.createElement(
|
|
65
|
+
), /* @__PURE__ */ React.createElement(Box, { mt: 3 }, tab === 0 && /* @__PURE__ */ React.createElement(AnalyticsDashboard, { api }), tab === 1 && /* @__PURE__ */ React.createElement(Box, null, devView === "leaderboard" && /* @__PURE__ */ React.createElement(React.Fragment, null, compareList.length > 0 && /* @__PURE__ */ React.createElement(Box, { mb: 2, display: "flex", justifyContent: "flex-end" }, /* @__PURE__ */ React.createElement(
|
|
66
|
+
Button,
|
|
67
|
+
{
|
|
68
|
+
variant: "outlined",
|
|
69
|
+
color: "primary",
|
|
70
|
+
size: "small",
|
|
71
|
+
startIcon: /* @__PURE__ */ React.createElement(CompareArrowsIcon, null),
|
|
72
|
+
onClick: handleOpenComparison
|
|
73
|
+
},
|
|
74
|
+
"Compare (",
|
|
75
|
+
compareList.length,
|
|
76
|
+
")"
|
|
77
|
+
)), /* @__PURE__ */ React.createElement(
|
|
78
|
+
DeveloperLeaderboard,
|
|
79
|
+
{
|
|
80
|
+
api,
|
|
81
|
+
onViewDeveloper: handleViewDeveloper,
|
|
82
|
+
onCompareDeveloper: handleToggleCompare,
|
|
83
|
+
compareList,
|
|
84
|
+
repoOptions
|
|
85
|
+
}
|
|
86
|
+
)), devView === "details" && /* @__PURE__ */ React.createElement(
|
|
87
|
+
DeveloperDetails,
|
|
88
|
+
{
|
|
89
|
+
api,
|
|
90
|
+
userId: selectedUserId,
|
|
91
|
+
onBack: handleBackToLeaderboard,
|
|
92
|
+
onCompare: handleToggleCompare,
|
|
93
|
+
isInCompare: compareList.includes(selectedUserId)
|
|
94
|
+
}
|
|
95
|
+
), devView === "comparison" && /* @__PURE__ */ React.createElement(
|
|
96
|
+
DeveloperComparison,
|
|
97
|
+
{
|
|
98
|
+
api,
|
|
99
|
+
initialUserIds: compareList,
|
|
100
|
+
onBack: handleBackToLeaderboard
|
|
101
|
+
}
|
|
102
|
+
)), tab === 2 && /* @__PURE__ */ React.createElement(
|
|
103
|
+
RepositoriesContent,
|
|
104
|
+
{
|
|
105
|
+
api,
|
|
106
|
+
onViewDeveloper: (userId) => {
|
|
107
|
+
handleViewDeveloper(userId);
|
|
108
|
+
setTab(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
), tab === 3 && /* @__PURE__ */ React.createElement(DashboardContent, { api }), tab === 4 && /* @__PURE__ */ React.createElement(SettingsContent, { api }))));
|
|
34
112
|
};
|
|
35
113
|
|
|
36
114
|
export { DevxpPage };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevxpPage.esm.js","sources":["../../src/components/DevxpPage.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Header, Page, Content } from '@backstage/core-components';\nimport {\n useApi,\n fetchApiRef,\n discoveryApiRef,\n} from '@backstage/core-plugin-api';\nimport { Tabs, Tab, Box } from '@material-ui/core';\nimport { DevxpClient } from '../api';\nimport { DashboardContent } from './DashboardContent';\nimport { SettingsContent } from './SettingsContent';\n\nexport const DevxpPage = () => {\n const [tab, setTab] = React.useState(0);\n const fetchApi = useApi(fetchApiRef);\n const discoveryApi = useApi(discoveryApiRef);\n\n const api = React.useMemo(\n () => new DevxpClient({ fetchApi, discoveryApi }),\n [fetchApi, discoveryApi],\n );\n\n return (\n <Page themeId=\"tool\">\n <Header\n title=\"Developer Intelligence\"\n subtitle=\"DevXP Analytics & Developer Name Mapping\"\n />\n <Content>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n indicatorColor=\"primary\"\n textColor=\"primary\"\n >\n <Tab label=\"
|
|
1
|
+
{"version":3,"file":"DevxpPage.esm.js","sources":["../../src/components/DevxpPage.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Header, Page, Content } from '@backstage/core-components';\nimport {\n useApi,\n fetchApiRef,\n discoveryApiRef,\n} from '@backstage/core-plugin-api';\nimport { Tabs, Tab, Box, Button } from '@material-ui/core';\nimport CompareArrowsIcon from '@material-ui/icons/CompareArrows';\nimport { DevxpClient } from '../api';\nimport { AnalyticsDashboard } from './AnalyticsDashboard';\nimport { DashboardContent } from './DashboardContent';\nimport { SettingsContent } from './SettingsContent';\nimport { DeveloperLeaderboard } from './DeveloperLeaderboard';\nimport { DeveloperDetails } from './DeveloperDetails';\nimport { DeveloperComparison } from './DeveloperComparison';\nimport { RepositoriesContent } from './RepositoriesContent';\n\ntype DevView = 'leaderboard' | 'details' | 'comparison';\n\nexport const DevxpPage = () => {\n const [tab, setTab] = React.useState(0);\n const fetchApi = useApi(fetchApiRef);\n const discoveryApi = useApi(discoveryApiRef);\n\n const api = React.useMemo(\n () => new DevxpClient({ fetchApi, discoveryApi }),\n [fetchApi, discoveryApi],\n );\n\n // Pre-load repo names so the Developers filter is populated before the user visits that tab\n const [repoOptions, setRepoOptions] = React.useState<string[]>([]);\n React.useEffect(() => {\n api\n .getRepositories({ pageSize: 200 })\n .then(data => setRepoOptions(data.repositories.map(r => r.repoName)))\n .catch(() => {});\n }, [api]);\n\n // Developers tab state\n const [devView, setDevView] = React.useState<DevView>('leaderboard');\n const [selectedUserId, setSelectedUserId] = React.useState('');\n const [compareList, setCompareList] = React.useState<string[]>([]);\n\n const handleViewDeveloper = (userId: string) => {\n setSelectedUserId(userId);\n setDevView('details');\n };\n\n const handleToggleCompare = (userId: string) => {\n setCompareList(prev =>\n prev.includes(userId) ? prev.filter(id => id !== userId) : [...prev.slice(-1), userId],\n );\n };\n\n const handleOpenComparison = () => {\n setDevView('comparison');\n };\n\n const handleBackToLeaderboard = () => {\n setDevView('leaderboard');\n };\n\n return (\n <Page themeId=\"tool\">\n <Header\n title=\"Developer Intelligence\"\n subtitle=\"DevXP Analytics & Developer Name Mapping\"\n />\n <Content>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n indicatorColor=\"primary\"\n textColor=\"primary\"\n >\n <Tab label=\"Analytics\" />\n <Tab label=\"Developers\" />\n <Tab label=\"Repositories\" />\n <Tab label=\"Identity\" />\n <Tab label=\"Settings\" />\n </Tabs>\n <Box mt={3}>\n {tab === 0 && <AnalyticsDashboard api={api} />}\n\n {tab === 1 && (\n <Box>\n {devView === 'leaderboard' && (\n <>\n {compareList.length > 0 && (\n <Box mb={2} display=\"flex\" justifyContent=\"flex-end\">\n <Button\n variant=\"outlined\"\n color=\"primary\"\n size=\"small\"\n startIcon={<CompareArrowsIcon />}\n onClick={handleOpenComparison}\n >\n Compare ({compareList.length})\n </Button>\n </Box>\n )}\n <DeveloperLeaderboard\n api={api}\n onViewDeveloper={handleViewDeveloper}\n onCompareDeveloper={handleToggleCompare}\n compareList={compareList}\n repoOptions={repoOptions}\n />\n </>\n )}\n\n {devView === 'details' && (\n <DeveloperDetails\n api={api}\n userId={selectedUserId}\n onBack={handleBackToLeaderboard}\n onCompare={handleToggleCompare}\n isInCompare={compareList.includes(selectedUserId)}\n />\n )}\n\n {devView === 'comparison' && (\n <DeveloperComparison\n api={api}\n initialUserIds={compareList}\n onBack={handleBackToLeaderboard}\n />\n )}\n </Box>\n )}\n\n {tab === 2 && (\n <RepositoriesContent\n api={api}\n onViewDeveloper={userId => {\n handleViewDeveloper(userId);\n setTab(1);\n }}\n />\n )}\n {tab === 3 && <DashboardContent api={api} />}\n {tab === 4 && <SettingsContent api={api} />}\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":"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"}
|