139 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|