@karimov-labs/backstage-plugin-devxp 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -32,7 +32,7 @@ Built for use with the [DevXP](https://devxp.net) developer analytics platform.
32
32
  | Dependency | Version |
33
33
  |---|---|
34
34
  | Backstage | >= 1.30 |
35
- | `@karimov-labs/backstage-plugin-devxp-backend` | `^1.0.0` |
35
+ | `@karimov-labs/backstage-plugin-devxp-backend` | `^1.1.0` |
36
36
  | React | `^18` |
37
37
 
38
38
  ---
package/dist/api.esm.js CHANGED
@@ -117,6 +117,75 @@ class DevxpClient {
117
117
  if (!response.ok) return { message: "Auto-sync trigger failed" };
118
118
  return response.json();
119
119
  }
120
+ async getDashboardStats() {
121
+ const url = `${await this.baseUrl()}/analytics/dashboard`;
122
+ const response = await this.fetchApi.fetch(url, { method: "POST" });
123
+ if (!response.ok) throw new Error(`Failed to fetch dashboard stats: ${response.statusText}`);
124
+ return response.json();
125
+ }
126
+ async getDeveloperLeaderboard(filters = {}) {
127
+ const url = `${await this.baseUrl()}/analytics/leaderboard`;
128
+ const body = {
129
+ page: filters.page ?? 1,
130
+ pageSize: filters.pageSize ?? 20,
131
+ categories: filters.categories ?? [],
132
+ repoNames: filters.repoNames ?? [],
133
+ sortBy: filters.sortBy ?? "avgProficiency",
134
+ sortOrder: filters.sortOrder ?? "desc",
135
+ searchQuery: filters.searchQuery ?? ""
136
+ };
137
+ const response = await this.fetchApi.fetch(url, {
138
+ method: "POST",
139
+ headers: { "Content-Type": "application/json" },
140
+ body: JSON.stringify(body)
141
+ });
142
+ if (!response.ok) throw new Error(`Failed to fetch leaderboard: ${response.statusText}`);
143
+ return response.json();
144
+ }
145
+ async getDeveloperPerformance(userId) {
146
+ const url = `${await this.baseUrl()}/analytics/developer/performance`;
147
+ const response = await this.fetchApi.fetch(url, {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify({ userId })
151
+ });
152
+ if (!response.ok) throw new Error(`Failed to fetch developer performance: ${response.statusText}`);
153
+ return response.json();
154
+ }
155
+ async getDeveloperAveragePerformance(repoNames = []) {
156
+ const url = `${await this.baseUrl()}/analytics/developer/performance/average`;
157
+ const response = await this.fetchApi.fetch(url, {
158
+ method: "POST",
159
+ headers: { "Content-Type": "application/json" },
160
+ body: JSON.stringify({ repoNames })
161
+ });
162
+ if (!response.ok) throw new Error(`Failed to fetch average performance: ${response.statusText}`);
163
+ return response.json();
164
+ }
165
+ async getRepositories(filters = {}) {
166
+ const url = `${await this.baseUrl()}/analytics/repositories`;
167
+ const response = await this.fetchApi.fetch(url, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({
171
+ page: filters.page ?? 1,
172
+ pageSize: filters.pageSize ?? 50,
173
+ searchQuery: filters.searchQuery ?? ""
174
+ })
175
+ });
176
+ if (!response.ok) throw new Error(`Failed to fetch repositories: ${response.statusText}`);
177
+ return response.json();
178
+ }
179
+ async getRepositoryDetails(repoName) {
180
+ const url = `${await this.baseUrl()}/analytics/repository/details`;
181
+ const response = await this.fetchApi.fetch(url, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify({ repoName })
185
+ });
186
+ if (!response.ok) throw new Error(`Failed to fetch repository details: ${response.statusText}`);
187
+ return response.json();
188
+ }
120
189
  }
121
190
 
122
191
  export { DevxpClient };
