support for filtering and updating caliber

This commit is contained in:
2026-01-08 15:26:40 -05:00
parent 72d8f559c9
commit ae86fe6867
16 changed files with 1358 additions and 842 deletions
+95
View File
@@ -0,0 +1,95 @@
import { API_BASE, getToken, qs } from "./auth";
import type {
AdminProductRow,
PageResponse,
PlatformDto,
ProductStatus,
ProductVisibility,
} from "./types";
async function authedFetch(url: string, init?: RequestInit) {
const token = getToken();
const res = await fetch(url, {
credentials: "include",
cache: "no-store",
...(init ?? {}),
headers: {
Accept: "application/json",
...(init?.headers ?? {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
} as any,
});
return res;
}
export async function fetchAdminRoles(params: Record<string, any>) {
const res = await authedFetch(
`${API_BASE}/api/v1/admin/products/roles?${qs(params)}`
);
if (!res.ok) throw new Error(`Failed to load roles (${res.status}): ${await res.text()}`);
return (await res.json()) as string[];
}
export async function fetchAdminCalibers(params: Record<string, any>) {
const res = await authedFetch(
`${API_BASE}/api/v1/admin/products/calibers?${qs(params)}`
);
if (!res.ok) throw new Error(`Failed to load calibers (${res.status}): ${await res.text()}`);
return (await res.json()) as string[];
}
export async function fetchAdminProducts(params: Record<string, any>) {
const res = await authedFetch(
`${API_BASE}/api/v1/admin/products?${qs(params)}`
);
if (!res.ok) throw new Error(`Failed to load admin products (${res.status}): ${await res.text()}`);
return (await res.json()) as PageResponse<AdminProductRow>;
}
export async function fetchPlatforms() {
const res = await fetch(`${API_BASE}/api/platforms`, {
method: "GET",
headers: { Accept: "application/json" },
cache: "no-store",
});
if (!res.ok) throw new Error(`Failed to load platforms (${res.status}): ${await res.text()}`);
const all = (await res.json()) as PlatformDto[];
return all.filter((p) => p.is_active);
}
export async function bulkUpdate(
productIds: number[],
set: Partial<{
visibility: ProductVisibility;
status: ProductStatus;
builderEligible: boolean;
adminLocked: boolean;
adminNote: string;
platform: string;
platformLocked: boolean;
caliber: string;
caliberGroup: string;
caliberLocked: boolean;
forceCaliberUpdate: boolean;
partRole: string;
partRoleLocked: boolean;
classificationReason: "Admin bulk override";
}>
) {
const token = getToken();
const res = await fetch(`${API_BASE}/api/v1/admin/products/bulk`, {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ productIds, ...set }),
});
if (!res.ok) throw new Error(`Bulk update failed (${res.status}): ${await res.text()}`);
return (await res.json()) as { updatedCount: number; skippedLockedCount: number };
}
+33
View File
@@ -0,0 +1,33 @@
export const API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
export function getCookie(name: string): string | null {
if (typeof document === "undefined") return null;
const match = document.cookie.match(
new RegExp(
"(^| )" + name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "=([^;]+)"
)
);
return match ? decodeURIComponent(match[2]) : null;
}
export function getToken(): string | null {
if (typeof window === "undefined") return null;
const ls = localStorage.getItem("ballistic_auth_token");
if (ls && ls.trim().length > 0) return ls;
const ck = getCookie("bb_access_token");
if (ck && ck.trim().length > 0) return ck;
return null;
}
export function qs(params: Record<string, any>) {
const sp = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
if (v === undefined || v === null || v === "") return;
sp.set(k, String(v));
});
return sp.toString();
}
+61
View File
@@ -0,0 +1,61 @@
export type ProductVisibility = "PUBLIC" | "HIDDEN";
export type ProductStatus = "ACTIVE" | "DISABLED" | "ARCHIVED";
export type AdminProductRow = {
id: number;
uuid: string;
name: string;
slug: string;
platform: string | null;
partRole: string | null;
brandName: string | null;
importStatus: string | null;
caliber: string | null;
caliberGroup: string | null;
caliberLocked: boolean;
visibility: ProductVisibility;
status: ProductStatus;
builderEligible: boolean;
adminLocked: boolean;
adminNote: string | null;
mainImageUrl: string | null;
createdAt: string | null;
updatedAt: string | null;
};
export type PageResponse<T> = {
content: T[];
totalElements: number;
totalPages: number;
number: number;
size: number;
};
export type AdminFilters = {
q: string;
platform: string;
partRole: string;
caliber: string;
visibility: ProductVisibility | "";
status: ProductStatus | "";
builderEligible: "" | "true" | "false";
adminLocked: "" | "true" | "false";
};
export type PlatformDto = {
id: number;
key: string;
label: string;
is_active: boolean;
};
export const initialFilters: AdminFilters = {
q: "",
platform: "",
partRole: "",
caliber: "",
visibility: "",
status: "",
builderEligible: "",
adminLocked: "",
};