diff --git a/sigap-website/components/admin/users/sheet.tsx b/sigap-website/components/admin/users/sheet.tsx
index 7cc0438..7f998f1 100644
--- a/sigap-website/components/admin/users/sheet.tsx
+++ b/sigap-website/components/admin/users/sheet.tsx
@@ -370,11 +370,11 @@ export function UserDetailsSheet({ open, onOpenChange, user, onUserUpdate }: Use
-
+ {/*
-
+ */}
)
diff --git a/sigap-website/components/admin/users/user-management.tsx b/sigap-website/components/admin/users/user-management.tsx
index 89c04be..bf78c06 100644
--- a/sigap-website/components/admin/users/user-management.tsx
+++ b/sigap-website/components/admin/users/user-management.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { useState, useMemo } from "react";
import {
PlusCircle,
Search,
@@ -10,6 +10,14 @@ import {
ChevronDown,
UserPlus,
Mail,
+ SortAsc,
+ SortDesc,
+ Mail as MailIcon,
+ Phone,
+ Clock,
+ Calendar,
+ ShieldAlert,
+ ListFilter,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -18,7 +26,9 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
+ DropdownMenuSeparator,
DropdownMenuTrigger,
+ DropdownMenuCheckboxItem,
} from "@/components/ui/dropdown-menu";
import { useQuery } from "@tanstack/react-query";
import { fetchUsers } from "@/app/protected/(admin)/dashboard/user-management/action";
@@ -36,6 +46,21 @@ export default function UserManagement() {
const [isAddUserOpen, setIsAddUserOpen] = useState(false);
const [isInviteUserOpen, setIsInviteUserOpen] = useState(false);
+ // Filter states
+ const [filters, setFilters] = useState<{
+ email: string;
+ phone: string;
+ lastSignIn: string;
+ createdAt: string;
+ status: string[];
+ }>({
+ email: "",
+ phone: "",
+ lastSignIn: "",
+ createdAt: "",
+ status: [],
+ });
+
// Use React Query to fetch users
const {
data: users = [],
@@ -63,21 +88,150 @@ export default function UserManagement() {
setIsSheetOpen(false);
};
- const filteredUsers = users.filter((user) => {
- if (!searchQuery) return true;
+ const filteredUsers = useMemo(() => {
+ return users.filter((user) => {
+ // Global search
+ if (searchQuery) {
+ const query = searchQuery.toLowerCase();
+ const matchesSearch =
+ user.email?.toLowerCase().includes(query) ||
+ user.phone?.toLowerCase().includes(query) ||
+ user.id.toLowerCase().includes(query);
- const query = searchQuery.toLowerCase();
- return (
- user.email?.toLowerCase().includes(query) ||
- user.phone?.toLowerCase().includes(query) ||
- user.id.toLowerCase().includes(query)
- );
- });
+ if (!matchesSearch) return false;
+ }
+
+ // Email filter
+ if (
+ filters.email &&
+ !user.email?.toLowerCase().includes(filters.email.toLowerCase())
+ ) {
+ return false;
+ }
+
+ // Phone filter
+ if (
+ filters.phone &&
+ !user.phone?.toLowerCase().includes(filters.phone.toLowerCase())
+ ) {
+ return false;
+ }
+
+ // Last sign in filter
+ if (filters.lastSignIn) {
+ if (filters.lastSignIn === "never" && user.last_sign_in_at) {
+ return false;
+ } else if (filters.lastSignIn === "today") {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const signInDate = user.last_sign_in_at
+ ? new Date(user.last_sign_in_at)
+ : null;
+ if (!signInDate || signInDate < today) return false;
+ } else if (filters.lastSignIn === "week") {
+ const weekAgo = new Date();
+ weekAgo.setDate(weekAgo.getDate() - 7);
+ const signInDate = user.last_sign_in_at
+ ? new Date(user.last_sign_in_at)
+ : null;
+ if (!signInDate || signInDate < weekAgo) return false;
+ } else if (filters.lastSignIn === "month") {
+ const monthAgo = new Date();
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
+ const signInDate = user.last_sign_in_at
+ ? new Date(user.last_sign_in_at)
+ : null;
+ if (!signInDate || signInDate < monthAgo) return false;
+ }
+ }
+
+ // Created at filter
+ if (filters.createdAt) {
+ if (filters.createdAt === "today") {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const createdAt = new Date(user.created_at);
+ if (createdAt < today) return false;
+ } else if (filters.createdAt === "week") {
+ const weekAgo = new Date();
+ weekAgo.setDate(weekAgo.getDate() - 7);
+ const createdAt = new Date(user.created_at);
+ if (createdAt < weekAgo) return false;
+ } else if (filters.createdAt === "month") {
+ const monthAgo = new Date();
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
+ const createdAt = new Date(user.created_at);
+ if (createdAt < monthAgo) return false;
+ }
+ }
+
+ // Status filter
+ if (filters.status.length > 0) {
+ const userStatus = user.banned_until
+ ? "banned"
+ : !user.email_confirmed_at
+ ? "unconfirmed"
+ : "active";
+
+ if (!filters.status.includes(userStatus)) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ }, [users, searchQuery, filters]);
+
+ const clearFilters = () => {
+ setFilters({
+ email: "",
+ phone: "",
+ lastSignIn: "",
+ createdAt: "",
+ status: [],
+ });
+ };
+
+ const activeFilterCount = Object.values(filters).filter(
+ (value) =>
+ (typeof value === "string" && value !== "") ||
+ (Array.isArray(value) && value.length > 0)
+ ).length;
const columns = [
{
id: "email",
- header: "Email",
+ header: ({ column }: any) => (
+
+
+
Email
+
+
+
+
+
+
+
+ setFilters({ ...filters, email: e.target.value })
+ }
+ className="w-full"
+ />
+
+
+ setFilters({ ...filters, email: "" })}
+ >
+ Clear filter
+
+
+
+
+ ),
cell: ({ row }: { row: { original: User } }) => (
@@ -93,21 +247,109 @@ export default function UserManagement() {
),
- filterFn: (row: any, id: string, value: string) => {
- return row.original.email?.toLowerCase().includes(value.toLowerCase());
- },
},
{
id: "phone",
- header: "Phone",
+ header: ({ column }: any) => (
+
+
+
Phone
+
+
+
+
+
+
+
+ setFilters({ ...filters, phone: e.target.value })
+ }
+ className="w-full"
+ />
+
+
+ setFilters({ ...filters, phone: "" })}
+ >
+ Clear filter
+
+
+
+
+ ),
cell: ({ row }: { row: { original: User } }) => row.original.phone || "-",
- filterFn: (row: any, id: string, value: string) => {
- return row.original.phone?.toLowerCase().includes(value.toLowerCase());
- },
},
{
id: "lastSignIn",
- header: "Last Sign In",
+ header: ({ column }: any) => (
+
+
+ Last Sign In
+
+
+
+
+
+
+ setFilters({
+ ...filters,
+ lastSignIn: filters.lastSignIn === "today" ? "" : "today",
+ })
+ }
+ >
+ Today
+
+
+ setFilters({
+ ...filters,
+ lastSignIn: filters.lastSignIn === "week" ? "" : "week",
+ })
+ }
+ >
+ Last 7 days
+
+
+ setFilters({
+ ...filters,
+ lastSignIn: filters.lastSignIn === "month" ? "" : "month",
+ })
+ }
+ >
+ Last 30 days
+
+
+ setFilters({
+ ...filters,
+ lastSignIn: filters.lastSignIn === "never" ? "" : "never",
+ })
+ }
+ >
+ Never
+
+
+ setFilters({ ...filters, lastSignIn: "" })}
+ >
+ Clear filter
+
+
+
+
+ ),
cell: ({ row }: { row: { original: User } }) => {
return row.original.last_sign_in_at
? new Date(row.original.last_sign_in_at).toLocaleString()
@@ -116,14 +358,129 @@ export default function UserManagement() {
},
{
id: "createdAt",
- header: "Created At",
+ header: ({ column }: any) => (
+
+
+ Created At
+
+
+
+
+
+
+ setFilters({
+ ...filters,
+ createdAt: filters.createdAt === "today" ? "" : "today",
+ })
+ }
+ >
+ Today
+
+
+ setFilters({
+ ...filters,
+ createdAt: filters.createdAt === "week" ? "" : "week",
+ })
+ }
+ >
+ Last 7 days
+
+
+ setFilters({
+ ...filters,
+ createdAt: filters.createdAt === "month" ? "" : "month",
+ })
+ }
+ >
+ Last 30 days
+
+
+ setFilters({ ...filters, createdAt: "" })}
+ >
+ Clear filter
+
+
+
+
+ ),
cell: ({ row }: { row: { original: User } }) => {
return new Date(row.original.created_at).toLocaleString();
},
},
{
id: "status",
- header: "Status",
+ header: ({ column }: any) => (
+
+
+ Status
+
+
+
+
+
+ {
+ const newStatus = [...filters.status];
+ if (newStatus.includes("active")) {
+ newStatus.splice(newStatus.indexOf("active"), 1);
+ } else {
+ newStatus.push("active");
+ }
+ setFilters({ ...filters, status: newStatus });
+ }}
+ >
+ Active
+
+ {
+ const newStatus = [...filters.status];
+ if (newStatus.includes("unconfirmed")) {
+ newStatus.splice(newStatus.indexOf("unconfirmed"), 1);
+ } else {
+ newStatus.push("unconfirmed");
+ }
+ setFilters({ ...filters, status: newStatus });
+ }}
+ >
+ Unconfirmed
+
+ {
+ const newStatus = [...filters.status];
+ if (newStatus.includes("banned")) {
+ newStatus.splice(newStatus.indexOf("banned"), 1);
+ } else {
+ newStatus.push("banned");
+ }
+ setFilters({ ...filters, status: newStatus });
+ }}
+ >
+ Banned
+
+
+ setFilters({ ...filters, status: [] })}
+ >
+ Clear filter
+
+
+
+
+ ),
cell: ({ row }: { row: { original: User } }) => {
if (row.original.banned_until) {
return Banned;
@@ -133,14 +490,6 @@ export default function UserManagement() {
}
return Active;
},
- filterFn: (row: any, id: string, value: string) => {
- const status = row.original.banned_until
- ? "banned"
- : !row.original.email_confirmed_at
- ? "unconfirmed"
- : "active";
- return status.includes(value.toLowerCase());
- },
},
{
id: "actions",
@@ -202,8 +551,19 @@ export default function UserManagement() {
-