110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import { useState } from "react";
|
||
import { useMutation } from "@tanstack/react-query";
|
||
import { Plus } from "lucide-react";
|
||
import { sstcApi } from "@/api/sstc";
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from "@/components/ui/dialog";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
|
||
interface SelectedCharterLike {
|
||
id: string;
|
||
}
|
||
|
||
interface SSTCTaskCreateDialogProps {
|
||
projectId: string;
|
||
stageLocked: boolean;
|
||
selectedCharter: SelectedCharterLike | null;
|
||
onRefresh: () => Promise<void> | void;
|
||
}
|
||
|
||
export function SSTCTaskCreateDialog({
|
||
projectId,
|
||
stageLocked,
|
||
selectedCharter,
|
||
onRefresh,
|
||
}: SSTCTaskCreateDialogProps) {
|
||
const [newTaskOpen, setNewTaskOpen] = useState(false);
|
||
const [newTaskTitle, setNewTaskTitle] = useState("");
|
||
const [newTaskDesc, setNewTaskDesc] = useState("");
|
||
const [newTaskPriority, setNewTaskPriority] = useState("medium");
|
||
const [newTaskHours, setNewTaskHours] = useState("");
|
||
const [newTaskDueDate, setNewTaskDueDate] = useState("");
|
||
|
||
const createTaskMutation = useMutation({
|
||
mutationFn: () =>
|
||
sstcApi.createTask(projectId, selectedCharter!.id, {
|
||
title: newTaskTitle,
|
||
description: newTaskDesc || undefined,
|
||
priority: newTaskPriority,
|
||
estimated_hours: newTaskHours ? Number(newTaskHours) : undefined,
|
||
due_date: newTaskDueDate || undefined,
|
||
}),
|
||
onSuccess: () => {
|
||
setNewTaskOpen(false);
|
||
setNewTaskTitle("");
|
||
setNewTaskDesc("");
|
||
setNewTaskHours("");
|
||
setNewTaskDueDate("");
|
||
onRefresh();
|
||
},
|
||
});
|
||
|
||
return (
|
||
<>
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
className="border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
|
||
disabled={!selectedCharter || stageLocked}
|
||
onClick={() => setNewTaskOpen(true)}
|
||
>
|
||
<Plus className="mr-2 h-4 w-4" />
|
||
新建任务
|
||
</Button>
|
||
|
||
<Dialog open={newTaskOpen} onOpenChange={setNewTaskOpen}>
|
||
<DialogContent className="w-[min(560px,calc(100vw-1rem))] max-w-[calc(100vw-1rem)] sm:max-w-[560px]">
|
||
<DialogHeader>
|
||
<DialogTitle>新建任务</DialogTitle>
|
||
<DialogDescription>任务将绑定当前宪章(SSTC 强制)</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-3">
|
||
<Input value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} placeholder="任务标题" />
|
||
<Textarea value={newTaskDesc} onChange={(e) => setNewTaskDesc(e.target.value)} placeholder="任务描述" />
|
||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||
<select
|
||
className="rounded-md border bg-background px-2 py-2 text-sm"
|
||
value={newTaskPriority}
|
||
onChange={(e) => setNewTaskPriority(e.target.value)}
|
||
>
|
||
<option value="high">P0 / 高</option>
|
||
<option value="medium">P1 / 中</option>
|
||
<option value="low">P2 / 低</option>
|
||
</select>
|
||
<Input value={newTaskHours} onChange={(e) => setNewTaskHours(e.target.value)} placeholder="工时(小时)" />
|
||
<Input value={newTaskDueDate} onChange={(e) => setNewTaskDueDate(e.target.value)} placeholder="截止时间(标准格式)" />
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setNewTaskOpen(false)}>取消</Button>
|
||
<Button
|
||
onClick={() => createTaskMutation.mutate()}
|
||
disabled={!newTaskTitle.trim() || !selectedCharter || createTaskMutation.isPending}
|
||
>
|
||
创建
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</>
|
||
);
|
||
}
|