<script setup lang="ts">
import { type Component, computed, markRaw, onActivated, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import dayjs from 'dayjs';
import type { RouteKey } from '@elegant-router/types';
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
import { RDMS_REQ_PRIORITY_DICT_CODE } from '@/constants/dict';
import {
  fetchApproveOvertimeApplication,
  fetchBatchApproveOvertimeApplication,
  fetchBatchRejectOvertimeApplication,
  fetchChangePersonalItemStatus,
  fetchChangeProjectTaskStatus,
  fetchGetMonthlyReportApprovalPage,
  fetchGetMonthlyReportSignOffPage,
  fetchGetMyTaskPage,
  fetchGetOvertimeApplicationApprovalPage,
  fetchGetPersonalItemDetail,
  fetchGetPersonalItemPage,
  fetchGetProjectReportApprovalPage,
  fetchGetProjectTask,
  fetchGetWeeklyReportApprovalPage,
  fetchPerformanceSheetPage,
  fetchRejectOvertimeApplication
} from '@/service/api';
import { useAuthStore } from '@/store/modules/auth';
import { useRouterPush } from '@/hooks/common/router';
import { useDict } from '@/hooks/business/dict';
import type { WorklogChangedPayload } from '@/views/project/project/execution/shared';
import TaskStatusActionDialog from '@/views/project/project/execution/modules/status-action-dialog.vue';
import TaskInfoReadonly from '@/views/project/project/execution/modules/task-info-readonly.vue';
import TaskWorklogDialog from '@/views/project/project/execution/modules/task-worklog-dialog.vue';
import PersonalItemDetailDialog from '@/views/personal-center/my-item/modules/personal-item-detail-dialog.vue';
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
import PersonalItemStatusActionDialog from '@/views/personal-center/my-item/modules/personal-item-status-action-dialog.vue';
import PerformanceActionDialog from '@/views/personal-center/my-performance/modules/performance-action-dialog.vue';
import PerformanceExcelEditorDrawer from '@/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue';
import { PerformancePermission } from '@/views/personal-center/my-performance/modules/performance-shared';
import OvertimeApplicationBatchDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-batch-detail-dialog.vue';
import OvertimeApplicationDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue';
import WorkReportPrototypePageDialog from '@/views/personal-center/work-report/shared/components/prototype-page-dialog.vue';
import {
  WORK_REPORT_TYPE_LABEL,
  type WorkReportRow,
  type WorkReportType,
  formatPeriod,
  formatWeeklyPeriodLabel
} from '@/views/personal-center/work-report/shared/types';
import {
  type WorkbenchTodoDeadlineFilter,
  type WorkbenchTodoItem,
  type WorkbenchTodoMainTab,
  buildWorkbenchTodoItems,
  filterWorkbenchTodoItemsByCategory,
  filterWorkbenchTodoItemsByDeadline,
  isWorkbenchTodoOverdue,
  sortWorkbenchTodoItemsByPriority
} from '../homepage';
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
import { useWorkbenchWorklogSignal } from '../composables/use-workbench-worklog-signal';
import WorkbenchModuleCard from './workbench-module-card.vue';
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
import IconMdiClipboardCheckOutline from '~icons/mdi/clipboard-check-outline';
import IconMdiClipboardEditOutline from '~icons/mdi/clipboard-edit-outline';
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
import IconMdiPauseCircleOutline from '~icons/mdi/pause-circle-outline';
import IconMdiPlayCircleOutline from '~icons/mdi/play-circle-outline';

type SortKey = 'created' | 'priority' | 'deadline';
type OvertimeApprovalActionType = 'approve' | 'reject';
type PerformanceApprovalActionType = 'confirm' | 'reject';
type ApprovalBizType = 'overtime_application' | 'performance' | WorkReportType;
type WorkReportDetailScene = 'approval' | 'sign-off';

defineOptions({ name: 'WorkbenchTodoPanel' });

interface Props {
  editing?: boolean;
}

withDefaults(defineProps<Props>(), { editing: false });

defineEmits<{
  (e: 'hide'): void;
}>();

const router = useRouter();
const { routerPushByKey } = useRouterPush();

const authStore = useAuthStore();
const currentUserId = computed(() => authStore.userInfo.userId || '');
const buttonPermissions = computed(() => new Set(authStore.userInfo.buttons || []));

function hasButtonPermission(permission: string) {
  return buttonPermissions.value.has(permission);
}

// 工时填报在工作台内弹层完成（不切路由），需广播给「我的工时」widget 重拉
const { notify: notifyWorklogChanged } = useWorkbenchWorklogSignal();

const { loading, refresh } = useWorkbenchRefresh(async () => {
  await Promise.all([
    loadMyTaskItems(),
    loadPersonalTodoItems(),
    loadOvertimeApprovalItems(),
    loadPerformanceApprovalItems(),
    loadWorkReportApprovalItems(),
    loadMonthlySignOffItems()
  ]);
});

const PAGE_SIZE = 5;

const activeTab = ref<WorkbenchTodoMainTab>('all');
const activeDeadlineFilter = ref<WorkbenchTodoDeadlineFilter>(null);
const activeApprovalBizType = ref<ApprovalBizType>('weekly');
const activeSort = ref<SortKey>('deadline');
const currentPage = ref(1);

const sortOptions = computed<Array<{ key: SortKey; label: string }>>(() => {
  const base: Array<{ key: SortKey; label: string }> = [
    { key: 'deadline', label: '截止时间' },
    { key: 'created', label: '创建时间' }
  ];
  if (activeTab.value === 'task') {
    base.push({ key: 'priority', label: '优先级' });
  }
  return base;
});

const mainTabs: Array<{ key: WorkbenchTodoMainTab; label: string }> = [
  { key: 'all', label: '全部' },
  { key: 'task', label: '任务' },
  { key: 'ticket', label: '工单' },
  { key: 'personal', label: '我的事项' },
  { key: 'approval', label: '待审批' },
  { key: 'confirm', label: '待确认' }
];

const deadlineFilters: Array<{ key: Exclude<WorkbenchTodoDeadlineFilter, null>; label: string }> = [
  { key: 'overdue', label: '已逾期' },
  { key: 'today', label: '今日到期' },
  { key: 'week', label: '本周到期' }
];

const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [
  { key: 'weekly', label: '周报' },
  { key: 'monthly', label: '月报' },
  { key: 'project', label: '研发简报' },
  { key: 'overtime_application', label: '业务学习' }
];
const confirmBizTabs: Array<{ key: Extract<ApprovalBizType, 'performance' | 'monthly'>; label: string }> = [
  { key: 'performance', label: '绩效表' },
  { key: 'monthly', label: '月报' }
];

const hasWorkReportApprovePermission = computed(() => hasButtonPermission('project:work-report:approve'));
const hasOvertimeApprovePermission = computed(() => hasButtonPermission('project:overtime-application:approve'));
const hasPerformanceApprovePermission = computed(
  () =>
    hasButtonPermission(PerformancePermission.SheetConfirm) && hasButtonPermission(PerformancePermission.SheetReject)
);
const hasMonthlySignOffQueryPermission = computed(() => hasButtonPermission('project:work-report:sign-off-query'));
const visibleMainTabs = computed(() =>
  mainTabs.filter(tab => {
    if (tab.key === 'confirm') {
      return hasPerformanceApprovePermission.value || hasMonthlySignOffQueryPermission.value;
    }
    return true;
  })
);
const visibleApprovalBizTabs = computed(() =>
  approvalBizTabs.filter(tab => {
    if (tab.key === 'performance') {
      return hasPerformanceApprovePermission.value;
    }

    if (tab.key === 'overtime_application') {
      return hasOvertimeApprovePermission.value;
    }

    return hasWorkReportApprovePermission.value;
  })
);
const visibleConfirmBizTabs = computed(() =>
  confirmBizTabs.filter(tab => {
    if (tab.key === 'performance') {
      return hasPerformanceApprovePermission.value;
    }

    return hasMonthlySignOffQueryPermission.value;
  })
);

const myTaskItems = ref<WorkbenchTodoItem[]>([]);
// 保留任务原始行，供操作图标按 availableActions 渲染并取 projectId / executionId 调状态变更接口
const myTaskRows = ref<Api.Project.MyTaskItem[]>([]);
const personalTodoItems = ref<WorkbenchTodoItem[]>([]);
// 保留我的事项原始行，供操作图标（详情/填报/完成）按 availableActions 渲染并回传给弹层
const personalItemRows = ref<Api.PersonalItem.PersonalItem[]>([]);
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
const performanceApprovalItems = ref<WorkbenchTodoItem[]>([]);
const performanceApprovalRows = ref<Api.Performance.Sheet.Sheet[]>([]);
const monthlySignOffItems = ref<WorkbenchTodoItem[]>([]);
const monthlySignOffRows = ref<Api.WorkReport.Monthly.MonthlyReport[]>([]);
const workReportApprovalItems = ref<WorkbenchTodoItem[]>([]);
const weeklyApprovalRows = ref<Api.WorkReport.Weekly.WeeklyReport[]>([]);
const monthlyApprovalRows = ref<Api.WorkReport.Monthly.MonthlyReport[]>([]);
const projectApprovalRows = ref<Api.WorkReport.Project.ProjectReport[]>([]);
// 工单 tab 等工单业务上线，当前为空态
const mergedItems = computed(() => [
  ...myTaskItems.value,
  ...personalTodoItems.value,
  ...overtimeApprovalItems.value,
  ...performanceApprovalItems.value,
  ...monthlySignOffItems.value,
  ...workReportApprovalItems.value
]);

// 我的事项操作弹层：详情用 view 模式、新增用 add 模式，共用同一实例
const personalOperateVisible = ref(false);
const personalOperateType = ref<'add' | 'view'>('add');
const personalOperateRow = ref<Api.PersonalItem.PersonalItem | null>(null);
// 我的事项填报：复用详情弹层的 worklog tab
const personalDetailVisible = ref(false);
const personalDetailRow = ref<Api.PersonalItem.PersonalItem | null>(null);
// 我的事项完成：复用状态动作弹层收集原因 + 二次确认
const personalStatusVisible = ref(false);
const personalStatusRow = ref<Api.PersonalItem.PersonalItem | null>(null);
const personalStatusAction = ref<Api.PersonalItem.PersonalItemLifecycleAction | null>(null);
// 任务生命周期动作：复用任务工作区的状态动作弹层收集原因
const taskStatusVisible = ref(false);
const taskStatusRow = ref<Api.Project.MyTaskItem | null>(null);
const taskStatusAction = ref<Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode> | null>(null);
const taskPreviewMap = ref<Record<string, Api.Project.ProjectTask>>({});
const taskPreviewLoadingIds = ref<string[]>([]);
// 任务填报工时：复用任务工作区的工时弹层；弹层需要完整任务详情，点击时按需拉取
const taskWorklogVisible = ref(false);
const taskWorklogTask = ref<Api.Project.ProjectTask | null>(null);
const overtimeDetailVisible = ref(false);
const overtimeActionSubmitting = ref(false);
const currentOvertimeApplication = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
const batchDetailVisible = ref(false);
const batchSubmitting = ref(false);
const workReportDetailVisible = ref(false);
const currentWorkReport = ref<WorkReportRow | null>(null);
const currentWorkReportType = ref<WorkReportType>('weekly');
const currentWorkReportScene = ref<WorkReportDetailScene>('approval');
const performanceDetailVisible = ref(false);
const performanceActionVisible = ref(false);
const currentPerformanceSheet = ref<Api.Performance.Sheet.Sheet | null>(null);
const currentPerformanceActionType = ref<PerformanceApprovalActionType>('confirm');

// 批量审批选中状态（存原始业务学习 id，避免映射转换）
const selectedOvertimeIds = ref<Set<string>>(new Set());

const APPROVAL_ENTRY_ICON = markRaw(IconMdiClipboardCheckOutline);

function getApprovalCategoryLabel(bizType: ApprovalBizType) {
  if (bizType === 'weekly') return '周报';
  if (bizType === 'monthly') return '月报';
  if (bizType === 'project') return '研发简报';
  if (bizType === 'performance') return '绩效表';
  if (bizType === 'overtime_application') return '业务学习';
  return '待审批';
}

const PERSONAL_ACTION_ICONS = {
  detail: markRaw(IconMdiEyeOutline),
  worklog: markRaw(IconMdiClipboardEditOutline),
  complete: markRaw(IconMdiCheckCircleOutline)
};

// auto_start 是系统自动动作，接口不返回；无图标的未知动作不渲染按钮
const TASK_ACTION_ICONS: Partial<Record<Api.Project.ProjectTaskActionCode, Component>> = {
  pause: markRaw(IconMdiPauseCircleOutline),
  resume: markRaw(IconMdiPlayCircleOutline),
  complete: markRaw(IconMdiCheckCircleOutline),
  cancel: markRaw(IconMdiCloseCircleOutline)
};

function normalizeTodoDeadline(deadline: string | null) {
  if (!deadline) return null;

  if (/^\d{4}-\d{2}-\d{2}$/.test(deadline)) {
    return dayjs(deadline).endOf('day').format('YYYY-MM-DD HH:mm');
  }

  return deadline;
}

function getTaskActionButtonType(code: Api.Project.ProjectTaskActionCode) {
  if (code === 'complete') return 'success' as const;
  if (code === 'cancel') return 'danger' as const;
  return 'primary' as const;
}

function getTodoProgress(item: WorkbenchTodoItem) {
  if (typeof item.progressRate !== 'number' || !Number.isFinite(item.progressRate)) {
    return 0;
  }

  return Math.min(100, Math.max(0, Math.round(item.progressRate)));
}

function shouldShowTodoProgress(item: WorkbenchTodoItem) {
  return (item.category === 'task' || item.category === 'personal') && typeof item.progressRate === 'number';
}

function handleOpenAdd() {
  personalOperateType.value = 'add';
  personalOperateRow.value = null;
  personalOperateVisible.value = true;
}

async function handleAddSubmitted() {
  activeTab.value = 'personal';
  activeDeadlineFilter.value = null;
  await loadPersonalTodoItems();
}

function findPersonalItemRow(item: WorkbenchTodoItem) {
  return personalItemRows.value.find(row => `personal-${row.id}` === item.id) || null;
}

function getPersonalCompleteAction(row: Api.PersonalItem.PersonalItem) {
  return row.availableActions?.find(action => action.actionCode === 'complete') || null;
}

// 仅当我的事项当前可完成（availableActions 含 complete）时才渲染完成图标
function canCompletePersonalItem(item: WorkbenchTodoItem) {
  const row = findPersonalItemRow(item);
  return Boolean(row && getPersonalCompleteAction(row));
}

// 详情：复用我的事项操作弹层的 view 只读模式
function openPersonalDetail(item: WorkbenchTodoItem) {
  const row = findPersonalItemRow(item);
  if (!row) return;

  personalOperateType.value = 'view';
  personalOperateRow.value = row;
  personalOperateVisible.value = true;
}

// 填报：拉最新详情后打开详情弹层的 worklog tab
async function openPersonalWorklog(item: WorkbenchTodoItem) {
  const row = findPersonalItemRow(item);
  if (!row) return;

  const { error, data } = await fetchGetPersonalItemDetail(row.id);
  personalDetailRow.value = error || !data ? row : data;
  personalDetailVisible.value = true;
}

// 完成：走状态动作弹层（二次确认 + 按 needReason 收集原因）
function openPersonalComplete(item: WorkbenchTodoItem) {
  const row = findPersonalItemRow(item);
  if (!row) return;

  const completeAction = getPersonalCompleteAction(row);
  if (!completeAction) return;

  personalStatusRow.value = row;
  personalStatusAction.value = completeAction;
  personalStatusVisible.value = true;
}

async function handlePersonalStatusSubmit(reason: string | null) {
  if (!personalStatusRow.value || !personalStatusAction.value) return;

  const { error } = await fetchChangePersonalItemStatus(personalStatusRow.value.id, {
    actionCode: personalStatusAction.value.actionCode,
    reason
  });

  if (error) return;

  personalStatusVisible.value = false;
  window.$message?.success(`${personalStatusAction.value.actionName}成功`);
  await loadPersonalTodoItems();
}

async function handlePersonalWorklogChanged() {
  notifyWorklogChanged();
  await loadPersonalTodoItems();
}

function findMyTaskRow(item: WorkbenchTodoItem) {
  return myTaskRows.value.find(row => `task-${row.id}` === item.id) || null;
}

function canPreviewTaskInfo(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  return Boolean(item.category === 'task' && row?.projectId && row.executionId);
}

function getTaskPreviewTask(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  if (!row) return null;
  return taskPreviewMap.value[row.id] || null;
}

function getTaskPreviewTaskOptions(item: WorkbenchTodoItem) {
  const task = getTaskPreviewTask(item);
  return task ? [task] : [];
}

function isTaskPreviewLoading(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  if (!row) return false;
  return taskPreviewLoadingIds.value.includes(row.id);
}

async function handleTaskPreviewShow(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  if (!row?.executionId) return;

  if (taskPreviewMap.value[row.id] || taskPreviewLoadingIds.value.includes(row.id)) {
    return;
  }

  taskPreviewLoadingIds.value = [...taskPreviewLoadingIds.value, row.id];

  const { error, data } = await fetchGetProjectTask(row.projectId, row.executionId, row.id);

  taskPreviewLoadingIds.value = taskPreviewLoadingIds.value.filter(id => id !== row.id);

  if (error || !data) {
    return;
  }

  taskPreviewMap.value = {
    ...taskPreviewMap.value,
    [row.id]: data
  };
}

// 状态变更接口挂在执行路径下，未挂执行的任务不渲染动作按钮
function getTaskActions(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  if (!row?.executionId) return [];
  return row.availableActions.filter(action => Boolean(TASK_ACTION_ICONS[action.actionCode]));
}

// 填报工时同样依赖执行路径（工时接口挂在 project/execution/task 下）
function canReportTaskWorklog(item: WorkbenchTodoItem) {
  return Boolean(findMyTaskRow(item)?.executionId);
}

async function refreshTaskWorklogTaskDetail(
  target?:
    | Pick<Api.Project.ProjectTask, 'id' | 'projectId' | 'executionId'>
    | Pick<Api.Project.MyTaskItem, 'id' | 'projectId' | 'executionId'>
    | null
) {
  const task = target ?? taskWorklogTask.value;
  if (!task?.executionId) return;

  const { error, data } = await fetchGetProjectTask(task.projectId, task.executionId, task.id);
  if (error || !data) return;

  if (taskWorklogTask.value?.id === data.id) {
    taskWorklogTask.value = data;
  }
}

// 填报：工时弹层需要完整任务详情（负责人/状态/日期），按需拉一次详情再打开
async function openTaskWorklog(item: WorkbenchTodoItem) {
  const row = findMyTaskRow(item);
  if (!row?.executionId) return;

  const { error, data } = await fetchGetProjectTask(row.projectId, row.executionId, row.id);
  if (error || !data) return;

  taskWorklogTask.value = data;
  taskWorklogVisible.value = true;
}

// 填报会联动任务进度/状态（如 auto_start），变更后刷新任务列表
async function handleTaskWorklogChanged(payload: WorklogChangedPayload) {
  notifyWorklogChanged();
  await loadMyTaskItems();
  if (taskWorklogTask.value?.id === payload.taskId) {
    await refreshTaskWorklogTaskDetail();
  }

  // 与任务工作区联动一致：进度填到 100 且我是任务负责人时提示完成（仅单任务，不做级联）
  if (payload.mode === 'delete' || payload.progressRate !== 100) return;

  const task = taskWorklogTask.value;
  if (!task || task.id !== payload.taskId || task.ownerId !== currentUserId.value) return;

  // 以刷新后的行为准：后端按进度/负责人口径返回了 complete 才提示
  const row = myTaskRows.value.find(item => item.id === payload.taskId);
  const completeAction = row?.availableActions.find(action => action.actionCode === 'complete');
  if (!row || !completeAction) return;

  taskStatusRow.value = row;
  taskStatusAction.value = completeAction;

  try {
    await window.$messageBox?.confirm('任务进度已达 100%，是否完成当前任务？', '完成确认', {
      confirmButtonText: '完成任务',
      cancelButtonText: '仅保留工时',
      type: 'info'
    });
  } catch {
    return;
  }

  await submitTaskStatusChange(null);
}

const taskStatusActionTitle = computed(() =>
  taskStatusAction.value ? `任务状态变更：${taskStatusAction.value.actionName}` : '任务状态变更'
);

async function handleTaskAction(
  item: WorkbenchTodoItem,
  action: Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode>
) {
  const row = findMyTaskRow(item);
  if (!row?.executionId) return;

  taskStatusRow.value = row;
  taskStatusAction.value = action;

  // 完成动作：二次确认后直接提交（与任务工作区同口径；有未完成子任务时后端会拒绝，按业务错误提示）
  if (action.actionCode === 'complete') {
    try {
      await window.$messageBox?.confirm(`确定要完成任务“${row.taskTitle}”吗？`, '完成确认', {
        confirmButtonText: '完成任务',
        cancelButtonText: '取消',
        type: 'warning'
      });
    } catch {
      return;
    }
    await submitTaskStatusChange(null);
    return;
  }

  // 其他非必填原因的动作（暂停/恢复）直接提交，不弹原因弹层
  if (!action.needReason) {
    await submitTaskStatusChange(null);
    return;
  }

  taskStatusVisible.value = true;
}

async function submitTaskStatusChange(reason: string | null) {
  const row = taskStatusRow.value;
  const action = taskStatusAction.value;
  if (!row?.executionId || !action) return;

  const { error } = await fetchChangeProjectTaskStatus(row.projectId, row.executionId, {
    taskId: row.id,
    data: { actionCode: action.actionCode, reason }
  });

  if (error) return;

  taskStatusVisible.value = false;
  window.$message?.success(`${action.actionName}成功`);
  await loadMyTaskItems();
  if (taskWorklogTask.value?.id === row.id) {
    await refreshTaskWorklogTaskDetail(row);
  }
}

const tabCounts = computed(() => {
  const counts: Record<WorkbenchTodoMainTab, number> = {
    all: mergedItems.value.length,
    task: 0,
    ticket: 0,
    personal: 0,
    approval: 0,
    confirm: 0
  };
  mergedItems.value.forEach(item => {
    counts[item.category] += 1;
  });
  return counts;
});

const tabOverdueCount = computed(() => {
  const map: Record<WorkbenchTodoMainTab, number> = {
    all: 0,
    task: 0,
    ticket: 0,
    personal: 0,
    approval: 0,
    confirm: 0
  };
  mergedItems.value.forEach(item => {
    if (!isWorkbenchTodoOverdue(item)) return;
    map.all += 1;
    map[item.category] += 1;
  });
  return map;
});

const itemsInTab = computed(() => filterWorkbenchTodoItemsByCategory(mergedItems.value, activeTab.value));

const filteredItems = computed(() => {
  if (activeTab.value === 'approval') {
    return itemsInTab.value.filter(item => item.approvalBizType === activeApprovalBizType.value);
  }

  if (activeTab.value === 'confirm') {
    return itemsInTab.value.filter(item => item.approvalBizType === activeApprovalBizType.value);
  }

  return filterWorkbenchTodoItemsByDeadline(itemsInTab.value, activeDeadlineFilter.value);
});

const approvalBizTabCounts = computed(() => {
  const counts: Record<ApprovalBizType, number> = {
    overtime_application: 0,
    performance: 0,
    weekly: 0,
    monthly: 0,
    project: 0
  };

  itemsInTab.value.forEach(item => {
    if (item.approvalBizType === 'overtime_application') {
      counts.overtime_application += 1;
      return;
    }

    if (item.approvalBizType === 'performance') {
      counts.performance += 1;
      return;
    }

    if (isWorkReportApprovalBizType(item.approvalBizType)) {
      counts[item.approvalBizType] += 1;
    }
  });

  return counts;
});

const sortedItems = computed(() => {
  const base = filteredItems.value;
  if (activeSort.value === 'priority') {
    return sortWorkbenchTodoItemsByPriority(base, 'desc');
  }
  if (activeSort.value === 'deadline') {
    return [...base].sort((left, right) => {
      const leftValue = left.remainingDays === null ? Number.POSITIVE_INFINITY : left.remainingDays;
      const rightValue = right.remainingDays === null ? Number.POSITIVE_INFINITY : right.remainingDays;
      return leftValue - rightValue;
    });
  }
  return base;
});

const currentSortLabel = computed(
  () => sortOptions.value.find(option => option.key === activeSort.value)?.label ?? '排序'
);

const pagedItems = computed(() => {
  const start = (currentPage.value - 1) * PAGE_SIZE;
  return sortedItems.value.slice(start, start + PAGE_SIZE);
});

watch([activeTab, activeDeadlineFilter, activeSort], () => {
  currentPage.value = 1;
});

watch(
  visibleApprovalBizTabs,
  tabs => {
    if (activeTab.value !== 'approval') return;
    if (!tabs.length) return;
    if (!tabs.some(tab => tab.key === activeApprovalBizType.value)) {
      activeApprovalBizType.value = tabs[0].key;
    }
  },
  { immediate: true }
);

watch(
  visibleConfirmBizTabs,
  tabs => {
    if (activeTab.value !== 'confirm') return;
    if (!tabs.length) return;
    if (!tabs.some(tab => tab.key === activeApprovalBizType.value)) {
      activeApprovalBizType.value = tabs[0].key;
    }
  },
  { immediate: true }
);

watch(
  visibleMainTabs,
  tabs => {
    if (!tabs.some(tab => tab.key === activeTab.value)) {
      activeTab.value = tabs[0]?.key ?? 'all';
    }
  },
  { immediate: true }
);

function handleSelectTab(key: WorkbenchTodoMainTab) {
  if (key === 'confirm' && !visibleConfirmBizTabs.value.length) return;
  if (activeTab.value === key) return;
  activeTab.value = key;
  activeDeadlineFilter.value = null;
  if (key === 'confirm') {
    activeApprovalBizType.value = visibleConfirmBizTabs.value[0]?.key ?? 'performance';
  } else if (key === 'approval' && activeApprovalBizType.value === 'performance') {
    activeApprovalBizType.value = visibleApprovalBizTabs.value[0]?.key ?? 'weekly';
  }
  if (key !== 'task' && activeSort.value === 'priority') {
    activeSort.value = 'deadline';
  }
}

function handleSelectDeadlineFilter(key: Exclude<WorkbenchTodoDeadlineFilter, null>) {
  activeDeadlineFilter.value = activeDeadlineFilter.value === key ? null : key;
}

function handleSelectApprovalBizType(key: ApprovalBizType) {
  activeApprovalBizType.value = key;
}

function handleSelectSort(key: SortKey) {
  activeSort.value = key;
}

function handleClickItem(item: WorkbenchTodoItem) {
  if (item.approvalBizType === 'overtime_application') {
    openOvertimeDetail(item);
    return;
  }

  if (item.approvalBizType === 'performance') {
    openPerformanceDetail(item);
    return;
  }

  if (isWorkReportApprovalBizType(item.approvalBizType)) {
    openWorkReportDetail(item);
    return;
  }

  // 任务条目：带项目对象上下文跳进该项目的任务导航（执行池页）
  if (item.category === 'task' && item.projectId) {
    router.push({
      path: '/project/project/execution',
      query: { [OBJECT_CONTEXT_QUERY_KEY]: item.projectId }
    });
    return;
  }

  if (!item.routeKey) return;
  routerPushByKey(item.routeKey as RouteKey);
}

function isWorkReportApprovalBizType(value?: string): value is WorkReportType {
  return value === 'weekly' || value === 'monthly' || value === 'project';
}

function findOvertimeApprovalRow(item: WorkbenchTodoItem) {
  if (!item.approvalBizId) {
    return null;
  }

  return overtimeApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
}

function findPerformanceApprovalRow(item: WorkbenchTodoItem) {
  if (!item.approvalBizId) {
    return null;
  }

  return performanceApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
}

function openOvertimeDetail(item: WorkbenchTodoItem) {
  const row = findOvertimeApprovalRow(item);
  if (!row) return;

  currentOvertimeApplication.value = row;
  overtimeDetailVisible.value = true;
}

function openPerformanceDetail(item: WorkbenchTodoItem) {
  const row = findPerformanceApprovalRow(item);
  if (!row) return;

  currentPerformanceSheet.value = row;
  performanceDetailVisible.value = true;
}

function openCurrentPerformanceAction(actionType: PerformanceApprovalActionType) {
  if (!currentPerformanceSheet.value) return;

  currentPerformanceActionType.value = actionType;
  performanceActionVisible.value = true;
}

function findWorkReportApprovalRow(item: WorkbenchTodoItem) {
  if (!item.approvalBizId || !isWorkReportApprovalBizType(item.approvalBizType)) {
    return null;
  }

  if (item.approvalBizType === 'weekly') {
    return weeklyApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
  }

  if (item.approvalBizType === 'monthly') {
    const sourceRows = item.category === 'confirm' ? monthlySignOffRows.value : monthlyApprovalRows.value;
    return sourceRows.find(row => row.id === item.approvalBizId) || null;
  }

  return projectApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
}

function openWorkReportDetail(item: WorkbenchTodoItem) {
  const row = findWorkReportApprovalRow(item);
  if (!row || !isWorkReportApprovalBizType(item.approvalBizType)) return;

  currentWorkReport.value = row;
  currentWorkReportType.value = item.approvalBizType;
  currentWorkReportScene.value = item.category === 'confirm' ? 'sign-off' : 'approval';
  workReportDetailVisible.value = true;
}

async function handleOvertimeActionSubmit(payload: { actionType: OvertimeApprovalActionType; reason: string | null }) {
  if (!currentOvertimeApplication.value) {
    return;
  }

  overtimeActionSubmitting.value = true;
  const result =
    payload.actionType === 'approve'
      ? await fetchApproveOvertimeApplication(currentOvertimeApplication.value.id, { reason: payload.reason })
      : await fetchRejectOvertimeApplication(currentOvertimeApplication.value.id, { reason: payload.reason });
  overtimeActionSubmitting.value = false;

  if (result.error) {
    return;
  }

  overtimeDetailVisible.value = false;
  window.$message?.success(payload.actionType === 'approve' ? '业务学习已通过' : '业务学习已退回');
  await loadOvertimeApprovalItems();
}

async function handleWorkReportSubmitted() {
  workReportDetailVisible.value = false;
  await Promise.all([loadWorkReportApprovalItems(), loadMonthlySignOffItems()]);
}

async function handlePerformanceActionSubmitted() {
  performanceActionVisible.value = false;
  performanceDetailVisible.value = false;
  await loadPerformanceApprovalItems();
}

// 优先级角标用字典 label 原样回显（rdms_req_priority：P0~P3），不翻译成高/中/低
const { getLabel: getPriorityLabel } = useDict(RDMS_REQ_PRIORITY_DICT_CODE);

// 任务优先级字典 value（"0" P0 ~ "3" P3，数字越小越高）映射待办优先级（仅用于排序与高亮权重）
function mapTaskTodoPriority(priority: string) {
  if (priority === '0') return 'high' as const;
  if (priority === '1') return 'mid' as const;
  return 'low' as const;
}

// 我的任务：跨项目单接口聚合（负责/协办并集、只返回非终态，过滤排序、进度与可执行动作均由后端完成）
async function loadMyTaskItems() {
  const { error, data } = await fetchGetMyTaskPage({ pageNo: 1, pageSize: -1 });

  taskPreviewMap.value = {};
  taskPreviewLoadingIds.value = [];

  if (error || !data) {
    myTaskRows.value = [];
    myTaskItems.value = [];
    return;
  }

  myTaskRows.value = data.list;
  myTaskItems.value = buildWorkbenchTodoItems(
    data.list.map(task => ({
      id: `task-${task.id}`,
      category: 'task' as const,
      title: task.taskTitle,
      createdTime: task.createTime,
      deadline: normalizeTodoDeadline(task.plannedEndDate),
      source: task.projectName,
      progressRate: task.progressRate,
      priority: mapTaskTodoPriority(task.priority),
      priorityLabel: getPriorityLabel(task.priority) || task.priority,
      projectId: task.projectId
    }))
  );
}

// 待办口径：未到终态的我的事项（pending / active / paused），terminal 态（completed / cancelled）不进待办
const PERSONAL_TODO_STATUSES: Api.PersonalItem.PersonalItemStatusCode[] = ['pending', 'active', 'paused'];

async function loadPersonalTodoItems() {
  const { error, data } = await fetchGetPersonalItemPage({ pageNo: 1, pageSize: 100 });

  if (error || !data) {
    personalItemRows.value = [];
    personalTodoItems.value = [];
    return;
  }

  const rows = data.list.filter(item => PERSONAL_TODO_STATUSES.includes(item.statusCode));
  personalItemRows.value = rows;
  personalTodoItems.value = buildWorkbenchTodoItems(
    rows.map(item => ({
      id: `personal-${item.id}`,
      category: 'personal',
      title: item.taskTitle,
      createdTime: item.createTime,
      deadline: normalizeTodoDeadline(item.plannedEndDate),
      source: '我的事项',
      progressRate: item.progressRate,
      routeKey: 'personal-center_my-item'
    }))
  );
}

// 批量审批选中状态管理
function isOvertimeItemSelected(item: WorkbenchTodoItem) {
  return item.approvalBizId ? selectedOvertimeIds.value.has(item.approvalBizId) : false;
}

function toggleOvertimeItem(item: WorkbenchTodoItem, checked: boolean) {
  if (!item.approvalBizId) return;
  if (checked) {
    selectedOvertimeIds.value.add(item.approvalBizId);
  } else {
    selectedOvertimeIds.value.delete(item.approvalBizId);
  }
}

function toggleSelectAllOvertimeItems(checked: boolean) {
  if (checked) {
    overtimeApprovalItems.value.forEach(item => {
      if (item.approvalBizId) selectedOvertimeIds.value.add(item.approvalBizId);
    });
  } else {
    selectedOvertimeIds.value.clear();
  }
}

function clearOvertimeSelection() {
  selectedOvertimeIds.value.clear();
}

// 切换审批子页签时清空选中
watch(activeApprovalBizType, () => {
  clearOvertimeSelection();
});

// 当前页业务学习是否全部选中（用于全选复选框状态）
const allOvertimeItemsSelected = computed(() => {
  const items = overtimeApprovalItems.value;
  if (items.length === 0) return false;
  return items.every(item => item.approvalBizId && selectedOvertimeIds.value.has(item.approvalBizId));
});

// 当前页是否有部分选中
const someOvertimeItemsSelected = computed(() => {
  return overtimeApprovalItems.value.some(
    item => item.approvalBizId && selectedOvertimeIds.value.has(item.approvalBizId)
  );
});

// 批量审批选中的 id 数组（用于传给批量详情弹窗）
const batchSelectedIds = computed(() => Array.from(selectedOvertimeIds.value));

// 打开批量审批详情弹窗
function handleBatchReview() {
  batchDetailVisible.value = true;
}

// 批量审批提交（对所有选中项执行批量 API）
async function handleBatchActionSubmit(payload: { actionType: OvertimeApprovalActionType; reason: string | null }) {
  const ids = Array.from(selectedOvertimeIds.value);
  if (ids.length === 0) return;

  const fn =
    payload.actionType === 'approve' ? fetchBatchApproveOvertimeApplication : fetchBatchRejectOvertimeApplication;

  batchSubmitting.value = true;
  const { error, data } = await fn({ ids, reason: payload.reason });
  batchSubmitting.value = false;

  if (error || !data) return;

  batchDetailVisible.value = false;
  clearOvertimeSelection();

  if (data.failCount > 0) {
    window.$message?.warning(`成功 ${data.successCount} 条，失败 ${data.failCount} 条`);
  } else {
    window.$message?.success(`已批量处理 ${data.successCount} 条`);
  }

  await loadOvertimeApprovalItems();
}

async function loadOvertimeApprovalItems() {
  if (!hasOvertimeApprovePermission.value) {
    overtimeApprovalRows.value = [];
    overtimeApprovalItems.value = [];
    return;
  }

  const { error, data } = await fetchGetOvertimeApplicationApprovalPage({
    pageNo: 1,
    pageSize: 20,
    statusCode: 'pending',
    keyword: undefined,
    applicantName: undefined,
    approverId: undefined,
    approverName: undefined,
    overtimeDate: undefined,
    createTime: undefined
  });

  if (error || !data) {
    overtimeApprovalRows.value = [];
    overtimeApprovalItems.value = [];
    return;
  }

  overtimeApprovalRows.value = data.list;
  overtimeApprovalItems.value = buildWorkbenchTodoItems(
    data.list.map(item => ({
      id: `overtime-application-${item.id}`,
      category: 'approval',
      categoryLabel: '业务学习',
      title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)} 月学习 ${item.overtimeDuration} 申请待审批`,
      createdTime: item.submitTime || item.createTime,
      deadline: item.submitTime || item.createTime,
      source: `业务学习 · ${item.applicantName}`,
      priority: 'mid',
      approvalBizType: 'overtime_application',
      approvalBizId: item.id
    }))
  );
}

async function loadPerformanceApprovalItems() {
  if (!hasPerformanceApprovePermission.value) {
    performanceApprovalRows.value = [];
    performanceApprovalItems.value = [];
    return;
  }

  const { error, data } = await fetchPerformanceSheetPage({
    pageNo: 1,
    pageSize: 20,
    statusCode: 'sent',
    signOffStatus: 'pending_employee_confirm'
  });

  if (error || !data) {
    performanceApprovalRows.value = [];
    performanceApprovalItems.value = [];
    return;
  }

  performanceApprovalRows.value = data.list;
  performanceApprovalItems.value = buildWorkbenchTodoItems(
    data.list.map(item => ({
      id: `performance-${item.id}`,
      category: 'confirm',
      categoryLabel: '绩效表',
      title: `${item.periodMonth} 绩效表待确认`,
      createdTime: item.sentTime || item.createTime || item.updateTime || '',
      deadline: item.sentTime || item.createTime || item.updateTime || null,
      source: `绩效表 · ${item.managerName || '--'}`,
      priority: 'mid',
      approvalBizType: 'performance',
      approvalBizId: item.id
    }))
  );
}

async function loadMonthlySignOffItems() {
  if (!hasMonthlySignOffQueryPermission.value) {
    monthlySignOffRows.value = [];
    monthlySignOffItems.value = [];
    return;
  }

  const { error, data } = await fetchGetMonthlyReportSignOffPage({
    pageNo: 1,
    pageSize: 20,
    statusCode: 'approved',
    signOffStatus: 'pending_employee_confirm',
    keyword: undefined,
    periodStartDate: undefined,
    submitTime: undefined,
    supervisorName: undefined
  });

  if (error || !data) {
    monthlySignOffRows.value = [];
    monthlySignOffItems.value = [];
    return;
  }

  monthlySignOffRows.value = data.list;
  monthlySignOffItems.value = buildWorkbenchTodoItems(
    data.list.map(item => ({
      id: `monthly-sign-off-${item.id}`,
      category: 'confirm',
      categoryLabel: '月报',
      title: `${WORK_REPORT_TYPE_LABEL.monthly} · ${formatPeriod(item)} 待确认`,
      createdTime: item.approvalTime || item.submitTime || item.createTime || '',
      deadline: item.approvalTime || item.submitTime || item.createTime || null,
      source: `${WORK_REPORT_TYPE_LABEL.monthly} · ${item.reporterName}`,
      priority: 'mid',
      approvalBizType: 'monthly',
      approvalBizId: item.id
    }))
  );
}

function buildWorkReportApprovalItems<T extends WorkReportRow>(
  bizType: WorkReportType,
  rows: T[]
): WorkbenchTodoItem[] {
  const reportTypeLabel = WORK_REPORT_TYPE_LABEL[bizType];

  return buildWorkbenchTodoItems(
    rows.map(item => ({
      id: `${bizType}-${item.id}`,
      category: 'approval',
      categoryLabel: getApprovalCategoryLabel(bizType),
      title: `${reportTypeLabel} · ${bizType === 'weekly' ? formatWeeklyPeriodLabel(item) : formatPeriod(item)} 待审批`,
      createdTime: item.submitTime || item.createTime || '',
      deadline: item.submitTime || item.createTime || null,
      source: `${reportTypeLabel} · ${'projectName' in item ? item.projectName : item.reporterName}`,
      priority: 'mid',
      approvalBizType: bizType,
      approvalBizId: item.id
    }))
  );
}

async function loadWorkReportApprovalItems() {
  if (!hasWorkReportApprovePermission.value) {
    weeklyApprovalRows.value = [];
    monthlyApprovalRows.value = [];
    projectApprovalRows.value = [];
    workReportApprovalItems.value = [];
    return;
  }

  const [weeklyResult, monthlyResult, projectResult] = await Promise.all([
    fetchGetWeeklyReportApprovalPage({
      pageNo: 1,
      pageSize: 20,
      statusCode: 'pending_approval',
      keyword: undefined,
      periodStartDate: undefined,
      submitTime: undefined,
      supervisorName: undefined,
      isBusinessTrip: undefined
    }),
    fetchGetMonthlyReportApprovalPage({
      pageNo: 1,
      pageSize: 20,
      statusCode: 'pending_approval',
      keyword: undefined,
      periodStartDate: undefined,
      submitTime: undefined,
      supervisorName: undefined
    }),
    fetchGetProjectReportApprovalPage({
      pageNo: 1,
      pageSize: 20,
      statusCode: 'pending_approval',
      keyword: undefined,
      periodStartDate: undefined,
      submitTime: undefined,
      supervisorName: undefined,
      projectId: undefined,
      flag: undefined
    })
  ]);

  weeklyApprovalRows.value = weeklyResult.error || !weeklyResult.data ? [] : weeklyResult.data.list;
  monthlyApprovalRows.value = monthlyResult.error || !monthlyResult.data ? [] : monthlyResult.data.list;
  projectApprovalRows.value = projectResult.error || !projectResult.data ? [] : projectResult.data.list;
  workReportApprovalItems.value = [
    ...buildWorkReportApprovalItems('weekly', weeklyApprovalRows.value),
    ...buildWorkReportApprovalItems('monthly', monthlyApprovalRows.value),
    ...buildWorkReportApprovalItems('project', projectApprovalRows.value)
  ];
}

function getDeadlineToneClass(item: WorkbenchTodoItem) {
  if (isWorkbenchTodoOverdue(item)) return 'workbench-todo__deadline--rose';
  if (item.remainingDays === 0) return 'workbench-todo__deadline--amber';
  return 'workbench-todo__deadline--slate';
}

// 工作台路由 keepAlive：切回不重挂，用 onActivated 替代 onMounted，每次激活经 refresh 重拉（首挂也会触发，不漏首屏）
onActivated(refresh);
</script>

<template>
  <WorkbenchModuleCard
    v-loading="loading"
    title="我的待办"
    icon="mdi:clipboard-text-clock-outline"
    :editing="editing"
    @hide="$emit('hide')"
    @refresh="refresh"
  >
    <div class="workbench-todo__tabs">
      <div class="workbench-todo__tabs-group">
        <ElTooltip
          v-for="tab in visibleMainTabs"
          :key="tab.key"
          :content="`已逾期 ${tabOverdueCount[tab.key]} 项，建议尽快处理`"
          :disabled="tabOverdueCount[tab.key] === 0"
          placement="top"
          effect="dark"
        >
          <button
            type="button"
            class="workbench-todo__tab"
            :class="{ 'workbench-todo__tab--active': activeTab === tab.key }"
            @click="handleSelectTab(tab.key)"
          >
            <span>{{ tab.label }}</span>
            <span class="workbench-todo__tab-count">{{ tabCounts[tab.key] }}</span>
            <span v-if="tabOverdueCount[tab.key] > 0" class="workbench-todo__tab-dot" />
          </button>
        </ElTooltip>
      </div>

      <button type="button" class="workbench-todo__add" @click="handleOpenAdd">
        <SvgIcon icon="mdi:plus" class="workbench-todo__add-icon" />
        <span>我的事项</span>
      </button>
    </div>

    <div class="workbench-todo__filters">
      <div v-if="activeTab !== 'approval' && activeTab !== 'confirm'" class="workbench-todo__filters-left">
        <button
          v-for="filter in deadlineFilters"
          :key="filter.key"
          type="button"
          class="workbench-todo__filter"
          :class="{ 'workbench-todo__filter--active': activeDeadlineFilter === filter.key }"
          @click="handleSelectDeadlineFilter(filter.key)"
        >
          {{ filter.label }}
        </button>
      </div>
      <div v-else-if="activeTab === 'approval'" class="workbench-todo__filters-left">
        <button
          v-for="tab in visibleApprovalBizTabs"
          :key="tab.key"
          type="button"
          class="workbench-todo__filter"
          :class="{ 'workbench-todo__filter--active': activeApprovalBizType === tab.key }"
          @click="handleSelectApprovalBizType(tab.key)"
        >
          {{ tab.label }}
          <span class="workbench-todo__filter-count">{{ approvalBizTabCounts[tab.key] }}</span>
        </button>
      </div>
      <div v-else class="workbench-todo__filters-left">
        <button
          v-for="tab in visibleConfirmBizTabs"
          :key="tab.key"
          type="button"
          class="workbench-todo__filter"
          :class="{ 'workbench-todo__filter--active': activeApprovalBizType === tab.key }"
          @click="handleSelectApprovalBizType(tab.key)"
        >
          {{ tab.label }}
          <span class="workbench-todo__filter-count">{{ approvalBizTabCounts[tab.key] }}</span>
        </button>
      </div>

      <ElDropdown trigger="click" placement="bottom-end" @command="handleSelectSort">
        <span class="workbench-todo__sort">
          排序：{{ currentSortLabel }}
          <SvgIcon icon="mdi:chevron-down" class="workbench-todo__sort-icon" />
        </span>
        <template #dropdown>
          <ElDropdownMenu>
            <ElDropdownItem
              v-for="option in sortOptions"
              :key="option.key"
              :command="option.key"
              :class="{ 'is-active': activeSort === option.key }"
            >
              {{ option.label }}
            </ElDropdownItem>
          </ElDropdownMenu>
        </template>
      </ElDropdown>
    </div>

    <div class="workbench-todo__content">
      <!-- 批量操作栏（仅业务学习待审批时显示） -->
      <div
        v-if="
          activeTab === 'approval' &&
          activeApprovalBizType === 'overtime_application' &&
          overtimeApprovalItems.length > 0
        "
        class="workbench-todo__batch-bar"
        :class="{ 'workbench-todo__batch-bar--active': selectedOvertimeIds.size > 0 }"
      >
        <div class="workbench-todo__batch-bar-left">
          <ElCheckbox
            :model-value="allOvertimeItemsSelected"
            :indeterminate="someOvertimeItemsSelected && !allOvertimeItemsSelected"
            @change="val => toggleSelectAllOvertimeItems(Boolean(val))"
            @click.stop
          >
            全选
          </ElCheckbox>
          <span v-if="selectedOvertimeIds.size > 0" class="workbench-todo__batch-bar-count">
            已选择 {{ selectedOvertimeIds.size }} 项
          </span>
        </div>
        <div v-if="selectedOvertimeIds.size > 0" class="workbench-todo__batch-bar-right">
          <ElButton size="small" type="primary" :loading="batchSubmitting" @click.stop="handleBatchReview">
            批量审批
          </ElButton>
          <ElButton size="small" link @click.stop="clearOvertimeSelection">取消选择</ElButton>
        </div>
      </div>

      <div v-if="pagedItems.length" class="workbench-todo__list">
        <article
          v-for="item in pagedItems"
          :key="item.id"
          class="workbench-todo__item"
          :class="{
            'workbench-todo__item--selected': isOvertimeItemSelected(item)
          }"
        >
          <div class="workbench-todo__leading">
            <!-- 业务学习待审批时显示复选框 -->
            <ElCheckbox
              v-if="
                activeTab === 'approval' &&
                activeApprovalBizType === 'overtime_application' &&
                item.approvalBizType === 'overtime_application'
              "
              :model-value="isOvertimeItemSelected(item)"
              @change="val => toggleOvertimeItem(item, Boolean(val))"
              @click.stop
            />
            <span class="workbench-todo__category" :class="`workbench-todo__category--${item.categoryTone}`">
              {{ item.categoryLabel }}
            </span>
            <span
              v-if="item.priorityLabel"
              class="workbench-todo__priority"
              :class="{ 'workbench-todo__priority--high': item.priority === 'high' }"
            >
              {{ item.priorityLabel }}
            </span>
          </div>

          <div class="workbench-todo__body">
            <h4 class="workbench-todo__item-title">
              <ElPopover
                v-if="canPreviewTaskInfo(item)"
                placement="right-start"
                trigger="hover"
                :width="960"
                popper-class="workbench-task-preview-popover"
                @show="handleTaskPreviewShow(item)"
              >
                <template #reference>
                  <span
                    class="workbench-todo__item-title-text"
                    :class="{
                      'workbench-todo__item-title-text--clickable': Boolean(
                        item.routeKey || item.approvalBizType || item.projectId
                      )
                    }"
                    @click="handleClickItem(item)"
                  >
                    {{ item.title }}
                  </span>
                </template>

                <div class="workbench-task-preview">
                  <div class="workbench-task-preview__header">任务信息</div>
                  <div v-loading="isTaskPreviewLoading(item)" class="workbench-task-preview__body">
                    <TaskInfoReadonly
                      v-if="getTaskPreviewTask(item)"
                      :task="getTaskPreviewTask(item)"
                      :task-options="getTaskPreviewTaskOptions(item)"
                    />
                    <ElEmpty v-else description="暂无任务信息" :image-size="56" />
                  </div>
                </div>
              </ElPopover>
              <span
                v-else
                class="workbench-todo__item-title-text"
                :class="{
                  'workbench-todo__item-title-text--clickable': Boolean(
                    item.routeKey || item.approvalBizType || item.projectId
                  )
                }"
                @click="handleClickItem(item)"
              >
                {{ item.title }}
              </span>
            </h4>
            <div class="workbench-todo__meta">
              <span class="workbench-todo__source">{{ item.source }}</span>
            </div>
            <div
              v-if="shouldShowTodoProgress(item)"
              class="workbench-todo__progress"
              :aria-label="`进度 ${getTodoProgress(item)}%`"
            >
              <span class="workbench-todo__progress-label">进度：</span>
              <ElProgress
                class="workbench-todo__progress-bar"
                :percentage="getTodoProgress(item)"
                :stroke-width="6"
                :show-text="false"
              />
              <span class="workbench-todo__progress-text">{{ getTodoProgress(item) }}%</span>
            </div>
          </div>

          <div class="workbench-todo__trailing">
            <div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
              <ElTooltip content="审批">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
                  <component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
            <div v-else-if="item.approvalBizType === 'performance'" class="workbench-todo__actions" @click.stop>
              <ElTooltip content="审批">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openPerformanceDetail(item)">
                  <component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
            <div
              v-else-if="isWorkReportApprovalBizType(item.approvalBizType)"
              class="workbench-todo__actions"
              @click.stop
            >
              <ElTooltip content="审批">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openWorkReportDetail(item)">
                  <component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
            <div
              v-else-if="item.category === 'task' && canReportTaskWorklog(item)"
              class="workbench-todo__actions"
              @click.stop
            >
              <ElTooltip content="填报">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openTaskWorklog(item)">
                  <component :is="PERSONAL_ACTION_ICONS.worklog" class="text-15px" />
                </ElButton>
              </ElTooltip>
              <ElTooltip v-for="action in getTaskActions(item)" :key="action.actionCode" :content="action.actionName">
                <ElButton
                  link
                  :type="getTaskActionButtonType(action.actionCode)"
                  class="workbench-todo__action-btn"
                  @click="handleTaskAction(item, action)"
                >
                  <component :is="TASK_ACTION_ICONS[action.actionCode]" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
            <div v-else-if="item.category === 'personal'" class="workbench-todo__actions" @click.stop>
              <ElTooltip content="详情">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openPersonalDetail(item)">
                  <component :is="PERSONAL_ACTION_ICONS.detail" class="text-15px" />
                </ElButton>
              </ElTooltip>
              <ElTooltip content="填报工时">
                <ElButton link type="primary" class="workbench-todo__action-btn" @click="openPersonalWorklog(item)">
                  <component :is="PERSONAL_ACTION_ICONS.worklog" class="text-15px" />
                </ElButton>
              </ElTooltip>
              <ElTooltip v-if="canCompletePersonalItem(item)" content="完成">
                <ElButton link type="success" class="workbench-todo__action-btn" @click="openPersonalComplete(item)">
                  <component :is="PERSONAL_ACTION_ICONS.complete" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
            <span class="workbench-todo__deadline" :class="getDeadlineToneClass(item)">
              {{ item.deadlineLabel }}
            </span>
          </div>
        </article>
      </div>
      <ElEmpty v-else description="当前筛选下暂无待办" :image-size="72" />
    </div>

    <div class="workbench-todo__pager">
      <ElPagination
        v-if="filteredItems.length > PAGE_SIZE"
        v-model:current-page="currentPage"
        :page-size="PAGE_SIZE"
        :total="filteredItems.length"
        background
        small
        layout="prev, pager, next"
      />
    </div>

    <!-- append-to-body：脱离 grid item 的 transform 容器，弹窗才能正常全屏居中 -->
    <PersonalItemOperateDialog
      v-model:visible="personalOperateVisible"
      :operate-type="personalOperateType"
      :row-data="personalOperateRow"
      append-to-body
      @submitted="handleAddSubmitted"
    />

    <PersonalItemDetailDialog
      v-model:visible="personalDetailVisible"
      :row-data="personalDetailRow"
      default-tab="worklog"
      append-to-body
      @changed="handlePersonalWorklogChanged"
    />

    <PersonalItemStatusActionDialog
      v-model:visible="personalStatusVisible"
      :action="personalStatusAction"
      append-to-body
      @submit="handlePersonalStatusSubmit"
    />

    <TaskStatusActionDialog
      v-model:visible="taskStatusVisible"
      :title="taskStatusActionTitle"
      :action="taskStatusAction"
      append-to-body
      @submit="submitTaskStatusChange"
    />

    <TaskWorklogDialog
      v-model:visible="taskWorklogVisible"
      :task="taskWorklogTask"
      append-to-body
      @changed="handleTaskWorklogChanged"
    />

    <OvertimeApplicationDetailDialog
      v-model:visible="overtimeDetailVisible"
      :row-data="currentOvertimeApplication"
      show-approval-actions
      :action-loading="overtimeActionSubmitting"
      append-to-body
      @submit="handleOvertimeActionSubmit"
    />

    <!-- 批量审批详情弹窗（左右箭头切换 + 通过/退回按钮 + 意见输入） -->
    <OvertimeApplicationBatchDetailDialog
      v-model:visible="batchDetailVisible"
      :selected-ids="batchSelectedIds"
      :rows="overtimeApprovalRows"
      :action-loading="batchSubmitting"
      append-to-body
      @submit="handleBatchActionSubmit"
    />

    <WorkReportPrototypePageDialog
      v-model:visible="workReportDetailVisible"
      mode="detail"
      :scene="currentWorkReportScene"
      :report-type="currentWorkReportType"
      :row-data="currentWorkReport"
      @submitted="handleWorkReportSubmitted"
    />

    <PerformanceExcelEditorDrawer
      v-model:visible="performanceDetailVisible"
      :row-data="currentPerformanceSheet"
      mode="view"
      show-approval-footer
      @start-approval="openCurrentPerformanceAction('confirm')"
    />

    <PerformanceActionDialog
      v-model:visible="performanceActionVisible"
      :row-data="currentPerformanceSheet"
      :action-type="currentPerformanceActionType"
      selectable-action-type
      @submitted="handlePerformanceActionSubmitted"
    />
  </WorkbenchModuleCard>
</template>

<style scoped>
.workbench-todo__tabs {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.workbench-todo__tabs-group {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.workbench-todo__add {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 12px;
  border: 1px solid rgb(14 116 144 / 60%);
  border-radius: 999px;
  background-color: rgb(240 253 250 / 80%);
  color: rgb(14 116 144 / 96%);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: all 160ms ease;
}

.workbench-todo__add:hover {
  background-color: rgb(14 116 144 / 96%);
  border-color: rgb(14 116 144 / 96%);
  color: white;
}

.workbench-todo__add-icon {
  font-size: 14px;
}

.workbench-todo__tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid rgb(226 232 240 / 92%);
  border-radius: 999px;
  background-color: rgb(255 255 255 / 96%);
  color: rgb(71 85 105 / 94%);
  font-size: 13px;
  cursor: pointer;
  transition: all 160ms ease;
}

.workbench-todo__tab:hover {
  border-color: rgb(14 116 144 / 64%);
  color: rgb(14 116 144 / 96%);
}

.workbench-todo__tab--active {
  border-color: rgb(14 116 144 / 92%);
  background-color: rgb(14 116 144 / 96%);
  color: white;
}

.workbench-todo__tab--active:hover {
  color: white;
}

.workbench-todo__tab-count {
  padding: 1px 6px;
  border-radius: 999px;
  background-color: rgb(241 245 249 / 96%);
  color: rgb(71 85 105 / 94%);
  font-size: 11px;
  font-weight: 600;
}

.workbench-todo__tab--active .workbench-todo__tab-count {
  background-color: rgb(255 255 255 / 22%);
  color: white;
}

.workbench-todo__tab-dot {
  position: absolute;
  top: 4px;
  right: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: rgb(225 29 72 / 96%);
  box-shadow: 0 0 0 2px rgb(255 255 255 / 96%);
}

.workbench-todo__tab--active .workbench-todo__tab-dot {
  box-shadow: 0 0 0 2px rgb(14 116 144 / 96%);
}

.workbench-todo__filters {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.workbench-todo__filters-left {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.workbench-todo__sort {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 4px 8px;
  border-radius: 6px;
  color: rgb(100 116 139 / 92%);
  font-size: 12px;
  white-space: nowrap;
  cursor: pointer;
  transition: all 160ms ease;
}

.workbench-todo__sort:hover {
  background-color: rgb(241 245 249 / 96%);
  color: rgb(14 116 144 / 96%);
}

.workbench-todo__sort-icon {
  font-size: 14px;
}

:deep(.el-dropdown-menu__item.is-active) {
  color: var(--el-color-primary);
  font-weight: 600;
}

.workbench-todo__filter {
  padding: 3px 10px;
  border: 1px dashed rgb(203 213 225 / 92%);
  border-radius: 999px;
  background-color: transparent;
  color: rgb(100 116 139 / 92%);
  font-size: 12px;
  cursor: pointer;
  transition: all 160ms ease;
}

.workbench-todo__filter:hover {
  border-style: solid;
  border-color: rgb(14 116 144 / 60%);
  color: rgb(14 116 144 / 96%);
}

.workbench-todo__filter--active {
  border-style: solid;
  border-color: rgb(190 18 60 / 80%);
  background-color: rgb(255 228 230 / 96%);
  color: rgb(190 18 60 / 96%);
}

.workbench-todo__filter--active:hover {
  border-color: rgb(190 18 60 / 92%);
  color: rgb(190 18 60 / 96%);
}

.workbench-todo__filter-count {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 700;
}

.workbench-todo__content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.workbench-todo__content :deep(.el-empty) {
  margin: auto;
}

/* 批量操作栏 */
.workbench-todo__batch-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 14px;
  margin-bottom: 10px;
  border: 1px solid rgb(226 232 240 / 90%);
  border-radius: 12px;
  background-color: rgb(248 250 252 / 96%);
  font-size: 13px;
  color: rgb(71 85 105 / 94%);
  transition: all 160ms ease;
}

.workbench-todo__batch-bar--active {
  border-color: rgb(14 116 144 / 40%);
  background-color: rgb(240 253 250 / 80%);
  color: rgb(14 116 144 / 96%);
}

.workbench-todo__batch-bar-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

.workbench-todo__batch-bar-right {
  display: flex;
  align-items: center;
  gap: 6px;
}

.workbench-todo__batch-bar-count {
  font-weight: 600;
}

.workbench-todo__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.workbench-todo__item {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid rgb(226 232 240 / 90%);
  border-radius: 16px;
  background-color: rgb(255 255 255 / 98%);
  transition:
    border-color 160ms ease,
    background-color 160ms ease;
}

.workbench-todo__item--selected {
  border-color: rgb(14 116 144 / 60%);
  background-color: rgb(240 253 250 / 90%);
}

.workbench-todo__leading {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: max-content;
}

.workbench-todo__category {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  flex: 0 0 auto;
}

.workbench-todo__category--sky {
  background-color: rgb(224 242 254 / 96%);
  color: rgb(14 116 144 / 96%);
}

.workbench-todo__category--emerald {
  background-color: rgb(220 252 231 / 96%);
  color: rgb(5 150 105 / 96%);
}

.workbench-todo__category--amber {
  background-color: rgb(254 243 199 / 96%);
  color: rgb(180 83 9 / 96%);
}

.workbench-todo__category--rose {
  background-color: rgb(255 228 230 / 96%);
  color: rgb(190 18 60 / 96%);
}

.workbench-todo__category--violet {
  background-color: rgb(237 233 254 / 96%);
  color: rgb(109 40 217 / 96%);
}

.workbench-todo__priority {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 4px;
  border-radius: 6px;
  background-color: rgb(241 245 249 / 96%);
  color: rgb(71 85 105 / 96%);
  font-size: 11px;
  font-weight: 800;
}

.workbench-todo__priority--high {
  background-color: rgb(254 226 226 / 96%);
  color: rgb(220 38 38 / 96%);
}

.workbench-todo__body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.workbench-todo__item-title {
  margin: 0;
  color: rgb(15 23 42 / 98%);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.workbench-todo__item-title-text--clickable {
  cursor: pointer;
  transition: color 160ms ease;
}

.workbench-todo__item-title-text--clickable:hover {
  color: rgb(14 116 144 / 96%);
  text-decoration: underline;
}

:deep(.workbench-task-preview-popover) {
  padding: 0;
  overflow: hidden;
}

.workbench-task-preview {
  background: var(--el-bg-color);
}

.workbench-task-preview__header {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 18px;
  border-bottom: 1px solid var(--el-border-color-lighter);
  font-size: 15px;
  font-weight: 600;
  color: var(--el-text-color-primary);
}

.workbench-task-preview__body {
  max-height: min(72vh, 760px);
  padding: 18px;
  overflow: auto;
}

.workbench-todo__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.workbench-todo__source {
  color: rgb(100 116 139 / 92%);
  font-size: 12px;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.workbench-todo__progress {
  display: flex;
  align-items: center;
  gap: 6px;
  width: min(100%, 260px);
  margin-top: 1px;
}

.workbench-todo__progress-label {
  flex: 0 0 auto;
  color: rgb(100 116 139 / 92%);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
}

.workbench-todo__progress-bar {
  flex: 1 1 auto;
  min-width: 96px;
}

.workbench-todo__progress-text {
  flex: 0 0 34px;
  color: rgb(71 85 105 / 94%);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.workbench-todo__progress :deep(.el-progress-bar__outer) {
  background-color: rgb(226 232 240 / 96%);
}

.workbench-todo__trailing {
  display: flex;
  align-items: center;
  gap: 12px;
}

.workbench-todo__actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.workbench-todo__actions :deep(.el-button + .el-button) {
  margin-left: 0;
}

:deep(.workbench-todo__action-btn) {
  min-width: auto;
  height: auto;
  padding: 3px;
  line-height: 1;
}

.workbench-todo__deadline {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.workbench-todo__deadline--slate {
  background-color: rgb(241 245 249 / 96%);
  color: rgb(71 85 105 / 94%);
}

.workbench-todo__deadline--amber {
  background-color: rgb(254 243 199 / 96%);
  color: rgb(180 83 9 / 96%);
}

.workbench-todo__deadline--rose {
  background-color: rgb(255 228 230 / 96%);
  color: rgb(190 18 60 / 96%);
}

.workbench-todo__pager {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-height: 32px;
  margin-top: 12px;
}

@media (width <= 600px) {
  .workbench-todo__item {
    grid-template-columns: 72px minmax(0, 1fr);
  }

  .workbench-todo__trailing {
    grid-column: 1 / -1;
    justify-content: flex-end;
  }
}
</style>
