@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.
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardContent.esm.js","sources":["../../src/components/DashboardContent.tsx"],"sourcesContent":["import React, { useState, useEffect, useCallback } from 'react';\nimport {\n Typography,\n TextField,\n Button,\n Grid,\n Box,\n Chip,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport CheckCircleIcon from '@material-ui/icons/CheckCircle';\nimport ErrorIcon from '@material-ui/icons/Error';\nimport { InfoCard } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { DevxpConfig, UnmaskResult, GithubSyncConfig } from '../types';\n\nconst useStyles = makeStyles(theme => ({\n configGrid: {\n marginBottom: theme.spacing(3),\n },\n statusChip: {\n marginLeft: theme.spacing(1),\n },\n unmaskResult: {\n marginTop: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: theme.palette.background.default,\n borderRadius: theme.shape.borderRadius,\n },\n resultLabel: {\n color: theme.palette.text.secondary,\n marginBottom: theme.spacing(0.5),\n },\n resultValue: {\n fontFamily: 'monospace',\n fontSize: '1.1em',\n },\n activeChip: {\n backgroundColor: '#e8f5e9',\n color: '#2e7d32',\n },\n inactiveChip: {\n backgroundColor: '#f5f5f5',\n color: '#757575',\n },\n lastSynced: {\n fontSize: '0.75rem',\n color: theme.palette.text.secondary,\n },\n emptySync: {\n padding: theme.spacing(2),\n color: theme.palette.text.secondary,\n fontStyle: 'italic',\n },\n}));\n\ninterface DashboardContentProps {\n api: DevxpApi;\n}\n\nexport const DashboardContent = ({ api }: DashboardContentProps) => {\n const classes = useStyles();\n const [config, setConfig] = useState<DevxpConfig | null>(null);\n const [loading, setLoading] = useState(true);\n const [maskedInput, setMaskedInput] = useState('');\n const [unmaskResult, setUnmaskResult] = useState<UnmaskResult | null>(null);\n const [unmaskError, setUnmaskError] = useState('');\n const [syncConfigs, setSyncConfigs] = useState<GithubSyncConfig[]>([]);\n const [syncConfigsLoading, setSyncConfigsLoading] = useState(true);\n\n const loadConfig = useCallback(async () => {\n try {\n setLoading(true);\n const cfg = await api.getConfig();\n setConfig(cfg);\n } catch (e) {\n // Config not available\n } finally {\n setLoading(false);\n }\n }, [api]);\n\n const loadSyncConfigs = useCallback(async () => {\n try {\n setSyncConfigsLoading(true);\n const result = await api.getGithubSyncConfigs();\n setSyncConfigs(result.configs);\n } catch {\n // failed silently\n } finally {\n setSyncConfigsLoading(false);\n }\n }, [api]);\n\n useEffect(() => {\n loadConfig();\n loadSyncConfigs();\n // Trigger auto-sync on page load (throttled to 24h server-side)\n api.triggerAutoSync().catch(() => {/* silent */});\n }, [loadConfig, loadSyncConfigs, api]);\n\n const handleUnmask = async () => {\n if (!maskedInput.trim()) return;\n setUnmaskError('');\n setUnmaskResult(null);\n try {\n const result = await api.unmask(maskedInput.trim());\n setUnmaskResult(result);\n } catch (e: any) {\n setUnmaskError(e.message || 'Failed to unmask');\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') handleUnmask();\n };\n\n if (loading) {\n return <Typography>Loading configuration...</Typography>;\n }\n\n return (\n <Grid container spacing={3}>\n {/* Configuration Status */}\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Configuration Status\">\n {config ? (\n <Box>\n <ConfigItem\n label=\"Mode\"\n value={config.masked ? 'Masked' : 'Unmasked'}\n ok\n />\n <ConfigItem\n label=\"Salt\"\n value={config.saltConfigured ? 'Configured' : 'Not configured'}\n ok={config.saltConfigured}\n />\n <ConfigItem\n label=\"API Endpoint\"\n value={config.apiEndpointConfigured ? 'Configured' : 'Not configured'}\n ok={config.apiEndpointConfigured}\n />\n <ConfigItem\n label=\"API Token\"\n value={config.apiTokenConfigured ? 'Configured' : 'Not configured'}\n ok={config.apiTokenConfigured}\n />\n <ConfigItem\n label=\"Project ID\"\n value={config.projectIdConfigured ? 'Configured' : 'Not configured'}\n ok={config.projectIdConfigured}\n />\n </Box>\n ) : (\n <Typography color=\"error\">\n Unable to load configuration. Is the backend plugin installed?\n </Typography>\n )}\n </InfoCard>\n </Grid>\n\n {/* Statistics */}\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Developer Mappings\">\n <Box display=\"flex\" alignItems=\"center\">\n <Typography variant=\"h3\">\n {config?.mappingCount ?? 0}\n </Typography>\n <Typography\n variant=\"body1\"\n style={{ marginLeft: 8 }}\n color=\"textSecondary\"\n >\n developer name mappings stored\n </Typography>\n </Box>\n <Box mt={2}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Upload developer names via CSV or configure GitHub auto-sync in the\n Settings tab to populate the mapping database.\n </Typography>\n </Box>\n </InfoCard>\n </Grid>\n\n {/* GitHub Auto-Sync Configurations */}\n <Grid item xs={12}>\n <InfoCard title=\"GitHub Auto-Sync Configurations\">\n {syncConfigsLoading ? (\n <Typography variant=\"body2\" color=\"textSecondary\">\n Loading sync configurations...\n </Typography>\n ) : syncConfigs.length === 0 ? (\n <Typography className={classes.emptySync} variant=\"body2\">\n No GitHub sync configurations. Go to Settings to register a GitHub App for automatic member syncing.\n </Typography>\n ) : (\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Organization</TableCell>\n <TableCell>GitHub Host</TableCell>\n <TableCell>Client ID</TableCell>\n <TableCell>Status</TableCell>\n <TableCell>Last Synced</TableCell>\n <TableCell>Registered</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {syncConfigs.map(cfg => (\n <TableRow key={cfg.id}>\n <TableCell>\n <strong>{cfg.org_name}</strong>\n </TableCell>\n <TableCell style={{ fontSize: '0.85em' }}>\n {cfg.github_hostname ?? 'github.com'}\n </TableCell>\n <TableCell style={{ fontFamily: 'monospace', fontSize: '0.85em' }}>\n {cfg.app_client_id}\n </TableCell>\n <TableCell>\n <Chip\n label={cfg.active ? 'Active' : 'Inactive'}\n size=\"small\"\n className={cfg.active ? classes.activeChip : classes.inactiveChip}\n />\n </TableCell>\n <TableCell>\n {cfg.last_synced_at ? (\n <Typography className={classes.lastSynced}>\n {new Date(cfg.last_synced_at).toLocaleString()}\n </Typography>\n ) : (\n <Typography className={classes.lastSynced}>Never</Typography>\n )}\n </TableCell>\n <TableCell>\n <Typography className={classes.lastSynced}>\n {new Date(cfg.created_at).toLocaleDateString()}\n </Typography>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n )}\n </InfoCard>\n </Grid>\n\n {/* Unmask Tester */}\n <Grid item xs={12}>\n <InfoCard title=\"Unmask Developer Name\">\n <Typography variant=\"body2\" color=\"textSecondary\" paragraph>\n Enter a masked developer username (16-character hex hash) to look up\n the original name from the mapping database.\n </Typography>\n <Box display=\"flex\" alignItems=\"flex-start\">\n <TextField\n label=\"Masked username\"\n value={maskedInput}\n onChange={e => setMaskedInput(e.target.value)}\n onKeyDown={handleKeyDown}\n variant=\"outlined\"\n size=\"small\"\n placeholder=\"e.g. a1b2c3d4e5f67890\"\n style={{ minWidth: 300 }}\n inputProps={{ maxLength: 16 }}\n />\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleUnmask}\n disabled={!maskedInput.trim()}\n style={{ marginLeft: 12, height: 40 }}\n >\n Unmask\n </Button>\n </Box>\n {unmaskResult && (\n <Box className={classes.unmaskResult}>\n <Typography className={classes.resultLabel} variant=\"body2\">\n Masked:\n </Typography>\n <Typography className={classes.resultValue}>\n {unmaskResult.maskedName}\n </Typography>\n <Box mt={1}>\n <Typography className={classes.resultLabel} variant=\"body2\">\n Real Name:\n </Typography>\n <Typography className={classes.resultValue}>\n {unmaskResult.realName ? (\n <Chip\n label={unmaskResult.realName}\n color=\"primary\"\n variant=\"outlined\"\n />\n ) : (\n <Chip\n label=\"No mapping found\"\n color=\"default\"\n variant=\"outlined\"\n />\n )}\n </Typography>\n </Box>\n </Box>\n )}\n {unmaskError && (\n <Box mt={2}>\n <Typography color=\"error\">{unmaskError}</Typography>\n </Box>\n )}\n </InfoCard>\n </Grid>\n </Grid>\n );\n};\n\nfunction ConfigItem({\n label,\n value,\n ok,\n}: {\n label: string;\n value: string;\n ok: boolean;\n}) {\n return (\n <Box display=\"flex\" alignItems=\"center\" mb={1}>\n {ok ? (\n <CheckCircleIcon style={{ color: '#4caf50', marginRight: 8 }} fontSize=\"small\" />\n ) : (\n <ErrorIcon style={{ color: '#f44336', marginRight: 8 }} fontSize=\"small\" />\n )}\n <Typography variant=\"body2\">\n <strong>{label}:</strong> {value}\n </Typography>\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DashboardContent.esm.js","sources":["../../src/components/DashboardContent.tsx"],"sourcesContent":["import React, { useState, useEffect, useCallback } from 'react';\nimport {\n Typography,\n TextField,\n Button,\n Grid,\n Box,\n Chip,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport CheckCircleIcon from '@material-ui/icons/CheckCircle';\nimport ErrorIcon from '@material-ui/icons/Error';\nimport { InfoCard } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { DevxpConfig, UnmaskResult, GithubSyncConfig } from '../types';\n\nconst useStyles = makeStyles(theme => ({\n configGrid: {\n marginBottom: theme.spacing(3),\n },\n statusChip: {\n marginLeft: theme.spacing(1),\n },\n unmaskResult: {\n marginTop: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: theme.palette.background.default,\n borderRadius: theme.shape.borderRadius,\n },\n resultLabel: {\n color: theme.palette.text.secondary,\n marginBottom: theme.spacing(0.5),\n },\n resultValue: {\n fontFamily: 'monospace',\n fontSize: '1.1em',\n },\n activeChip: {\n backgroundColor: '#e8f5e9',\n color: '#2e7d32',\n },\n inactiveChip: {\n backgroundColor: '#f5f5f5',\n color: '#757575',\n },\n lastSynced: {\n fontSize: '0.75rem',\n color: theme.palette.text.secondary,\n },\n emptySync: {\n padding: theme.spacing(2),\n color: theme.palette.text.secondary,\n fontStyle: 'italic',\n },\n}));\n\ninterface DashboardContentProps {\n api: DevxpApi;\n}\n\nexport const DashboardContent = ({ api }: DashboardContentProps) => {\n const classes = useStyles();\n const [config, setConfig] = useState<DevxpConfig | null>(null);\n const [loading, setLoading] = useState(true);\n const [maskedInput, setMaskedInput] = useState('');\n const [unmaskResult, setUnmaskResult] = useState<UnmaskResult | null>(null);\n const [unmaskError, setUnmaskError] = useState('');\n const [syncConfigs, setSyncConfigs] = useState<GithubSyncConfig[]>([]);\n const [syncConfigsLoading, setSyncConfigsLoading] = useState(true);\n\n const loadConfig = useCallback(async () => {\n try {\n setLoading(true);\n const cfg = await api.getConfig();\n setConfig(cfg);\n } catch (e) {\n // Config not available\n } finally {\n setLoading(false);\n }\n }, [api]);\n\n const loadSyncConfigs = useCallback(async () => {\n try {\n setSyncConfigsLoading(true);\n const result = await api.getGithubSyncConfigs();\n setSyncConfigs(result.configs);\n } catch {\n // failed silently\n } finally {\n setSyncConfigsLoading(false);\n }\n }, [api]);\n\n useEffect(() => {\n loadConfig();\n loadSyncConfigs();\n // Trigger auto-sync on page load (throttled to 24h server-side)\n api.triggerAutoSync().catch(() => {/* silent */});\n }, [loadConfig, loadSyncConfigs, api]);\n\n const handleUnmask = async () => {\n if (!maskedInput.trim()) return;\n setUnmaskError('');\n setUnmaskResult(null);\n try {\n const result = await api.unmask(maskedInput.trim());\n setUnmaskResult(result);\n } catch (e: any) {\n setUnmaskError(e.message || 'Failed to unmask');\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') handleUnmask();\n };\n\n if (loading) {\n return <Typography>Loading configuration...</Typography>;\n }\n\n return (\n <Grid container spacing={3}>\n {/* Configuration Status */}\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Configuration Status\">\n {config ? (\n <Box>\n <ConfigItem\n label=\"Mode\"\n value={config.masked ? 'Masked' : 'Unmasked'}\n ok\n />\n <ConfigItem\n label=\"Salt\"\n value={config.saltConfigured ? 'Configured' : 'Not configured'}\n ok={config.saltConfigured}\n />\n <ConfigItem\n label=\"API Endpoint\"\n value={config.apiEndpointConfigured ? 'Configured' : 'Not configured'}\n ok={config.apiEndpointConfigured}\n />\n <ConfigItem\n label=\"API Token\"\n value={config.apiTokenConfigured ? 'Configured' : 'Not configured'}\n ok={config.apiTokenConfigured}\n />\n <ConfigItem\n label=\"Project ID\"\n value={config.projectIdConfigured ? 'Configured' : 'Not configured'}\n ok={config.projectIdConfigured}\n />\n </Box>\n ) : (\n <Typography color=\"error\">\n Unable to load configuration. Is the backend plugin installed?\n </Typography>\n )}\n </InfoCard>\n </Grid>\n\n {/* Statistics */}\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Developer Mappings\">\n <Box display=\"flex\" alignItems=\"center\">\n <Typography variant=\"h3\">\n {config?.mappingCount ?? 0}\n </Typography>\n <Typography\n variant=\"body1\"\n style={{ marginLeft: 8 }}\n color=\"textSecondary\"\n >\n developer name mappings stored\n </Typography>\n </Box>\n <Box mt={2}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Upload developer names via CSV or configure GitHub auto-sync in the\n Settings tab to populate the mapping database.\n </Typography>\n </Box>\n </InfoCard>\n </Grid>\n\n {/* GitHub Auto-Sync Configurations */}\n <Grid item xs={12}>\n <InfoCard title=\"GitHub Auto-Sync Configurations\">\n {syncConfigsLoading ? (\n <Typography variant=\"body2\" color=\"textSecondary\">\n Loading sync configurations...\n </Typography>\n ) : syncConfigs.length === 0 ? (\n <Typography className={classes.emptySync} variant=\"body2\">\n No GitHub sync configurations. Go to Settings to register a GitHub App for automatic member syncing.\n </Typography>\n ) : (\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Organization</TableCell>\n <TableCell>GitHub Host</TableCell>\n <TableCell>Client ID</TableCell>\n <TableCell>Status</TableCell>\n <TableCell>Last Synced</TableCell>\n <TableCell>Registered</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {syncConfigs.map(cfg => (\n <TableRow key={cfg.id}>\n <TableCell>\n <strong>{cfg.org_name}</strong>\n </TableCell>\n <TableCell style={{ fontSize: '0.85em' }}>\n {cfg.github_hostname ?? 'github.com'}\n </TableCell>\n <TableCell style={{ fontFamily: 'monospace', fontSize: '0.85em' }}>\n {cfg.app_client_id}\n </TableCell>\n <TableCell>\n <Chip\n label={cfg.active ? 'Active' : 'Inactive'}\n size=\"small\"\n className={cfg.active ? classes.activeChip : classes.inactiveChip}\n />\n </TableCell>\n <TableCell>\n {cfg.last_synced_at ? (\n <Typography className={classes.lastSynced}>\n {new Date(cfg.last_synced_at).toLocaleString()}\n </Typography>\n ) : (\n <Typography className={classes.lastSynced}>Never</Typography>\n )}\n </TableCell>\n <TableCell>\n <Typography className={classes.lastSynced}>\n {new Date(cfg.created_at).toLocaleDateString()}\n </Typography>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n )}\n </InfoCard>\n </Grid>\n\n {/* Unmask Tester */}\n <Grid item xs={12}>\n <InfoCard title=\"Unmask Developer Name\">\n <Typography variant=\"body2\" color=\"textSecondary\" paragraph>\n Enter a masked developer username (16-character hex hash) to look up\n the original name from the mapping database.\n </Typography>\n <Box display=\"flex\" alignItems=\"flex-start\">\n <TextField\n label=\"Masked username\"\n value={maskedInput}\n onChange={e => setMaskedInput(e.target.value)}\n onKeyDown={handleKeyDown}\n variant=\"outlined\"\n size=\"small\"\n placeholder=\"e.g. a1b2c3d4e5f67890\"\n style={{ minWidth: 300 }}\n inputProps={{ maxLength: 16 }}\n />\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleUnmask}\n disabled={!maskedInput.trim()}\n style={{ marginLeft: 12, height: 40 }}\n >\n Unmask\n </Button>\n </Box>\n {unmaskResult && (\n <Box className={classes.unmaskResult}>\n <Typography className={classes.resultLabel} variant=\"body2\">\n Masked:\n </Typography>\n <Typography className={classes.resultValue}>\n {unmaskResult.maskedName}\n </Typography>\n <Box mt={1}>\n <Typography className={classes.resultLabel} variant=\"body2\">\n Real Name:\n </Typography>\n <Typography className={classes.resultValue}>\n {unmaskResult.realName ? (\n <Chip\n label={unmaskResult.realName}\n color=\"primary\"\n variant=\"outlined\"\n />\n ) : (\n <Chip\n label=\"No mapping found\"\n color=\"default\"\n variant=\"outlined\"\n />\n )}\n </Typography>\n </Box>\n </Box>\n )}\n {unmaskError && (\n <Box mt={2}>\n <Typography color=\"error\">{unmaskError}</Typography>\n </Box>\n )}\n </InfoCard>\n </Grid>\n </Grid>\n );\n};\n\nfunction ConfigItem({\n label,\n value,\n ok,\n}: {\n label: string;\n value: string;\n ok: boolean;\n}) {\n return (\n <Box display=\"flex\" alignItems=\"center\" mb={1}>\n {ok ? (\n <CheckCircleIcon style={{ color: '#4caf50', marginRight: 8 }} fontSize=\"small\" />\n ) : (\n <ErrorIcon style={{ color: '#f44336', marginRight: 8 }} fontSize=\"small\" />\n )}\n <Typography variant=\"body2\">\n <strong>{label}:</strong> {value}\n </Typography>\n </Box>\n );\n}\n"],"names":["React"],"mappings":"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"}
@@ -0,0 +1,258 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import { Box, Tooltip, IconButton, Typography, Grid, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, CircularProgress, Chip, TextField, Button } from '@material-ui/core';
3
+ import { makeStyles } from '@material-ui/core/styles';
4
+ import ArrowBackIcon from '@material-ui/icons/ArrowBack';
5
+ import PersonAddIcon from '@material-ui/icons/PersonAdd';
6
+ import CloseIcon from '@material-ui/icons/Close';
7
+ import { InfoCard, EmptyState } from '@backstage/core-components';
8
+ import { SpiderChart } from './SpiderChart.esm.js';
9
+
10
+ const CATEGORIES = [
11
+ "Frontend Engineering",
12
+ "Backend Systems",
13
+ "Database & Data",
14
+ "Infrastructure (DevOps)",
15
+ "Mobile Development",
16
+ "AI & Machine Learning",
17
+ "Testing & QA",
18
+ "Security & Auth",
19
+ "Distributed Systems",
20
+ "Documentation"
21
+ ];
22
+ const SHORT_NAMES = {
23
+ "Frontend Engineering": "Frontend",
24
+ "Backend Systems": "Backend",
25
+ "Database & Data": "Database",
26
+ "Infrastructure (DevOps)": "DevOps",
27
+ "Mobile Development": "Mobile",
28
+ "AI & Machine Learning": "AI/ML",
29
+ "Testing & QA": "Testing",
30
+ "Security & Auth": "Security",
31
+ "Distributed Systems": "Distributed",
32
+ "Documentation": "Docs"
33
+ };
34
+ const useStyles = makeStyles((theme) => ({
35
+ header: {
36
+ display: "flex",
37
+ alignItems: "center",
38
+ gap: theme.spacing(1),
39
+ marginBottom: theme.spacing(2)
40
+ },
41
+ slotCard: {
42
+ border: `2px dashed ${theme.palette.divider}`,
43
+ borderRadius: theme.shape.borderRadius,
44
+ padding: theme.spacing(2),
45
+ textAlign: "center",
46
+ minHeight: 80,
47
+ display: "flex",
48
+ flexDirection: "column",
49
+ alignItems: "center",
50
+ justifyContent: "center",
51
+ gap: theme.spacing(1)
52
+ },
53
+ loadedSlot: {
54
+ borderStyle: "solid",
55
+ borderColor: theme.palette.primary.main
56
+ },
57
+ userIdLabel: {
58
+ fontFamily: "monospace",
59
+ fontWeight: 700
60
+ },
61
+ legendDot: {
62
+ width: 12,
63
+ height: 12,
64
+ borderRadius: "50%",
65
+ display: "inline-block",
66
+ marginRight: 6,
67
+ flexShrink: 0
68
+ },
69
+ scoreCell: {
70
+ fontWeight: 600
71
+ },
72
+ betterCell: {
73
+ color: theme.palette.success.main,
74
+ fontWeight: 700
75
+ },
76
+ worseCell: {
77
+ color: theme.palette.error.main
78
+ },
79
+ chartSubtitle: {
80
+ textAlign: "center",
81
+ color: theme.palette.text.secondary,
82
+ fontSize: "0.75rem",
83
+ marginTop: theme.spacing(1)
84
+ },
85
+ legend: {
86
+ display: "flex",
87
+ gap: theme.spacing(3),
88
+ justifyContent: "center",
89
+ marginTop: theme.spacing(1),
90
+ flexWrap: "wrap"
91
+ },
92
+ legendItem: {
93
+ display: "flex",
94
+ alignItems: "center"
95
+ }
96
+ }));
97
+ const DeveloperComparison = ({ api, initialUserIds, onBack }) => {
98
+ const classes = useStyles();
99
+ const makeSlot = (userId = "") => ({
100
+ userId,
101
+ data: null,
102
+ loading: false,
103
+ error: null
104
+ });
105
+ const [slots, setSlots] = useState([
106
+ makeSlot(initialUserIds[0] ?? ""),
107
+ makeSlot(initialUserIds[1] ?? "")
108
+ ]);
109
+ const [inputValues, setInputValues] = useState([
110
+ initialUserIds[0] ?? "",
111
+ initialUserIds[1] ?? ""
112
+ ]);
113
+ const [average, setAverage] = useState(null);
114
+ const loadSlot = useCallback(
115
+ async (index, userId) => {
116
+ if (!userId.trim()) return;
117
+ setSlots((prev) => {
118
+ const updated = [...prev];
119
+ updated[index] = { userId, data: null, loading: true, error: null };
120
+ return updated;
121
+ });
122
+ try {
123
+ const data = await api.getDeveloperPerformance(userId.trim());
124
+ setSlots((prev) => {
125
+ const updated = [...prev];
126
+ updated[index] = { userId, data, loading: false, error: null };
127
+ return updated;
128
+ });
129
+ } catch (e) {
130
+ setSlots((prev) => {
131
+ const updated = [...prev];
132
+ updated[index] = { userId, data: null, loading: false, error: e.message ?? "Failed to load" };
133
+ return updated;
134
+ });
135
+ }
136
+ },
137
+ [api]
138
+ );
139
+ useEffect(() => {
140
+ api.getDeveloperAveragePerformance().then(setAverage).catch(() => {
141
+ });
142
+ }, [api]);
143
+ useEffect(() => {
144
+ if (initialUserIds[0]) loadSlot(0, initialUserIds[0]);
145
+ if (initialUserIds[1]) loadSlot(1, initialUserIds[1]);
146
+ }, []);
147
+ const clearSlot = (index) => {
148
+ setSlots((prev) => {
149
+ const updated = [...prev];
150
+ updated[index] = makeSlot("");
151
+ return updated;
152
+ });
153
+ setInputValues((prev) => {
154
+ const updated = [...prev];
155
+ updated[index] = "";
156
+ return updated;
157
+ });
158
+ };
159
+ const slot0Data = slots[0].data;
160
+ const slot1Data = slots[1].data;
161
+ const getCategoryValue = (perf, category) => {
162
+ if (!perf) return null;
163
+ const found = perf.categoryAverages.find((c) => c.category === category);
164
+ return found ? found.averageProficiency : 0;
165
+ };
166
+ const getAvgValue = (category) => {
167
+ if (!average) return null;
168
+ const found = average.categoryAverages.find((c) => c.category === category);
169
+ return found ? found.averageProficiency : 0;
170
+ };
171
+ const renderSlotHeader = (index) => {
172
+ const slot = slots[index];
173
+ const color = index === 0 ? "#3f51b5" : "#e91e63";
174
+ return /* @__PURE__ */ React.createElement(
175
+ Box,
176
+ {
177
+ className: `${classes.slotCard} ${slot.data ? classes.loadedSlot : ""}`,
178
+ style: { borderColor: slot.data ? color : void 0 }
179
+ },
180
+ slot.loading ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 24 }) : slot.data ? /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 }, width: "100%" }, /* @__PURE__ */ React.createElement("span", { className: classes.legendDot, style: { backgroundColor: color } }), /* @__PURE__ */ React.createElement(Typography, { className: classes.userIdLabel, style: { flex: 1, textAlign: "left" } }, slot.userId), /* @__PURE__ */ React.createElement(Chip, { label: `${slot.data.uniqueSkills.length} skills`, size: "small" }), /* @__PURE__ */ React.createElement(Tooltip, { title: "Remove" }, /* @__PURE__ */ React.createElement(IconButton, { size: "small", onClick: () => clearSlot(index) }, /* @__PURE__ */ React.createElement(CloseIcon, { fontSize: "small" })))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
181
+ TextField,
182
+ {
183
+ size: "small",
184
+ label: `Developer ${index + 1}`,
185
+ variant: "outlined",
186
+ value: inputValues[index],
187
+ onChange: (e) => {
188
+ const val = e.target.value;
189
+ setInputValues((prev) => {
190
+ const updated = [...prev];
191
+ updated[index] = val;
192
+ return updated;
193
+ });
194
+ },
195
+ onKeyDown: (e) => {
196
+ if (e.key === "Enter") loadSlot(index, inputValues[index]);
197
+ },
198
+ style: { width: "100%" }
199
+ }
200
+ ), slot.error && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "error" }, slot.error), /* @__PURE__ */ React.createElement(
201
+ Button,
202
+ {
203
+ size: "small",
204
+ variant: "outlined",
205
+ startIcon: /* @__PURE__ */ React.createElement(PersonAddIcon, null),
206
+ onClick: () => loadSlot(index, inputValues[index]),
207
+ disabled: !inputValues[index].trim()
208
+ },
209
+ "Load"
210
+ ))
211
+ );
212
+ };
213
+ const bothLoaded = !!slot0Data && !!slot1Data;
214
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Box, { className: classes.header }, /* @__PURE__ */ React.createElement(Tooltip, { title: "Back to leaderboard" }, /* @__PURE__ */ React.createElement(IconButton, { size: "small", onClick: onBack }, /* @__PURE__ */ React.createElement(ArrowBackIcon, null))), /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Developer Comparison")), /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2, style: { marginBottom: 16 } }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, sm: 6 }, renderSlotHeader(0)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, sm: 6 }, renderSlotHeader(1))), (slot0Data || slot1Data) && /* @__PURE__ */ React.createElement(InfoCard, { title: "Skill Radar Comparison" }, /* @__PURE__ */ React.createElement(
215
+ SpiderChart,
216
+ {
217
+ primary: slot0Data ? { categoryAverages: slot0Data.categoryAverages } : { categoryAverages: [] },
218
+ compare: slot1Data ? { categoryAverages: slot1Data.categoryAverages } : void 0
219
+ }
220
+ ), /* @__PURE__ */ React.createElement(Box, { className: classes.legend }, slot0Data && /* @__PURE__ */ React.createElement(Box, { className: classes.legendItem }, /* @__PURE__ */ React.createElement("span", { className: classes.legendDot, style: { backgroundColor: "#3f51b5" } }), /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, slots[0].userId)), slot1Data && /* @__PURE__ */ React.createElement(Box, { className: classes.legendItem }, /* @__PURE__ */ React.createElement("span", { className: classes.legendDot, style: { backgroundColor: "#e91e63" } }), /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, slots[1].userId)), average && /* @__PURE__ */ React.createElement(Box, { className: classes.legendItem }, /* @__PURE__ */ React.createElement("span", { className: classes.legendDot, style: { backgroundColor: "#ccc" } }), /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, "Platform avg (", average.developerCount, " devs)"))), /* @__PURE__ */ React.createElement(Typography, { className: classes.chartSubtitle }, "Blue = ", slots[0].userId || "Developer 1", " \xB7 Pink = ", slots[1].userId || "Developer 2")), bothLoaded && /* @__PURE__ */ React.createElement(Box, { mt: 2 }, /* @__PURE__ */ React.createElement(InfoCard, { title: "Category Breakdown" }, /* @__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, "Category"), /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, slots[0].userId), /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, slots[1].userId), average && /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, "Platform Avg"), /* @__PURE__ */ React.createElement(TableCell, { align: "center" }, "Winner"))), /* @__PURE__ */ React.createElement(TableBody, null, CATEGORIES.map((cat) => {
221
+ const v0 = getCategoryValue(slot0Data, cat) ?? 0;
222
+ const v1 = getCategoryValue(slot1Data, cat) ?? 0;
223
+ const avg = getAvgValue(cat);
224
+ const winner = v0 > v1 ? 0 : v1 > v0 ? 1 : -1;
225
+ return /* @__PURE__ */ React.createElement(TableRow, { key: cat }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, SHORT_NAMES[cat] ?? cat)), /* @__PURE__ */ React.createElement(
226
+ TableCell,
227
+ {
228
+ align: "right",
229
+ className: winner === 0 ? classes.betterCell : classes.worseCell
230
+ },
231
+ v0.toFixed(2)
232
+ ), /* @__PURE__ */ React.createElement(
233
+ TableCell,
234
+ {
235
+ align: "right",
236
+ className: winner === 1 ? classes.betterCell : classes.worseCell
237
+ },
238
+ v1.toFixed(2)
239
+ ), average && /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, avg !== null ? avg.toFixed(2) : "\u2013")), /* @__PURE__ */ React.createElement(TableCell, { align: "center" }, winner === -1 ? /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Tie") : /* @__PURE__ */ React.createElement(
240
+ Typography,
241
+ {
242
+ variant: "caption",
243
+ style: { fontFamily: "monospace", fontWeight: 600 }
244
+ },
245
+ winner === 0 ? slots[0].userId : slots[1].userId
246
+ )));
247
+ })))))), !slot0Data && !slot1Data && /* @__PURE__ */ React.createElement(
248
+ EmptyState,
249
+ {
250
+ missing: "data",
251
+ title: "No developers selected",
252
+ description: "Enter developer usernames above to start comparing."
253
+ }
254
+ ));
255
+ };
256
+
257
+ export { DeveloperComparison };
258
+ //# sourceMappingURL=DeveloperComparison.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeveloperComparison.esm.js","sources":["../../src/components/DeveloperComparison.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n Box,\n Typography,\n Grid,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n TextField,\n Button,\n CircularProgress,\n IconButton,\n Tooltip,\n Chip,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ArrowBackIcon from '@material-ui/icons/ArrowBack';\nimport PersonAddIcon from '@material-ui/icons/PersonAdd';\nimport CloseIcon from '@material-ui/icons/Close';\nimport { InfoCard, EmptyState } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { DeveloperPerformance, DeveloperAveragePerformance } from '../types';\nimport { SpiderChart } from './SpiderChart';\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 SHORT_NAMES: Record<string, string> = {\n 'Frontend Engineering': 'Frontend',\n 'Backend Systems': 'Backend',\n 'Database & Data': 'Database',\n 'Infrastructure (DevOps)': 'DevOps',\n 'Mobile Development': 'Mobile',\n 'AI & Machine Learning': 'AI/ML',\n 'Testing & QA': 'Testing',\n 'Security & Auth': 'Security',\n 'Distributed Systems': 'Distributed',\n 'Documentation': 'Docs',\n};\n\nconst useStyles = makeStyles(theme => ({\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n marginBottom: theme.spacing(2),\n },\n slotCard: {\n border: `2px dashed ${theme.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n padding: theme.spacing(2),\n textAlign: 'center',\n minHeight: 80,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: theme.spacing(1),\n },\n loadedSlot: {\n borderStyle: 'solid',\n borderColor: theme.palette.primary.main,\n },\n userIdLabel: {\n fontFamily: 'monospace',\n fontWeight: 700,\n },\n legendDot: {\n width: 12,\n height: 12,\n borderRadius: '50%',\n display: 'inline-block',\n marginRight: 6,\n flexShrink: 0,\n },\n scoreCell: {\n fontWeight: 600,\n },\n betterCell: {\n color: theme.palette.success.main,\n fontWeight: 700,\n },\n worseCell: {\n color: theme.palette.error.main,\n },\n chartSubtitle: {\n textAlign: 'center',\n color: theme.palette.text.secondary,\n fontSize: '0.75rem',\n marginTop: theme.spacing(1),\n },\n legend: {\n display: 'flex',\n gap: theme.spacing(3),\n justifyContent: 'center',\n marginTop: theme.spacing(1),\n flexWrap: 'wrap',\n },\n legendItem: {\n display: 'flex',\n alignItems: 'center',\n },\n}));\n\ninterface DeveloperSlot {\n userId: string;\n data: DeveloperPerformance | null;\n loading: boolean;\n error: string | null;\n}\n\ninterface DeveloperComparisonProps {\n api: DevxpApi;\n initialUserIds: string[];\n onBack: () => void;\n}\n\nexport const DeveloperComparison = ({ api, initialUserIds, onBack }: DeveloperComparisonProps) => {\n const classes = useStyles();\n\n const makeSlot = (userId = ''): DeveloperSlot => ({\n userId,\n data: null,\n loading: false,\n error: null,\n });\n\n const [slots, setSlots] = useState<[DeveloperSlot, DeveloperSlot]>([\n makeSlot(initialUserIds[0] ?? ''),\n makeSlot(initialUserIds[1] ?? ''),\n ]);\n const [inputValues, setInputValues] = useState<[string, string]>([\n initialUserIds[0] ?? '',\n initialUserIds[1] ?? '',\n ]);\n const [average, setAverage] = useState<DeveloperAveragePerformance | null>(null);\n\n const loadSlot = useCallback(\n async (index: 0 | 1, userId: string) => {\n if (!userId.trim()) return;\n setSlots(prev => {\n const updated = [...prev] as [DeveloperSlot, DeveloperSlot];\n updated[index] = { userId, data: null, loading: true, error: null };\n return updated;\n });\n try {\n const data = await api.getDeveloperPerformance(userId.trim());\n setSlots(prev => {\n const updated = [...prev] as [DeveloperSlot, DeveloperSlot];\n updated[index] = { userId, data, loading: false, error: null };\n return updated;\n });\n } catch (e: any) {\n setSlots(prev => {\n const updated = [...prev] as [DeveloperSlot, DeveloperSlot];\n updated[index] = { userId, data: null, loading: false, error: e.message ?? 'Failed to load' };\n return updated;\n });\n }\n },\n [api],\n );\n\n // Load average performance for baseline\n useEffect(() => {\n api.getDeveloperAveragePerformance().then(setAverage).catch(() => {});\n }, [api]);\n\n // Auto-load initially provided userIds\n useEffect(() => {\n if (initialUserIds[0]) loadSlot(0, initialUserIds[0]);\n if (initialUserIds[1]) loadSlot(1, initialUserIds[1]);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const clearSlot = (index: 0 | 1) => {\n setSlots(prev => {\n const updated = [...prev] as [DeveloperSlot, DeveloperSlot];\n updated[index] = makeSlot('');\n return updated;\n });\n setInputValues(prev => {\n const updated = [...prev] as [string, string];\n updated[index] = '';\n return updated;\n });\n };\n\n // Build combined chart data\n const slot0Data = slots[0].data;\n const slot1Data = slots[1].data;\n\n const getCategoryValue = (perf: DeveloperPerformance | null, category: string) => {\n if (!perf) return null;\n const found = perf.categoryAverages.find(c => c.category === category);\n return found ? found.averageProficiency : 0;\n };\n\n const getAvgValue = (category: string) => {\n if (!average) return null;\n const found = average.categoryAverages.find(c => c.category === category);\n return found ? found.averageProficiency : 0;\n };\n\n const renderSlotHeader = (index: 0 | 1) => {\n const slot = slots[index];\n const color = index === 0 ? '#3f51b5' : '#e91e63';\n\n return (\n <Box className={`${classes.slotCard} ${slot.data ? classes.loadedSlot : ''}`}\n style={{ borderColor: slot.data ? color : undefined }}>\n {slot.loading ? (\n <CircularProgress size={24} />\n ) : slot.data ? (\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }} width=\"100%\">\n <span className={classes.legendDot} style={{ backgroundColor: color }} />\n <Typography className={classes.userIdLabel} style={{ flex: 1, textAlign: 'left' }}>\n {slot.userId}\n </Typography>\n <Chip label={`${slot.data.uniqueSkills.length} skills`} size=\"small\" />\n <Tooltip title=\"Remove\">\n <IconButton size=\"small\" onClick={() => clearSlot(index)}>\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </Box>\n ) : (\n <>\n <TextField\n size=\"small\"\n label={`Developer ${index + 1}`}\n variant=\"outlined\"\n value={inputValues[index]}\n onChange={e => {\n const val = e.target.value;\n setInputValues(prev => {\n const updated = [...prev] as [string, string];\n updated[index] = val;\n return updated;\n });\n }}\n onKeyDown={e => {\n if (e.key === 'Enter') loadSlot(index, inputValues[index]);\n }}\n style={{ width: '100%' }}\n />\n {slot.error && (\n <Typography variant=\"caption\" color=\"error\">{slot.error}</Typography>\n )}\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<PersonAddIcon />}\n onClick={() => loadSlot(index, inputValues[index])}\n disabled={!inputValues[index].trim()}\n >\n Load\n </Button>\n </>\n )}\n </Box>\n );\n };\n\n const bothLoaded = !!slot0Data && !!slot1Data;\n\n return (\n <Box>\n <Box className={classes.header}>\n <Tooltip title=\"Back to leaderboard\">\n <IconButton size=\"small\" onClick={onBack}>\n <ArrowBackIcon />\n </IconButton>\n </Tooltip>\n <Typography variant=\"h6\">Developer Comparison</Typography>\n </Box>\n\n {/* Slot selectors */}\n <Grid container spacing={2} style={{ marginBottom: 16 }}>\n <Grid item xs={12} sm={6}>{renderSlotHeader(0)}</Grid>\n <Grid item xs={12} sm={6}>{renderSlotHeader(1)}</Grid>\n </Grid>\n\n {/* Overlaid spider chart */}\n {(slot0Data || slot1Data) && (\n <InfoCard title=\"Skill Radar Comparison\">\n <SpiderChart\n primary={\n slot0Data\n ? { categoryAverages: slot0Data.categoryAverages }\n : { categoryAverages: [] }\n }\n compare={\n slot1Data\n ? { categoryAverages: slot1Data.categoryAverages }\n : undefined\n }\n />\n <Box className={classes.legend}>\n {slot0Data && (\n <Box className={classes.legendItem}>\n <span className={classes.legendDot} style={{ backgroundColor: '#3f51b5' }} />\n <Typography variant=\"caption\">{slots[0].userId}</Typography>\n </Box>\n )}\n {slot1Data && (\n <Box className={classes.legendItem}>\n <span className={classes.legendDot} style={{ backgroundColor: '#e91e63' }} />\n <Typography variant=\"caption\">{slots[1].userId}</Typography>\n </Box>\n )}\n {average && (\n <Box className={classes.legendItem}>\n <span className={classes.legendDot} style={{ backgroundColor: '#ccc' }} />\n <Typography variant=\"caption\">Platform avg ({average.developerCount} devs)</Typography>\n </Box>\n )}\n </Box>\n <Typography className={classes.chartSubtitle}>\n Blue = {slots[0].userId || 'Developer 1'} · Pink = {slots[1].userId || 'Developer 2'}\n </Typography>\n </InfoCard>\n )}\n\n {/* Category breakdown table */}\n {bothLoaded && (\n <Box mt={2}>\n <InfoCard title=\"Category Breakdown\">\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Category</TableCell>\n <TableCell align=\"right\">{slots[0].userId}</TableCell>\n <TableCell align=\"right\">{slots[1].userId}</TableCell>\n {average && <TableCell align=\"right\">Platform Avg</TableCell>}\n <TableCell align=\"center\">Winner</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {CATEGORIES.map(cat => {\n const v0 = getCategoryValue(slot0Data, cat) ?? 0;\n const v1 = getCategoryValue(slot1Data, cat) ?? 0;\n const avg = getAvgValue(cat);\n const winner = v0 > v1 ? 0 : v1 > v0 ? 1 : -1;\n return (\n <TableRow key={cat}>\n <TableCell>\n <Typography variant=\"body2\">{SHORT_NAMES[cat] ?? cat}</Typography>\n </TableCell>\n <TableCell\n align=\"right\"\n className={winner === 0 ? classes.betterCell : classes.worseCell}\n >\n {v0.toFixed(2)}\n </TableCell>\n <TableCell\n align=\"right\"\n className={winner === 1 ? classes.betterCell : classes.worseCell}\n >\n {v1.toFixed(2)}\n </TableCell>\n {average && (\n <TableCell align=\"right\">\n <Typography variant=\"caption\" color=\"textSecondary\">\n {avg !== null ? avg.toFixed(2) : '–'}\n </Typography>\n </TableCell>\n )}\n <TableCell align=\"center\">\n {winner === -1 ? (\n <Typography variant=\"caption\" color=\"textSecondary\">Tie</Typography>\n ) : (\n <Typography\n variant=\"caption\"\n style={{ fontFamily: 'monospace', fontWeight: 600 }}\n >\n {winner === 0 ? slots[0].userId : slots[1].userId}\n </Typography>\n )}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </TableContainer>\n </InfoCard>\n </Box>\n )}\n\n {!slot0Data && !slot1Data && (\n <EmptyState\n missing=\"data\"\n title=\"No developers selected\"\n description=\"Enter developer usernames above to start comparing.\"\n />\n )}\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import React__default, { useState, useCallback, useEffect } from 'react';
2
+ import { Box, Tooltip, IconButton, Typography, CircularProgress, Grid, Chip, Divider } from '@material-ui/core';
3
+ import { makeStyles } from '@material-ui/core/styles';
4
+ import ArrowBackIcon from '@material-ui/icons/ArrowBack';
5
+ import CompareArrowsIcon from '@material-ui/icons/CompareArrows';
6
+ import StorageIcon from '@material-ui/icons/Storage';
7
+ import { EmptyState, InfoCard } from '@backstage/core-components';
8
+ import { SpiderChart } from './SpiderChart.esm.js';
9
+
10
+ const CATEGORY_COLORS = [
11
+ "#3f51b5",
12
+ "#e91e63",
13
+ "#9c27b0",
14
+ "#ff9800",
15
+ "#4caf50",
16
+ "#607d8b",
17
+ "#00bcd4",
18
+ "#f44336",
19
+ "#795548",
20
+ "#9e9e9e"
21
+ ];
22
+ const useStyles = makeStyles((theme) => ({
23
+ header: {
24
+ display: "flex",
25
+ alignItems: "center",
26
+ gap: theme.spacing(1),
27
+ marginBottom: theme.spacing(2)
28
+ },
29
+ userId: {
30
+ fontFamily: "monospace",
31
+ fontWeight: 700,
32
+ fontSize: "1.1rem"
33
+ },
34
+ skillChip: {
35
+ margin: theme.spacing(0.25),
36
+ fontSize: "0.7rem",
37
+ height: 22
38
+ },
39
+ categorySection: {
40
+ marginBottom: theme.spacing(2)
41
+ },
42
+ categoryTitle: {
43
+ fontWeight: 600,
44
+ fontSize: "0.8rem",
45
+ textTransform: "uppercase",
46
+ letterSpacing: "0.05em",
47
+ color: theme.palette.text.secondary,
48
+ marginBottom: theme.spacing(0.5)
49
+ },
50
+ repoItem: {
51
+ display: "flex",
52
+ alignItems: "center",
53
+ gap: theme.spacing(1),
54
+ padding: theme.spacing(0.75, 1),
55
+ borderRadius: theme.shape.borderRadius,
56
+ marginBottom: theme.spacing(0.5),
57
+ backgroundColor: theme.palette.action.hover
58
+ },
59
+ repoName: {
60
+ fontFamily: "monospace",
61
+ fontSize: "0.85rem"
62
+ },
63
+ chartSubtitle: {
64
+ textAlign: "center",
65
+ color: theme.palette.text.secondary,
66
+ fontSize: "0.75rem",
67
+ marginTop: theme.spacing(1)
68
+ }
69
+ }));
70
+ const DeveloperDetails = ({
71
+ api,
72
+ userId,
73
+ onBack,
74
+ onCompare,
75
+ isInCompare
76
+ }) => {
77
+ const classes = useStyles();
78
+ const [perf, setPerf] = useState(null);
79
+ const [loading, setLoading] = useState(true);
80
+ const [error, setError] = useState(null);
81
+ const load = useCallback(async () => {
82
+ setLoading(true);
83
+ setError(null);
84
+ try {
85
+ const data = await api.getDeveloperPerformance(userId);
86
+ setPerf(data);
87
+ } catch (e) {
88
+ setError(e.message ?? "Failed to load developer performance");
89
+ } finally {
90
+ setLoading(false);
91
+ }
92
+ }, [api, userId]);
93
+ useEffect(() => {
94
+ load();
95
+ }, [load]);
96
+ const groupedSkills = React__default.useMemo(() => {
97
+ if (!perf) return [];
98
+ const map = /* @__PURE__ */ new Map();
99
+ perf.uniqueSkills.forEach((s) => {
100
+ if (!map.has(s.category)) map.set(s.category, []);
101
+ map.get(s.category).push(s.skillName);
102
+ });
103
+ return Array.from(map.entries()).map(([cat, skills]) => ({ cat, skills }));
104
+ }, [perf]);
105
+ return /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(Box, { className: classes.header }, /* @__PURE__ */ React__default.createElement(Tooltip, { title: "Back to leaderboard" }, /* @__PURE__ */ React__default.createElement(IconButton, { size: "small", onClick: onBack }, /* @__PURE__ */ React__default.createElement(ArrowBackIcon, null))), /* @__PURE__ */ React__default.createElement(Typography, { variant: "h6", className: classes.userId }, userId), /* @__PURE__ */ React__default.createElement(Tooltip, { title: isInCompare ? "In comparison" : "Add to comparison" }, /* @__PURE__ */ React__default.createElement(
106
+ IconButton,
107
+ {
108
+ size: "small",
109
+ color: isInCompare ? "primary" : "default",
110
+ onClick: () => onCompare(userId)
111
+ },
112
+ /* @__PURE__ */ React__default.createElement(CompareArrowsIcon, null)
113
+ ))), loading ? /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "center", py: 6 }, /* @__PURE__ */ React__default.createElement(CircularProgress, null)) : error ? /* @__PURE__ */ React__default.createElement(EmptyState, { missing: "data", title: "Could not load performance", description: error }) : !perf ? null : /* @__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: "Skill Proficiency Radar" }, /* @__PURE__ */ React__default.createElement(SpiderChart, { primary: { categoryAverages: perf.categoryAverages } }), /* @__PURE__ */ React__default.createElement(Typography, { className: classes.chartSubtitle }, "Average proficiency (0\u201310) per skill category"))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: `Repositories (${perf.linkedRepos.length})` }, perf.linkedRepos.length === 0 ? /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No repositories linked") : /* @__PURE__ */ React__default.createElement(Box, { style: { maxHeight: 320, overflowY: "auto" } }, perf.linkedRepos.map((repo) => /* @__PURE__ */ React__default.createElement(Box, { key: repo, className: classes.repoItem }, /* @__PURE__ */ React__default.createElement(StorageIcon, { fontSize: "small", color: "action" }), /* @__PURE__ */ React__default.createElement(Typography, { className: classes.repoName }, repo)))))), /* @__PURE__ */ React__default.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React__default.createElement(InfoCard, { title: `Skills (${perf.uniqueSkills.length} unique)` }, groupedSkills.length === 0 ? /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No skills detected") : /* @__PURE__ */ React__default.createElement(Box, null, groupedSkills.map(({ cat, skills }, idx) => /* @__PURE__ */ React__default.createElement(Box, { key: cat, className: classes.categorySection }, /* @__PURE__ */ React__default.createElement(Typography, { className: classes.categoryTitle }, cat), /* @__PURE__ */ React__default.createElement(Box, { display: "flex", flexWrap: "wrap" }, skills.map((skill) => /* @__PURE__ */ React__default.createElement(
114
+ Chip,
115
+ {
116
+ key: skill,
117
+ label: skill,
118
+ size: "small",
119
+ className: classes.skillChip,
120
+ style: {
121
+ backgroundColor: `${CATEGORY_COLORS[idx % CATEGORY_COLORS.length]}20`,
122
+ color: CATEGORY_COLORS[idx % CATEGORY_COLORS.length]
123
+ }
124
+ }
125
+ ))), idx < groupedSkills.length - 1 && /* @__PURE__ */ React__default.createElement(Divider, { style: { marginTop: 12 } }))))))));
126
+ };
127
+
128
+ export { DeveloperDetails };
129
+ //# sourceMappingURL=DeveloperDetails.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeveloperDetails.esm.js","sources":["../../src/components/DeveloperDetails.tsx"],"sourcesContent":["import React, { useState, useEffect, useCallback } from 'react';\nimport {\n Box,\n Typography,\n Grid,\n Chip,\n CircularProgress,\n IconButton,\n Tooltip,\n Divider,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ArrowBackIcon from '@material-ui/icons/ArrowBack';\nimport CompareArrowsIcon from '@material-ui/icons/CompareArrows';\nimport StorageIcon from '@material-ui/icons/Storage';\nimport { InfoCard, EmptyState } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { DeveloperPerformance } from '../types';\nimport { SpiderChart } from './SpiderChart';\n\nconst CATEGORY_COLORS = [\n '#3f51b5', '#e91e63', '#9c27b0', '#ff9800',\n '#4caf50', '#607d8b', '#00bcd4', '#f44336',\n '#795548', '#9e9e9e',\n];\n\nconst useStyles = makeStyles(theme => ({\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n marginBottom: theme.spacing(2),\n },\n userId: {\n fontFamily: 'monospace',\n fontWeight: 700,\n fontSize: '1.1rem',\n },\n skillChip: {\n margin: theme.spacing(0.25),\n fontSize: '0.7rem',\n height: 22,\n },\n categorySection: {\n marginBottom: theme.spacing(2),\n },\n categoryTitle: {\n fontWeight: 600,\n fontSize: '0.8rem',\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n color: theme.palette.text.secondary,\n marginBottom: theme.spacing(0.5),\n },\n repoItem: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n padding: theme.spacing(0.75, 1),\n borderRadius: theme.shape.borderRadius,\n marginBottom: theme.spacing(0.5),\n backgroundColor: theme.palette.action.hover,\n },\n repoName: {\n fontFamily: 'monospace',\n fontSize: '0.85rem',\n },\n chartSubtitle: {\n textAlign: 'center',\n color: theme.palette.text.secondary,\n fontSize: '0.75rem',\n marginTop: theme.spacing(1),\n },\n}));\n\ninterface DeveloperDetailsProps {\n api: DevxpApi;\n userId: string;\n onBack: () => void;\n onCompare: (userId: string) => void;\n isInCompare: boolean;\n}\n\nexport const DeveloperDetails = ({\n api,\n userId,\n onBack,\n onCompare,\n isInCompare,\n}: DeveloperDetailsProps) => {\n const classes = useStyles();\n const [perf, setPerf] = useState<DeveloperPerformance | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const data = await api.getDeveloperPerformance(userId);\n setPerf(data);\n } catch (e: any) {\n setError(e.message ?? 'Failed to load developer performance');\n } finally {\n setLoading(false);\n }\n }, [api, userId]);\n\n useEffect(() => {\n load();\n }, [load]);\n\n // Group unique skills by category\n const groupedSkills = React.useMemo(() => {\n if (!perf) return [];\n const map = new Map<string, string[]>();\n perf.uniqueSkills.forEach(s => {\n if (!map.has(s.category)) map.set(s.category, []);\n map.get(s.category)!.push(s.skillName);\n });\n return Array.from(map.entries()).map(([cat, skills]) => ({ cat, skills }));\n }, [perf]);\n\n return (\n <Box>\n {/* Back header */}\n <Box className={classes.header}>\n <Tooltip title=\"Back to leaderboard\">\n <IconButton size=\"small\" onClick={onBack}>\n <ArrowBackIcon />\n </IconButton>\n </Tooltip>\n <Typography variant=\"h6\" className={classes.userId}>\n {userId}\n </Typography>\n <Tooltip title={isInCompare ? 'In comparison' : 'Add to comparison'}>\n <IconButton\n size=\"small\"\n color={isInCompare ? 'primary' : 'default'}\n onClick={() => onCompare(userId)}\n >\n <CompareArrowsIcon />\n </IconButton>\n </Tooltip>\n </Box>\n\n {loading ? (\n <Box display=\"flex\" justifyContent=\"center\" py={6}>\n <CircularProgress />\n </Box>\n ) : error ? (\n <EmptyState missing=\"data\" title=\"Could not load performance\" description={error} />\n ) : !perf ? null : (\n <Grid container spacing={3}>\n {/* Spider chart */}\n <Grid item xs={12} md={6}>\n <InfoCard title=\"Skill Proficiency Radar\">\n <SpiderChart primary={{ categoryAverages: perf.categoryAverages }} />\n <Typography className={classes.chartSubtitle}>\n Average proficiency (0–10) per skill category\n </Typography>\n </InfoCard>\n </Grid>\n\n {/* Linked repositories */}\n <Grid item xs={12} md={6}>\n <InfoCard title={`Repositories (${perf.linkedRepos.length})`}>\n {perf.linkedRepos.length === 0 ? (\n <Typography variant=\"body2\" color=\"textSecondary\">\n No repositories linked\n </Typography>\n ) : (\n <Box style={{ maxHeight: 320, overflowY: 'auto' }}>\n {perf.linkedRepos.map(repo => (\n <Box key={repo} className={classes.repoItem}>\n <StorageIcon fontSize=\"small\" color=\"action\" />\n <Typography className={classes.repoName}>{repo}</Typography>\n </Box>\n ))}\n </Box>\n )}\n </InfoCard>\n </Grid>\n\n {/* Unique skills by category */}\n <Grid item xs={12}>\n <InfoCard title={`Skills (${perf.uniqueSkills.length} unique)`}>\n {groupedSkills.length === 0 ? (\n <Typography variant=\"body2\" color=\"textSecondary\">\n No skills detected\n </Typography>\n ) : (\n <Box>\n {groupedSkills.map(({ cat, skills }, idx) => (\n <Box key={cat} className={classes.categorySection}>\n <Typography className={classes.categoryTitle}>{cat}</Typography>\n <Box display=\"flex\" flexWrap=\"wrap\">\n {skills.map(skill => (\n <Chip\n key={skill}\n label={skill}\n size=\"small\"\n className={classes.skillChip}\n style={{\n backgroundColor: `${CATEGORY_COLORS[idx % CATEGORY_COLORS.length]}20`,\n color: CATEGORY_COLORS[idx % CATEGORY_COLORS.length],\n }}\n />\n ))}\n </Box>\n {idx < groupedSkills.length - 1 && (\n <Divider style={{ marginTop: 12 }} />\n )}\n </Box>\n ))}\n </Box>\n )}\n </InfoCard>\n </Grid>\n </Grid>\n )}\n </Box>\n );\n};\n"],"names":["React"],"mappings":"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"}