Ad Hoc Data Selectietool
Geographic Search Enhancement for Legacy B2B Data Platform
Visit AdHocData.beOverview
Complete UI/UX redesign and refactor of a B2B selection tool for 1.2M+ Belgian companies. The original spaghetti code was hard to maintain - now split into 33 modules. During the project, I discovered that the existing dataset and API endpoints also enabled geographic and thematic search - two features I added as extras.
The Challenge
Modernizing a legacy stack (Angular 1.x, LESS, vanilla JS) with unstructured code into a maintainable system, without changing existing functionality or API structure.
The Solution
Code Refactor
Spaghetti code split into 33 organized modules with clear sections and config objects.
Design System
From 352 to 5,848 lines of CSS with 40+ design tokens, fully scoped to avoid conflicts.
Geo Search
GeoJSON polygons for 1,053 Belgian regions with an interactive Leaflet map interface for visual multi-selection - discovered from existing coordinates in exports.
Thematic Search
400+ search terms that automatically map to NACE codes - built on existing API endpoints.
Thematic Search
Search by industry theme instead of NACE codes
Users no longer need to know exact NACE codes. Simply type 'horeca', 'bakker', or 'advocaat' and the system automatically maps these terms to the correct NACE codes. Supports 400+ search terms in both Dutch and French, including singular and plural forms.
horeca→I, 55, 56bakker→10.71, 47.24advocaat→69.10Klaar om te filteren?
Stel uw ideale doelgroep samen door branches te selecteren.
- Zoek op thema (horeca, bakker)
- Selecteer meerdere codes
- Check de teller live rechts
UI/UX Redesign
From minimal Bootstrap to a complete custom design system


Color System
22 color tokens: primary, secondary, accent, backgrounds, text, and status colors
Spacing Scale
5 spacing tokens (xs to xl) for consistent margins and padding
Shadow Hierarchy
3 shadow levels (sm, md, lg) for visual depth without harshness
Scoped Styles
All CSS scoped under .ahd-selectietool-modern - zero conflicts with existing styles
Code Refactoring
From spaghetti to structured: a complete architectural overhaul
Error Handling
⚠️ Haiku poems as error messages
✓ Try/catch with meaningful logging
const explanations = {
noField: `
expectation void
a mystery wavering
surge of heat and cold
`,
notAFunction: `
action required
laziness or negligence
action required
`,
};function saveFilterState() {
try {
var state = {
selectedNaceCodes: vm.selectedNaceCodes || [],
selectedLocations: vm.selectedLocations || { included: [], excluded: [] },
filters: {}
};
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
console.log('[SessionStorage] Filter state saved');
} catch (e) {
console.warn('[SessionStorage] Could not save:', e);
}
}Code Structure
⚠️ No sections, mixed concerns, jQuery in Angular
✓ Clear sections with config objects
Object.assign(window, {
vars, vm: this,
$http, $params, $webhooks, $scope, $filter,
wipe: () => {
sessionStorage.clear();
localStorage.clear();
},
});
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="collapse"]').on('click', function () {
var e = $(this);
setTimeout(function () { /* nested callbacks */ }, 400);
});// ============================================
// API CONFIGURATIE
// ============================================
var API_CONFIG = {
baseUrl: '/api',
merchantId: null,
langKey: 'NL'
};
// ============================================
// LOOKUP ID MAPPING
// ============================================
var LOOKUP_IDS = {
NACE_2025: { NL: '2025_nace', FR: '2025_nace' },
WERKKLASSE: { NL: 'NLBE_WERKNEMERSKLASSE', FR: 'FRBE_WERKNEMERSKLASSE' },
// ...
};Initialization Flow
⚠️ Nested setTimeout callbacks with magic delays
✓ Clean promise chain with named functions
Promise.allSettled(FilterField.lookupRequestList)
.then((values) => {
setTimeout( () => {
if (isModelDictUsed) {
for (let item of fieldContainer.getItems()) {
// deep nesting continues...
}
}
vm.addressCount.count();
}, 500); // magic number delay
});function initializeApp() {
loadLookupData()
.then(buildFilterTree)
.then(restoreFilterState)
.then(updateCount)
.catch(handleError);
}
function restoreFilterState() {
var stored = sessionStorage.getItem(STORAGE_KEY);
if (!stored) return;
var state = JSON.parse(stored);
// Clean restore logic
}CSS Design System
⚠️ Hardcoded values, no scoping, Bootstrap overrides
✓ 40+ design tokens, fully scoped styles
#pills-tab:before {
border-left: 1px solid var(--bs-secondary);
border-top-right-radius: 0.375rem;
}
ul.fancytree-container ul {
padding: 0 !important;
padding-left: 1em !important;
}// Design Tokens
@ahd-primary: #2563eb;
@ahd-border-radius: 10px;
@ahd-shadow-md: 0 4px 6px rgba(0,0,0,0.05);
// All styles scoped
.ahd-selectietool-modern {
background: @ahd-bg-main;
border-radius: @ahd-border-radius;
box-shadow: @ahd-shadow-md;
}Results
Geographic search: filter 1.2M+ companies across 43 arrondissements, 10 provinces, and 1,000+ postal codes
Thematic search: 400+ search terms to find companies by industry without knowing NACE codes
Code refactored from 0 to 33 organized modules with 0 TODOs remaining
Design system: 352 → 5,848 lines of CSS with 40+ design tokens and 100% scoped styles
Full backward compatibility - zero changes to existing API endpoints