2026-02-15 16:13:41 +08:00

139 lines
4.4 KiB
TypeScript

import { useState, FormEvent } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import api, { getErrorMessage } from '@/lib/api';
import type { Project } from '@/types';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogClose,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Select } from '@/components/ui/select';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
interface ProjectFormProps {
project: Project | null;
onClose: () => void;
}
export default function ProjectForm({ project, onClose }: ProjectFormProps) {
const queryClient = useQueryClient();
const [formData, setFormData] = useState({
name: project?.name || '',
description: project?.description || '',
status: project?.status || 'not_started',
color: project?.color || '',
due_date: project?.due_date || '',
});
const mutation = useMutation({
mutationFn: async (data: typeof formData) => {
if (project) {
const response = await api.put(`/projects/${project.id}`, data);
return response.data;
} else {
const response = await api.post('/projects', data);
return response.data;
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['projects'] });
if (project) {
queryClient.invalidateQueries({ queryKey: ['projects', project.id.toString()] });
}
toast.success(project ? 'Project updated' : 'Project created');
onClose();
},
onError: (error) => {
toast.error(getErrorMessage(error, project ? 'Failed to update project' : 'Failed to create project'));
},
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
mutation.mutate(formData);
};
return (
<Dialog open={true} onOpenChange={onClose}>
<DialogContent>
<DialogClose onClick={onClose} />
<DialogHeader>
<DialogTitle>{project ? 'Edit Project' : 'New Project'}</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={4}
/>
</div>
<div className="space-y-2">
<Label htmlFor="status">Status</Label>
<Select
id="status"
value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e.target.value as any })}
>
<option value="not_started">Not Started</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="color">Color</Label>
<Input
id="color"
type="color"
value={formData.color}
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="due_date">Due Date</Label>
<Input
id="due_date"
type="date"
value={formData.due_date}
onChange={(e) => setFormData({ ...formData, due_date: e.target.value })}
/>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : project ? 'Update' : 'Create'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}