60c68ac586
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 210h47m24s
CI/CD Pipeline / Frontend Lint (push) Failing after 210h47m48s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 210h47m52s
201 lines
5.6 KiB
TypeScript
201 lines
5.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
|
|
|
interface CreateTaskFormProps {
|
|
projectId: string;
|
|
workspaceId: string;
|
|
onSuccess?: () => void;
|
|
onCancel?: () => void;
|
|
}
|
|
|
|
export default function CreateTaskForm({ projectId, workspaceId, onSuccess, onCancel }: CreateTaskFormProps) {
|
|
const router = useRouter();
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
description: '',
|
|
priority: 'medium',
|
|
assignee_user_id: '',
|
|
parent_task_id: '',
|
|
});
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError('');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/v1/project-management/tasks`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
project_id: projectId,
|
|
workspace_id: workspaceId,
|
|
...formData,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.detail || '创建失败');
|
|
}
|
|
|
|
if (onSuccess) {
|
|
onSuccess();
|
|
} else {
|
|
router.push('/projects');
|
|
}
|
|
} catch (err: unknown) {
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} style={{
|
|
background: 'var(--bg-white)',
|
|
padding: '24px',
|
|
borderRadius: '8px',
|
|
maxWidth: '600px',
|
|
margin: '0 auto',
|
|
}}>
|
|
<h2 style={{ marginBottom: '20px', fontSize: '20px', fontWeight: 'bold' }}>新增任务</h2>
|
|
|
|
{error && (
|
|
<div style={{
|
|
padding: '12px',
|
|
background: '#FFECE8',
|
|
border: '1px solid var(--error)',
|
|
borderRadius: '4px',
|
|
color: 'var(--error)',
|
|
marginBottom: '20px',
|
|
}}>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
|
任务名称 <span style={{ color: 'var(--error)' }}>*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={formData.name}
|
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px 12px',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '4px',
|
|
fontSize: '14px',
|
|
}}
|
|
placeholder="输入任务名称"
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
|
任务描述
|
|
</label>
|
|
<textarea
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
rows={4}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px 12px',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '4px',
|
|
fontSize: '14px',
|
|
resize: 'vertical',
|
|
}}
|
|
placeholder="详细描述任务内容"
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
|
优先级
|
|
</label>
|
|
<select
|
|
value={formData.priority}
|
|
onChange={(e) => setFormData({ ...formData, priority: e.target.value })}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px 12px',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '4px',
|
|
fontSize: '14px',
|
|
}}
|
|
>
|
|
<option value="low">低</option>
|
|
<option value="medium">中</option>
|
|
<option value="high">高</option>
|
|
<option value="urgent">紧急</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
|
负责人 ID(可选)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.assignee_user_id}
|
|
onChange={(e) => setFormData({ ...formData, assignee_user_id: e.target.value })}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px 12px',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '4px',
|
|
fontSize: '14px',
|
|
}}
|
|
placeholder="输入负责人 ID"
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: '12px', marginTop: '24px' }}>
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
style={{
|
|
flex: 1,
|
|
padding: '10px',
|
|
borderRadius: '4px',
|
|
border: 'none',
|
|
background: loading ? '#ccc' : 'var(--primary)',
|
|
color: 'white',
|
|
fontWeight: 'bold',
|
|
cursor: loading ? 'not-allowed' : 'pointer',
|
|
}}
|
|
>
|
|
{loading ? '创建中...' : '创建任务'}
|
|
</button>
|
|
{onCancel && (
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
style={{
|
|
flex: 1,
|
|
padding: '10px',
|
|
borderRadius: '4px',
|
|
border: '1px solid var(--border)',
|
|
background: 'white',
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
取消
|
|
</button>
|
|
)}
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|