diff --git a/apps/server/src/models/term/term.service.ts b/apps/server/src/models/term/term.service.ts index cc119e6..7f21041 100755 --- a/apps/server/src/models/term/term.service.ts +++ b/apps/server/src/models/term/term.service.ts @@ -298,12 +298,12 @@ export class TermService extends BaseTreeService { ...(hasAnyPerms ? {} // 当有全局权限时,不添加任何额外条件 : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), + // 当无全局权限时,添加域ID过滤 + OR: [ + { domainId: null }, // 通用记录 + { domainId: domainId }, // 特定域记录 + ], + }), }, ancestorId: parentId, relDepth: 1, @@ -315,29 +315,29 @@ export class TermService extends BaseTreeService { }), termIds ? db.term.findMany({ - where: { - ...(termIds && { - OR: [ - ...(validTermIds.length - ? [{ id: { in: validTermIds } }] - : []), - ], - }), - taxonomyId: taxonomyId, - // 动态权限控制条件 - ...(hasAnyPerms - ? {} // 当有全局权限时,不添加任何额外条件 - : { - // 当无全局权限时,添加域ID过滤 + where: { + ...(termIds && { OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 + ...(validTermIds.length + ? [{ id: { in: validTermIds } }] + : []), ], }), - }, - include: { children: true }, - orderBy: { order: 'asc' }, - }) + taxonomyId: taxonomyId, + // 动态权限控制条件 + ...(hasAnyPerms + ? {} // 当有全局权限时,不添加任何额外条件 + : { + // 当无全局权限时,添加域ID过滤 + OR: [ + { domainId: null }, // 通用记录 + { domainId: domainId }, // 特定域记录 + ], + }), + }, + include: { children: true }, + orderBy: { order: 'asc' }, + }) : [], ]); const children = childrenData @@ -371,12 +371,12 @@ export class TermService extends BaseTreeService { ...(hasAnyPerms ? {} // 当有全局权限时,不添加任何额外条件 : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), + // 当无全局权限时,添加域ID过滤 + OR: [ + { domainId: null }, // 通用记录 + { domainId: domainId }, // 特定域记录 + ], + }), }, }, include: { @@ -398,12 +398,12 @@ export class TermService extends BaseTreeService { ...(hasAnyPerms ? {} // 当有全局权限时,不添加任何额外条件 : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), + // 当无全局权限时,添加域ID过滤 + OR: [ + { domainId: null }, // 通用记录 + { domainId: domainId }, // 特定域记录 + ], + }), }, include: { children: true }, // 包含子节点信息 orderBy: { order: 'asc' }, // 按顺序升序排序 diff --git a/apps/web/src/app/main/courses/components/FilterSection.tsx b/apps/web/src/app/main/courses/components/FilterSection.tsx index 6fe06c4..c64a1f8 100755 --- a/apps/web/src/app/main/courses/components/FilterSection.tsx +++ b/apps/web/src/app/main/courses/components/FilterSection.tsx @@ -1,10 +1,12 @@ -import { Checkbox, Divider, Radio, Space , Spin} from 'antd'; -import { categories, levels } from '../mockData'; -import { TaxonomySlug, TermDto } from '@nice/common'; +import { Checkbox, Divider, Radio, Space, Spin } from "antd"; +import { categories, levels } from "../mockData"; +import { TaxonomySlug, TermDto } from "@nice/common"; -import { useEffect, useMemo } from 'react'; -import { api } from '@nice/client'; -import { useSearchParams } from 'react-router-dom'; +import { useEffect, useMemo } from "react"; +import { api } from "@nice/client"; +import { useSearchParams } from "react-router-dom"; +import TermTree from "@web/src/components/models/term/term-tree"; +import TermSelect from "@web/src/components/models/term/term-select"; interface FilterSectionProps { selectedCategory: string; @@ -14,105 +16,99 @@ interface FilterSectionProps { } interface GetTaxonomyProps { - categories: string[]; - isLoading: boolean; + categories: string[]; + isLoading: boolean; } -function useGetTaxonomy({type}) : GetTaxonomyProps { - const {data,isLoading} :{data:TermDto[],isLoading:boolean}= api.term.findMany.useQuery({ - where:{ - taxonomy: { - //TaxonomySlug.CATEGORY - slug:type - } - }, - include:{ - children :true - }, - take:10, // 只取前10个 - orderBy: { - createdAt: 'desc', // 按创建时间降序排列 - }, - }) - const categories = useMemo(() => { - const allCategories = isLoading ? [] : data?.map((course) => course.name); - return [...Array.from(new Set(allCategories))]; - }, [data]); - return {categories,isLoading} +function useGetTaxonomy({ type }): GetTaxonomyProps { + const { data, isLoading }: { data: TermDto[]; isLoading: boolean } = + api.term.findMany.useQuery({ + where: { + taxonomy: { + //TaxonomySlug.CATEGORY + slug: type, + }, + }, + include: { + children: true, + }, + take: 10, // 只取前10个 + orderBy: { + createdAt: "desc", // 按创建时间降序排列 + }, + }); + const categories = useMemo(() => { + const allCategories = isLoading + ? [] + : data?.map((course) => course.name); + return [...Array.from(new Set(allCategories))]; + }, [data]); + return { categories, isLoading }; } - export default function FilterSection({ selectedCategory, selectedLevel, onCategoryChange, onLevelChange, }: FilterSectionProps) { - const gateGory : GetTaxonomyProps = useGetTaxonomy({ - type: TaxonomySlug.CATEGORY, - }) - const levels : GetTaxonomyProps = useGetTaxonomy({ - type: TaxonomySlug.LEVEL, - }) + const gateGory: GetTaxonomyProps = useGetTaxonomy({ + type: TaxonomySlug.CATEGORY, + }); + const levels: GetTaxonomyProps = useGetTaxonomy({ + type: TaxonomySlug.LEVEL, + }); - const [searchParams,setSearchParams] = useSearchParams() - useEffect(() => { - if(searchParams.get('category')) onCategoryChange(searchParams.get('category')) - },[searchParams.get('category')]) + const [searchParams, setSearchParams] = useSearchParams(); + useEffect(() => { + if (searchParams.get("category")) + onCategoryChange(searchParams.get("category")); + }, [searchParams.get("category")]); - return ( -
-
-

课程分类

- onCategoryChange(e.target.value)} - className="flex flex-col space-y-3" - > - { - gateGory.isLoading? - () - : - ( - <> - 全部课程 - {gateGory.categories.map(category => ( - - {category} - - ))} - ) - } - - -
+ const { data: taxonomies } = api.taxonomy.getAll.useQuery({}); + + return ( +
+
+ +
+ {taxonomies.map((tax) => { + return ( + <> +
+

+ {tax?.name} +

+ +
+ + ); + })} -
-

难度等级

- onLevelChange(e.target.value)} - className="flex flex-col space-y-3" - > - { - levels.isLoading ? - () - : - ( - <> - 全部难度 - {levels.categories.map(level => ( - - {level} - - ))} - ) - } - - -
-
- ); +
+

