Add article detail view to documentation page
- Add state to track selected article (sectionId + articleId) - Implement renderArticleContent() function to display full article content - Display intro, steps, tips, and conclusion sections from translations - Add click handlers to article cards to show detail view - Add back button to return to article list - Reset selected article when switching sections in sidebar Fixes issue where clicking on article cards only showed metadata instead of full content with steps, tips, and detailed information.
This commit is contained in:
@@ -41,6 +41,7 @@ interface DocArticle {
|
||||
const DocumentationPage: React.FC = () => {
|
||||
const { t } = useTranslation('help');
|
||||
const [activeSection, setActiveSection] = useState<string>('getting-started');
|
||||
const [selectedArticle, setSelectedArticle] = useState<{ sectionId: string; articleId: string } | null>(null);
|
||||
|
||||
const sections: DocSection[] = [
|
||||
{
|
||||
@@ -287,6 +288,160 @@ const DocumentationPage: React.FC = () => {
|
||||
return t(`difficulty.${difficulty}` as any) || difficulty;
|
||||
};
|
||||
|
||||
const getCategoryKey = (sectionId: string): string => {
|
||||
const categoryMap: Record<string, string> = {
|
||||
'getting-started': 'gettingStarted',
|
||||
'features': 'features',
|
||||
'analytics': 'analytics',
|
||||
'account': 'account',
|
||||
'billing': 'billing',
|
||||
'privacy': 'privacy',
|
||||
};
|
||||
return categoryMap[sectionId] || sectionId;
|
||||
};
|
||||
|
||||
const getArticleKey = (articleId: string): string => {
|
||||
const articleMap: Record<string, string> = {
|
||||
'quick-start': 'quickStart',
|
||||
'import-data': 'importData',
|
||||
'products-catalog': 'productsCatalog',
|
||||
'first-prediction': 'firstPrediction',
|
||||
'demand-forecasting': 'demandForecasting',
|
||||
'production-planning': 'productionPlanning',
|
||||
'inventory-management': 'inventoryManagement',
|
||||
'pos-integration': 'posIntegration',
|
||||
'waste-tracking': 'wasteTracking',
|
||||
'dashboard-overview': 'dashboardOverview',
|
||||
'reports': 'reports',
|
||||
'ai-insights': 'aiInsights',
|
||||
'performance-metrics': 'performanceMetrics',
|
||||
};
|
||||
return articleMap[articleId] || articleId;
|
||||
};
|
||||
|
||||
const handleArticleClick = (sectionId: string, articleId: string) => {
|
||||
setSelectedArticle({ sectionId, articleId });
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const handleBackToList = () => {
|
||||
setSelectedArticle(null);
|
||||
};
|
||||
|
||||
const renderArticleContent = () => {
|
||||
if (!selectedArticle) return null;
|
||||
|
||||
const categoryKey = getCategoryKey(selectedArticle.sectionId);
|
||||
const articleKey = getArticleKey(selectedArticle.articleId);
|
||||
const contentPath = `articles.${categoryKey}.${articleKey}.content`;
|
||||
|
||||
const content: any = t(contentPath, { returnObjects: true });
|
||||
const article = sections
|
||||
.find((s) => s.id === selectedArticle.sectionId)
|
||||
?.articles.find((a) => a.id === selectedArticle.articleId);
|
||||
|
||||
if (!article || !content) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={handleBackToList}
|
||||
className="mb-6 flex items-center gap-2 text-[var(--color-primary)] hover:underline"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5 transform rotate-180" />
|
||||
<span>{t('docs.backToHelp')}</span>
|
||||
</button>
|
||||
|
||||
{/* Article Header */}
|
||||
<div className="mb-8 pb-8 border-b border-[var(--border-primary)]">
|
||||
<h1 className="text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
{article.title}
|
||||
</h1>
|
||||
<p className="text-xl text-[var(--text-secondary)] mb-4">{article.description}</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="flex items-center gap-1 text-[var(--text-tertiary)]">
|
||||
<PlayCircle className="w-4 h-4" />
|
||||
{article.readTime}
|
||||
</span>
|
||||
<span className={`font-medium ${getDifficultyColor(article.difficulty)}`}>
|
||||
{getDifficultyLabel(article.difficulty)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Article Content */}
|
||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||
{/* Intro */}
|
||||
{content.intro && (
|
||||
<div className="bg-[var(--color-primary)]/5 border-l-4 border-[var(--color-primary)] p-6 rounded-r-xl mb-8">
|
||||
<p className="text-[var(--text-primary)] text-lg leading-relaxed m-0">
|
||||
{content.intro}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Steps */}
|
||||
{content.steps && Array.isArray(content.steps) && (
|
||||
<div className="space-y-6 mb-8">
|
||||
{content.steps.map((step: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-[var(--bg-secondary)] rounded-xl p-6 border border-[var(--border-primary)]"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3 mt-0">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] leading-relaxed m-0">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tips */}
|
||||
{content.tips && Array.isArray(content.tips) && content.tips.length > 0 && (
|
||||
<div className="bg-amber-50 dark:bg-amber-900/20 border-2 border-amber-200 dark:border-amber-800 rounded-xl p-6 mb-8">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4 mt-0 flex items-center gap-2">
|
||||
<Info className="w-6 h-6 text-amber-600" />
|
||||
<span>Consejos Útiles</span>
|
||||
</h3>
|
||||
<ul className="space-y-2 m-0 pl-0 list-none">
|
||||
{content.tips.map((tip: string, index: number) => (
|
||||
<li key={index} className="flex items-start gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[var(--text-secondary)]">{tip}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Conclusion */}
|
||||
{content.conclusion && (
|
||||
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-600 p-6 rounded-r-xl">
|
||||
<p className="text-[var(--text-primary)] leading-relaxed m-0">
|
||||
{content.conclusion}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Back to List Button */}
|
||||
<div className="mt-12 pt-8 border-t border-[var(--border-primary)]">
|
||||
<button
|
||||
onClick={handleBackToList}
|
||||
className="flex items-center gap-2 px-6 py-3 bg-[var(--color-primary)] text-white rounded-xl font-bold hover:shadow-xl transition-all"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5 transform rotate-180" />
|
||||
<span>Volver a la Lista</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="default"
|
||||
@@ -349,7 +504,10 @@ const DocumentationPage: React.FC = () => {
|
||||
{sections.map((section) => (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => setActiveSection(section.id)}
|
||||
onClick={() => {
|
||||
setActiveSection(section.id);
|
||||
setSelectedArticle(null);
|
||||
}}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left transition-all ${
|
||||
activeSection === section.id
|
||||
? 'bg-[var(--color-primary)] text-white'
|
||||
@@ -405,53 +563,57 @@ const DocumentationPage: React.FC = () => {
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="lg:col-span-3">
|
||||
{activeContent && (
|
||||
<div>
|
||||
{/* Section Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center">
|
||||
<activeContent.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-extrabold text-[var(--text-primary)]">
|
||||
{activeContent.title}
|
||||
</h2>
|
||||
<p className="text-[var(--text-secondary)]">{activeContent.description}</p>
|
||||
{selectedArticle ? (
|
||||
renderArticleContent()
|
||||
) : (
|
||||
activeContent && (
|
||||
<div>
|
||||
{/* Section Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center">
|
||||
<activeContent.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-extrabold text-[var(--text-primary)]">
|
||||
{activeContent.title}
|
||||
</h2>
|
||||
<p className="text-[var(--text-secondary)]">{activeContent.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Articles Grid */}
|
||||
<div className="space-y-4">
|
||||
{activeContent.articles.map((article) => (
|
||||
<a
|
||||
key={article.id}
|
||||
href={`#${article.id}`}
|
||||
className="block bg-[var(--bg-secondary)] rounded-xl p-6 border border-[var(--border-primary)] hover:border-[var(--color-primary)] hover:shadow-xl transition-all group"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2 group-hover:text-[var(--color-primary)] transition-colors">
|
||||
{article.title}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] mb-4">{article.description}</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="flex items-center gap-1 text-[var(--text-tertiary)]">
|
||||
<PlayCircle className="w-4 h-4" />
|
||||
{article.readTime}
|
||||
</span>
|
||||
<span className={`font-medium ${getDifficultyColor(article.difficulty)}`}>
|
||||
{getDifficultyLabel(article.difficulty)}
|
||||
</span>
|
||||
{/* Articles Grid */}
|
||||
<div className="space-y-4">
|
||||
{activeContent.articles.map((article) => (
|
||||
<button
|
||||
key={article.id}
|
||||
onClick={() => handleArticleClick(activeSection, article.id)}
|
||||
className="w-full block bg-[var(--bg-secondary)] rounded-xl p-6 border border-[var(--border-primary)] hover:border-[var(--color-primary)] hover:shadow-xl transition-all group text-left"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2 group-hover:text-[var(--color-primary)] transition-colors">
|
||||
{article.title}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] mb-4">{article.description}</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="flex items-center gap-1 text-[var(--text-tertiary)]">
|
||||
<PlayCircle className="w-4 h-4" />
|
||||
{article.readTime}
|
||||
</span>
|
||||
<span className={`font-medium ${getDifficultyColor(article.difficulty)}`}>
|
||||
{getDifficultyLabel(article.difficulty)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="w-6 h-6 text-[var(--text-tertiary)] group-hover:text-[var(--color-primary)] transition-colors flex-shrink-0" />
|
||||
</div>
|
||||
<ChevronRight className="w-6 h-6 text-[var(--text-tertiary)] group-hover:text-[var(--color-primary)] transition-colors flex-shrink-0" />
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user