@@ -1 +1 @@
1
- {"version":3,"file":"api.esm.js","sources":["../src/api.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport type {\n DevxpConfig,\n DeveloperMapping,\n UnmaskResult,\n HashResult,\n UploadResult,\n GithubSyncConfig,\n GithubSyncResult,\n} from './types';\n\nexport interface DevxpApi {\n getConfig(): Promise<DevxpConfig>;\n getMappings(): Promise<{ mappings: DeveloperMapping[] }>;\n uploadCsv(csvContent: string): Promise<UploadResult>;\n unmask(maskedName: string): Promise<UnmaskResult>;\n hash(realName: string): Promise<HashResult>;\n deleteMapping(maskedName: string): Promise<{ message: string }>;\n // GitHub sync\n getGithubSyncConfigs(): Promise<{ configs: GithubSyncConfig[] }>;\n createGithubSyncConfig(orgName: string, githubHostname: string, appClientId: string, appPrivateKey: string): Promise<{ id: number; message: string }>;\n toggleGithubSyncConfig(id: number, active: boolean): Promise<{ message: string }>;\n deleteGithubSyncConfig(id: number): Promise<{ message: string }>;\n syncGithubConfig(id: number): Promise<GithubSyncResult>;\n triggerAutoSync(): Promise<{ message: string }>;\n}\n\nexport const devxpApiRef = createApiRef<DevxpApi>({\n id: 'plugin.devxp.api',\n});\n\nexport class DevxpClient implements DevxpApi {\n private readonly fetchApi: { fetch: typeof fetch };\n private readonly discoveryApi: { getBaseUrl: (pluginId: string) => Promise<string> };\n\n constructor(options: {\n fetchApi: { fetch: typeof fetch };\n discoveryApi: { getBaseUrl: (pluginId: string) => Promise<string> };\n }) {\n this.fetchApi = options.fetchApi;\n this.discoveryApi = options.discoveryApi;\n }\n\n private async baseUrl(): Promise<string> {\n return this.discoveryApi.getBaseUrl('devxp');\n }\n\n async getConfig(): Promise<DevxpConfig> {\n const url = `${await this.baseUrl()}/config`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch config: ${response.statusText}`);\n return response.json();\n }\n\n async getMappings(): Promise<{ mappings: DeveloperMapping[] }> {\n const url = `${await this.baseUrl()}/mappings`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch mappings: ${response.statusText}`);\n return response.json();\n }\n\n async uploadCsv(csvContent: string): Promise<UploadResult> {\n const url = `${await this.baseUrl()}/mappings/upload`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ csvContent }),\n });\n const data = await response.json();\n if (!response.ok) return { message: data.error || 'Upload failed', count: 0, error: data.error };\n return data;\n }\n\n async unmask(maskedName: string): Promise<UnmaskResult> {\n const url = `${await this.baseUrl()}/unmask`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maskedName }),\n });\n if (!response.ok) throw new Error(`Failed to unmask: ${response.statusText}`);\n return response.json();\n }\n\n async hash(realName: string): Promise<HashResult> {\n const url = `${await this.baseUrl()}/hash`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ realName }),\n });\n if (!response.ok) throw new Error(`Failed to hash: ${response.statusText}`);\n return response.json();\n }\n\n async deleteMapping(maskedName: string): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/mappings/delete`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maskedName }),\n });\n if (!response.ok) throw new Error(`Failed to delete mapping: ${response.statusText}`);\n return response.json();\n }\n\n // ─── GitHub Sync ─────────────────────────────────────────────────────────────\n\n async getGithubSyncConfigs(): Promise<{ configs: GithubSyncConfig[] }> {\n const url = `${await this.baseUrl()}/github-sync`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch GitHub sync configs: ${response.statusText}`);\n return response.json();\n }\n\n async createGithubSyncConfig(\n orgName: string,\n githubHostname: string,\n appClientId: string,\n appPrivateKey: string,\n ): Promise<{ id: number; message: string }> {\n const url = `${await this.baseUrl()}/github-sync`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgName, githubHostname, appClientId, appPrivateKey }),\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to create GitHub sync config');\n return data;\n }\n\n async toggleGithubSyncConfig(id: number, active: boolean): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/${id}/toggle`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ active }),\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to toggle GitHub sync config');\n return data;\n }\n\n async deleteGithubSyncConfig(id: number): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/${id}`;\n const response = await this.fetchApi.fetch(url, {\n method: 'DELETE',\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to delete GitHub sync config');\n return data;\n }\n\n async syncGithubConfig(id: number): Promise<GithubSyncResult> {\n const url = `${await this.baseUrl()}/github-sync/${id}/sync`;\n const response = await this.fetchApi.fetch(url, { method: 'POST' });\n const data = await response.json();\n if (!response.ok) return { message: data.error || 'Sync failed', count: 0, orgName: '', error: data.error };\n return data;\n }\n\n async triggerAutoSync(): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/auto`;\n const response = await this.fetchApi.fetch(url, { method: 'POST' });\n if (!response.ok) return { message: 'Auto-sync trigger failed' };\n return response.json();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.esm.js","sources":["../src/api.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport type {\n DevxpConfig,\n DeveloperMapping,\n UnmaskResult,\n HashResult,\n UploadResult,\n GithubSyncConfig,\n GithubSyncResult,\n DashboardStats,\n DeveloperLeaderboard,\n LeaderboardFilters,\n DeveloperPerformance,\n DeveloperAveragePerformance,\n RepositoryList,\n RepositoryDetails,\n} from './types';\n\nexport interface DevxpApi {\n getConfig(): Promise<DevxpConfig>;\n getMappings(): Promise<{ mappings: DeveloperMapping[] }>;\n uploadCsv(csvContent: string): Promise<UploadResult>;\n unmask(maskedName: string): Promise<UnmaskResult>;\n hash(realName: string): Promise<HashResult>;\n deleteMapping(maskedName: string): Promise<{ message: string }>;\n // GitHub sync\n getGithubSyncConfigs(): Promise<{ configs: GithubSyncConfig[] }>;\n createGithubSyncConfig(orgName: string, githubHostname: string, appClientId: string, appPrivateKey: string): Promise<{ id: number; message: string }>;\n toggleGithubSyncConfig(id: number, active: boolean): Promise<{ message: string }>;\n deleteGithubSyncConfig(id: number): Promise<{ message: string }>;\n syncGithubConfig(id: number): Promise<GithubSyncResult>;\n triggerAutoSync(): Promise<{ message: string }>;\n // Analytics dashboard\n getDashboardStats(): Promise<DashboardStats>;\n getDeveloperLeaderboard(filters: Partial<LeaderboardFilters>): Promise<DeveloperLeaderboard>;\n getDeveloperPerformance(userId: string): Promise<DeveloperPerformance>;\n getDeveloperAveragePerformance(repoNames?: string[]): Promise<DeveloperAveragePerformance>;\n getRepositories(filters: { page?: number; pageSize?: number; searchQuery?: string }): Promise<RepositoryList>;\n getRepositoryDetails(repoName: string): Promise<RepositoryDetails>;\n}\n\nexport const devxpApiRef = createApiRef<DevxpApi>({\n id: 'plugin.devxp.api',\n});\n\nexport class DevxpClient implements DevxpApi {\n private readonly fetchApi: { fetch: typeof fetch };\n private readonly discoveryApi: { getBaseUrl: (pluginId: string) => Promise<string> };\n\n constructor(options: {\n fetchApi: { fetch: typeof fetch };\n discoveryApi: { getBaseUrl: (pluginId: string) => Promise<string> };\n }) {\n this.fetchApi = options.fetchApi;\n this.discoveryApi = options.discoveryApi;\n }\n\n private async baseUrl(): Promise<string> {\n return this.discoveryApi.getBaseUrl('devxp');\n }\n\n async getConfig(): Promise<DevxpConfig> {\n const url = `${await this.baseUrl()}/config`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch config: ${response.statusText}`);\n return response.json();\n }\n\n async getMappings(): Promise<{ mappings: DeveloperMapping[] }> {\n const url = `${await this.baseUrl()}/mappings`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch mappings: ${response.statusText}`);\n return response.json();\n }\n\n async uploadCsv(csvContent: string): Promise<UploadResult> {\n const url = `${await this.baseUrl()}/mappings/upload`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ csvContent }),\n });\n const data = await response.json();\n if (!response.ok) return { message: data.error || 'Upload failed', count: 0, error: data.error };\n return data;\n }\n\n async unmask(maskedName: string): Promise<UnmaskResult> {\n const url = `${await this.baseUrl()}/unmask`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maskedName }),\n });\n if (!response.ok) throw new Error(`Failed to unmask: ${response.statusText}`);\n return response.json();\n }\n\n async hash(realName: string): Promise<HashResult> {\n const url = `${await this.baseUrl()}/hash`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ realName }),\n });\n if (!response.ok) throw new Error(`Failed to hash: ${response.statusText}`);\n return response.json();\n }\n\n async deleteMapping(maskedName: string): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/mappings/delete`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maskedName }),\n });\n if (!response.ok) throw new Error(`Failed to delete mapping: ${response.statusText}`);\n return response.json();\n }\n\n // ─── GitHub Sync ─────────────────────────────────────────────────────────────\n\n async getGithubSyncConfigs(): Promise<{ configs: GithubSyncConfig[] }> {\n const url = `${await this.baseUrl()}/github-sync`;\n const response = await this.fetchApi.fetch(url);\n if (!response.ok) throw new Error(`Failed to fetch GitHub sync configs: ${response.statusText}`);\n return response.json();\n }\n\n async createGithubSyncConfig(\n orgName: string,\n githubHostname: string,\n appClientId: string,\n appPrivateKey: string,\n ): Promise<{ id: number; message: string }> {\n const url = `${await this.baseUrl()}/github-sync`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgName, githubHostname, appClientId, appPrivateKey }),\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to create GitHub sync config');\n return data;\n }\n\n async toggleGithubSyncConfig(id: number, active: boolean): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/${id}/toggle`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ active }),\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to toggle GitHub sync config');\n return data;\n }\n\n async deleteGithubSyncConfig(id: number): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/${id}`;\n const response = await this.fetchApi.fetch(url, {\n method: 'DELETE',\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.error || 'Failed to delete GitHub sync config');\n return data;\n }\n\n async syncGithubConfig(id: number): Promise<GithubSyncResult> {\n const url = `${await this.baseUrl()}/github-sync/${id}/sync`;\n const response = await this.fetchApi.fetch(url, { method: 'POST' });\n const data = await response.json();\n if (!response.ok) return { message: data.error || 'Sync failed', count: 0, orgName: '', error: data.error };\n return data;\n }\n\n async triggerAutoSync(): Promise<{ message: string }> {\n const url = `${await this.baseUrl()}/github-sync/auto`;\n const response = await this.fetchApi.fetch(url, { method: 'POST' });\n if (!response.ok) return { message: 'Auto-sync trigger failed' };\n return response.json();\n }\n\n async getDashboardStats(): Promise<DashboardStats> {\n const url = `${await this.baseUrl()}/analytics/dashboard`;\n const response = await this.fetchApi.fetch(url, { method: 'POST' });\n if (!response.ok) throw new Error(`Failed to fetch dashboard stats: ${response.statusText}`);\n return response.json();\n }\n\n async getDeveloperLeaderboard(filters: Partial<LeaderboardFilters> = {}): Promise<DeveloperLeaderboard> {\n const url = `${await this.baseUrl()}/analytics/leaderboard`;\n const body: Record<string, any> = {\n page: filters.page ?? 1,\n pageSize: filters.pageSize ?? 20,\n categories: filters.categories ?? [],\n repoNames: filters.repoNames ?? [],\n sortBy: filters.sortBy ?? 'avgProficiency',\n sortOrder: filters.sortOrder ?? 'desc',\n searchQuery: filters.searchQuery ?? '',\n };\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!response.ok) throw new Error(`Failed to fetch leaderboard: ${response.statusText}`);\n return response.json();\n }\n\n async getDeveloperPerformance(userId: string): Promise<DeveloperPerformance> {\n const url = `${await this.baseUrl()}/analytics/developer/performance`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ userId }),\n });\n if (!response.ok) throw new Error(`Failed to fetch developer performance: ${response.statusText}`);\n return response.json();\n }\n\n async getDeveloperAveragePerformance(repoNames: string[] = []): Promise<DeveloperAveragePerformance> {\n const url = `${await this.baseUrl()}/analytics/developer/performance/average`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ repoNames }),\n });\n if (!response.ok) throw new Error(`Failed to fetch average performance: ${response.statusText}`);\n return response.json();\n }\n\n async getRepositories(filters: { page?: number; pageSize?: number; searchQuery?: string } = {}): Promise<RepositoryList> {\n const url = `${await this.baseUrl()}/analytics/repositories`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n page: filters.page ?? 1,\n pageSize: filters.pageSize ?? 50,\n searchQuery: filters.searchQuery ?? '',\n }),\n });\n if (!response.ok) throw new Error(`Failed to fetch repositories: ${response.statusText}`);\n return response.json();\n }\n\n async getRepositoryDetails(repoName: string): Promise<RepositoryDetails> {\n const url = `${await this.baseUrl()}/analytics/repository/details`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ repoName }),\n });\n if (!response.ok) throw new Error(`Failed to fetch repository details: ${response.statusText}`);\n return response.json();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,249 @@
1
+ import React__default, { useState, useCallback, useEffect } from 'react';
2
+ import { Box, CircularProgress, Grid, Typography, LinearProgress, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Chip } from '@material-ui/core';
3
+ import { makeStyles } from '@material-ui/core/styles';
4
+ import TrendingUpIcon from '@material-ui/icons/TrendingUp';
5
+ import PeopleIcon from '@material-ui/icons/People';
6
+ import StorageIcon from '@material-ui/icons/Storage';
7
+ import BuildIcon from '@material-ui/icons/Build';
8
+ import { EmptyState, InfoCard } from '@backstage/core-components';
9
+
10
+ const useStyles = makeStyles((theme) => ({
11
+ statCard: {
12
+ display: "flex",
13
+ alignItems: "center",
14
+ gap: theme.spacing(2),
15
+ padding: theme.spacing(2.5)
16
+ },
17
+ statIcon: {
18
+ width: 48,
19
+ height: 48,
20
+ borderRadius: "50%",
21
+ display: "flex",
22
+ alignItems: "center",
23
+ justifyContent: "center",
24
+ flexShrink: 0
25
+ },
26
+ statValue: {
27
+ fontWeight: 700,
28
+ lineHeight: 1.1
29
+ },
30
+ statLabel: {
31
+ color: theme.palette.text.secondary,
32
+ marginTop: 2
33
+ },
34
+ categoryBar: {
35
+ height: 6,
36
+ borderRadius: 3,
37
+ backgroundColor: theme.palette.action.hover
38
+ },
39
+ categoryBarFill: {
40
+ borderRadius: 3,
41
+ transition: "width 0.6s ease"
42
+ },
43
+ proficiencyBar: {
44
+ height: 8,
45
+ borderRadius: 4,
46
+ marginTop: 4
47
+ },
48
+ skillChip: {
49
+ margin: theme.spacing(0.25),
50
+ fontSize: "0.7rem",
51
+ height: 22
52
+ },
53
+ timestampCell: {
54
+ fontSize: "0.75rem",
55
+ color: theme.palette.text.secondary,
56
+ whiteSpace: "nowrap"
57
+ },
58
+ notConfigured: {
59
+ padding: theme.spacing(3),
60
+ textAlign: "center"
61
+ },
62
+ categoryName: {
63
+ textTransform: "capitalize",
64
+ fontWeight: 500
65
+ },
66
+ timelineBar: {
67
+ display: "flex",
68
+ alignItems: "flex-end",
69
+ height: 60,
70
+ gap: 2,
71
+ overflow: "hidden"
72
+ },
73
+ timelineBarItem: {
74
+ flex: 1,
75
+ borderRadius: "2px 2px 0 0",
76
+ minWidth: 2,
77
+ backgroundColor: theme.palette.primary.main,
78
+ opacity: 0.7,
79
+ transition: "height 0.4s ease"
80
+ }
81
+ }));
82
+ const CATEGORY_COLORS = {
83
+ backend: "#3f51b5",
84
+ frontend: "#e91e63",
85
+ devops: "#ff9800",
86
+ mobile: "#4caf50",
87
+ data: "#9c27b0",
88
+ security: "#f44336",
89
+ testing: "#00bcd4",
90
+ architecture: "#795548",
91
+ ai: "#607d8b",
92
+ infrastructure: "#ff5722"
93
+ };
94
+ function categoryColor(category) {
95
+ return CATEGORY_COLORS[category.toLowerCase()] ?? "#9e9e9e";
96
+ }
97
+ function StatCard({ icon, iconBg, value, label }) {
98
+ const classes = useStyles();
99
+ return /* @__PURE__ */ React__default.createElement(Box, { className: classes.statCard }, /* @__PURE__ */ React__default.createElement(Box, { className: classes.statIcon, style: { backgroundColor: iconBg } }, icon), /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "h4", className: classes.statValue }, value.toLocaleString()), /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", className: classes.statLabel }, label)));
100
+ }
101
+ const AnalyticsDashboard = ({ api }) => {
102
+ const classes = useStyles();
103
+ const [stats, setStats] = useState(null);
104
+ const [loading, setLoading] = useState(true);
105
+ const [error, setError] = useState(null);
106
+ const loadStats = useCallback(async () => {
107
+ setLoading(true);
108
+ setError(null);
109
+ try {
110
+ const data = await api.getDashboardStats();
111
+ setStats(data);
112
+ } catch (e) {
113
+ setError(e.message ?? "Failed to load analytics");
114
+ } finally {
115
+ setLoading(false);
116
+ }
117
+ }, [api]);
118
+ useEffect(() => {
119
+ loadStats();
120
+ }, [loadStats]);
121
+ if (loading) {
122
+ return /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 200 }, /* @__PURE__ */ React__default.createElement(CircularProgress, null));
123
+ }
124
+ if (error) {
125
+ return /* @__PURE__ */ React__default.createElement(
126
+ EmptyState,
127
+ {
128
+ missing: "data",
129
+ title: "Analytics unavailable",
130
+ description: error.includes("not configured") ? "Configure devxp.apiEndpoint, devxp.apiToken, and devxp.projectId in app-config.yaml to enable the analytics dashboard." : error
131
+ }
132
+ );
133
+ }
134
+ if (!stats) return null;
135
+ const maxCategoryEvents = Math.max(...(stats.categoryBreakdown ?? []).map((c) => c.eventCount), 1);
136
+ const maxSkillUsage = Math.max(...(stats.topSkills ?? []).map((s) => s.usageCount), 1);
137
+ const maxTimelineCount = Math.max(...(stats.activityTimeline ?? []).map((t) => t.eventCount), 1);
138
+ return /* @__PURE__ */ React__default.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, sm: 6, md: 3 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "", noPadding: true }, /* @__PURE__ */ React__default.createElement(
139
+ StatCard,
140
+ {
141
+ icon: /* @__PURE__ */ React__default.createElement(PeopleIcon, { style: { color: "#fff" } }),
142
+ iconBg: "#3f51b5",
143
+ value: stats.totalUsers,
144
+ label: "Developers"
145
+ }
146
+ ))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, sm: 6, md: 3 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "", noPadding: true }, /* @__PURE__ */ React__default.createElement(
147
+ StatCard,
148
+ {
149
+ icon: /* @__PURE__ */ React__default.createElement(StorageIcon, { style: { color: "#fff" } }),
150
+ iconBg: "#009688",
151
+ value: stats.totalRepos,
152
+ label: "Repositories"
153
+ }
154
+ ))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, sm: 6, md: 3 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "", noPadding: true }, /* @__PURE__ */ React__default.createElement(
155
+ StatCard,
156
+ {
157
+ icon: /* @__PURE__ */ React__default.createElement(TrendingUpIcon, { style: { color: "#fff" } }),
158
+ iconBg: "#ff9800",
159
+ value: stats.totalEvents,
160
+ label: "Total Events"
161
+ }
162
+ ))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, sm: 6, md: 3 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "", noPadding: true }, /* @__PURE__ */ React__default.createElement(
163
+ StatCard,
164
+ {
165
+ icon: /* @__PURE__ */ React__default.createElement(BuildIcon, { style: { color: "#fff" } }),
166
+ iconBg: "#9c27b0",
167
+ value: stats.totalSkills,
168
+ label: "Unique Skills"
169
+ }
170
+ ))), stats.activityTimeline.length > 0 && /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: 8 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Activity (last 30 days)" }, /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(Box, { className: classes.timelineBar }, stats.activityTimeline.map((d) => /* @__PURE__ */ React__default.createElement(
171
+ Box,
172
+ {
173
+ key: d.date,
174
+ className: classes.timelineBarItem,
175
+ title: `${d.date}: ${d.eventCount} events`,
176
+ style: { height: `${Math.round(d.eventCount / maxTimelineCount * 100)}%` }
177
+ }
178
+ ))), /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "space-between", mt: 0.5 }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", color: "textSecondary" }, stats.activityTimeline[0]?.date), /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", color: "textSecondary" }, stats.activityTimeline[stats.activityTimeline.length - 1]?.date))))), stats.categoryBreakdown.length > 0 && /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: stats.activityTimeline.length > 0 ? 4 : 6 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Skill Categories" }, /* @__PURE__ */ React__default.createElement(Box, null, stats.categoryBreakdown.slice(0, 8).map((cat) => /* @__PURE__ */ React__default.createElement(Box, { key: cat.category, mb: 1.5 }, /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 0.5 }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", className: classes.categoryName }, cat.category), /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", color: "textSecondary" }, cat.eventCount.toLocaleString(), " events")), /* @__PURE__ */ React__default.createElement(
179
+ LinearProgress,
180
+ {
181
+ variant: "determinate",
182
+ value: cat.eventCount / maxCategoryEvents * 100,
183
+ className: classes.proficiencyBar,
184
+ style: { color: categoryColor(cat.category) }
185
+ }
186
+ )))))), stats.topSkills.length > 0 && /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Top Skills" }, /* @__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, null, "Skill"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Category"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Usage"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Avg score"))), /* @__PURE__ */ React__default.createElement(TableBody, null, stats.topSkills.slice(0, 10).map((skill) => /* @__PURE__ */ React__default.createElement(TableRow, { key: `${skill.skillName}-${skill.category}` }, /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", style: { fontWeight: 500 } }, skill.skillName), /* @__PURE__ */ React__default.createElement(
187
+ LinearProgress,
188
+ {
189
+ variant: "determinate",
190
+ value: skill.usageCount / maxSkillUsage * 100,
191
+ className: classes.proficiencyBar,
192
+ style: { color: categoryColor(skill.category) }
193
+ }
194
+ )), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
195
+ Chip,
196
+ {
197
+ label: skill.category,
198
+ size: "small",
199
+ className: classes.skillChip,
200
+ style: {
201
+ backgroundColor: `${categoryColor(skill.category)}20`,
202
+ color: categoryColor(skill.category)
203
+ }
204
+ }
205
+ )), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption" }, skill.usageCount.toLocaleString())), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption" }, (skill.avgProficiency * 100).toFixed(0), "%"))))))))), stats.recentActivity.length > 0 && /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: stats.topSkills.length > 0 ? 6 : 12 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Recent Activity" }, /* @__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, null, "Developer"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Skill"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Repository"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Score"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Time"))), /* @__PURE__ */ React__default.createElement(TableBody, null, stats.recentActivity.map((activity, idx) => /* @__PURE__ */ React__default.createElement(TableRow, { key: idx }, /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
206
+ Typography,
207
+ {
208
+ variant: "caption",
209
+ style: { fontFamily: "monospace" }
210
+ },
211
+ activity.userId.slice(0, 12),
212
+ "\u2026"
213
+ )), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", style: { fontWeight: 500 } }, activity.skillName), /* @__PURE__ */ React__default.createElement(
214
+ Chip,
215
+ {
216
+ label: activity.category,
217
+ size: "small",
218
+ className: classes.skillChip,
219
+ style: {
220
+ backgroundColor: `${categoryColor(activity.category)}20`,
221
+ color: categoryColor(activity.category),
222
+ marginLeft: 4
223
+ }
224
+ }
225
+ ))), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
226
+ Typography,
227
+ {
228
+ variant: "caption",
229
+ color: "textSecondary",
230
+ style: { fontFamily: "monospace" }
231
+ },
232
+ activity.repoName
233
+ )), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption" }, (activity.proficiencyScore * 100).toFixed(0), "%")), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React__default.createElement(Typography, { className: classes.timestampCell }, new Date(activity.timestamp).toLocaleString(void 0, {
234
+ month: "short",
235
+ day: "numeric",
236
+ hour: "2-digit",
237
+ minute: "2-digit"
238
+ })))))))))), stats.totalEvents === 0 && /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React__default.createElement(
239
+ EmptyState,
240
+ {
241
+ missing: "data",
242
+ title: "No analytics data yet",
243
+ description: "Analytics data will appear here once developers start committing code and dev-xp-analyzer processes their contributions."
244
+ }
245
+ )));
246
+ };
247
+
248
+ export { AnalyticsDashboard };
249
+ //# sourceMappingURL=AnalyticsDashboard.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsDashboard.esm.js","sources":["../../src/components/AnalyticsDashboard.tsx"],"sourcesContent":["import React, { useState, useEffect, useCallback } from 'react';\nimport {\n Typography,\n Grid,\n Box,\n Chip,\n LinearProgress,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n CircularProgress,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport TrendingUpIcon from '@material-ui/icons/TrendingUp';\nimport PeopleIcon from '@material-ui/icons/People';\nimport StorageIcon from '@material-ui/icons/Storage';\nimport BuildIcon from '@material-ui/icons/Build';\nimport { InfoCard, EmptyState } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { DashboardStats, CategoryData, TopSkillData, RecentActivityData } from '../types';\n\nconst useStyles = makeStyles(theme => ({\n statCard: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(2),\n padding: theme.spacing(2.5),\n },\n statIcon: {\n width: 48,\n height: 48,\n borderRadius: '50%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n },\n statValue: {\n fontWeight: 700,\n lineHeight: 1.1,\n },\n statLabel: {\n color: theme.palette.text.secondary,\n marginTop: 2,\n },\n categoryBar: {\n height: 6,\n borderRadius: 3,\n backgroundColor: theme.palette.action.hover,\n },\n categoryBarFill: {\n borderRadius: 3,\n transition: 'width 0.6s ease',\n },\n proficiencyBar: {\n height: 8,\n borderRadius: 4,\n marginTop: 4,\n },\n skillChip: {\n margin: theme.spacing(0.25),\n fontSize: '0.7rem',\n height: 22,\n },\n timestampCell: {\n fontSize: '0.75rem',\n color: theme.palette.text.secondary,\n whiteSpace: 'nowrap',\n },\n notConfigured: {\n padding: theme.spacing(3),\n textAlign: 'center',\n },\n categoryName: {\n textTransform: 'capitalize',\n fontWeight: 500,\n },\n timelineBar: {\n display: 'flex',\n alignItems: 'flex-end',\n height: 60,\n gap: 2,\n overflow: 'hidden',\n },\n timelineBarItem: {\n flex: 1,\n borderRadius: '2px 2px 0 0',\n minWidth: 2,\n backgroundColor: theme.palette.primary.main,\n opacity: 0.7,\n transition: 'height 0.4s ease',\n },\n}));\n\nconst CATEGORY_COLORS: Record<string, string> = {\n backend: '#3f51b5',\n frontend: '#e91e63',\n devops: '#ff9800',\n mobile: '#4caf50',\n data: '#9c27b0',\n security: '#f44336',\n testing: '#00bcd4',\n architecture: '#795548',\n ai: '#607d8b',\n infrastructure: '#ff5722',\n};\n\nfunction categoryColor(category: string): string {\n return CATEGORY_COLORS[category.toLowerCase()] ?? '#9e9e9e';\n}\n\ninterface StatCardProps {\n icon: React.ReactNode;\n iconBg: string;\n value: number;\n label: string;\n}\n\nfunction StatCard({ icon, iconBg, value, label }: StatCardProps) {\n const classes = useStyles();\n return (\n <Box className={classes.statCard}>\n <Box className={classes.statIcon} style={{ backgroundColor: iconBg }}>\n {icon}\n </Box>\n <Box>\n <Typography variant=\"h4\" className={classes.statValue}>\n {value.toLocaleString()}\n </Typography>\n <Typography variant=\"body2\" className={classes.statLabel}>\n {label}\n </Typography>\n </Box>\n </Box>\n );\n}\n\ninterface AnalyticsDashboardProps {\n api: DevxpApi;\n}\n\nexport const AnalyticsDashboard = ({ api }: AnalyticsDashboardProps) => {\n const classes = useStyles();\n const [stats, setStats] = useState<DashboardStats | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const loadStats = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const data = await api.getDashboardStats();\n setStats(data);\n } catch (e: any) {\n setError(e.message ?? 'Failed to load analytics');\n } finally {\n setLoading(false);\n }\n }, [api]);\n\n useEffect(() => {\n loadStats();\n }, [loadStats]);\n\n if (loading) {\n return (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" minHeight={200}>\n <CircularProgress />\n </Box>\n );\n }\n\n if (error) {\n return (\n <EmptyState\n missing=\"data\"\n title=\"Analytics unavailable\"\n description={error.includes('not configured')\n ? 'Configure devxp.apiEndpoint, devxp.apiToken, and devxp.projectId in app-config.yaml to enable the analytics dashboard.'\n : error}\n />\n );\n }\n\n if (!stats) return null;\n\n const maxCategoryEvents = Math.max(...(stats.categoryBreakdown ?? []).map(c => c.eventCount), 1);\n const maxSkillUsage = Math.max(...(stats.topSkills ?? []).map(s => s.usageCount), 1);\n const maxTimelineCount = Math.max(...(stats.activityTimeline ?? []).map(t => t.eventCount), 1);\n\n return (\n <Grid container spacing={3}>\n\n {/* ── Summary stats ──────────────────────────────────────────────── */}\n <Grid item xs={12} sm={6} md={3}>\n <InfoCard title=\"\" noPadding>\n <StatCard\n icon={<PeopleIcon style={{ color: '#fff' }} />}\n iconBg=\"#3f51b5\"\n value={stats.totalUsers}\n label=\"Developers\"\n />\n </InfoCard>\n </Grid>\n <Grid item xs={12} sm={6} md={3}>\n <InfoCard title=\"\" noPadding>\n <StatCard\n icon={<StorageIcon style={{ color: '#fff' }} />}\n iconBg=\"#009688\"\n value={stats.totalRepos}\n label=\"Repositories\"\n />\n </InfoCard>\n </Grid>\n <Grid item xs={12} sm={6} md={3}>\n <InfoCard title=\"\" noPadding>\n <StatCard\n icon={<TrendingUpIcon style={{ color: '#fff' }} />}\n iconBg=\"#ff9800\"\n value={stats.totalEvents}\n label=\"Total Events\"\n />\n </InfoCard>\n </Grid>\n <Grid item xs={12} sm={6} md={3}>\n <InfoCard title=\"\" noPadding>\n <StatCard\n icon={<BuildIcon style={{ color: '#fff' }} />}\n iconBg=\"#9c27b0\"\n value={stats.totalSkills}\n label=\"Unique Skills\"\n />\n </InfoCard>\n </Grid>\n\n {/* ── Activity timeline ──────────────────────────────────────────── */}\n {stats.activityTimeline.length > 0 && (\n <Grid item xs={12} md={8}>\n <InfoCard title=\"Activity (last 30 days)\">\n <Box>\n <Box className={classes.timelineBar}>\n {stats.activityTimeline.map(d => (\n <Box\n key={d.date}\n className={classes.timelineBarItem}\n title={`${d.date}: ${d.eventCount} events`}\n style={{ height: `${Math.round((d.eventCount / maxTimelineCount) * 100)}%` }}\n />\n ))}\n </Box>\n <Box display=\"flex\" justifyContent=\"space-between\" mt={0.5}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {stats.activityTimeline[0]?.date}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {stats.activityTimeline[stats.activityTimeline.length - 1]?.date}\n </Typography>\n </Box>\n </Box>\n </InfoCard>\n </Grid>\n )}\n\n {/* ── Category breakdown ─────────────────────────────────────────── */}\n {stats.categoryBreakdown.length > 0 && (\n <Grid item xs={12} md={stats.activityTimeline.length > 0 ? 4 : 6}>\n <InfoCard title=\"Skill Categories\">\n <Box>\n {stats.categoryBreakdown.slice(0, 8).map((cat: CategoryData) => (\n <Box key={cat.category} mb={1.5}>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={0.5}>\n <Typography variant=\"body2\" className={classes.categoryName}>\n {cat.category}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {cat.eventCount.toLocaleString()} events\n </Typography>\n </Box>\n <LinearProgress\n variant=\"determinate\"\n value={(cat.eventCount / maxCategoryEvents) * 100}\n className={classes.proficiencyBar}\n style={{ color: categoryColor(cat.category) }}\n />\n </Box>\n ))}\n </Box>\n </InfoCard>\n </Grid>\n )}\n\n {/* ── Top skills ─────────────────────────────────────────────────── */}\n {stats.topSkills.length > 0 && (\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Top Skills\">\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Skill</TableCell>\n <TableCell>Category</TableCell>\n <TableCell align=\"right\">Usage</TableCell>\n <TableCell align=\"right\">Avg score</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {stats.topSkills.slice(0, 10).map((skill: TopSkillData) => (\n <TableRow key={`${skill.skillName}-${skill.category}`}>\n <TableCell>\n <Typography variant=\"body2\" style={{ fontWeight: 500 }}>\n {skill.skillName}\n </Typography>\n <LinearProgress\n variant=\"determinate\"\n value={(skill.usageCount / maxSkillUsage) * 100}\n className={classes.proficiencyBar}\n style={{ color: categoryColor(skill.category) }}\n />\n </TableCell>\n <TableCell>\n <Chip\n label={skill.category}\n size=\"small\"\n className={classes.skillChip}\n style={{\n backgroundColor: `${categoryColor(skill.category)}20`,\n color: categoryColor(skill.category),\n }}\n />\n </TableCell>\n <TableCell align=\"right\">\n <Typography variant=\"caption\">{skill.usageCount.toLocaleString()}</Typography>\n </TableCell>\n <TableCell align=\"right\">\n <Typography variant=\"caption\">\n {(skill.avgProficiency * 100).toFixed(0)}%\n </Typography>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </InfoCard>\n </Grid>\n )}\n\n {/* ── Recent activity ────────────────────────────────────────────── */}\n {stats.recentActivity.length > 0 && (\n <Grid item xs={12} md={stats.topSkills.length > 0 ? 6 : 12}>\n <InfoCard title=\"Recent Activity\">\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Developer</TableCell>\n <TableCell>Skill</TableCell>\n <TableCell>Repository</TableCell>\n <TableCell align=\"right\">Score</TableCell>\n <TableCell align=\"right\">Time</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {stats.recentActivity.map((activity: RecentActivityData, idx: number) => (\n <TableRow key={idx}>\n <TableCell>\n <Typography\n variant=\"caption\"\n style={{ fontFamily: 'monospace' }}\n >\n {activity.userId.slice(0, 12)}…\n </Typography>\n </TableCell>\n <TableCell>\n <Box>\n <Typography variant=\"caption\" style={{ fontWeight: 500 }}>\n {activity.skillName}\n </Typography>\n <Chip\n label={activity.category}\n size=\"small\"\n className={classes.skillChip}\n style={{\n backgroundColor: `${categoryColor(activity.category)}20`,\n color: categoryColor(activity.category),\n marginLeft: 4,\n }}\n />\n </Box>\n </TableCell>\n <TableCell>\n <Typography\n variant=\"caption\"\n color=\"textSecondary\"\n style={{ fontFamily: 'monospace' }}\n >\n {activity.repoName}\n </Typography>\n </TableCell>\n <TableCell align=\"right\">\n <Typography variant=\"caption\">\n {(activity.proficiencyScore * 100).toFixed(0)}%\n </Typography>\n </TableCell>\n <TableCell align=\"right\">\n <Typography className={classes.timestampCell}>\n {new Date(activity.timestamp).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n })}\n </Typography>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </InfoCard>\n </Grid>\n )}\n\n {/* Empty state when there's no data at all */}\n {stats.totalEvents === 0 && (\n <Grid item xs={12}>\n <EmptyState\n missing=\"data\"\n title=\"No analytics data yet\"\n description=\"Analytics data will appear here once developers start committing code and dev-xp-analyzer processes their contributions.\"\n />\n </Grid>\n )}\n </Grid>\n );\n};\n"],"names":["React"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
1
+ import React__default, { useState, useCallback, useEffect } from 'react';
2
2
  import { Typography, Grid, Box, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Chip, TextField, Button } from '@material-ui/core';
