feat(users, api-keys): add consistent search filters

This commit is contained in:
KodekaTeam committed 2026-08-05 12:43:30 +07:00
1 parent fd4585843f
commit 626cadbde6
7 files changed
+108 -81

No files matched your search

@@ -20,6 +20,7 @@ interface ActiveUsersPanelProps {
totalItems: number;
startItem: number;
endItem: number;
emptyMessage: string;
onPageChange: (page: number) => void;
onEditAccess: (user: UserRecord) => void;
onEditRole: (user: UserRecord) => void;
@@ -38,6 +39,7 @@ export default function ActiveUsersPanel({
totalItems,
startItem,
endItem,
emptyMessage,
onPageChange,
onEditAccess,
onEditRole,
@@ -69,7 +71,7 @@ export default function ActiveUsersPanel({
fontSize: 13,
}}
>
No active users found.
{emptyMessage}
</div>
) : (
<>
@@ -16,6 +16,7 @@ interface ArchivedUsersPanelProps {
totalItems: number;
startItem: number;
endItem: number;
emptyMessage: string;
onPageChange: (page: number) => void;
}
@@ -27,6 +28,7 @@ export default function ArchivedUsersPanel({
totalItems,
startItem,
endItem,
emptyMessage,
onPageChange,
}: ArchivedUsersPanelProps) {
return (
@@ -77,7 +79,7 @@ export default function ArchivedUsersPanel({
fontSize: 13,
}}
>
No archived users.
{emptyMessage}
</div>
) : (
<>
@@ -17,6 +17,7 @@ interface PendingInvitesPanelProps {
totalItems: number;
startItem: number;
endItem: number;
emptyMessage: string;
onPageChange: (page: number) => void;
onCopyFreshInviteLink: (invitationId: string) => void | Promise<void>;
onRevokeInvitation: (invitationId: string) => void | Promise<void>;
@@ -32,6 +33,7 @@ export default function PendingInvitesPanel({
totalItems,
startItem,
endItem,
emptyMessage,
onPageChange,
onCopyFreshInviteLink,
onRevokeInvitation,
@@ -83,7 +85,7 @@ export default function PendingInvitesPanel({
fontSize: 13,
}}
>
No pending invitations.
{emptyMessage}
</div>
) : (
<>
+31 -58
View File
@@ -1,9 +1,11 @@
import { Clock, Loader2, Lock, Plus, RefreshCw, Users } from "lucide-react";
import SearchField from "@/components/SearchField";
import { Loader2, Plus, RefreshCw } from "lucide-react";
export type UsersTabKey = "active-users" | "pending-invites" | "archived-users";
interface UsersToolbarProps {
activeTab: UsersTabKey;
search: string;
activeUsersCount: number;
invitationsCount: number;
archivedUsersCount: number;
@@ -11,12 +13,14 @@ interface UsersToolbarProps {
refreshing: boolean;
canInvite: boolean;
onTabChange: (tab: UsersTabKey) => void;
onSearchChange: (value: string) => void;
onRefresh: () => void | Promise<void>;
onInvite: () => void;
}
export default function UsersToolbar({
activeTab,
search,
activeUsersCount,
invitationsCount,
archivedUsersCount,
@@ -24,81 +28,50 @@ export default function UsersToolbar({
refreshing,
canInvite,
onTabChange,
onSearchChange,
onRefresh,
onInvite,
}: UsersToolbarProps) {
const tabs = [
const tabs: Array<{ key: UsersTabKey; label: string }> = [
{
key: "active-users" as const,
key: "active-users",
label: `Active Users (${activeUsersCount})`,
icon: Users,
},
{
key: "pending-invites" as const,
key: "pending-invites",
label: `Pending Invites (${invitationsCount})`,
icon: Clock,
},
{
key: "archived-users" as const,
key: "archived-users",
label: `Archived Users (${archivedUsersCount})`,
icon: Lock,
},
];
return (
<div
className="card"
style={{
padding: "12px 16px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
flexWrap: "wrap",
}}
className="card ui-responsive-toolbar users-toolbar"
style={{ padding: "12px 16px" }}
>
<div
style={{
display: "flex",
gap: 4,
background: "var(--bg-card)",
borderRadius: 10,
padding: 4,
border: "1px solid var(--border)",
}}
<SearchField
placeholder="Search users, roles, emails, or servers..."
value={search}
onChange={(event) => onSearchChange(event.target.value)}
containerStyle={{ flex: "1 1 360px", minWidth: 240 }}
/>
<select
className="input users-toolbar-filter"
aria-label="Filter user records"
value={activeTab}
onChange={(event) => onTabChange(event.target.value as UsersTabKey)}
style={{ fontSize: 12 }}
>
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
key={tab.key}
type="button"
onClick={() => onTabChange(tab.key)}
style={{
display: "flex",
alignItems: "center",
gap: 7,
padding: "7px 16px",
borderRadius: 7,
border: "none",
cursor: "pointer",
fontSize: 13,
fontWeight: 500,
background:
activeTab === tab.key
? "rgba(59,130,246,0.15)"
: "transparent",
color: activeTab === tab.key ? "#3b82f6" : "var(--text-muted)",
transition: "all 0.2s",
}}
>
<Icon size={13} />
{tab.label}
</button>
);
})}
</div>
<div style={{ display: "flex", gap: 8 }}>
{tabs.map((tab) => (
<option key={tab.key} value={tab.key}>
{tab.label}
</option>
))}
</select>
<div className="ui-toolbar-actions">
<button
className="btn btn-ghost"
style={{ fontSize: 12 }}