mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 00:39:41 +02:00
Integrated techbase (software comparison platform) into monorepo structure: - Created NestJS backend with votes and comments modules - Migrated from external Supabase to own PostgreSQL - Set up Drizzle ORM schema for votes and comments - Created API client replacing Supabase in Astro frontend - Added environment configuration (port 3021) Archived immediately as it's not yet ready for active development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
775 lines
No EOL
33 KiB
Text
775 lines
No EOL
33 KiB
Text
---
|
|
import { loadTranslations } from '../../utils/i18n';
|
|
import SoftwareRatings from './SoftwareRatings.astro';
|
|
import CommentSystem from '../CommentSystem.astro';
|
|
|
|
const { locale } = Astro.props;
|
|
const t = await loadTranslations(locale);
|
|
---
|
|
|
|
<div
|
|
class="bg-white dark:bg-gray-800 rounded-lg shadow-sm overflow-hidden"
|
|
>
|
|
<!-- Header mit Software-Info und Steuerungselementen -->
|
|
<div class="flex flex-col md:flex-row md:items-center mb-8 p-4 border-b border-gray-200 dark:border-gray-700">
|
|
<!-- Minimieren-Button -->
|
|
<button
|
|
id="minimizeCompareBtn"
|
|
class="mr-4 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
aria-label="Minimize View"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Software-Info -->
|
|
<div class="flex items-center flex-1">
|
|
<div class="w-24 h-24 bg-white p-2 rounded-lg shadow-sm flex items-center justify-center mr-6 mb-4 md:mb-0">
|
|
<img id="compare-software-logo" src="/logos/sample-logo.svg" alt="Software logo" class="max-w-full max-h-full" />
|
|
</div>
|
|
<div>
|
|
<h2 id="compare-software-name" class="text-3xl font-bold">Select Software</h2>
|
|
<div id="compare-software-categories" class="flex flex-wrap text-sm text-gray-500 mt-2"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aktionsbuttons -->
|
|
<div class="md:ml-auto mt-4 md:mt-0 flex space-x-2">
|
|
<button
|
|
id="viewDemoBtn"
|
|
class="btn btn-primary"
|
|
>
|
|
Visit Website
|
|
</button>
|
|
|
|
<button
|
|
id="backToSoftwareListBtn"
|
|
class="btn btn-secondary whitespace-nowrap"
|
|
>
|
|
Select Another
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Software-Liste (angezeigt, wenn noch keine Software ausgewählt ist) -->
|
|
<div
|
|
id="software-list-view"
|
|
class="p-0 overflow-y-auto"
|
|
style="max-height: calc(100vh - 200px);">
|
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-lg font-bold">Select Another Software</h3>
|
|
<div class="mt-4">
|
|
<input
|
|
type="text"
|
|
id="software-search-input"
|
|
placeholder="Search"
|
|
class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-primary focus:border-primary dark:bg-gray-700 dark:text-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-0 overflow-y-auto" id="software-list-container">
|
|
<div id="loading-indicator" class="py-8 text-center text-gray-500 dark:text-gray-400" style="display: none;">
|
|
<svg class="animate-spin h-8 w-8 mx-auto mb-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Loading...
|
|
</div>
|
|
|
|
<div id="error-message" class="py-8 text-center text-red-500" style="display: none;">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
<p id="error-text">An error occurred</p>
|
|
</div>
|
|
|
|
<div id="empty-message" class="py-8 text-center text-gray-500" style="display: none;">
|
|
No similar software available
|
|
</div>
|
|
|
|
<div id="software-items">
|
|
<!-- Software-Einträge werden hier per JavaScript eingefügt -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detaillierte Software-Vergleichsansicht (wenn Software ausgewählt wurde) -->
|
|
<div class="hidden" id="software-detail-view">
|
|
<div id="detail-loading" class="text-center text-gray-500 dark:text-gray-400 p-6">
|
|
<svg class="animate-spin h-8 w-8 mx-auto mb-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Loading software details...
|
|
</div>
|
|
|
|
<div id="detail-content" style="display: none;" class="software-content px-4">
|
|
<!-- Hauptinhaltsbereich mit einspaltigem Layout ohne Grid -->
|
|
<div class="grid grid-cols-1 gap-8">
|
|
<!-- Software-Übersichtskomponente -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6 mb-8 h-[500px] overflow-y-auto">
|
|
<h2 class="text-2xl font-bold mb-4">Overview</h2>
|
|
<p id="software-description" class="text-gray-700 dark:text-gray-300 mb-6"></p>
|
|
|
|
<!-- Screenshots-Container (wird dynamisch befüllt) -->
|
|
<div id="compare-screenshots" class="mb-6" style="display: none;">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<!-- Hier werden Screenshots per JS eingefügt -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Features -->
|
|
<h3 class="text-xl font-bold mb-4">Features</h3>
|
|
<ul id="software-features" class="list-disc list-inside mb-6 text-gray-700 dark:text-gray-300"></ul>
|
|
|
|
<!-- Plattformen -->
|
|
<div id="platforms-section">
|
|
<h3 class="text-xl font-bold mb-4">Platforms</h3>
|
|
<div id="software-platforms" class="flex flex-wrap gap-2 mb-6"></div>
|
|
</div>
|
|
|
|
<!-- Last Updated -->
|
|
<div id="last-updated" class="text-sm text-gray-500 dark:text-gray-400">
|
|
Last Updated: <span id="update-date"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Preisgestaltungsbereich (jetzt an zweiter Stelle wie auf der linken Seite) -->
|
|
<div id="pricing-section" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6 mb-8 h-[400px] overflow-y-auto">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold">Pricing</h2>
|
|
|
|
<div class="flex items-center bg-gray-100 dark:bg-gray-700 rounded-full p-1">
|
|
<button
|
|
id="compare-monthly-btn"
|
|
class="px-3 py-1 rounded-full bg-primary text-white text-sm font-medium"
|
|
>
|
|
Monthly
|
|
</button>
|
|
<button
|
|
id="compare-yearly-btn"
|
|
class="px-3 py-1 rounded-full text-sm font-medium"
|
|
>
|
|
Yearly
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="software-pricing" class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<!-- Pricing Info wird hier per JavaScript eingefügt -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kommentar-Bereich (jetzt an dritter Stelle wie auf der linken Seite) -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6 mb-8 h-[350px] overflow-y-auto">
|
|
<h2 class="text-2xl font-bold mb-4">Comments</h2>
|
|
<div id="compare-comments">
|
|
<p class="text-gray-500">Comments will be loaded when comparing specific software.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Software-Bewertungskomponente (jetzt an vierter Stelle wie auf der linken Seite) -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6 mb-8 h-[300px] overflow-y-auto">
|
|
<h3 class="text-xl font-bold mb-4">Ratings</h3>
|
|
<div id="compare-ratings" class="space-y-4">
|
|
<!-- Ease of Use -->
|
|
<div>
|
|
<div class="flex justify-between items-center mb-1">
|
|
<span class="font-medium">Ease of Use</span>
|
|
<span id="compare-ease-value" class="font-bold">0.0 / 5</span>
|
|
</div>
|
|
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div id="compare-ease-bar" class="h-full bg-primary" style="width: 0%"></div>
|
|
</div>
|
|
<div id="compare-ease-count" class="text-xs text-gray-500 dark:text-gray-400 text-right mt-1">0 votes</div>
|
|
</div>
|
|
|
|
<!-- Features -->
|
|
<div>
|
|
<div class="flex justify-between items-center mb-1">
|
|
<span class="font-medium">Features</span>
|
|
<span id="compare-features-value" class="font-bold">0.0 / 5</span>
|
|
</div>
|
|
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div id="compare-features-bar" class="h-full bg-primary" style="width: 0%"></div>
|
|
</div>
|
|
<div id="compare-features-count" class="text-xs text-gray-500 dark:text-gray-400 text-right mt-1">0 votes</div>
|
|
</div>
|
|
|
|
<!-- Value -->
|
|
<div>
|
|
<div class="flex justify-between items-center mb-1">
|
|
<span class="font-medium">Value for Money</span>
|
|
<span id="compare-value-value" class="font-bold">0.0 / 5</span>
|
|
</div>
|
|
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div id="compare-value-bar" class="h-full bg-primary" style="width: 0%"></div>
|
|
</div>
|
|
<div id="compare-value-count" class="text-xs text-gray-500 dark:text-gray-400 text-right mt-1">0 votes</div>
|
|
</div>
|
|
|
|
<!-- Support -->
|
|
<div>
|
|
<div class="flex justify-between items-center mb-1">
|
|
<span class="font-medium">Support</span>
|
|
<span id="compare-support-value" class="font-bold">0.0 / 5</span>
|
|
</div>
|
|
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div id="compare-support-bar" class="h-full bg-primary" style="width: 0%"></div>
|
|
</div>
|
|
<div id="compare-support-count" class="text-xs text-gray-500 dark:text-gray-400 text-right mt-1">0 votes</div>
|
|
</div>
|
|
|
|
<!-- Reliability -->
|
|
<div>
|
|
<div class="flex justify-between items-center mb-1">
|
|
<span class="font-medium">Reliability</span>
|
|
<span id="compare-reliability-value" class="font-bold">0.0 / 5</span>
|
|
</div>
|
|
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div id="compare-reliability-bar" class="h-full bg-primary" style="width: 0%"></div>
|
|
</div>
|
|
<div id="compare-reliability-count" class="text-xs text-gray-500 dark:text-gray-400 text-right mt-1">0 votes</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Minimierte Ansicht -->
|
|
<div id="minimized-view" class="p-4 flex flex-col items-center justify-center border-l border-gray-200 dark:border-gray-700 h-full hidden">
|
|
<div class="w-16 h-16 bg-white p-2 rounded-lg shadow-sm flex items-center justify-center mb-2">
|
|
<img id="minimized-logo" src="/logos/sample-logo.svg" alt="Software logo" class="max-w-full max-h-full" />
|
|
</div>
|
|
<h2 id="minimized-name" class="text-center text-lg font-bold mb-2">Software Name</h2>
|
|
<p id="minimized-description" class="text-center text-sm text-gray-500 dark:text-gray-400 mb-4 line-clamp-3">Description will appear here</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Client-side Funktionalität für die Vergleichsansicht
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// DOM-Elemente
|
|
const softwareListView = document.getElementById('software-list-view');
|
|
const softwareDetailView = document.getElementById('software-detail-view');
|
|
const softwareItems = document.getElementById('software-items');
|
|
const searchInput = document.getElementById('software-search-input');
|
|
const loadingIndicator = document.getElementById('loading-indicator');
|
|
const errorMessage = document.getElementById('error-message');
|
|
const errorText = document.getElementById('error-text');
|
|
const emptyMessage = document.getElementById('empty-message');
|
|
const backButton = document.getElementById('backToSoftwareListBtn');
|
|
const minimizeButton = document.getElementById('minimizeCompareBtn');
|
|
const minimizedView = document.getElementById('minimized-view');
|
|
|
|
// Detail-Elemente
|
|
const softwareLogo = document.getElementById('compare-software-logo');
|
|
const softwareName = document.getElementById('compare-software-name');
|
|
const softwareCategories = document.getElementById('compare-software-categories');
|
|
const softwareDescription = document.getElementById('software-description');
|
|
const softwareFeatures = document.getElementById('software-features');
|
|
const softwarePlatforms = document.getElementById('software-platforms');
|
|
const softwarePricing = document.getElementById('software-pricing');
|
|
const platformsSection = document.getElementById('platforms-section');
|
|
const pricingSection = document.getElementById('pricing-section');
|
|
const detailLoading = document.getElementById('detail-loading');
|
|
const detailContent = document.getElementById('detail-content');
|
|
|
|
// Bewertungselemente
|
|
const compareEaseValue = document.getElementById('compare-ease-value');
|
|
const compareFeaturesValue = document.getElementById('compare-features-value');
|
|
const compareValueValue = document.getElementById('compare-value-value');
|
|
const compareSupportValue = document.getElementById('compare-support-value');
|
|
const compareReliabilityValue = document.getElementById('compare-reliability-value');
|
|
|
|
// Minimized-Elemente
|
|
const minimizedLogo = document.getElementById('minimized-logo');
|
|
const minimizedName = document.getElementById('minimized-name');
|
|
const minimizedDescription = document.getElementById('minimized-description');
|
|
|
|
let isMinimized = false;
|
|
let similarSoftware = [];
|
|
let filteredSoftware = [];
|
|
let currentSoftware = null;
|
|
let currentLocale = 'en';
|
|
|
|
// Initialisiere mit dem Data-Attribut des Containers
|
|
const initializeFromContainer = () => {
|
|
// Container-Element für die Daten
|
|
const softwareDetail = document.getElementById('softwareDetail');
|
|
|
|
if (softwareDetail) {
|
|
try {
|
|
console.log('Container data:', softwareDetail.dataset);
|
|
|
|
let similarData = [];
|
|
if (softwareDetail.dataset.similarSoftware) {
|
|
similarData = JSON.parse(softwareDetail.dataset.similarSoftware);
|
|
console.log('Parsed similar software data:', similarData);
|
|
} else {
|
|
console.warn('No similar software data found in container');
|
|
}
|
|
|
|
// Ähnliche Software speichern
|
|
similarSoftware = similarData;
|
|
filteredSoftware = similarData;
|
|
|
|
// Locale erhalten
|
|
currentLocale = document.documentElement.lang || 'en';
|
|
|
|
// Initialisiere die Software-Liste
|
|
renderSoftwareList(similarSoftware);
|
|
|
|
// Zeige die Anzahl der geladenen Einträge
|
|
console.log(`Loaded ${similarSoftware.length} similar software entries`);
|
|
} catch (e) {
|
|
console.error('Error initializing compare panel:', e);
|
|
console.error(e);
|
|
}
|
|
} else {
|
|
console.error('Software detail container not found');
|
|
}
|
|
};
|
|
|
|
// Rendere die Software-Liste
|
|
const renderSoftwareList = (softwareList) => {
|
|
// Liste leeren
|
|
softwareItems.innerHTML = '';
|
|
|
|
if (softwareList.length === 0) {
|
|
emptyMessage.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
emptyMessage.style.display = 'none';
|
|
|
|
// Für jede Software einen Eintrag erstellen
|
|
softwareList.forEach(software => {
|
|
const item = document.createElement('div');
|
|
item.className = 'p-4 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer';
|
|
|
|
item.innerHTML = `
|
|
<div class="flex items-center">
|
|
<div class="w-10 h-10 rounded-lg bg-gray-100 dark:bg-gray-700 flex items-center justify-center mr-3">
|
|
${software.logo
|
|
? `<img src="${software.logo}" alt="${software.name}" class="max-w-full max-h-full p-1" />`
|
|
: `<span class="text-lg">${software.name.charAt(0)}</span>`
|
|
}
|
|
</div>
|
|
<div>
|
|
<div class="font-medium dark:text-white">${software.name}</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">${software.categories.join(', ')}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Click-Event zum Auswählen der Software
|
|
item.addEventListener('click', () => selectComparisonSoftware(software.id));
|
|
|
|
softwareItems.appendChild(item);
|
|
});
|
|
};
|
|
|
|
// Software zum Vergleich auswählen
|
|
const selectComparisonSoftware = async (id) => {
|
|
try {
|
|
// Lade-Zustand anzeigen
|
|
softwareListView.style.display = 'none';
|
|
softwareDetailView.style.display = 'block';
|
|
detailLoading.style.display = 'block';
|
|
detailContent.style.display = 'none';
|
|
|
|
// URL-Parameter aktualisieren
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('compare', id);
|
|
window.history.replaceState({}, '', url.toString());
|
|
|
|
// Software-Daten laden
|
|
const softwareData = await loadSoftwareData(id, currentLocale);
|
|
|
|
if (!softwareData) {
|
|
throw new Error('Failed to load software data');
|
|
}
|
|
|
|
// Aktuelle Software speichern
|
|
currentSoftware = softwareData;
|
|
|
|
// UI aktualisieren
|
|
updateSoftwareUI(softwareData);
|
|
|
|
// Informiere die SoftwareDetail-Komponente über die ausgewählte Software
|
|
const detailPanel = document.querySelector('.left-panel');
|
|
if (detailPanel) {
|
|
// Für die Einzelansicht in mobilen Geräten
|
|
detailPanel.classList.add('lg:w-1/2');
|
|
detailPanel.classList.remove('w-full');
|
|
}
|
|
} catch (e) {
|
|
console.error('Error selecting software for comparison:', e);
|
|
detailLoading.style.display = 'none';
|
|
errorText.textContent = e.message || 'Failed to load software data';
|
|
errorMessage.style.display = 'block';
|
|
}
|
|
};
|
|
|
|
// Software-Daten über API laden
|
|
const loadSoftwareData = async (id, locale) => {
|
|
try {
|
|
const response = await fetch(`/api/software/${id}.json?lang=${locale}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Error status: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (e) {
|
|
console.error('Error loading software data:', e);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// UI mit Software-Daten aktualisieren
|
|
const updateSoftwareUI = (software) => {
|
|
// Header-Informationen
|
|
softwareLogo.src = software.logo || '/logos/sample-logo.svg';
|
|
softwareLogo.alt = software.name;
|
|
softwareName.textContent = software.name;
|
|
|
|
// Kategorie-Tags erstellen
|
|
softwareCategories.innerHTML = '';
|
|
if (software.categories && software.categories.length > 0) {
|
|
software.categories.forEach((category, index) => {
|
|
const a = document.createElement('a');
|
|
a.href = `/${currentLocale}/category/${category.toLowerCase().replace(' ', '-')}`;
|
|
a.className = 'text-primary hover:underline';
|
|
a.textContent = category;
|
|
softwareCategories.appendChild(a);
|
|
|
|
if (index < software.categories.length - 1) {
|
|
const separator = document.createElement('span');
|
|
separator.className = 'mx-2 text-gray-400';
|
|
separator.textContent = '•';
|
|
softwareCategories.appendChild(separator);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Website-Button aktivieren, wenn URL vorhanden
|
|
const viewDemoBtn = document.getElementById('viewDemoBtn');
|
|
if (viewDemoBtn) {
|
|
if (software.website) {
|
|
viewDemoBtn.disabled = false;
|
|
} else {
|
|
viewDemoBtn.disabled = true;
|
|
}
|
|
}
|
|
|
|
// Beschreibung
|
|
softwareDescription.textContent = software.description || 'No description available';
|
|
|
|
// Screenshots, falls vorhanden
|
|
const screenshotsContainer = document.getElementById('compare-screenshots');
|
|
const screenshotsGrid = screenshotsContainer.querySelector('.grid');
|
|
|
|
if (software.screenshots && software.screenshots.length > 0) {
|
|
screenshotsGrid.innerHTML = '';
|
|
screenshotsContainer.style.display = 'block';
|
|
|
|
software.screenshots.forEach(screenshot => {
|
|
const screenshotDiv = document.createElement('div');
|
|
screenshotDiv.className = 'aspect-video bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden';
|
|
|
|
const img = document.createElement('img');
|
|
img.src = screenshot;
|
|
img.alt = `${software.name} screenshot`;
|
|
img.className = 'w-full h-full object-cover';
|
|
|
|
screenshotDiv.appendChild(img);
|
|
screenshotsGrid.appendChild(screenshotDiv);
|
|
});
|
|
} else {
|
|
screenshotsContainer.style.display = 'none';
|
|
}
|
|
|
|
// Features
|
|
softwareFeatures.innerHTML = '';
|
|
if (software.features && software.features.length > 0) {
|
|
software.features.forEach(feature => {
|
|
const li = document.createElement('li');
|
|
li.className = 'mb-2 text-gray-700 dark:text-gray-300';
|
|
li.textContent = feature;
|
|
softwareFeatures.appendChild(li);
|
|
});
|
|
} else {
|
|
const li = document.createElement('li');
|
|
li.className = 'mb-2 text-gray-700 dark:text-gray-300';
|
|
li.textContent = 'No features listed';
|
|
softwareFeatures.appendChild(li);
|
|
}
|
|
|
|
// Plattformen
|
|
softwarePlatforms.innerHTML = '';
|
|
if (software.platforms && software.platforms.length > 0) {
|
|
platformsSection.style.display = 'block';
|
|
software.platforms.forEach(platform => {
|
|
const span = document.createElement('span');
|
|
span.className = 'px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-sm';
|
|
span.textContent = platform;
|
|
softwarePlatforms.appendChild(span);
|
|
});
|
|
} else {
|
|
platformsSection.style.display = 'none';
|
|
}
|
|
|
|
// Last Updated Datum
|
|
const updateDate = document.getElementById('update-date');
|
|
if (updateDate && software.lastUpdated) {
|
|
updateDate.textContent = new Date(software.lastUpdated).toLocaleDateString();
|
|
} else if (updateDate) {
|
|
updateDate.textContent = 'N/A';
|
|
}
|
|
|
|
// Preisgestaltung
|
|
softwarePricing.innerHTML = '';
|
|
if (software.pricing && software.pricing.length > 0) {
|
|
pricingSection.style.display = 'block';
|
|
|
|
// Preis-Schalter einrichten
|
|
const monthlyBtn = document.getElementById('compare-monthly-btn');
|
|
const yearlyBtn = document.getElementById('compare-yearly-btn');
|
|
|
|
if (monthlyBtn && yearlyBtn) {
|
|
// Standard ist monatlich
|
|
let showMonthly = true;
|
|
|
|
// Preise generieren
|
|
const generatePricing = () => {
|
|
softwarePricing.innerHTML = '';
|
|
|
|
software.pricing.forEach(plan => {
|
|
const planDiv = document.createElement('div');
|
|
planDiv.className = 'border border-gray-200 dark:border-gray-700 rounded-lg p-6';
|
|
|
|
const planTitle = document.createElement('h3');
|
|
planTitle.className = 'text-xl font-bold mb-2';
|
|
planTitle.textContent = plan.model;
|
|
|
|
// Preisanzeige-Container für monatlich/jährlich
|
|
const priceContainer = document.createElement('div');
|
|
priceContainer.className = 'mb-4';
|
|
|
|
const planPrice = document.createElement('p');
|
|
planPrice.className = 'text-2xl font-bold text-primary dark:text-blue-400';
|
|
planPrice.textContent = showMonthly ? plan.price : (plan.yearly_price || 'N/A');
|
|
|
|
priceContainer.appendChild(planPrice);
|
|
|
|
// Features
|
|
const featuresList = document.createElement('ul');
|
|
featuresList.className = 'space-y-2';
|
|
|
|
plan.features.forEach(feature => {
|
|
const li = document.createElement('li');
|
|
li.className = 'flex items-start';
|
|
li.innerHTML = `
|
|
<svg class="h-5 w-5 text-green-500 mr-2 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
<span class="dark:text-gray-300">${feature}</span>
|
|
`;
|
|
featuresList.appendChild(li);
|
|
});
|
|
|
|
planDiv.appendChild(planTitle);
|
|
planDiv.appendChild(priceContainer);
|
|
planDiv.appendChild(featuresList);
|
|
softwarePricing.appendChild(planDiv);
|
|
});
|
|
};
|
|
|
|
// Initial erzeugen
|
|
generatePricing();
|
|
|
|
// Event-Listener für Schalter
|
|
monthlyBtn.addEventListener('click', () => {
|
|
monthlyBtn.classList.add('bg-primary', 'text-white');
|
|
yearlyBtn.classList.remove('bg-primary', 'text-white');
|
|
showMonthly = true;
|
|
generatePricing();
|
|
});
|
|
|
|
yearlyBtn.addEventListener('click', () => {
|
|
yearlyBtn.classList.add('bg-primary', 'text-white');
|
|
monthlyBtn.classList.remove('bg-primary', 'text-white');
|
|
showMonthly = false;
|
|
generatePricing();
|
|
});
|
|
}
|
|
} else {
|
|
pricingSection.style.display = 'none';
|
|
}
|
|
|
|
// Bewertungen aktualisieren
|
|
if (software.metrics) {
|
|
updateRatings(software.metrics);
|
|
} else {
|
|
// Leere Bewertungen anzeigen, wenn keine vorhanden sind
|
|
updateRatings({
|
|
easeOfUse: { average: 0, count: 0 },
|
|
featureRichness: { average: 0, count: 0 },
|
|
valueForMoney: { average: 0, count: 0 },
|
|
support: { average: 0, count: 0 },
|
|
reliability: { average: 0, count: 0 }
|
|
});
|
|
}
|
|
|
|
// Minimierte Ansicht aktualisieren
|
|
minimizedLogo.src = software.logo || '/logos/sample-logo.svg';
|
|
minimizedLogo.alt = software.name;
|
|
minimizedName.textContent = software.name;
|
|
minimizedDescription.textContent = software.description || 'No description available';
|
|
|
|
// Lade-Zustand ausblenden, Inhalte einblenden
|
|
detailLoading.style.display = 'none';
|
|
detailContent.style.display = 'block';
|
|
};
|
|
|
|
// Bewertungen aktualisieren
|
|
const updateRatings = (metrics) => {
|
|
// Bewertungen abrufen
|
|
const easeOfUse = metrics.easeOfUse || { average: 0, count: 0 };
|
|
const featureRichness = metrics.featureRichness || { average: 0, count: 0 };
|
|
const valueForMoney = metrics.valueForMoney || { average: 0, count: 0 };
|
|
const support = metrics.support || { average: 0, count: 0 };
|
|
const reliability = metrics.reliability || { average: 0, count: 0 };
|
|
|
|
// Werte aktualisieren für Ease of Use
|
|
if (compareEaseValue) compareEaseValue.textContent = `${easeOfUse.average.toFixed(1)} / 5`;
|
|
const easeBar = document.getElementById('compare-ease-bar');
|
|
if (easeBar) easeBar.style.width = `${(easeOfUse.average / 5) * 100}%`;
|
|
const easeCount = document.getElementById('compare-ease-count');
|
|
if (easeCount) easeCount.textContent = `${easeOfUse.count} votes`;
|
|
|
|
// Werte aktualisieren für Features
|
|
if (compareFeaturesValue) compareFeaturesValue.textContent = `${featureRichness.average.toFixed(1)} / 5`;
|
|
const featuresBar = document.getElementById('compare-features-bar');
|
|
if (featuresBar) featuresBar.style.width = `${(featureRichness.average / 5) * 100}%`;
|
|
const featuresCount = document.getElementById('compare-features-count');
|
|
if (featuresCount) featuresCount.textContent = `${featureRichness.count} votes`;
|
|
|
|
// Werte aktualisieren für Value
|
|
if (compareValueValue) compareValueValue.textContent = `${valueForMoney.average.toFixed(1)} / 5`;
|
|
const valueBar = document.getElementById('compare-value-bar');
|
|
if (valueBar) valueBar.style.width = `${(valueForMoney.average / 5) * 100}%`;
|
|
const valueCount = document.getElementById('compare-value-count');
|
|
if (valueCount) valueCount.textContent = `${valueForMoney.count} votes`;
|
|
|
|
// Werte aktualisieren für Support
|
|
if (compareSupportValue) compareSupportValue.textContent = `${support.average.toFixed(1)} / 5`;
|
|
const supportBar = document.getElementById('compare-support-bar');
|
|
if (supportBar) supportBar.style.width = `${(support.average / 5) * 100}%`;
|
|
const supportCount = document.getElementById('compare-support-count');
|
|
if (supportCount) supportCount.textContent = `${support.count} votes`;
|
|
|
|
// Werte aktualisieren für Reliability
|
|
if (compareReliabilityValue) compareReliabilityValue.textContent = `${reliability.average.toFixed(1)} / 5`;
|
|
const reliabilityBar = document.getElementById('compare-reliability-bar');
|
|
if (reliabilityBar) reliabilityBar.style.width = `${(reliability.average / 5) * 100}%`;
|
|
const reliabilityCount = document.getElementById('compare-reliability-count');
|
|
if (reliabilityCount) reliabilityCount.textContent = `${reliability.count} votes`;
|
|
};
|
|
|
|
// Filter-Funktion für die Suche
|
|
const filterSoftware = (searchTerm) => {
|
|
if (!searchTerm || searchTerm.length < 2) {
|
|
filteredSoftware = similarSoftware;
|
|
renderSoftwareList(filteredSoftware);
|
|
return;
|
|
}
|
|
|
|
const term = searchTerm.toLowerCase();
|
|
filteredSoftware = similarSoftware.filter(software => {
|
|
return software.name.toLowerCase().includes(term) ||
|
|
software.description.toLowerCase().includes(term) ||
|
|
(software.categories && software.categories.some(cat => cat.toLowerCase().includes(term)));
|
|
});
|
|
|
|
renderSoftwareList(filteredSoftware);
|
|
};
|
|
|
|
// Minimierte Ansicht umschalten
|
|
const toggleMinimize = () => {
|
|
isMinimized = !isMinimized;
|
|
|
|
if (isMinimized) {
|
|
softwareDetailView.style.display = 'none';
|
|
minimizedView.style.display = 'flex';
|
|
} else {
|
|
softwareDetailView.style.display = 'block';
|
|
minimizedView.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
// Zurück zur Software-Liste
|
|
const backToList = () => {
|
|
softwareDetailView.style.display = 'none';
|
|
minimizedView.style.display = 'none';
|
|
softwareListView.style.display = 'block';
|
|
isMinimized = false;
|
|
};
|
|
|
|
// Event-Listener einrichten
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', () => filterSoftware(searchInput.value));
|
|
}
|
|
|
|
if (backButton) {
|
|
backButton.addEventListener('click', backToList);
|
|
}
|
|
|
|
if (minimizeButton) {
|
|
minimizeButton.addEventListener('click', toggleMinimize);
|
|
}
|
|
|
|
// URL-Parameter überprüfen
|
|
const checkURLParams = () => {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const compareId = urlParams.get('compare');
|
|
|
|
if (compareId) {
|
|
selectComparisonSoftware(compareId);
|
|
}
|
|
};
|
|
|
|
// Website-Button-Handler
|
|
const viewDemoBtn = document.getElementById('viewDemoBtn');
|
|
if (viewDemoBtn) {
|
|
viewDemoBtn.addEventListener('click', () => {
|
|
if (currentSoftware && currentSoftware.website) {
|
|
window.open(currentSoftware.website, '_blank');
|
|
}
|
|
});
|
|
|
|
// Initial deaktivieren, bis Software ausgewählt ist
|
|
viewDemoBtn.disabled = true;
|
|
}
|
|
|
|
// Initialisierung starten
|
|
initializeFromContainer();
|
|
checkURLParams();
|
|
|
|
// Prüfen, ob Daten vorhanden sind
|
|
if (similarSoftware.length === 0) {
|
|
// Verzögerung, um sicherzustellen, dass Daten verfügbar sind
|
|
setTimeout(() => {
|
|
initializeFromContainer();
|
|
|
|
if (similarSoftware.length === 0) {
|
|
emptyMessage.style.display = 'block';
|
|
}
|
|
}, 500);
|
|
}
|
|
});
|
|
</script> |