3
3
  import { makeStyles } from '@material-ui/core/styles';
4
4
  import CheckCircleIcon from '@material-ui/icons/CheckCircle';
@@ -94,44 +94,44 @@ const DashboardContent = ({ api }) => {
94
94
  if (e.key === "Enter") handleUnmask();
95
95
  };
96
96
  if (loading) {
97
- return /* @__PURE__ */ React.createElement(Typography, null, "Loading configuration...");
97
+ return /* @__PURE__ */ React__default.createElement(Typography, null, "Loading configuration...");
98
98
  }
99
- return /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React.createElement(InfoCard, { title: "Configuration Status" }, config ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
99
+ return /* @__PURE__ */ React__default.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Configuration Status" }, config ? /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(
100
100
  ConfigItem,
101
101
  {
102
102
  label: "Mode",
103
103
  value: config.masked ? "Masked" : "Unmasked",
104
104
  ok: true
105
105
  }
106
- ), /* @__PURE__ */ React.createElement(
106
+ ), /* @__PURE__ */ React__default.createElement(
107
107
  ConfigItem,
108
108
  {
109
109
  label: "Salt",
110
110
  value: config.saltConfigured ? "Configured" : "Not configured",
111
111
  ok: config.saltConfigured
112
112
  }
113
- ), /* @__PURE__ */ React.createElement(
113
+ ), /* @__PURE__ */ React__default.createElement(
114
114
  ConfigItem,
115
115
  {
116
116
  label: "API Endpoint",
117
117
  value: config.apiEndpointConfigured ? "Configured" : "Not configured",
118
118
  ok: config.apiEndpointConfigured
119
119
  }
120
- ), /* @__PURE__ */ React.createElement(
120
+ ), /* @__PURE__ */ React__default.createElement(
121
121
  ConfigItem,
122
122
  {
123
123
  label: "API Token",
124
124
  value: config.apiTokenConfigured ? "Configured" : "Not configured",
125
125
  ok: config.apiTokenConfigured
126
126
  }
127
- ), /* @__PURE__ */ React.createElement(
127
+ ), /* @__PURE__ */ React__default.createElement(
128
128
  ConfigItem,
129
129
  {
130
130
  label: "Project ID",
131
131
  value: config.projectIdConfigured ? "Configured" : "Not configured",
132
132
  ok: config.projectIdConfigured
133
133
  }
134
- )) : /* @__PURE__ */ React.createElement(Typography, { color: "error" }, "Unable to load configuration. Is the backend plugin installed?"))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React.createElement(InfoCard, { title: "Developer Mappings" }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, config?.mappingCount ?? 0), /* @__PURE__ */ React.createElement(
134
+ )) : /* @__PURE__ */ React__default.createElement(Typography, { color: "error" }, "Unable to load configuration. Is the backend plugin installed?"))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Developer Mappings" }, /* @__PURE__ */ React__default.createElement(Box, { display: "flex", alignItems: "center" }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "h3" }, config?.mappingCount ?? 0), /* @__PURE__ */ React__default.createElement(
135
135
  Typography,
