kyd-shared-badge 0.3.65 → 0.3.67
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
|
@@ -31,7 +31,6 @@ import AiUsageBody from './components/AiUsageBody';
|
|
|
31
31
|
import SanctionsMatches from './components/SanctionsMatches';
|
|
32
32
|
import AppendixContent from './components/AppendixContent';
|
|
33
33
|
import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy, getCategoryTooltipCopy, barColor } from './utils/provider';
|
|
34
|
-
import ResumeView from './components/ResumeView';
|
|
35
34
|
type ChatWidgetProps = Partial<{
|
|
36
35
|
api: string;
|
|
37
36
|
title: string;
|
|
@@ -224,21 +223,6 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
224
223
|
|
|
225
224
|
|
|
226
225
|
<div className="pt-8 space-y-8">
|
|
227
|
-
{(() => {
|
|
228
|
-
const resumeJson = (assessmentResult as any)?.resume?.json || null;
|
|
229
|
-
const roleName = (() => {
|
|
230
|
-
try { return (assessmentResult?.enterprise_match?.role?.name) || undefined; } catch { return undefined; }
|
|
231
|
-
})();
|
|
232
|
-
if (!resumeJson) return null;
|
|
233
|
-
return (
|
|
234
|
-
<Reveal headless={isHeadless}>
|
|
235
|
-
<div className={'kyd-avoid-break'}>
|
|
236
|
-
<h3 className={'text-2xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>Resume</h3>
|
|
237
|
-
<ResumeView resume={resumeJson} roleName={roleName} showDownloadButton={false} />
|
|
238
|
-
</div>
|
|
239
|
-
</Reveal>
|
|
240
|
-
);
|
|
241
|
-
})()}
|
|
242
226
|
<Reveal headless={isHeadless} as={'h3'} offsetY={8} className={`text-2xl font-bold ${isHeadless ? 'kyd-break-before' : ''}`} style={{ color: 'var(--text-main)' }}>KYD Risk - Overview</Reveal>
|
|
243
227
|
|
|
244
228
|
{/* Risk Graph Insights and Category Bars */}
|
|
@@ -113,11 +113,11 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
113
113
|
const tabs = useMemo(() => {
|
|
114
114
|
const arr = [
|
|
115
115
|
{ key: 'overview', label: 'Overview' },
|
|
116
|
-
{ key: 'resume', label: 'Resume' },
|
|
117
|
-
{ key: 'role', label: 'Role Fit & Coaching' },
|
|
118
116
|
{ key: 'technical', label: 'KYD Technical' },
|
|
119
117
|
{ key: 'risk', label: 'KYD Risk' },
|
|
120
118
|
{ key: 'ai', label: 'KYD AI' },
|
|
119
|
+
{ key: 'role', label: 'Role Fit & Coaching' },
|
|
120
|
+
{ key: 'resume', label: 'Resume' },
|
|
121
121
|
{ key: 'appendix', label: 'Appendix' }
|
|
122
122
|
];
|
|
123
123
|
return arr;
|
|
@@ -564,11 +564,11 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
564
564
|
<TabNav />
|
|
565
565
|
<div className={'px-2 sm:px-0 pb-2'}>
|
|
566
566
|
{activeTab === 'overview' && OverviewSection()}
|
|
567
|
-
{activeTab === 'resume' && <ResumeSection />}
|
|
568
|
-
{activeTab === 'role' && <RoleFitSection />}
|
|
569
567
|
{activeTab === 'technical' && TechnicalSection()}
|
|
570
568
|
{activeTab === 'risk' && RiskSection()}
|
|
571
569
|
{activeTab === 'ai' && AiSection()}
|
|
570
|
+
{activeTab === 'role' && <RoleFitSection />}
|
|
571
|
+
{activeTab === 'resume' && <ResumeSection />}
|
|
572
572
|
{activeTab === 'appendix' && AppendixSection()}
|
|
573
573
|
</div>
|
|
574
574
|
</>
|
|
@@ -5,6 +5,10 @@ import { formatLocalDate } from '../utils/date';
|
|
|
5
5
|
import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } from 'react-icons/fa';
|
|
6
6
|
import { SiCredly, SiFiverr } from 'react-icons/si';
|
|
7
7
|
import { DomainCSVRow } from '../types';
|
|
8
|
+
import { red, yellow, green, hexToRgba } from '../colors';
|
|
9
|
+
import RiskIcon from './icons/risk';
|
|
10
|
+
import CodeIcon from './icons/code';
|
|
11
|
+
import AiIcon from './icons/ai';
|
|
8
12
|
|
|
9
13
|
interface SanctionSource {
|
|
10
14
|
issuingEntity: string;
|
|
@@ -134,6 +138,8 @@ const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource
|
|
|
134
138
|
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName, genreMapping, headless }) => {
|
|
135
139
|
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
136
140
|
const [expanded, setExpanded] = useState<{ [k: number]: boolean }>({});
|
|
141
|
+
const [sortBy, setSortBy] = useState<'pillar' | 'category' | 'label' | 'weight' | null>(null);
|
|
142
|
+
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
|
|
137
143
|
|
|
138
144
|
useEffect(() => {
|
|
139
145
|
const flashIfRule = () => {
|
|
@@ -168,6 +174,27 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
168
174
|
? ["Country", "Entity Type", "Entity/Domain", "Searched On", "Value", "Findings"]
|
|
169
175
|
: ["Platform", "KYD Pillar", "Category", "Observation", "Weight Impact"];
|
|
170
176
|
|
|
177
|
+
const sortableBusinessHeaders: Record<string, 'pillar' | 'category' | 'label' | 'weight'> = {
|
|
178
|
+
'KYD Pillar': 'pillar',
|
|
179
|
+
'Category': 'category',
|
|
180
|
+
'Observation': 'label',
|
|
181
|
+
'Weight Impact': 'weight',
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const onHeaderClick = (header: string) => {
|
|
185
|
+
if (type !== 'business_rules') return;
|
|
186
|
+
const key = sortableBusinessHeaders[header as keyof typeof sortableBusinessHeaders];
|
|
187
|
+
if (!key) return;
|
|
188
|
+
setSortBy(prev => {
|
|
189
|
+
if (prev === key) {
|
|
190
|
+
setSortDir(d => (d === 'asc' ? 'desc' : 'asc'));
|
|
191
|
+
return prev;
|
|
192
|
+
}
|
|
193
|
+
setSortDir('asc');
|
|
194
|
+
return key;
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
171
198
|
// Build quick lookup: category -> KYD Pillar (e.g., Technical or Risk)
|
|
172
199
|
const categoryToPillar: Record<string, string> = (() => {
|
|
173
200
|
const map: Record<string, string> = {};
|
|
@@ -214,16 +241,34 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
214
241
|
? (parsedSources as SanctionSource[]).slice().sort((a, b) => (b.matched === true ? 1 : 0) - (a.matched === true ? 1 : 0))
|
|
215
242
|
: type === 'business_rules'
|
|
216
243
|
? (() => {
|
|
217
|
-
const orderForPillar = (p: string) => (p === 'Technical' ? 0 : p === 'Risk' ? 1 : 2);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
244
|
+
const orderForPillar = (p: string) => (p === 'Technical' ? 0 : p === 'Risk' ? 1 : p === 'AI' ? 2 : 3);
|
|
245
|
+
const items = (parsedSources as (BusinessRuleRow & { pillar?: string })[]).slice();
|
|
246
|
+
if (!sortBy) {
|
|
247
|
+
return items.sort((a, b) => {
|
|
248
|
+
const ap = (a as any).pillar || '';
|
|
249
|
+
const bp = (b as any).pillar || '';
|
|
250
|
+
const cmpPillar = orderForPillar(ap) - orderForPillar(bp);
|
|
251
|
+
if (cmpPillar !== 0) return cmpPillar;
|
|
252
|
+
const ac = (a.category || '').localeCompare(b.category || '');
|
|
253
|
+
if (ac !== 0) return ac;
|
|
254
|
+
return Number(b.weight || 0) - Number(a.weight || 0);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return items.sort((a, b) => {
|
|
258
|
+
const dir = sortDir === 'asc' ? 1 : -1;
|
|
259
|
+
if (sortBy === 'pillar') {
|
|
260
|
+
return dir * (orderForPillar((a as any).pillar || '') - orderForPillar((b as any).pillar || ''));
|
|
261
|
+
}
|
|
262
|
+
if (sortBy === 'category') {
|
|
263
|
+
return dir * ((a.category || '').localeCompare(b.category || ''));
|
|
264
|
+
}
|
|
265
|
+
if (sortBy === 'label') {
|
|
266
|
+
return dir * ((a.label || '').localeCompare(b.label || ''));
|
|
267
|
+
}
|
|
268
|
+
if (sortBy === 'weight') {
|
|
269
|
+
return dir * (Number(a.weight || 0) - Number(b.weight || 0));
|
|
270
|
+
}
|
|
271
|
+
return 0;
|
|
227
272
|
});
|
|
228
273
|
})()
|
|
229
274
|
: parsedSources;
|
|
@@ -251,11 +296,23 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
251
296
|
</colgroup>
|
|
252
297
|
<thead className={''}>
|
|
253
298
|
<tr>
|
|
254
|
-
{headers.map(header =>
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
299
|
+
{headers.map(header => {
|
|
300
|
+
const isSortable = type === 'business_rules' && Object.prototype.hasOwnProperty.call(sortableBusinessHeaders, header);
|
|
301
|
+
const isActive = isSortable && sortBy === sortableBusinessHeaders[header as keyof typeof sortableBusinessHeaders];
|
|
302
|
+
const arrow = isActive ? (sortDir === 'asc' ? ' ▲' : ' ▼') : '';
|
|
303
|
+
return (
|
|
304
|
+
<th
|
|
305
|
+
key={header}
|
|
306
|
+
scope="col"
|
|
307
|
+
onClick={() => onHeaderClick(header)}
|
|
308
|
+
className={'px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider'}
|
|
309
|
+
style={{ color: 'var(--text-secondary)', cursor: isSortable ? 'pointer' : 'default', userSelect: isSortable ? 'none' : 'auto' }}
|
|
310
|
+
aria-sort={isActive ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'}
|
|
311
|
+
>
|
|
312
|
+
{header}{arrow}
|
|
313
|
+
</th>
|
|
314
|
+
);
|
|
315
|
+
})}
|
|
259
316
|
</tr>
|
|
260
317
|
</thead>
|
|
261
318
|
<tbody className={''}>
|
|
@@ -280,7 +337,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
280
337
|
}
|
|
281
338
|
const br = source as BusinessRuleRow & { pillar?: string };
|
|
282
339
|
const anchorId = br.uid ? `rule-${br.uid}` : undefined;
|
|
283
|
-
const ProviderIcon = ({ name }: { name?: string }) => {
|
|
340
|
+
const ProviderIcon = ({ name, pillar }: { name?: string, pillar?: string }) => {
|
|
284
341
|
const n = (name || '').toLowerCase();
|
|
285
342
|
if (n.includes('github')) return <FaGithub />;
|
|
286
343
|
if (n.includes('gitlab')) return <FaGitlab />;
|
|
@@ -290,13 +347,29 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
290
347
|
if (n.includes('kaggle')) return <FaKaggle />;
|
|
291
348
|
if (n.includes('google')) return <FaGoogle />;
|
|
292
349
|
if (n.includes('linkedin')) return <FaLinkedin />;
|
|
350
|
+
const p = (pillar || '').toLowerCase();
|
|
351
|
+
if (p === 'risk') return <RiskIcon width={28} height={28} />;
|
|
352
|
+
if (p === 'technical') return <CodeIcon width={28} height={28} />;
|
|
353
|
+
if (p === 'ai') return <AiIcon width={28} height={28} />;
|
|
293
354
|
return <span className="inline-block w-8 h-8 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
|
|
294
355
|
};
|
|
295
356
|
return (
|
|
296
|
-
<tr
|
|
357
|
+
<tr
|
|
358
|
+
id={anchorId}
|
|
359
|
+
key={index}
|
|
360
|
+
className={'transition-colors hover:bg-black/5'}
|
|
361
|
+
style={{
|
|
362
|
+
backgroundColor: (() => {
|
|
363
|
+
const w = Number(br.weight);
|
|
364
|
+
if (!Number.isFinite(w)) return undefined;
|
|
365
|
+
const hex = w > 0 ? green : w < 0 ? red : yellow;
|
|
366
|
+
return hexToRgba(hex, 0.06);
|
|
367
|
+
})(),
|
|
368
|
+
}}
|
|
369
|
+
>
|
|
297
370
|
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
298
371
|
<span title={br.provider || ''} className={'inline-flex items-center text-4xl'} style={{ color: 'var(--text-main)' }}>
|
|
299
|
-
<ProviderIcon name={br.provider} />
|
|
372
|
+
<ProviderIcon name={br.provider} pillar={br.pillar} />
|
|
300
373
|
</span>
|
|
301
374
|
</td>
|
|
302
375
|
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.pillar || '—'}</td>
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,10 @@ export type Provider = {
|
|
|
9
9
|
hoverBg: string;
|
|
10
10
|
text: string;
|
|
11
11
|
};
|
|
12
|
+
endpoint?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
copy?: string;
|
|
15
|
+
beta?: boolean;
|
|
12
16
|
};
|
|
13
17
|
|
|
14
18
|
export interface PublicBadgeData {
|
|
@@ -45,6 +49,7 @@ export type User = {
|
|
|
45
49
|
assessments: PublicBadgeData[];
|
|
46
50
|
connectedAccounts?: { name: string; url: string | null; handle: string | null; observedAt?: string }[];
|
|
47
51
|
userPendingRetryBadgeId?: string;
|
|
52
|
+
createdAt?: string;
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
export interface SummaryScore {
|