| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- import React, { useState, useEffect } from 'react';
- import { KnowledgeGroup, CreateGroupData, UpdateGroupData } from '../types';
- import { knowledgeGroupService } from '../services/knowledgeGroupService';
- import { useToast } from '../contexts/ToastContext';
- import { useConfirm } from '../contexts/ConfirmContext';
- import { useLanguage } from '../contexts/LanguageContext';
- import { Folder, Plus, Edit2, Trash2, X } from 'lucide-react';
- interface GroupManagerProps {
- groups: KnowledgeGroup[];
- onGroupsChange: (groups: KnowledgeGroup[]) => void;
- }
- const DEFAULT_COLORS = [
- '#3B82F6', '#10B981', '#F59E0B', '#EF4444',
- '#8B5CF6', '#06B6D4', '#84CC16', '#F97316'
- ];
- export const GroupManager: React.FC<GroupManagerProps> = ({ groups, onGroupsChange }) => {
- const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
- const [editingGroup, setEditingGroup] = useState<KnowledgeGroup | null>(null);
- const [formData, setFormData] = useState<CreateGroupData>({
- name: '',
- description: '',
- color: DEFAULT_COLORS[0],
- });
- const [loading, setLoading] = useState(false);
- const { showSuccess, showError } = useToast();
- const { confirm } = useConfirm();
- const { t } = useLanguage();
- const resetForm = () => {
- setFormData({
- name: '',
- description: '',
- color: DEFAULT_COLORS[0],
- });
- };
- const handleCreate = async (e: React.FormEvent) => {
- e.preventDefault();
- if (!formData.name.trim()) return;
- setLoading(true);
- try {
- const newGroup = await knowledgeGroupService.createGroup(formData);
- onGroupsChange([...groups, newGroup]);
- setIsCreateModalOpen(false);
- resetForm();
- showSuccess(t('successNoteCreated')); // Note: Should probably have a more specific translation for group
- } catch (error) {
- showError(t('createFailed'));
- } finally {
- setLoading(false);
- }
- };
- const handleUpdate = async (e: React.FormEvent) => {
- e.preventDefault();
- if (!editingGroup || !formData.name.trim()) return;
- setLoading(true);
- try {
- const updatedGroup = await knowledgeGroupService.updateGroup(editingGroup.id, formData);
- onGroupsChange(groups.map(g => g.id === editingGroup.id ? updatedGroup : g));
- setEditingGroup(null);
- resetForm();
- showSuccess(t('successNoteUpdated'));
- } catch (error) {
- showError(t('updateFailedRetry'));
- } finally {
- setLoading(false);
- }
- };
- const handleDelete = async (group: KnowledgeGroup) => {
- if (!(await confirm(t('confirmDeleteGroup').replace('$1', group.name)))) return;
- try {
- await knowledgeGroupService.deleteGroup(group.id);
- onGroupsChange(groups.filter(g => g.id !== group.id));
- showSuccess(t('successNoteDeleted'));
- } catch (error) {
- showError(t('deleteFailed'));
- }
- };
- const openEditModal = (group: KnowledgeGroup) => {
- setEditingGroup(group);
- setFormData({
- name: group.name,
- description: group.description || '',
- color: group.color,
- });
- };
- const closeModal = () => {
- setIsCreateModalOpen(false);
- setEditingGroup(null);
- resetForm();
- };
- const isModalOpen = isCreateModalOpen || editingGroup !== null;
- return (
- <div className="space-y-4">
- {/* 分组列表 */}
- <div className="space-y-2">
- {groups.map((group) => (
- <div
- key={group.id}
- className="flex items-center justify-between p-3 bg-white rounded-lg border hover:shadow-sm transition-shadow"
- >
- <div className="flex items-center space-x-3">
- <div
- className="w-4 h-4 rounded-full"
- style={{ backgroundColor: group.color }}
- />
- <div>
- <div className="font-medium text-gray-900">{group.name}</div>
- {group.description && (
- <div className="text-sm text-gray-500">{group.description}</div>
- )}
- <div className="text-xs text-gray-400">
- {group.fileCount} 个文件
- </div>
- </div>
- </div>
- <div className="flex items-center space-x-2">
- <button
- onClick={() => openEditModal(group)}
- className="p-1 text-gray-400 hover:text-blue-600 transition-colors"
- >
- <Edit2 size={16} />
- </button>
- <button
- onClick={() => handleDelete(group)}
- className="p-1 text-gray-400 hover:text-red-600 transition-colors"
- >
- <Trash2 size={16} />
- </button>
- </div>
- </div>
- ))}
- </div>
- {/* 创建按钮 */}
- <button
- onClick={() => setIsCreateModalOpen(true)}
- className="w-full flex items-center justify-center p-2 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-blue-400 hover:text-blue-600 transition-colors"
- title={t('createNotebook')}
- >
- <Plus size={18} />
- </button>
- {/* 创建/编辑模态框 */}
- {isModalOpen && (
- <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
- <div className="bg-white rounded-lg p-6 w-full max-w-md">
- <div className="flex items-center justify-between mb-4">
- <h3 className="text-lg font-semibold">
- {editingGroup ? t('editNotebookTitle') : t('createNotebookTitle')}
- </h3>
- <button
- onClick={closeModal}
- className="text-gray-400 hover:text-gray-600"
- >
- <X size={20} />
- </button>
- </div>
- <form onSubmit={editingGroup ? handleUpdate : handleCreate} className="space-y-4">
- <div>
- <label className="block text-sm font-medium text-gray-700 mb-1">
- {t('name')} *
- </label>
- <input
- type="text"
- value={formData.name}
- onChange={(e) => setFormData({ ...formData, name: e.target.value })}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder={t('namePlaceholder')}
- required
- />
- </div>
- <div>
- <label className="block text-sm font-medium text-gray-700 mb-1">
- {t('shortDescription')}
- </label>
- <textarea
- value={formData.description}
- onChange={(e) => setFormData({ ...formData, description: e.target.value })}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder={t('descPlaceholder')}
- rows={3}
- />
- </div>
- <div>
- <label className="block text-sm font-medium text-gray-700 mb-2">
- 颜色标识
- </label>
- <div className="flex space-x-2">
- {DEFAULT_COLORS.map((color) => (
- <button
- key={color}
- type="button"
- onClick={() => setFormData({ ...formData, color })}
- className={`w-8 h-8 rounded-full border-2 ${formData.color === color ? 'border-gray-400' : 'border-gray-200'
- }`}
- style={{ backgroundColor: color }}
- />
- ))}
- </div>
- </div>
- <div className="flex space-x-3 pt-4">
- <button
- type="button"
- onClick={closeModal}
- className="flex-1 px-4 py-2 text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
- >
- {t('cancel')}
- </button>
- <button
- type="submit"
- disabled={loading || !formData.name.trim()}
- className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
- >
- {loading ? t('saving') : (editingGroup ? t('save') : t('create'))}
- </button>
- </div>
- </form>
- </div>
- </div>
- )}
- </div>
- );
- };
|