Sifa professional network frontend (Next.js, React, TailwindCSS)
sifa.id/
1import type { ProfileSkill } from '@/lib/types';
2
3export const CATEGORY_ORDER = [
4 'technical',
5 'business',
6 'creative',
7 'interpersonal',
8 'industry',
9 'community',
10 'security',
11] as const;
12
13export type SkillCategory = (typeof CATEGORY_ORDER)[number];
14
15export const CATEGORY_LABELS: Record<string, string> = {
16 technical: 'Technical',
17 business: 'Business',
18 creative: 'Creative',
19 interpersonal: 'Interpersonal',
20 industry: 'Industry',
21 community: 'Community',
22 security: 'Security',
23 other: 'Other',
24};
25
26/**
27 * Groups skills by category in a defined display order.
28 *
29 * - Known categories appear in CATEGORY_ORDER
30 * - Skills with no category or unrecognised categories go to "other"
31 * - Within each group: sorted by endorsementCount desc, then alphabetical by skillName
32 * - Empty groups are omitted
33 */
34export function groupSkillsByCategory(skills: ProfileSkill[]): [string, ProfileSkill[]][] {
35 const grouped = new Map<string, ProfileSkill[]>();
36
37 for (const skill of skills) {
38 const normalised = skill.category?.toLowerCase().trim() ?? '';
39 const key = (CATEGORY_ORDER as readonly string[]).includes(normalised) ? normalised : 'other';
40 if (!grouped.has(key)) grouped.set(key, []);
41 grouped.get(key)!.push(skill);
42 }
43
44 // Sort within each group
45 for (const [, groupSkills] of grouped) {
46 groupSkills.sort((a, b) => {
47 const countDiff = (b.endorsementCount ?? 0) - (a.endorsementCount ?? 0);
48 if (countDiff !== 0) return countDiff;
49 return a.name.localeCompare(b.name);
50 });
51 }
52
53 // Build ordered entries: known categories first, then "other"
54 const ordered: [string, ProfileSkill[]][] = [];
55 for (const cat of CATEGORY_ORDER) {
56 const group = grouped.get(cat);
57 if (group?.length) ordered.push([cat, group]);
58 }
59 const otherGroup = grouped.get('other');
60 if (otherGroup?.length) ordered.push(['other', otherGroup]);
61
62 return ordered;
63}