'use client'; import * as React from 'react'; import { useState, useRef } from 'react'; import { Button } from '@nice/ui/components/button'; import { Popover, PopoverContent, PopoverTrigger } from '@nice/ui/components/popover'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@nice/ui/components/command'; import { IconChevronDown, IconCheck, IconSchool, IconX } from '@tabler/icons-react'; import { cn } from '@nice/ui/lib/utils'; import { useTRPC } from '@fenghuo/client'; import { useQuery } from '@tanstack/react-query'; import { Badge } from '@nice/ui/components/badge'; import { TaxonomySlug } from '@fenghuo/common'; import type { Term } from '@fenghuo/db'; // 专业选择器属性 interface ProfessionSelectorProps { value?: string | string[]; // 支持单选和多选 onValueChange?: (value: string | string[]) => void; placeholder?: string; className?: string; disabled?: boolean; allowClear?: boolean; multiple?: boolean; // 是否支持多选 showDescription?: boolean; // 是否显示专业描述 includeInactive?: boolean; // 是否包含非活跃专业 modal?: boolean; // 是否为模态模式,用于在 Dialog 中解决滚轮问题 } // 专业项组件 interface ProfessionItemProps { profession: Term; isSelected: boolean; showDescription?: boolean; onSelect: () => void; } function ProfessionItem({ profession, isSelected, showDescription = true, onSelect }: ProfessionItemProps) { return (
{/* 专业图标 */} {/* 专业信息 */}
{profession.name}
{showDescription && profession.description && (

{profession.description}

)}
{/* 选中状态指示 */} {isSelected && }
); } // 主组件 export function ProfessionSelect({ value, onValueChange, placeholder = '选择专业', className, disabled = false, allowClear = true, multiple = false, showDescription = true, includeInactive = false, modal = false, }: ProfessionSelectorProps) { const [open, setOpen] = useState(false); const [searchValue, setSearchValue] = useState(''); const containerRef = useRef(null); const trpc = useTRPC(); // 使用 tRPC 获取专业列表 const { data: professions = [], isLoading, error, } = useQuery({ ...trpc.term.findMany.queryOptions({ where: { taxonomy: { slug: TaxonomySlug.PROFESSION, }, deletedAt: null, }, orderBy: [ { order: 'asc' }, { name: 'asc' }, ], }), }); // 过滤专业(根据搜索关键词) const filteredProfessions = React.useMemo(() => { if (!searchValue.trim()) { return professions; } const searchTerm = searchValue.toLowerCase(); return professions.filter( (profession) => profession.name.toLowerCase().includes(searchTerm) || profession.description?.toLowerCase().includes(searchTerm) || profession.slug.toLowerCase().includes(searchTerm), ); }, [professions, searchValue]); // 获取选中的专业 const selectedProfessions = React.useMemo(() => { if (!value) return []; const selectedIds = Array.isArray(value) ? value : [value]; return professions.filter((profession) => selectedIds.includes(profession.id)); }, [professions, value]); // 处理选择逻辑 const handleSelect = (professionId: string) => { if (!onValueChange) return; if (multiple) { const currentValues = Array.isArray(value) ? value : value ? [value] : []; if (currentValues.includes(professionId)) { // 取消选择 const newValues = currentValues.filter((id) => id !== professionId); onValueChange(newValues); } else { // 添加选择 onValueChange([...currentValues, professionId]); } } else { // 单选模式 onValueChange(professionId); setOpen(false); } }; // 清除选择 const handleClear = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onValueChange?.(multiple ? [] : ''); }; // 处理单个专业移除(仅多选模式) const handleRemoveProfession = (professionId: string, e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (multiple) { const currentValues = Array.isArray(value) ? value : value ? [value] : []; const newValues = currentValues.filter((id) => id !== professionId); onValueChange?.(newValues); } }; // 渲染触发器内容 const renderTriggerContent = () => { if (selectedProfessions.length === 0) { return placeholder; } if (multiple) { if (selectedProfessions.length === 1) { return selectedProfessions[0]!.name; } else { return (
{selectedProfessions.slice(0, 1).map((profession) => ( {profession.name} handleRemoveProfession(profession.id, e)} className="ml-1 hover:bg-muted-foreground/20 rounded-sm p-0.5 cursor-pointer inline-flex items-center justify-center" role="button" tabIndex={0} aria-label={`移除专业 ${profession.name}`} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleRemoveProfession(profession.id, e as any); } }} > ))} {selectedProfessions.length > 1 && ( +{selectedProfessions.length - 1} )}
); } } return selectedProfessions[0]!.name; }; // 检查是否选中 const isSelected = (professionId: string) => { if (!value) return false; return Array.isArray(value) ? value.includes(professionId) : value === professionId; }; if (error) { console.error('加载专业列表失败:', error); } return (
{isLoading ? ( 加载中... ) : filteredProfessions.length === 0 ? ( 未找到专业 ) : ( {/* 多选模式下显示已选择数量 */} {multiple && selectedProfessions.length > 0 && (
已选择 {selectedProfessions.length} 个专业
)} {filteredProfessions.map((profession) => ( handleSelect(profession.id)} /> ))}
)}
); } // 导出便捷的单选和多选组件 export function SingleProfessionSelector( props: Omit & { onValueChange?: (value: string) => void; }, ) { const handleValueChange = (value: string | string[]) => { if (props.onValueChange && typeof value === 'string') { props.onValueChange(value); } }; return ; } export function MultipleProfessionSelector( props: Omit & { onValueChange?: (value: string[]) => void; }, ) { const handleValueChange = (value: string | string[]) => { if (props.onValueChange && Array.isArray(value)) { props.onValueChange(value); } }; return ; }