Archived
2
0
This repository has been archived on 2025-07-16. You can view files and clone it, but cannot push or open issues or pull requests.
cc24-hub/src/js/search.js
overcuriousity 64d5e75045 progress
2025-07-13 22:18:53 +02:00

383 lines
14 KiB
JavaScript

// File: ./src/js/search.js
// Search and filtering functionality
(function() {
'use strict';
// State management
let currentFilters = {
search: '',
domain: '',
phase: '',
mode: 'selector'
};
let allTools = [];
let domains = [];
let phases = [];
// DOM elements
let searchInput;
let domainSelect;
let phaseSelect;
let resetButton;
let resetEmptyButton;
let toolResults;
let toolsGrid;
let defaultState;
let noResults;
let resultsTitle;
let resultsCount;
let resultsDescription;
let viewModes;
let modeButtons;
// Initialize when DOM is loaded
function init() {
// Get data from global variables (set by template)
if (typeof window.toolsData !== 'undefined') {
allTools = window.toolsData;
domains = window.domains || [];
phases = window.phases || [];
}
// Get DOM elements
searchInput = document.getElementById('search-input');
domainSelect = document.getElementById('domain-select');
phaseSelect = document.getElementById('phase-select');
resetButton = document.getElementById('reset-filters');
resetEmptyButton = document.getElementById('reset-filters-empty');
toolResults = document.getElementById('tool-results');
toolsGrid = document.getElementById('tools-grid');
defaultState = document.getElementById('default-state');
noResults = document.getElementById('no-results');
resultsTitle = document.getElementById('results-title');
resultsCount = document.getElementById('results-count');
resultsDescription = document.getElementById('results-description');
// View mode elements
viewModes = {
selector: document.getElementById('view-selector'),
matrix: document.getElementById('view-matrix'),
saas: document.getElementById('view-saas')
};
modeButtons = {
selector: document.getElementById('mode-selector'),
matrix: document.getElementById('mode-matrix'),
saas: document.getElementById('mode-saas')
};
// Add event listeners
if (searchInput) {
searchInput.addEventListener('input', handleSearchChange);
}
if (domainSelect) {
domainSelect.addEventListener('change', handleDomainChange);
}
if (phaseSelect) {
phaseSelect.addEventListener('change', handlePhaseChange);
}
if (resetButton) {
resetButton.addEventListener('click', resetFilters);
}
if (resetEmptyButton) {
resetEmptyButton.addEventListener('click', resetFilters);
}
// Mode switching
Object.keys(modeButtons).forEach(mode => {
if (modeButtons[mode]) {
modeButtons[mode].addEventListener('click', () => switchMode(mode));
}
});
// Initialize views
updateDisplay();
populateMatrix();
populateSaasGrid();
}
// Event handlers
function handleSearchChange(event) {
currentFilters.search = event.target.value;
updateDisplay();
}
function handleDomainChange(event) {
currentFilters.domain = event.target.value;
updateDisplay();
}
function handlePhaseChange(event) {
currentFilters.phase = event.target.value;
updateDisplay();
}
function resetFilters() {
currentFilters.search = '';
currentFilters.domain = '';
currentFilters.phase = '';
if (searchInput) searchInput.value = '';
if (domainSelect) domainSelect.value = '';
if (phaseSelect) phaseSelect.value = '';
updateDisplay();
}
function switchMode(mode) {
currentFilters.mode = mode;
// Update button states
Object.keys(modeButtons).forEach(m => {
if (modeButtons[m]) {
modeButtons[m].classList.toggle('view-mode-active', m === mode);
modeButtons[m].classList.toggle('view-mode-btn', m !== mode);
}
});
// Update view visibility
Object.keys(viewModes).forEach(m => {
if (viewModes[m]) {
viewModes[m].classList.toggle('hidden', m !== mode);
viewModes[m].classList.toggle('view-mode-active', m === mode);
}
});
// Update displays based on mode
if (mode === 'matrix') {
populateMatrix();
} else if (mode === 'saas') {
populateSaasGrid();
} else {
updateDisplay();
}
}
// Filter tools based on current filters
function filterTools() {
let filtered = allTools;
// Filter by type based on mode
if (currentFilters.mode === 'saas') {
filtered = filtered.filter(tool => tool.type === 'SaaS');
} else if (currentFilters.mode === 'selector' || currentFilters.mode === 'matrix') {
filtered = filtered.filter(tool => tool.type === 'FOSS');
}
// Apply search filter
if (currentFilters.search) {
const searchTerm = currentFilters.search.toLowerCase();
filtered = filtered.filter(tool =>
tool.name.toLowerCase().includes(searchTerm) ||
tool.description.toLowerCase().includes(searchTerm) ||
(tool.tags && tool.tags.some(tag => tag.toLowerCase().includes(searchTerm)))
);
}
// Apply domain filter (only for selector mode)
if (currentFilters.mode === 'selector' && currentFilters.domain) {
filtered = filtered.filter(tool =>
tool.domains && tool.domains.includes(currentFilters.domain)
);
}
// Apply phase filter (only for selector mode)
if (currentFilters.mode === 'selector' && currentFilters.phase) {
filtered = filtered.filter(tool =>
tool.phases && tool.phases.includes(currentFilters.phase)
);
}
return filtered;
}
// Update the display based on current filters
function updateDisplay() {
if (currentFilters.mode !== 'selector') return;
const hasFilters = currentFilters.search || currentFilters.domain || currentFilters.phase;
if (!hasFilters) {
// Show default state
if (toolResults) toolResults.classList.add('hidden');
if (defaultState) defaultState.classList.remove('hidden');
return;
}
const filtered = filterTools();
// Hide default state, show results
if (defaultState) defaultState.classList.add('hidden');
if (toolResults) toolResults.classList.remove('hidden');
// Update results metadata
if (resultsCount) resultsCount.textContent = filtered.length;
updateResultsDescription();
// Show/hide no results message
if (filtered.length === 0) {
if (toolsGrid) toolsGrid.classList.add('hidden');
if (noResults) noResults.classList.remove('hidden');
} else {
if (noResults) noResults.classList.add('hidden');
if (toolsGrid) toolsGrid.classList.remove('hidden');
renderTools(filtered);
}
}
// Update results description text
function updateResultsDescription() {
if (!resultsDescription) return;
const parts = [];
if (currentFilters.domain) parts.push(`Bereich: ${currentFilters.domain}`);
if (currentFilters.phase) parts.push(`Phase: ${currentFilters.phase}`);
if (currentFilters.search) parts.push(`Suche: "${currentFilters.search}"`);
resultsDescription.textContent = parts.join(' • ');
}
// Render tools in the grid
function renderTools(tools) {
if (!toolsGrid) return;
toolsGrid.innerHTML = tools.map(tool => createToolCard(tool)).join('');
// Add click handlers for tool cards
const toolCards = toolsGrid.querySelectorAll('.tool-card');
toolCards.forEach((card, index) => {
card.addEventListener('click', () => showToolModal(tools[index]));
});
}
// Create HTML for a tool card
function createToolCard(tool) {
const typeLabel = tool.type === 'SaaS' ?
'<span class="tag tag-purple">SaaS</span>' : '';
const tags = (tool.tags || []).slice(0, 3).map(tag =>
`<span class="tag tag-blue">${tag}</span>`
).join('');
const platforms = (tool.platforms || []).join(', ');
return `
<div class="tool-card" data-tool-id="${tool.id}">
<div class="flex items-start justify-between mb-2">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 text-lg">
${tool.name}
</h3>
<div class="flex items-center gap-2">
${typeLabel}
<svg class="w-4 h-4 text-gray-400 hover:text-blue-600 dark:text-gray-500 dark:hover:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</div>
</div>
<p class="text-gray-600 dark:text-gray-300 text-sm mb-3">${tool.description}</p>
<div class="flex flex-wrap gap-1 mb-2">
${tags}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
${tool.skillLevel}${tool.accessType}${platforms}
</div>
</div>
`;
}
// Populate matrix view
function populateMatrix() {
if (currentFilters.mode !== 'matrix') return;
domains.forEach(domain => {
phases.forEach(phase => {
const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
if (!cell) return;
const cellTools = allTools.filter(tool =>
tool.type === 'FOSS' &&
tool.domains && tool.domains.includes(domain) &&
tool.phases && tool.phases.includes(phase) &&
(!currentFilters.search ||
tool.name.toLowerCase().includes(currentFilters.search.toLowerCase()) ||
tool.description.toLowerCase().includes(currentFilters.search.toLowerCase()) ||
(tool.tags && tool.tags.some(tag => tag.toLowerCase().includes(currentFilters.search.toLowerCase()))))
);
if (cellTools.length === 0) {
cell.innerHTML = '<div class="text-gray-400 dark:text-gray-500 text-sm text-center py-2">-</div>';
} else {
cell.innerHTML = cellTools.map(tool =>
`<div class="matrix-cell-tool" data-tool-id="${tool.id}">${tool.name}</div>`
).join('');
// Add click handlers
const toolElements = cell.querySelectorAll('.matrix-cell-tool');
toolElements.forEach((element, index) => {
element.addEventListener('click', () => showToolModal(cellTools[index]));
});
}
});
});
}
// Populate SaaS grid
function populateSaasGrid() {
if (currentFilters.mode !== 'saas') return;
const saasGrid = document.getElementById('saas-grid');
const saasCount = document.getElementById('saas-count');
const noSaasResults = document.getElementById('no-saas-results');
if (!saasGrid) return;
const saasTools = filterTools();
if (saasCount) saasCount.textContent = saasTools.length;
if (saasTools.length === 0) {
saasGrid.classList.add('hidden');
if (noSaasResults) noSaasResults.classList.remove('hidden');
} else {
if (noSaasResults) noSaasResults.classList.add('hidden');
saasGrid.classList.remove('hidden');
saasGrid.innerHTML = saasTools.map(tool => createToolCard(tool)).join('');
// Add click handlers
const toolCards = saasGrid.querySelectorAll('.tool-card');
toolCards.forEach((card, index) => {
card.addEventListener('click', () => showToolModal(saasTools[index]));
});
}
}
// Show tool modal (defined in modal.js)
function showToolModal(tool) {
if (typeof window.showToolModal === 'function') {
window.showToolModal(tool);
}
}
// Export for use by other scripts
window.searchModule = {
init,
filterTools,
updateDisplay,
populateMatrix,
populateSaasGrid,
resetFilters
};
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();