updated Api Defaults

This commit is contained in:
2025-05-02 20:46:04 +03:00
parent 1920c2a25d
commit 1ce28ec5f0
51 changed files with 986 additions and 1235 deletions

View 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 };

View File

@@ -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 };

View File

@@ -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,
};

View File

@@ -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>

View File

@@ -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":

View File

@@ -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>
)}

View File

@@ -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}

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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,
};
}