feat(cabinet): дать пользователю самому удалять задачи из кабинета
All checks were successful
Deploy / test (push) Successful in 3m3s
Deploy / deploy (push) Successful in 20s

Раньше удалить задачу мог только админ через свою панель — обычный
пользователь с зависшей (например, навечно застрявшей в processing)
задачей не мог убрать её из кабинета вообще. Кнопка удаления на
карточке + снято искусственное ограничение "нельзя удалить, пока
processing" (все воркеры уже корректно проверяют task is None перед
записью, удаление во время реальной обработки безопасно).
This commit is contained in:
jze9
2026-08-25 15:24:46 +05:00
parent 5eac465917
commit 29301c3a4c
3 changed files with 30 additions and 10 deletions

View File

@@ -119,11 +119,5 @@ async def delete_task(
if task is None: if task is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Задача не найдена") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Задача не найдена")
if task.status == "processing":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Нельзя удалить задачу в процессе выполнения",
)
await db.delete(task) await db.delete(task)
await db.commit() await db.commit()

View File

@@ -1,13 +1,14 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { ru } from 'date-fns/locale'; import { ru } from 'date-fns/locale';
import { Search, FileText, BookOpen, AlignLeft, ChevronRight } from 'lucide-react'; import { Search, FileText, BookOpen, AlignLeft, ChevronRight, Trash2 } from 'lucide-react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import { StatusBadge } from './StatusBadge'; import { StatusBadge } from './StatusBadge';
import type { Task } from '../types'; import type { Task } from '../types';
interface TaskCardProps { interface TaskCardProps {
task: Task; task: Task;
onDelete?: (publicId: string) => void;
} }
const TYPE_CONFIG = { const TYPE_CONFIG = {
@@ -63,7 +64,7 @@ function getTaskSummary(task: Task): string {
return 'Результат готов'; return 'Результат готов';
} }
export function TaskCard({ task }: TaskCardProps) { export function TaskCard({ task, onDelete }: TaskCardProps) {
const config = TYPE_CONFIG[task.type] || TYPE_CONFIG.search; const config = TYPE_CONFIG[task.type] || TYPE_CONFIG.search;
const Icon = config.icon; const Icon = config.icon;
const summary = getTaskSummary(task); const summary = getTaskSummary(task);
@@ -94,6 +95,21 @@ export function TaskCard({ task }: TaskCardProps) {
</p> </p>
</div> </div>
{/* Удалить */}
{onDelete && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (confirm('Удалить задачу?')) onDelete(task.public_id);
}}
title="Удалить"
className="p-1.5 rounded-lg text-gray-300 hover:text-red-600 hover:bg-red-50 flex-shrink-0 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
)}
{/* Стрелка */} {/* Стрелка */}
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-brand-500 flex-shrink-0 transition-colors" /> <ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-brand-500 flex-shrink-0 transition-colors" />
</div> </div>

View File

@@ -1,5 +1,5 @@
import { Navigate, useNavigate, Link } from 'react-router-dom'; import { Navigate, useNavigate, Link } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Crown, Search, Shield, BookOpen, ShieldAlert, Settings as SettingsIcon } from 'lucide-react'; import { Crown, Search, Shield, BookOpen, ShieldAlert, Settings as SettingsIcon } from 'lucide-react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { TaskCard } from '../components/TaskCard'; import { TaskCard } from '../components/TaskCard';
@@ -17,6 +17,16 @@ const PLAN_BADGE_STYLES = {
export function Cabinet() { export function Cabinet() {
const { isAuthenticated, user } = useAuthStore(); const { isAuthenticated, user } = useAuthStore();
const navigate = useNavigate(); const navigate = useNavigate();
const qc = useQueryClient();
const deleteTask = useMutation({
mutationFn: (publicId: string) => tasksApi.delete(publicId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['tasks'] });
toast.success('Задача удалена');
},
onError: () => toast.error('Не удалось удалить задачу'),
});
const openAdmin = async () => { const openAdmin = async () => {
try { try {
@@ -144,7 +154,7 @@ export function Cabinet() {
{tasks && tasks.length > 0 && ( {tasks && tasks.length > 0 && (
<div className="space-y-2"> <div className="space-y-2">
{tasks.map((task) => ( {tasks.map((task) => (
<TaskCard key={task.public_id} task={task} /> <TaskCard key={task.public_id} task={task} onDelete={(id) => deleteTask.mutate(id)} />
))} ))}
</div> </div>
)} )}