R19/R20: 项目主域进 OA 并打通报销差旅周报待办
这个提交包含在:
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { cancelTodo, completeTodo, createTodo, fetchTodos } from "../api/todos";
|
||||
import { fetchProjects } from "../api/projects";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
@@ -20,17 +21,23 @@ export function TodosPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [title, setTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [projectId, setProjectId] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<"todo" | "doing" | "done" | "cancelled" | "">("");
|
||||
const todosQuery = useQuery({
|
||||
queryKey: ["todos", statusFilter],
|
||||
queryFn: () => fetchTodos({ status: statusFilter || undefined }),
|
||||
});
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: ["projects", "todo-options"],
|
||||
queryFn: () => fetchProjects({}),
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () => createTodo({ title, description: description || undefined }),
|
||||
mutationFn: () => createTodo({ title, description: description || undefined, projectId: projectId || null }),
|
||||
onSuccess: async () => {
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
setProjectId("");
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["todos"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["portal-home"] }),
|
||||
@@ -66,12 +73,20 @@ export function TodosPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader title="待办" subtitle="员工维护个人待办,老板看高优先级和未完成项。" />
|
||||
<PageHeader title="待办" subtitle="员工维护个人待办,老板看高优先级和未完成项。支持直接挂到项目。" />
|
||||
|
||||
<div className="card-std p-6 space-y-4">
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<div className="grid gap-3 md:grid-cols-4">
|
||||
<Input placeholder="待办标题" value={title} onChange={(event) => setTitle(event.target.value)} />
|
||||
<Input placeholder="补充说明(可选)" value={description} onChange={(event) => setDescription(event.target.value)} />
|
||||
<select className="select-std" value={projectId} onChange={(event) => setProjectId(event.target.value)}>
|
||||
<option value="">不关联项目</option>
|
||||
{(projectsQuery.data?.items ?? []).map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.projectNo} · {item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex gap-2">
|
||||
<select className="select-std" value={statusFilter} onChange={(event) => setStatusFilter(event.target.value as typeof statusFilter)}>
|
||||
<option value="">全部状态</option>
|
||||
@@ -90,6 +105,7 @@ export function TodosPage() {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>标题</TableHead>
|
||||
<TableHead>项目</TableHead>
|
||||
<TableHead>负责人</TableHead>
|
||||
<TableHead>优先级</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
@@ -103,6 +119,7 @@ export function TodosPage() {
|
||||
<div className="font-medium text-zinc-900">{item.title}</div>
|
||||
<div className="text-xs text-zinc-500">{item.description ?? "-"}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.project ? `${item.project.projectNo} · ${item.project.name}` : "-"}</TableCell>
|
||||
<TableCell>{item.assignee?.name ?? "-"}</TableCell>
|
||||
<TableCell>{item.priority}</TableCell>
|
||||
<TableCell>{item.status}</TableCell>
|
||||
|
||||
在新工单中引用
屏蔽一个用户