feat(cabinet): дать пользователю самому удалять задачи из кабинета
Раньше удалить задачу мог только админ через свою панель — обычный пользователь с зависшей (например, навечно застрявшей в processing) задачей не мог убрать её из кабинета вообще. Кнопка удаления на карточке + снято искусственное ограничение "нельзя удалить, пока processing" (все воркеры уже корректно проверяют task is None перед записью, удаление во время реальной обработки безопасно).
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user