update
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useKnowledgeBaseCategories, useFeaturedArticles, useKnowledgeBaseArticles } from '@/lib/hooks/useSupport';
|
||||
import KnowledgeBaseArticleModal from './KnowledgeBaseArticleModal';
|
||||
|
||||
@@ -9,39 +9,137 @@ const KnowledgeBase = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [selectedArticleSlug, setSelectedArticleSlug] = useState<string | null>(null);
|
||||
|
||||
// Refs for scrolling to results
|
||||
const articlesRef = useRef<HTMLDivElement>(null);
|
||||
const emptyStateRef = useRef<HTMLDivElement>(null);
|
||||
const articlesListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Fetch all articles (for browsing and category filtering)
|
||||
const { articles: allArticles, loading: allArticlesLoading } = useKnowledgeBaseArticles();
|
||||
|
||||
// Fetch featured articles (for default view)
|
||||
const { articles: featuredArticles, loading: featuredLoading } = useFeaturedArticles();
|
||||
|
||||
// Determine which articles to display
|
||||
let displayArticles = featuredArticles;
|
||||
let isLoading = featuredLoading;
|
||||
let headerText = 'Featured Articles';
|
||||
|
||||
if (searchTerm) {
|
||||
// If searching, filter all articles by search term
|
||||
displayArticles = allArticles.filter(article =>
|
||||
article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
article.content.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
isLoading = allArticlesLoading;
|
||||
headerText = 'Search Results';
|
||||
} else if (selectedCategory) {
|
||||
// If a category is selected, filter articles by that category
|
||||
displayArticles = allArticles.filter(article => article.category_slug === selectedCategory);
|
||||
isLoading = allArticlesLoading;
|
||||
const categoryName = categories.find(cat => cat.slug === selectedCategory)?.name || 'Category';
|
||||
headerText = `${categoryName} Articles`;
|
||||
}
|
||||
// Determine which articles to display using useMemo for reactivity
|
||||
const { displayArticles, isLoading, headerText } = useMemo(() => {
|
||||
if (searchTerm) {
|
||||
// If searching, filter all articles by search term
|
||||
const filtered = allArticles.filter(article =>
|
||||
article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
article.content.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
return {
|
||||
displayArticles: filtered,
|
||||
isLoading: allArticlesLoading,
|
||||
headerText: 'Search Results'
|
||||
};
|
||||
} else if (selectedCategory) {
|
||||
// If a category is selected, filter articles by that category
|
||||
const filtered = allArticles.filter(article => article.category_slug === selectedCategory);
|
||||
const categoryName = categories.find(cat => cat.slug === selectedCategory)?.name || 'Category';
|
||||
return {
|
||||
displayArticles: filtered,
|
||||
isLoading: allArticlesLoading,
|
||||
headerText: `${categoryName} Articles`
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
displayArticles: featuredArticles,
|
||||
isLoading: featuredLoading,
|
||||
headerText: 'Featured Articles'
|
||||
};
|
||||
}
|
||||
}, [searchTerm, selectedCategory, allArticles, featuredArticles, allArticlesLoading, featuredLoading, categories]);
|
||||
|
||||
// Helper function to find the scrollable parent container
|
||||
const findScrollableParent = (element: HTMLElement | null): HTMLElement | null => {
|
||||
if (!element) return null;
|
||||
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
const style = window.getComputedStyle(parent);
|
||||
const overflowY = style.overflowY || style.overflow;
|
||||
const maxHeight = style.maxHeight;
|
||||
|
||||
// Check if this element is scrollable (has overflow and max-height)
|
||||
if ((overflowY === 'auto' || overflowY === 'scroll') && maxHeight && maxHeight !== 'none') {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Scroll to results when category is selected or search changes
|
||||
useEffect(() => {
|
||||
// Only scroll if we have a search term or selected category and articles are loaded
|
||||
if ((searchTerm || selectedCategory) && !isLoading) {
|
||||
// Wait for React to render the articles - use longer delay for category clicks
|
||||
const scrollTimeout = setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (articlesRef.current) {
|
||||
// Try to find the articles list first (most specific), then empty state, then first article item, then section
|
||||
const articlesList = articlesRef.current.querySelector('.articles-list');
|
||||
const firstArticle = articlesRef.current.querySelector('.article-item');
|
||||
const emptyState = articlesRef.current.querySelector('.empty-state');
|
||||
const targetElement = articlesList || firstArticle || emptyState || articlesRef.current;
|
||||
|
||||
if (targetElement) {
|
||||
// Find the scrollable parent container (the modal)
|
||||
const scrollableContainer = findScrollableParent(targetElement as HTMLElement);
|
||||
|
||||
if (scrollableContainer) {
|
||||
// Calculate position relative to the scrollable container
|
||||
const containerRect = scrollableContainer.getBoundingClientRect();
|
||||
const targetRect = targetElement.getBoundingClientRect();
|
||||
|
||||
// Calculate the scroll position within the container
|
||||
const scrollTop = scrollableContainer.scrollTop;
|
||||
const relativeTop = targetRect.top - containerRect.top;
|
||||
const offset = 20; // Small offset from top of container
|
||||
|
||||
scrollableContainer.scrollTo({
|
||||
top: scrollTop + relativeTop - offset,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
} else {
|
||||
// Fallback to window scroll if no scrollable container found
|
||||
const elementTop = targetElement.getBoundingClientRect().top + window.pageYOffset;
|
||||
const offsetPosition = elementTop - 100;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 600); // Longer delay to ensure React has fully rendered the articles
|
||||
|
||||
return () => clearTimeout(scrollTimeout);
|
||||
}
|
||||
}, [searchTerm, selectedCategory, isLoading, displayArticles.length]);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// The search is already being performed by the hook
|
||||
};
|
||||
|
||||
const handleCategoryClick = (categorySlug: string) => {
|
||||
setSelectedCategory(categorySlug);
|
||||
// Clear search when category is selected
|
||||
if (searchTerm) {
|
||||
setSearchTerm('');
|
||||
}
|
||||
// Scroll will be handled by useEffect after articles render
|
||||
};
|
||||
|
||||
const filteredCategories = selectedCategory
|
||||
? categories.filter(cat => cat.slug === selectedCategory)
|
||||
: categories;
|
||||
@@ -95,7 +193,7 @@ const KnowledgeBase = () => {
|
||||
<div key={category.id} className="col-md-6 col-lg-4">
|
||||
<div
|
||||
className="category-card"
|
||||
onClick={() => setSelectedCategory(category.slug)}
|
||||
onClick={() => handleCategoryClick(category.slug)}
|
||||
style={{ borderLeftColor: category.color }}
|
||||
>
|
||||
<div
|
||||
@@ -122,7 +220,7 @@ const KnowledgeBase = () => {
|
||||
)}
|
||||
|
||||
{/* Featured/Search Results Articles */}
|
||||
<div className="kb-articles">
|
||||
<div ref={articlesRef} className="kb-articles">
|
||||
<div className="articles-header">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<h3>{headerText}</h3>
|
||||
@@ -150,7 +248,7 @@ const KnowledgeBase = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : displayArticles.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<div ref={emptyStateRef} className="empty-state">
|
||||
<i className="fa-solid fa-search empty-icon"></i>
|
||||
<h4>No articles found</h4>
|
||||
<p>
|
||||
@@ -160,7 +258,7 @@ const KnowledgeBase = () => {
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="articles-list">
|
||||
<div ref={articlesListRef} className="articles-list">
|
||||
{Array.isArray(displayArticles) && displayArticles.map(article => (
|
||||
<div
|
||||
key={article.id}
|
||||
|
||||
Reference in New Issue
Block a user