casualroom/apps/fenghuo/web/hooks/use-articles.ts

173 lines
5.2 KiB
TypeScript

import { useTRPC } from '@fenghuo/client';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { useState, useMemo, useEffect } from 'react';
import type { FilterState, PaginationState } from '../lib/articles/types.js';
import { DEFAULT_PAGE_SIZE } from '../lib/articles/constants.js';
import { Prisma } from '@fenghuo/db';
export function useArticles() {
const trpc = useTRPC();
// 状态管理
const [filters, setFilters] = useState<FilterState>({
searchTerm: '',
statusFilter: 'all',
categoryFilter: 'all',
sortBy: 'created-desc',
});
const [pagination, setPagination] = useState<PaginationState>({
currentPage: 1,
pageSize: DEFAULT_PAGE_SIZE,
totalPages: 1,
totalCount: 0
});
const [selectedArticles, setSelectedArticles] = useState<string[]>([]);
const [quickEditId, setQuickEditId] = useState<string | null>(null);
const [batchAction, setBatchAction] = useState<string>('');
// 将前端筛选状态映射到 Prisma 查询条件
const { where, orderBy } = useMemo(() => {
const where: Prisma.PostWhereInput = {};
const orderBy: Prisma.PostOrderByWithRelationInput = {};
// 组合查询条件
const andConditions: Prisma.PostWhereInput[] = [];
if (filters.statusFilter !== 'all') {
andConditions.push({ status: filters.statusFilter });
} else {
// 默认不包含草稿和垃圾箱
andConditions.push({ status: { notIn: ['DRAFT', 'TRASH'] } });
}
if (filters.searchTerm) {
andConditions.push({
OR: [
{ title: { contains: filters.searchTerm, mode: 'insensitive' } },
{ content: { contains: filters.searchTerm, mode: 'insensitive' } },
],
});
}
if (filters.categoryFilter !== 'all') {
andConditions.push({ terms: { some: { id: filters.categoryFilter } } });
}
if (andConditions.length > 0) {
where.AND = andConditions;
}
// 排序
switch (filters.sortBy) {
case 'created-asc':
orderBy.createdAt = 'asc';
break;
case 'created-desc':
orderBy.createdAt = 'desc';
break;
case 'published-asc':
orderBy.publishedAt = 'asc';
break;
case 'published-desc':
orderBy.publishedAt = 'desc';
break;
case 'title-asc':
orderBy.title = 'asc';
break;
case 'title-desc':
orderBy.title = 'desc';
break;
}
return { where, orderBy };
}, [filters]);
const { data: articlesData, isLoading: isLoadingArticles, refetch: refetchArticles } = useQuery(
trpc.post.findManyWithPagination.queryOptions(
{
page: pagination.currentPage,
pageSize: pagination.pageSize,
where,
orderBy
},
{
placeholderData: keepPreviousData,
}
)
);
useEffect(() => {
if (articlesData) {
setPagination(prev => ({
...prev,
totalPages: articlesData.totalPages,
totalCount: articlesData.totalCount
}));
}
}, [articlesData]);
const { data: statsData, isLoading: isLoadingStats } = useQuery(
trpc.post.getStats.queryOptions({ where })
);
// 更新筛选器
const updateFilters = (updates: Partial<FilterState>) => {
setFilters(prev => ({ ...prev, ...updates }));
setPagination(prev => ({ ...prev, currentPage: 1 })); // 重置页码
};
const setCurrentPage = (page: number) => {
setPagination(prev => ({ ...prev, currentPage: page }));
};
// 选择操作
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedArticles(articlesData?.items.map(a => a.id) ?? []);
} else {
setSelectedArticles([]);
}
};
const handleSelectArticle = (articleId: string, checked: boolean) => {
setSelectedArticles(prev =>
checked ? [...prev, articleId] : prev.filter(id => id !== articleId)
);
};
const resetSelection = () => {
setSelectedArticles([]);
setBatchAction('');
};
return {
// 数据
articles: articlesData?.items ?? [],
paginatedArticles: articlesData?.items ?? [], // The api already paginates
stats: statsData,
// terms: termsData ?? [], // TODO: fetch terms separately if needed
// 状态
filters,
pagination,
selectedArticles,
quickEditId,
batchAction,
isLoading: isLoadingArticles || isLoadingStats,
// 更新函数
updateFilters,
setCurrentPage,
setSelectedArticles,
setQuickEditId,
setBatchAction,
// 操作函数
handleSelectAll,
handleSelectArticle,
resetSelection,
refetchArticles
};
}