updated Api Defaults
This commit is contained in:
177
WebServices/client-frontend/src/apicalls/api-fetcher.ts
Normal file
177
WebServices/client-frontend/src/apicalls/api-fetcher.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
"use server";
|
||||
import { retrieveAccessToken } from "@/apicalls/cookies/token";
|
||||
import {
|
||||
DEFAULT_RESPONSE,
|
||||
defaultHeaders,
|
||||
FetchOptions,
|
||||
HttpMethod,
|
||||
ApiResponse,
|
||||
DEFAULT_TIMEOUT,
|
||||
} from "./basics";
|
||||
|
||||
/**
|
||||
* Creates a promise that rejects after a specified timeout
|
||||
* @param ms Timeout in milliseconds
|
||||
* @param controller AbortController to abort the fetch request
|
||||
* @returns A promise that rejects after the timeout
|
||||
*/
|
||||
const createTimeoutPromise = (
|
||||
ms: number,
|
||||
controller: AbortController
|
||||
): Promise<never> => {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
controller.abort();
|
||||
reject(new Error(`Request timed out after ${ms}ms`));
|
||||
}, ms);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares a standardized API response
|
||||
* @param response The response data
|
||||
* @param statusCode HTTP status code
|
||||
* @returns Standardized API response
|
||||
*/
|
||||
const prepareResponse = <T>(
|
||||
response: T,
|
||||
statusCode: number
|
||||
): ApiResponse<T> => {
|
||||
try {
|
||||
return {
|
||||
status: statusCode,
|
||||
data: response || ({} as T),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error preparing response:", error);
|
||||
return {
|
||||
...DEFAULT_RESPONSE,
|
||||
error: "Response parsing error",
|
||||
} as ApiResponse<T>;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Core fetch function with timeout and error handling
|
||||
* @param url The URL to fetch
|
||||
* @param options Fetch options
|
||||
* @param headers Request headers
|
||||
* @param payload Request payload
|
||||
* @returns API response
|
||||
*/
|
||||
async function coreFetch<T>(
|
||||
url: string,
|
||||
options: FetchOptions = {},
|
||||
headers: Record<string, string> = defaultHeaders,
|
||||
payload?: any
|
||||
): Promise<ApiResponse<T>> {
|
||||
const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options;
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
signal,
|
||||
};
|
||||
|
||||
// Add body if needed
|
||||
if (method !== "GET" && payload) {
|
||||
fetchOptions.body = JSON.stringify(
|
||||
// Handle special case for updateDataWithToken
|
||||
payload.payload ? payload.payload : payload
|
||||
);
|
||||
}
|
||||
|
||||
// Create timeout promise
|
||||
const timeoutPromise = createTimeoutPromise(timeout, controller);
|
||||
|
||||
// Race between fetch and timeout
|
||||
const response = (await Promise.race([
|
||||
fetch(url, fetchOptions),
|
||||
timeoutPromise,
|
||||
])) as Response;
|
||||
|
||||
const responseJson = await response.json();
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("Fetching:", url, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
}
|
||||
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error(`Fetch error (${url}):`, error);
|
||||
return {
|
||||
...DEFAULT_RESPONSE,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
} as ApiResponse<T>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data without authentication
|
||||
*/
|
||||
async function fetchData<T>(
|
||||
endpoint: string,
|
||||
payload?: any,
|
||||
method: HttpMethod = "POST",
|
||||
cache: boolean = false,
|
||||
timeout: number = DEFAULT_TIMEOUT
|
||||
): Promise<ApiResponse<T>> {
|
||||
return coreFetch<T>(
|
||||
endpoint,
|
||||
{ method, cache, timeout },
|
||||
defaultHeaders,
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data with authentication token
|
||||
*/
|
||||
async function fetchDataWithToken<T>(
|
||||
endpoint: string,
|
||||
payload?: any,
|
||||
method: HttpMethod = "POST",
|
||||
cache: boolean = false,
|
||||
timeout: number = DEFAULT_TIMEOUT
|
||||
): Promise<ApiResponse<T>> {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data with authentication token and UUID
|
||||
*/
|
||||
async function updateDataWithToken<T>(
|
||||
endpoint: string,
|
||||
uuid: string,
|
||||
payload?: any,
|
||||
method: HttpMethod = "POST",
|
||||
cache: boolean = false,
|
||||
timeout: number = DEFAULT_TIMEOUT
|
||||
): Promise<ApiResponse<T>> {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
return coreFetch<T>(
|
||||
`${endpoint}/${uuid}`,
|
||||
{ method, cache, timeout },
|
||||
headers,
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
export { fetchData, fetchDataWithToken, updateDataWithToken };
|
||||
@@ -1,144 +0,0 @@
|
||||
"use server";
|
||||
import { retrieveAccessToken } from "@/apicalls/cookies/token";
|
||||
|
||||
const defaultHeaders = {
|
||||
accept: "application/json",
|
||||
language: "tr",
|
||||
domain: "evyos.com.tr",
|
||||
tz: "GMT+3",
|
||||
"Content-type": "application/json",
|
||||
};
|
||||
|
||||
const DefaultResponse = {
|
||||
error: "Hata tipi belirtilmedi",
|
||||
status: "500",
|
||||
data: {},
|
||||
};
|
||||
|
||||
const cacheList = ["no-cache", "no-store", "force-cache", "only-if-cached"];
|
||||
|
||||
const prepareResponse = (response: any, statusCode: number) => {
|
||||
try {
|
||||
return {
|
||||
status: statusCode,
|
||||
data: response || {},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error preparing response:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: "Response parsing error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async (
|
||||
endpoint: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
try {
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
};
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method === "POST" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", endpoint, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const updateDataWithToken = async (
|
||||
endpoint: string,
|
||||
uuid: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
try {
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method !== "GET" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload.payload);
|
||||
}
|
||||
|
||||
const response = await fetch(`${endpoint}/${uuid}`, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", `${endpoint}/${uuid}`, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataWithToken = async (
|
||||
endpoint: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
try {
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method === "POST" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", endpoint, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Fetch with token error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { fetchData, fetchDataWithToken, updateDataWithToken };
|
||||
@@ -3,18 +3,34 @@ const formatServiceUrl = (url: string) => {
|
||||
return url.startsWith("http") ? url : `http://${url}`;
|
||||
};
|
||||
|
||||
export const baseUrlAuth = formatServiceUrl(
|
||||
const baseUrlAuth = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
|
||||
);
|
||||
export const baseUrlPeople = formatServiceUrl(
|
||||
const baseUrlPeople = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002"
|
||||
);
|
||||
// export const baseUrlEvent = formatServiceUrl(
|
||||
// process.env.NEXT_PUBLIC_EVENT_SERVICE_URL || "eventservice:8888"
|
||||
// );
|
||||
export const tokenSecret = process.env.TOKENSECRET_90 || "";
|
||||
const baseUrlApplication = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8004"
|
||||
);
|
||||
|
||||
export const cookieObject: any = {
|
||||
// Types for better type safety
|
||||
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||||
|
||||
interface ApiResponse<T = any> {
|
||||
status: number;
|
||||
data: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
method?: HttpMethod;
|
||||
cache?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
const tokenSecret = process.env.TOKENSECRET_90 || "";
|
||||
|
||||
const cookieObject: any = {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "none",
|
||||
@@ -23,53 +39,29 @@ export const cookieObject: any = {
|
||||
priority: "high",
|
||||
};
|
||||
|
||||
interface FilterListInterface {
|
||||
page?: number | null | undefined;
|
||||
size?: number | null | undefined;
|
||||
orderField?: string | null | undefined;
|
||||
orderType?: string | null | undefined;
|
||||
includeJoins?: any[] | null | undefined;
|
||||
query?: any | null | undefined;
|
||||
}
|
||||
// Constants
|
||||
const DEFAULT_TIMEOUT = 10000; // 10 seconds
|
||||
const defaultHeaders = {
|
||||
accept: "application/json",
|
||||
language: "tr",
|
||||
domain: "management.com.tr",
|
||||
tz: "GMT+3",
|
||||
"Content-type": "application/json",
|
||||
};
|
||||
const DEFAULT_RESPONSE: ApiResponse = {
|
||||
error: "Hata tipi belirtilmedi",
|
||||
status: 500,
|
||||
data: {},
|
||||
};
|
||||
|
||||
class FilterList {
|
||||
page: number;
|
||||
size: number;
|
||||
orderField: string;
|
||||
orderType: string;
|
||||
includeJoins: any[];
|
||||
query: any;
|
||||
|
||||
constructor({
|
||||
page = 1,
|
||||
size = 5,
|
||||
orderField = "id",
|
||||
orderType = "asc",
|
||||
includeJoins = [],
|
||||
query = {},
|
||||
}: FilterListInterface = {}) {
|
||||
this.page = page ?? 1;
|
||||
this.size = size ?? 5;
|
||||
this.orderField = orderField ?? "uu_id";
|
||||
this.orderType = orderType ?? "asc";
|
||||
this.orderType = this.orderType.startsWith("a") ? "asc" : "desc";
|
||||
this.includeJoins = includeJoins ?? [];
|
||||
this.query = query ?? {};
|
||||
}
|
||||
|
||||
filter() {
|
||||
return {
|
||||
page: this.page,
|
||||
size: this.size,
|
||||
orderField: this.orderField,
|
||||
orderType: this.orderType,
|
||||
includeJoins: this.includeJoins,
|
||||
query: this.query,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const defaultFilterList = new FilterList({});
|
||||
|
||||
export { FilterList, defaultFilterList };
|
||||
export type { FilterListInterface };
|
||||
export type { HttpMethod, ApiResponse, FetchOptions };
|
||||
export {
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_RESPONSE,
|
||||
defaultHeaders,
|
||||
baseUrlAuth,
|
||||
baseUrlPeople,
|
||||
baseUrlApplication,
|
||||
tokenSecret,
|
||||
cookieObject,
|
||||
};
|
||||
|
||||
@@ -2,29 +2,17 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { CreateComponentProps } from "./types";
|
||||
import { CreateComponentProps, FieldDefinition } from "./types";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
// Import field definitions type
|
||||
interface FieldDefinition {
|
||||
type: string;
|
||||
group: string;
|
||||
label: string;
|
||||
options?: string[];
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export function CreateComponent<T>({
|
||||
refetch,
|
||||
setMode,
|
||||
@@ -33,6 +21,7 @@ export function CreateComponent<T>({
|
||||
lang,
|
||||
translations,
|
||||
formProps = {},
|
||||
apiUrl,
|
||||
}: CreateComponentProps<T>) {
|
||||
const t = translations[lang as keyof typeof translations] || {};
|
||||
|
||||
@@ -75,7 +64,7 @@ export function CreateComponent<T>({
|
||||
formState: { errors },
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm({
|
||||
} = useForm<Record<string, any>>({
|
||||
defaultValues,
|
||||
resolver: validationSchema ? zodResolver(validationSchema) : undefined,
|
||||
});
|
||||
@@ -83,14 +72,26 @@ export function CreateComponent<T>({
|
||||
const formValues = watch();
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: Record<string, any>) => {
|
||||
const onSubmit: SubmitHandler<Record<string, any>> = async (data) => {
|
||||
try {
|
||||
console.log("Form data to save:", data);
|
||||
if (apiUrl) {
|
||||
const createUrl = `${apiUrl}/create`;
|
||||
const response = await fetch(createUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
console.log("Response:", response.ok);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
|
||||
// Here you would make an API call to save the data
|
||||
// For example: await createApplication(data);
|
||||
const createdItem = await response.json();
|
||||
console.log("Created item:", createdItem);
|
||||
}
|
||||
|
||||
// Mock API call success
|
||||
if (refetch) refetch();
|
||||
setMode("list");
|
||||
setSelectedItem(null);
|
||||
@@ -140,13 +141,13 @@ export function CreateComponent<T>({
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
<Input
|
||||
id={fieldName}
|
||||
{...register(fieldName)}
|
||||
placeholder={t[fieldName] || field.label}
|
||||
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
{errorMessage && (
|
||||
@@ -159,13 +160,13 @@ export function CreateComponent<T>({
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
<Textarea
|
||||
id={fieldName}
|
||||
{...register(fieldName)}
|
||||
placeholder={t[fieldName] || field.label}
|
||||
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
rows={3}
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
@@ -179,7 +180,7 @@ export function CreateComponent<T>({
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
<Select
|
||||
@@ -188,7 +189,7 @@ export function CreateComponent<T>({
|
||||
disabled={field.readOnly}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t[fieldName] || field.label} />
|
||||
<SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{field.options?.map((option) => (
|
||||
@@ -216,7 +217,7 @@ export function CreateComponent<T>({
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{errorMessage && (
|
||||
@@ -231,7 +232,7 @@ export function CreateComponent<T>({
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(() => console.log("Form data to save:"))}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Card className="w-full mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>{t.create || "Create"}</CardTitle>
|
||||
|
||||
@@ -15,9 +15,10 @@ export function FormDisplay<T>({
|
||||
lang,
|
||||
translations,
|
||||
formProps = {},
|
||||
apiUrl,
|
||||
}: FormDisplayProps<T>) {
|
||||
const [enhancedFormProps, setEnhancedFormProps] = useState(formProps);
|
||||
|
||||
|
||||
// Dynamically import schema definitions if provided in formProps
|
||||
useEffect(() => {
|
||||
const loadSchemaDefinitions = async () => {
|
||||
@@ -26,7 +27,7 @@ export function FormDisplay<T>({
|
||||
if (formProps.schemaPath) {
|
||||
// Dynamic import of the schema module
|
||||
const schemaModule = await import(formProps.schemaPath);
|
||||
|
||||
|
||||
// Get the appropriate field definitions based on mode
|
||||
let fieldDefs;
|
||||
if (schemaModule.fieldDefinitions?.getDefinitionsByMode) {
|
||||
@@ -38,7 +39,7 @@ export function FormDisplay<T>({
|
||||
} else if (mode === "view" && schemaModule.viewFieldDefinitions) {
|
||||
fieldDefs = schemaModule.viewFieldDefinitions;
|
||||
}
|
||||
|
||||
|
||||
// Get the appropriate validation schema based on mode
|
||||
let validationSchema;
|
||||
if (mode === "create" && schemaModule.CreateApplicationSchema) {
|
||||
@@ -50,10 +51,10 @@ export function FormDisplay<T>({
|
||||
} else if (schemaModule.ApplicationSchema) {
|
||||
validationSchema = schemaModule.ApplicationSchema;
|
||||
}
|
||||
|
||||
|
||||
// Get the grouped field definitions structure if available
|
||||
const groupedFieldDefs = schemaModule.baseFieldDefinitions || {};
|
||||
|
||||
|
||||
// Update form props with schema information
|
||||
setEnhancedFormProps({
|
||||
...formProps,
|
||||
@@ -67,10 +68,10 @@ export function FormDisplay<T>({
|
||||
console.error("Error loading schema definitions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
loadSchemaDefinitions();
|
||||
}, [formProps, mode, lang]); // Added lang as a dependency to ensure re-fetch when language changes
|
||||
|
||||
|
||||
// Render the appropriate component based on the mode
|
||||
switch (mode) {
|
||||
case "create":
|
||||
@@ -84,6 +85,7 @@ export function FormDisplay<T>({
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
formProps={enhancedFormProps}
|
||||
apiUrl={apiUrl}
|
||||
/>
|
||||
);
|
||||
case "update":
|
||||
@@ -98,6 +100,7 @@ export function FormDisplay<T>({
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
formProps={enhancedFormProps}
|
||||
apiUrl={apiUrl}
|
||||
/>
|
||||
) : null;
|
||||
case "view":
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { UpdateComponentProps } from "./types";
|
||||
import { UpdateComponentProps, FieldDefinition } from "./types";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
@@ -13,18 +13,6 @@ import { useForm } from "react-hook-form";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
// Import field definitions type
|
||||
interface FieldDefinition {
|
||||
type: string;
|
||||
group: string;
|
||||
label: string;
|
||||
options?: string[];
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
name?: string; // Add name property for TypeScript compatibility
|
||||
}
|
||||
|
||||
export function UpdateComponent<T>({
|
||||
initialData,
|
||||
refetch,
|
||||
@@ -33,23 +21,20 @@ export function UpdateComponent<T>({
|
||||
onCancel,
|
||||
lang,
|
||||
translations,
|
||||
apiUrl,
|
||||
formProps = {},
|
||||
}: UpdateComponentProps<T>) {
|
||||
const t = translations[lang as keyof typeof translations] || {};
|
||||
|
||||
// Get field definitions from formProps if available
|
||||
const fieldDefinitions = formProps.fieldDefinitions || {};
|
||||
const validationSchema = formProps.validationSchema;
|
||||
|
||||
// Group fields by their group property
|
||||
const [groupedFields, setGroupedFields] = useState<Record<string, FieldDefinition[]>>({});
|
||||
|
||||
// Process field definitions to group them
|
||||
useEffect(() => {
|
||||
if (Object.keys(fieldDefinitions).length > 0) {
|
||||
const groups: Record<string, FieldDefinition[]> = {};
|
||||
|
||||
// Group fields by their group property
|
||||
Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => {
|
||||
const def = definition as FieldDefinition;
|
||||
if (!groups[def.group]) {
|
||||
@@ -62,32 +47,36 @@ export function UpdateComponent<T>({
|
||||
}
|
||||
}, [fieldDefinitions]);
|
||||
|
||||
// Initialize form with default values from field definitions and initialData
|
||||
const defaultValues: Record<string, any> = {};
|
||||
Object.entries(fieldDefinitions).forEach(([key, def]) => {
|
||||
const fieldDef = def as FieldDefinition;
|
||||
defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : "";
|
||||
});
|
||||
|
||||
// Merge initialData with default values
|
||||
if (initialData) {
|
||||
Object.assign(defaultValues, initialData as Record<string, any>);
|
||||
}
|
||||
|
||||
// Setup form with validation schema if available
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
setValue,
|
||||
watch,
|
||||
reset,
|
||||
trigger,
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: validationSchema ? zodResolver(validationSchema) : undefined,
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
// Reset form when initialData changes
|
||||
useEffect(() => {
|
||||
if (Object.keys(errors).length > 0) {
|
||||
console.log("Form errors:", errors);
|
||||
}
|
||||
}, [errors]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
reset({ ...initialData as Record<string, any> });
|
||||
@@ -99,17 +88,52 @@ export function UpdateComponent<T>({
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
console.log("Form data to update:", data);
|
||||
|
||||
// Here you would make an API call to update the data
|
||||
// For example: await updateApplication(data);
|
||||
const isFormValid = await trigger();
|
||||
if (!isFormValid) {
|
||||
console.error("Form validation failed - stopping submission");
|
||||
return; // Stop submission if validation fails
|
||||
}
|
||||
if (!apiUrl) {
|
||||
console.error("API URL is missing or undefined");
|
||||
return;
|
||||
}
|
||||
const uuid = initialData ? (initialData as any).uuid || (initialData as any).uu_id : null;
|
||||
if (!uuid) {
|
||||
console.error("UUID not found in initialData");
|
||||
throw new Error("UUID is required for update operations");
|
||||
}
|
||||
const dataToSend = { ...data };
|
||||
Object.entries(fieldDefinitions).forEach(([key, def]) => {
|
||||
const fieldDef = def as FieldDefinition;
|
||||
if (fieldDef.readOnly) {
|
||||
delete dataToSend[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Mock API call success
|
||||
if (refetch) refetch();
|
||||
setMode("list");
|
||||
setSelectedItem(null);
|
||||
const updateUrl = `${apiUrl}/update?uuid=${uuid}`;
|
||||
try {
|
||||
let response = await fetch(updateUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataToSend),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("API error response:", errorText);
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
if (refetch) refetch();
|
||||
setMode("list");
|
||||
setSelectedItem(null);
|
||||
} catch (fetchError) {
|
||||
console.error("Error during fetch:", fetchError);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating form:", error);
|
||||
console.error("Error details:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -125,43 +149,53 @@ export function UpdateComponent<T>({
|
||||
|
||||
// Translate group names for display dynamically
|
||||
const getGroupTitle = (groupName: string) => {
|
||||
// First check if there's a translation for the exact group key
|
||||
if (t[groupName]) {
|
||||
return t[groupName];
|
||||
}
|
||||
|
||||
// Try to format the group name in a more readable way if no translation exists
|
||||
// Convert camelCase or snake_case to Title Case with spaces
|
||||
const formattedName = groupName
|
||||
// Insert space before capital letters and uppercase the first letter
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
// Replace underscores with spaces
|
||||
.replace(/_/g, ' ')
|
||||
// Capitalize first letter
|
||||
.replace(/^./, (str) => str.toUpperCase())
|
||||
// Capitalize each word
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
|
||||
return formattedName;
|
||||
};
|
||||
|
||||
// Render a field based on its type
|
||||
const renderField = (fieldName: string, field: FieldDefinition) => {
|
||||
const errorMessage = errors[fieldName]?.message as string;
|
||||
const fieldValue = formValues[fieldName];
|
||||
|
||||
const renderLabel = () => (
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
);
|
||||
|
||||
if (field.readOnly) {
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
{renderLabel()}
|
||||
<div className="p-2 bg-gray-50 rounded border border-gray-200">
|
||||
{field.type === "checkbox" ?
|
||||
(fieldValue ? "Yes" : "No") :
|
||||
(fieldValue || "-")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// For editable fields, render the appropriate input type
|
||||
switch (field.type) {
|
||||
case "text":
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{renderLabel()}
|
||||
<Input
|
||||
id={fieldName}
|
||||
{...register(fieldName)}
|
||||
placeholder={t[fieldName] || field.label}
|
||||
disabled={field.readOnly}
|
||||
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
@@ -172,16 +206,12 @@ export function UpdateComponent<T>({
|
||||
case "textarea":
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{renderLabel()}
|
||||
<Textarea
|
||||
id={fieldName}
|
||||
{...register(fieldName)}
|
||||
placeholder={t[fieldName] || field.label}
|
||||
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
|
||||
rows={3}
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
@@ -192,17 +222,13 @@ export function UpdateComponent<T>({
|
||||
case "select":
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{renderLabel()}
|
||||
<Select
|
||||
value={formValues[fieldName]}
|
||||
value={fieldValue}
|
||||
onValueChange={(value) => handleSelectChange(fieldName, value)}
|
||||
disabled={field.readOnly}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t[fieldName] || field.label} />
|
||||
<SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{field.options?.map((option) => (
|
||||
@@ -223,16 +249,12 @@ export function UpdateComponent<T>({
|
||||
<div className="flex items-center space-x-2" key={fieldName}>
|
||||
<Checkbox
|
||||
id={fieldName}
|
||||
checked={formValues[fieldName]}
|
||||
checked={fieldValue}
|
||||
onCheckedChange={(checked) =>
|
||||
handleCheckboxChange(fieldName, checked as boolean)
|
||||
}
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{renderLabel()}
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
)}
|
||||
@@ -242,15 +264,11 @@ export function UpdateComponent<T>({
|
||||
case "date":
|
||||
return (
|
||||
<div className="space-y-2" key={fieldName}>
|
||||
<Label htmlFor={fieldName}>
|
||||
{t[fieldName] || field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
{renderLabel()}
|
||||
<Input
|
||||
id={fieldName}
|
||||
type="date"
|
||||
{...register(fieldName)}
|
||||
disabled={field.readOnly}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
@@ -277,6 +295,13 @@ export function UpdateComponent<T>({
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{t.formErrors || "Please correct the errors in the form"}
|
||||
<ul className="mt-2 list-disc pl-5">
|
||||
{Object.entries(errors).map(([field, error]) => (
|
||||
<li key={field}>
|
||||
{t[field] || field}: {(error as any)?.message || 'Invalid value'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -2,21 +2,10 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { ViewComponentProps } from "./types";
|
||||
import { ViewComponentProps, FieldDefinition } from "./types";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { z } from "zod";
|
||||
|
||||
// Import field definitions type
|
||||
export interface FieldDefinition {
|
||||
type: string;
|
||||
group: string;
|
||||
label: string;
|
||||
options?: string[];
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
name?: string; // Add name property for TypeScript compatibility
|
||||
}
|
||||
|
||||
// Utility function to format field label
|
||||
const formatFieldLabel = (fieldName: string) =>
|
||||
@@ -103,7 +92,7 @@ const ViewFieldGroup: React.FC<{
|
||||
key={fieldName}
|
||||
fieldName={fieldName}
|
||||
value={value}
|
||||
label={field.label}
|
||||
label={field.label[lang as "en" | "tr"]}
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
hasError={hasError}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
"use client";
|
||||
|
||||
// Import field definitions type
|
||||
export interface FieldDefinition {
|
||||
type: string;
|
||||
group: string;
|
||||
label: { tr: string; en: string };
|
||||
options?: string[];
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// Define the FormMode type to ensure consistency
|
||||
export type FormMode = "list" | "create" | "update" | "view";
|
||||
|
||||
@@ -12,12 +24,14 @@ export interface BaseFormProps<T> {
|
||||
lang: string;
|
||||
translations: Record<string, Record<string, string>>;
|
||||
formProps?: Record<string, any>;
|
||||
apiUrl?: string;
|
||||
}
|
||||
|
||||
export interface CreateComponentProps<T> extends BaseFormProps<T> {}
|
||||
|
||||
export interface UpdateComponentProps<T> extends BaseFormProps<T> {
|
||||
initialData: T; // Required for update
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
export interface ViewComponentProps<T> extends BaseFormProps<T> {
|
||||
@@ -34,4 +48,5 @@ export interface FormDisplayProps<T> {
|
||||
lang: string;
|
||||
translations: Record<string, Record<string, string>>;
|
||||
formProps?: Record<string, any>;
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ export function useApiData<T>(
|
||||
params: RequestParams
|
||||
): Promise<ApiResponse<T>> => {
|
||||
try {
|
||||
// Prepare the request body with action and all params
|
||||
// Prepare the request body with pagination parameters
|
||||
const requestBody = {
|
||||
action: "list",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
orderField: params.orderField,
|
||||
@@ -26,8 +25,11 @@ export function useApiData<T>(
|
||||
query: params.query,
|
||||
};
|
||||
|
||||
// Construct the list endpoint URL
|
||||
const listEndpoint = `${endpoint}/list`;
|
||||
|
||||
// Make the API request using POST
|
||||
const response = await fetch(endpoint, {
|
||||
const response = await fetch(listEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { retrievePagebyUrl, retrievePageList } from "@/apicalls/cookies/token";
|
||||
import { retrievePageByUrlAndPageId } from "@/components/navigator/retriever";
|
||||
import React from "react";
|
||||
import { retrievePageByUrl } from "@/eventRouters/pageRetriever";
|
||||
import { PageProps } from "@/validations/translations/translation";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
export interface DashboardPageParams {
|
||||
/**
|
||||
* The active page path, e.g., "/individual", "/dashboard"
|
||||
* The active page path, e.g., "/application", "/dashboard"
|
||||
*/
|
||||
pageUrl: string;
|
||||
|
||||
@@ -33,20 +33,15 @@ export interface DashboardPageResult {
|
||||
/**
|
||||
* The page component to render
|
||||
*/
|
||||
PageComponent: any;
|
||||
|
||||
/**
|
||||
* The list of site URLs for the menu
|
||||
*/
|
||||
siteUrlsList: string[];
|
||||
PageComponent: React.FC<PageProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to retrieve and prepare dashboard page data for client-frontend application
|
||||
* Handles retrieving site URLs, page components, and other necessary data
|
||||
* Hook to retrieve and prepare dashboard page data
|
||||
* Throws errors for Next.js error boundary to catch
|
||||
*
|
||||
* @param params The dashboard page parameters
|
||||
* @returns The processed dashboard page data including site URLs
|
||||
* @returns The processed dashboard page data
|
||||
* @throws Error if page URL is invalid or page component is not found
|
||||
*/
|
||||
export async function useDashboardPage({
|
||||
@@ -55,7 +50,6 @@ export async function useDashboardPage({
|
||||
}: DashboardPageParams): Promise<DashboardPageResult> {
|
||||
let searchParamsInstance: { [key: string]: string | undefined } = {};
|
||||
const defaultLang = "en";
|
||||
|
||||
// Validate pageUrl
|
||||
if (!pageUrl || typeof pageUrl !== "string") {
|
||||
throw new Error(`Invalid page URL: ${pageUrl}`);
|
||||
@@ -80,25 +74,8 @@ export async function useDashboardPage({
|
||||
);
|
||||
}
|
||||
|
||||
// Get site URLs list
|
||||
let siteUrlsList: string[] = [];
|
||||
try {
|
||||
siteUrlsList = (await retrievePageList()) || [];
|
||||
} catch (err) {
|
||||
console.error("Error retrieving site URLs:", err);
|
||||
throw new Error(`Failed to retrieve site URLs: ${err}`);
|
||||
}
|
||||
|
||||
// Get page component
|
||||
let pageToDirect;
|
||||
let PageComponent;
|
||||
try {
|
||||
pageToDirect = await retrievePagebyUrl(pageUrl);
|
||||
PageComponent = retrievePageByUrlAndPageId(pageToDirect, pageUrl);
|
||||
} catch (err) {
|
||||
console.error("Error retrieving page component:", err);
|
||||
throw new Error(`Page component not found for URL: ${pageUrl}`);
|
||||
}
|
||||
const PageComponent = retrievePageByUrl(pageUrl);
|
||||
|
||||
// Check if page component exists
|
||||
if (!PageComponent) {
|
||||
@@ -110,7 +87,6 @@ export async function useDashboardPage({
|
||||
searchParamsInstance,
|
||||
lang,
|
||||
PageComponent,
|
||||
siteUrlsList,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user