diff --git a/frontend/src/components/FilePreview.js b/frontend/src/components/FilePreview.js index 8ef91e7..a8ea33e 100644 --- a/frontend/src/components/FilePreview.js +++ b/frontend/src/components/FilePreview.js @@ -155,7 +155,7 @@ export default function FilePreview({ file, zoom = 1, darkMode = false }) { }; return ( -
+
{renderFileContent()} {/* Badge für jede einzelne Datei */} {(file.width || file.height || file.size || file.duration) && ( diff --git a/frontend/src/pages/ProjectDetail.js b/frontend/src/pages/ProjectDetail.js index 42faa7b..50f944a 100644 --- a/frontend/src/pages/ProjectDetail.js +++ b/frontend/src/pages/ProjectDetail.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { Tabs, Button, Spin, Skeleton, Result, Grid, Select, Layout } from 'antd'; -import { ArrowLeftOutlined, LoadingOutlined, LockOutlined, MinusOutlined, PlusOutlined, EllipsisOutlined } from '@ant-design/icons'; +import { Tabs, Button, Spin, Skeleton, Result, Grid, Select, Layout, Tooltip, message } from 'antd'; +import { ArrowLeftOutlined, LoadingOutlined, LockOutlined, MinusOutlined, PlusOutlined, EllipsisOutlined, ShareAltOutlined } from '@ant-design/icons'; import UserMenu from '../components/UserMenu'; import FilePreview, { formatFileSize, formatDuration } from '../components/FilePreview'; import debugLogger from '../utils/debugLogger'; @@ -9,6 +9,18 @@ import debugLogger from '../utils/debugLogger'; const backendUrl = process.env.REACT_APP_BACKEND || ''; const { useBreakpoint } = Grid; +// Hash-Funktion für Ad-IDs +function generateAdId(adName, subcategoryName, categoryName) { + const combined = `${categoryName}-${subcategoryName}-${adName}`; + let hash = 0; + for (let i = 0; i < combined.length; i++) { + const char = combined.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // 32-bit integer + } + return Math.abs(hash).toString(36); // Base36 für kürzere IDs +} + function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overrideParams, ...props }) { // URL-Parameter holen, mit Override-Support für Smart Resolution const routeParams = useParams(); @@ -51,6 +63,28 @@ function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overridePar }); } + // Link kopieren Funktionalität + const handleCopyLink = async (adId, adName) => { + try { + // Entferne bestehende Sprungmarke aus der URL + const currentUrl = window.location.href; + const baseUrl = currentUrl.split('#')[0]; // Alles vor dem ersten # + const linkWithAnchor = `${baseUrl}#${adId}`; + await navigator.clipboard.writeText(linkWithAnchor); + message.success(`Link zu "${adName}" wurde kopiert!`); + } catch (err) { + // Fallback für ältere Browser + const textArea = document.createElement('textarea'); + const baseUrl = window.location.href.split('#')[0]; + textArea.value = `${baseUrl}#${adId}`; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + message.success(`Link zu "${adName}" wurde kopiert!`); + } + }; + useEffect(() => { async function fetchLogo() { if (!client || !project) { @@ -105,9 +139,21 @@ function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overridePar

{sub.name}

{/* Ads unterhalb der Subcategory anzeigen */} {Array.isArray(sub.children) && sub.children.filter(ad => ad.type === 'ad').length > 0 ? ( - sub.children.filter(ad => ad.type === 'ad').map(ad => ( -
-
{ad.name}
+ sub.children.filter(ad => ad.type === 'ad').map(ad => { + const adId = generateAdId(ad.name, sub.name, cat.title); + return ( +
+
+ {ad.name} + +
{/* Dateien unterhalb des Ads anzeigen */} {Array.isArray(ad.files) && ad.files.length > 0 ? (
@@ -119,7 +165,8 @@ function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overridePar
Keine Dateien vorhanden.
)}
- )) + ); + }) ) : (
Keine Ads vorhanden.
)} @@ -232,6 +279,32 @@ function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overridePar fetchTabs(); }, [user, client, project, tab]); + // Scroll zu Sprungmarke nach dem Laden + useEffect(() => { + if (!loading && tabs.length > 0) { + const hash = window.location.hash.substring(1); + if (hash) { + // Kurz warten bis die Inhalte gerendert sind + setTimeout(() => { + const element = document.getElementById(hash); + if (element) { + // Scroll mit Offset, um Navigation zu berücksichtigen + const elementTop = element.offsetTop - 20; + window.scrollTo({ + top: elementTop, + behavior: 'smooth' + }); + // Kurz hervorheben + element.style.backgroundColor = '#edff5f'; + setTimeout(() => { + element.style.backgroundColor = ''; + }, 2000); + } + }, 300); + } + } + }, [loading, tabs, activeKey]); + // Tab-Wechsel: Schreibe Tab in die URL const handleTabChange = (key) => { setActiveKey(key);