136
136
  {
137
137
  variant: "body1",
@@ -139,14 +139,14 @@ const DashboardContent = ({ api }) => {
139
139
  color: "textSecondary"
140
140
  },
141
141
  "developer name mappings stored"
142
- )), /* @__PURE__ */ React.createElement(Box, { mt: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Upload developer names via CSV or configure GitHub auto-sync in the Settings tab to populate the mapping database.")))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(InfoCard, { title: "GitHub Auto-Sync Configurations" }, syncConfigsLoading ? /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Loading sync configurations...") : syncConfigs.length === 0 ? /* @__PURE__ */ React.createElement(Typography, { className: classes.emptySync, variant: "body2" }, "No GitHub sync configurations. Go to Settings to register a GitHub App for automatic member syncing.") : /* @__PURE__ */ React.createElement(TableContainer, { component: Paper, variant: "outlined" }, /* @__PURE__ */ React.createElement(Table, { size: "small" }, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, null, "Organization"), /* @__PURE__ */ React.createElement(TableCell, null, "GitHub Host"), /* @__PURE__ */ React.createElement(TableCell, null, "Client ID"), /* @__PURE__ */ React.createElement(TableCell, null, "Status"), /* @__PURE__ */ React.createElement(TableCell, null, "Last Synced"), /* @__PURE__ */ React.createElement(TableCell, null, "Registered"))), /* @__PURE__ */ React.createElement(TableBody, null, syncConfigs.map((cfg) => /* @__PURE__ */ React.createElement(TableRow, { key: cfg.id }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("strong", null, cfg.org_name)), /* @__PURE__ */ React.createElement(TableCell, { style: { fontSize: "0.85em" } }, cfg.github_hostname ?? "github.com"), /* @__PURE__ */ React.createElement(TableCell, { style: { fontFamily: "monospace", fontSize: "0.85em" } }, cfg.app_client_id), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(
142
+ )), /* @__PURE__ */ React__default.createElement(Box, { mt: 2 }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Upload developer names via CSV or configure GitHub auto-sync in the Settings tab to populate the mapping database.")))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "GitHub Auto-Sync Configurations" }, syncConfigsLoading ? /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Loading sync configurations...") : syncConfigs.length === 0 ? /* @__PURE__ */ React__default.createElement(Typography, { className: classes.emptySync, variant: "body2" }, "No GitHub sync configurations. Go to Settings to register a GitHub App for automatic member syncing.") : /* @__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, null, "Organization"), /* @__PURE__ */ React__default.createElement(TableCell, null, "GitHub Host"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Client ID"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Status"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Last Synced"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Registered"))), /* @__PURE__ */ React__default.createElement(TableBody, null, syncConfigs.map((cfg) => /* @__PURE__ */ React__default.createElement(TableRow, { key: cfg.id }, /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement("strong", null, cfg.org_name)), /* @__PURE__ */ React__default.createElement(TableCell, { style: { fontSize: "0.85em" } }, cfg.github_hostname ?? "github.com"), /* @__PURE__ */ React__default.createElement(TableCell, { style: { fontFamily: "monospace", fontSize: "0.85em" } }, cfg.app_client_id), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
143
143
  Chip,
144
144
  {
145
145
  label: cfg.active ? "Active" : "Inactive",
146
146
  size: "small",
147
147
  className: cfg.active ? classes.activeChip : classes.inactiveChip
148
148
  }
149
- )), /* @__PURE__ */ React.createElement(TableCell, null, cfg.last_synced_at ? /* @__PURE__ */ React.createElement(Typography, { className: classes.lastSynced }, new Date(cfg.last_synced_at).toLocaleString()) : /* @__PURE__ */ React.createElement(Typography, { className: classes.lastSynced }, "Never")), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(Typography, { className: classes.lastSynced }, new Date(cfg.created_at).toLocaleDateString()))))))))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(InfoCard, { title: "Unmask Developer Name" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary", paragraph: true }, "Enter a masked developer username (16-character hex hash) to look up the original name from the mapping database."), /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "flex-start" }, /* @__PURE__ */ React.createElement(
149
+ )), /* @__PURE__ */ React__default.createElement(TableCell, null, cfg.last_synced_at ? /* @__PURE__ */ React__default.createElement(Typography, { className: classes.lastSynced }, new Date(cfg.last_synced_at).toLocaleString()) : /* @__PURE__ */ React__default.createElement(Typography, { className: classes.lastSynced }, "Never")), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { className: classes.lastSynced }, new Date(cfg.created_at).toLocaleDateString()))))))))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: "Unmask Developer Name" }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", color: "textSecondary", paragraph: true }, "Enter a masked developer username (16-character hex hash) to look up the original name from the mapping database."), /* @__PURE__ */ React__default.createElement(Box, { display: "flex", alignItems: "flex-start" }, /* @__PURE__ */ React__default.createElement(
150
150
  TextField,
