v1.5.6: boneyard-js skeleton migration, TanStack Query refactor, rate-limit config
- Replace hand-coded skeleton CSS/JSX with boneyard-js auto-generated bones - Remove skeleton.css and @keyframes shimmer from base.css - Add <Skeleton> wrappers with fixtures to all 25+ page components - Generate 20 bone captures via boneyard CLI (CDP auth-gated capture) - Refactor data fetching from useEffect+useState to TanStack Query - Extract query hooks into src/admin/lib/queries/ and apiAdapter - Add usePaginatedQuery hook replacing useApiCall/useListData - Fix parseFloat || 0 anti-pattern in OfferDetail and OffersTemplates inputs - Fix customer_id mandatory validation on offer creation - Fix leave-requests comma-separated status filter (Prisma enum in: []) - Add cross-entity cache invalidation for orders/offers/invoices/projects - Make rate limits configurable via env vars (RATE_LIMIT_MAX, RATE_LIMIT_REFRESH, etc.) - Add boneyard.config.json with routes and breakpoints Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
99
src/admin/lib/queries/attendance.ts
Normal file
99
src/admin/lib/queries/attendance.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import apiFetch from "../../utils/api";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
interface LocationRaw {
|
||||
users?: { first_name: string; last_name: string };
|
||||
user_name?: string;
|
||||
shift_date: string;
|
||||
arrival_time?: string | null;
|
||||
departure_time?: string | null;
|
||||
arrival_lat?: string | number | null;
|
||||
arrival_lng?: string | number | null;
|
||||
arrival_accuracy?: number | null;
|
||||
arrival_address?: string | null;
|
||||
departure_lat?: string | number | null;
|
||||
departure_lng?: string | number | null;
|
||||
departure_accuracy?: number | null;
|
||||
departure_address?: string | null;
|
||||
}
|
||||
|
||||
export interface LocationRecord {
|
||||
user_name: string;
|
||||
shift_date: string;
|
||||
arrival_time?: string | null;
|
||||
departure_time?: string | null;
|
||||
arrival_lat?: string | number | null;
|
||||
arrival_lng?: string | number | null;
|
||||
arrival_accuracy?: number | null;
|
||||
arrival_address?: string | null;
|
||||
departure_lat?: string | number | null;
|
||||
departure_lng?: string | number | null;
|
||||
departure_accuracy?: number | null;
|
||||
departure_address?: string | null;
|
||||
}
|
||||
|
||||
export const attendanceHistoryOptions = (filters: {
|
||||
month?: string;
|
||||
userId?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["attendance", "history", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("limit", "1000");
|
||||
if (filters.month) params.set("month", filters.month);
|
||||
if (filters.userId) params.set("user_id", String(filters.userId));
|
||||
return jsonQuery<Record<string, unknown>[]>(
|
||||
`/api/admin/attendance?${params.toString()}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const attendanceBalancesOptions = (year: number) =>
|
||||
queryOptions({
|
||||
queryKey: ["attendance", "balances", year],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
`/api/admin/attendance?action=balances&year=${year}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const attendanceWorkFundOptions = (year: number) =>
|
||||
queryOptions({
|
||||
queryKey: ["attendance", "workfund", year],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
`/api/admin/attendance?action=workfund&year=${year}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const attendanceProjectReportOptions = (year: number) =>
|
||||
queryOptions({
|
||||
queryKey: ["attendance", "project-report", year],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
`/api/admin/attendance?action=project_report&year=${year}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const attendanceLocationOptions = (id: string | undefined) =>
|
||||
queryOptions({
|
||||
queryKey: ["attendance", "location", id],
|
||||
queryFn: async (): Promise<LocationRecord> => {
|
||||
const response = await apiFetch(
|
||||
`/api/admin/attendance?action=location&id=${id}`,
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Záznam nebyl nalezen");
|
||||
}
|
||||
const raw = (result.data.record || result.data) as LocationRaw;
|
||||
const userName = raw.users
|
||||
? `${raw.users.first_name} ${raw.users.last_name}`.trim()
|
||||
: raw.user_name || "";
|
||||
const { users: _users, ...rest } = raw;
|
||||
return { ...rest, user_name: userName };
|
||||
},
|
||||
enabled: !!id,
|
||||
});
|
||||
28
src/admin/lib/queries/auditLog.ts
Normal file
28
src/admin/lib/queries/auditLog.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const auditLogOptions = (filters: {
|
||||
search?: string;
|
||||
action?: string;
|
||||
entityType?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
page?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["audit-log", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.action) params.set("action", filters.action);
|
||||
if (filters.entityType) params.set("entity_type", filters.entityType);
|
||||
if (filters.dateFrom) params.set("date_from", filters.dateFrom);
|
||||
if (filters.dateTo) params.set("date_to", filters.dateTo);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<{
|
||||
data: Record<string, unknown>[];
|
||||
pagination: Record<string, unknown>;
|
||||
}>(`/api/admin/audit-log${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
});
|
||||
18
src/admin/lib/queries/common.ts
Normal file
18
src/admin/lib/queries/common.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const bankAccountsOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["bank-accounts"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>[]>("/api/admin/bank-accounts"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
|
||||
export const supplierListOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["suppliers"],
|
||||
queryFn: () =>
|
||||
jsonQuery<string[]>("/api/admin/received-invoices/suppliers"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
42
src/admin/lib/queries/dashboard.ts
Normal file
42
src/admin/lib/queries/dashboard.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import apiFetch from "../../utils/api";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const dashboardOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["dashboard"],
|
||||
queryFn: () => jsonQuery<Record<string, unknown>>("/api/admin/dashboard"),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
export const require2FAOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["settings", "2fa"],
|
||||
queryFn: () =>
|
||||
jsonQuery<{ require_2fa: boolean }>("/api/admin/totp/required"),
|
||||
});
|
||||
|
||||
export interface Session {
|
||||
id: number | string;
|
||||
is_current: boolean;
|
||||
device_info?: {
|
||||
icon?: string;
|
||||
browser?: string;
|
||||
os?: string;
|
||||
};
|
||||
ip_address: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const sessionsOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["sessions"],
|
||||
queryFn: async (): Promise<Session[]> => {
|
||||
const response = await apiFetch("/api/admin/sessions");
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
return Array.isArray(data.data) ? data.data : data.data?.sessions || [];
|
||||
}
|
||||
throw new Error(data.error || "Nepodařilo se načíst relace");
|
||||
},
|
||||
});
|
||||
126
src/admin/lib/queries/invoices.ts
Normal file
126
src/admin/lib/queries/invoices.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
||||
|
||||
export interface CurrencyAmount {
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface Invoice {
|
||||
id: number;
|
||||
invoice_number: string;
|
||||
customer_name: string | null;
|
||||
status: string;
|
||||
issue_date: string;
|
||||
due_date: string;
|
||||
total: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface InvoiceStats {
|
||||
paid_month: CurrencyAmount[];
|
||||
paid_month_czk: number;
|
||||
paid_month_count: number;
|
||||
awaiting: CurrencyAmount[];
|
||||
awaiting_czk: number;
|
||||
awaiting_count: number;
|
||||
overdue: CurrencyAmount[];
|
||||
overdue_czk: number;
|
||||
overdue_count: number;
|
||||
vat_month: CurrencyAmount[];
|
||||
vat_month_czk: number;
|
||||
}
|
||||
|
||||
export const invoiceListOptions = (filters: {
|
||||
search?: string;
|
||||
sort?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
month?: number;
|
||||
year?: number;
|
||||
status?: string;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["invoices", "list", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.sort) params.set("sort", filters.sort);
|
||||
if (filters.order) params.set("order", filters.order);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
if (filters.month) params.set("month", String(filters.month));
|
||||
if (filters.year) params.set("year", String(filters.year));
|
||||
if (filters.status) params.set("status", filters.status);
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery<Invoice>(
|
||||
`/api/admin/invoices${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const receivedInvoiceListOptions = (filters: {
|
||||
month?: number;
|
||||
year?: number;
|
||||
search?: string;
|
||||
sort?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"invoices",
|
||||
"received",
|
||||
{
|
||||
month: filters.month,
|
||||
year: filters.year,
|
||||
search: filters.search,
|
||||
sort: filters.sort,
|
||||
order: filters.order,
|
||||
page: filters.page,
|
||||
perPage: filters.perPage,
|
||||
},
|
||||
],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.month) params.set("month", String(filters.month));
|
||||
if (filters.year) params.set("year", String(filters.year));
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.sort) params.set("sort", filters.sort);
|
||||
if (filters.order) params.set("order", filters.order);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery(
|
||||
`/api/admin/received-invoices${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const invoiceStatsOptions = (month: number, year: number) =>
|
||||
queryOptions({
|
||||
queryKey: ["invoices", "stats", month, year],
|
||||
queryFn: () =>
|
||||
jsonQuery<InvoiceStats>(
|
||||
`/api/admin/invoices/stats?month=${month}&year=${year}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const receivedInvoiceStatsOptions = (month: number, year: number) =>
|
||||
queryOptions({
|
||||
queryKey: ["invoices", "received", "stats", month, year],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
`/api/admin/received-invoices/stats?month=${month}&year=${year}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const invoiceDetailOptions = (id: string | undefined) =>
|
||||
queryOptions({
|
||||
queryKey: ["invoices", id],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(`/api/admin/invoices/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
29
src/admin/lib/queries/leave.ts
Normal file
29
src/admin/lib/queries/leave.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const leaveRequestsOptions = (mine = true) =>
|
||||
queryOptions({
|
||||
queryKey: ["leave-requests", mine ? "mine" : "all"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
`/api/admin/leave-requests${mine ? "?mine=1" : ""}`,
|
||||
),
|
||||
});
|
||||
|
||||
export const leavePendingOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["leave", "pending"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>[]>(
|
||||
"/api/admin/leave-requests?status=pending",
|
||||
),
|
||||
});
|
||||
|
||||
export const leaveProcessedOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["leave", "processed"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>[]>(
|
||||
"/api/admin/leave-requests?status=approved,rejected",
|
||||
),
|
||||
});
|
||||
58
src/admin/lib/queries/offers.ts
Normal file
58
src/admin/lib/queries/offers.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
||||
|
||||
export const offerCustomersOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["offer-customers"],
|
||||
queryFn: () => jsonQuery<Record<string, unknown>>("/api/admin/customers"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
|
||||
export const offerTemplatesOptions = (action?: string) =>
|
||||
queryOptions({
|
||||
queryKey: ["offer-templates", action ?? "all"],
|
||||
queryFn: () => {
|
||||
const url = action
|
||||
? `/api/admin/offers-templates?action=${action}`
|
||||
: "/api/admin/offers-templates";
|
||||
return jsonQuery<Record<string, unknown>[]>(url);
|
||||
},
|
||||
});
|
||||
|
||||
export const offerListOptions = (filters: {
|
||||
search?: string;
|
||||
sort?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["offers", "list", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.sort) params.set("sort", filters.sort);
|
||||
if (filters.order) params.set("order", filters.order);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery(`/api/admin/offers${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const offerDetailOptions = (id: string | undefined) =>
|
||||
queryOptions({
|
||||
queryKey: ["offers", id],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(`/api/admin/offers/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
|
||||
export const offerNextNumberOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["offers", "next-number"],
|
||||
queryFn: () =>
|
||||
jsonQuery<{ next_number?: string; number?: string }>(
|
||||
"/api/admin/offers/next-number",
|
||||
),
|
||||
});
|
||||
31
src/admin/lib/queries/orders.ts
Normal file
31
src/admin/lib/queries/orders.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
||||
|
||||
export const orderListOptions = (filters: {
|
||||
search?: string;
|
||||
sort?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["orders", "list", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.sort) params.set("sort", filters.sort);
|
||||
if (filters.order) params.set("order", filters.order);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery(`/api/admin/orders${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const orderDetailOptions = (id: string | undefined) =>
|
||||
queryOptions({
|
||||
queryKey: ["orders", id],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(`/api/admin/orders/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
78
src/admin/lib/queries/projects.ts
Normal file
78
src/admin/lib/queries/projects.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
||||
import apiFetch from "../../utils/api";
|
||||
|
||||
export const projectListOptions = (filters: {
|
||||
search?: string;
|
||||
sort?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["projects", "list", filters],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.sort) params.set("sort", filters.sort);
|
||||
if (filters.order) params.set("order", filters.order);
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery(`/api/admin/projects${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const projectDetailOptions = (id: string | undefined) =>
|
||||
queryOptions({
|
||||
queryKey: ["projects", id],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(`/api/admin/projects/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
|
||||
export interface ProjectFilesData {
|
||||
items: Array<{
|
||||
name: string;
|
||||
type: "file" | "folder";
|
||||
size?: number;
|
||||
size_formatted?: string;
|
||||
modified?: string;
|
||||
extension?: string;
|
||||
item_count?: number;
|
||||
is_symlink?: boolean;
|
||||
link_target?: string;
|
||||
}>;
|
||||
breadcrumb: string[];
|
||||
path: string;
|
||||
full_path: string;
|
||||
}
|
||||
|
||||
export const projectFilesOptions = (projectId: number, path: string) =>
|
||||
queryOptions({
|
||||
queryKey: ["projects", String(projectId), "files", path],
|
||||
queryFn: async (): Promise<ProjectFilesData> => {
|
||||
const params = new URLSearchParams({ project_id: String(projectId) });
|
||||
if (path) params.set("path", path);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await apiFetch(`/api/admin/project-files?${params}`);
|
||||
} catch {
|
||||
throw new Error("Chyba připojení");
|
||||
}
|
||||
if (res.status === 401) throw new Error("Unauthorized");
|
||||
if (res.status === 404) {
|
||||
return { items: [], breadcrumb: [""], path: "", full_path: "" };
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.success) {
|
||||
throw new Error(data.error || `Request failed (${res.status})`);
|
||||
}
|
||||
return {
|
||||
items: data.data.items || [],
|
||||
breadcrumb: data.data.breadcrumb || [""],
|
||||
path: data.data.path || "",
|
||||
full_path: data.data.full_path || "",
|
||||
};
|
||||
},
|
||||
});
|
||||
29
src/admin/lib/queries/settings.ts
Normal file
29
src/admin/lib/queries/settings.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const companySettingsOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["company-settings"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>("/api/admin/company-settings"),
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
export const systemInfoOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["settings", "system-info"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>>(
|
||||
"/api/admin/company-settings/system-info",
|
||||
),
|
||||
});
|
||||
|
||||
/** @deprecated Use systemInfoOptions instead — this query fetches system-info, not system settings. */
|
||||
export const systemSettingsOptions = systemInfoOptions;
|
||||
|
||||
export const require2FAOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["settings", "2fa"],
|
||||
queryFn: () =>
|
||||
jsonQuery<{ require_2fa: boolean }>("/api/admin/totp/required"),
|
||||
});
|
||||
82
src/admin/lib/queries/trips.ts
Normal file
82
src/admin/lib/queries/trips.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const tripListOptions = (filters: {
|
||||
month?: number;
|
||||
year?: number;
|
||||
vehicleId?: number;
|
||||
userId?: number;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"trips",
|
||||
"list",
|
||||
{
|
||||
month: filters.month,
|
||||
year: filters.year,
|
||||
vehicleId: filters.vehicleId,
|
||||
userId: filters.userId,
|
||||
page: filters.page,
|
||||
perPage: filters.perPage,
|
||||
},
|
||||
],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.month) params.set("month", String(filters.month));
|
||||
if (filters.year) params.set("year", String(filters.year));
|
||||
if (filters.vehicleId)
|
||||
params.set("vehicle_id", String(filters.vehicleId));
|
||||
if (filters.userId) params.set("user_id", String(filters.userId));
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<Record<string, unknown>[]>(
|
||||
`/api/admin/trips${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const tripVehiclesOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["trips", "vehicles"],
|
||||
queryFn: () => jsonQuery<Record<string, unknown>[]>("/api/admin/vehicles"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
|
||||
export const tripUsersOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["trips", "users"],
|
||||
queryFn: () =>
|
||||
jsonQuery<Record<string, unknown>[]>("/api/admin/trips/users"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
|
||||
export const tripHistoryOptions = (filters: {
|
||||
month?: string;
|
||||
vehicleId?: number;
|
||||
userId?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"trips",
|
||||
"history",
|
||||
{
|
||||
month: filters.month,
|
||||
vehicleId: filters.vehicleId,
|
||||
userId: filters.userId,
|
||||
},
|
||||
],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.month) params.set("month", filters.month);
|
||||
if (filters.vehicleId)
|
||||
params.set("vehicle_id", String(filters.vehicleId));
|
||||
if (filters.userId) params.set("user_id", String(filters.userId));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<Record<string, unknown>[]>(
|
||||
`/api/admin/trips${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
18
src/admin/lib/queries/users.ts
Normal file
18
src/admin/lib/queries/users.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const userListOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["users"],
|
||||
queryFn: () => {
|
||||
// The users endpoint returns { success, data: { items, ... } } or { success, data: [...] }
|
||||
return jsonQuery<Record<string, unknown>>("/api/admin/users");
|
||||
},
|
||||
});
|
||||
|
||||
export const roleListOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["roles"],
|
||||
queryFn: () => jsonQuery<Record<string, unknown>[]>("/api/admin/roles"),
|
||||
staleTime: 2 * 60_000,
|
||||
});
|
||||
8
src/admin/lib/queries/vehicles.ts
Normal file
8
src/admin/lib/queries/vehicles.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
|
||||
export const vehicleListOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["vehicles"],
|
||||
queryFn: () => jsonQuery<Record<string, unknown>[]>("/api/admin/vehicles"),
|
||||
});
|
||||
Reference in New Issue
Block a user