health-petal/apps/pharmanager/src/routes/customers/add.tsx
2026-05-24 15:12:43 +05:30

89 lines
4.1 KiB
TypeScript

import { useEffect } from "react";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { Plus } from "lucide-react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useCreateCustomer, useUpdateCustomer, useGetCustomerById, trpc } from "shared-react";
import { Button, Input, BackLink, buttonVariants } from "#/components/ui";
const formSchema = z.object({
mobile: z.string().min(1, "Mobile is required"),
name: z.string().nullable().optional(),
added_on: z.string().min(1, "Date is required"),
});
type FormValues = z.infer<typeof formSchema>;
export const Route = createFileRoute("/customers/add")({
component: AddCustomerPage,
validateSearch: (search: Record<string, unknown>) => ({ id: search.id ? Number(search.id) : undefined }),
staticData: { title: "Add Customer", subtitle: "Register a new customer" },
});
function AddCustomerPage() {
const navigate = useNavigate();
const { id: editId } = Route.useSearch();
const createMutation = useCreateCustomer();
const updateMutation = useUpdateCustomer();
const { data: existingCustomer } = useGetCustomerById(editId ?? 0);
const utils = trpc.useUtils();
const isEditing = typeof editId === "number" && editId > 0;
const { register, handleSubmit, reset, formState: { errors } } = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { mobile: "", name: null, added_on: new Date().toISOString().slice(0, 10) },
});
useEffect(() => {
if (isEditing && existingCustomer) {
reset({ mobile: existingCustomer.mobile, name: existingCustomer.name, added_on: existingCustomer.added_on });
}
}, [isEditing, existingCustomer, reset]);
function onSubmit(values: FormValues) {
if (isEditing) {
updateMutation.mutate(
{ id: editId!, ...values },
{ onSuccess: () => { utils.customers.list.invalidate(); utils.customers.byId.invalidate({ id: editId! }); navigate({ to: "/customers" }); } },
);
} else {
createMutation.mutate(values, { onSuccess: () => { utils.customers.list.invalidate(); navigate({ to: "/customers" }); } });
}
}
const mutation = isEditing ? updateMutation : createMutation;
return (
<div>
<BackLink to="/customers" label="Customers" />
<form onSubmit={handleSubmit(onSubmit)} className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-8 max-w-xl">
<h2 className="text-xl font-semibold mb-1">{isEditing ? "Edit Customer" : "New Customer"}</h2>
<p className="text-sm text-slate-600 mb-7">{isEditing ? "Update customer details." : "Register a new customer."}</p>
<div className="space-y-5">
<div>
<label className="block text-sm font-medium text-slate-900 mb-1.5">Mobile <span className="text-red-600">*</span></label>
<Input {...register("mobile")} variant={errors.mobile ? "error" : "default"} placeholder="9876543210" />
{errors.mobile && <p className="text-sm text-red-600 mt-1">{errors.mobile.message}</p>}
</div>
<div>
<label className="block text-sm font-medium text-slate-900 mb-1.5">Name</label>
<Input {...register("name", { setValueAs: (v: string) => v || null })} placeholder="John Doe" />
</div>
<div>
<label className="block text-sm font-medium text-slate-900 mb-1.5">Registered Date <span className="text-red-600">*</span></label>
<Input type="date" {...register("added_on")} variant={errors.added_on ? "error" : "default"} />
{errors.added_on && <p className="text-sm text-red-600 mt-1">{errors.added_on.message}</p>}
</div>
</div>
<div className="flex justify-end gap-3 pt-5 mt-5 border-t border-slate-200">
<Link to="/customers" className={buttonVariants({ variant: "outline" })}>Cancel</Link>
<Button type="submit" disabled={mutation.isPending}>
<Plus className="w-[15px] h-[15px]" />{mutation.isPending ? "Saving..." : isEditing ? "Update" : "Save"}
</Button>
</div>
{mutation.error && <p className="text-sm text-red-600 mt-4">Failed to {isEditing ? "update" : "create"} customer.</p>}
</form>
</div>
);
}