💄 filter by approval state

This commit is contained in:
Fred KISSIE
2026-01-14 02:24:11 +01:00
parent 4c78e93143
commit d5b6a426a9
4 changed files with 61 additions and 19 deletions

View File

@@ -454,11 +454,12 @@
"filterByResource": "Filter By Resource", "filterByResource": "Filter By Resource",
"selectApprovalState": "Select Approval State", "selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State", "filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approval yet at the moment", "approvalListEmpty": "No approvals",
"approvalState": "Approval State", "approvalState": "Approval State",
"approve": "Approve", "approve": "Approve",
"approved": "Approved", "approved": "Approved",
"denied": "Denied", "denied": "Denied",
"all": "All",
"deny": "Deny", "deny": "Deny",
"viewDetails": "View Details", "viewDetails": "View Details",
"requestingNewDeviceApproval": "is requesting new device", "requestingNewDeviceApproval": "is requesting new device",

View File

@@ -21,7 +21,7 @@ import type { Request, Response, NextFunction } from "express";
import { build } from "@server/build"; import { build } from "@server/build";
import { getOrgTierData } from "@server/lib/billing"; import { getOrgTierData } from "@server/lib/billing";
import { TierId } from "@server/lib/billing/tiers"; import { TierId } from "@server/lib/billing/tiers";
import { approvals, clients, db, users } from "@server/db"; import { approvals, clients, db, users, type Approval } from "@server/db";
import { eq, isNull, sql, not, and, desc } from "drizzle-orm"; import { eq, isNull, sql, not, and, desc } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
@@ -41,10 +41,35 @@ const querySchema = z.strictObject({
.optional() .optional()
.default("0") .default("0")
.transform(Number) .transform(Number)
.pipe(z.int().nonnegative()) .pipe(z.int().nonnegative()),
approvalState: z
.enum(["pending", "approved", "denied", "all"])
.optional()
.default("pending")
.catch("all")
}); });
async function queryApprovals(orgId: string, limit: number, offset: number) { async function queryApprovals(
orgId: string,
limit: number,
offset: number,
approvalState: z.infer<typeof querySchema>["approvalState"]
) {
let state: Array<Approval["decision"]> = [];
switch (approvalState) {
case "pending":
state = ["pending"];
break;
case "approved":
state = ["approved"];
break;
case "denied":
state = ["denied"];
break;
default:
state = ["approved", "denied", "pending"];
}
const res = await db const res = await db
.select({ .select({
approvalId: approvals.id, approvalId: approvals.id,
@@ -67,7 +92,12 @@ async function queryApprovals(orgId: string, limit: number, offset: number) {
not(isNull(clients.userId)) // only user devices not(isNull(clients.userId)) // only user devices
) )
) )
.where(eq(approvals.orgId, orgId)) .where(
and(
eq(approvals.orgId, orgId),
sql`${approvals.decision} in ${state}`
)
)
.orderBy( .orderBy(
sql`CASE ${approvals.decision} WHEN 'pending' THEN 0 ELSE 1 END`, sql`CASE ${approvals.decision} WHEN 'pending' THEN 0 ELSE 1 END`,
desc(approvals.timestamp) desc(approvals.timestamp)
@@ -107,7 +137,7 @@ export async function listApprovals(
) )
); );
} }
const { limit, offset } = parsedQuery.data; const { limit, offset, approvalState } = parsedQuery.data;
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
@@ -127,7 +157,8 @@ export async function listApprovals(
const approvalsList = await queryApprovals( const approvalsList = await queryApprovals(
orgId.toString(), orgId.toString(),
limit, limit,
offset offset,
approvalState
); );
const [{ count }] = await db const [{ count }] = await db

View File

@@ -36,15 +36,11 @@ export function ApprovalFeed({ orgId }: ApprovalFeedProps) {
); );
const { data, isFetching, refetch } = useQuery( const { data, isFetching, refetch } = useQuery(
approvalQueries.listApprovals(orgId) approvalQueries.listApprovals(orgId, filters)
); );
const approvals = data?.approvals ?? []; const approvals = data?.approvals ?? [];
console.log({
approvals
});
return ( return (
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<Card className=""> <Card className="">
@@ -75,11 +71,16 @@ export function ApprovalFeed({ orgId }: ApprovalFeedProps) {
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent className="w-full"> <SelectContent className="w-full">
<SelectItem value="pending">Pending</SelectItem> <SelectItem value="pending">
<SelectItem value="approved"> {t("pending")}
Approved
</SelectItem> </SelectItem>
<SelectItem value="all">All</SelectItem> <SelectItem value="approved">
{t("approved")}
</SelectItem>
<SelectItem value="denied">
{t("denied")}
</SelectItem>
<SelectItem value="all">{t("all")}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@@ -321,13 +321,22 @@ export const approvalFiltersSchema = z.object({
}); });
export const approvalQueries = { export const approvalQueries = {
listApprovals: (orgId: string) => listApprovals: (
orgId: string,
filters: z.infer<typeof approvalFiltersSchema>
) =>
queryOptions({ queryOptions({
queryKey: ["APPROVALS", orgId] as const, queryKey: ["APPROVALS", orgId, filters] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
const sp = new URLSearchParams();
if (filters.approvalState) {
sp.set("approvalState", filters.approvalState);
}
const res = await meta!.api.get< const res = await meta!.api.get<
AxiosResponse<ListApprovalsResponse> AxiosResponse<ListApprovalsResponse>
>(`/org/${orgId}/approvals`, { >(`/org/${orgId}/approvals?${sp.toString()}`, {
signal signal
}); });
return res.data.data; return res.data.data;