151
151
  {
152
152
  label: "Masked username",
@@ -159,7 +159,7 @@ const DashboardContent = ({ api }) => {
159
159
  style: { minWidth: 300 },
160
160
  inputProps: { maxLength: 16 }
161
161
  }
162
- ), /* @__PURE__ */ React.createElement(
162
+ ), /* @__PURE__ */ React__default.createElement(
163
163
  Button,
164
164
  {
165
165
  variant: "contained",
@@ -169,28 +169,28 @@ const DashboardContent = ({ api }) => {
169
169
  style: { marginLeft: 12, height: 40 }
170
170
  },
171
171
  "Unmask"
172
- )), unmaskResult && /* @__PURE__ */ React.createElement(Box, { className: classes.unmaskResult }, /* @__PURE__ */ React.createElement(Typography, { className: classes.resultLabel, variant: "body2" }, "Masked:"), /* @__PURE__ */ React.createElement(Typography, { className: classes.resultValue }, unmaskResult.maskedName), /* @__PURE__ */ React.createElement(Box, { mt: 1 }, /* @__PURE__ */ React.createElement(Typography, { className: classes.resultLabel, variant: "body2" }, "Real Name:"), /* @__PURE__ */ React.createElement(Typography, { className: classes.resultValue }, unmaskResult.realName ? /* @__PURE__ */ React.createElement(
172
+ )), unmaskResult && /* @__PURE__ */ React__default.createElement(Box, { className: classes.unmaskResult }, /* @__PURE__ */ React__default.createElement(Typography, { className: classes.resultLabel, variant: "body2" }, "Masked:"), /* @__PURE__ */ React__default.createElement(Typography, { className: classes.resultValue }, unmaskResult.maskedName), /* @__PURE__ */ React__default.createElement(Box, { mt: 1 }, /* @__PURE__ */ React__default.createElement(Typography, { className: classes.resultLabel, variant: "body2" }, "Real Name:"), /* @__PURE__ */ React__default.createElement(Typography, { className: classes.resultValue }, unmaskResult.realName ? /* @__PURE__ */ React__default.createElement(
173
173
  Chip,
174
174
  {
175
175
  label: unmaskResult.realName,
176
176
  color: "primary",
177
177
  variant: "outlined"
178
178
  }
179
- ) : /* @__PURE__ */ React.createElement(
179
+ ) : /* @__PURE__ */ React__default.createElement(
180
180
  Chip,
181
181
  {
182
182
  label: "No mapping found",
183
183
  color: "default",
184
184
  variant: "outlined"
185
185
  }
186
- )))), unmaskError && /* @__PURE__ */ React.createElement(Box, { mt: 2 }, /* @__PURE__ */ React.createElement(Typography, { color: "error" }, unmaskError)))));
186
+ )))), unmaskError && /* @__PURE__ */ React__default.createElement(Box, { mt: 2 }, /* @__PURE__ */ React__default.createElement(Typography, { color: "error" }, unmaskError)))));
187
187
  };
188
188
  function ConfigItem({
189
189
  label,
190
190
  value,
191
191
  ok
192
192
  }) {
193
- return /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", mb: 1 }, ok ? /* @__PURE__ */ React.createElement(CheckCircleIcon, { style: { color: "#4caf50", marginRight: 8 }, fontSize: "small" }) : /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: "#f44336", marginRight: 8 }, fontSize: "small" }), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, /* @__PURE__ */ React.createElement("strong", null, label, ":"), " ", value));
193
+ return /* @__PURE__ */ React__default.createElement(Box, { display: "flex", alignItems: "center", mb: 1 }, ok ? /* @__PURE__ */ React__default.createElement(CheckCircleIcon, { style: { color: "#4caf50", marginRight: 8 }, fontSize: "small" }) : /* @__PURE__ */ React__default.createElement(ErrorIcon, { style: { color: "#f44336", marginRight: 8 }, fontSize: "small" }), /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2" }, /* @__PURE__ */ React__default.createElement("strong", null, label, ":"), " ", value));
194
194
  }
195
195
 
196
196
  export { DashboardContent };