Update page.tsx
This commit is contained in:
@@ -38,10 +38,13 @@ import {
|
|||||||
ArrowUpDown,
|
ArrowUpDown,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
MoreHorizontal
|
MoreHorizontal,
|
||||||
|
Search,
|
||||||
|
X
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
@@ -52,6 +55,8 @@ import {
|
|||||||
export default function CustomerManagementPage() {
|
export default function CustomerManagementPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [customers, setCustomers] = useState<CustomerStats[]>([]);
|
const [customers, setCustomers] = useState<CustomerStats[]>([]);
|
||||||
|
const [filteredCustomers, setFilteredCustomers] = useState<CustomerStats[]>([]);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
@@ -93,6 +98,7 @@ export default function CustomerManagementPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setCustomers(sortedCustomers);
|
setCustomers(sortedCustomers);
|
||||||
|
setFilteredCustomers(sortedCustomers);
|
||||||
setTotalPages(Math.ceil(response.total / itemsPerPage));
|
setTotalPages(Math.ceil(response.total / itemsPerPage));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Failed to fetch customers");
|
toast.error("Failed to fetch customers");
|
||||||
@@ -117,6 +123,21 @@ export default function CustomerManagementPage() {
|
|||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
// Add filter function to filter customers when search query changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!searchQuery.trim()) {
|
||||||
|
setFilteredCustomers(customers);
|
||||||
|
} else {
|
||||||
|
const query = searchQuery.toLowerCase().trim();
|
||||||
|
const filtered = customers.filter(customer => {
|
||||||
|
const usernameMatch = customer.telegramUsername?.toLowerCase().includes(query);
|
||||||
|
const userIdMatch = customer.telegramUserId.toString().includes(query);
|
||||||
|
return usernameMatch || userIdMatch;
|
||||||
|
});
|
||||||
|
setFilteredCustomers(filtered);
|
||||||
|
}
|
||||||
|
}, [searchQuery, customers]);
|
||||||
|
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
};
|
};
|
||||||
@@ -133,6 +154,10 @@ export default function CustomerManagementPage() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
setSearchQuery("");
|
||||||
|
};
|
||||||
|
|
||||||
// Format date with time
|
// Format date with time
|
||||||
const formatDate = (dateString: string | null | undefined) => {
|
const formatDate = (dateString: string | null | undefined) => {
|
||||||
if (!dateString) return "N/A";
|
if (!dateString) return "N/A";
|
||||||
@@ -161,7 +186,7 @@ export default function CustomerManagementPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-black/40 border border-zinc-800 rounded-md overflow-hidden">
|
<div className="bg-black/40 border border-zinc-800 rounded-md overflow-hidden">
|
||||||
<div className="p-4 border-b border-zinc-800 bg-black/60 flex justify-between items-center">
|
<div className="p-4 border-b border-zinc-800 bg-black/60 flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-sm font-medium text-gray-400">Show:</div>
|
<div className="text-sm font-medium text-gray-400">Show:</div>
|
||||||
@@ -178,10 +203,33 @@ export default function CustomerManagementPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm text-gray-400">
|
<div className="relative flex-1 max-w-md">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<Search className="h-4 w-4 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search by username or Telegram ID..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-10 pr-10 py-2 w-full bg-black/40 border-zinc-700 text-white"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||||
|
onClick={clearSearch}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 text-gray-400 hover:text-gray-200" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm text-gray-400 whitespace-nowrap">
|
||||||
{loading
|
{loading
|
||||||
? "Loading..."
|
? "Loading..."
|
||||||
: `Showing ${customers.length} of ${totalPages * itemsPerPage} customers`}
|
: searchQuery
|
||||||
|
? `Found ${filteredCustomers.length} matching customers`
|
||||||
|
: `Showing ${filteredCustomers.length} of ${totalPages * itemsPerPage} customers`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -189,13 +237,22 @@ export default function CustomerManagementPage() {
|
|||||||
<div className="p-8 flex justify-center bg-black/60">
|
<div className="p-8 flex justify-center bg-black/60">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||||
</div>
|
</div>
|
||||||
) : customers.length === 0 ? (
|
) : filteredCustomers.length === 0 ? (
|
||||||
<div className="p-8 text-center bg-black/60">
|
<div className="p-8 text-center bg-black/60">
|
||||||
<Users className="h-12 w-12 mx-auto text-gray-500 mb-4" />
|
<Users className="h-12 w-12 mx-auto text-gray-500 mb-4" />
|
||||||
<h3 className="text-lg font-medium mb-2 text-white">No customers found</h3>
|
<h3 className="text-lg font-medium mb-2 text-white">
|
||||||
|
{searchQuery ? "No customers matching your search" : "No customers found"}
|
||||||
|
</h3>
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500">
|
||||||
Once you have customers placing orders, they will appear here.
|
{searchQuery
|
||||||
|
? "Try a different search term or clear the search"
|
||||||
|
: "Once you have customers placing orders, they will appear here."}
|
||||||
</p>
|
</p>
|
||||||
|
{searchQuery && (
|
||||||
|
<Button variant="outline" size="sm" onClick={clearSearch} className="mt-4">
|
||||||
|
Clear search
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
@@ -234,7 +291,7 @@ export default function CustomerManagementPage() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{customers.map((customer) => (
|
{filteredCustomers.map((customer) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={customer.userId}
|
key={customer.userId}
|
||||||
className={`cursor-pointer ${!customer.hasOrders ? "bg-black/30" : ""}`}
|
className={`cursor-pointer ${!customer.hasOrders ? "bg-black/30" : ""}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user