136 lines
3.5 KiB
TypeScript
136 lines
3.5 KiB
TypeScript
'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>
|
|
)
|
|
}
|