add ai functions
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
This commit is contained in:
@ -40,18 +40,35 @@ import {
|
||||
useIdeasQuery,
|
||||
useDeleteIdea,
|
||||
useReorderIdeas,
|
||||
useUpdateIdea,
|
||||
} from '../../hooks/useIdeas';
|
||||
import {
|
||||
useEstimateIdea,
|
||||
useGenerateSpecification,
|
||||
useSpecificationHistory,
|
||||
useDeleteSpecificationHistoryItem,
|
||||
useRestoreSpecificationFromHistory,
|
||||
} from '../../hooks/useAi';
|
||||
import { useIdeasStore } from '../../store/ideas';
|
||||
import { createColumns } from './columns';
|
||||
import { DraggableRow } from './DraggableRow';
|
||||
import { CommentsPanel } from '../CommentsPanel';
|
||||
import { AiEstimateModal } from '../AiEstimateModal';
|
||||
import { SpecificationModal } from '../SpecificationModal';
|
||||
import type { EstimateResult } from '../../services/ai';
|
||||
import type { Idea } from '../../types/idea';
|
||||
|
||||
const SKELETON_COLUMNS_COUNT = 9;
|
||||
const SKELETON_COLUMNS_COUNT = 10;
|
||||
|
||||
export function IdeasTable() {
|
||||
const { data, isLoading, isError } = useIdeasQuery();
|
||||
const deleteIdea = useDeleteIdea();
|
||||
const reorderIdeas = useReorderIdeas();
|
||||
const updateIdea = useUpdateIdea();
|
||||
const estimateIdea = useEstimateIdea();
|
||||
const generateSpecification = useGenerateSpecification();
|
||||
const deleteSpecificationHistoryItem = useDeleteSpecificationHistoryItem();
|
||||
const restoreSpecificationFromHistory = useRestoreSpecificationFromHistory();
|
||||
const { sorting, setSorting, pagination, setPage, setLimit } =
|
||||
useIdeasStore();
|
||||
|
||||
@ -59,19 +76,140 @@ export function IdeasTable() {
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
// ID идеи с раскрытыми комментариями
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
// AI-оценка
|
||||
const [estimatingId, setEstimatingId] = useState<string | null>(null);
|
||||
const [estimateModalOpen, setEstimateModalOpen] = useState(false);
|
||||
const [estimateResult, setEstimateResult] = useState<EstimateResult | null>(null);
|
||||
// ТЗ (спецификация)
|
||||
const [specificationModalOpen, setSpecificationModalOpen] = useState(false);
|
||||
const [specificationIdea, setSpecificationIdea] = useState<Idea | null>(null);
|
||||
const [generatedSpecification, setGeneratedSpecification] = useState<string | null>(null);
|
||||
const [generatingSpecificationId, setGeneratingSpecificationId] = useState<string | null>(null);
|
||||
|
||||
// История ТЗ
|
||||
const specificationHistory = useSpecificationHistory(specificationIdea?.id ?? null);
|
||||
|
||||
const handleToggleComments = (id: string) => {
|
||||
setExpandedId((prev) => (prev === id ? null : id));
|
||||
};
|
||||
|
||||
const handleEstimate = (id: string) => {
|
||||
setEstimatingId(id);
|
||||
setEstimateModalOpen(true);
|
||||
setEstimateResult(null);
|
||||
estimateIdea.mutate(id, {
|
||||
onSuccess: (result) => {
|
||||
setEstimateResult(result);
|
||||
setEstimatingId(null);
|
||||
},
|
||||
onError: () => {
|
||||
setEstimatingId(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseEstimateModal = () => {
|
||||
setEstimateModalOpen(false);
|
||||
setEstimateResult(null);
|
||||
};
|
||||
|
||||
const handleViewEstimate = (idea: Idea) => {
|
||||
if (!idea.estimatedHours || !idea.estimateDetails) return;
|
||||
|
||||
// Показываем сохранённые результаты оценки
|
||||
setEstimateResult({
|
||||
ideaId: idea.id,
|
||||
ideaTitle: idea.title,
|
||||
totalHours: idea.estimatedHours,
|
||||
complexity: idea.complexity!,
|
||||
breakdown: idea.estimateDetails.breakdown,
|
||||
recommendations: idea.estimateDetails.recommendations,
|
||||
estimatedAt: idea.estimatedAt!,
|
||||
});
|
||||
setEstimateModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSpecification = (idea: Idea) => {
|
||||
setSpecificationIdea(idea);
|
||||
setSpecificationModalOpen(true);
|
||||
|
||||
// Если ТЗ уже есть — показываем его
|
||||
if (idea.specification) {
|
||||
setGeneratedSpecification(idea.specification);
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе генерируем
|
||||
setGeneratedSpecification(null);
|
||||
setGeneratingSpecificationId(idea.id);
|
||||
generateSpecification.mutate(idea.id, {
|
||||
onSuccess: (result) => {
|
||||
setGeneratedSpecification(result.specification);
|
||||
setGeneratingSpecificationId(null);
|
||||
},
|
||||
onError: () => {
|
||||
setGeneratingSpecificationId(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseSpecificationModal = () => {
|
||||
setSpecificationModalOpen(false);
|
||||
setSpecificationIdea(null);
|
||||
setGeneratedSpecification(null);
|
||||
};
|
||||
|
||||
const handleSaveSpecification = (specification: string) => {
|
||||
if (!specificationIdea) return;
|
||||
updateIdea.mutate(
|
||||
{ id: specificationIdea.id, data: { specification } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setGeneratedSpecification(specification);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleRegenerateSpecification = () => {
|
||||
if (!specificationIdea) return;
|
||||
setGeneratingSpecificationId(specificationIdea.id);
|
||||
generateSpecification.mutate(specificationIdea.id, {
|
||||
onSuccess: (result) => {
|
||||
setGeneratedSpecification(result.specification);
|
||||
setGeneratingSpecificationId(null);
|
||||
},
|
||||
onError: () => {
|
||||
setGeneratingSpecificationId(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteHistoryItem = (historyId: string) => {
|
||||
deleteSpecificationHistoryItem.mutate(historyId);
|
||||
};
|
||||
|
||||
const handleRestoreFromHistory = (historyId: string) => {
|
||||
restoreSpecificationFromHistory.mutate(historyId, {
|
||||
onSuccess: (result) => {
|
||||
setGeneratedSpecification(result.specification);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
onDelete: (id) => deleteIdea.mutate(id),
|
||||
onToggleComments: handleToggleComments,
|
||||
onEstimate: handleEstimate,
|
||||
onViewEstimate: handleViewEstimate,
|
||||
onSpecification: handleSpecification,
|
||||
expandedId,
|
||||
estimatingId,
|
||||
generatingSpecificationId,
|
||||
}),
|
||||
[deleteIdea, expandedId],
|
||||
[deleteIdea, expandedId, estimatingId, generatingSpecificationId],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
@ -307,6 +445,29 @@ export function IdeasTable() {
|
||||
rowsPerPageOptions={[10, 20, 50, 100]}
|
||||
/>
|
||||
)}
|
||||
<AiEstimateModal
|
||||
open={estimateModalOpen}
|
||||
onClose={handleCloseEstimateModal}
|
||||
result={estimateResult}
|
||||
isLoading={estimateIdea.isPending && !estimateResult}
|
||||
error={estimateIdea.error}
|
||||
/>
|
||||
<SpecificationModal
|
||||
open={specificationModalOpen}
|
||||
onClose={handleCloseSpecificationModal}
|
||||
idea={specificationIdea}
|
||||
specification={generatedSpecification}
|
||||
isLoading={generateSpecification.isPending && !generatedSpecification}
|
||||
error={generateSpecification.error}
|
||||
onSave={handleSaveSpecification}
|
||||
isSaving={updateIdea.isPending}
|
||||
onRegenerate={handleRegenerateSpecification}
|
||||
history={specificationHistory.data ?? []}
|
||||
isHistoryLoading={specificationHistory.isLoading}
|
||||
onDeleteHistoryItem={handleDeleteHistoryItem}
|
||||
onRestoreFromHistory={handleRestoreFromHistory}
|
||||
isRestoring={restoreSpecificationFromHistory.isPending}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user