diff --git a/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx b/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx
deleted file mode 100644
index 3c925ff..0000000
--- a/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-"use client";
-import React from "react";
-import { loginSelectEmployee } from "@/apicalls/login/login";
-import { useRouter } from "next/navigation";
-import { Company } from "./types";
-
-interface LoginEmployeeProps {
- selectionList: Company[];
- lang?: "en" | "tr";
- onSelect?: (uu_id: string) => void;
-}
-
-// Language dictionary for internationalization
-const languageDictionary = {
- tr: {
- companySelection: "Şirket Seçimi",
- loggedInAs: "Çalışan olarak giriş yaptınız",
- duty: "Görev",
- id: "Kimlik",
- noSelections: "Seçenek bulunamadı",
- },
- en: {
- companySelection: "Select your company",
- loggedInAs: "You are logged in as an employee",
- duty: "Duty",
- id: "ID",
- noSelections: "No selections available",
- },
-};
-
-function LoginEmployee({
- selectionList,
- lang = "en",
- onSelect,
-}: LoginEmployeeProps) {
- const t = languageDictionary[lang] || languageDictionary.en;
- const router = useRouter();
-
- const handleSelect = (uu_id: string) => {
- console.log("Selected employee uu_id:", uu_id);
-
- // If an external onSelect handler is provided, use it
- if (onSelect) {
- onSelect(uu_id);
- return;
- }
-
- // Otherwise use the internal handler
- loginSelectEmployee({ company_uu_id: uu_id })
- .then((responseData: any) => {
- if (responseData?.status === 200 || responseData?.status === 202) {
- router.push("/dashboard");
- }
- })
- .catch((error) => {
- console.error(error);
- });
- };
-
- return (
- <>
-
{t.companySelection}
-
{t.loggedInAs}
-
- {Array.isArray(selectionList) && selectionList.length === 0 && (
-
{t.noSelections}
- )}
-
- {Array.isArray(selectionList) && selectionList.length === 1 && (
-
-
-
-
- {selectionList[0].public_name}
-
- {selectionList[0].company_type && (
-
- {selectionList[0].company_type}
-
- )}
-
- {selectionList[0].duty && (
-
-
- {t.duty}: {selectionList[0].duty}
-
-
- )}
-
-
-
- {t.id}: {selectionList[0].uu_id}
-
-
-
-
-
-
-
-
- )}
-
- {Array.isArray(selectionList) &&
- selectionList.length > 1 &&
- selectionList.map((item: Company, index: number) => (
-
handleSelect(item.uu_id)}
- >
-
-
- {item.public_name}
- {item.company_type && (
-
- {item.company_type}
-
- )}
-
- {item.duty && (
-
-
- {t.duty}: {item.duty}
-
-
- )}
-
-
-
- {t.id}: {item.uu_id}
-
-
-
-
-
- ))}
- >
- );
-}
-
-export default LoginEmployee;
diff --git a/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx b/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx
deleted file mode 100644
index 493e27c..0000000
--- a/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-"use client";
-import React from "react";
-import { loginSelectOccupant } from "@/apicalls/login/login";
-import { useRouter } from "next/navigation";
-import { BuildingMap } from "./types";
-
-interface LoginOccupantProps {
- selectionList: BuildingMap;
- lang?: "en" | "tr";
-}
-
-// Language dictionary for internationalization
-const languageDictionary = {
- tr: {
- occupantSelection: "Daire Seçimi",
- loggedInAs: "Kiracı olarak giriş yaptınız",
- buildingInfo: "Bina Bilgisi",
- level: "Kat",
- noSelections: "Seçenek bulunamadı",
- },
- en: {
- occupantSelection: "Select your occupant type",
- loggedInAs: "You are logged in as an occupant",
- buildingInfo: "Building Info",
- level: "Level",
- noSelections: "No selections available",
- },
-};
-
-function LoginOccupant({
- selectionList,
- lang = "en"
-}: LoginOccupantProps) {
- const t = languageDictionary[lang] || languageDictionary.en;
- const router = useRouter();
-
- const handleSelect = (uu_id: string) => {
- console.log("Selected occupant uu_id:", uu_id);
-
- loginSelectOccupant({
- build_living_space_uu_id: uu_id,
- })
- .then((responseData: any) => {
- if (responseData?.status === 200 || responseData?.status === 202) {
- router.push("/dashboard");
- }
- })
- .catch((error) => {
- console.error(error);
- });
- };
-
- return (
- <>
-
{t.occupantSelection}
-
- {t.loggedInAs}
-
- {selectionList && Object.keys(selectionList).length > 0 ? (
- Object.keys(selectionList).map((buildKey: string) => {
- const building = selectionList[buildKey];
- return (
-
-
-
- {t.buildingInfo}:
- {building.build_name} - No: {building.build_no}
-
-
-
-
- {building.occupants.map((occupant: any, idx: number) => (
-
handleSelect(occupant.build_living_space_uu_id)}
- >
-
-
-
- {occupant.description}
-
-
- {occupant.code}
-
-
-
-
- {occupant.part_name}
-
-
-
-
- {t.level}: {occupant.part_level}
-
-
-
-
- ))}
-
-
- );
- })
- ) : (
-
{t.noSelections}
- )}
- >
- );
-}
-
-export default LoginOccupant;
diff --git a/WebServices/client-frontend/src/components/auth/login.tsx b/WebServices/client-frontend/src/components/auth/login.tsx
deleted file mode 100644
index f468d19..0000000
--- a/WebServices/client-frontend/src/components/auth/login.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-"use client";
-import { useState, useTransition } from "react";
-import { useRouter } from "next/navigation";
-import { useForm } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { loginViaAccessKeys } from "@/apicalls/login/login";
-import { z } from "zod";
-
-const loginSchema = z.object({
- email: z.string().email("Invalid email address"),
- password: z.string().min(5, "Password must be at least 5 characters"),
- remember_me: z.boolean().optional().default(false),
-});
-
-type LoginFormData = {
- email: string;
- password: string;
- remember_me?: boolean;
-};
-
-function Login() {
- // Open transition for form login
- const [isPending, startTransition] = useTransition();
- const [error, setError] = useState
(null);
- const [jsonText, setJsonText] = useState(null);
-
- const Router = useRouter();
-
- const {
- register,
- formState: { errors },
- handleSubmit,
- } = useForm({
- resolver: zodResolver(loginSchema),
- });
-
- const onSubmit = async (data: LoginFormData) => {
- try {
- startTransition(() => {
- try {
- loginViaAccessKeys({
- accessKey: data.email,
- password: data.password,
- rememberMe: false,
- })
- .then((result: any) => {
- const dataResponse = result?.data;
- if (dataResponse?.access_token) {
- setJsonText(JSON.stringify(dataResponse));
- setTimeout(() => {
- Router.push("/auth/select");
- }, 2000);
- }
- return dataResponse;
- })
- .catch(() => {});
- } catch (error) {}
- });
- } catch (error) {
- console.error("Login error:", error);
- setError("An error occurred during login");
- }
- };
- return (
- <>
-
-
-
- {jsonText && (
-
-
- Response Data
-
-
- {Object.entries(JSON.parse(jsonText)).map(([key, value]) => (
-
- {key}:
-
- {typeof value === "object"
- ? JSON.stringify(value)
- : value?.toString() || "N/A"}
-
-
- ))}
-
-
- )}
-
- >
- );
-}
-
-export default Login;
diff --git a/WebServices/client-frontend/src/components/auth/select.tsx b/WebServices/client-frontend/src/components/auth/select.tsx
deleted file mode 100644
index 4077251..0000000
--- a/WebServices/client-frontend/src/components/auth/select.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-"use client";
-import React from "react";
-import {
- loginSelectEmployee,
- loginSelectOccupant,
-} from "@/apicalls/login/login";
-import { useRouter } from "next/navigation";
-import LoginEmployee from "./LoginEmployee";
-import LoginOccupant from "./LoginOccupant";
-import { SelectListProps, Company, BuildingMap } from "./types";
-
-function SelectList({
- selectionList,
- isEmployee,
- isOccupant,
- lang = "en",
-}: SelectListProps) {
- const router = useRouter();
-
- // Log the complete selectionList object and its structure
- console.log("selectionList (complete):", selectionList);
- console.log(
- "selectionList (type):",
- Array.isArray(selectionList) ? "Array" : "Object"
- );
-
- if (isEmployee && Array.isArray(selectionList)) {
- console.log("Employee companies:", selectionList);
- } else if (isOccupant && !Array.isArray(selectionList)) {
- // Log each building and its occupants
- Object.entries(selectionList).forEach(([buildingKey, building]) => {
- console.log(`Building ${buildingKey}:`, building);
- console.log(`Occupants for building ${buildingKey}:`, building.occupants);
- });
- }
-
- const setSelectionHandler = (uu_id: string) => {
- if (isEmployee) {
- console.log("Selected isEmployee uu_id:", uu_id);
- loginSelectEmployee({ company_uu_id: uu_id })
- .then((responseData: any) => {
- if (responseData?.status === 200 || responseData?.status === 202) {
- router.push("/dashboard");
- }
- })
- .catch((error) => {
- console.error(error);
- });
- } else if (isOccupant) {
- console.log("Selected isOccupant uu_id:", uu_id);
- // For occupants, the uu_id is a composite of buildKey|partUuid
- loginSelectOccupant({
- build_living_space_uu_id: uu_id,
- })
- .then((responseData: any) => {
- if (responseData?.status === 200 || responseData?.status === 202) {
- router.push("/dashboard");
- }
- })
- .catch((error) => {
- console.error(error);
- });
- }
- };
-
- return (
- <>
- {isEmployee && Array.isArray(selectionList) && (
-
- )}
-
- {isOccupant && !Array.isArray(selectionList) && (
-
- )}
- >
- );
-}
-
-export default SelectList;
diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx
new file mode 100644
index 0000000..2716947
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx
@@ -0,0 +1,25 @@
+"use client";
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { Plus } from "lucide-react";
+
+interface CreateButtonProps {
+ onClick: () => void;
+ translations: Record;
+ lang: string;
+}
+
+export const CreateButton: React.FC = ({
+ onClick,
+ translations,
+ lang,
+}) => {
+ const t = translations[lang] || {};
+
+ return (
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx
new file mode 100644
index 0000000..2d86b8b
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx
@@ -0,0 +1,31 @@
+"use client";
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { CustomButton } from "./types";
+import { cn } from "@/lib/utils";
+
+interface CustomButtonComponentProps {
+ button: CustomButton;
+ isSelected: boolean;
+ onClick: () => void;
+}
+
+export const CustomButtonComponent: React.FC = ({
+ button,
+ isSelected,
+ onClick,
+}) => {
+ return (
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts
new file mode 100644
index 0000000..5b1e2de
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts
@@ -0,0 +1,3 @@
+export * from './CreateButton';
+export * from './CustomButtonComponent';
+export * from './types';
diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts
new file mode 100644
index 0000000..270f86a
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts
@@ -0,0 +1,17 @@
+import { ReactNode } from "react";
+
+export interface CustomButton {
+ id: string;
+ label: string;
+ onClick: () => void;
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
+ icon?: ReactNode;
+}
+
+export interface ActionButtonsProps {
+ onCreateClick: () => void;
+ translations: Record;
+ lang: string;
+ customButtons?: CustomButton[];
+ defaultSelectedButtonId?: string;
+}
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx
new file mode 100644
index 0000000..2dc2a49
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx
@@ -0,0 +1,74 @@
+"use client";
+import React from "react";
+import { CardItem } from "./CardItem";
+import { CardSkeleton } from "./CardSkeleton";
+import { getFieldValue, getGridClasses } from "./utils";
+import { CardDisplayProps } from "./schema";
+import { GridSize } from "../HeaderSelections/GridSelectionComponent";
+
+export function CardDisplay({
+ showFields,
+ data,
+ lang,
+ translations,
+ error,
+ loading,
+ titleField,
+ onCardClick,
+ renderCustomField,
+ gridCols = 4,
+ showViewIcon = false,
+ showUpdateIcon = false,
+ onViewClick,
+ onUpdateClick,
+ size = "lg",
+}: CardDisplayProps) {
+ if (error) {
+ return (
+
+ {error.message || "An error occurred while fetching data."}
+
+ );
+ }
+
+ return (
+
+ {loading ? (
+ // Loading skeletons
+ Array.from({ length: 10 }).map((_, index) => (
+
+ ))
+ ) : data.length === 0 ? (
+
+ {translations[lang].noData || "No data found"}
+
+ ) : (
+ data.map((item: T, index: number) => (
+
+ ))
+ )}
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx
new file mode 100644
index 0000000..d28e89e
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx
@@ -0,0 +1,245 @@
+"use client";
+import React from "react";
+import { Card, CardContent, CardHeader } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Eye, Edit } from "lucide-react";
+import { CardItemProps, CardActionsProps, CardFieldProps } from "./schema";
+
+export function CardItem({
+ item,
+ index,
+ showFields,
+ titleField,
+ lang,
+ translations,
+ onCardClick,
+ renderCustomField,
+ showViewIcon,
+ showUpdateIcon,
+ onViewClick,
+ onUpdateClick,
+ getFieldValue,
+ size = "lg",
+}: CardItemProps) {
+
+ const getCardHeight = () => {
+ switch (size) {
+ case "xs": return "h-16 max-h-16";
+ case "sm": return "h-20 max-h-20";
+ case "md": return "h-24 max-h-24";
+ case "lg":
+ default: return "h-full";
+ }
+ };
+
+ const getCardStyle = () => {
+ switch (size) {
+ case "xs": return "!py-0 !gap-0 !flex !flex-col";
+ case "sm": return "!py-1 !gap-1 !flex !flex-col";
+ case "md": return "!py-2 !gap-2 !flex !flex-col";
+ case "lg":
+ default: return "";
+ }
+ };
+
+ const getTitleSize = () => {
+ switch (size) {
+ case "xs": return "text-xs";
+ case "sm": return "text-sm";
+ case "md": return "text-base";
+ case "lg":
+ default: return "text-lg";
+ }
+ };
+
+ const getContentPadding = () => {
+ switch (size) {
+ case "xs": return "p-1 py-1";
+ case "sm": return "p-1 py-1";
+ case "md": return "p-2 py-1";
+ case "lg":
+ default: return "p-3";
+ }
+ };
+
+ if (size === "xs" || size === "sm") {
+ return (
+
+
onCardClick(item) : undefined}
+ >
+ {showViewIcon && (
+
+ )}
+ {showUpdateIcon && (
+
+ )}
+
+
{getFieldValue(item, titleField)}
+
+ {showFields.map((field) => (
+
+ ))}
+
+
+
+
+ );
+ }
+
+ return (
+
+
onCardClick(item) : undefined}
+ >
+
+ {getFieldValue(item, titleField)}
+
+
+
+
+ {showFields.map((field) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+function CardActions({
+ item,
+ showViewIcon,
+ showUpdateIcon,
+ onViewClick,
+ onUpdateClick,
+}: CardActionsProps) {
+ if (!showViewIcon && !showUpdateIcon) return null;
+
+ return (
+
+ {showViewIcon && (
+
+ )}
+ {showUpdateIcon && (
+
+ )}
+
+ );
+}
+
+function CardField({
+ item,
+ field,
+ lang,
+ translations,
+ renderCustomField,
+ getFieldValue,
+ size = "lg",
+}: CardFieldProps) {
+ const getTextSize = () => {
+ switch (size) {
+ case "xs": return "text-xs";
+ case "sm": return "text-xs";
+ case "md": return "text-sm";
+ case "lg":
+ default: return "text-base";
+ }
+ };
+
+ const getLabelWidth = () => {
+ switch (size) {
+ case "xs": return "w-16";
+ case "sm": return "w-20";
+ case "md": return "w-24";
+ case "lg":
+ default: return "w-32";
+ }
+ };
+
+ if (renderCustomField) {
+ return renderCustomField(item, field);
+ }
+
+ const label = translations?.[field]?.[lang] || field;
+ const value = getFieldValue(item, field);
+
+ if (size === "xs" || size === "sm") {
+ return (
+
+ {label}:
+ {value}
+
+ );
+ }
+
+ return (
+
+ {label}:
+ {value}
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx
new file mode 100644
index 0000000..73c0620
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx
@@ -0,0 +1,46 @@
+"use client";
+import React from "react";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+} from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import { CardSkeletonProps } from "./schema";
+
+// Interface moved to schema.ts
+
+export function CardSkeleton({
+ index,
+ showFields,
+ showViewIcon,
+ showUpdateIcon,
+}: CardSkeletonProps) {
+ return (
+
+
+
+
+
+ {showViewIcon && (
+
+ )}
+ {showUpdateIcon && (
+
+ )}
+
+
+
+
+ {showFields.map((field, fieldIndex) => (
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx
new file mode 100644
index 0000000..b3dba2c
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx
@@ -0,0 +1 @@
+export { CardDisplay } from './CardDisplay';
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts b/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts
new file mode 100644
index 0000000..5ff2294
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts
@@ -0,0 +1,62 @@
+import { GridSize } from "../HeaderSelections/GridSelectionComponent";
+
+export type CardSize = "xs" | "sm" | "md" | "lg";
+
+export interface CardDisplayProps {
+ showFields: string[];
+ data: T[];
+ lang: string;
+ translations: Record;
+ error: Error | null;
+ loading: boolean;
+ titleField: string;
+ onCardClick?: (item: T) => void;
+ renderCustomField?: (item: T, field: string) => React.ReactNode;
+ gridCols?: number | GridSize;
+ showViewIcon?: boolean;
+ showUpdateIcon?: boolean;
+ onViewClick?: (item: T) => void;
+ onUpdateClick?: (item: T) => void;
+ size?: CardSize;
+}
+
+export interface CardItemProps {
+ item: T;
+ index: number;
+ showFields: string[];
+ titleField: string;
+ lang: string;
+ translations: Record;
+ onCardClick?: (item: T) => void;
+ renderCustomField?: (item: T, field: string) => React.ReactNode;
+ showViewIcon: boolean;
+ showUpdateIcon: boolean;
+ onViewClick?: (item: T) => void;
+ onUpdateClick?: (item: T) => void;
+ getFieldValue: (item: any, field: string) => any;
+ size?: CardSize;
+}
+
+export interface CardActionsProps {
+ item: T;
+ showViewIcon: boolean;
+ showUpdateIcon: boolean;
+ onViewClick?: (item: T) => void;
+ onUpdateClick?: (item: T) => void;
+}
+export interface CardFieldProps {
+ item: T;
+ field: string;
+ lang: string;
+ translations: Record;
+ renderCustomField?: (item: T, field: string) => React.ReactNode;
+ getFieldValue: (item: any, field: string) => any;
+ size?: CardSize;
+}
+
+export interface CardSkeletonProps {
+ index: number;
+ showFields: string[];
+ showViewIcon: boolean;
+ showUpdateIcon: boolean;
+}
diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts b/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts
new file mode 100644
index 0000000..8f690cb
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts
@@ -0,0 +1,39 @@
+export function getFieldValue(item: any, field: string): any {
+ if (!item) return "";
+ if (field.includes(".")) {
+ const parts = field.split(".");
+ let value = item;
+ for (const part of parts) {
+ if (value === null || value === undefined) return "";
+ value = value[part];
+ }
+ return value;
+ }
+ return item[field];
+}
+
+export function getFieldLabel(
+ field: string,
+ translations: Record,
+ lang: string
+): string {
+ const t = translations[lang] || {};
+ return (
+ t[field] ||
+ field.charAt(0).toUpperCase() + field.slice(1).replace(/_/g, " ")
+ );
+}
+
+export function getGridClasses(gridCols: 1 | 2 | 3 | 4 | 5 | 6): string {
+ const baseClass = "grid grid-cols-1 gap-4";
+ const colClasses: Record = {
+ 1: "",
+ 2: "sm:grid-cols-2",
+ 3: "sm:grid-cols-2 md:grid-cols-3",
+ 4: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4",
+ 5: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5",
+ 6: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6",
+ };
+
+ return `${baseClass} ${colClasses[gridCols]}`;
+}
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx
new file mode 100644
index 0000000..1a339bf
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx
@@ -0,0 +1,303 @@
+"use client";
+import React, { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+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, SubmitHandler } from "react-hook-form";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { AlertCircle } from "lucide-react";
+
+export function CreateComponent({
+ refetch,
+ setMode,
+ setSelectedItem,
+ onCancel,
+ lang,
+ translations,
+ formProps = {},
+ apiUrl,
+}: CreateComponentProps) {
+ 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>({});
+
+ // Process field definitions to group them
+ useEffect(() => {
+ if (Object.keys(fieldDefinitions).length > 0) {
+ const groups: Record = {};
+
+ // Group fields by their group property
+ Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => {
+ const def = definition as FieldDefinition;
+ if (!groups[def.group]) {
+ groups[def.group] = [];
+ }
+ groups[def.group].push({ ...def, name: fieldName });
+ });
+
+ setGroupedFields(groups);
+ }
+ }, [fieldDefinitions]);
+
+ // Initialize form with default values from field definitions
+ const defaultValues: Record = {};
+ Object.entries(fieldDefinitions).forEach(([key, def]) => {
+ const fieldDef = def as FieldDefinition;
+ defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : "";
+ });
+
+ // Setup form with validation schema if available
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ setValue,
+ watch,
+ reset,
+ } = useForm>({
+ defaultValues,
+ resolver: validationSchema ? zodResolver(validationSchema) : undefined,
+ });
+
+ const formValues = watch();
+
+ // Get language-specific validation schema if available
+ useEffect(() => {
+ if (formProps.schemaPath) {
+ const loadLanguageValidationSchema = async () => {
+ try {
+ // Dynamic import of the schema module
+ const schemaModule = await import(formProps.schemaPath);
+
+ // Check if language-specific schema functions are available
+ if (schemaModule.getCreateApplicationSchema) {
+ const langValidationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr");
+
+ // Reset the form with the current values
+ reset(defaultValues);
+
+ // Update the validation schema in formProps for future validations
+ formProps.validationSchema = langValidationSchema;
+ }
+ } catch (error) {
+ console.error("Error loading language-specific validation schema:", error);
+ }
+ };
+
+ loadLanguageValidationSchema();
+ }
+ }, [lang, formProps.schemaPath, reset, defaultValues]);
+
+ // Handle form submission
+ const onSubmit: SubmitHandler> = async (data) => {
+ try {
+ 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}`);
+ }
+
+ const createdItem = await response.json();
+ console.log("Created item:", createdItem);
+ }
+
+ if (refetch) refetch();
+ setMode("list");
+ setSelectedItem(null);
+ } catch (error) {
+ console.error("Error saving form:", error);
+ }
+ };
+
+ // Handle select changes
+ const handleSelectChange = (name: string, value: string) => {
+ setValue(name, value);
+ };
+
+ // Handle checkbox changes
+ const handleCheckboxChange = (name: string, checked: boolean) => {
+ setValue(name, checked);
+ };
+
+ // Translate group names for display dynamically
+ const getGroupTitle = (groupName: string) => {
+ // Check if we have a translation for this group name
+ if (t[groupName]) {
+ return t[groupName];
+ }
+
+ // If no translation is found, just format the name as a fallback
+ const formattedName = groupName
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/_/g, ' ')
+ .replace(/^./, (str) => str.toUpperCase())
+ .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;
+
+ switch (field.type) {
+ case "text":
+ return (
+
+
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "textarea":
+ return (
+
+
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "select":
+ return (
+
+
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "checkbox":
+ return (
+
+
+ handleCheckboxChange(fieldName, checked as boolean)
+ }
+ disabled={field.readOnly}
+ />
+
+ {errorMessage && (
+ {errorMessage}
+ )}
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx
new file mode 100644
index 0000000..ad50410
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx
@@ -0,0 +1,127 @@
+"use client";
+import React, { useEffect, useState } from "react";
+import { CreateComponent } from "./CreateComponent";
+import { UpdateComponent } from "./UpdateComponent";
+import { ViewComponent } from "./ViewComponent";
+import { FormDisplayProps } from "./types";
+
+export function FormDisplay({
+ initialData,
+ mode,
+ refetch,
+ setMode,
+ setSelectedItem,
+ onCancel,
+ lang,
+ translations,
+ formProps = {},
+ apiUrl,
+}: FormDisplayProps) {
+ const [enhancedFormProps, setEnhancedFormProps] = useState(formProps);
+
+
+ useEffect(() => {
+ const updateFormProps = async () => {
+ try {
+ if (formProps.schemaPath) {
+ const schemaModule = await import(formProps.schemaPath);
+
+ let fieldDefs;
+ if (schemaModule.fieldDefinitions?.getDefinitionsByMode) {
+ fieldDefs = schemaModule.fieldDefinitions.getDefinitionsByMode(mode);
+ } else if (mode === "create" && schemaModule.createFieldDefinitions) {
+ fieldDefs = schemaModule.createFieldDefinitions;
+ } else if (mode === "update" && schemaModule.updateFieldDefinitions) {
+ fieldDefs = schemaModule.updateFieldDefinitions;
+ } else if (mode === "view" && schemaModule.viewFieldDefinitions) {
+ fieldDefs = schemaModule.viewFieldDefinitions;
+ }
+
+ let validationSchema;
+ if (mode === "create" && schemaModule.getCreateApplicationSchema) {
+ validationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr");
+ } else if (mode === "update" && schemaModule.getUpdateApplicationSchema) {
+ validationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr");
+ } else if (mode === "view" && schemaModule.ViewApplicationSchema) {
+ validationSchema = schemaModule.ViewApplicationSchema;
+ } else if (schemaModule.ApplicationSchema) {
+ validationSchema = schemaModule.ApplicationSchema;
+ }
+
+ const groupedFieldDefs = schemaModule.baseFieldDefinitions || {};
+ setEnhancedFormProps({
+ ...formProps,
+ fieldDefinitions: fieldDefs || {},
+ validationSchema,
+ fieldsByMode: schemaModule.fieldsByMode || {},
+ groupedFieldDefinitions: groupedFieldDefs,
+ currentLang: lang,
+ schemaPath: formProps.schemaPath
+ });
+ } else {
+ setEnhancedFormProps({
+ ...formProps,
+ currentLang: lang
+ });
+ }
+ } catch (error) {
+ console.error("Error loading schema definitions:", error);
+ setEnhancedFormProps({
+ ...formProps,
+ currentLang: lang
+ });
+ }
+ };
+
+ updateFormProps();
+ }, [formProps, mode, lang]);
+
+ switch (mode) {
+ case "create":
+ return (
+
+ key={`create-${lang}`}
+ refetch={refetch}
+ setMode={setMode}
+ setSelectedItem={setSelectedItem}
+ onCancel={onCancel}
+ lang={lang}
+ translations={translations}
+ formProps={enhancedFormProps}
+ apiUrl={apiUrl}
+ />
+ );
+ case "update":
+ const updateKey = `update-${lang}-${(initialData as any)?.uu_id || 'new'}`;
+ return initialData ? (
+
+ key={updateKey}
+ initialData={initialData}
+ refetch={refetch}
+ setMode={setMode}
+ setSelectedItem={setSelectedItem}
+ onCancel={onCancel}
+ lang={lang}
+ translations={translations}
+ formProps={enhancedFormProps}
+ apiUrl={apiUrl}
+ />
+ ) : null;
+ case "view":
+ return initialData ? (
+
+ key={`view-${lang}`}
+ initialData={initialData}
+ refetch={refetch}
+ setMode={setMode}
+ setSelectedItem={setSelectedItem}
+ onCancel={onCancel}
+ lang={lang}
+ translations={translations}
+ formProps={enhancedFormProps}
+ />
+ ) : null;
+ default:
+ return null;
+ }
+}
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx
new file mode 100644
index 0000000..cebaa2c
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx
@@ -0,0 +1,412 @@
+"use client";
+import React, { useState, useEffect, useMemo } from "react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { UpdateComponentProps, 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 { Alert, AlertDescription } from "@/components/ui/alert";
+import { AlertCircle } from "lucide-react";
+
+export function UpdateComponent({
+ initialData,
+ refetch,
+ setMode,
+ setSelectedItem,
+ onCancel,
+ lang,
+ translations,
+ apiUrl,
+ formProps = {},
+}: UpdateComponentProps) {
+ const t = translations[lang as keyof typeof translations] || {};
+
+ // Get field definitions from formProps if available
+ const fieldDefinitions = formProps.fieldDefinitions || {};
+ const validationSchema = formProps.validationSchema;
+
+ // Ensure field definitions are processed only once
+ const processedFieldDefinitions = useMemo(() => {
+ const processed = { ...fieldDefinitions };
+ // Make all fields editable except system fields
+ Object.entries(processed).forEach(([fieldName, definition]) => {
+ if (fieldName !== 'uu_id' && fieldName !== 'created_at' && fieldName !== 'updated_at') {
+ (processed[fieldName] as FieldDefinition).readOnly = false;
+ }
+ });
+ return processed;
+ }, [fieldDefinitions]);
+
+ const [groupedFields, setGroupedFields] = useState>({});
+
+ useEffect(() => {
+ if (Object.keys(processedFieldDefinitions).length > 0) {
+ const groups: Record = {};
+
+ // Group the processed field definitions
+ Object.entries(processedFieldDefinitions).forEach(([fieldName, definition]) => {
+ // Convert to FieldDefinition type
+ const def = definition as FieldDefinition;
+
+ // Add the field name to the definition
+ const fieldDef = { ...def, name: fieldName };
+
+ // Add to the appropriate group
+ if (!groups[def.group]) {
+ groups[def.group] = [];
+ }
+ groups[def.group].push(fieldDef);
+ });
+ setGroupedFields(groups);
+ }
+ }, [processedFieldDefinitions]);
+
+ const defaultValues: Record = {};
+ Object.entries(processedFieldDefinitions).forEach(([key, def]) => {
+ const fieldDef = def as FieldDefinition;
+ defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : "";
+ });
+
+ if (initialData) {
+ Object.assign(defaultValues, initialData as Record);
+ }
+
+ // Track the current language to detect changes
+ const [currentLang, setCurrentLang] = useState(lang);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting, isValid },
+ setValue,
+ watch,
+ reset,
+ trigger,
+ } = useForm({
+ defaultValues,
+ resolver: validationSchema ? zodResolver(validationSchema) : undefined,
+ mode: "onChange",
+ });
+
+ useEffect(() => {
+ if (Object.keys(errors).length > 0) {
+ console.log("Form errors:", errors);
+ }
+ }, [errors]);
+
+ useEffect(() => {
+ if (initialData) {
+ reset({ ...initialData as Record });
+ }
+ }, [initialData, reset]);
+
+ // Detect language changes and update validation schema
+ useEffect(() => {
+ // If language has changed, update the form
+ if (currentLang !== lang || formProps.currentLang !== lang) {
+ const updateValidationForLanguage = async () => {
+ try {
+ // If we have a schema path, dynamically load the schema for the current language
+ if (formProps.schemaPath) {
+ // Dynamic import of the schema module
+ const schemaModule = await import(formProps.schemaPath);
+
+ // Check if language-specific schema functions are available
+ if (schemaModule.getUpdateApplicationSchema) {
+ // Get the schema for the current language
+ const langValidationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr");
+
+ // Save current form values
+ const formValues = watch();
+
+ // Reset the form with current values but clear errors
+ reset(formValues, {
+ keepDirty: true,
+ keepValues: true,
+ keepErrors: false
+ });
+
+ // Manually trigger validation after reset
+ setTimeout(() => {
+ // Trigger validation for all fields to show updated error messages
+ Object.keys(formValues).forEach(fieldName => {
+ trigger(fieldName);
+ });
+ }, 0);
+
+ // Update our tracked language
+ setCurrentLang(lang);
+ }
+ }
+ } catch (error) {
+ console.error("Error updating validation schema for language:", error);
+ }
+ };
+
+ updateValidationForLanguage();
+ }
+ }, [lang, formProps.currentLang, currentLang, formProps.schemaPath, reset, watch, trigger]);
+
+ const formValues = watch();
+
+ // Handle form submission
+ const onSubmit = async (data: any) => {
+ try {
+
+ 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).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];
+ }
+ });
+
+ const updateUrl = `${apiUrl}/update?uuid=${uuid}`;
+ console.log("Updating application with payload:", dataToSend, '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 details:", error);
+ }
+ };
+
+ // Handle select changes
+ const handleSelectChange = (name: string, value: string) => {
+ setValue(name, value);
+ };
+
+ // Handle checkbox changes
+ const handleCheckboxChange = (name: string, checked: boolean) => {
+ setValue(name, checked);
+ };
+
+ // Translate group names for display dynamically
+ const getGroupTitle = (groupName: string) => {
+ // Check if we have a translation for this group name
+ if (t[groupName]) {
+ return t[groupName];
+ }
+
+ // If no translation is found, just format the name as a fallback
+ const formattedName = groupName
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/_/g, ' ')
+ .replace(/^./, (str) => str.toUpperCase())
+ .replace(/\b\w/g, (c) => c.toUpperCase());
+
+ return formattedName;
+ };
+
+ const renderField = (fieldName: string, field: FieldDefinition) => {
+ const errorMessage = errors[fieldName]?.message as string;
+ const fieldValue = formValues[fieldName];
+
+ const renderLabel = () => (
+
+ );
+
+ if (field.readOnly) {
+ return (
+
+ {renderLabel()}
+
+ {field.type === "checkbox" ?
+ (fieldValue ? "Yes" : "No") :
+ (fieldValue || "-")}
+
+
+ );
+ }
+
+ // For editable fields, render the appropriate input type
+ switch (field.type) {
+ case "text":
+ return (
+
+ {renderLabel()}
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "textarea":
+ return (
+
+ {renderLabel()}
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "select":
+ return (
+
+ {renderLabel()}
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ case "checkbox":
+ return (
+
+
+ handleCheckboxChange(fieldName, checked as boolean)
+ }
+ />
+ {renderLabel()}
+ {errorMessage && (
+ {errorMessage}
+ )}
+
+ );
+
+ case "date":
+ return (
+
+ {renderLabel()}
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/ViewComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/ViewComponent.tsx
new file mode 100644
index 0000000..a81a3f6
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/ViewComponent.tsx
@@ -0,0 +1,219 @@
+"use client";
+import React, { useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { ViewComponentProps, FieldDefinition } from "./types";
+import { Label } from "@/components/ui/label";
+import { z } from "zod";
+
+
+// Utility function to format field label
+const formatFieldLabel = (fieldName: string) => fieldName.replace(/_/g, ' ').replace(/^./, (str) => str.toUpperCase()).replace(/\b\w/g, (c) => c.toUpperCase());
+
+// Component for rendering a single field
+const ViewField: React.FC<{
+ fieldName: string;
+ value: any;
+ label: string;
+ lang: string;
+ translations: any;
+ hasError?: string;
+}> = ({ fieldName, value, label, lang, translations: t, hasError }) => {
+ const formatFieldValue = () => {
+ if (value === undefined || value === null) return "-";
+
+ switch (true) {
+ case typeof value === 'string' && !isNaN(Date.parse(value)):
+ return new Date(value).toLocaleString(lang === "tr" ? "tr-TR" : "en-US");
+ case typeof value === 'boolean':
+ return value ? (
+
+ {t.yes || "Yes"}
+
+ ) : (
+
+ {t.no || "No"}
+
+ );
+ case fieldName === "application_type" && (value === "employee" || value === "occupant"):
+ return t[value] || value;
+ default:
+ return value;
+ }
+ };
+
+ return (
+
+
+
+ {formatFieldValue()}
+
+ {hasError &&
{hasError}
}
+
+ );
+};
+
+// Component for rendering a group of fields
+const ViewFieldGroup: React.FC<{
+ groupName: string;
+ fields: FieldDefinition[];
+ initialData: any;
+ lang: string;
+ translations: any;
+ validationErrors: Record;
+}> = ({ groupName, fields, initialData, lang, translations, validationErrors }) => {
+ const getGroupTitle = (name: string) => { return translations[name] || formatFieldLabel(name); };
+
+ return (
+
+
+ {getGroupTitle(groupName)}
+
+
+
+ {fields.map((field) => {
+ const fieldName = field.name || "";
+ const value = initialData ? (initialData as any)[fieldName] : undefined;
+ const hasError = validationErrors[fieldName];
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
+
+export function ViewComponent({
+ initialData,
+ setMode,
+ setSelectedItem,
+ onCancel,
+ lang,
+ translations,
+ formProps = {},
+}: ViewComponentProps) {
+ const t = translations[lang as keyof typeof translations] || {};
+
+ const fieldDefinitions = formProps.fieldDefinitions || {};
+ const validationSchema = formProps.validationSchema as z.ZodObject | undefined;
+
+ const [groupedFields, setGroupedFields] = useState>({});
+ const [validationErrors, setValidationErrors] = useState>({});
+
+ useEffect(() => {
+ if (Object.keys(fieldDefinitions).length > 0) {
+ const groups: Record = {};
+
+ Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => {
+ const def = definition as FieldDefinition;
+ if (!groups[def.group]) { groups[def.group] = []; }
+ groups[def.group].push({ ...def, name: fieldName });
+ });
+
+ setGroupedFields(groups);
+ }
+ }, [fieldDefinitions]);
+
+ useEffect(() => {
+ if (validationSchema && initialData) {
+ try {
+ validationSchema.parse(initialData);
+ setValidationErrors({});
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ console.warn('View data validation issues (not shown to user):', error.errors);
+ setValidationErrors({});
+ }
+ }
+ }
+ }, [initialData, validationSchema]);
+
+ const handleEdit = () => { setMode("update") };
+
+ console.log("Grouped Fields", groupedFields);
+ console.log("Validation Errors", validationErrors);
+
+ return (
+
+
+ {t.view || "View"}
+ {t.viewDescription || "View item details"}
+
+
+
+
+
+
+
+ {
+ Object.keys(groupedFields).length > 0 ? (
+ Object.entries(groupedFields).map(([groupName, fields]) => (
+
+ ))
+ ) : (
+ initialData && (
+
+ {validationSchema ? (
+ Object.entries(validationSchema.shape || {}).map(([fieldName, _]) => {
+ const value = (initialData as any)[fieldName];
+ if (value === undefined || value === null) return null;
+ return (
+
+ );
+ })
+ ) : (
+ Object.entries(initialData as Record).map(([fieldName, value]) => {
+ if (value === undefined || value === null) return null;
+ return (
+
+ );
+ })
+ )}
+
+ )
+ )}
+
+
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/index.ts b/WebServices/client-frontend/src/components/common/FormDisplay/index.ts
new file mode 100644
index 0000000..cb64a23
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/index.ts
@@ -0,0 +1,5 @@
+// Export the main components
+export { FormDisplay } from "./FormDisplay";
+
+// Export types
+export type { FormMode } from "./types";
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/types.ts b/WebServices/client-frontend/src/components/common/FormDisplay/types.ts
new file mode 100644
index 0000000..af32e71
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/FormDisplay/types.ts
@@ -0,0 +1,51 @@
+"use client";
+
+export interface FieldDefinition {
+ type: string;
+ group: string;
+ label: { tr: string; en: string };
+ options?: string[];
+ readOnly?: boolean;
+ required?: boolean;
+ defaultValue?: any;
+ name?: string;
+}
+
+export type FormMode = "list" | "create" | "update" | "view";
+export type FormModeView = "list" | "view";
+
+export interface BaseFormProps {
+ initialData?: T;
+ refetch?: () => void;
+ setMode: React.Dispatch>;
+ setSelectedItem: React.Dispatch>;
+ onCancel: () => void;
+ lang: string;
+ translations: Record>;
+ formProps?: Record;
+ apiUrl?: string;
+}
+
+export interface CreateComponentProps extends BaseFormProps {}
+
+export interface UpdateComponentProps extends BaseFormProps {
+ initialData: T;
+ apiUrl: string;
+}
+
+export interface ViewComponentProps extends BaseFormProps {
+ initialData: T;
+}
+
+export interface FormDisplayProps {
+ mode: FormMode | FormModeView;
+ initialData?: T;
+ refetch?: (additionalParams?: Record) => void;
+ setMode: React.Dispatch>;
+ setSelectedItem: React.Dispatch>;
+ onCancel: () => void;
+ lang: string;
+ translations: Record>;
+ formProps?: Record;
+ apiUrl: string;
+}
diff --git a/WebServices/client-frontend/src/components/common/HeaderSelections/GridSelectionComponent.tsx b/WebServices/client-frontend/src/components/common/HeaderSelections/GridSelectionComponent.tsx
new file mode 100644
index 0000000..f5e6f45
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/HeaderSelections/GridSelectionComponent.tsx
@@ -0,0 +1,42 @@
+"use client";
+import React from "react";
+
+export type GridSize = 1 | 2 | 3 | 4 | 5 | 6;
+
+interface GridSelectionComponentProps {
+ gridCols: GridSize;
+ setGridCols: (size: GridSize) => void;
+ translations?: Record;
+ lang?: string;
+}
+
+export const GridSelectionComponent: React.FC = ({
+ gridCols,
+ setGridCols,
+ translations,
+ lang = "en",
+}) => {
+ const t = translations?.[lang] || {};
+
+ const handleChange = (e: React.ChangeEvent) => {
+ setGridCols(Number(e.target.value) as GridSize);
+ };
+
+ return (
+
+ {t.gridSize || "Grid Size:"}:
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx b/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx
new file mode 100644
index 0000000..7ea13b0
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx
@@ -0,0 +1,27 @@
+"use client";
+import React from "react";
+import { Language, LanguageSelectionComponentProps } from "@/components/common/schemas";
+
+export const LanguageSelectionComponent: React.FC = ({
+ lang,
+ setLang,
+ className = "p-2 border rounded",
+}) => {
+
+ const handleChange = (e: React.ChangeEvent) => {
+ setLang(e.target.value as Language);
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/Loader/loader.tsx b/WebServices/client-frontend/src/components/common/Loader/loader.tsx
new file mode 100644
index 0000000..ada4310
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/Loader/loader.tsx
@@ -0,0 +1,15 @@
+import React from 'react'
+
+const Loader = () => {
+ return (
+ <>
+
+ >
+ )
+}
+
+export default Loader
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/PageNavigation.tsx b/WebServices/client-frontend/src/components/common/PaginationModifiers/PageNavigation.tsx
new file mode 100644
index 0000000..ca167bc
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/PageNavigation.tsx
@@ -0,0 +1,116 @@
+"use client";
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { PaginationBaseProps } from "./types";
+
+/**
+ * Calculate the page numbers to display in pagination
+ * @param currentPage Current active page
+ * @param totalPages Total number of pages
+ * @param maxButtons Maximum number of page buttons to show (default: 5)
+ * @returns Array of page numbers to display
+ */
+const getPageNumbers = (currentPage: number, totalPages: number, maxButtons: number = 5): number[] => {
+ const pageNumbers: number[] = [];
+
+ // If we have fewer pages than the maximum buttons, show all pages
+ if (totalPages <= maxButtons) {
+ for (let i = 1; i <= totalPages; i++) {
+ pageNumbers.push(i);
+ }
+ }
+ // If we're near the beginning, show first maxButtons pages
+ else if (currentPage <= 3) {
+ for (let i = 1; i <= maxButtons; i++) {
+ pageNumbers.push(i);
+ }
+ }
+ // If we're near the end, show last maxButtons pages
+ else if (currentPage >= totalPages - 2) {
+ for (let i = totalPages - maxButtons + 1; i <= totalPages; i++) {
+ pageNumbers.push(i);
+ }
+ }
+ // Otherwise, show pages centered around current page
+ else {
+ for (let i = currentPage - 2; i <= currentPage + 2; i++) {
+ pageNumbers.push(i);
+ }
+ }
+
+ return pageNumbers;
+};
+
+export const PageNavigation: React.FC = ({
+ pagination,
+ updatePagination,
+ loading = false,
+ lang,
+ translations,
+}) => {
+ const t = translations[lang] || {};
+
+ const handlePageChange = (newPage: number) => {
+ if (newPage >= 1 && newPage <= pagination.totalPages) {
+ updatePagination({ page: newPage });
+ }
+ };
+
+ // Get the page numbers to display
+ const pageNumbers = getPageNumbers(pagination.page, pagination.totalPages);
+
+ return (
+
+ {pagination.back ? (
+
+ ) : (
+
+ )}
+
+ {/* Page number buttons */}
+
+ {pageNumbers.map((pageNum) => (
+
+ ))}
+
+
+ {pagination.page < pagination.totalPages ? (
+
+ ) : (
+
+ )}
+
+ {/* Page text display */}
+
+ {t.page || "Page"} {pagination.page} {t.of || "of"} {pagination.totalPages}
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/PageSizeSelector.tsx b/WebServices/client-frontend/src/components/common/PaginationModifiers/PageSizeSelector.tsx
new file mode 100644
index 0000000..74c272c
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/PageSizeSelector.tsx
@@ -0,0 +1,52 @@
+"use client";
+import React from "react";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { PaginationBaseProps } from "./types";
+
+interface PageSizeSelectorProps extends PaginationBaseProps {
+ pageSizeOptions?: number[];
+}
+
+export const PageSizeSelector: React.FC = ({
+ pagination,
+ updatePagination,
+ lang,
+ translations,
+ pageSizeOptions = [5, 10, 20, 50],
+}) => {
+ const t = translations[lang] || {};
+
+ return (
+
+
+ {t.itemsPerPage || "Items per page"}
+
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationStats.tsx b/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationStats.tsx
new file mode 100644
index 0000000..81ba5d8
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationStats.tsx
@@ -0,0 +1,36 @@
+"use client";
+import React from "react";
+import { PaginationBaseProps } from "./types";
+
+export const PaginationStats: React.FC = ({
+ pagination,
+ lang,
+ translations,
+}) => {
+ const t = translations[lang] || {};
+
+ return (
+
+
+ {t.showing || "Showing"}{" "}
+ {/* Show the range based on filtered count when available */}
+ {(pagination.totalCount || pagination.allCount || 0) > 0
+ ? (pagination.page - 1) * pagination.size + 1
+ : 0}{" "}
+ -{" "}
+ {Math.min(
+ pagination.page * pagination.size,
+ pagination.totalCount || pagination.allCount || 0
+ )}{" "}
+ {t.of || "of"} {pagination.totalCount || pagination.allCount || 0} {t.items || "items"}
+
+ {pagination.totalCount &&
+ pagination.totalCount !== (pagination.allCount || 0) && (
+
+ {t.total || "Total"}: {pagination.allCount || 0} {t.items || "items"} ({t.filtered || "Filtered"}:{" "}
+ {pagination.totalCount} {t.items || "items"})
+
+ )}
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationToolsComponent.tsx b/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationToolsComponent.tsx
new file mode 100644
index 0000000..aaaf97f
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/PaginationToolsComponent.tsx
@@ -0,0 +1,43 @@
+"use client";
+import React from "react";
+import { PaginationBaseProps } from "./types";
+import { PaginationStats } from "./PaginationStats";
+import { PageNavigation } from "./PageNavigation";
+import { PageSizeSelector } from "./PageSizeSelector";
+
+export const PaginationToolsComponent: React.FC = ({
+ pagination,
+ updatePagination,
+ loading = false,
+ lang,
+ translations,
+}) => {
+ return (
+
+ {/* Pagination stats - left side */}
+
+
+ {/* Navigation buttons - center */}
+
+
+ {/* Items per page selector - right side */}
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/index.ts b/WebServices/client-frontend/src/components/common/PaginationModifiers/index.ts
new file mode 100644
index 0000000..9927a3a
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/index.ts
@@ -0,0 +1,5 @@
+export * from './PaginationToolsComponent';
+export * from './PaginationStats';
+export * from './PageNavigation';
+export * from './PageSizeSelector';
+export * from './types';
diff --git a/WebServices/client-frontend/src/components/common/PaginationModifiers/types.ts b/WebServices/client-frontend/src/components/common/PaginationModifiers/types.ts
new file mode 100644
index 0000000..2ca8844
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/PaginationModifiers/types.ts
@@ -0,0 +1,19 @@
+import { PagePagination } from "../hooks/useDataFetching";
+
+export interface ResponseMetadata {
+ totalCount: number;
+ totalItems: number;
+ totalPages: number;
+ pageCount: number;
+ allCount?: number;
+ next: boolean;
+ back: boolean;
+}
+
+export interface PaginationBaseProps {
+ pagination: PagePagination;
+ updatePagination: (updates: Partial) => void;
+ loading?: boolean;
+ lang: string;
+ translations: Record;
+}
diff --git a/WebServices/client-frontend/src/components/common/QueryModifiers/SelectQueryModifier.tsx b/WebServices/client-frontend/src/components/common/QueryModifiers/SelectQueryModifier.tsx
new file mode 100644
index 0000000..7fd6cb3
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/QueryModifiers/SelectQueryModifier.tsx
@@ -0,0 +1,67 @@
+"use client";
+import React, { useCallback } from "react";
+import { Button } from "@/components/ui/button";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { SelectQueryModifierProps } from "./types";
+
+export const SelectQueryModifier: React.FC = ({
+ fieldKey,
+ value,
+ label,
+ options,
+ placeholder,
+ onQueryChange,
+ translations,
+ lang,
+}) => {
+ const t = translations[lang] || {};
+
+ const handleChange = useCallback((newValue: string) => {
+ const formattedValue = newValue.trim() ? `%${newValue.trim()}%` : null;
+ onQueryChange(`${fieldKey}__ilike`, formattedValue);
+ }, [fieldKey, onQueryChange]);
+
+ const handleClear = useCallback(() => {
+ onQueryChange(fieldKey, null);
+ onQueryChange(`${fieldKey}__ilike`, null);
+ }, [fieldKey, onQueryChange]);
+
+ return (
+
+
+
+ {value && (
+
+ )}
+
+
+
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/QueryModifiers/TextQueryModifier.tsx b/WebServices/client-frontend/src/components/common/QueryModifiers/TextQueryModifier.tsx
new file mode 100644
index 0000000..91068cb
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/QueryModifiers/TextQueryModifier.tsx
@@ -0,0 +1,69 @@
+"use client";
+import React, { useCallback } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Search, X } from "lucide-react";
+import { TextQueryModifierProps } from "./types";
+
+export const TextQueryModifier: React.FC = ({
+ fieldKey,
+ value,
+ label,
+ placeholder,
+ translations,
+ lang,
+ onQueryChange,
+}) => {
+ const t = translations[lang] || {};
+ const handleChange = useCallback((e: React.ChangeEvent) => {
+ const newValue = e.target.value; onQueryChange(fieldKey, newValue);
+ }, [fieldKey, onQueryChange]);
+
+ const handleClear = useCallback(() => {
+ onQueryChange(fieldKey, null); onQueryChange(`${fieldKey}__ilike`, null);
+ }, [fieldKey, onQueryChange]);
+
+ const handleKeyUp = useCallback((e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ const formattedValue = value.trim() ? `%${value.trim()}%` : null;
+ onQueryChange(`${fieldKey}__ilike`, formattedValue);
+ }
+ }, [fieldKey, value, onQueryChange]);
+
+ const handleSearch = useCallback(() => {
+ const formattedValue = value.trim() ? `%${value.trim()}%` : null;
+ onQueryChange(`${fieldKey}__ilike`, formattedValue);
+ }, [fieldKey, value, onQueryChange]);
+
+ return (
+
+
+
+
+
+
+ {value && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/QueryModifiers/TypeQueryModifier.tsx b/WebServices/client-frontend/src/components/common/QueryModifiers/TypeQueryModifier.tsx
new file mode 100644
index 0000000..75efe6e
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/QueryModifiers/TypeQueryModifier.tsx
@@ -0,0 +1,51 @@
+"use client";
+import React, { useCallback, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { User } from "lucide-react";
+import { TypeQueryModifierProps } from "./types";
+
+export const TypeQueryModifier: React.FC = ({
+ fieldKey,
+ value,
+ options,
+ onQueryChange,
+ translations,
+ lang,
+ defaultValue,
+}) => {
+ const t = translations[lang] || {};
+
+ const handleTypeSelect = useCallback((selectedValue: string) => {
+ const formattedValue = selectedValue.trim() ? `%${selectedValue.trim()}%` : null;
+ onQueryChange(`${fieldKey}__ilike`, formattedValue);
+ }, [fieldKey, onQueryChange]);
+
+ // Apply default value on initial render if no value is set
+ useEffect(() => {
+ if (defaultValue && !value && options.some(opt => opt.value === defaultValue)) {
+ handleTypeSelect(defaultValue);
+ }
+ }, [defaultValue, value, options, handleTypeSelect]);
+
+ if (!options || options.length === 0) return null;
+
+ return (
+
+
+
+ {t.typeSelection || "Type Selection"}
+
+ {options?.map((option) => (
+
+ ))}
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/QueryModifiers/index.ts b/WebServices/client-frontend/src/components/common/QueryModifiers/index.ts
new file mode 100644
index 0000000..8135a35
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/QueryModifiers/index.ts
@@ -0,0 +1,4 @@
+export * from './types';
+export * from './TextQueryModifier';
+export * from './SelectQueryModifier';
+export * from './TypeQueryModifier';
diff --git a/WebServices/client-frontend/src/components/common/QueryModifiers/types.ts b/WebServices/client-frontend/src/components/common/QueryModifiers/types.ts
new file mode 100644
index 0000000..81c760f
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/QueryModifiers/types.ts
@@ -0,0 +1,35 @@
+import { ReactNode } from 'react';
+
+export interface QueryModifierProps {
+ onQueryChange: (key: string, value: string | null) => void;
+ translations: Record>;
+ lang: string;
+}
+
+export interface TextQueryModifierProps extends QueryModifierProps {
+ fieldKey: string;
+ value: string;
+ label: string;
+ placeholder?: string;
+}
+
+export interface SelectQueryModifierProps extends QueryModifierProps {
+ fieldKey: string;
+ value: string;
+ label: string;
+ options: { value: string; label: string }[];
+ placeholder?: string;
+}
+
+export interface TypeQueryModifierProps extends QueryModifierProps {
+ fieldKey: string;
+ value: string;
+ options: { value: string; label: string; icon?: React.ReactNode }[];
+}
+
+export interface QueryModifierValue {
+ key: string;
+ value: string;
+}
+
+export type QueryModifierResult = Record;
diff --git a/WebServices/client-frontend/src/components/common/ReadMe.md b/WebServices/client-frontend/src/components/common/ReadMe.md
new file mode 100644
index 0000000..806f089
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/ReadMe.md
@@ -0,0 +1,128 @@
+# EVYOS Management Frontend - Common Components
+
+## Overview
+
+This directory contains modular, reusable components for building consistent UIs across the EVYOS Management Frontend. These components follow a modular design pattern where complex functionality is broken down into smaller, focused components with clear responsibilities.
+
+## Component Structure
+
+### ActionButtonsDisplay
+- **CreateButton**: A button component for triggering create actions with translation support
+- **CustomButtonComponent**: Configurable button component with selection state support
+- **types.ts**: Shared type definitions for button components
+
+### CardDisplay
+- **CardDisplay**: Main component for displaying data in a responsive grid layout
+- **CardItem**: Individual card component with customizable fields and actions
+- **CardSkeleton**: Loading state placeholder for cards
+- **schema.ts**: API response and data schemas
+- **utils.ts**: Helper functions for card operations
+
+### FormDisplay
+- **FormDisplay**: Container component that handles form mode switching
+- **CreateComponent**: Form implementation for creating new records
+- **UpdateComponent**: Form implementation for updating existing records
+- **ViewComponent**: Read-only view of record details
+- **types.ts**: Type definitions for form components and modes
+
+### HeaderSelections
+- **GridSelectionComponent**: Controls the number of columns in card grid layouts
+- **LanguageSelectionComponent**: Language switcher with translation support
+
+### PaginationModifiers
+- **PaginationToolsComponent**: Main container for pagination controls
+- **PaginationStats**: Displays record count information
+- **PageNavigation**: Handles page navigation buttons with smart page number calculation
+- **PageSizeSelector**: Controls items per page selection
+- **types.ts**: Type definitions including ResponseMetadata interface
+
+### QueryModifiers
+- **TextQueryModifier**: Text search input with clear functionality
+- **SelectQueryModifier**: Dropdown selection for filtering
+- **TypeQueryModifier**: Button-based type selection
+- **types.ts**: Shared interfaces for query components
+
+### Hooks
+- **useApiData**: Custom hook for fetching and managing API data with pagination
+- **useDataFetching**: Base hook for data fetching with pagination, sorting, and filtering
+
+## Usage Example
+
+```tsx
+// Import components
+import { CardDisplay } from "@/components/common/CardDisplay";
+import { TextQueryModifier, SelectQueryModifier, TypeQueryModifier } from "@/components/common/QueryModifiers";
+import { CreateButton } from "@/components/common/ActionButtonsDisplay/CreateButton";
+import { PaginationToolsComponent } from "@/components/common/PaginationModifiers/PaginationToolsComponent";
+import { GridSelectionComponent, GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent";
+import { LanguageSelectionComponent, Language } from "@/components/common/HeaderSelections/LanguageSelectionComponent";
+import { FormDisplay } from "@/components/common/FormDisplay/FormDisplay";
+import { useApiData } from "@/components/common/hooks/useApiData";
+
+// Use the API data hook
+const {
+ data,
+ pagination,
+ loading,
+ error,
+ updatePagination,
+ refetch
+} = useApiData("/api/your-endpoint");
+
+// Define fields to display
+const showFields = ["field1", "field2", "field3"];
+
+// Example component usage
+
+
+
+
+
+
+
+```
+
+## API Response Structure
+
+Components expect API responses in this format:
+
+```typescript
+interface ApiResponse {
+ data: T[];
+ pagination: {
+ page: number;
+ size: number;
+ totalCount: number;
+ totalItems: number;
+ totalPages: number;
+ pageCount: number;
+ allCount?: number;
+ orderField: string[];
+ orderType: string[];
+ query: Record;
+ next: boolean;
+ back: boolean;
+ };
+}
+```
diff --git a/WebServices/client-frontend/src/components/common/Screenshot from 2025-04-29 19-36-45.png b/WebServices/client-frontend/src/components/common/Screenshot from 2025-04-29 19-36-45.png
new file mode 100644
index 0000000..309f151
Binary files /dev/null and b/WebServices/client-frontend/src/components/common/Screenshot from 2025-04-29 19-36-45.png differ
diff --git a/WebServices/client-frontend/src/components/common/SortingComponent/SortingComponent.tsx b/WebServices/client-frontend/src/components/common/SortingComponent/SortingComponent.tsx
new file mode 100644
index 0000000..2d1a4f8
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/SortingComponent/SortingComponent.tsx
@@ -0,0 +1,49 @@
+"use client";
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { ArrowDown, ArrowUp } from "lucide-react";
+import { SortingComponentProps, SortField } from "./types";
+
+export const SortingComponent: React.FC = ({
+ sortField,
+ sortDirection,
+ onSort,
+ translations,
+ lang,
+ sortFields = [
+ { key: "name", label: "Name" },
+ { key: "code", label: "Code" },
+ { key: "type", label: "Type" },
+ { key: "created_at", label: "Created" },
+ ],
+}) => {
+ const t = translations?.[lang] || {};
+
+ return (
+
+
+ {t.sortBy || "Sort by:"}
+
+ {sortFields.map((field) => (
+
+ ))}
+
+ );
+};
diff --git a/WebServices/client-frontend/src/components/common/SortingComponent/index.ts b/WebServices/client-frontend/src/components/common/SortingComponent/index.ts
new file mode 100644
index 0000000..11485fc
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/SortingComponent/index.ts
@@ -0,0 +1,2 @@
+export * from './SortingComponent';
+export * from './types';
diff --git a/WebServices/client-frontend/src/components/common/SortingComponent/types.ts b/WebServices/client-frontend/src/components/common/SortingComponent/types.ts
new file mode 100644
index 0000000..e7486b1
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/SortingComponent/types.ts
@@ -0,0 +1,13 @@
+export interface SortField {
+ key: string;
+ label: string;
+}
+
+export interface SortingComponentProps {
+ sortField: string | null;
+ sortDirection: "asc" | "desc" | null;
+ onSort: (field: string) => void;
+ translations?: Record;
+ lang: string;
+ sortFields?: SortField[];
+}
diff --git a/WebServices/client-frontend/src/components/common/hooks/useApiData.ts b/WebServices/client-frontend/src/components/common/hooks/useApiData.ts
new file mode 100644
index 0000000..3f8ec3d
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/hooks/useApiData.ts
@@ -0,0 +1,51 @@
+import { useDataFetching, ApiResponse } from "./useDataFetching";
+import { RequestParams } from "../schemas";
+import { defaultPaginationResponse } from "@/app/api/utils/types";
+
+/**
+ * Hook for fetching data from Next.js API routes
+ * @param endpoint The API endpoint to fetch data from (e.g., '/api/applications')
+ * @param initialParams Initial request parameters
+ * @returns Object containing data, pagination, loading, error, updatePagination, and refetch
+ */
+export function useApiData(
+ endpoint: string,
+ initialParams: Partial = {}
+) {
+ const fetchFromApi = async (
+ params: RequestParams
+ ): Promise> => {
+ try {
+ const requestBody = {
+ page: params.page,
+ size: params.size,
+ orderField: params.orderField,
+ orderType: params.orderType,
+ query: params.query,
+ };
+
+ const response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(requestBody),
+ });
+
+ if (!response.ok) {
+ throw new Error(`API request failed with status ${response.status}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error("Error fetching data from API:", error);
+
+ return {
+ data: [],
+ pagination: defaultPaginationResponse,
+ };
+ }
+ };
+
+ return useDataFetching(fetchFromApi, initialParams);
+}
diff --git a/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts b/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts
new file mode 100644
index 0000000..3f34ae8
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts
@@ -0,0 +1,70 @@
+import {
+ retrieveApplicationbyUrl,
+ retrievePageList,
+} from "@/apicalls/cookies/token";
+import { retrievePageByUrl } from "@/eventRouters/pageRetriever";
+import { PageProps } from "@/validations/translations/translation";
+import React from "react";
+
+export interface DashboardPageParams {
+ pageUrl: string;
+ searchParams: Promise<{ [key: string]: string | undefined }>;
+}
+
+export interface DashboardPageResult {
+ activePage: string;
+ searchParamsInstance: { [key: string]: string | undefined };
+ lang: "en" | "tr";
+ PageComponent: React.FC;
+ siteUrlsList: string[];
+}
+
+/**
+ * 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
+ * @throws Error if page URL is invalid or page component is not found
+ */
+export async function useDashboardPage({
+ pageUrl,
+ searchParams,
+}: DashboardPageParams): Promise {
+ let searchParamsInstance: { [key: string]: string | undefined } = {};
+ const defaultLang = "en";
+
+ if (!pageUrl || typeof pageUrl !== "string") {
+ throw new Error(`Invalid page URL: ${pageUrl}`);
+ }
+
+ try {
+ searchParamsInstance = await searchParams;
+ } catch (err) {
+ console.error("Error resolving search parameters:", err);
+ throw err;
+ }
+
+ const applicationName = (await retrieveApplicationbyUrl(pageUrl)) || "";
+ const siteUrlsList = (await retrievePageList()) || [];
+ const lang = (searchParamsInstance?.lang as "en" | "tr") || defaultLang;
+ if (lang !== "en" && lang !== "tr") {
+ console.warn(
+ `Invalid language "${lang}" specified, falling back to "${defaultLang}"`
+ );
+ }
+
+ const PageComponent = retrievePageByUrl(pageUrl, applicationName);
+ if (!PageComponent) {
+ throw new Error(`Page component not found for URL: ${pageUrl}`);
+ }
+ return {
+ activePage: pageUrl,
+ searchParamsInstance,
+ lang,
+ PageComponent,
+ siteUrlsList,
+ };
+}
+
+export default useDashboardPage;
diff --git a/WebServices/client-frontend/src/components/common/hooks/useDataFetching.ts b/WebServices/client-frontend/src/components/common/hooks/useDataFetching.ts
new file mode 100644
index 0000000..0fc0539
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/hooks/useDataFetching.ts
@@ -0,0 +1,167 @@
+import { useState, useEffect, useCallback, useRef } from "react";
+import { RequestParams, ResponseMetadata } from "../schemas";
+
+export interface PagePagination extends RequestParams, ResponseMetadata {}
+
+export interface ApiResponse {
+ data: T[];
+ pagination: PagePagination;
+}
+
+/**
+ * Generic data fetching hook that can be used with any API endpoint
+ * @param fetchFunction - The API function to call for fetching data
+ * @param initialParams - Initial request parameters
+ * @returns Object containing data, pagination, loading, error, updatePagination, and refetch
+ */
+export function useDataFetching(
+ fetchFunction: (params: RequestParams) => Promise>,
+ initialParams: Partial = {}
+) {
+ const [data, setData] = useState([]);
+
+ // Request parameters - these are controlled by the user
+ const [requestParams, setRequestParams] = useState({
+ page: initialParams.page || 1,
+ size: initialParams.size || 10,
+ orderField: initialParams.orderField || ["uu_id"],
+ orderType: initialParams.orderType || ["asc"],
+ query: initialParams.query || {},
+ });
+
+ // Response metadata - these come from the API
+ const [responseMetadata, setResponseMetadata] = useState({
+ totalCount: 0,
+ totalItems: 0,
+ totalPages: 0,
+ pageCount: 0,
+ next: true,
+ back: false,
+ });
+
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchDataFromApi = useCallback(async () => {
+ setLoading(true);
+ try {
+ const result = await fetchFunction({
+ page: requestParams.page,
+ size: requestParams.size,
+ orderField: requestParams.orderField,
+ orderType: requestParams.orderType,
+ query: requestParams.query,
+ });
+
+ if (result && result.data) {
+ setData(result.data);
+
+ // Update response metadata from API response
+ if (result.pagination) {
+ setResponseMetadata({
+ totalCount: result.pagination.totalCount || 0,
+ totalItems: result.pagination.totalCount || 0,
+ totalPages: result.pagination.totalPages || 1,
+ pageCount: result.pagination.pageCount || 0,
+ allCount: result.pagination.allCount || 0,
+ next: result.pagination.next || false,
+ back: result.pagination.back || false,
+ });
+ }
+ }
+
+ setError(null);
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error("Unknown error"));
+ } finally {
+ setLoading(false);
+ }
+ }, [
+ fetchFunction,
+ requestParams.page,
+ requestParams.size,
+ requestParams.orderField,
+ requestParams.orderType,
+ requestParams.query,
+ ]);
+
+ const initialMountRef = useRef(true);
+ const prevRequestParamsRef = useRef(requestParams);
+
+ useEffect(() => {
+ const paramsChanged =
+ JSON.stringify(prevRequestParamsRef.current) !==
+ JSON.stringify(requestParams);
+
+ if (initialMountRef.current || paramsChanged) {
+ const timer = setTimeout(() => {
+ fetchDataFromApi();
+ initialMountRef.current = false;
+ prevRequestParamsRef.current = { ...requestParams };
+ }, 300);
+
+ return () => clearTimeout(timer);
+ }
+ }, [fetchDataFromApi, requestParams]);
+
+ const updatePagination = useCallback((updates: Partial) => {
+ if (updates.query) {
+ const transformedQuery: Record = {};
+
+ Object.entries(updates.query).forEach(([key, value]) => {
+ if (
+ typeof value === "string" &&
+ !key.includes("__") &&
+ value.trim() !== ""
+ ) {
+ transformedQuery[`${key}__ilike`] = `%${value}%`;
+ } else {
+ transformedQuery[key] = value;
+ }
+ });
+
+ updates.query = transformedQuery;
+ if (!updates.hasOwnProperty("page")) {
+ updates.page = 1;
+ }
+ setResponseMetadata({
+ totalCount: 0,
+ totalItems: 0,
+ totalPages: 0,
+ pageCount: 0,
+ allCount: 0,
+ next: true,
+ back: false,
+ });
+ }
+
+ setRequestParams((prev) => ({
+ ...prev,
+ ...updates,
+ }));
+ }, []);
+
+ // Create a combined refetch function
+ const refetch = useCallback(() => {
+ setRequestParams((prev) => ({
+ ...prev,
+ page: 1,
+ }));
+ fetchDataFromApi();
+ }, [fetchDataFromApi]);
+
+ // Combine request params and response metadata
+ const pagination: PagePagination = {
+ ...requestParams,
+ ...responseMetadata,
+ };
+
+ return {
+ data,
+ pagination,
+ loading,
+ error,
+ updatePagination,
+ refetch,
+ };
+}
diff --git a/WebServices/client-frontend/src/components/common/hooks/useStandardApiFetch.ts b/WebServices/client-frontend/src/components/common/hooks/useStandardApiFetch.ts
new file mode 100644
index 0000000..a9b775b
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/hooks/useStandardApiFetch.ts
@@ -0,0 +1,181 @@
+import { useState, useEffect } from 'react';
+
+/**
+ * Cache options for the fetch request
+ */
+export type CacheOptions = {
+ /** Whether to cache the request (default: true) */
+ cache?: boolean;
+ /** Revalidate time in seconds (if not provided, uses Next.js defaults) */
+ revalidate?: number;
+ /** Force cache to be revalidated (equivalent to cache: 'no-store' in fetch) */
+ noStore?: boolean;
+};
+
+/**
+ * Request options for the fetch
+ */
+export type FetchOptions = {
+ /** HTTP method (default: 'GET') */
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+ /** Request headers */
+ headers?: HeadersInit;
+ /** Request body (for POST, PUT, PATCH) */
+ body?: any;
+};
+
+/**
+ * A hook for fetching data from an API endpoint without pagination using Next.js fetch
+ * @param url The API endpoint URL
+ * @param initialParams Initial query parameters
+ * @param options Additional fetch options
+ * @param cacheOptions Cache control options
+ * @returns Object containing data, loading state, error state, and refetch function
+ */
+export function useStandardApiFetch(
+ url: string,
+ initialParams: Record = {},
+ options: FetchOptions = {},
+ cacheOptions: CacheOptions = { cache: true }
+) {
+ const [data, setData] = useState(null);
+ const [params, setParams] = useState>(initialParams);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ /**
+ * Builds the URL with query parameters
+ */
+ const buildUrl = () => {
+ const queryParams = new URLSearchParams();
+
+ // Add all non-null and non-empty params
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== null && value !== '') {
+ queryParams.append(key, String(value));
+ }
+ });
+
+ const queryString = queryParams.toString();
+ return queryString ? `${url}?${queryString}` : url;
+ };
+
+ /**
+ * Configure fetch options including cache settings
+ */
+ const getFetchOptions = (): RequestInit => {
+ const { method = 'GET', headers = {}, body } = options;
+
+ const fetchOptions: RequestInit = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...headers,
+ },
+ };
+
+ // Add body for non-GET requests if provided
+ if (method !== 'GET' && body) {
+ fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
+ }
+
+ // Configure cache options
+ if (!cacheOptions.cache) {
+ fetchOptions.cache = 'no-store';
+ } else if (cacheOptions.noStore) {
+ fetchOptions.cache = 'no-store';
+ } else if (cacheOptions.revalidate !== undefined) {
+ fetchOptions.next = { revalidate: cacheOptions.revalidate };
+ }
+
+ return fetchOptions;
+ };
+
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ const fullUrl = buildUrl();
+ const fetchOptions = getFetchOptions();
+
+ const response = await fetch(fullUrl, fetchOptions);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const responseData = await response.json();
+ setData(responseData);
+ setError(null);
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error('An unknown error occurred'));
+ setData(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, [url, JSON.stringify(params), JSON.stringify(options), JSON.stringify(cacheOptions)]);
+
+ /**
+ * Update the query parameters and trigger a refetch
+ * @param newParams New parameters to merge with existing ones
+ */
+ const updateParams = (newParams: Record) => {
+ // Filter out null or empty string values
+ const filteredParams = Object.entries(newParams).reduce((acc, [key, value]) => {
+ if (value !== null && value !== '') {
+ acc[key] = value;
+ }
+ return acc;
+ }, {} as Record);
+
+ setParams(prev => ({
+ ...prev,
+ ...filteredParams
+ }));
+ };
+
+ /**
+ * Reset all parameters to initial values
+ */
+ const resetParams = () => {
+ setParams(initialParams);
+ };
+
+ /**
+ * Manually trigger a refetch of the data
+ */
+ const refetch = () => {
+ fetchData();
+ };
+
+ return {
+ data,
+ loading,
+ error,
+ updateParams,
+ resetParams,
+ refetch
+ };
+}
+
+// // Basic usage (with default caching)
+// const { data, loading, error, refetch } = useStandardApiFetch('/api/your-endpoint');
+
+// // With no caching (for data that changes frequently)
+// const { data, loading, error, refetch } = useStandardApiFetch(
+// '/api/your-endpoint',
+// {},
+// {},
+// { cache: false }
+// );
+
+// // With specific revalidation time
+// const { data, loading, error, refetch } = useStandardApiFetch(
+// '/api/your-endpoint',
+// {},
+// {},
+// { revalidate: 60 } // Revalidate every 60 seconds
+// );
diff --git a/WebServices/client-frontend/src/components/common/index.ts b/WebServices/client-frontend/src/components/common/index.ts
new file mode 100644
index 0000000..2a47d2e
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/index.ts
@@ -0,0 +1,26 @@
+// Export all components from the common directory
+export { CardDisplay } from "./CardDisplay";
+export { SortingComponent, type SortingComponentProps, type SortField } from "./SortingComponent";
+
+// Export QueryModifiers
+export {
+ TextQueryModifier,
+ SelectQueryModifier,
+ TypeQueryModifier,
+ type QueryModifierProps,
+ type TextQueryModifierProps,
+ type SelectQueryModifierProps,
+ type TypeQueryModifierProps,
+ type QueryModifierValue,
+ type QueryModifierResult
+} from "./QueryModifiers";
+
+// Export hooks
+export {
+ useDataFetching,
+ type RequestParams,
+ type ResponseMetadata,
+ type PagePagination,
+ type ApiResponse,
+} from "./hooks/useDataFetching";
+export { useApiData } from "./hooks/useApiData";
diff --git a/WebServices/client-frontend/src/components/common/schemas.ts b/WebServices/client-frontend/src/components/common/schemas.ts
new file mode 100644
index 0000000..17d62d1
--- /dev/null
+++ b/WebServices/client-frontend/src/components/common/schemas.ts
@@ -0,0 +1,45 @@
+// Carried schemas from any request and response
+
+// Common request parameters interface
+export interface RequestParams {
+ page: number;
+ size: number;
+ orderField: string[];
+ orderType: string[];
+ query: Record;
+}
+
+// Common response metadata interface
+export interface ResponseMetadata {
+ totalCount: number;
+ totalItems: number;
+ totalPages: number;
+ pageCount: number;
+ allCount?: number;
+ next: boolean;
+ back: boolean;
+}
+
+// Generic API response interface
+export interface ApiResponse {
+ data: T[];
+ metadata: ResponseMetadata;
+}
+
+// Pagination state interface
+export interface PagePagination {
+ page: number;
+ size: number;
+ orderField: string[];
+ orderType: string[];
+ query: Record;
+}
+
+export type Language = "en" | "tr";
+
+export interface LanguageSelectionComponentProps {
+ lang: Language;
+ setLang: (lang: Language) => void;
+ translations?: Record;
+ className?: string;
+}
diff --git a/WebServices/client-frontend/src/components/header/Header.tsx b/WebServices/client-frontend/src/components/header/Header.tsx
index cd870fd..80f5d73 100644
--- a/WebServices/client-frontend/src/components/header/Header.tsx
+++ b/WebServices/client-frontend/src/components/header/Header.tsx
@@ -112,7 +112,6 @@ const Header: React.FC = ({ lang, setLang }) => {
const router = useRouter();
- // Close dropdowns when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
@@ -142,7 +141,6 @@ const Header: React.FC = ({ lang, setLang }) => {
}, []);
const handleLogout = () => {
- // Implement logout functionality
console.log("Logging out...");
logoutActiveSession()
.then(() => {
diff --git a/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx b/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx
index 541414f..1a7bc76 100644
--- a/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx
+++ b/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx
@@ -23,6 +23,7 @@ export const DashboardLayout: React.FC = ({
children,
lang,
activePage,
+ siteUrls,
}) => {
const [language, setLanguage] = useState(lang as Language);
@@ -30,7 +31,7 @@ export const DashboardLayout: React.FC = ({
{/* Sidebar */}
{/* Main Content Area */}
diff --git a/WebServices/client-frontend/src/components/layouts/schema.ts b/WebServices/client-frontend/src/components/layouts/schema.ts
index 7e5018e..eb8150c 100644
--- a/WebServices/client-frontend/src/components/layouts/schema.ts
+++ b/WebServices/client-frontend/src/components/layouts/schema.ts
@@ -4,4 +4,5 @@ export interface DashboardLayoutProps {
children: ReactNode;
lang: "en" | "tr";
activePage: string;
+ siteUrls: string[];
}
diff --git a/WebServices/client-frontend/src/components/menu/store.tsx b/WebServices/client-frontend/src/components/menu/IndexStore/store.tsx
similarity index 96%
rename from WebServices/client-frontend/src/components/menu/store.tsx
rename to WebServices/client-frontend/src/components/menu/IndexStore/store.tsx
index c04419f..3c18d2e 100644
--- a/WebServices/client-frontend/src/components/menu/store.tsx
+++ b/WebServices/client-frontend/src/components/menu/IndexStore/store.tsx
@@ -22,7 +22,7 @@ const Build = {
tr: "Apartman",
en: "Build",
},
- siteUrl: "/build",
+ siteUrl: "/buildings",
};
const Dashboard = {
@@ -40,7 +40,7 @@ const BuildParts = {
tr: "Daireler",
en: "Build Parts",
},
- siteUrl: "/build/parts",
+ siteUrl: "/buildings/parts",
};
const BuildArea = {
@@ -49,14 +49,14 @@ const BuildArea = {
tr: "Daire Alanları",
en: "Build Area",
},
- siteUrl: "/build/area",
+ siteUrl: "/buildings/area",
};
const ManagementAccounting = {
name: "ManagementAccounting",
lg: {
tr: "Yönetim Cari Hareketler",
- en: "ManagementAccounting",
+ en: "Management Accounting",
},
siteUrl: "/management/accounting",
};
@@ -76,7 +76,7 @@ const BuildPartsAccounting = {
tr: "Daire Cari Hareketler",
en: "Build Parts Accounting",
},
- siteUrl: "/build/parts/accounting",
+ siteUrl: "/buildings/parts/accounting",
};
const AnnualMeeting = {
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu.tsx b/WebServices/client-frontend/src/components/menu/NavigationMenu.tsx
deleted file mode 100644
index 7a37da2..0000000
--- a/WebServices/client-frontend/src/components/menu/NavigationMenu.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import Link from "next/link";
-import { Home, ChevronDown, ChevronRight } from "lucide-react";
-import type { LanguageTranslation } from "./handler";
-
-interface NavigationMenuProps {
- transformedMenu: any[];
- lang: string;
- activePage?: string;
-}
-
-const NavigationMenu: React.FC
= ({ transformedMenu, lang, activePage }) => {
- // State to track which menu items are expanded
- const [firstLayerIndex, setFirstLayerIndex] = useState(-1);
- const [secondLayerIndex, setSecondLayerIndex] = useState(-1);
-
- // Handle first level menu click
- const handleFirstLevelClick = (index: number) => {
- setFirstLayerIndex(index === firstLayerIndex ? -1 : index);
- setSecondLayerIndex(-1); // Reset second layer selection when first layer changes
- };
-
- // Handle second level menu click
- const handleSecondLevelClick = (index: number) => {
- setSecondLayerIndex(index === secondLayerIndex ? -1 : index);
- };
-
- return (
-
- );
-};
-
-export default NavigationMenu;
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuItem.tsx b/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuItem.tsx
new file mode 100644
index 0000000..fadba39
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuItem.tsx
@@ -0,0 +1,62 @@
+"use client";
+import React from "react";
+import { ChevronDown, ChevronRight } from "lucide-react";
+import { MenuItemProps } from "./types";
+import SubMenuItem from "./SubMenuItem";
+
+const MenuItem: React.FC = ({
+ item,
+ firstIndex,
+ firstLayerIndex,
+ secondLayerIndex,
+ activeMenuPath,
+ lang,
+ activePage,
+ handleFirstLevelClick,
+ handleSecondLevelClick,
+}) => {
+ const isFirstActive = activeMenuPath?.first === firstIndex;
+
+ return (
+
+
+
+ {/* First level separator and second layer */}
+ {(firstIndex === firstLayerIndex || isFirstActive) && (
+
+ {item.subList.map((subItem, secondIndex) => (
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default MenuItem;
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuLink.tsx b/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuLink.tsx
new file mode 100644
index 0000000..bb15318
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/NavigationMenu/MenuLink.tsx
@@ -0,0 +1,25 @@
+"use client";
+import React from "react";
+import Link from "next/link";
+import { Home } from "lucide-react";
+import { MenuLinkProps } from "./types";
+
+const MenuLink: React.FC = ({ subSubItem, activePage, lang }) => {
+ const isActive = activePage === subSubItem.siteUrl;
+
+ return (
+
+
+ {subSubItem.lg[lang as keyof typeof subSubItem.lg]}
+
+ );
+};
+
+export default MenuLink;
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu/SubMenuItem.tsx b/WebServices/client-frontend/src/components/menu/NavigationMenu/SubMenuItem.tsx
new file mode 100644
index 0000000..5ed98d4
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/NavigationMenu/SubMenuItem.tsx
@@ -0,0 +1,56 @@
+"use client";
+import React from "react";
+import { ChevronDown, ChevronRight } from "lucide-react";
+import { SubMenuItemProps } from "./types";
+import MenuLink from "./MenuLink";
+
+const SubMenuItem: React.FC = ({
+ subItem,
+ secondIndex,
+ firstIndex,
+ firstLayerIndex,
+ secondLayerIndex,
+ isFirstActive,
+ activeMenuPath,
+ lang,
+ activePage,
+ handleSecondLevelClick,
+}) => {
+ const isSecondActive = isFirstActive && activeMenuPath?.second === secondIndex;
+
+ return (
+
+
+
+ {/* Second level separator and third layer */}
+ {(firstIndex === firstLayerIndex && secondIndex === secondLayerIndex) || isSecondActive ? (
+
+ {subItem.subList.map((subSubItem) => (
+
+ ))}
+
+ ) : null}
+
+ );
+};
+
+export default SubMenuItem;
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu/index.tsx b/WebServices/client-frontend/src/components/menu/NavigationMenu/index.tsx
new file mode 100644
index 0000000..fc8a19b
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/NavigationMenu/index.tsx
@@ -0,0 +1,68 @@
+"use client";
+import React, { useState, useEffect } from "react";
+import { NavigationMenuProps, ActiveMenuPath } from "./types";
+import MenuItem from "./MenuItem";
+
+const NavigationMenu: React.FC = ({ transformedMenu, lang, activePage }) => {
+ // State to track which menu items are expanded
+ const [firstLayerIndex, setFirstLayerIndex] = useState(-1);
+ const [secondLayerIndex, setSecondLayerIndex] = useState(-1);
+ const [activeMenuPath, setActiveMenuPath] = useState(null);
+
+ // Find the active menu path when component mounts or activePage changes
+ useEffect(() => {
+ if (activePage && activePage !== "/dashboard") {
+ // Find which menu item contains the active page
+ transformedMenu.forEach((firstItem, firstIdx) => {
+ firstItem.subList.forEach((secondItem, secondIdx) => {
+ secondItem.subList.forEach((thirdItem) => {
+ if (thirdItem.siteUrl === activePage) {
+ setFirstLayerIndex(firstIdx);
+ setSecondLayerIndex(secondIdx);
+ setActiveMenuPath({ first: firstIdx, second: secondIdx, third: thirdItem.siteUrl });
+ }
+ });
+ });
+ });
+ }
+ }, [activePage, transformedMenu]);
+
+ // Handle first level menu click
+ const handleFirstLevelClick = (index: number) => {
+ // Only allow collapsing if we're not on an active page or if it's dashboard
+ if (activePage === "/dashboard" || !activeMenuPath) {
+ setFirstLayerIndex(index === firstLayerIndex ? -1 : index);
+ setSecondLayerIndex(-1); // Reset second layer selection when first layer changes
+ }
+ };
+
+ // Handle second level menu click
+ const handleSecondLevelClick = (index: number) => {
+ // Only allow collapsing if we're not on an active page or if it's dashboard
+ if (activePage === "/dashboard" || !activeMenuPath) {
+ setSecondLayerIndex(index === secondLayerIndex ? -1 : index);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default NavigationMenu;
diff --git a/WebServices/client-frontend/src/components/menu/NavigationMenu/types.ts b/WebServices/client-frontend/src/components/menu/NavigationMenu/types.ts
new file mode 100644
index 0000000..91cea90
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/NavigationMenu/types.ts
@@ -0,0 +1,52 @@
+// Import types from the handler
+import {
+ LanguageTranslation,
+ FilteredMenuFirstLevel,
+ FilteredMenuSecondLevel,
+ FilteredMenuThirdLevel
+} from "../handler";
+
+// Navigation menu props and state types
+export interface NavigationMenuProps {
+ transformedMenu: FilteredMenuFirstLevel[];
+ lang: string;
+ activePage?: string;
+}
+
+export interface ActiveMenuPath {
+ first: number;
+ second: number;
+ third: string;
+}
+
+// Component props interfaces
+export interface MenuItemProps {
+ item: FilteredMenuFirstLevel;
+ firstIndex: number;
+ firstLayerIndex: number;
+ secondLayerIndex: number;
+ activeMenuPath: ActiveMenuPath | null;
+ lang: string;
+ activePage?: string;
+ handleFirstLevelClick: (index: number) => void;
+ handleSecondLevelClick: (index: number) => void;
+}
+
+export interface SubMenuItemProps {
+ subItem: FilteredMenuSecondLevel;
+ secondIndex: number;
+ firstIndex: number;
+ firstLayerIndex: number;
+ secondLayerIndex: number;
+ isFirstActive: boolean;
+ activeMenuPath: ActiveMenuPath | null;
+ lang: string;
+ activePage?: string;
+ handleSecondLevelClick: (index: number) => void;
+}
+
+export interface MenuLinkProps {
+ subSubItem: FilteredMenuThirdLevel;
+ activePage?: string;
+ lang: string;
+}
diff --git a/WebServices/client-frontend/src/components/menu/EmployeeProfileSection.tsx b/WebServices/client-frontend/src/components/menu/ProfileSections/EmployeeProfileSection.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/menu/EmployeeProfileSection.tsx
rename to WebServices/client-frontend/src/components/menu/ProfileSections/EmployeeProfileSection.tsx
diff --git a/WebServices/client-frontend/src/components/menu/OccupantProfileSection.tsx b/WebServices/client-frontend/src/components/menu/ProfileSections/OccupantProfileSection.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/menu/OccupantProfileSection.tsx
rename to WebServices/client-frontend/src/components/menu/ProfileSections/OccupantProfileSection.tsx
diff --git a/WebServices/client-frontend/src/components/menu/ProfileLoadingState.tsx b/WebServices/client-frontend/src/components/menu/ProfileSections/ProfileLoadingState.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/menu/ProfileLoadingState.tsx
rename to WebServices/client-frontend/src/components/menu/ProfileSections/ProfileLoadingState.tsx
diff --git a/WebServices/client-frontend/src/components/menu/handler.tsx b/WebServices/client-frontend/src/components/menu/handler.tsx
index d39db23..dd41b81 100644
--- a/WebServices/client-frontend/src/components/menu/handler.tsx
+++ b/WebServices/client-frontend/src/components/menu/handler.tsx
@@ -1,6 +1,6 @@
"use client";
-import Menu from "./store";
+import Menu from "./IndexStore/store";
// Define TypeScript interfaces for menu structure
export interface LanguageTranslation {
diff --git a/WebServices/client-frontend/src/components/menu/hook.ts b/WebServices/client-frontend/src/components/menu/hook.ts
new file mode 100644
index 0000000..81b18be
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/hook.ts
@@ -0,0 +1,18 @@
+import { UserSelection } from "../validations/menu/menu";
+
+export async function getUserSelectionHook(
+ setError: any
+): Promise {
+ try {
+ const response = await fetch("/api/cookies/selection", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ });
+ const data = await response.json();
+ // setJsonText(JSON.stringify(data));
+ return data.data;
+ } catch (error) {
+ setError("An error occurred during login");
+ }
+ return null;
+}
diff --git a/WebServices/client-frontend/src/components/menu/language.ts b/WebServices/client-frontend/src/components/menu/language.ts
new file mode 100644
index 0000000..1d97efc
--- /dev/null
+++ b/WebServices/client-frontend/src/components/menu/language.ts
@@ -0,0 +1,12 @@
+const dashboardLanguage = {
+ tr: {
+ dashboard: "Kontrol Paneli",
+ loading: "Yükleniyor...",
+ },
+ en: {
+ dashboard: "Control Panel",
+ loading: "Loading...",
+ },
+};
+
+export default dashboardLanguage;
diff --git a/WebServices/client-frontend/src/components/menu/menu.tsx b/WebServices/client-frontend/src/components/menu/menu.tsx
index 1c49c6b..40690d0 100644
--- a/WebServices/client-frontend/src/components/menu/menu.tsx
+++ b/WebServices/client-frontend/src/components/menu/menu.tsx
@@ -1,92 +1,59 @@
"use client";
-
import React, { useEffect, useState, Suspense } from "react";
-import { transformMenu } from "./handler";
-import { retrieveUserSelection } from "@/apicalls/cookies/token";
-import EmployeeProfileSection from "./EmployeeProfileSection";
-import OccupantProfileSection from "./OccupantProfileSection";
-import ProfileLoadingState from "./ProfileLoadingState";
+
+import EmployeeProfileSection from "./ProfileSections/EmployeeProfileSection";
+import OccupantProfileSection from "./ProfileSections/OccupantProfileSection";
+import dashboardLanguage from "./language";
+import ProfileLoadingState from "./ProfileSections/ProfileLoadingState";
import NavigationMenu from "./NavigationMenu";
-import {
- ClientMenuProps,
- UserSelection,
-} from "@/components/validations/menu/menu";
-// Language definitions for dashboard title
-const dashboardLanguage = {
- tr: {
- dashboard: "Kontrol Paneli",
- loading: "Yükleniyor...",
- },
- en: {
- dashboard: "Control Panel",
- loading: "Loading...",
- },
-};
+import { transformMenu } from "./handler";
+import { ClientMenuProps, UserSelection } from "@/components/validations/menu/menu";
+import { getUserSelectionHook } from "./hook";
-const ClientMenu: React.FC = ({ siteUrls, lang = "en", activePage }) => {
+const ClientMenu: React.FC = ({ siteUrls, lang = "en" as keyof typeof dashboardLanguage, activePage }) => {
+ const lng = lang as keyof typeof dashboardLanguage;
const transformedMenu = transformMenu(siteUrls);
- const t =
- dashboardLanguage[lang as keyof typeof dashboardLanguage] ||
- dashboardLanguage.en;
-
- // State for loading indicator, user type, and user selection data
+ const [translation, setTranslation] = useState>(dashboardLanguage[lng] || dashboardLanguage.en);
const [loading, setLoading] = useState(true);
const [userType, setUserType] = useState(null);
- const [userSelectionData, setUserSelectionData] =
- useState(null);
+ const [userSelectionData, setUserSelectionData] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ setTranslation(dashboardLanguage[lng] || dashboardLanguage.en);
+ }, [lng]);
- // Fetch user selection data
useEffect(() => {
setLoading(true);
-
- retrieveUserSelection()
- .then((data) => {
- console.log("User Selection:", data);
-
- if (data && "userType" in data) {
- setUserType((data as UserSelection).userType);
- setUserSelectionData(data as UserSelection);
- }
- })
- .catch((err) => {
- console.error("Error fetching user selection data:", err);
- })
- .finally(() => setLoading(false));
+ getUserSelectionHook(setError).then((data) => {
+ if (data) { setUserType(data.userType); setUserSelectionData({ userType: data.userType, selected: data.selected }) }
+ setLoading(false);
+ }).catch((err) => { setError(err?.message); setLoading(false) }).finally(() => { setLoading(false) });
}, []);
+
return (
-
- {t.dashboard}
-
+ {translation.dashboard}
+ {/* Error Section */}
+ {error &&
{error}
}
+
{/* Profile Section with Suspense */}
- {t.loading}
}
- >
- {loading ? (
-
- ) : userType === "employee" && userSelectionData ? (
-
+
{translation.loading} }>
+ {loading ? () : userType === "employee" && userSelectionData ? (
+
) : userType === "occupant" && userSelectionData ? (
-
- ) : (
- {t.loading}
- )}
+
+ ) : ({translation.loading}
)}
{/* Navigation Menu */}
-
+
);
};
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/a.txt b/WebServices/client-frontend/src/components/menu/types.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/a.txt
rename to WebServices/client-frontend/src/components/menu/types.ts
diff --git a/WebServices/client-frontend/src/components/navigator/retriever.tsx b/WebServices/client-frontend/src/components/navigator/retriever.tsx
index 6903e9a..2ee8620 100644
--- a/WebServices/client-frontend/src/components/navigator/retriever.tsx
+++ b/WebServices/client-frontend/src/components/navigator/retriever.tsx
@@ -1,6 +1,6 @@
import { PageComponent } from "@/components/validations/translations/translation";
import { UnAuthorizedPage } from "@/components/navigator/unauthorizedpage";
-import { PageNavigator } from "../Pages";
+import { PageNavigator } from "../../eventRouters/Pages";
export function retrievePageByUrlAndPageId(
pageId: string,
diff --git a/WebServices/client-frontend/src/eventRouters/Pages/dashboard/a.txt b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/a.txt
new file mode 100644
index 0000000..e69de29
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/index.ts b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/index.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/index.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/index.ts
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/ActionButtonsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/ActionButtonsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/DataDisplayComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/DataDisplayComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/FormComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/FormComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/ListInfoComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/ListInfoComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/PaginationToolsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/PaginationToolsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/SearchComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/SearchComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/SortingComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/SortingComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/app.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/app.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/hooks.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/hooks.ts
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/language.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/language.ts
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts b/WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/schema.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/dashboard/superusers/schema.ts
diff --git a/WebServices/client-frontend/src/components/Pages/index.ts b/WebServices/client-frontend/src/eventRouters/Pages/index.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/index.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/index.ts
diff --git a/WebServices/client-frontend/src/components/Pages/people/index.ts b/WebServices/client-frontend/src/eventRouters/Pages/people/index.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/index.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/people/index.ts
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/ActionButtonsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/ActionButtonsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/ActionButtonsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/ActionButtonsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/DataDisplayComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/DataDisplayComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/DataDisplayComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/DataDisplayComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/FormComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/FormComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/FormComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/FormComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/ListInfoComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/ListInfoComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/ListInfoComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/ListInfoComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/PaginationToolsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/PaginationToolsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/PaginationToolsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/PaginationToolsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/SearchComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/SearchComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/SearchComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/SearchComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/SortingComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/SortingComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/SortingComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/SortingComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/app.tsx b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/app.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/app.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/app.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/hooks.ts b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/hooks.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/hooks.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/hooks.ts
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/language.ts b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/language.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/language.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/language.ts
diff --git a/WebServices/client-frontend/src/components/Pages/people/superusers/schema.ts b/WebServices/client-frontend/src/eventRouters/Pages/people/superusers/schema.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/superusers/schema.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/people/superusers/schema.ts
diff --git a/WebServices/client-frontend/src/components/Pages/template/ActionButtonsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/ActionButtonsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/ActionButtonsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/ActionButtonsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/DataDisplayComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/DataDisplayComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/DataDisplayComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/DataDisplayComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/FormComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/FormComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/FormComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/FormComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/ListInfoComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/ListInfoComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/ListInfoComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/ListInfoComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/PaginationToolsComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/PaginationToolsComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/PaginationToolsComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/PaginationToolsComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/REACT_TEMPLATE_BLUEPRINT.md b/WebServices/client-frontend/src/eventRouters/Pages/template/REACT_TEMPLATE_BLUEPRINT.md
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/REACT_TEMPLATE_BLUEPRINT.md
rename to WebServices/client-frontend/src/eventRouters/Pages/template/REACT_TEMPLATE_BLUEPRINT.md
diff --git a/WebServices/client-frontend/src/components/Pages/template/README.md b/WebServices/client-frontend/src/eventRouters/Pages/template/README.md
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/README.md
rename to WebServices/client-frontend/src/eventRouters/Pages/template/README.md
diff --git a/WebServices/client-frontend/src/components/Pages/template/SearchComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/SearchComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/SearchComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/SearchComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/SortingComponent.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/SortingComponent.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/SortingComponent.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/SortingComponent.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/app.tsx b/WebServices/client-frontend/src/eventRouters/Pages/template/app.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/app.tsx
rename to WebServices/client-frontend/src/eventRouters/Pages/template/app.tsx
diff --git a/WebServices/client-frontend/src/components/Pages/template/hooks.ts b/WebServices/client-frontend/src/eventRouters/Pages/template/hooks.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/hooks.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/template/hooks.ts
diff --git a/WebServices/client-frontend/src/components/Pages/template/language.ts b/WebServices/client-frontend/src/eventRouters/Pages/template/language.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/language.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/template/language.ts
diff --git a/WebServices/client-frontend/src/components/Pages/template/schema.ts b/WebServices/client-frontend/src/eventRouters/Pages/template/schema.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/template/schema.ts
rename to WebServices/client-frontend/src/eventRouters/Pages/template/schema.ts
diff --git a/WebServices/client-frontend/src/eventRouters/index.tsx b/WebServices/client-frontend/src/eventRouters/index.tsx
index 9ce34d1..fb8498e 100644
--- a/WebServices/client-frontend/src/eventRouters/index.tsx
+++ b/WebServices/client-frontend/src/eventRouters/index.tsx
@@ -1,6 +1,10 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import { peopleApplications } from "./Pages/people";
+import { dashboardApplications } from "./Pages/dashboard";
-
-const menuPages = {
+export const menuPages: Record