|
@@ -2,11 +2,20 @@
|
|
|
* TaskListView Component
|
|
* TaskListView Component
|
|
|
*
|
|
*
|
|
|
* Displays a list of tasks with filtering and operations.
|
|
* Displays a list of tasks with filtering and operations.
|
|
|
|
|
+ * Modern design with theme support and clean table layout.
|
|
|
* Requirements: 2.3, 2.4
|
|
* Requirements: 2.3, 2.4
|
|
|
*/
|
|
*/
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import React, { useEffect, useState } from 'react';
|
|
|
import { useAtom } from 'jotai';
|
|
import { useAtom } from 'jotai';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
|
+import {
|
|
|
|
|
+ Play,
|
|
|
|
|
+ Eye,
|
|
|
|
|
+ Trash2,
|
|
|
|
|
+ Search,
|
|
|
|
|
+ ListTodo,
|
|
|
|
|
+ AlertCircle,
|
|
|
|
|
+} from 'lucide-react';
|
|
|
import {
|
|
import {
|
|
|
tasksAtom,
|
|
tasksAtom,
|
|
|
taskLoadingAtom,
|
|
taskLoadingAtom,
|
|
@@ -20,21 +29,7 @@ import {
|
|
|
listTasks,
|
|
listTasks,
|
|
|
deleteTask,
|
|
deleteTask,
|
|
|
} from '../../services/api';
|
|
} from '../../services/api';
|
|
|
-import {
|
|
|
|
|
- DataTable,
|
|
|
|
|
- type ExtendedDataTableColumnDef,
|
|
|
|
|
- Button,
|
|
|
|
|
- Badge,
|
|
|
|
|
- IconPlay,
|
|
|
|
|
- IconEyeOpened,
|
|
|
|
|
- IconTrash,
|
|
|
|
|
- Dialog,
|
|
|
|
|
- DialogContent,
|
|
|
|
|
- DialogDescription,
|
|
|
|
|
- DialogFooter,
|
|
|
|
|
- DialogHeader,
|
|
|
|
|
- DialogTitle,
|
|
|
|
|
-} from '@humansignal/ui';
|
|
|
|
|
|
|
+import styles from './task-list-view.module.scss';
|
|
|
|
|
|
|
|
export const TaskListView: React.FC = () => {
|
|
export const TaskListView: React.FC = () => {
|
|
|
const navigate = useNavigate();
|
|
const navigate = useNavigate();
|
|
@@ -48,6 +43,9 @@ export const TaskListView: React.FC = () => {
|
|
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
|
const [taskToDelete, setTaskToDelete] = useState<Task | null>(null);
|
|
const [taskToDelete, setTaskToDelete] = useState<Task | null>(null);
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ // Search state
|
|
|
|
|
+ const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
|
|
|
|
// Load tasks on mount
|
|
// Load tasks on mount
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -104,263 +102,244 @@ export const TaskListView: React.FC = () => {
|
|
|
// Status badge mapping
|
|
// Status badge mapping
|
|
|
const getStatusBadge = (status: TaskStatus) => {
|
|
const getStatusBadge = (status: TaskStatus) => {
|
|
|
const statusMap = {
|
|
const statusMap = {
|
|
|
- pending: { label: '待处理', variant: 'secondary' as const },
|
|
|
|
|
- in_progress: { label: '进行中', variant: 'info' as const },
|
|
|
|
|
- completed: { label: '已完成', variant: 'success' as const },
|
|
|
|
|
|
|
+ pending: { label: '待处理', className: styles.badgePending },
|
|
|
|
|
+ in_progress: { label: '进行中', className: styles.badgeInProgress },
|
|
|
|
|
+ completed: { label: '已完成', className: styles.badgeCompleted },
|
|
|
};
|
|
};
|
|
|
return statusMap[status];
|
|
return statusMap[status];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // Define table columns
|
|
|
|
|
- const columns: ExtendedDataTableColumnDef<Task>[] = [
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'name',
|
|
|
|
|
- header: '任务名称',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <span className="text-body-medium text-primary-foreground font-semibold">
|
|
|
|
|
- {row.original.name}
|
|
|
|
|
- </span>
|
|
|
|
|
- ),
|
|
|
|
|
- size: 300,
|
|
|
|
|
- minSize: 200,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'project_id',
|
|
|
|
|
- header: '所属项目',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <span className="text-body-small text-secondary-foreground font-mono">
|
|
|
|
|
- {row.original.project_id}
|
|
|
|
|
- </span>
|
|
|
|
|
- ),
|
|
|
|
|
- size: 200,
|
|
|
|
|
- minSize: 150,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'status',
|
|
|
|
|
- header: '状态',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => {
|
|
|
|
|
- const status = getStatusBadge(row.original.status);
|
|
|
|
|
- return <Badge variant={status.variant}>{status.label}</Badge>;
|
|
|
|
|
- },
|
|
|
|
|
- size: 120,
|
|
|
|
|
- minSize: 100,
|
|
|
|
|
- maxSize: 150,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'progress',
|
|
|
|
|
- header: '进度',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <div className="flex items-center gap-tight">
|
|
|
|
|
- <div className="flex-1 bg-secondary-background rounded-full h-2 overflow-hidden">
|
|
|
|
|
- <div
|
|
|
|
|
- className="bg-primary-border h-full transition-all"
|
|
|
|
|
- style={{ width: `${row.original.progress}%` }}
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ // Filter tasks by search query
|
|
|
|
|
+ const searchFilteredTasks = filteredTasks.filter((task) => {
|
|
|
|
|
+ if (!searchQuery) return true;
|
|
|
|
|
+ const query = searchQuery.toLowerCase();
|
|
|
|
|
+ return (
|
|
|
|
|
+ task.name.toLowerCase().includes(query) ||
|
|
|
|
|
+ task.project_id.toString().includes(query) ||
|
|
|
|
|
+ task.assigned_to?.toLowerCase().includes(query)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className={styles.root}>
|
|
|
|
|
+ {/* Header */}
|
|
|
|
|
+ <div className={styles.header}>
|
|
|
|
|
+ <div className={styles.headerContent}>
|
|
|
|
|
+ <div className={styles.headerText}>
|
|
|
|
|
+ <h1 className={styles.title}>任务管理</h1>
|
|
|
|
|
+ <p className={styles.subtitle}>查看和管理所有标注任务</p>
|
|
|
</div>
|
|
</div>
|
|
|
- <span className="text-body-small text-secondary-foreground min-w-[3rem] text-right">
|
|
|
|
|
- {row.original.progress}%
|
|
|
|
|
- </span>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- ),
|
|
|
|
|
- size: 150,
|
|
|
|
|
- minSize: 120,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'assigned_to',
|
|
|
|
|
- header: '分配给',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <span className="text-body-medium text-secondary-foreground">
|
|
|
|
|
- {row.original.assigned_to || '未分配'}
|
|
|
|
|
- </span>
|
|
|
|
|
- ),
|
|
|
|
|
- size: 150,
|
|
|
|
|
- minSize: 120,
|
|
|
|
|
- maxSize: 180,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- accessorKey: 'created_at',
|
|
|
|
|
- header: '创建时间',
|
|
|
|
|
- enableSorting: true,
|
|
|
|
|
- cell: ({ row }) => {
|
|
|
|
|
- const date = new Date(row.original.created_at);
|
|
|
|
|
- return (
|
|
|
|
|
- <span className="text-body-medium text-secondary-foreground">
|
|
|
|
|
- {date.toLocaleDateString('zh-CN', {
|
|
|
|
|
- year: 'numeric',
|
|
|
|
|
- month: '2-digit',
|
|
|
|
|
- day: '2-digit',
|
|
|
|
|
- })}
|
|
|
|
|
- </span>
|
|
|
|
|
- );
|
|
|
|
|
- },
|
|
|
|
|
- size: 150,
|
|
|
|
|
- minSize: 120,
|
|
|
|
|
- maxSize: 180,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 'actions',
|
|
|
|
|
- header: '操作',
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <div className="flex items-center gap-tight">
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="primary"
|
|
|
|
|
- look="string"
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleStartAnnotation(row.original)}
|
|
|
|
|
- aria-label="开始标注"
|
|
|
|
|
- tooltip="开始标注"
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Filters and Search */}
|
|
|
|
|
+ <div className={styles.filterBar}>
|
|
|
|
|
+ <div className={styles.statusFilters}>
|
|
|
|
|
+ <span className={styles.filterLabel}>状态筛选:</span>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.filterButton} ${
|
|
|
|
|
+ filter.status === null ? styles.filterButtonActive : ''
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => handleStatusFilter(null)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 全部
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.filterButton} ${
|
|
|
|
|
+ filter.status === 'pending' ? styles.filterButtonActive : ''
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => handleStatusFilter('pending')}
|
|
|
>
|
|
>
|
|
|
- <IconPlay className="size-4" />
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="neutral"
|
|
|
|
|
- look="string"
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleViewTask(row.original)}
|
|
|
|
|
- aria-label="查看详情"
|
|
|
|
|
- tooltip="查看详情"
|
|
|
|
|
|
|
+ 待处理
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.filterButton} ${
|
|
|
|
|
+ filter.status === 'in_progress' ? styles.filterButtonActive : ''
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => handleStatusFilter('in_progress')}
|
|
|
>
|
|
>
|
|
|
- <IconEyeOpened className="size-4" />
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="negative"
|
|
|
|
|
- look="string"
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => openDeleteDialog(row.original)}
|
|
|
|
|
- aria-label="删除任务"
|
|
|
|
|
- tooltip="删除任务"
|
|
|
|
|
|
|
+ 进行中
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.filterButton} ${
|
|
|
|
|
+ filter.status === 'completed' ? styles.filterButtonActive : ''
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => handleStatusFilter('completed')}
|
|
|
>
|
|
>
|
|
|
- <IconTrash className="size-4" />
|
|
|
|
|
- </Button>
|
|
|
|
|
|
|
+ 已完成
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
- ),
|
|
|
|
|
- size: 150,
|
|
|
|
|
- minSize: 120,
|
|
|
|
|
- maxSize: 180,
|
|
|
|
|
- enableResizing: false,
|
|
|
|
|
- },
|
|
|
|
|
- ];
|
|
|
|
|
|
|
|
|
|
- return (
|
|
|
|
|
- <div className="flex flex-col gap-comfortable h-full">
|
|
|
|
|
- {/* Header */}
|
|
|
|
|
- <div className="flex items-center justify-between pb-comfortable border-b border-neutral-border">
|
|
|
|
|
- <div>
|
|
|
|
|
- <h1 className="text-heading-large font-bold text-primary-foreground">
|
|
|
|
|
- 任务管理
|
|
|
|
|
- </h1>
|
|
|
|
|
- <p className="text-body-medium text-secondary-foreground mt-tighter">
|
|
|
|
|
- 查看和管理所有标注任务
|
|
|
|
|
- </p>
|
|
|
|
|
|
|
+ <div className={styles.searchInput}>
|
|
|
|
|
+ <Search size={18} className={styles.searchIcon} />
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ placeholder="搜索任务名称、项目或负责人..."
|
|
|
|
|
+ value={searchQuery}
|
|
|
|
|
+ onChange={(e) => setSearchQuery(e.target.value)}
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* Filters */}
|
|
|
|
|
- <div className="flex items-center gap-tight">
|
|
|
|
|
- <span className="text-body-medium text-secondary-foreground">状态筛选:</span>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant={filter.status === null ? 'primary' : 'neutral'}
|
|
|
|
|
- look={filter.status === null ? 'filled' : 'outlined'}
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleStatusFilter(null)}
|
|
|
|
|
- >
|
|
|
|
|
- 全部
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant={filter.status === 'pending' ? 'primary' : 'neutral'}
|
|
|
|
|
- look={filter.status === 'pending' ? 'filled' : 'outlined'}
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleStatusFilter('pending')}
|
|
|
|
|
- >
|
|
|
|
|
- 待处理
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant={filter.status === 'in_progress' ? 'primary' : 'neutral'}
|
|
|
|
|
- look={filter.status === 'in_progress' ? 'filled' : 'outlined'}
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleStatusFilter('in_progress')}
|
|
|
|
|
- >
|
|
|
|
|
- 进行中
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant={filter.status === 'completed' ? 'primary' : 'neutral'}
|
|
|
|
|
- look={filter.status === 'completed' ? 'filled' : 'outlined'}
|
|
|
|
|
- size="small"
|
|
|
|
|
- onClick={() => handleStatusFilter('completed')}
|
|
|
|
|
- >
|
|
|
|
|
- 已完成
|
|
|
|
|
- </Button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
{/* Error message */}
|
|
{/* Error message */}
|
|
|
{error && (
|
|
{error && (
|
|
|
- <div className="bg-error-background text-error-foreground p-comfortable rounded-lg border border-error-border">
|
|
|
|
|
- <div className="flex items-center gap-tight">
|
|
|
|
|
- <span className="text-body-medium font-semibold">错误</span>
|
|
|
|
|
- <span className="text-body-medium">{error}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className={styles.errorMessage}>
|
|
|
|
|
+ <AlertCircle size={18} />
|
|
|
|
|
+ <span>{error}</span>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Tasks table */}
|
|
|
|
|
- <div className="flex-1 overflow-hidden">
|
|
|
|
|
- <DataTable
|
|
|
|
|
- data={filteredTasks}
|
|
|
|
|
- columns={columns}
|
|
|
|
|
- isLoading={loading}
|
|
|
|
|
- loadingRows={5}
|
|
|
|
|
- enableSorting={true}
|
|
|
|
|
- emptyState={{
|
|
|
|
|
- title: filter.status ? '没有匹配的任务' : '暂无任务',
|
|
|
|
|
- description: filter.status
|
|
|
|
|
- ? '尝试更改筛选条件或创建新任务'
|
|
|
|
|
- : '项目中还没有创建任何任务',
|
|
|
|
|
- }}
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ {/* Tasks Content */}
|
|
|
|
|
+ <div className={styles.content}>
|
|
|
|
|
+ {loading ? (
|
|
|
|
|
+ <div className={styles.loadingState}>
|
|
|
|
|
+ <div className={styles.spinner} />
|
|
|
|
|
+ <p>加载中...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : searchFilteredTasks.length === 0 ? (
|
|
|
|
|
+ <div className={styles.emptyState}>
|
|
|
|
|
+ <ListTodo size={48} className={styles.emptyIcon} />
|
|
|
|
|
+ <h3>
|
|
|
|
|
+ {searchQuery || filter.status
|
|
|
|
|
+ ? '没有匹配的任务'
|
|
|
|
|
+ : '暂无任务'}
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <p>
|
|
|
|
|
+ {searchQuery || filter.status
|
|
|
|
|
+ ? '尝试更改筛选条件或搜索关键词'
|
|
|
|
|
+ : '项目中还没有创建任何任务'}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className={styles.tableWrapper}>
|
|
|
|
|
+ <table className={styles.table}>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>任务名称</th>
|
|
|
|
|
+ <th>所属项目</th>
|
|
|
|
|
+ <th>状态</th>
|
|
|
|
|
+ <th>进度</th>
|
|
|
|
|
+ <th>分配给</th>
|
|
|
|
|
+ <th>创建时间</th>
|
|
|
|
|
+ <th>操作</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ {searchFilteredTasks.map((task) => {
|
|
|
|
|
+ const status = getStatusBadge(task.status);
|
|
|
|
|
+ const date = new Date(task.created_at);
|
|
|
|
|
+ return (
|
|
|
|
|
+ <tr key={task.id}>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span className={styles.taskName}>{task.name}</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span className={styles.projectId}>
|
|
|
|
|
+ {task.project_id}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span className={`${styles.badge} ${status.className}`}>
|
|
|
|
|
+ {status.label}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div className={styles.progressBar}>
|
|
|
|
|
+ <div className={styles.progressTrack}>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className={styles.progressFill}
|
|
|
|
|
+ style={{ width: `${task.progress}%` }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span className={styles.progressText}>
|
|
|
|
|
+ {task.progress}%
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span className={styles.assignee}>
|
|
|
|
|
+ {task.assigned_to || '未分配'}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span className={styles.date}>
|
|
|
|
|
+ {date.toLocaleDateString('zh-CN', {
|
|
|
|
|
+ year: 'numeric',
|
|
|
|
|
+ month: '2-digit',
|
|
|
|
|
+ day: '2-digit',
|
|
|
|
|
+ })}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div className={styles.actions}>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.actionButton} ${styles.actionButtonPrimary}`}
|
|
|
|
|
+ onClick={() => handleStartAnnotation(task)}
|
|
|
|
|
+ title="开始标注"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Play size={16} />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={styles.actionButton}
|
|
|
|
|
+ onClick={() => handleViewTask(task)}
|
|
|
|
|
+ title="查看详情"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Eye size={16} />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`${styles.actionButton} ${styles.actionButtonDanger}`}
|
|
|
|
|
+ onClick={() => openDeleteDialog(task)}
|
|
|
|
|
+ title="删除任务"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Trash2 size={16} />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
{/* Delete Confirmation Dialog */}
|
|
|
- <Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
|
|
|
|
- <DialogContent>
|
|
|
|
|
- <DialogHeader>
|
|
|
|
|
- <DialogTitle className="text-heading-medium text-error-foreground">
|
|
|
|
|
- 确认删除
|
|
|
|
|
- </DialogTitle>
|
|
|
|
|
- <DialogDescription className="text-body-medium">
|
|
|
|
|
- 确定要删除任务 <span className="font-semibold">"{taskToDelete?.name}"</span> 吗?
|
|
|
|
|
- <br />
|
|
|
|
|
- <br />
|
|
|
|
|
- 此操作将同时删除该任务的所有标注结果,且无法撤销。
|
|
|
|
|
- </DialogDescription>
|
|
|
|
|
- </DialogHeader>
|
|
|
|
|
-
|
|
|
|
|
- <DialogFooter className="gap-tight">
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="neutral"
|
|
|
|
|
- look="outlined"
|
|
|
|
|
- onClick={() => {
|
|
|
|
|
- setIsDeleteDialogOpen(false);
|
|
|
|
|
- setTaskToDelete(null);
|
|
|
|
|
- }}
|
|
|
|
|
- disabled={isSubmitting}
|
|
|
|
|
- >
|
|
|
|
|
- 取消
|
|
|
|
|
- </Button>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="negative"
|
|
|
|
|
- onClick={handleDeleteTask}
|
|
|
|
|
- disabled={isSubmitting}
|
|
|
|
|
- >
|
|
|
|
|
- {isSubmitting ? '删除中...' : '确认删除'}
|
|
|
|
|
- </Button>
|
|
|
|
|
- </DialogFooter>
|
|
|
|
|
- </DialogContent>
|
|
|
|
|
- </Dialog>
|
|
|
|
|
|
|
+ {isDeleteDialogOpen && (
|
|
|
|
|
+ <div className={styles.dialogOverlay} onClick={() => setIsDeleteDialogOpen(false)}>
|
|
|
|
|
+ <div className={styles.dialog} onClick={(e) => e.stopPropagation()}>
|
|
|
|
|
+ <div className={styles.dialogHeader}>
|
|
|
|
|
+ <h2 className={styles.dialogTitleDanger}>确认删除</h2>
|
|
|
|
|
+ <p>
|
|
|
|
|
+ 确定要删除任务{' '}
|
|
|
|
|
+ <strong>"{taskToDelete?.name}"</strong> 吗?
|
|
|
|
|
+ <br />
|
|
|
|
|
+ <br />
|
|
|
|
|
+ 此操作将同时删除该任务的所有标注结果,且无法撤销。
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className={styles.dialogFooter}>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={styles.cancelButton}
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ setIsDeleteDialogOpen(false);
|
|
|
|
|
+ setTaskToDelete(null);
|
|
|
|
|
+ }}
|
|
|
|
|
+ disabled={isSubmitting}
|
|
|
|
|
+ >
|
|
|
|
|
+ 取消
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={styles.deleteButton}
|
|
|
|
|
+ onClick={handleDeleteTask}
|
|
|
|
|
+ disabled={isSubmitting}
|
|
|
|
|
+ >
|
|
|
|
|
+ {isSubmitting ? '删除中...' : '确认删除'}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|