+ {/* Header */}
+
+
Skill Marketplace
+
+ {data?.totalResources ?? 0} active resources · Search skills, identify gaps, plan capacity
+
+
+
+ {/* ── Section 1: Skill Search ──────────────────────────────────────────── */}
+
+
Skill Search
+
+
+ {/* Search input */}
+
+
+
setSearchSkill(e.target.value)}
+ className="pl-8 pr-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 dark:text-gray-200 focus:outline-none focus:ring-2 focus:ring-brand-500 w-60"
+ />
+
+
+ {/* Min proficiency */}
+
+
Min. proficiency:
+
+ {[1, 2, 3, 4, 5].map((lvl) => (
+
+ ))}
+
+
+
+ {/* Available only */}
+
+
+
+ {/* Search results table */}
+ {debouncedSearch && debouncedSearch.trim().length > 0 && (
+
+ {sortedSearch.length === 0 ? (
+
+ No resources found with "{debouncedSearch}" at proficiency {minProficiency}+.
+
+ ) : (
+ <>
+
+ {sortedSearch.length} resource{sortedSearch.length !== 1 ? "s" : ""} found
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {sortedSearch.map((r) => (
+
+ |
+
+ {r.displayName}
+
+ |
+ {r.chapter ?? "---"} |
+ {r.skillName} |
+
+
+ |
+
+ = 90
+ ? "text-red-600 dark:text-red-400"
+ : r.utilizationPercent >= 70
+ ? "text-amber-600 dark:text-amber-400"
+ : "text-green-600 dark:text-green-400"
+ }`}>
+ {r.utilizationPercent}%
+
+ |
+
+ {formatDate(r.availableFrom)}
+ |
+
+ ))}
+
+
+
+ >
+ )}
+
+ )}
+
+
+ {/* ── Section 2: Skill Gap Heat Map ────────────────────────────────────── */}
+
+
+
Skill Gap Analysis
+
+ Supply = resources with proficiency 3+ · Demand = unfilled demand requirements · Sorted by largest gap
+
+
+
+ {sortedGap.length === 0 ? (
+
+ No gap data available. Gaps appear when projects have unfilled demand requirements with required skills.
+
+ ) : (
+
+
+
+
+
+
+
+
+ | Visual |
+
+
+
+ {sortedGap.map((row) => {
+ const maxBar = Math.max(row.supply, row.demand, 1);
+ return (
+
+ |
+
+ |
+ {row.supply} |
+ {row.demand} |
+
+
+ |
+
+
+ 0 ? 4 : 0 }}
+ title={`Supply: ${row.supply}`}
+ />
+ 0 ? 4 : 0 }}
+ title={`Demand: ${row.demand}`}
+ />
+
+ |
+
+ );
+ })}
+
+
+
+
+ Supply (prof. 3+)
+
+
+ Demand (unfilled)
+
+
+
+ )}
+
+
+ {/* ── Section 3: Skill Distribution ────────────────────────────────────── */}
+ {(data?.distribution ?? []).length > 0 && (
+
+
+ Top 20 Skills by Resource Count
+
+
+
+ Bar color = average proficiency (light to dark = low to high)
+
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/components/layout/AppShell.tsx b/apps/web/src/components/layout/AppShell.tsx
index 01f07b4..e12d88f 100644
--- a/apps/web/src/components/layout/AppShell.tsx
+++ b/apps/web/src/components/layout/AppShell.tsx
@@ -55,6 +55,9 @@ function RolesIcon() {
function SkillsIcon() {
return