75 lines
3.8 KiB
TypeScript
75 lines
3.8 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Button } from '../components/ui/Button.js';
|
|
import { createObjective, listUsers } from '../lib/api.js';
|
|
import { createObjectiveSchema, type CreateObjectiveFormData } from '../schemas/objective.schema.js';
|
|
|
|
export function CreateObjective(): JSX.Element {
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof CreateObjectiveFormData, string>>>({});
|
|
const { register, handleSubmit } = useForm<CreateObjectiveFormData>({
|
|
defaultValues: { title: '', description: '', quarter: 'Q2/2026' },
|
|
});
|
|
const { data: users = [] } = useQuery({ queryKey: ['users'], queryFn: listUsers });
|
|
const mutation = useMutation({
|
|
mutationFn: createObjective,
|
|
onSuccess: async (objective) => {
|
|
await queryClient.invalidateQueries({ queryKey: ['objectives'] });
|
|
navigate(`/objectives/${objective.id}`);
|
|
},
|
|
});
|
|
|
|
const onSubmit = handleSubmit((values) => {
|
|
const parsed = createObjectiveSchema.safeParse(values);
|
|
if (!parsed.success) {
|
|
setFieldErrors(Object.fromEntries(parsed.error.issues.map((issue) => [issue.path[0], issue.message])));
|
|
return;
|
|
}
|
|
setFieldErrors({});
|
|
mutation.mutate(parsed.data);
|
|
});
|
|
|
|
return (
|
|
<section className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
<h1 className="mb-6 text-xl font-semibold text-gray-800">Create Objective</h1>
|
|
<form className="max-w-2xl space-y-4" onSubmit={onSubmit}>
|
|
<label className="block">
|
|
<span className="mb-1 block text-sm font-medium text-gray-700">Title</span>
|
|
<input className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" {...register('title')} />
|
|
{fieldErrors.title !== undefined && <span className="mt-1 block text-sm text-orange-600">{fieldErrors.title}</span>}
|
|
</label>
|
|
<label className="block">
|
|
<span className="mb-1 block text-sm font-medium text-gray-700">Description</span>
|
|
<textarea rows={4} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" {...register('description')} />
|
|
</label>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<label className="block">
|
|
<span className="mb-1 block text-sm font-medium text-gray-700">Owner</span>
|
|
<select className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" {...register('ownerId')}>
|
|
<option value="">Select owner</option>
|
|
{users.map((user) => (
|
|
<option key={user.id} value={user.id}>
|
|
{user.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{fieldErrors.ownerId !== undefined && <span className="mt-1 block text-sm text-orange-600">{fieldErrors.ownerId}</span>}
|
|
</label>
|
|
<label className="block">
|
|
<span className="mb-1 block text-sm font-medium text-gray-700">Quarter</span>
|
|
<input className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" {...register('quarter')} />
|
|
{fieldErrors.quarter !== undefined && <span className="mt-1 block text-sm text-orange-600">{fieldErrors.quarter}</span>}
|
|
</label>
|
|
</div>
|
|
{mutation.isError && <div className="rounded-lg bg-orange-100 px-3 py-2 text-sm text-orange-700">Unable to create objective.</div>}
|
|
<Button type="submit" disabled={mutation.isPending}>
|
|
Save
|
|
</Button>
|
|
</form>
|
|
</section>
|
|
);
|
|
}
|