200 lines
6.0 KiB
TypeScript
Executable File
200 lines
6.0 KiB
TypeScript
Executable File
import React, { useState } from "react";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { apiRequest } from "@/lib/queryClient";
|
|
import { Button } from "../ui/button";
|
|
import { Edit, Delete, Plus } from "lucide-react";
|
|
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
|
import { NpiProviderForm } from "./npiProviderForm";
|
|
|
|
type NpiProvider = {
|
|
id: number;
|
|
npiNumber: string;
|
|
providerName: string;
|
|
};
|
|
|
|
export function NpiProviderTable() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [editingProvider, setEditingProvider] =
|
|
useState<NpiProvider | null>(null);
|
|
|
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
const [providerToDelete, setProviderToDelete] =
|
|
useState<NpiProvider | null>(null);
|
|
|
|
const providersPerPage = 5;
|
|
|
|
const {
|
|
data: providers = [],
|
|
isLoading,
|
|
error,
|
|
} = useQuery({
|
|
queryKey: ["/api/npiProviders/"],
|
|
queryFn: async () => {
|
|
const res = await apiRequest("GET", "/api/npiProviders/");
|
|
if (!res.ok) throw new Error("Failed to fetch NPI providers");
|
|
return res.json() as Promise<NpiProvider[]>;
|
|
},
|
|
});
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: async (provider: NpiProvider) => {
|
|
const res = await apiRequest(
|
|
"DELETE",
|
|
`/api/npiProviders/${provider.id}`
|
|
);
|
|
if (!res.ok) throw new Error("Failed to delete NPI provider");
|
|
return true;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["/api/npiProviders/"],
|
|
});
|
|
},
|
|
});
|
|
|
|
const handleDeleteClick = (provider: NpiProvider) => {
|
|
setProviderToDelete(provider);
|
|
setIsDeleteDialogOpen(true);
|
|
};
|
|
|
|
const handleConfirmDelete = () => {
|
|
if (!providerToDelete) return;
|
|
|
|
deleteMutation.mutate(providerToDelete, {
|
|
onSuccess: () => {
|
|
setIsDeleteDialogOpen(false);
|
|
setProviderToDelete(null);
|
|
},
|
|
});
|
|
};
|
|
|
|
const indexOfLast = currentPage * providersPerPage;
|
|
const indexOfFirst = indexOfLast - providersPerPage;
|
|
const currentProviders = providers.slice(
|
|
indexOfFirst,
|
|
indexOfLast
|
|
);
|
|
const totalPages = Math.ceil(providers.length / providersPerPage);
|
|
|
|
return (
|
|
<div className="bg-white shadow rounded-lg overflow-hidden">
|
|
<div className="flex justify-between items-center p-4 border-b border-gray-200">
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
NPI Providers
|
|
</h2>
|
|
<Button
|
|
onClick={() => {
|
|
setEditingProvider(null);
|
|
setModalOpen(true);
|
|
}}
|
|
>
|
|
<Plus className="mr-2 h-4 w-4" /> Add NPI Providers
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
NPI Number
|
|
</th>
|
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Provider Name
|
|
</th>
|
|
<th className="px-4 py-2" />
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{isLoading ? (
|
|
<tr>
|
|
<td colSpan={3} className="text-center py-4">
|
|
Loading NPI providers...
|
|
</td>
|
|
</tr>
|
|
) : error ? (
|
|
<tr>
|
|
<td colSpan={3} className="text-center py-4 text-red-600">
|
|
Error loading NPI providers
|
|
</td>
|
|
</tr>
|
|
) : currentProviders.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={3} className="text-center py-4">
|
|
No NPI providers found.
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
currentProviders.map((provider) => (
|
|
<tr key={provider.id}>
|
|
<td className="px-4 py-2">
|
|
{provider.npiNumber}
|
|
</td>
|
|
<td className="px-4 py-2">
|
|
{provider.providerName}
|
|
</td>
|
|
<td className="px-4 py-2 text-right">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => {
|
|
setEditingProvider(provider);
|
|
setModalOpen(true);
|
|
}}
|
|
>
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => handleDeleteClick(provider)}
|
|
>
|
|
<Delete className="h-4 w-4 text-red-600" />
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{providers.length > providersPerPage && (
|
|
<div className="px-4 py-3 border-t flex justify-between">
|
|
<Button
|
|
variant="ghost"
|
|
disabled={currentPage === 1}
|
|
onClick={() => setCurrentPage((p) => p - 1)}
|
|
>
|
|
Previous
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
disabled={currentPage === totalPages}
|
|
onClick={() => setCurrentPage((p) => p + 1)}
|
|
>
|
|
Next
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{modalOpen && (
|
|
<NpiProviderForm
|
|
defaultValues={editingProvider || undefined}
|
|
onClose={() => setModalOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
<DeleteConfirmationDialog
|
|
isOpen={isDeleteDialogOpen}
|
|
onConfirm={handleConfirmDelete}
|
|
onCancel={() => setIsDeleteDialogOpen(false)}
|
|
entityName={providerToDelete?.providerName}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|