302 lines
11 KiB
TypeScript
302 lines
11 KiB
TypeScript
import { useState, useCallback } from 'react'
|
||
import { trpc } from '@/trpc/client'
|
||
|
||
export function UserConnectRoute() {
|
||
const [search, setSearch] = useState('')
|
||
const [searchInput, setSearchInput] = useState('')
|
||
const [currentPage, setCurrentPage] = useState(1)
|
||
const [visitedCursors, setVisitedCursors] = useState<(number | undefined)[]>([undefined])
|
||
const [itemsPerPage, setItemsPerPage] = useState(50)
|
||
|
||
const cursor = visitedCursors[currentPage - 1]
|
||
|
||
const { data, isLoading, error } = trpc.admin.user.getAllUsers.useQuery({
|
||
limit: itemsPerPage,
|
||
cursor,
|
||
search,
|
||
})
|
||
|
||
const handleSearch = () => {
|
||
setSearch(searchInput)
|
||
setCurrentPage(1)
|
||
setVisitedCursors([undefined])
|
||
}
|
||
|
||
const handleNextPage = () => {
|
||
if (data?.nextCursor) {
|
||
const newCursors = [...visitedCursors]
|
||
newCursors[currentPage] = data.nextCursor
|
||
setVisitedCursors(newCursors)
|
||
setCurrentPage(currentPage + 1)
|
||
}
|
||
}
|
||
|
||
const handlePrevPage = () => {
|
||
if (currentPage > 1) {
|
||
setCurrentPage(currentPage - 1)
|
||
}
|
||
}
|
||
|
||
const handlePageSelect = (page: number) => {
|
||
setCurrentPage(page)
|
||
}
|
||
|
||
const handleClearSearch = () => {
|
||
setSearch('')
|
||
setSearchInput('')
|
||
setCurrentPage(1)
|
||
setVisitedCursors([undefined])
|
||
}
|
||
|
||
const handleItemsPerPageChange = (newLimit: number) => {
|
||
setItemsPerPage(newLimit)
|
||
setCurrentPage(1)
|
||
setVisitedCursors([undefined])
|
||
}
|
||
|
||
const totalPages = Math.min(currentPage + (data?.hasMore ? 3 : 0), 20)
|
||
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1)
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="max-w-6xl mx-auto">
|
||
<div className="bg-white rounded-lg shadow-md p-6">
|
||
<p className="text-gray-600 text-center">Loading users...</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="max-w-6xl mx-auto">
|
||
<div className="bg-white rounded-lg shadow-md p-6">
|
||
<p className="text-red-600 text-center">Error loading users: {error.message}</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-6xl mx-auto space-y-6">
|
||
<div className="bg-white rounded-lg shadow-md p-6">
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-2">User Connect</h1>
|
||
<p className="text-gray-600">View all registered users and their contact information</p>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-lg shadow-md p-6">
|
||
<div className="flex gap-2 mb-4">
|
||
<input
|
||
type="text"
|
||
value={searchInput}
|
||
onChange={(e) => setSearchInput(e.target.value)}
|
||
placeholder="Search by mobile number..."
|
||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
handleSearch()
|
||
}
|
||
}}
|
||
/>
|
||
<button
|
||
onClick={handleSearch}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
Search
|
||
</button>
|
||
{search && (
|
||
<button
|
||
onClick={handleClearSearch}
|
||
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
|
||
>
|
||
Clear
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center mb-4 pb-4 border-b border-gray-200">
|
||
<div className="flex items-center gap-4">
|
||
<p className="text-sm text-gray-600">
|
||
Page {currentPage} • Showing {data?.users.length || 0} users
|
||
</p>
|
||
<div className="flex items-center gap-2">
|
||
<label className="text-sm text-gray-600">Per page:</label>
|
||
<select
|
||
value={itemsPerPage}
|
||
onChange={(e) => handleItemsPerPageChange(Number(e.target.value))}
|
||
className="px-2 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value={10}>10</option>
|
||
<option value={25}>25</option>
|
||
<option value={50}>50</option>
|
||
<option value={100}>100</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
{pageNumbers.length > 1 && (
|
||
<>
|
||
<button
|
||
onClick={handlePrevPage}
|
||
disabled={currentPage === 1}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
currentPage === 1
|
||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
‹
|
||
</button>
|
||
|
||
{pageNumbers.map((page) => (
|
||
<button
|
||
key={page}
|
||
onClick={() => handlePageSelect(page)}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
currentPage === page
|
||
? 'bg-blue-600 text-white'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
{page}
|
||
</button>
|
||
))}
|
||
|
||
{data?.hasMore && currentPage >= totalPages - 2 && (
|
||
<span className="px-2 text-gray-400">...</span>
|
||
)}
|
||
|
||
<button
|
||
onClick={handleNextPage}
|
||
disabled={!data?.hasMore}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
!data?.hasMore
|
||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
›
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="border-b border-gray-200">
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">ID</th>
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">Name</th>
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">Mobile</th>
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">Total Orders</th>
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">Last Order</th>
|
||
<th className="text-left py-3 px-4 font-semibold text-gray-700">Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{data?.users.map((user) => (
|
||
<tr key={user.id} className="border-b border-gray-100 hover:bg-gray-50">
|
||
<td className="py-3 px-4 text-gray-600">{user.id}</td>
|
||
<td className="py-3 px-4 text-gray-900">{user.name || '-'}</td>
|
||
<td className="py-3 px-4">
|
||
{user.mobile ? (
|
||
<a
|
||
href={`https://wa.me/91${user.mobile.replace(/\D/g, '')}`}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-blue-600 hover:text-blue-800 font-mono hover:underline"
|
||
>
|
||
{user.mobile}
|
||
</a>
|
||
) : (
|
||
<span className="text-gray-400">-</span>
|
||
)}
|
||
</td>
|
||
<td className="py-3 px-4 text-gray-600">{user.totalOrders}</td>
|
||
<td className="py-3 px-4 text-gray-600">
|
||
{user.lastOrderDate
|
||
? new Date(user.lastOrderDate).toLocaleDateString()
|
||
: '-'}
|
||
</td>
|
||
<td className="py-3 px-4">
|
||
{user.isSuspended ? (
|
||
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs font-medium rounded">
|
||
Suspended
|
||
</span>
|
||
) : (
|
||
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs font-medium rounded">
|
||
Active
|
||
</span>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
|
||
{data?.users.length === 0 && (
|
||
<p className="text-center text-gray-500 py-8">No users found</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center mt-4 pt-4 border-t border-gray-200">
|
||
<div className="flex items-center gap-4">
|
||
<p className="text-sm text-gray-600">
|
||
Page {currentPage} • Showing {data?.users.length || 0} users
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
{pageNumbers.length > 1 && (
|
||
<>
|
||
<button
|
||
onClick={handlePrevPage}
|
||
disabled={currentPage === 1}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
currentPage === 1
|
||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
‹
|
||
</button>
|
||
|
||
{pageNumbers.map((page) => (
|
||
<button
|
||
key={page}
|
||
onClick={() => handlePageSelect(page)}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
currentPage === page
|
||
? 'bg-blue-600 text-white'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
{page}
|
||
</button>
|
||
))}
|
||
|
||
{data?.hasMore && currentPage >= totalPages - 2 && (
|
||
<span className="px-2 text-gray-400">...</span>
|
||
)}
|
||
|
||
<button
|
||
onClick={handleNextPage}
|
||
disabled={!data?.hasMore}
|
||
className={`px-3 py-1.5 rounded-md transition-colors ${
|
||
!data?.hasMore
|
||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
}`}
|
||
>
|
||
›
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|