UserTasksPage.tsx 2.19 KB
// Workflow user-tasks list page.
//
// Fetches pending human tasks from the embedded workflow engine
// and renders them in a DataTable. Clicking a row navigates to
// the task detail page where the user can complete the task.

import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { workflow } from '@/api/client'
import type { UserTaskSummary } from '@/types/api'
import { PageHeader } from '@/components/PageHeader'
import { Loading } from '@/components/Loading'
import { ErrorBox } from '@/components/ErrorBox'
import { DataTable, type Column } from '@/components/DataTable'
import { useT } from '@/i18n/LocaleContext'

export function UserTasksPage() {
  const t = useT()
  const navigate = useNavigate()
  const [rows, setRows] = useState<UserTaskSummary[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    workflow
      .listTasks()
      .then(setRows)
      .catch((e: unknown) => setError(e instanceof Error ? e : new Error(String(e))))
      .finally(() => setLoading(false))
  }, [])

  const columns: Column<UserTaskSummary>[] = [
    {
      header: t('label.taskName'),
      key: 'taskName',
      render: (r) => (
        <button
          className="text-brand-600 hover:underline"
          onClick={() => navigate(`/workflow/tasks/${r.taskId}`)}
        >
          {r.taskName}
        </button>
      ),
    },
    { header: t('label.process'), key: 'processDefinitionKey' },
    { header: t('label.formKey'), key: 'formKey', render: (r) => r.formKey ?? '\u2014' },
    { header: t('label.created'), key: 'createTime' },
    { header: t('label.assignee'), key: 'assignee', render: (r) => r.assignee ?? '\u2014' },
  ]

  return (
    <div>
      <PageHeader
        title={t('page.tasks.title')}
        subtitle={t('page.tasks.subtitle')}
      />
      {loading && <Loading />}
      {error && <ErrorBox error={error} />}
      {!loading && !error && (
        <DataTable
          rows={rows}
          columns={columns}
          rowKey={(r) => r.taskId}
          empty={<div className="p-6 text-sm text-slate-400">{t('label.noPendingTasks')}</div>}
        />
      )}
    </div>
  )
}