khata compliant with new react-openapi
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
function getNested(obj: any, path: string): any {
|
||||
return path.split(".").reduce((o, k) => o?.[k], obj);
|
||||
}
|
||||
|
||||
export function applyDisplayFormat(item: any, format: string): string {
|
||||
if (!item || typeof item !== "object") return String(item ?? "");
|
||||
return format.replace(/\{(\w+)\}/g, (_, key) => String(item[key] ?? ""));
|
||||
return format.replace(/\{([\w.]+)\}/g, (_, key) => {
|
||||
const val = getNested(item, key);
|
||||
return val != null ? String(val) : "";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -350,12 +350,24 @@ function buildFilterComponent(field: FieldConfig, resourceName: string): React.F
|
||||
export function useResource(resourceName: string): UseResourceReturn {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find((r) => r.name === resourceName);
|
||||
if (!resource) {
|
||||
throw new Error(`Resource "${resourceName}" not found`);
|
||||
}
|
||||
|
||||
const [state, setState] = useState<ResourceState>({ loading: false, error: null });
|
||||
|
||||
if (!resource) {
|
||||
const noop = async () => { throw new Error(`Resource "${resourceName}" not found yet`); };
|
||||
return {
|
||||
resource: null as unknown as ResourceConfig,
|
||||
components: {},
|
||||
list: noop,
|
||||
get: noop,
|
||||
create: noop,
|
||||
update: noop,
|
||||
remove: noop,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
const setLoading = useCallback((loading: boolean) => {
|
||||
setState((s) => ({ ...s, loading }));
|
||||
}, []);
|
||||
@@ -437,7 +449,8 @@ export function useResource(resourceName: string): UseResourceReturn {
|
||||
setError(null);
|
||||
try {
|
||||
const api = getApi();
|
||||
const res = await api.put(`${resource.path}/${id}`, data);
|
||||
const method = resource.updateMethod ?? "put";
|
||||
const res = await (method === "patch" ? api.patch : api.put)(`${resource.path}/${id}`, data);
|
||||
return res.data;
|
||||
} catch (e: any) {
|
||||
setError(parseError(e));
|
||||
@@ -446,7 +459,7 @@ export function useResource(resourceName: string): UseResourceReturn {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[resource.path, setLoading, setError]
|
||||
[resource.path, resource.updateMethod, setLoading, setError]
|
||||
);
|
||||
|
||||
const remove = useCallback(
|
||||
|
||||
@@ -120,10 +120,10 @@ export function validateSpec(spec: OpenApiSpec): ValidationMessage[] {
|
||||
messages.push({ type: "error", message: `"${resourcePath}/{id}" has no GET endpoint — detail view not possible` });
|
||||
}
|
||||
if (!itemPath?.put) {
|
||||
messages.push({ type: "error", message: `"${resourcePath}/{id}" has no PUT endpoint — update not possible` });
|
||||
messages.push({ type: "info", message: `"${resourcePath}/{id}" has no PUT endpoint — update not available` });
|
||||
}
|
||||
if (!itemPath?.delete) {
|
||||
messages.push({ type: "error", message: `"${resourcePath}/{id}" has no DELETE endpoint — deletion not possible` });
|
||||
messages.push({ type: "info", message: `"${resourcePath}/{id}" has no DELETE endpoint — deletion not available` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,10 @@ export function buildResourceConfigs(spec: OpenApiSpec): ResourceConfig[] {
|
||||
list: hasOperation(collectionPathObj, "get"),
|
||||
get: hasOperation(itemPathObj, "get"),
|
||||
create: hasOperation(collectionPathObj, "post"),
|
||||
update: hasOperation(itemPathObj, "put"),
|
||||
update: hasOperation(itemPathObj, "put") || hasOperation(itemPathObj, "patch"),
|
||||
delete: hasOperation(itemPathObj, "delete"),
|
||||
},
|
||||
updateMethod: hasOperation(itemPathObj, "patch") && !hasOperation(itemPathObj, "put") ? "patch" : "put",
|
||||
pagination: detectPagination(collectionPathObj),
|
||||
relationships,
|
||||
streaming: hasSSE || undefined,
|
||||
@@ -91,6 +92,7 @@ export function buildResourceConfigs(spec: OpenApiSpec): ResourceConfig[] {
|
||||
|
||||
if (hasSSE) {
|
||||
resource.operations = { list: true, get: false, create: false, update: false, delete: false };
|
||||
resource.updateMethod = "put";
|
||||
resource.pagination = null;
|
||||
resource.relationships = [];
|
||||
resource.fields = [SSE_RECEIVED_FIELD, ...fields.map((f) => ({ ...f, readOnly: true }))];
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface ResourceConfig {
|
||||
update: boolean;
|
||||
delete: boolean;
|
||||
};
|
||||
updateMethod: "put" | "patch";
|
||||
pagination: {
|
||||
limitParam: string;
|
||||
offsetParam: string;
|
||||
|
||||
@@ -145,9 +145,8 @@ function isMathValid(candidate: { amount: number; balance: number }, prevBalance
|
||||
export default function FetchRequestDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { resources, config } = useAppContext();
|
||||
const fetchRequestResource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { get, update } = useResource(fetchRequestResource);
|
||||
const { config } = useAppContext();
|
||||
const { get, update } = useResource("fetch-requests");
|
||||
|
||||
const { data: fetchRequest, isLoading, error: fetchError, refetch: refetchRequest } = useQuery({
|
||||
queryKey: ["fetch-requests", "detail", id],
|
||||
|
||||
@@ -47,7 +47,7 @@ import type {
|
||||
} from "./features/fetch-requests";
|
||||
import { RETRY_MAX, formatApiError } from "./features/fetch-requests";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAppContext, useResource, FormFieldRenderer } from "../react-openapi";
|
||||
import { useResource, FormFieldRenderer } from "../react-openapi";
|
||||
import type { FieldConfig } from "../react-openapi";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
@@ -118,9 +118,8 @@ export default function FetchRequests() {
|
||||
const [accountFilter, setAccountFilter] = React.useState("");
|
||||
const [sourceFilter, setSourceFilter] = React.useState<"all" | "file" | "email">("all");
|
||||
|
||||
const { resources } = useAppContext();
|
||||
const fetchRequestsRes = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { list, create, update, remove } = useResource(fetchRequestsRes);
|
||||
const fr = useResource("fetch-requests");
|
||||
const { list, create, update, remove, resource: fetchRes } = fr;
|
||||
|
||||
const { data: listData, isLoading, isFetching, refetch } = useQuery({
|
||||
queryKey: ["fetch-requests", "list", { statusFilter, accountFilter, sourceFilter }],
|
||||
@@ -131,22 +130,21 @@ export default function FetchRequests() {
|
||||
}),
|
||||
});
|
||||
|
||||
const accountsResource = resources.find(r => r.name === "accounts");
|
||||
const { list: listAccounts } = accountsResource ? useResource(accountsResource) : { list: async () => ({ items: [] }) };
|
||||
const { list: listAccounts } = useResource("accounts");
|
||||
const { data: accountsData } = useQuery({
|
||||
queryKey: ["accounts", "list"],
|
||||
queryFn: () => listAccounts(),
|
||||
enabled: !!accountsResource,
|
||||
});
|
||||
const accountOptions: string[] = React.useMemo(() => {
|
||||
return (accountsData?.items ?? []).map((a: any) => a.name).filter(Boolean);
|
||||
}, [accountsData]);
|
||||
|
||||
const formatField: FieldConfig | undefined = fetchRequestsRes?.orderedFields.find(f => f.name === "format");
|
||||
const fields = fetchRes?.orderedFields ?? [];
|
||||
const formatField: FieldConfig | undefined = fields.find(f => f.name === "format");
|
||||
const formatOptions: string[] = formatField?.enumValues ?? [];
|
||||
const startDateField: FieldConfig | undefined = fetchRequestsRes?.orderedFields.find(f => f.name === "start_date");
|
||||
const endDateField: FieldConfig | undefined = fetchRequestsRes?.orderedFields.find(f => f.name === "end_date");
|
||||
const payorUsernameField: FieldConfig | undefined = fetchRequestsRes?.orderedFields.find(f => f.name === "payor_username");
|
||||
const startDateField: FieldConfig | undefined = fields.find(f => f.name === "start_date");
|
||||
const endDateField: FieldConfig | undefined = fields.find(f => f.name === "end_date");
|
||||
const payorUsernameField: FieldConfig | undefined = fields.find(f => f.name === "payor_username");
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => create(data),
|
||||
|
||||
@@ -21,8 +21,7 @@ import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import AddCircleIcon from "@mui/icons-material/AddCircle";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||
import { useAppContext, useResource, FormFieldRenderer } from "../react-openapi";
|
||||
import type { FieldConfig } from "../react-openapi";
|
||||
import { useResource } from "../react-openapi";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
interface ReportSnapshotQuery {
|
||||
@@ -54,9 +53,7 @@ export default function ReportSnapshots() {
|
||||
const [deleteTarget, setDeleteTarget] = React.useState<ReportSnapshot | null>(null);
|
||||
const [createdSnapshotId, setCreatedSnapshotId] = React.useState<string | null>(null);
|
||||
|
||||
const { resources } = useAppContext();
|
||||
const reportsResource = resources.find(r => r.name === "reports")!;
|
||||
const { list, create, remove } = useResource(reportsResource);
|
||||
const { list, create, remove } = useResource("reports");
|
||||
|
||||
const { data: listData, isLoading, isFetching, refetch } = useQuery({
|
||||
queryKey: ["reports", "list"],
|
||||
@@ -73,12 +70,6 @@ export default function ReportSnapshots() {
|
||||
},
|
||||
});
|
||||
|
||||
const ignoreSelfField: FieldConfig | undefined = reportsResource?.orderedFields.find(f => f.name === "ignore_self");
|
||||
const startDateField: FieldConfig | undefined = reportsResource?.orderedFields.find(f => f.name === "start_date");
|
||||
const endDateField: FieldConfig | undefined = reportsResource?.orderedFields.find(f => f.name === "end_date");
|
||||
const minAmountField: FieldConfig | undefined = reportsResource?.orderedFields.find(f => f.name === "min_amount");
|
||||
const maxAmountField: FieldConfig | undefined = reportsResource?.orderedFields.find(f => f.name === "max_amount");
|
||||
|
||||
const snapshots: ReportSnapshot[] = listData?.items ?? [];
|
||||
|
||||
const handleCreate = async () => {
|
||||
@@ -135,58 +126,50 @@ export default function ReportSnapshots() {
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
{ignoreSelfField ? (
|
||||
<FormFieldRenderer
|
||||
field={ignoreSelfField}
|
||||
value={ignoreSelf}
|
||||
onChange={(val: boolean) => setIgnoreSelf(val)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="Start Date"
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setStartDate(e.target.value)}
|
||||
size="small"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
inputProps={{ max: new Date().toISOString().split("T")[0] }}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="End Date"
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEndDate(e.target.value)}
|
||||
size="small"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
inputProps={{ max: new Date().toISOString().split("T")[0] }}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="Start Date"
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setStartDate(e.target.value)}
|
||||
size="small"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
inputProps={{ max: new Date().toISOString().split("T")[0] }}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="End Date"
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEndDate(e.target.value)}
|
||||
size="small"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
inputProps={{ max: new Date().toISOString().split("T")[0] }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
{minAmountField ? (
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<FormFieldRenderer
|
||||
field={minAmountField}
|
||||
value={minAmount}
|
||||
onChange={(val: string) => setMinAmount(val)}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{maxAmountField ? (
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<FormFieldRenderer
|
||||
field={maxAmountField}
|
||||
value={maxAmount}
|
||||
onChange={(val: string) => setMaxAmount(val)}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="Min Amount"
|
||||
type="number"
|
||||
value={minAmount}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMinAmount(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
label="Max Amount"
|
||||
type="number"
|
||||
value={maxAmount}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMaxAmount(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAppContext, useResource, getApi } from "../../../react-openapi";
|
||||
import { useResource, getApi } from "../../../react-openapi";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { ResolveAmbiguityPayload } from "./fetch-requests.models";
|
||||
|
||||
@@ -7,9 +7,7 @@ export function useFetchRequestsList(params?: {
|
||||
account_name?: string;
|
||||
source_type?: string;
|
||||
}) {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { list } = useResource(resource);
|
||||
const { list } = useResource("fetch-requests");
|
||||
return useQuery({
|
||||
queryKey: ["fetch-requests", "list", params],
|
||||
queryFn: () => list(params),
|
||||
@@ -17,9 +15,7 @@ export function useFetchRequestsList(params?: {
|
||||
}
|
||||
|
||||
export function useFetchRequest(id: string) {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { get } = useResource(resource);
|
||||
const { get } = useResource("fetch-requests");
|
||||
return useQuery({
|
||||
queryKey: ["fetch-requests", "detail", id],
|
||||
queryFn: () => get(id),
|
||||
@@ -28,27 +24,21 @@ export function useFetchRequest(id: string) {
|
||||
}
|
||||
|
||||
export function useCreateFetchRequest() {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { create } = useResource(resource);
|
||||
const { create } = useResource("fetch-requests");
|
||||
return useMutation({
|
||||
mutationFn: (data: any) => create(data),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateFetchRequest() {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { update } = useResource(resource);
|
||||
const { update } = useResource("fetch-requests");
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: any }) => update(id, data),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteFetchRequest() {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "fetch-requests")!;
|
||||
const { remove } = useResource(resource);
|
||||
const { remove } = useResource("fetch-requests");
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => remove(id),
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useAppContext, useResource } from "../../../react-openapi";
|
||||
import { useResource } from "../../../react-openapi";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export function useReportSnapshotsList() {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "reports")!;
|
||||
const { list } = useResource(resource);
|
||||
const { list } = useResource("reports");
|
||||
return useQuery({
|
||||
queryKey: ["reports", "list"],
|
||||
queryFn: () => list(),
|
||||
@@ -12,9 +10,7 @@ export function useReportSnapshotsList() {
|
||||
}
|
||||
|
||||
export function useCreateSnapshot() {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "reports")!;
|
||||
const { create } = useResource(resource);
|
||||
const { create } = useResource("reports");
|
||||
return useMutation({
|
||||
mutationFn: (data: any) => create(data),
|
||||
});
|
||||
@@ -22,9 +18,7 @@ export function useCreateSnapshot() {
|
||||
|
||||
export function useDeleteSnapshot() {
|
||||
const queryClient = useQueryClient();
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "reports")!;
|
||||
const { remove } = useResource(resource);
|
||||
const { remove } = useResource("reports");
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => remove(id),
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAppContext, useResource } from "../../../react-openapi";
|
||||
import { useResource } from "../../../react-openapi";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
export interface ReportParams {
|
||||
@@ -10,9 +10,7 @@ export interface ReportParams {
|
||||
}
|
||||
|
||||
export function useReport(params: ReportParams) {
|
||||
const { resources } = useAppContext();
|
||||
const resource = resources.find(r => r.name === "reports")!;
|
||||
const { get } = useResource(resource);
|
||||
const { get } = useResource("reports");
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["reports", "read", params],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
BrowserRouter,
|
||||
Routes,
|
||||
@@ -23,6 +24,8 @@ import Header from './Header';
|
||||
import Footer from './Footer';
|
||||
import AppTheme from './shared-theme/AppTheme';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
window.Buffer = Buffer;
|
||||
window.process = process;
|
||||
|
||||
@@ -50,6 +53,7 @@ const routerMapping = [
|
||||
];
|
||||
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppProvider specConfiguration={specConfig}>
|
||||
<BrowserRouter>
|
||||
<AuthProvider authBaseUrl={AUTH_BASE}>
|
||||
@@ -82,4 +86,5 @@ root.render(
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</AppProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user