ChenyuRabbitLove's picture
feat: add embeddedable chat component
c4412d0
"use client";
// Core React imports
import { useState, useEffect } from "react";
// Local component imports
import ChatBotCard from "./chat-bot-card";
// Type definitions and data
import { ChatBot } from "@/app/(audiences)/for-students/data/chatbots";
/**
* Props interface for the Section component that displays a group of chatbots
* in a responsive grid layout with a title and subtitle
*/
interface SectionProps {
title: string;
subtitle: string;
bots: ChatBot[];
columns?: number;
}
/**
* Renders a section of chatbots with a header and responsive grid layout
* Returns null if no bots are provided
*/
function Section({ title, subtitle, bots, columns = 4 }: SectionProps) {
if (bots.length === 0) return null;
return (
<div className="mb-16">
<div className="mb-8">
<h2 className="text-2xl font-bold text-text-primary mb-2">{title}</h2>
<p className="text-text-secondary">{subtitle}</p>
</div>
{/* Responsive grid: 1 column on mobile, 2 on sm screens, configurable columns on lg */}
<div className={`grid gap-4 sm:grid-cols-2 lg:grid-cols-${columns}`}>
{bots.map((chatbot) => (
<ChatBotCard key={chatbot.id} chatbot={chatbot} />
))}
</div>
</div>
);
}
/**
* Props interface for the main ChatBotGrid component
* Includes filter criteria and functions to fetch different bot categories
*/
interface ChatBotGridProps {
selectedCategories: string[];
selectedSubjects: string[];
searchQuery: string;
getPopularBots: () => ChatBot[];
getTrendingBots: () => ChatBot[];
getAllBots: () => ChatBot[];
}
/**
* Main component that displays a filterable grid of chatbots
* Supports filtering by category, subject, and search query
* Shows different sections based on filter state
*/
export default function ChatBotGrid({
selectedCategories,
selectedSubjects,
searchQuery,
getPopularBots,
getTrendingBots,
getAllBots,
}: ChatBotGridProps) {
// Initialize state with lazy evaluation using callback functions
const [filteredPopularBots, setFilteredPopularBots] = useState<ChatBot[]>(
() => getPopularBots(),
);
const [filteredTrendingBots, setFilteredTrendingBots] = useState<ChatBot[]>(
() => getTrendingBots(),
);
const [filteredAllBots, setFilteredAllBots] = useState<ChatBot[]>(() =>
getAllBots(),
);
/**
* Effect to filter bots whenever filter criteria or bot data changes
* Applies search query, category, and subject filters
*/
useEffect(() => {
const filterBots = (bots: ChatBot[]) => {
let filtered = bots;
// Apply text search filter across multiple fields
if (searchQuery) {
const query = searchQuery.toLowerCase();
filtered = filtered.filter(
(bot) =>
bot.title.toLowerCase().includes(query) ||
bot.description.toLowerCase().includes(query) ||
bot.subject.toLowerCase().includes(query) ||
bot.category.toLowerCase().includes(query),
);
}
// Apply category filter if categories are selected
if (selectedCategories.length > 0) {
filtered = filtered.filter(
(bot) => bot.category && selectedCategories.includes(bot.category),
);
}
// Apply subject filter if subjects are selected
if (selectedSubjects.length > 0) {
filtered = filtered.filter((bot) =>
selectedSubjects.includes(bot.subject),
);
}
return filtered;
};
// Update all filtered bot lists
setFilteredPopularBots(filterBots(getPopularBots()));
setFilteredTrendingBots(filterBots(getTrendingBots()));
setFilteredAllBots(filterBots(getAllBots()));
}, [
selectedCategories,
selectedSubjects,
searchQuery,
getPopularBots,
getTrendingBots,
getAllBots,
]);
// Check if any filters are active
const hasFiltersOrSearch =
selectedCategories.length > 0 || selectedSubjects.length > 0 || searchQuery;
// When filters are active, show only search results
if (hasFiltersOrSearch) {
return (
<Section
title="搜尋結果"
subtitle={`符合 ${searchQuery ? '"' + searchQuery + '" ' : ""}的工具`}
bots={filteredAllBots}
columns={4}
/>
);
}
// Filter bots by category and subject for different sections
const tutorBots = filteredAllBots.filter((bot) => bot.category === "教育");
const teachingBots = filteredAllBots.filter((bot) => bot.subject === "教學");
const assessmentBots = filteredAllBots.filter(
(bot) => bot.subject === "評量",
);
// Default view showing multiple sections of categorized bots
return (
<div className="space-y-8">
<Section
title="精選"
subtitle="歷來最多人使用的工具"
bots={filteredPopularBots}
columns={4}
/>
<Section
title="熱門"
subtitle="近期最多人使用的工具"
bots={filteredTrendingBots}
columns={4}
/>
<Section
title="教學輔助"
subtitle="教學規劃與課程設計工具"
bots={teachingBots}
columns={4}
/>
<Section
title="評量工具"
subtitle="作業與評量相關工具"
bots={assessmentBots}
columns={4}
/>
<Section
title="教育資源"
subtitle="其他教育相關工具"
bots={tutorBots}
columns={4}
/>
</div>
);
}