feat: 状态渲染修复+中文姓名映射+SUPERVISOR/SUPER_ADMIN权限控制(前端+后端)
This commit is contained in:
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import uuid
|
import uuid
|
||||||
from fastapi import APIRouter, Depends, Query, Body
|
from fastapi import APIRouter, Depends, Query, Body
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from app.services.auth_service import get_current_user
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
@ -173,6 +174,7 @@ async def receive_task_endpoint(
|
|||||||
remark: str | None = Body(None, description="接收备注", embed=True),
|
remark: str | None = Body(None, description="接收备注", embed=True),
|
||||||
task_name: str | None = Body(None, description="接收人选定的工序名称", embed=True),
|
task_name: str | None = Body(None, description="接收人选定的工序名称", embed=True),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: dict = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
**确认接收任务。工人选定工序名称后接收。**
|
**确认接收任务。工人选定工序名称后接收。**
|
||||||
@ -181,7 +183,8 @@ async def receive_task_endpoint(
|
|||||||
动作:状态改为 WIP,记录 received_at,更新 task_name,同步产品宏观状态。
|
动作:状态改为 WIP,记录 received_at,更新 task_name,同步产品宏观状态。
|
||||||
"""
|
"""
|
||||||
return await task_service.receive_task(
|
return await task_service.receive_task(
|
||||||
db, uuid.UUID(task_id), operator_id, remark, task_name
|
db, uuid.UUID(task_id), operator_id, remark, task_name,
|
||||||
|
operator_role=current_user.get("role"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -195,6 +198,7 @@ async def reject_task_endpoint(
|
|||||||
request: TaskRejectRequest,
|
request: TaskRejectRequest,
|
||||||
operator_id: str | None = Query(None, description="操作人ID"),
|
operator_id: str | None = Query(None, description="操作人ID"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: dict = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
**品质驳回:将任务标记为 REJECTED,自动创建返工任务。**
|
**品质驳回:将任务标记为 REJECTED,自动创建返工任务。**
|
||||||
@ -205,7 +209,8 @@ async def reject_task_endpoint(
|
|||||||
3. 为该负责人新建返工任务(is_rework=True, status=PENDING)。
|
3. 为该负责人新建返工任务(is_rework=True, status=PENDING)。
|
||||||
"""
|
"""
|
||||||
return await task_service.reject_task(
|
return await task_service.reject_task(
|
||||||
db, uuid.UUID(task_id), request, operator_id
|
db, uuid.UUID(task_id), request, operator_id,
|
||||||
|
operator_role=current_user.get("role"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -219,6 +224,7 @@ async def transfer_task_endpoint(
|
|||||||
request: TaskTransferRequest,
|
request: TaskTransferRequest,
|
||||||
operator_id: str | None = Query(None, description="操作人ID"),
|
operator_id: str | None = Query(None, description="操作人ID"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: dict = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
**完工并裂变转交:完成当前任务,批量创建下一道工序任务。**
|
**完工并裂变转交:完成当前任务,批量创建下一道工序任务。**
|
||||||
@ -237,7 +243,8 @@ async def transfer_task_endpoint(
|
|||||||
- 否则 → 顶层同级转交。
|
- 否则 → 顶层同级转交。
|
||||||
"""
|
"""
|
||||||
return await task_service.transfer_task(
|
return await task_service.transfer_task(
|
||||||
db, uuid.UUID(task_id), request, operator_id
|
db, uuid.UUID(task_id), request, operator_id,
|
||||||
|
operator_role=current_user.get("role"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,20 @@ from app.schemas.task import (
|
|||||||
# 特殊位置常量
|
# 特殊位置常量
|
||||||
VIRTUAL_WAREHOUSE = "virtual_warehouse"
|
VIRTUAL_WAREHOUSE = "virtual_warehouse"
|
||||||
|
|
||||||
|
# 管理员/主管角色白名单 — 拥有上帝视角操作权限
|
||||||
|
ADMIN_ROLES = {"SUPER_ADMIN", "SUPERVISOR"}
|
||||||
|
|
||||||
|
|
||||||
|
def _check_permission(task_assignee_id: str | None, operator_id: str | None, operator_role: str | None = None) -> None:
|
||||||
|
"""权限校验:本人 或 管理员/主管 可操作"""
|
||||||
|
if operator_role and operator_role in ADMIN_ROLES:
|
||||||
|
return # 上帝视角,直接放行
|
||||||
|
if operator_id and task_assignee_id and operator_id != task_assignee_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=f"您无权操作此任务,当前任务负责人为 {task_assignee_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 内部辅助函数
|
# 内部辅助函数
|
||||||
@ -433,6 +447,7 @@ ARCHIVED_STATUS = "ARCHIVED"
|
|||||||
async def receive_task(
|
async def receive_task(
|
||||||
db: AsyncSession, task_id: uuid.UUID, operator_id: str | None = None,
|
db: AsyncSession, task_id: uuid.UUID, operator_id: str | None = None,
|
||||||
remark: str | None = None, task_name: str | None = None,
|
remark: str | None = None, task_name: str | None = None,
|
||||||
|
operator_role: str | None = None,
|
||||||
) -> TaskResponse:
|
) -> TaskResponse:
|
||||||
"""
|
"""
|
||||||
操作员确认接收任务。工人选定工序名称后接收。
|
操作员确认接收任务。工人选定工序名称后接收。
|
||||||
@ -442,12 +457,8 @@ async def receive_task(
|
|||||||
"""
|
"""
|
||||||
task = await _get_task_or_404(db, task_id)
|
task = await _get_task_or_404(db, task_id)
|
||||||
|
|
||||||
# 权限校验:只有负责人本人可接收
|
# 权限校验:本人 或 管理员/主管 可操作
|
||||||
if operator_id and task.assignee_id and operator_id != task.assignee_id:
|
_check_permission(task.assignee_id, operator_id, operator_role)
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 校验:只有 PENDING 状态可接收
|
# 校验:只有 PENDING 状态可接收
|
||||||
if task.status != TASK_STATUS_PENDING:
|
if task.status != TASK_STATUS_PENDING:
|
||||||
@ -492,7 +503,8 @@ async def receive_task(
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
async def reject_task(
|
async def reject_task(
|
||||||
db: AsyncSession, task_id: uuid.UUID, request: TaskRejectRequest, operator_id: str | None = None
|
db: AsyncSession, task_id: uuid.UUID, request: TaskRejectRequest, operator_id: str | None = None,
|
||||||
|
operator_role: str | None = None,
|
||||||
) -> TaskResponse:
|
) -> TaskResponse:
|
||||||
"""
|
"""
|
||||||
品质驳回:将当前任务标记为 REJECTED,并自动创建返工任务给上一道工序负责人。
|
品质驳回:将当前任务标记为 REJECTED,并自动创建返工任务给上一道工序负责人。
|
||||||
@ -507,12 +519,8 @@ async def reject_task(
|
|||||||
"""
|
"""
|
||||||
task = await _get_task_or_404(db, task_id)
|
task = await _get_task_or_404(db, task_id)
|
||||||
|
|
||||||
# 权限校验:只有负责人本人可驳回
|
# 权限校验:本人 或 管理员/主管 可驳回
|
||||||
if operator_id and task.assignee_id and operator_id != task.assignee_id:
|
_check_permission(task.assignee_id, operator_id, operator_role)
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 校验:不能重复驳回已完成/已驳回的任务
|
# 校验:不能重复驳回已完成/已驳回的任务
|
||||||
if task.status in (TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED):
|
if task.status in (TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED):
|
||||||
@ -612,7 +620,8 @@ async def reject_task(
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
async def transfer_task(
|
async def transfer_task(
|
||||||
db: AsyncSession, task_id: uuid.UUID, request: TaskTransferRequest, operator_id: str | None = None
|
db: AsyncSession, task_id: uuid.UUID, request: TaskTransferRequest, operator_id: str | None = None,
|
||||||
|
operator_role: str | None = None,
|
||||||
) -> TaskTransferResponse:
|
) -> TaskTransferResponse:
|
||||||
"""
|
"""
|
||||||
完工并裂变转交:
|
完工并裂变转交:
|
||||||
@ -633,6 +642,9 @@ async def transfer_task(
|
|||||||
"""
|
"""
|
||||||
task = await _get_task_or_404(db, task_id)
|
task = await _get_task_or_404(db, task_id)
|
||||||
|
|
||||||
|
# 权限校验:本人 或 管理员/主管 可转交
|
||||||
|
_check_permission(task.assignee_id, operator_id, operator_role)
|
||||||
|
|
||||||
# 校验:不能重复完成
|
# 校验:不能重复完成
|
||||||
if task.status == TASK_STATUS_COMPLETED:
|
if task.status == TASK_STATUS_COMPLETED:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@ -92,15 +92,23 @@ const FlowCard = memo(function FlowCard({
|
|||||||
task,
|
task,
|
||||||
isActive,
|
isActive,
|
||||||
onAction,
|
onAction,
|
||||||
|
currentUser,
|
||||||
|
assigneeName,
|
||||||
}: {
|
}: {
|
||||||
task: TaskResponse;
|
task: TaskResponse;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onAction: (target: ModalTarget) => void;
|
onAction: (target: ModalTarget) => void;
|
||||||
|
currentUser?: { username?: string; role?: string } | null;
|
||||||
|
assigneeName?: string;
|
||||||
}) {
|
}) {
|
||||||
const cfg = getStatusConfig(task.status);
|
const cfg = getStatusConfig(task.status);
|
||||||
const dwell = calcDwell(task.received_at, task.completed_at, task.status);
|
const dwell = calcDwell(task.received_at, task.completed_at, task.status);
|
||||||
const isCompleted = task.status?.toUpperCase() === TASK_STATUS.COMPLETED;
|
const isCompleted = task.status?.toUpperCase() === TASK_STATUS.COMPLETED;
|
||||||
const isArchived = task.status?.toUpperCase() === TASK_STATUS.ARCHIVED;
|
const isArchived = task.status?.toUpperCase() === TASK_STATUS.ARCHIVED;
|
||||||
|
// 权限:本人或管理员/主管可操作
|
||||||
|
const isOwner = !!(currentUser?.username && task.assignee_id === currentUser.username);
|
||||||
|
const isManager = currentUser?.role === "SUPER_ADMIN" || currentUser?.role === "SUPERVISOR";
|
||||||
|
const canOperate = isOwner || isManager;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -137,7 +145,7 @@ const FlowCard = memo(function FlowCard({
|
|||||||
</span>
|
</span>
|
||||||
{task.assignee_id && (
|
{task.assignee_id && (
|
||||||
<span className="text-[10px] text-gray-400 truncate max-w-[80px]">
|
<span className="text-[10px] text-gray-400 truncate max-w-[80px]">
|
||||||
{task.assignee_id}
|
{assigneeName || task.assignee_id}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -160,49 +168,75 @@ const FlowCard = memo(function FlowCard({
|
|||||||
{task.received_at && `接收: ${new Date(task.received_at).toLocaleDateString("zh-CN")}`}
|
{task.received_at && `接收: ${new Date(task.received_at).toLocaleDateString("zh-CN")}`}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* 操作按钮 — 仅激活态显示 */}
|
{/* 操作按钮 — 仅激活态 + 有权限时显示 */}
|
||||||
{isActive && (
|
{isActive && canOperate && (
|
||||||
<div className="mt-2 flex gap-1.5 border-t border-gray-100 pt-2">
|
<div className="mt-2 flex gap-1.5 border-t border-gray-100 pt-2">
|
||||||
{task.status?.toUpperCase() === TASK_STATUS.PENDING && (
|
{task.status?.toUpperCase() === TASK_STATUS.PENDING && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button onClick={() => onAction({ task, action: "receive" })}
|
||||||
onClick={() => onAction({ task, action: "receive" })}
|
className="flex-1 rounded border border-blue-200 bg-blue-50 py-1 text-[10px] font-medium text-blue-600 hover:bg-blue-100">
|
||||||
className="flex-1 rounded border border-blue-200 bg-blue-50 py-1 text-[10px] font-medium text-blue-600 hover:bg-blue-100"
|
|
||||||
>
|
|
||||||
接收
|
接收
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={() => onAction({ task, action: "reject" })}
|
||||||
onClick={() => onAction({ task, action: "reject" })}
|
className="rounded border border-red-200 bg-red-50 px-2 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100">
|
||||||
className="rounded border border-red-200 bg-red-50 px-2 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100"
|
|
||||||
>
|
|
||||||
驳回
|
驳回
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||||
onClick={() => onAction({ task, action: "transfer" })}
|
className="rounded border border-green-200 bg-green-50 px-2 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100">
|
||||||
className="rounded border border-green-200 bg-green-50 px-2 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100"
|
|
||||||
>
|
|
||||||
转交
|
转交
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{task.status?.toUpperCase() === TASK_STATUS.WIP && (
|
{task.status?.toUpperCase() === TASK_STATUS.WIP && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button onClick={() => onAction({ task, action: "reject" })}
|
||||||
onClick={() => onAction({ task, action: "reject" })}
|
className="flex-1 rounded border border-red-200 bg-red-50 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100">
|
||||||
className="flex-1 rounded border border-red-200 bg-red-50 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100"
|
|
||||||
>
|
|
||||||
驳回
|
驳回
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||||
onClick={() => onAction({ task, action: "transfer" })}
|
className="flex-1 rounded border border-green-200 bg-green-50 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100">
|
||||||
className="flex-1 rounded border border-green-200 bg-green-50 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100"
|
|
||||||
>
|
|
||||||
转交
|
转交
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 非本人但管理员可见:干预提示 */}
|
||||||
|
{isActive && !isOwner && isManager && (
|
||||||
|
<div className="mt-2 border-t border-orange-100 pt-2">
|
||||||
|
<p className="text-[9px] text-orange-500 mb-1">⚠ 运维干预模式</p>
|
||||||
|
{task.status?.toUpperCase() === TASK_STATUS.PENDING && (
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<button onClick={() => onAction({ task, action: "reject" })}
|
||||||
|
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||||
|
强制驳回
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||||
|
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||||
|
强制转交
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{task.status?.toUpperCase() === TASK_STATUS.WIP && (
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<button onClick={() => onAction({ task, action: "reject" })}
|
||||||
|
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||||
|
强制驳回
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||||
|
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||||
|
强制转交
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 非本人非管理员:只读提示 */}
|
||||||
|
{isActive && !canOperate && (
|
||||||
|
<div className="mt-2 border-t border-gray-100 pt-2">
|
||||||
|
<p className="text-[9px] text-gray-400 text-center">非当前任务责任人,无法操作</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -212,9 +246,11 @@ const FlowCard = memo(function FlowCard({
|
|||||||
interface TaskFlowViewProps {
|
interface TaskFlowViewProps {
|
||||||
tasks: TaskResponse[];
|
tasks: TaskResponse[];
|
||||||
onAction: (target: ModalTarget) => void;
|
onAction: (target: ModalTarget) => void;
|
||||||
|
currentUser?: { username?: string; role?: string } | null;
|
||||||
|
assigneeNames?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction }: TaskFlowViewProps) {
|
export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, currentUser, assigneeNames }: TaskFlowViewProps) {
|
||||||
// 按 depth 分组的层级数据
|
// 按 depth 分组的层级数据
|
||||||
const depthMap = useMemo(() => {
|
const depthMap = useMemo(() => {
|
||||||
if (!tasks || tasks.length === 0) return new Map<number, TaskResponse[]>();
|
if (!tasks || tasks.length === 0) return new Map<number, TaskResponse[]>();
|
||||||
@ -266,6 +302,8 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction }: Task
|
|||||||
task={task}
|
task={task}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
onAction={onAction}
|
onAction={onAction}
|
||||||
|
currentUser={currentUser}
|
||||||
|
assigneeName={assigneeNames?.[task.assignee_id || ""]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import type { ProductResponse } from "../../types/admin";
|
|||||||
import type { ProductScanResponse, TaskResponse } from "../../types/api";
|
import type { ProductScanResponse, TaskResponse } from "../../types/api";
|
||||||
import { useToast } from "../../components/ui/Toast";
|
import { useToast } from "../../components/ui/Toast";
|
||||||
import { getStatusConfig } from "../../constants/task";
|
import { getStatusConfig } from "../../constants/task";
|
||||||
|
import { useAuth } from "../../contexts/AuthContext";
|
||||||
|
|
||||||
const STATUS_TABS = [
|
const STATUS_TABS = [
|
||||||
{ key: "", label: "全部" },
|
{ key: "", label: "全部" },
|
||||||
@ -33,6 +34,7 @@ interface OrderGroup {
|
|||||||
|
|
||||||
export default function AdminTasksPage() {
|
export default function AdminTasksPage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { user: currentUser } = useAuth();
|
||||||
|
|
||||||
// 搜索 & 筛选
|
// 搜索 & 筛选
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
@ -357,7 +359,14 @@ export default function AdminTasksPage() {
|
|||||||
const productExpanded = expandedProducts.has(p.serial_number);
|
const productExpanded = expandedProducts.has(p.serial_number);
|
||||||
const isTreeLoading = treeLoading[p.serial_number];
|
const isTreeLoading = treeLoading[p.serial_number];
|
||||||
const tree = taskTrees[p.serial_number];
|
const tree = taskTrees[p.serial_number];
|
||||||
const statusCfg = getStatusConfig(p.status);
|
// 综合状态:优先流转树状态,兜底产品状态
|
||||||
|
const treeStatus = tree
|
||||||
|
? (tree.task_tree?.some(t => t.status === "WIP") ? "WIP"
|
||||||
|
: tree.task_tree?.every(t => t.status === "COMPLETED" || t.status === "ARCHIVED") ? "COMPLETED"
|
||||||
|
: tree.task_tree?.[0]?.status)
|
||||||
|
: null;
|
||||||
|
const displayStatus = treeStatus || p.status;
|
||||||
|
const statusCfg = getStatusConfig(displayStatus);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={p.id}>
|
<div key={p.id}>
|
||||||
@ -386,7 +395,7 @@ export default function AdminTasksPage() {
|
|||||||
🏭 仓库
|
🏭 仓库
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
p.current_location_id ?? "—"
|
p.current_location_name || p.current_location_id || "—"
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 text-xs text-gray-400">
|
<div className="col-span-2 text-xs text-gray-400">
|
||||||
@ -420,6 +429,7 @@ export default function AdminTasksPage() {
|
|||||||
<TaskFlowView
|
<TaskFlowView
|
||||||
tasks={tree.task_tree}
|
tasks={tree.task_tree}
|
||||||
onAction={setModalTarget}
|
onAction={setModalTarget}
|
||||||
|
currentUser={currentUser}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
|
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
|
||||||
|
|||||||
Reference in New Issue
Block a user