难度等级

+ onLevelChange(e.target.value)} + className="flex flex-col space-y-3"> + {levels.isLoading ? ( + + ) : ( + <> + 全部难度 + {levels.categories.map((level) => ( + + {level} + + ))} + + )} + +
+
+ ); } diff --git a/apps/web/src/app/main/courses/page.tsx b/apps/web/src/app/main/courses/page.tsx index a411d60..080368d 100755 --- a/apps/web/src/app/main/courses/page.tsx +++ b/apps/web/src/app/main/courses/page.tsx @@ -3,80 +3,125 @@ import { mockCourses } from "./mockData"; import FilterSection from "./components/FilterSection"; import CourseList from "./components/CourseList"; import { api } from "@nice/client"; -import { courseDetailSelect, CourseDto, LectureType, PostType } from "@nice/common"; +import { + courseDetailSelect, + CourseDto, + LectureType, + PostType, +} from "@nice/common"; import { useSearchParams } from "react-router-dom"; import { set } from "idb-keyval"; - +interface paginationData { + items:CourseDto[], + totalPages:number +} export default function CoursesPage() { const [currentPage, setCurrentPage] = useState(1); const [selectedCategory, setSelectedCategory] = useState(""); const [selectedLevel, setSelectedLevel] = useState(""); - const pageSize = 9; - const [isAll,setIsAll] = useState(true) + const pageSize = 12; + const [isAll,setIsAll] = useState(true) const [searchParams, setSearchParams] = useSearchParams(); - let coursesData = [] - let isCourseLoading = false - if(!searchParams.get('searchValue')){ - console.log('no category') - const {data,isLoading} = api.post.findManyWithPagination.useQuery({ + + const [coursesData, setCoursesData] = useState([]); + const [isCourseLoading, setIsCourseLoading] = useState(false); + const [totalPagesNum, setTotalPagesNum] = useState(0); + + if(!searchParams.get('searchValue') && !searchParams.get('searchValue')){ + const {data,isLoading} :{ data:paginationData,isLoading:boolean} = api.post.findManyWithPagination.useQuery({ where: { type: PostType.COURSE, - terms:isAll?{}:{ - some: { - OR : [ - selectedCategory?{name:selectedCategory}:{}, - selectedLevel?{name:selectedLevel}:{} - ], - }, - }, - + terms: isAll + ? {} + : { + some: { + OR: [ + selectedCategory + ? { name: selectedCategory } + : {}, + selectedLevel + ? { name: selectedLevel } + : {}, + ], + }, + }, }, - select:courseDetailSelect + pageSize, + page:currentPage, + select:courseDetailSelect, }); - coursesData = data?.items - isCourseLoading = isLoading + console.log(data) + useEffect(()=>{ + console.log(currentPage); + setIsCourseLoading(isLoading) + setCoursesData(data?.items) + setTotalPagesNum(data?.totalPages) + },[currentPage,data]) }else{ console.log('searchValue:'+searchParams.get('searchValue')) const searchValue = searchParams.get('searchValue') - const {data,isLoading} = api.post.findManyWithPagination.useQuery({ + const {data,isLoading} :{ data:paginationData,isLoading:boolean}= api.post.findManyWithPagination.useQuery({ where: { type: PostType.COURSE, - OR:[ - { title: { contains: searchValue, mode: 'insensitive' } }, - { subTitle: { contains: searchValue, mode: 'insensitive' } }, - { content: { contains: searchValue, mode: 'insensitive' } }, - { terms: { some: { name: { contains: searchValue, mode: 'insensitive' } } } } - ] + OR: [ + { title: { contains: searchValue, mode: "insensitive" } }, + { + subTitle: { + contains: searchValue, + mode: "insensitive", + }, + }, + { content: { contains: searchValue, mode: "insensitive" } }, + { + terms: { + some: { + name: { + contains: searchValue, + mode: "insensitive", + }, + }, + }, + }, + ], }, - select:courseDetailSelect + select:courseDetailSelect, + pageSize, + page:currentPage, }) - coursesData = data?.items - isCourseLoading = isLoading + useEffect(()=>{ + setIsCourseLoading(isLoading) + setCoursesData(data?.items) + setTotalPagesNum(data?.totalPages) + },[currentPage]) } useEffect(() => { - if(searchParams.get('searchValue')==''){ - setSelectedCategory(''); - setSelectedLevel('') + if (searchParams.get("searchValue") == "") { + setSelectedCategory(""); + setSelectedLevel(""); } - }, [searchParams.get('searchValue')]); + }, [searchParams.get("searchValue")]); const filteredCourses = useMemo(() => { return isCourseLoading ? [] : coursesData; }, [isCourseLoading, coursesData, selectedCategory, selectedLevel]); - const paginatedCourses :CourseDto[]= useMemo(() => { + const paginatedCourses: CourseDto[] = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; - return isCourseLoading ? [] : (filteredCourses.slice(startIndex, startIndex + pageSize) as any as CourseDto[]); + return isCourseLoading + ? [] + : (filteredCourses.slice( + startIndex, + startIndex + pageSize + ) as any as CourseDto[]); }, [filteredCourses, currentPage]); const handlePageChange = (page: number) => { setCurrentPage(page); window.scrollTo({ top: 0, behavior: "smooth" }); }; - useEffect(()=>{ - setCurrentPage(1) - },[]) - + useEffect(() => { + setCurrentPage(1); + }, []); return (
@@ -93,13 +138,19 @@ export default function CoursesPage() { setSelectedCategory(category); setCurrentPage(1); setIsAll(!category) - setSearchParams({ searchValue: ''}); + setSearchParams(prev => { + prev.delete('searchValue'); + return prev; + }); }} onLevelChange={(level) => { setSelectedLevel(level); setCurrentPage(1); setIsAll(!level) - setSearchParams({ searchValue: ''}); + setSearchParams(prev => { + prev.delete('searchValue'); + return prev; + }); }} />
@@ -110,12 +161,12 @@ export default function CoursesPage() {
- 共找到 {filteredCourses.length} 门课程 + 共找到 {totalPagesNum * pageSize || 0} 门课程
{ onClick={()=>{ console.log(category.name) navigate(`/courses?category=${category.name}`) + window.scrollTo({ top: 0, behavior: 'smooth' }) }} >
diff --git a/apps/web/src/app/main/home/components/HeroSection.tsx b/apps/web/src/app/main/home/components/HeroSection.tsx index f73357d..10ecbfe 100755 --- a/apps/web/src/app/main/home/components/HeroSection.tsx +++ b/apps/web/src/app/main/home/components/HeroSection.tsx @@ -48,12 +48,11 @@ const carouselItems: CarouselItem[] = [ const HeroSection = () => { const carouselRef = useRef(null); const { statistics, baseSetting } = useAppConfig(); - const platformStats: PlatformStat[] = [ - { icon: , value: "50,000+", label: "注册学员" }, - { icon: , value: "1,000+", label: "精品课程" }, - // { icon: , value: '98%', label: '好评度' }, - { icon: , value: "4552", label: "观看次数" }, + { icon: , value: statistics.staffs.toString(), label: "注册学员" }, + { icon: , value: statistics.courses.toString(), label: "精品课程" }, + { icon: , value: statistics.lectures.toString(), label: '课程章节' }, + { icon: , value: statistics.reads.toString(), label: "观看次数" }, ]; const handlePrev = useCallback(() => { carouselRef.current?.prev(); @@ -119,8 +118,8 @@ const HeroSection = () => {
{/* Stats Container */} -
-
+
+
{platformStats.map((stat, index) => (
{ - // const mockCourses = [ - // { - // id: 1, - // title: 'Python 零基础入门', - // instructor: '张教授', - // students: 12000, - // rating: 4.8, - // level: '入门', - // duration: '36小时', - // category: '编程语言', - // progress: 16, - // thumbnail: '/images/course1.jpg', - // }, - // { - // id: 2, - // title: '数据结构与算法', - // instructor: '李教授', - // students: 8500, - // rating: 4.9, - // level: '进阶', - // duration: '48小时', - // category: '计算机基础', - // progress: 35, - // thumbnail: '/images/course2.jpg', - // }, - // { - // id: 3, - // title: '前端开发实战', - // instructor: '王教授', - // students: 10000, - // rating: 4.7, - // level: '中级', - // duration: '42小时', - // category: '前端开发', - // progress: 68, - // thumbnail: '/images/course3.jpg', - // }, - // { - // id: 4, - // title: 'Java企业级开发', - // instructor: '刘教授', - // students: 9500, - // rating: 4.6, - // level: '高级', - // duration: '56小时', - // category: '编程语言', - // progress: 15, - // thumbnail: '/images/course4.jpg', - // }, - // { - // id: 5, - // title: '人工智能基础', - // instructor: '陈教授', - // students: 11000, - // rating: 4.9, - // level: '中级', - // duration: '45小时', - // category: '人工智能', - // progress: 20, - // thumbnail: '/images/course5.jpg', - // }, - // { - // id: 6, - // title: '大数据分析', - // instructor: '赵教授', - // students: 8000, - // rating: 4.8, - // level: '进阶', - // duration: '50小时', - // category: '数据科学', - // progress: 45, - // thumbnail: '/images/course6.jpg', - // }, - // { - // id: 7, - // title: '云计算实践', - // instructor: '孙教授', - // students: 7500, - // rating: 4.7, - // level: '高级', - // duration: '48小时', - // category: '云计算', - // progress: 15, - // thumbnail: '/images/course7.jpg', - // }, - // { - // id: 8, - // title: '移动应用开发', - // instructor: '周教授', - // students: 9000, - // rating: 4.8, - // level: '中级', - // duration: '40小时', - // category: '移动开发', - // progress: 70, - // thumbnail: '/images/course8.jpg', - // }, - // ]; - const { data ,isLoading}: { data: Courses[] , isLoading:boolean} = api.post.findMany.useQuery({ - where: {}, - include: { - instructors: true, - }, - orderBy: { - createdAt: 'desc' // 按创建时间降序排列 - }, - take: 8 // 只获取前8个课程 - }); - useEffect(() => { - if (data) { - console.log('Courses data:', data); - } - }, [data]); + // { + // id: 1, + // title: 'Python 零基础入门', + // instructor: '张教授', + // students: 12000, + // rating: 4.8, + // level: '入门', + // duration: '36小时', + // category: '编程语言', + // progress: 16, + // thumbnail: '/images/course1.jpg', + // }, + // { + // id: 2, + // title: '数据结构与算法', + // instructor: '李教授', + // students: 8500, + // rating: 4.9, + // level: '进阶', + // duration: '48小时', + // category: '计算机基础', + // progress: 35, + // thumbnail: '/images/course2.jpg', + // }, + // { + // id: 3, + // title: '前端开发实战', + // instructor: '王教授', + // students: 10000, + // rating: 4.7, + // level: '中级', + // duration: '42小时', + // category: '前端开发', + // progress: 68, + // thumbnail: '/images/course3.jpg', + // }, + // { + // id: 4, + // title: 'Java企业级开发', + // instructor: '刘教授', + // students: 9500, + // rating: 4.6, + // level: '高级', + // duration: '56小时', + // category: '编程语言', + // progress: 15, + // thumbnail: '/images/course4.jpg', + // }, + // { + // id: 5, + // title: '人工智能基础', + // instructor: '陈教授', + // students: 11000, + // rating: 4.9, + // level: '中级', + // duration: '45小时', + // category: '人工智能', + // progress: 20, + // thumbnail: '/images/course5.jpg', + // }, + // { + // id: 6, + // title: '大数据分析', + // instructor: '赵教授', + // students: 8000, + // rating: 4.8, + // level: '进阶', + // duration: '50小时', + // category: '数据科学', + // progress: 45, + // thumbnail: '/images/course6.jpg', + // }, + // { + // id: 7, + // title: '云计算实践', + // instructor: '孙教授', + // students: 7500, + // rating: 4.7, + // level: '高级', + // duration: '48小时', + // category: '云计算', + // progress: 15, + // thumbnail: '/images/course7.jpg', + // }, + // { + // id: 8, + // title: '移动应用开发', + // instructor: '周教授', + // students: 9000, + // rating: 4.8, + // level: '中级', + // duration: '40小时', + // category: '移动开发', + // progress: 70, + // thumbnail: '/images/course8.jpg', + // }, + // ]; + const { data, isLoading }: { data: Courses[]; isLoading: boolean } = + api.post.findMany.useQuery({ + where: {}, + include: { + instructors: true, + }, + orderBy: { + createdAt: "desc", // 按创建时间降序排列 + }, + take: 8, // 只获取前8个课程 + }); + useEffect(() => { + if (data) { + console.log("mockCourses data:", data); + } + }, [data]); -return ( -
- - - {/* {formattedCourses.map((course)=>{ + // 数据处理逻辑 + // 修正依赖数组 + return ( +
+ + + + {/* {formattedCourses.map((course)=>{ return ( <> course.title ) })} */} - {/* */} - - {/* */} -
-); + + {/* */} +
+ ); }; -export default HomePage; \ No newline at end of file +export default HomePage; diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx index 76a15b4..40053e1 100755 --- a/apps/web/src/app/main/layout/MainHeader.tsx +++ b/apps/web/src/app/main/layout/MainHeader.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Input, Layout, Avatar, Button, Dropdown } from "antd"; import { EditFilled, SearchOutlined, UserOutlined } from "@ant-design/icons"; import { useAuth } from "@web/src/providers/auth-provider"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { UserMenu } from "./UserMenu/UserMenu"; import { NavigationMenu } from "./NavigationMenu"; @@ -12,6 +12,7 @@ export function MainHeader() { const [searchValue, setSearchValue] = useState(""); const { isAuthenticated, user } = useAuth(); const navigate = useNavigate(); + const [searchParams,setSearchParams] = useSearchParams(); return (
@@ -37,7 +38,11 @@ export function MainHeader() { onChange={(e) => setSearchValue(e.target.value)} onPressEnter={(e) => { //console.log(e) - setSearchValue(""); + //setSearchValue(""); + setSearchParams((prev)=>{ + if(searchParams.get("category")) prev.delete("category") + return prev + }) navigate( `/courses/?searchValue=${searchValue}` ); diff --git a/apps/web/src/components/models/term/term-select.tsx b/apps/web/src/components/models/term/term-select.tsx index 045e0e1..ce5d9be 100755 --- a/apps/web/src/components/models/term/term-select.tsx +++ b/apps/web/src/components/models/term/term-select.tsx @@ -23,7 +23,7 @@ export default function TermSelect({ value, onChange, className, - placeholder = "选择单位", + placeholder = "选择分类", multiple = false, taxonomyId, domainId, @@ -190,4 +190,4 @@ export default function TermSelect({ onDropdownVisibleChange={handleDropdownVisibleChange} /> ); -} \ No newline at end of file +} diff --git a/apps/web/src/components/models/term/term-tree.tsx b/apps/web/src/components/models/term/term-tree.tsx new file mode 100644 index 0000000..ae88a68 --- /dev/null +++ b/apps/web/src/components/models/term/term-tree.tsx @@ -0,0 +1,199 @@ +import React, { useEffect, useState, useCallback } from "react"; +import { Tree } from "antd"; +import type { DataNode, TreeProps } from "antd/es/tree"; +import { getUniqueItems } from "@nice/common"; +import { api } from "@nice/client"; + +interface TermData { + value?: string; + children?: TermData[]; + key?: string; + hasChildren?: boolean; + isLeaf?: boolean; + pId?: string; + title?: React.ReactNode; + data?: any; + order?: string; + id?: string; +} + +interface TermTreeProps { + defaultValue?: string | string[]; + value?: string | string[]; + onChange?: (value: string | string[]) => void; + multiple?: boolean; + taxonomyId?: string; + disabled?: boolean; + className?: string; + domainId?: string; + style?: React.CSSProperties; +} + +const TermTree: React.FC = ({ + defaultValue, + value, + onChange, + className, + multiple = false, + taxonomyId, + domainId, + disabled = false, + style, +}) => { + const utils = api.useUtils(); + const [treeData, setTreeData] = useState([]); + + const processTermData = (terms: TermData[]): TermData[] => { + return terms.map((term) => ({ + ...term, + key: term.key || term.id || "", + title: term.title || term.value, + children: term.children + ? processTermData(term.children) + : undefined, + })); + }; + + const fetchParentTerms = useCallback( + async (termIds: string | string[], taxonomyId?: string) => { + const idsArray = Array.isArray(termIds) + ? termIds + : [termIds].filter(Boolean); + try { + const result = await utils.term.getParentSimpleTree.fetch({ + termIds: idsArray, + taxonomyId, + domainId, + }); + return processTermData(result); + } catch (error) { + console.error( + "Error fetching parent terms for termIds", + idsArray, + ":", + error + ); + throw error; + } + }, + [utils, domainId] + ); + + const fetchTerms = useCallback(async () => { + try { + const rootTerms = await utils.term.getChildSimpleTree.fetch({ + taxonomyId, + domainId, + }); + let combinedTerms = processTermData(rootTerms); + if (defaultValue) { + const defaultTerms = await fetchParentTerms( + defaultValue, + taxonomyId + ); + combinedTerms = getUniqueItems( + [...treeData, ...combinedTerms, ...defaultTerms], + "key" + ); + } + if (value) { + const valueTerms = await fetchParentTerms(value, taxonomyId); + combinedTerms = getUniqueItems( + [...treeData, ...combinedTerms, ...valueTerms], + "key" + ); + } + + setTreeData(combinedTerms); + } catch (error) { + console.error("Error fetching terms:", error); + } + }, [ + defaultValue, + value, + taxonomyId, + utils, + fetchParentTerms, + domainId, + treeData, + ]); + + useEffect(() => { + fetchTerms(); + }, [fetchTerms]); + + const onLoadData = async ({ key }: any) => { + try { + const result = await utils.term.getChildSimpleTree.fetch({ + termIds: [key], + taxonomyId, + domainId, + }); + const processedResult = processTermData(result); + const newItems = getUniqueItems( + [...treeData, ...processedResult], + "key" + ); + setTreeData(newItems); + } catch (error) { + console.error( + "Error loading data for node with key", + key, + ":", + error + ); + } + }; + + const handleCheck: TreeProps["onCheck"] = (checkedKeys, info) => { + if (onChange) { + if (multiple) { + onChange(checkedKeys as string[]); + } else { + onChange((checkedKeys as string[])[0] || ""); + } + } + }; + + const handleExpand = async (expandedKeys: React.Key[]) => { + try { + const allKeyIds = expandedKeys + .map((key) => key.toString()) + .filter(Boolean); + const expandedNodes = await utils.term.getChildSimpleTree.fetch({ + termIds: allKeyIds, + taxonomyId, + domainId, + }); + const processedNodes = processTermData(expandedNodes); + const newItems = getUniqueItems( + [...treeData, ...processedNodes], + "key" + ); + setTreeData(newItems); + } catch (error) { + console.error( + "Error expanding nodes with keys", + expandedKeys, + ":", + error + ); + } + }; + + return ( + + ); +}; + +export default TermTree; diff --git a/apps/web/src/components/models/term/util.ts b/apps/web/src/components/models/term/util.ts index c54910a..f7aebc8 100755 --- a/apps/web/src/components/models/term/util.ts +++ b/apps/web/src/components/models/term/util.ts @@ -1,15 +1,16 @@ -import { TreeDataNode } from "@nice/common" +import { TreeDataNode } from "@nice/common"; +import React from "react"; export const treeVisitor = ( - data: TreeDataNode[], - key: React.Key, - callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void + data: TreeDataNode[], + key: React.Key, + callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void ) => { - for (let i = 0; i < data.length; i++) { - if (data[i].key === key) { - return callback(data[i], i, data); - } - if (data[i].children) { - treeVisitor(data[i].children!, key, callback); - } - } -}; \ No newline at end of file + for (let i = 0; i < data.length; i++) { + if (data[i].key === key) { + return callback(data[i], i, data); + } + if (data[i].children) { + treeVisitor(data[i].children!, key, callback); + } + } +};