cleanup
This commit is contained in:
@@ -96,6 +96,7 @@ export function AdminDiagnosticsPage() {
|
||||
["Audit events", diagnostics.auditEventCount.toString()],
|
||||
["Support logs", diagnostics.supportLogCount.toString()],
|
||||
["Active users", `${diagnostics.activeUserCount} / ${diagnostics.userCount}`],
|
||||
["Sessions to review", diagnostics.reviewSessionCount.toString()],
|
||||
["Sales docs", diagnostics.salesDocumentCount.toString()],
|
||||
["Work orders", diagnostics.workOrderCount.toString()],
|
||||
["Projects", diagnostics.projectCount.toString()],
|
||||
@@ -108,6 +109,7 @@ export function AdminDiagnosticsPage() {
|
||||
["Uploads directory", diagnostics.uploadsDir],
|
||||
["Client origin", diagnostics.clientOrigin],
|
||||
["Company profile", diagnostics.companyProfilePresent ? "Present" : "Missing"],
|
||||
["Active sessions", diagnostics.activeSessionCount.toString()],
|
||||
["Roles / permissions", `${diagnostics.roleCount} / ${diagnostics.permissionCount}`],
|
||||
["Customers / vendors", `${diagnostics.customerCount} / ${diagnostics.vendorCount}`],
|
||||
["Inventory / warehouses", `${diagnostics.inventoryItemCount} / ${diagnostics.warehouseCount}`],
|
||||
|
||||
@@ -37,6 +37,9 @@ export function UserManagementPage() {
|
||||
const [selectedUserId, setSelectedUserId] = useState<string>("new");
|
||||
const [selectedRoleId, setSelectedRoleId] = useState<string>("new");
|
||||
const [sessionUserFilter, setSessionUserFilter] = useState<string>("all");
|
||||
const [sessionStatusFilter, setSessionStatusFilter] = useState<"ALL" | AdminAuthSessionDto["status"]>("ALL");
|
||||
const [sessionReviewFilter, setSessionReviewFilter] = useState<"ALL" | AdminAuthSessionDto["reviewState"]>("ALL");
|
||||
const [sessionQuery, setSessionQuery] = useState("");
|
||||
const [userForm, setUserForm] = useState<AdminUserInput>(emptyUserForm);
|
||||
const [roleForm, setRoleForm] = useState<AdminRoleInput>(emptyRoleForm);
|
||||
const [status, setStatus] = useState("Loading admin access controls...");
|
||||
@@ -224,10 +227,36 @@ export function UserManagementPage() {
|
||||
await refreshData("Revoked session. The user must sign in again to restore access unless their account is inactive.");
|
||||
}
|
||||
|
||||
const filteredSessions = sessions.filter((session) => sessionUserFilter === "all" || session.userId === sessionUserFilter);
|
||||
const normalizedSessionQuery = sessionQuery.trim().toLowerCase();
|
||||
const filteredSessions = sessions.filter((session) => {
|
||||
if (sessionUserFilter !== "all" && session.userId !== sessionUserFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sessionStatusFilter !== "ALL" && session.status !== sessionStatusFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sessionReviewFilter !== "ALL" && session.reviewState !== sessionReviewFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!normalizedSessionQuery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
session.userName.toLowerCase().includes(normalizedSessionQuery) ||
|
||||
session.userEmail.toLowerCase().includes(normalizedSessionQuery) ||
|
||||
(session.ipAddress ?? "").toLowerCase().includes(normalizedSessionQuery) ||
|
||||
(session.userAgent ?? "").toLowerCase().includes(normalizedSessionQuery) ||
|
||||
session.reviewReasons.some((reason) => reason.toLowerCase().includes(normalizedSessionQuery))
|
||||
);
|
||||
});
|
||||
const activeSessionCount = sessions.filter((session) => session.status === "ACTIVE").length;
|
||||
const revokedSessionCount = sessions.filter((session) => session.status === "REVOKED").length;
|
||||
const expiredSessionCount = sessions.filter((session) => session.status === "EXPIRED").length;
|
||||
const reviewSessionCount = sessions.filter((session) => session.reviewState === "REVIEW").length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -432,24 +461,60 @@ export function UserManagementPage() {
|
||||
Review recent authenticated sessions, see their current state, and revoke stale or risky access without changing the user record.
|
||||
</p>
|
||||
</div>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Filter by user</span>
|
||||
<select
|
||||
value={sessionUserFilter}
|
||||
onChange={(event) => setSessionUserFilter(event.target.value)}
|
||||
className="rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none"
|
||||
>
|
||||
<option value="all">All users</option>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.firstName} {user.lastName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Search</span>
|
||||
<input
|
||||
value={sessionQuery}
|
||||
onChange={(event) => setSessionQuery(event.target.value)}
|
||||
placeholder="User, email, IP, agent, review reason"
|
||||
className="w-full rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">User</span>
|
||||
<select
|
||||
value={sessionUserFilter}
|
||||
onChange={(event) => setSessionUserFilter(event.target.value)}
|
||||
className="w-full rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none"
|
||||
>
|
||||
<option value="all">All users</option>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.firstName} {user.lastName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Status</span>
|
||||
<select
|
||||
value={sessionStatusFilter}
|
||||
onChange={(event) => setSessionStatusFilter(event.target.value as "ALL" | AdminAuthSessionDto["status"])}
|
||||
className="w-full rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none"
|
||||
>
|
||||
<option value="ALL">All statuses</option>
|
||||
<option value="ACTIVE">Active</option>
|
||||
<option value="EXPIRED">Expired</option>
|
||||
<option value="REVOKED">Revoked</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Review</span>
|
||||
<select
|
||||
value={sessionReviewFilter}
|
||||
onChange={(event) => setSessionReviewFilter(event.target.value as "ALL" | AdminAuthSessionDto["reviewState"])}
|
||||
className="w-full rounded-2xl border border-line/70 bg-page px-3 py-2 text-sm text-text outline-none"
|
||||
>
|
||||
<option value="ALL">All sessions</option>
|
||||
<option value="REVIEW">Needs review</option>
|
||||
<option value="NORMAL">Normal</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-3 md:grid-cols-3">
|
||||
<div className="mt-5 grid gap-3 md:grid-cols-4">
|
||||
<div className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-muted">Active</p>
|
||||
<p className="mt-2 text-2xl font-bold text-text">{activeSessionCount}</p>
|
||||
@@ -462,6 +527,10 @@ export function UserManagementPage() {
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-muted">Expired</p>
|
||||
<p className="mt-2 text-2xl font-bold text-text">{expiredSessionCount}</p>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-amber-300/60 bg-amber-50 px-3 py-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-amber-700">Needs Review</p>
|
||||
<p className="mt-2 text-2xl font-bold text-amber-900">{reviewSessionCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-3">
|
||||
@@ -474,6 +543,11 @@ export function UserManagementPage() {
|
||||
<span className="rounded-full border border-line/70 px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted">
|
||||
{session.status}
|
||||
</span>
|
||||
{session.reviewState === "REVIEW" ? (
|
||||
<span className="rounded-full border border-amber-300/70 bg-amber-50 px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-800">
|
||||
Review
|
||||
</span>
|
||||
) : null}
|
||||
{session.isCurrent ? (
|
||||
<span className="rounded-full bg-brand px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white">
|
||||
Current
|
||||
@@ -488,6 +562,15 @@ export function UserManagementPage() {
|
||||
<p>IP: {session.ipAddress || "Unknown"}</p>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-muted">Agent: {session.userAgent || "Unknown"}</p>
|
||||
{session.reviewReasons.length > 0 ? (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{session.reviewReasons.map((reason) => (
|
||||
<span key={reason} className="rounded-full border border-amber-300/70 bg-amber-50 px-2 py-1 text-[11px] font-semibold text-amber-800">
|
||||
{reason}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{session.revokedAt ? (
|
||||
<p className="mt-2 text-xs text-muted">
|
||||
Revoked {new Date(session.revokedAt).toLocaleString()}
|
||||
|
||||
Reference in New Issue
Block a user