mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
improve endpoints
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
VITE_API_URL=http://localhost:3001
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const FILE_SETTINGS = {
|
||||||
|
CUSTOMER_REGISTRATION: "customer_registration"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const QUERY_KEYS = {
|
||||||
|
USERS: "users",
|
||||||
|
CUSTOMER: "Customers",
|
||||||
|
FILES: {
|
||||||
|
FILE_UPLOAD_SETTINGS: "file-upload-settings",
|
||||||
|
BY_CODE: "by-code"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ export const URL_CONSTANTS = {
|
|||||||
BASE: "/files",
|
BASE: "/files",
|
||||||
UPLOAD: "/files/upload",
|
UPLOAD: "/files/upload",
|
||||||
FILE_UPLOAD_SETTINGS: "/files/upload",
|
FILE_UPLOAD_SETTINGS: "/files/upload",
|
||||||
|
FILE_UPLOAD_SETTINGS_BY_CODE: "/api/file-upload-settings/by-code",
|
||||||
DOWNLOAD: (id: string | number) => `/files/${id}/download`,
|
DOWNLOAD: (id: string | number) => `/files/${id}/download`,
|
||||||
DELETE: (id: string | number) => `/files/${id}`,
|
DELETE: (id: string | number) => `/files/${id}`,
|
||||||
BY_ID: (id: string | number) => `/files/${id}`,
|
BY_ID: (id: string | number) => `/files/${id}`,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import { IFileUploadSetting } from "@edr/types/freight";
|
||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
SmartFileInput,
|
||||||
} from "@edr/ui-common";
|
} from "@edr/ui-common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +24,9 @@ import {
|
|||||||
MapPin,
|
MapPin,
|
||||||
FileText,
|
FileText,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { getFileUploadSettingByCode } from "@/services/fileUploadSettings.service";
|
||||||
|
import { FILE_SETTINGS } from "@/constants/FILE_SETTINGS";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
export interface CustomerFormData {
|
export interface CustomerFormData {
|
||||||
companyName?: string;
|
companyName?: string;
|
||||||
@@ -46,7 +51,78 @@ export default function NewCustomerPage({
|
|||||||
mode = "create",
|
mode = "create",
|
||||||
customer,
|
customer,
|
||||||
children,
|
children,
|
||||||
}: NewCustomerPageProps = {}) {
|
}: NewCustomerPageProps = {}) { //getFileUploadSettingByCode
|
||||||
|
const [files, setFiles] = useState<
|
||||||
|
Record<string, File | File[] | null>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
const { data: customerRegistrationFiles, isLoading, isError, error } = useQuery(
|
||||||
|
getFileUploadSettingByCode.queryOptions({
|
||||||
|
input: FILE_SETTINGS.CUSTOMER_REGISTRATION,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
console.log("customerRegistrationFiles", customerRegistrationFiles)
|
||||||
|
// const shipmentFileUploadSetting: IFileUploadSetting = {
|
||||||
|
// id: "setting_1",
|
||||||
|
// code: "shipment_documents",
|
||||||
|
// label: "Shipment Documents",
|
||||||
|
// description: "Upload all required shipment-related documents",
|
||||||
|
|
||||||
|
// entity: "shipment", // depends on your FileUploadEntity enum/type
|
||||||
|
|
||||||
|
// fields: [
|
||||||
|
// {
|
||||||
|
// id: "field_1",
|
||||||
|
// fileKey: "invoice",
|
||||||
|
// fileLabel: "Invoice",
|
||||||
|
// order: 1,
|
||||||
|
// isRequired: true,
|
||||||
|
// isMultiple: false,
|
||||||
|
// maxFiles: 1,
|
||||||
|
// maxSizeMb: 5,
|
||||||
|
// allowedExtensions: ["pdf", "jpg", "png"],
|
||||||
|
// helpText: "Upload commercial invoice",
|
||||||
|
// settingId: "",
|
||||||
|
// createdAt: "",
|
||||||
|
// updatedAt: ""
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// id: "field_2",
|
||||||
|
// fileKey: "packing_list",
|
||||||
|
// fileLabel: "Packing List",
|
||||||
|
// order: 2,
|
||||||
|
// isRequired: false,
|
||||||
|
// isMultiple: true,
|
||||||
|
// maxFiles: 3,
|
||||||
|
// maxSizeMb: 10,
|
||||||
|
// allowedExtensions: ["pdf", "xlsx"],
|
||||||
|
// helpText: "Optional packing list documents",
|
||||||
|
// settingId: "",
|
||||||
|
// createdAt: "",
|
||||||
|
// updatedAt: ""
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// id: "field_3",
|
||||||
|
// fileKey: "cargo_images",
|
||||||
|
// fileLabel: "Cargo Images",
|
||||||
|
// order: 3,
|
||||||
|
// isRequired: false,
|
||||||
|
// isMultiple: true,
|
||||||
|
// maxFiles: 5,
|
||||||
|
// maxSizeMb: 2,
|
||||||
|
// allowedExtensions: ["jpg", "jpeg", "png"],
|
||||||
|
// helpText: "Photos of cargo condition",
|
||||||
|
// settingId: "",
|
||||||
|
// createdAt: "",
|
||||||
|
// updatedAt: ""
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// createdAt: "",
|
||||||
|
// updatedAt: ""
|
||||||
|
// };
|
||||||
|
|
||||||
const isEdit = mode === "edit";
|
const isEdit = mode === "edit";
|
||||||
const title = isEdit ? "Edit Customer" : "New Customer";
|
const title = isEdit ? "Edit Customer" : "New Customer";
|
||||||
const description = isEdit
|
const description = isEdit
|
||||||
@@ -208,7 +284,16 @@ export default function NewCustomerPage({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
customerRegistrationFiles &&
|
||||||
|
<SmartFileInput
|
||||||
|
file={customerRegistrationFiles}
|
||||||
|
value={files}
|
||||||
|
onChange={setFiles}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
<Button variant="outline">Cancel</Button>
|
<Button variant="outline">Cancel</Button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { api } from "./crud";
|
import { endpoint, unwrap } from "@/utils/endpoint";
|
||||||
|
import { client } from "@/utils/api";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CreateFileUploadFieldDto,
|
CreateFileUploadFieldDto,
|
||||||
@@ -8,6 +9,9 @@ import type {
|
|||||||
UpdateFileUploadFieldDto,
|
UpdateFileUploadFieldDto,
|
||||||
UpdateFileUploadSettingDto,
|
UpdateFileUploadSettingDto,
|
||||||
} from "@/types/fileUploadSettings";
|
} from "@/types/fileUploadSettings";
|
||||||
|
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||||
|
import { QUERY_KEYS } from "@/constants/TANSTACK_QUEY_KEY";
|
||||||
|
import { ApiResponse } from "@/types/apiResponse";
|
||||||
|
|
||||||
const BASE = "/api/file-upload-settings";
|
const BASE = "/api/file-upload-settings";
|
||||||
|
|
||||||
@@ -18,25 +22,24 @@ const BASE = "/api/file-upload-settings";
|
|||||||
*/
|
*/
|
||||||
type Envelope<T> = { data: T } | T;
|
type Envelope<T> = { data: T } | T;
|
||||||
|
|
||||||
function unwrap<T>(payload: Envelope<T>): T {
|
// function unwrap<T>(payload: Envelope<T>): T {
|
||||||
if (
|
// if (
|
||||||
payload &&
|
// payload &&
|
||||||
typeof payload === "object" &&
|
// typeof payload === "object" &&
|
||||||
"data" in (payload as object)
|
// "data" in (payload as object)
|
||||||
) {
|
// ) {
|
||||||
return (payload as { data: T }).data;
|
// return (payload as { data: T }).data;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return payload as T;
|
// return payload as T;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const fileUploadSettingsService = {
|
export const fileUploadSettingsService = {
|
||||||
// GET /file-upload-settings
|
// GET /file-upload-settings
|
||||||
list: async (): Promise<FileUploadSetting[]> => {
|
list: async (): Promise<FileUploadSetting[]> => {
|
||||||
const response =
|
const response =
|
||||||
await api.get<Envelope<FileUploadSetting[]>>(BASE);
|
await client.get<ApiResponse<FileUploadSetting[]>>(BASE);
|
||||||
|
return unwrap(response.data);
|
||||||
return unwrap(response);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// GET /file-upload-settings/:id
|
// GET /file-upload-settings/:id
|
||||||
@@ -44,11 +47,11 @@ export const fileUploadSettingsService = {
|
|||||||
id: string
|
id: string
|
||||||
): Promise<FileUploadSetting> => {
|
): Promise<FileUploadSetting> => {
|
||||||
const response =
|
const response =
|
||||||
await api.get<Envelope<FileUploadSetting>>(
|
await client.get<ApiResponse<FileUploadSetting>>(
|
||||||
`${BASE}/${id}`
|
`${BASE}/${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return unwrap(response);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// GET /file-upload-settings/by-code/:code
|
// GET /file-upload-settings/by-code/:code
|
||||||
@@ -56,11 +59,11 @@ export const fileUploadSettingsService = {
|
|||||||
code: string
|
code: string
|
||||||
): Promise<FileUploadSetting> => {
|
): Promise<FileUploadSetting> => {
|
||||||
const response =
|
const response =
|
||||||
await api.get<Envelope<FileUploadSetting>>(
|
await client.get<ApiResponse<FileUploadSetting>>(
|
||||||
`${BASE}/by-code/${encodeURIComponent(code)}`
|
`${BASE}/by-code/${encodeURIComponent(code)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return unwrap(response);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// POST /file-upload-settings
|
// POST /file-upload-settings
|
||||||
@@ -68,12 +71,12 @@ export const fileUploadSettingsService = {
|
|||||||
payload: CreateFileUploadSettingDto
|
payload: CreateFileUploadSettingDto
|
||||||
): Promise<FileUploadSetting> => {
|
): Promise<FileUploadSetting> => {
|
||||||
const response =
|
const response =
|
||||||
await api.post<Envelope<FileUploadSetting>>(
|
await client.post<ApiResponse<FileUploadSetting>>(
|
||||||
BASE,
|
BASE,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
|
|
||||||
return unwrap(response);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// PATCH /file-upload-settings/:id
|
// PATCH /file-upload-settings/:id
|
||||||
@@ -82,17 +85,17 @@ export const fileUploadSettingsService = {
|
|||||||
payload: UpdateFileUploadSettingDto
|
payload: UpdateFileUploadSettingDto
|
||||||
): Promise<FileUploadSetting> => {
|
): Promise<FileUploadSetting> => {
|
||||||
const response =
|
const response =
|
||||||
await api.patch<Envelope<FileUploadSetting>>(
|
await client.patch<ApiResponse<FileUploadSetting>>(
|
||||||
`${BASE}/${id}`,
|
`${BASE}/${id}`,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
|
|
||||||
return unwrap(response);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// DELETE /file-upload-settings/:id
|
// DELETE /file-upload-settings/:id
|
||||||
remove: async (id: string): Promise<void> => {
|
remove: async (id: string): Promise<void> => {
|
||||||
await api.delete(`${BASE}/${id}`);
|
await client.delete(`${BASE}/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// PUT /file-upload-settings/:id/fields
|
// PUT /file-upload-settings/:id/fields
|
||||||
@@ -101,12 +104,11 @@ export const fileUploadSettingsService = {
|
|||||||
fields: CreateFileUploadFieldDto[]
|
fields: CreateFileUploadFieldDto[]
|
||||||
): Promise<FileUploadField[]> => {
|
): Promise<FileUploadField[]> => {
|
||||||
const response =
|
const response =
|
||||||
await api.put<Envelope<FileUploadField[]>>(
|
await client.put<ApiResponse<FileUploadField[]>>(
|
||||||
`${BASE}/${id}/fields`,
|
`${BASE}/${id}/fields`,
|
||||||
fields
|
fields
|
||||||
);
|
);
|
||||||
|
return unwrap(response.data);
|
||||||
return unwrap(response);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// POST /file-upload-settings/:id/fields
|
// POST /file-upload-settings/:id/fields
|
||||||
@@ -115,12 +117,12 @@ export const fileUploadSettingsService = {
|
|||||||
payload: CreateFileUploadFieldDto
|
payload: CreateFileUploadFieldDto
|
||||||
): Promise<FileUploadField> => {
|
): Promise<FileUploadField> => {
|
||||||
const response =
|
const response =
|
||||||
await api.post<Envelope<FileUploadField>>(
|
await client.post<ApiResponse<FileUploadField>>(
|
||||||
`${BASE}/${id}/fields`,
|
`${BASE}/${id}/fields`,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
|
|
||||||
return unwrap(response);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// PATCH /file-upload-settings/fields/:fieldId
|
// PATCH /file-upload-settings/fields/:fieldId
|
||||||
@@ -129,20 +131,29 @@ export const fileUploadSettingsService = {
|
|||||||
payload: UpdateFileUploadFieldDto
|
payload: UpdateFileUploadFieldDto
|
||||||
): Promise<FileUploadField> => {
|
): Promise<FileUploadField> => {
|
||||||
const response =
|
const response =
|
||||||
await api.patch<Envelope<FileUploadField>>(
|
await client.patch<ApiResponse<FileUploadField>>(
|
||||||
`${BASE}/fields/${fieldId}`,
|
`${BASE}/fields/${fieldId}`,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
|
return unwrap(response.data);
|
||||||
return unwrap(response);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// DELETE /file-upload-settings/fields/:fieldId
|
// DELETE /file-upload-settings/fields/:fieldId
|
||||||
removeField: async (
|
removeField: async (
|
||||||
fieldId: string
|
fieldId: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await api.delete(
|
await client.delete(
|
||||||
`${BASE}/fields/${fieldId}`
|
`${BASE}/fields/${fieldId}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getFileUploadSettingByCode = endpoint<string, FileUploadSetting>(
|
||||||
|
QUERY_KEYS.FILES.FILE_UPLOAD_SETTINGS,
|
||||||
|
QUERY_KEYS.FILES.BY_CODE,
|
||||||
|
(code) =>
|
||||||
|
client
|
||||||
|
.get<ApiResponse<FileUploadSetting>>(`${URL_CONSTANTS.FILES.FILE_UPLOAD_SETTINGS_BY_CODE}/${code}`)
|
||||||
|
.then(res => res.data.data)
|
||||||
|
);
|
||||||
5
apps/edr-freight-web/portal/src/types/apiResponse.ts
Normal file
5
apps/edr-freight-web/portal/src/types/apiResponse.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type ApiResponse<T> = {
|
||||||
|
success: boolean;
|
||||||
|
data: T;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
import { QueryObserverOptions, UseQueryOptions } from "@tanstack/react-query";
|
import {
|
||||||
|
UseQueryOptions,
|
||||||
|
QueryObserverOptions,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Axios client
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export const client = axios.create({
|
export const client = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
baseURL: import.meta.env.VITE_API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Attach auth token to every request
|
||||||
client.interceptors.request.use((config) => {
|
client.interceptors.request.use((config) => {
|
||||||
|
// TODO: replace with secure storage (cookie/localStorage/auth provider)
|
||||||
const token = document.cookie
|
const token = document.cookie
|
||||||
.split("; ")
|
.split("; ")
|
||||||
.find((row) => row.startsWith("auth-token="))
|
.find((row) => row.startsWith("auth-token="))
|
||||||
@@ -13,9 +22,11 @@ client.interceptors.request.use((config) => {
|
|||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle auth errors globally
|
||||||
client.interceptors.response.use(
|
client.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -23,90 +34,5 @@ client.interceptors.response.use(
|
|||||||
window.location.href = "/auth";
|
window.location.href = "/auth";
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Types
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export type QueryConfig<T> = Omit<
|
|
||||||
UseQueryOptions<T, Error, T, readonly unknown[]>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface EndpointWithInput<TInput, TResponse> {
|
|
||||||
call(input: TInput): Promise<TResponse>;
|
|
||||||
queryKey(): readonly unknown[];
|
|
||||||
queryKey(input: TInput): readonly unknown[];
|
|
||||||
queryOptions(
|
|
||||||
config: { input: TInput } & QueryConfig<TResponse>,
|
|
||||||
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EndpointWithoutInput<TResponse> {
|
|
||||||
call(): Promise<TResponse>;
|
|
||||||
queryKey(): readonly unknown[];
|
|
||||||
queryOptions(
|
|
||||||
config?: QueryConfig<TResponse>,
|
|
||||||
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Endpoint<TInput, TResponse> = TInput extends void
|
|
||||||
? EndpointWithoutInput<TResponse>
|
|
||||||
: EndpointWithInput<TInput, TResponse>;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Builder
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export function endpoint<TInput, TResponse>(
|
|
||||||
service: string,
|
|
||||||
action: string,
|
|
||||||
execute: (input: TInput) => Promise<TResponse>,
|
|
||||||
): Endpoint<TInput, TResponse> {
|
|
||||||
const call = (input: TInput) => execute(input);
|
|
||||||
|
|
||||||
const queryKey = (input?: TInput): readonly unknown[] => {
|
|
||||||
if (input === undefined) return [service, action] as const;
|
|
||||||
return [service, action, input] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryOptions = (
|
|
||||||
config?: Record<string, unknown>,
|
|
||||||
): QueryObserverOptions<
|
|
||||||
TResponse,
|
|
||||||
Error,
|
|
||||||
TResponse,
|
|
||||||
TResponse,
|
|
||||||
readonly unknown[]
|
|
||||||
> => {
|
|
||||||
const input = config?.input as TInput | undefined;
|
|
||||||
const { input: _, ...rest } = config ?? {};
|
|
||||||
const key: readonly unknown[] =
|
|
||||||
input !== undefined ? [service, action, input] : [service, action];
|
|
||||||
return {
|
|
||||||
...rest,
|
|
||||||
queryKey: key,
|
|
||||||
queryFn: () => execute(input as TInput),
|
|
||||||
} as ReturnType<typeof queryOptions>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return { call, queryKey, queryOptions } as Endpoint<TInput, TResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Some API endpoints return `{ data: T }`, others return `T` directly. */
|
|
||||||
export function unwrap<T>(response: { data: T } | T): T {
|
|
||||||
if (
|
|
||||||
response &&
|
|
||||||
typeof response === "object" &&
|
|
||||||
"data" in (response as object)
|
|
||||||
) {
|
|
||||||
return (response as { data: T }).data;
|
|
||||||
}
|
}
|
||||||
return response as T;
|
);
|
||||||
}
|
|
||||||
|
|||||||
94
apps/edr-freight-web/portal/src/utils/endpoint.ts
Normal file
94
apps/edr-freight-web/portal/src/utils/endpoint.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import {
|
||||||
|
UseQueryOptions,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// React Query shared types
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export type QueryConfig<T> = Omit<
|
||||||
|
UseQueryOptions<T, Error, T, readonly unknown[]>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Endpoint interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface EndpointWithInput<TInput, TResponse> {
|
||||||
|
call(input: TInput): Promise<TResponse>;
|
||||||
|
queryKey(input: TInput): readonly unknown[];
|
||||||
|
queryOptions(
|
||||||
|
config: { input: TInput } & QueryConfig<TResponse>,
|
||||||
|
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EndpointWithoutInput<TResponse> {
|
||||||
|
call(): Promise<TResponse>;
|
||||||
|
queryKey(): readonly unknown[];
|
||||||
|
queryOptions(
|
||||||
|
config?: QueryConfig<TResponse>,
|
||||||
|
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Endpoint<TInput, TResponse> = TInput extends void
|
||||||
|
? EndpointWithoutInput<TResponse>
|
||||||
|
: EndpointWithInput<TInput, TResponse>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Endpoint builder
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function endpoint<TInput, TResponse>(
|
||||||
|
service: string,
|
||||||
|
action: string,
|
||||||
|
execute: (input: TInput) => Promise<TResponse>,
|
||||||
|
) {
|
||||||
|
const buildKey = (input?: TInput): readonly unknown[] =>
|
||||||
|
input === undefined
|
||||||
|
? [service, action]
|
||||||
|
: [service, action, input];
|
||||||
|
|
||||||
|
const call = (input: TInput) => execute(input);
|
||||||
|
|
||||||
|
const queryKey = (input?: TInput) => buildKey(input);
|
||||||
|
|
||||||
|
const queryOptions = (
|
||||||
|
config?: { input?: TInput } & QueryConfig<TResponse>,
|
||||||
|
): UseQueryOptions<
|
||||||
|
TResponse,
|
||||||
|
Error,
|
||||||
|
TResponse,
|
||||||
|
readonly unknown[]
|
||||||
|
> => {
|
||||||
|
const { input, ...rest } = config ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
queryKey: buildKey(input),
|
||||||
|
queryFn: () => execute(input as TInput),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
call,
|
||||||
|
queryKey,
|
||||||
|
queryOptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helper utilities
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function unwrap<T>(response: { data: T } | T): T {
|
||||||
|
if (
|
||||||
|
response &&
|
||||||
|
typeof response === "object" &&
|
||||||
|
"data" in (response as object)
|
||||||
|
) {
|
||||||
|
return (response as { data: T }).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response as T;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export { default as Table } from "./components/Table";
|
export { Table } from "./components/table";
|
||||||
export type { TableProps, TableColumn } from "./components/Table";
|
export type { TableProps, TableColumn } from "./components/Table/Table";
|
||||||
|
|
||||||
export { FormField } from "./components/Form";
|
export { FormField } from "./components/Form";
|
||||||
export type { FormFieldProps } from "./components/Form";
|
export type { FormFieldProps } from "./components/Form";
|
||||||
@@ -10,8 +10,8 @@ export type { SmartFileInputProps } from "./components/SmartFileInput";
|
|||||||
export { default as Modal } from "./components/Modal";
|
export { default as Modal } from "./components/Modal";
|
||||||
export type { ModalProps } from "./components/Modal";
|
export type { ModalProps } from "./components/Modal";
|
||||||
|
|
||||||
export { default as Badge } from "./components/Badge";
|
export { Badge } from "./components/badge";
|
||||||
export type { BadgeProps, BadgeTone } from "./components/Badge";
|
// export type { BadgeProps } from "./components/badge";
|
||||||
|
|
||||||
export { Sidebar, DashboardLayout } from "./components/Layout";
|
export { Sidebar, DashboardLayout } from "./components/Layout";
|
||||||
|
|
||||||
|
|||||||
1934
pnpm-lock.yaml
generated
1934
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user