Initial commit: Todo app with Jira-style board
This commit is contained in:
135
src/components/tasks/TaskForm.tsx
Normal file
135
src/components/tasks/TaskForm.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Task } from '@prisma/client'
|
||||
import { Button, Input, TextArea, Select } from '@/components/ui'
|
||||
import { formatDateInput } from '@/lib/utils'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
interface TaskFormProps {
|
||||
task?: Task | null
|
||||
defaultStatus?: string
|
||||
onSubmit: (data: TaskFormData) => Promise<void>
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export interface TaskFormData {
|
||||
title: string
|
||||
description: string
|
||||
status: string
|
||||
deadline: string
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 'TODO', label: 'Chưa làm' },
|
||||
{ value: 'IN_PROGRESS', label: 'Đang làm' },
|
||||
{ value: 'DONE', label: 'Hoàn thành' },
|
||||
]
|
||||
|
||||
export function TaskForm({ task, defaultStatus = 'TODO', onSubmit, onCancel }: TaskFormProps) {
|
||||
const [formData, setFormData] = useState<TaskFormData>({
|
||||
title: '',
|
||||
description: '',
|
||||
status: defaultStatus,
|
||||
deadline: '',
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||
|
||||
useEffect(() => {
|
||||
if (task) {
|
||||
setFormData({
|
||||
title: task.title,
|
||||
description: task.description || '',
|
||||
status: task.status,
|
||||
deadline: formatDateInput(task.deadline),
|
||||
})
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, status: defaultStatus }))
|
||||
}
|
||||
}, [task, defaultStatus])
|
||||
|
||||
const validate = (): boolean => {
|
||||
const newErrors: Record<string, string> = {}
|
||||
|
||||
if (!formData.title.trim()) {
|
||||
newErrors.title = 'Tiêu đề không được để trống'
|
||||
} else if (formData.title.length > 200) {
|
||||
newErrors.title = 'Tiêu đề tối đa 200 ký tự'
|
||||
}
|
||||
|
||||
if (formData.description.length > 1000) {
|
||||
newErrors.description = 'Mô tả tối đa 1000 ký tự'
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!validate()) return
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await onSubmit(formData)
|
||||
} catch (error) {
|
||||
toast.error('Đã có lỗi xảy ra')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Input
|
||||
label="Tiêu đề *"
|
||||
placeholder="Nhập tiêu đề công việc"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
error={errors.title}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
label="Mô tả"
|
||||
placeholder="Nhập mô tả công việc (không bắt buộc)"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
error={errors.description}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Trạng thái"
|
||||
options={statusOptions}
|
||||
value={formData.status}
|
||||
onChange={(e) => setFormData({ ...formData, status: e.target.value })}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Deadline"
|
||||
type="datetime-local"
|
||||
value={formData.deadline}
|
||||
onChange={(e) => setFormData({ ...formData, deadline: e.target.value })}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end space-x-3 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
disabled={loading}
|
||||
>
|
||||
Hủy
|
||||
</Button>
|
||||
<Button type="submit" loading={loading}>
|
||||
{task ? 'Cập nhật' : 'Tạo mới'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user