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 DocumentationPage: React.FC = () => {
|
||||||
const { t } = useTranslation('help');
|
const { t } = useTranslation('help');
|
||||||
const [activeSection, setActiveSection] = useState<string>('getting-started');
|
const [activeSection, setActiveSection] = useState<string>('getting-started');
|
||||||
|
const [selectedArticle, setSelectedArticle] = useState<{ sectionId: string; articleId: string } | null>(null);
|
||||||
|
|
||||||
const sections: DocSection[] = [
|
const sections: DocSection[] = [
|
||||||
{
|
{
|
||||||
@@ -287,6 +288,160 @@ const DocumentationPage: React.FC = () => {
|
|||||||
return t(`difficulty.${difficulty}` as any) || difficulty;
|
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 (
|
return (
|
||||||
<PublicLayout
|
<PublicLayout
|
||||||
variant="default"
|
variant="default"
|
||||||
@@ -349,7 +504,10 @@ const DocumentationPage: React.FC = () => {
|
|||||||
{sections.map((section) => (
|
{sections.map((section) => (
|
||||||
<button
|
<button
|
||||||
key={section.id}
|
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 ${
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left transition-all ${
|
||||||
activeSection === section.id
|
activeSection === section.id
|
||||||
? 'bg-[var(--color-primary)] text-white'
|
? 'bg-[var(--color-primary)] text-white'
|
||||||
@@ -405,53 +563,57 @@ const DocumentationPage: React.FC = () => {
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
{activeContent && (
|
{selectedArticle ? (
|
||||||
<div>
|
renderArticleContent()
|
||||||
{/* Section Header */}
|
) : (
|
||||||
<div className="mb-8">
|
activeContent && (
|
||||||
<div className="flex items-center gap-4 mb-4">
|
<div>
|
||||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center">
|
{/* Section Header */}
|
||||||
<activeContent.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
<div className="mb-8">
|
||||||
</div>
|
<div className="flex items-center gap-4 mb-4">
|
||||||
<div>
|
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center">
|
||||||
<h2 className="text-3xl font-extrabold text-[var(--text-primary)]">
|
<activeContent.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||||
{activeContent.title}
|
</div>
|
||||||
</h2>
|
<div>
|
||||||
<p className="text-[var(--text-secondary)]">{activeContent.description}</p>
|
<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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Articles Grid */}
|
{/* Articles Grid */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{activeContent.articles.map((article) => (
|
{activeContent.articles.map((article) => (
|
||||||
<a
|
<button
|
||||||
key={article.id}
|
key={article.id}
|
||||||
href={`#${article.id}`}
|
onClick={() => handleArticleClick(activeSection, 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"
|
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 items-start justify-between gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2 group-hover:text-[var(--color-primary)] transition-colors">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2 group-hover:text-[var(--color-primary)] transition-colors">
|
||||||
{article.title}
|
{article.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[var(--text-secondary)] mb-4">{article.description}</p>
|
<p className="text-[var(--text-secondary)] mb-4">{article.description}</p>
|
||||||
<div className="flex items-center gap-4 text-sm">
|
<div className="flex items-center gap-4 text-sm">
|
||||||
<span className="flex items-center gap-1 text-[var(--text-tertiary)]">
|
<span className="flex items-center gap-1 text-[var(--text-tertiary)]">
|
||||||
<PlayCircle className="w-4 h-4" />
|
<PlayCircle className="w-4 h-4" />
|
||||||
{article.readTime}
|
{article.readTime}
|
||||||
</span>
|
</span>
|
||||||
<span className={`font-medium ${getDifficultyColor(article.difficulty)}`}>
|
<span className={`font-medium ${getDifficultyColor(article.difficulty)}`}>
|
||||||
{getDifficultyLabel(article.difficulty)}
|
{getDifficultyLabel(article.difficulty)}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ChevronRight className="w-6 h-6 text-[var(--text-tertiary)] group-hover:text-[var(--color-primary)] transition-colors flex-shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="w-6 h-6 text-[var(--text-tertiary)] group-hover:text-[var(--color-primary)] transition-colors flex-shrink-0" />
|
</button>
|
||||||
</div>
|
))}
|
||||||
</a>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user