import { ChevronDown, ChevronLeft, ChevronRight, Search, TrendingUp, UserCheck, Users, X } from "lucide-react" import { useEffect, useState } from "react" import { useNavigate } from "react-router-dom" import * as DropdownMenu from "@radix-ui/react-dropdown-menu" import { Input } from "../../components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table" import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar" import { Button } from "../../components/ui/button" import { Card, CardContent } from "../../components/ui/card" import { SpinnerIcon } from "../../components/ui/spinner-icon" import { cn } from "../../lib/utils" import { TABLE_PAGE_SIZE_OPTIONS } from "../../lib/tablePagination" import { getDashboard } from "../../api/analytics.api" import { getUsers, updateUserStatus, type UserStatus } from "../../api/users.api" import type { DashboardUsers } from "../../types/analytics.types" import { mapUserApiToUser } from "../../types/user.types" import { useUsersStore } from "../../zustand/userStore" import { toast } from "sonner" import axios from "axios" import { USER_FILTER_COUNTRIES, USER_FILTER_ETHIOPIA_REGIONS } from "../../data/userFilterLocations" function formatJoinedAt(iso: string): string { if (!iso?.trim()) return "—" const d = new Date(iso) if (Number.isNaN(d.getTime())) return "—" return d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) } /** Convert `` value to RFC3339 for GET /users. */ function toRfc3339FromDatetimeLocal(value: string): string | undefined { const t = value?.trim() if (!t) return undefined const d = new Date(t) if (Number.isNaN(d.getTime())) return undefined return d.toISOString() } /** Portaled menu — native ` setSearch(e.target.value)} />
{ setCreatedAfterLocal(e.target.value) setPage(1) }} className="h-9 w-full rounded-md border border-grayScale-200 bg-white px-2 text-sm text-grayScale-700 focus:outline-none focus:ring-1 focus:ring-brand-500" />
{ setCreatedBeforeLocal(e.target.value) setPage(1) }} className="h-9 w-full rounded-md border border-grayScale-200 bg-white px-2 text-sm text-grayScale-700 focus:outline-none focus:ring-1 focus:ring-brand-500" />
{ setCountryFilter(next) setPage(1) }} /> { setRegionFilter(next) setPage(1) }} />

Dates are sent as RFC3339 (UTC). Country and region filters use the lists above; the API matches case-insensitively.

{/* Table */} handleSelectAll(e.target.checked)} className="h-4 w-4 rounded border-grayScale-300 text-brand-600 focus:ring-brand-500" /> USER Contact details Country Region Joined at Subscription status Status {loading ? (

Loading users...

) : users.length === 0 ? (

No users found

Try adjusting your search or filters.

) : ( users.map((u) => { const isActive = toggledStatuses[u.id] ?? false const isUpdatingStatus = updatingStatusIds.has(u.id) return ( handleRowClick(u.id)} > e.stopPropagation()}> handleSelectOne(u.id, e.target.checked)} className="h-4 w-4 rounded border-grayScale-300 text-brand-600 focus:ring-brand-500" />
{`${u.firstName?.[0] ?? ""}${u.lastName?.[0] ?? ""}`.toUpperCase()}
{u.firstName} {u.lastName}
{renderContactDetails(u.phoneNumber, u.email)} {u.country || "-"} {u.region || "-"} {formatJoinedAt(u.createdAt)} {u.subscriptionStatus} e.stopPropagation()}>
) }) )}
{/* Pagination */}
Showing {startEntry}-{endEntry} of {total} entries Rows per page
{getPageNumbers().map((n, idx) => typeof n === "string" ? ( ... ) : ( ) )}
{confirmDialog && (

Confirm Status Change

Are you sure you want to change the status of{" "} {confirmDialog.name || "this user"} to{" "} {confirmDialog.nextStatus.toLowerCase()}?

)} ) }