health-petal/apps/pharmanager/src/routes/distributors/index.tsx
2026-05-23 14:37:24 +05:30

190 lines
4.9 KiB
TypeScript

import { useState, useMemo, useCallback } from "react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { Search, Plus, Pencil, Trash2, Truck } from "lucide-react";
import { GridTable } from "#/components/GridTable";
import type { GridTableColumn } from "#/components/GridTable";
import {
useListDistributors,
useRemoveDistributor,
trpc,
} from "shared-react";
interface DistributorRow {
id: number;
agency: string;
contact: string;
mobile: string;
address: string | null;
}
function makeColumns(
onDelete: (row: DistributorRow) => void,
): GridTableColumn<DistributorRow>[] {
return [
{
id: "agency",
header: "Agency Name",
cell: ({ row }) => (
<Link
to="/distributors/$id"
params={{ id: row.id.toString() }}
className="font-medium text-blue-600 hover:underline"
>
{row.agency}
</Link>
),
},
{
id: "contact",
header: "Contact Person",
cell: ({ row }) => (
<span className="text-sm">{row.contact}</span>
),
},
{
id: "mobile",
header: "Contact Mobile",
cell: ({ row }) => (
<span className="text-sm text-slate-600 font-mono">
{row.mobile}
</span>
),
},
{
id: "address",
header: "Address",
cell: ({ row }) => (
<span
className="text-sm text-slate-600 max-w-[200px] truncate block"
title={row.address ?? undefined}
>
{row.address || "—"}
</span>
),
},
{
id: "actions",
header: "Actions",
size: 90,
cell: ({ row }) => (
<div className="flex items-center justify-center gap-1">
<button
type="button"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-blue-600 hover:bg-blue-50 transition-colors"
aria-label={`Edit ${row.agency}`}
>
<Pencil className="w-[15px] h-[15px]" />
</button>
<button
type="button"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 transition-colors"
aria-label={`Delete ${row.agency}`}
onClick={() => onDelete(row)}
>
<Trash2 className="w-[15px] h-[15px]" />
</button>
</div>
),
},
];
}
export const Route = createFileRoute("/distributors/")({
component: DistributorsIndexPage,
staticData: {
title: "Distributors",
subtitle: "Manage product distributors & agencies",
},
});
function DistributorsIndexPage() {
const [searchQuery, setSearchQuery] = useState("");
const { data: distributors, isLoading, error } = useListDistributors();
const removeMutation = useRemoveDistributor();
const utils = trpc.useUtils();
const handleDelete = useCallback(
(row: DistributorRow) => {
if (!confirm(`Delete ${row.agency}?`)) return;
removeMutation.mutate(
{ id: row.id },
{
onSuccess: () => utils.distributor.list.invalidate(),
},
);
},
[removeMutation, utils],
);
const columns = useMemo(() => makeColumns(handleDelete), [handleDelete]);
const filtered = useMemo(() => {
const q = searchQuery.toLowerCase().trim();
if (!q) return distributors ?? [];
return (distributors ?? []).filter((d) => {
return (
d.agency.toLowerCase().includes(q) ||
d.contact.toLowerCase().includes(q) ||
d.mobile.includes(q)
);
});
}, [searchQuery, distributors]);
if (isLoading) {
return (
<div className="text-sm text-slate-600 py-8">
Loading distributors...
</div>
);
}
if (error) {
return (
<div className="text-sm text-red-600 py-8">
Failed to load distributors.
</div>
);
}
return (
<div>
<div className="flex items-center gap-3 mb-5 flex-wrap">
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[400px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
<Search className="w-4 h-4 text-slate-600 shrink-0" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search distributors..."
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
/>
</div>
<Link
to="/distributors/add"
className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"
>
<Plus className="w-[15px] h-[15px]" />
Add Distributor
</Link>
</div>
<GridTable
columns={columns}
data={filtered}
emptyState={
<div className="flex flex-col items-center py-16 px-6 text-slate-600">
<Truck className="w-12 h-12 mb-4 opacity-40" />
<h3 className="text-base font-semibold text-slate-900 mb-1.5">
No distributors found
</h3>
<p className="text-sm">
{searchQuery
? "No distributors match your search. Try a different term."
: "Add your first distributor to get started."}
</p>
</div>
}
/>
</div>
);
}