181 lines
5.5 KiB
TypeScript
181 lines
5.5 KiB
TypeScript
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
|
import { ArrowLeft, Plus } from "lucide-react";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { z } from "zod";
|
|
import { useCreateDistributor, trpc } from "shared-react";
|
|
|
|
const formSchema = z.object({
|
|
agency: z.string().min(1, "Agency name is required"),
|
|
contact: z.string().min(1, "Contact person is required"),
|
|
mobile: z
|
|
.string()
|
|
.length(10, "Valid 10-digit mobile number is required")
|
|
.regex(/^\d{10}$/, "Must be 10 digits"),
|
|
address: z.string().nullable().optional(),
|
|
});
|
|
|
|
type FormValues = z.infer<typeof formSchema>;
|
|
|
|
export const Route = createFileRoute("/distributors/add")({
|
|
component: AddDistributorPage,
|
|
staticData: {
|
|
title: "Add Distributor",
|
|
subtitle: "Register a new distributor agency",
|
|
},
|
|
});
|
|
|
|
function AddDistributorPage() {
|
|
const navigate = useNavigate();
|
|
const createMutation = useCreateDistributor();
|
|
const utils = trpc.useUtils();
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
} = useForm<FormValues>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
agency: "",
|
|
contact: "",
|
|
mobile: "",
|
|
address: null,
|
|
},
|
|
});
|
|
|
|
function onSubmit(values: FormValues) {
|
|
createMutation.mutate(
|
|
{
|
|
agency: values.agency,
|
|
contact: values.contact,
|
|
mobile: values.mobile,
|
|
address: values.address ?? null,
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
utils.distributor.list.invalidate();
|
|
navigate({ to: "/distributors" });
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<Link
|
|
to="/distributors"
|
|
className="inline-flex items-center gap-1.5 text-sm text-blue-600 hover:underline mb-5"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Back to Distributors
|
|
</Link>
|
|
|
|
<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-2xl"
|
|
>
|
|
<div className="grid grid-cols-2 gap-5">
|
|
<div className="col-span-full">
|
|
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
|
Agency Name{" "}
|
|
<span className="text-red-600 ml-0.5">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
{...register("agency")}
|
|
placeholder="e.g. MediDistributors"
|
|
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.agency ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
|
|
/>
|
|
{errors.agency && (
|
|
<p className="text-sm text-red-600 mt-1">
|
|
{errors.agency.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
|
Contact Person{" "}
|
|
<span className="text-red-600 ml-0.5">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
{...register("contact")}
|
|
placeholder="e.g. Rahul Mehta"
|
|
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.contact ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
|
|
/>
|
|
{errors.contact && (
|
|
<p className="text-sm text-red-600 mt-1">
|
|
{errors.contact.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
|
Contact Mobile{" "}
|
|
<span className="text-red-600 ml-0.5">*</span>
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
{...register("mobile", {
|
|
onChange: (e) => {
|
|
e.target.value = e.target.value
|
|
.replace(/\D/g, "")
|
|
.slice(0, 10);
|
|
},
|
|
})}
|
|
placeholder="e.g. 9876500001"
|
|
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.mobile ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
|
|
/>
|
|
{errors.mobile && (
|
|
<p className="text-sm text-red-600 mt-1">
|
|
{errors.mobile.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="col-span-full">
|
|
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
|
Address
|
|
</label>
|
|
<textarea
|
|
{...register("address", {
|
|
setValueAs: (v: string) => v || null,
|
|
})}
|
|
rows={3}
|
|
placeholder="Full address of the distributor"
|
|
className="w-full px-3.5 py-2.5 border border-slate-200 rounded-md text-sm text-slate-900 bg-white resize-y min-h-[80px] transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 focus:border-blue-600"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3 pt-5 mt-2 border-t border-slate-200">
|
|
<Link
|
|
to="/distributors"
|
|
className="inline-flex items-center px-5 py-2.5 border border-slate-200 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 transition-colors"
|
|
>
|
|
Cancel
|
|
</Link>
|
|
<button
|
|
type="submit"
|
|
disabled={createMutation.isPending}
|
|
className="inline-flex items-center gap-1.5 px-6 py-2.5 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors"
|
|
>
|
|
<Plus className="w-[15px] h-[15px]" />
|
|
{createMutation.isPending
|
|
? "Saving..."
|
|
: "Save Distributor"}
|
|
</button>
|
|
</div>
|
|
|
|
{createMutation.error && (
|
|
<p className="text-sm text-red-600 mt-4">
|
|
Failed to create distributor. Please try again.
|
|
</p>
|
|
)}
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|