kyd-shared-badge 0.3.94 → 0.3.95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.94",
3
+ "version": "0.3.95",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -288,7 +288,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
288
288
  <Reveal headless={isHeadless}>
289
289
  <div className={'kyd-avoid-break'}>
290
290
  <h4 className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Skills Footprint</h4>
291
- <SkillsBubble skillsCategoryRadar={graphInsights?.skillsCategoryRadar} headless={isHeadless} />
291
+ <SkillsBubble skillsCategoryRadar={graphInsights?.skillsCategoryRadar} headless={isHeadless} skillsByCategory={assessmentResult?.graph_insights?.skillsByCategory} />
292
292
  </div>
293
293
  </Reveal>
294
294
  <div className={'pt-6 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
@@ -56,14 +56,15 @@ const pickGreenByExperience = (experience: number): string => {
56
56
  return green5;
57
57
  };
58
58
 
59
- export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, headless }: { skillsCategoryRadar?: SkillsRadarPoint[]; skillsByCategory?: Record<string, string[]>; headless?: boolean }) {
59
+ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, skillsMeta, headless }: { skillsCategoryRadar?: SkillsRadarPoint[]; skillsByCategory?: Record<string, string[]>; skillsMeta?: Record<string, { presence?: 'certified' | 'observed' | 'self-reported'; years?: number }>; headless?: boolean }) {
60
60
  const hasRadar = !!(skillsCategoryRadar && skillsCategoryRadar.length > 0);
61
61
  const skillsRadarLimited = (skillsCategoryRadar || []).slice(0, 24);
62
62
  const containerRef = useRef<HTMLDivElement>(null);
63
63
  const legendRef = useRef<HTMLDivElement>(null);
64
64
  const [legendTooltip, setLegendTooltip] = useState<HoverTooltipState>(null);
65
65
  const [activeCategory, setActiveCategory] = useState<string | null>(null);
66
-
66
+ console.log('skillsRadarLimited', skillsRadarLimited);
67
+ console.log('skillsByCategory', skillsByCategory);
67
68
  useEffect(() => {
68
69
  if (typeof window !== 'undefined') {
69
70
  const id = window.setTimeout(() => {
@@ -128,20 +129,44 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, he
128
129
  const skillsGrid = useMemo(() => {
129
130
  const cat = activeCategory || '';
130
131
  const list = (skillsByCategory && cat in (skillsByCategory || {})) ? (skillsByCategory?.[cat] || []) : [];
131
- const items = list.slice(0, 10);
132
+ // Enrich with meta and sort by years desc
133
+ const enriched = list.map((name) => {
134
+ const meta = skillsMeta?.[name] || {};
135
+ return { name, years: Number(meta.years || 0), presence: (meta.presence as string) || '' };
136
+ }).sort((a, b) => b.years - a.years || a.name.localeCompare(b.name));
137
+ const items = enriched.slice(0, 10);
132
138
  const overflow = list.length - items.length;
133
- const display: string[] = [];
134
- for (let i = 0; i < Math.min(9, items.length); i++) display.push(items[i]);
139
+ const display: Array<{ label: string; years?: number; presence?: string } | ''> = [];
140
+ for (let i = 0; i < Math.min(9, items.length); i++) {
141
+ const it = items[i];
142
+ const label = it.name ? `${it.name}` : '';
143
+ display.push({ label, years: it.years, presence: it.presence });
144
+ }
135
145
  if (list.length > 10) {
136
- display.push(`Others (${overflow})`);
146
+ display.push({ label: `Others (${overflow})` });
137
147
  } else if (items.length >= 10) {
138
- display.push(items[9]);
148
+ const it = items[9];
149
+ display.push({ label: it?.name || '', years: it?.years, presence: it?.presence });
139
150
  } else {
140
- if (items.length > 9) display.push(items[9]);
151
+ if (items.length > 9) {
152
+ const it = items[9];
153
+ display.push({ label: it?.name || '', years: it?.years, presence: it?.presence });
154
+ }
141
155
  }
142
156
  while (display.length < 10) display.push('');
143
157
  return display;
144
- }, [activeCategory, skillsByCategory]);
158
+ }, [activeCategory, skillsByCategory, skillsMeta]);
159
+
160
+ // Compute category percent share (by ratio) for header display
161
+ const categoryPercentMap = useMemo(() => {
162
+ const total = bubbles.reduce((sum, b) => sum + (b.data?.ratio || 0), 0);
163
+ const map: Record<string, number> = {};
164
+ for (const b of bubbles) {
165
+ const p = total > 0 ? Math.round(((b.data?.ratio || 0) / total) * 100) : 0;
166
+ map[b.label] = p;
167
+ }
168
+ return map;
169
+ }, [bubbles]);
145
170
 
146
171
  if (!hasRadar) return null;
147
172
 
@@ -199,48 +224,11 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, he
199
224
  (node as HTMLElement).style.color = 'var(--text-main)';
200
225
  (node as HTMLElement).style.pointerEvents = 'none';
201
226
 
202
- const header = document.createElement('div');
203
- header.className = 'mb-1';
204
227
  const title = document.createElement('div');
205
228
  title.className = 'font-medium';
206
229
  title.style.color = 'var(--text-main)';
207
230
  title.textContent = String(d.displayText || d._id || '');
208
- header.appendChild(title);
209
-
210
- const content = document.createElement('div');
211
- content.className = 'space-y-1';
212
- const ratio = Number(d.value || 0);
213
- const experience = Number(d.colorValue || 0);
214
-
215
- const row1 = document.createElement('div');
216
- row1.className = 'flex items-center justify-between gap-3';
217
- const row1Left = document.createElement('span');
218
- row1Left.style.color = 'var(--text-secondary)';
219
- row1Left.textContent = 'Ratio';
220
- const row1Right = document.createElement('span');
221
- row1Right.className = 'font-medium';
222
- row1Right.style.color = 'var(--text-main)';
223
- row1Right.textContent = `${ratio}%`;
224
- row1.appendChild(row1Left);
225
- row1.appendChild(row1Right);
226
-
227
- const row2 = document.createElement('div');
228
- row2.className = 'flex items-center justify-between gap-3';
229
- const row2Left = document.createElement('span');
230
- row2Left.style.color = 'var(--text-secondary)';
231
- row2Left.textContent = 'Experience';
232
- const row2Right = document.createElement('span');
233
- row2Right.className = 'font-medium';
234
- row2Right.style.color = 'var(--text-main)';
235
- row2Right.textContent = String(experience);
236
- row2.appendChild(row2Left);
237
- row2.appendChild(row2Right);
238
-
239
- content.appendChild(row1);
240
- content.appendChild(row2);
241
-
242
- node.appendChild(header);
243
- node.appendChild(content);
231
+ node.appendChild(title);
244
232
  } catch {}
245
233
  }}
246
234
  />
@@ -249,12 +237,22 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, he
249
237
  <div ref={legendRef} className={'kyd-avoid-break'} style={{ position: 'relative', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
250
238
  <div className="mb-2 text-xs font-medium" style={{ color: 'var(--text-main)' }}>
251
239
  {activeCategory ? `Category: ${activeCategory}` : 'Category'}
240
+ {activeCategory ? ` • ${categoryPercentMap[activeCategory] ?? 0}%` : ''}
252
241
  </div>
253
242
  <div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs" style={{ color: 'var(--text-secondary)', height: 160 }}>
254
- {skillsGrid.map((label, idx) => (
243
+ {skillsGrid.map((entry, idx) => (
255
244
  <div key={idx} className="flex items-center gap-2 min-w-0">
256
- <span className={'inline-block h-2 w-2 rounded-full'} style={{ backgroundColor: label ? 'var(--icon-button-secondary)' : 'transparent', flexShrink: 0 }} />
257
- <span className="truncate" title={label}>{label || '\u00A0'}</span>
245
+ <span className={'inline-block h-2 w-2 rounded-full'} style={{ backgroundColor: entry ? 'var(--icon-button-secondary)' : 'transparent', flexShrink: 0 }} />
246
+ <span className="shrink-0 opacity-70">{idx + 1}.</span>
247
+ {entry && typeof entry !== 'string' ? (
248
+ <span className="truncate" title={entry.label}>
249
+ {entry.label}
250
+ {entry.years ? ` • ${entry.years}y` : ''}
251
+ {entry.presence ? ` • ${entry.presence}` : ''}
252
+ </span>
253
+ ) : (
254
+ <span className="truncate">{typeof entry === 'string' ? entry : '\u00A0'}</span>
255
+ )}
258
256
  </div>
259
257
  ))}
260
258
  </div>
package/src/types.ts CHANGED
@@ -383,6 +383,8 @@ export interface GraphInsightsPayload {
383
383
  }>;
384
384
  // New: mapping of category -> list of skills contributing to that category
385
385
  skillsByCategory?: Record<string, string[]>;
386
+ // New: per-skill metadata used by UI (e.g., presence label, experience years)
387
+ skillsMeta?: Record<string, { presence?: 'certified' | 'observed' | 'self-reported'; years?: number }>;
386
388
  // New: Flattened list of business rule selections (for appendix)
387
389
  business_rules_all?: Array<{
388
390
  provider: string;