diff --git a/ApiServices/DealerService/init_applications.py b/ApiServices/DealerService/init_applications.py
index 856b3d2..767de93 100644
--- a/ApiServices/DealerService/init_applications.py
+++ b/ApiServices/DealerService/init_applications.py
@@ -181,7 +181,7 @@ def init_applications_for_tenant(super_user: BuildLivingSpace, db_session=None)
),
dict(
name="TenantSendMessageToBuildManager",
- application_code="app000014",
+ application_code="app000022",
site_url="/tenant/messageToBM",
application_type="Dash",
description="Individual Page for tenant send message to build manager",
@@ -200,6 +200,7 @@ def init_applications_for_tenant(super_user: BuildLivingSpace, db_session=None)
application_type="Dash",
description="Individual Page for tenant account view",
),
+
]
for list_of_created_app in list_of_created_apps:
diff --git a/ApiServices/InitialService/alembic.ini b/ApiServices/InitialService/alembic.ini
index df27668..38dbce0 100644
--- a/ApiServices/InitialService/alembic.ini
+++ b/ApiServices/InitialService/alembic.ini
@@ -63,7 +63,7 @@ version_path_separator = os
# are written from script.py.mako
# output_encoding = utf-8
-sqlalchemy.url = postgresql+psycopg2://berkay_wag_user:berkay_wag_user_password@postgres-service:5432/wag_database
+sqlalchemy.url = postgresql+psycopg2://postgres:password@10.10.2.14:5432/postgres
[post_write_hooks]
diff --git a/ApiServices/InitialService/alembic/env.py b/ApiServices/InitialService/alembic/env.py
index f28b8a8..c30e41a 100644
--- a/ApiServices/InitialService/alembic/env.py
+++ b/ApiServices/InitialService/alembic/env.py
@@ -1,7 +1,9 @@
from logging.config import fileConfig
+import os
from sqlalchemy import engine_from_config
from sqlalchemy import pool
+from sqlalchemy import create_engine
from alembic import context
from Schemas import *
@@ -11,6 +13,19 @@ from Controllers.Postgres.database import Base
# access to the values within the .ini file in use.
config = context.config
+# Override sqlalchemy.url with environment variables if they exist
+db_host = os.getenv("POSTGRES_HOST", "10.10.2.14")
+db_port = os.getenv("POSTGRES_PORT", "5432")
+db_user = os.getenv("POSTGRES_USER", "postgres")
+db_password = os.getenv("POSTGRES_PASSWORD", "password")
+db_name = os.getenv("POSTGRES_DB", "postgres")
+
+# Build the connection URL from environment variables
+db_url = f"postgresql+psycopg2://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
+
+# Override the sqlalchemy.url in the alembic.ini file
+config.set_main_option("sqlalchemy.url", db_url)
+
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
diff --git a/ApiServices/InitialService/app.py b/ApiServices/InitialService/app.py
index 9e2dddf..d4b8cae 100644
--- a/ApiServices/InitialService/app.py
+++ b/ApiServices/InitialService/app.py
@@ -1,3 +1,4 @@
+import os
from Controllers.Postgres.database import get_db
from init_app_defaults import create_application_defaults
@@ -8,7 +9,7 @@ from init_services import create_modules_and_services_and_actions
from init_address import create_one_address
from init_occ_defaults import create_occupant_defaults
-set_alembic = False
+set_alembic = bool(os.getenv("set_alembic", 0))
if __name__ == "__main__":
diff --git a/WebServices/client-frontend/src/apicalls/login/login.tsx b/WebServices/client-frontend/src/apicalls/login/login.tsx
index 4d811fa..42bc0f9 100644
--- a/WebServices/client-frontend/src/apicalls/login/login.tsx
+++ b/WebServices/client-frontend/src/apicalls/login/login.tsx
@@ -28,7 +28,7 @@ interface LoginSelectOccupant {
async function logoutActiveSession() {
const cookieStore = await cookies();
- const response = await fetchDataWithToken(logoutEndpoint, {}, "POST", false);
+ const response = await fetchDataWithToken(logoutEndpoint, {}, "GET", false);
cookieStore.delete("accessToken");
cookieStore.delete("accessObject");
cookieStore.delete("userProfile");
diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx
index 56317af..ec00dc8 100644
--- a/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx
+++ b/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx
@@ -1,9 +1,10 @@
"use server";
import React from "react";
-import ClientMenu from "@/components/menu/menu";
import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token";
-import { retrievePage } from "@/components/NavigatePages";
+
+import ClientMenu from "@/components/menu/menu";
import Header from "@/components/header/Header";
+import retrievePageByUrlAndPageId from "@/components/navigator/retriever";
export default async function DashboardLayout({
searchParams,
@@ -13,7 +14,7 @@ export default async function DashboardLayout({
const activePage = "/dashboard";
const siteUrlsList = (await retrievePageList()) || [];
const pageToDirect = await retrievePagebyUrl(activePage);
- const PageComponent = retrievePage(pageToDirect);
+ const PageComponent = retrievePageByUrlAndPageId(pageToDirect, activePage);
const searchParamsInstance = await searchParams;
const lang = (searchParamsInstance?.lang as "en" | "tr") || "en";
diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx
index 3e60221..0f4ed5b 100644
--- a/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx
+++ b/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx
@@ -2,7 +2,7 @@
import React from "react";
import ClientMenu from "@/components/menu/menu";
import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token";
-import { retrievePage } from "@/components/NavigatePages";
+import { retrievePageByUrlAndPageId } from "@/components/navigator/retriever";
import { searchPlaceholder } from "@/app/commons/pageDefaults";
const pageInfo = {
@@ -18,7 +18,7 @@ export default async function Dashboard({
const activePage = "/individual";
const siteUrlsList = (await retrievePageList()) || [];
const pageToDirect = await retrievePagebyUrl(activePage);
- const PageComponent = retrievePage(pageToDirect);
+ const PageComponent = retrievePageByUrlAndPageId(pageToDirect, activePage);
const searchParamsInstance = await searchParams;
const lang = (searchParamsInstance?.lang as "en" | "tr") || "en";
diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx
index d0b42df..c1ae5aa 100644
--- a/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx
+++ b/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx
@@ -1,8 +1,9 @@
"use server";
import React from "react";
import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token";
-import { retrievePage } from "@/components/NavigatePages";
-import LeftMenu from "@/components/menu/leftMenu";
+import retrievePageByUrlAndPageId from "@/components/navigator/retriever";
+import ClientMenu from "@/components/menu/menu";
+import Header from "@/components/header/Header";
export default async function Dashboard({
searchParams,
@@ -14,35 +15,20 @@ export default async function Dashboard({
const searchParamsInstance = await searchParams;
const activePage = "/management/accounting";
const pageToDirect = await retrievePagebyUrl(activePage);
- const PageComponent = retrievePage(pageToDirect);
+ const PageComponent = retrievePageByUrlAndPageId(activePage, pageToDirect);
return (
<>
{/* Sidebar */}
{/* Main Content Area */}
{/* Sticky Header */}
-
+
diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx
index d395af9..c9b5db1 100644
--- a/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx
+++ b/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx
@@ -1,8 +1,9 @@
"use server";
import React from "react";
import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token";
-import { retrievePage } from "@/components/NavigatePages";
-import LeftMenu from "@/components/menu/leftMenu";
+import retrievePageByUrlAndPageId from "@/components/navigator/retriever";
+import ClientMenu from "@/components/menu/menu";
+import Header from "@/components/header/Header";
export default async function Dashboard({
searchParams,
@@ -14,35 +15,20 @@ export default async function Dashboard({
const searchParamsInstance = await searchParams;
const activePage = "/management/budget";
const pageToDirect = await retrievePagebyUrl(activePage);
- const PageComponent = retrievePage(pageToDirect);
+ const PageComponent = retrievePageByUrlAndPageId(activePage, pageToDirect);
return (
<>
{/* Sidebar */}
{/* Main Content Area */}
{/* Sticky Header */}
-
+
diff --git a/WebServices/client-frontend/src/components/NavigatePages/index.tsx b/WebServices/client-frontend/src/components/NavigatePages/index.tsx
deleted file mode 100644
index 8ed8dee..0000000
--- a/WebServices/client-frontend/src/components/NavigatePages/index.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from "react";
-import { PageProps } from "./interFaces";
-
-import PeopleSuperUserApp from "../Pages/people/superusers/app";
-
-// Ensure all components in PageIndex accept PageProps
-type PageComponent = React.ComponentType;
-
-export const PageIndex: Record = {
- app000003: PeopleSuperUserApp as unknown as PageComponent,
-};
-
-// Language dictionary for internationalization
-const languageDictionary = {
- en: {
- title: "Unauthorized Access",
- message1: "You do not have permission to access this page.",
- message2: "Please contact the administrator.",
- footer: `© ${new Date().getFullYear()} My Application`
- },
- tr: {
- title: "Yetkisiz Erişim",
- message1: "Bu sayfaya erişim izniniz yok.",
- message2: "Lütfen yönetici ile iletişime geçin.",
- footer: `© ${new Date().getFullYear()} Uygulamam`
- }
-};
-
-const UnAuthorizedPage: React.FC = ({ lang = "en", queryParams }) => {
- // Use the language dictionary based on the lang prop, defaulting to English
- const t = languageDictionary[lang as keyof typeof languageDictionary] || languageDictionary.en;
- return (
- <>
-
-
-
-
- {t.message1}
-
- {t.message2}
-
-
-
- >
- );
-};
-
-export function retrievePage(pageId: string): React.ComponentType {
- try {
- const PageComponent = PageIndex[pageId as keyof typeof PageIndex];
- if (!PageComponent) {
- console.log(`Page component not found for pageId: ${pageId}`);
- return UnAuthorizedPage;
- }
- return PageComponent;
- } catch (error) {
- console.error(`Error retrieving page component for pageId: ${pageId}`, error);
- return UnAuthorizedPage;
- }
-}
-
-export default retrievePage;
diff --git a/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx b/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx
deleted file mode 100644
index df3635d..0000000
--- a/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { LanguageTranslation } from "@/components/menu/runner";
-
-export interface PageProps {
- lang: keyof LanguageTranslation;
- queryParams: { [key: string]: string | undefined };
-}
diff --git a/WebServices/client-frontend/src/components/Pages/people/a.txt b/WebServices/client-frontend/src/components/Pages/dashboard/a.txt
similarity index 100%
rename from WebServices/client-frontend/src/components/Pages/people/a.txt
rename to WebServices/client-frontend/src/components/Pages/dashboard/a.txt
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/index.ts b/WebServices/client-frontend/src/components/Pages/dashboard/index.ts
new file mode 100644
index 0000000..f777f34
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/index.ts
@@ -0,0 +1,6 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import DashboardSuperUserApp from "./superusers/app";
+
+export const dashboardApplications: Record = {
+ app000001: DashboardSuperUserApp,
+};
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx
new file mode 100644
index 0000000..633ca19
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { getTranslation, LanguageKey } from "./language";
+
+interface ActionButtonsComponentProps {
+ onCreateClick: () => void;
+ lang?: LanguageKey;
+}
+
+export function ActionButtonsComponent({
+ onCreateClick,
+ lang = "en",
+}: ActionButtonsComponentProps) {
+ const t = getTranslation(lang);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx
new file mode 100644
index 0000000..4255c0e
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import { PeopleFormData } from "./schema";
+import { getTranslation, LanguageKey } from "./language";
+import { DataCard } from "./ListInfoComponent";
+
+interface DataDisplayComponentProps {
+ data: PeopleFormData[];
+ loading: boolean;
+ error: Error | null;
+ onViewClick: (item: PeopleFormData) => void;
+ onUpdateClick: (item: PeopleFormData) => void;
+ lang?: LanguageKey;
+}
+
+export function DataDisplayComponent({
+ data,
+ loading,
+ error,
+ onViewClick,
+ onUpdateClick,
+ lang = "en",
+}: DataDisplayComponentProps) {
+ const t = getTranslation(lang);
+
+ if (error) {
+ return (
+
+ {t.error} {error.message}
+
+ );
+ }
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {data.map((item) => (
+
+ ))}
+
+ {data.length === 0 && (
+
{t.noItemsFound}
+ )}
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx
new file mode 100644
index 0000000..d267772
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx
@@ -0,0 +1,513 @@
+"use client";
+import React, { useEffect, useState } from "react";
+import {
+ PeopleSchema,
+ PeopleFormData,
+ CreatePeopleSchema,
+ UpdatePeopleSchema,
+ ViewPeopleSchema,
+ fieldDefinitions,
+ fieldsByMode,
+} from "./schema";
+import { getTranslation, LanguageKey } from "./language";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import { cn } from "@/lib/utils";
+
+interface FormComponentProps {
+ lang: LanguageKey;
+ mode: "create" | "update" | "view";
+ onCancel: () => void;
+ refetch: () => void;
+ setMode: React.Dispatch<
+ React.SetStateAction<"list" | "create" | "view" | "update">
+ >;
+ setSelectedItem: React.Dispatch>;
+ initialData?: Partial;
+}
+
+export function FormComponent({
+ lang,
+ mode,
+ onCancel,
+ refetch,
+ setMode,
+ setSelectedItem,
+ initialData,
+}: FormComponentProps) {
+ // Derive readOnly from mode
+ const readOnly = mode === "view";
+ const t = getTranslation(lang);
+ const [formSubmitting, setFormSubmitting] = useState(false);
+
+ // Select the appropriate schema based on the mode
+ const getSchemaForMode = () => {
+ switch (mode) {
+ case "create":
+ return CreatePeopleSchema;
+ case "update":
+ return UpdatePeopleSchema;
+ case "view":
+ return ViewPeopleSchema;
+ default:
+ return PeopleSchema;
+ }
+ };
+
+ // Get field definitions for the current mode
+ const modeFieldDefinitions = fieldDefinitions.getDefinitionsByMode(
+ mode
+ ) as Record;
+
+ // Define FormValues type based on the current mode to fix TypeScript errors
+ type FormValues = Record;
+
+ // Get default values directly from the field definitions
+ const getDefaultValues = (): FormValues => {
+ // For view and update modes, use initialData if available
+ if ((mode === "view" || mode === "update") && initialData) {
+ return initialData as FormValues;
+ }
+
+ // For create mode or when initialData is not available, use default values from schema
+ const defaults: FormValues = {};
+
+ Object.entries(modeFieldDefinitions).forEach(([key, field]) => {
+ if (field && typeof field === "object" && "defaultValue" in field) {
+ defaults[key] = field.defaultValue;
+ }
+ });
+
+ return defaults;
+ };
+
+ // Define form with react-hook-form and zod validation
+ const form = useForm({
+ resolver: zodResolver(getSchemaForMode()) as any, // Type assertion to fix TypeScript errors
+ defaultValues: getDefaultValues(),
+ mode: "onChange",
+ });
+
+ // Update form values when initialData changes
+ useEffect(() => {
+ if (initialData && (mode === "update" || mode === "view")) {
+ // Reset the form with initialData
+ form.reset(initialData as FormValues);
+ }
+ }, [initialData, form, mode]);
+
+ // Define the submission handler function
+ const onSubmitHandler = async (data: FormValues) => {
+ setFormSubmitting(true);
+
+ try {
+ // Call different API methods based on the current mode
+ if (mode === "create") {
+ // Call create API
+ console.log("Creating new record:", data);
+ // Simulate API call
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ // In a real application, you would call your API here
+ // Example: await createPerson(data);
+ } else if (mode === "update") {
+ // Call update API
+ console.log("Updating existing record:", data);
+ // Simulate API call
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ // In a real application, you would call your API here
+ // Example: await updatePerson(data);
+ }
+
+ // Show success message or notification here
+
+ // Return to list view and reset selected item
+ handleReturnToList();
+
+ // Refresh data
+ refetch();
+ } catch (error) {
+ // Handle any errors from the API calls
+ console.error("Error saving data:", error);
+ // You could set an error state here to display to the user
+ } finally {
+ setFormSubmitting(false);
+ }
+ };
+
+ // Helper function to return to list view
+ const handleReturnToList = () => {
+ setMode("list");
+ setSelectedItem(null);
+ };
+
+ // Handle cancel button click
+ const handleCancel = () => {
+ onCancel();
+
+ // Return to list view
+ handleReturnToList();
+ };
+
+ // Filter fields based on the current mode
+ const activeFields = fieldsByMode[readOnly ? "view" : mode];
+
+ // Group fields by their section using mode-specific field definitions
+ const fieldGroups = activeFields.reduce(
+ (groups: Record, fieldName: string) => {
+ const field = modeFieldDefinitions[fieldName];
+ if (field && typeof field === "object" && "group" in field) {
+ const group = field.group as string;
+ if (!groups[group]) {
+ groups[group] = [];
+ }
+ groups[group].push({
+ name: fieldName,
+ type: field.type as string,
+ readOnly: (field.readOnly as boolean) || readOnly, // Combine component readOnly with field readOnly
+ required: (field.required as boolean) || false,
+ label: (field.label as string) || fieldName,
+ });
+ }
+ return groups;
+ },
+ {} as Record<
+ string,
+ {
+ name: string;
+ type: string;
+ readOnly: boolean;
+ required: boolean;
+ label: string;
+ }[]
+ >
+ );
+
+ // Create helper variables for field group checks
+ const hasIdentificationFields = fieldGroups.identificationInfo?.length > 0;
+ const hasPersonalFields = fieldGroups.personalInfo?.length > 0;
+ const hasLocationFields = fieldGroups.locationInfo?.length > 0;
+ const hasExpiryFields = fieldGroups.expiryInfo?.length > 0;
+ const hasStatusFields = fieldGroups.statusInfo?.length > 0;
+
+ return (
+
+ {formSubmitting && (
+
+
+
+
+
{mode === "create" ? t.creating : t.updating}...
+
+
+
+ )}
+
{t.title || "Person Details"}
+
+ {readOnly ? t.view : initialData?.uu_id ? t.update : t.createNew}
+
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx
new file mode 100644
index 0000000..cb16012
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx
@@ -0,0 +1,141 @@
+import React from "react";
+import { PeopleFormData } from "./schema";
+import { getTranslation, LanguageKey } from "./language";
+import { ActionButtonsComponent } from "./ActionButtonsComponent";
+import { SortingComponent } from "./SortingComponent";
+import { PaginationToolsComponent } from "./PaginationToolsComponent";
+import { PagePagination } from "@/components/validations/list/paginations";
+
+interface DataCardProps {
+ item: PeopleFormData;
+ onView: (item: PeopleFormData) => void;
+ onUpdate: (item: PeopleFormData) => void;
+ lang?: LanguageKey;
+}
+
+export function DataCard({
+ item,
+ onView,
+ onUpdate,
+ lang = "en",
+}: DataCardProps) {
+ const t = getTranslation(lang);
+
+ return (
+
+
+
+
{item.person_tag}
+
+ {item.birth_date ? new Date(item.birth_date).toLocaleString() : ""}
+
+
+
+ {item.is_confirmed}
+
+
+ {t.formLabels.createdAt}:{" "}
+ {item.created_at ? new Date(item.created_at).toLocaleString() : ""}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+interface ListInfoComponentProps {
+ data: PeopleFormData[];
+ pagination: PagePagination;
+ loading: boolean;
+ error: Error | null;
+ updatePagination: (updates: Partial) => void;
+ onCreateClick: () => void;
+ onViewClick: (item: PeopleFormData) => void;
+ onUpdateClick: (item: PeopleFormData) => void;
+ lang?: LanguageKey;
+}
+
+export function ListInfoComponent({
+ data,
+ pagination,
+ loading,
+ error,
+ updatePagination,
+ onCreateClick,
+ onViewClick,
+ onUpdateClick,
+ lang = "en",
+}: ListInfoComponentProps) {
+ const t = getTranslation(lang);
+
+ if (error) {
+ return (
+
+ {t.error} {error.message}
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ {loading ? (
+
+ ) : (
+
+ {data.map((item) => (
+
+ ))}
+
+ {data.length === 0 && (
+
+ {t.noItemsFound}
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx
new file mode 100644
index 0000000..2d1a530
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx
@@ -0,0 +1,86 @@
+import React from "react";
+import { getTranslation, LanguageKey } from "./language";
+import { PagePagination } from "@/components/validations/list/paginations";
+
+interface PaginationToolsComponentProps {
+ pagination: PagePagination;
+ updatePagination: (updates: Partial) => void;
+ lang?: LanguageKey;
+}
+
+export function PaginationToolsComponent({
+ pagination,
+ updatePagination,
+ lang = "en",
+}: PaginationToolsComponentProps) {
+ const t = getTranslation(lang);
+
+ const handlePageChange = (newPage: number) => {
+ if (newPage >= 1 && newPage <= pagination.totalPages) {
+ updatePagination({ page: newPage });
+ }
+ };
+
+ const handleSizeChange = (e: React.ChangeEvent) => {
+ updatePagination({ size: Number(e.target.value), page: 1 });
+ };
+
+ return (
+
+
+ {/* Navigation buttons */}
+
+
+
+
+ {t.page} {pagination.page} {t.of} {pagination.totalPages}
+
+
+
+
+
+ {/* Items per page selector */}
+
+
+
+
+
+ {/* Pagination stats */}
+
+
+ {t.showing} {pagination.pageCount} {t.of} {pagination.totalCount}{" "}
+ {t.items}
+
+
+ {t.total}: {pagination.allCount} {t.items}
+
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx
new file mode 100644
index 0000000..a2aba2d
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx
@@ -0,0 +1,143 @@
+import React, { useState, useEffect } from "react";
+import { PeopleSchema } from "./schema";
+import { getTranslation, LanguageKey } from "./language";
+
+interface SearchComponentProps {
+ onSearch: (query: Record) => void;
+ lang?: LanguageKey;
+}
+
+export function SearchComponent({
+ onSearch,
+ lang = "en",
+}: SearchComponentProps) {
+ const t = getTranslation(lang);
+ const [searchValue, setSearchValue] = useState("");
+ const [activeFields, setActiveFields] = useState([]);
+ const [searchQuery, setSearchQuery] = useState>({});
+
+ // Update search query when fields or search value changes
+ useEffect(() => {
+ // Only update if we have active fields and a search value
+ // or if we have no active fields (to clear the search)
+ if ((activeFields.length > 0 && searchValue) || activeFields.length === 0) {
+ const newQuery: Record = {};
+
+ // Only add fields if we have a search value
+ if (searchValue) {
+ activeFields.forEach((field) => {
+ newQuery[field] = searchValue;
+ });
+ }
+
+ // Update local state
+ setSearchQuery(newQuery);
+
+ // Don't call onSearch here - it creates an infinite loop
+ // We'll call it in a separate effect
+ }
+ }, [activeFields, searchValue]);
+
+ // This effect handles calling the onSearch callback
+ // It runs when searchQuery changes, not when onSearch changes
+ useEffect(() => {
+ onSearch(searchQuery);
+ }, [searchQuery]);
+
+ const handleSearchChange = (e: React.ChangeEvent) => {
+ const value = e.target.value;
+ setSearchValue(value);
+
+ if (!value) {
+ setSearchQuery({});
+ onSearch({});
+ return;
+ }
+
+ if (activeFields.length === 0) {
+ // If no fields are selected, don't search
+ return;
+ }
+
+ const newQuery: Record = {};
+ activeFields.forEach((field) => {
+ newQuery[field] = value;
+ });
+
+ setSearchQuery(newQuery);
+ onSearch(newQuery);
+ };
+
+ const toggleField = (field: string) => {
+ setActiveFields((prev) => {
+ if (prev.includes(field)) {
+ return prev.filter((f) => f !== field);
+ } else {
+ return [...prev, field];
+ }
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {t.searchFields || "Search in fields"}:
+
+
+ {Object.keys(PeopleSchema.shape).map((field) => (
+
+ ))}
+
+
+
+ {Object.keys(searchQuery).length > 0 && (
+
+
+ {t.activeSearch || "Active search"}:
+
+
+ {Object.entries(searchQuery).map(([field, value]) => (
+
+ {field}: {value}
+
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx
new file mode 100644
index 0000000..b1ebe0b
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import { PeopleSchema } from "./schema";
+import { getTranslation, LanguageKey } from "./language";
+import { PagePagination } from "@/components/validations/list/paginations";
+
+interface SortingComponentProps {
+ pagination: PagePagination;
+ updatePagination: (updates: Partial) => void;
+ lang?: LanguageKey;
+}
+
+export function SortingComponent({
+ pagination,
+ updatePagination,
+ lang = "en",
+}: SortingComponentProps) {
+ const t = getTranslation(lang);
+
+ const handleSortChange = (field: string) => {
+ // Find if the field is already in the orderFields array
+ const fieldIndex = pagination.orderFields.indexOf(field);
+
+ // Create copies of the arrays to modify
+ const newOrderFields = [...pagination.orderFields];
+ const newOrderTypes = [...pagination.orderTypes];
+
+ if (fieldIndex === -1) {
+ // Field is not being sorted yet - add it with 'asc' direction
+ newOrderFields.push(field);
+ newOrderTypes.push("asc");
+ } else if (pagination.orderTypes[fieldIndex] === "asc") {
+ // Field is being sorted ascending - change to descending
+ newOrderTypes[fieldIndex] = "desc";
+ } else {
+ // Field is being sorted descending - remove it from sorting
+ newOrderFields.splice(fieldIndex, 1);
+ newOrderTypes.splice(fieldIndex, 1);
+ }
+
+ updatePagination({
+ orderFields: newOrderFields,
+ orderTypes: newOrderTypes,
+ page: 1,
+ });
+ };
+
+ return (
+
+
+
+
+ {Object.keys(PeopleSchema.shape).map((field) => {
+ // Find if this field is in the orderFields array
+ const fieldIndex = pagination.orderFields.indexOf(field);
+ const isActive = fieldIndex !== -1;
+ const direction = isActive
+ ? pagination.orderTypes[fieldIndex]
+ : null;
+
+ return (
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx
new file mode 100644
index 0000000..4e69727
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx
@@ -0,0 +1,112 @@
+"use client";
+import React, { useState } from "react";
+import { usePaginatedData } from "./hooks";
+import { FormComponent } from "./FormComponent";
+import { SearchComponent } from "./SearchComponent";
+import { LanguageKey } from "./language";
+import { ActionButtonsComponent } from "./ActionButtonsComponent";
+import { SortingComponent } from "./SortingComponent";
+import { PaginationToolsComponent } from "./PaginationToolsComponent";
+import { DataDisplayComponent } from "./DataDisplayComponent";
+import { PeopleFormData } from "./schema";
+
+interface TemplateProps {
+ lang?: LanguageKey;
+}
+
+// Main template component
+function DashboardSuperUserApp({ lang = "en" }: TemplateProps) {
+ const { data, pagination, loading, error, updatePagination, refetch } =
+ usePaginatedData();
+
+ const [mode, setMode] = useState<"list" | "create" | "view" | "update">(
+ "list"
+ );
+ const [selectedItem, setSelectedItem] = useState(null);
+
+ // These functions are used by the DataDisplayComponent to handle item actions
+ const handleViewClick = (item: PeopleFormData) => {
+ setSelectedItem(item);
+ setMode("view");
+ };
+
+ const handleUpdateClick = (item: PeopleFormData) => {
+ setSelectedItem(item);
+ setMode("update");
+ };
+
+ // Function to handle the create button click
+ const handleCreateClick = () => {
+ setSelectedItem(null);
+ setMode("create");
+ };
+
+ return (
+
+ {/* Search Component */}
+ {mode === "list" && (
+
) => {
+ // Update pagination with both page reset and new query
+ updatePagination({
+ page: 1,
+ query: query,
+ });
+ }}
+ lang={lang}
+ />
+ )}
+
+ {/* Action Buttons Component */}
+ {mode === "list" && (
+
+ )}
+
+ {/* Sorting Component */}
+ {mode === "list" && (
+
+ )}
+
+ {/* Pagination Tools Component */}
+ {mode === "list" && (
+
+ )}
+
+ {/* Data Display - Only shown in list mode */}
+ {mode === "list" && (
+
+ )}
+
+ {mode !== "list" && (
+
+ {}}
+ />
+
+ )}
+
+ );
+}
+
+export default DashboardSuperUserApp;
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts
new file mode 100644
index 0000000..f4e6018
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts
@@ -0,0 +1,136 @@
+import { useState, useEffect, useCallback } from "react";
+import { PeopleFormData, PeopleSchema, fetchData } from "./schema";
+import {
+ PagePagination,
+ RequestParams,
+ ResponseMetadata,
+} from "@/components/validations/list/paginations";
+
+// Custom hook for pagination and data fetching
+export function usePaginatedData() {
+ const [data, setData] = useState([]);
+
+ // Request parameters - these are controlled by the user
+ const [requestParams, setRequestParams] = useState({
+ page: 1,
+ size: 10,
+ orderFields: ["createdAt"],
+ orderTypes: ["desc"],
+ query: {},
+ });
+
+ // Response metadata - these come from the API
+ const [responseMetadata, setResponseMetadata] = useState({
+ totalCount: 0,
+ allCount: 0,
+ totalPages: 0,
+ pageCount: 0,
+ });
+
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchDataFromApi = useCallback(async () => {
+ setLoading(true);
+ try {
+ const result = await fetchData({
+ page: requestParams.page,
+ size: requestParams.size,
+ orderFields: requestParams.orderFields,
+ orderTypes: requestParams.orderTypes,
+ query: requestParams.query,
+ });
+
+ // Validate data with Zod
+ const validatedData = result.data
+ .map((item: any) => {
+ try {
+ return PeopleSchema.parse(item);
+ } catch (err) {
+ console.error("Validation error for item:", item, err);
+ return null;
+ }
+ })
+ .filter(Boolean) as PeopleFormData[];
+
+ setData(validatedData);
+
+ // Update response metadata from API response
+ setResponseMetadata({
+ totalCount: result.pagination.totalCount,
+ allCount: result.pagination.allCount,
+ totalPages: result.pagination.totalPages,
+ pageCount: result.pagination.pageCount,
+ });
+
+ setError(null);
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error("Unknown error"));
+ } finally {
+ setLoading(false);
+ }
+ }, [
+ requestParams.page,
+ requestParams.size,
+ requestParams.orderFields,
+ requestParams.orderTypes,
+ requestParams.query,
+ ]);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ fetchDataFromApi();
+ }, 300); // Debounce
+
+ return () => clearTimeout(timer);
+ }, [fetchDataFromApi]);
+
+ const updatePagination = (updates: Partial) => {
+ // Transform query parameters to use __ilike with %value% format
+ if (updates.query) {
+ const transformedQuery: Record = {};
+
+ Object.entries(updates.query).forEach(([key, value]) => {
+ // Only transform string values that aren't already using a special operator
+ if (typeof value === 'string' && !key.includes('__')) {
+ transformedQuery[`${key}__ilike`] = `%${value}%`;
+ } else {
+ transformedQuery[key] = value;
+ }
+ });
+
+ updates.query = transformedQuery;
+ }
+
+ setRequestParams((prev) => ({
+ ...prev,
+ ...updates,
+ }));
+ };
+
+ // Create a combined refetch object that includes the setQuery function
+ const setQuery = (query: Record) => {
+ setRequestParams((prev) => ({
+ ...prev,
+ query,
+ }));
+ };
+
+ const refetch = Object.assign(fetchDataFromApi, { setQuery });
+
+ // Combine request params and response metadata for backward compatibility
+ const pagination: PagePagination = {
+ ...requestParams,
+ ...responseMetadata,
+ };
+
+ return {
+ data,
+ pagination,
+ loading,
+ error,
+ updatePagination,
+ setQuery,
+ refetch,
+ };
+}
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts
new file mode 100644
index 0000000..a2dd298
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts
@@ -0,0 +1,140 @@
+// Language dictionary for the template component
+const language = {
+ en: {
+ title: "Data Management",
+ create: "Create New",
+ view: "View Item",
+ update: "Update Item",
+ createNew: "Create New Item",
+ back: "Back",
+ cancel: "Cancel",
+ submit: "Submit",
+ creating: "Creating",
+ updating: "Updating",
+ noItemsFound: "No items found",
+ previous: "Previous",
+ next: "Next",
+ page: "Page",
+ of: "of",
+ itemsPerPage: "Items per page:",
+ sortBy: "Sort by:",
+ loading: "Loading...",
+ error: "Error loading data:",
+ showing: "Showing",
+ items: "items",
+ total: "Total",
+ search: "Search",
+ searchPlaceholder: "Enter search term...",
+ searchFields: "Search in fields",
+ activeSearch: "Active search",
+ clearSearch: "Clear",
+ formLabels: {
+ title: "Title",
+ description: "Description",
+ status: "Status",
+ createdAt: "Created",
+ uu_id: "ID",
+ created_at: "Created At",
+ updated_at: "Updated At",
+ person_tag: "Person Tag",
+ expiry_starts: "Expiry Starts",
+ expiry_ends: "Expiry Ends",
+ firstname: "First Name",
+ middle_name: "Middle Name",
+ surname: "Surname",
+ birth_date: "Birth Date",
+ birth_place: "Birth Place",
+ sex_code: "Sex Code",
+ country_code: "Country Code",
+ tax_no: "Tax Number",
+ active: "Active",
+ deleted: "Deleted",
+ is_confirmed: "Confirmed",
+ is_notification_send: "Notification Sent",
+ },
+ status: {
+ active: "Active",
+ inactive: "Inactive",
+ },
+ buttons: {
+ view: "View",
+ update: "Update",
+ create: "Create",
+ save: "Save",
+ },
+ actions: "Actions",
+ },
+ tr: {
+ title: "Veri Yönetimi",
+ create: "Yeni Oluştur",
+ view: "Görüntüle",
+ update: "Güncelle",
+ createNew: "Yeni Oluştur",
+ back: "Geri",
+ cancel: "İptal",
+ submit: "Gönder",
+ creating: "Oluşturuluyor",
+ updating: "Güncelleniyor",
+ noItemsFound: "Hiçbir kayıt bulunamadı",
+ previous: "Önceki",
+ next: "Sonraki",
+ page: "Sayfa",
+ of: "of",
+ itemsPerPage: "Sayfa başına kayıt:",
+ sortBy: "Sırala:",
+ loading: "Yükleniyor...",
+ error: "Veri yüklenirken hata:",
+ showing: "Gösteriliyor",
+ items: "kayıtlar",
+ total: "Toplam",
+ search: "Ara",
+ searchPlaceholder: "Ara...",
+ searchFields: "Ara alanları",
+ activeSearch: "Aktif arama",
+ clearSearch: "Temizle",
+ formLabels: {
+ title: "Başlık",
+ description: "Açıklama",
+ status: "Durum",
+ createdAt: "Oluşturulma",
+ uu_id: "Kimlik",
+ created_at: "Oluşturulma Tarihi",
+ updated_at: "Güncelleme Tarihi",
+ person_tag: "Kişi Etiketi",
+ expiry_starts: "Geçerlilik Başlangıcı",
+ expiry_ends: "Geçerlilik Bitişi",
+ firstname: "Ad",
+ middle_name: "İkinci Ad",
+ surname: "Soyad",
+ birth_date: "Doğum Tarihi",
+ birth_place: "Doğum Yeri",
+ sex_code: "Cinsiyet Kodu",
+ country_code: "Ülke Kodu",
+ tax_no: "Vergi Numarası",
+ active: "Aktif",
+ deleted: "Silinmiş",
+ is_confirmed: "Onaylanmış",
+ is_notification_send: "Bildirim Gönderildi",
+ },
+ status: {
+ active: "Aktif",
+ inactive: "Pasif",
+ },
+ buttons: {
+ view: "Görüntüle",
+ update: "Güncelle",
+ create: "Oluştur",
+ save: "Kaydet",
+ },
+ actions: "Eylemler",
+ },
+ // Add more languages as needed
+};
+
+export type LanguageKey = keyof typeof language;
+
+export const getTranslation = (lang: LanguageKey = "en") => {
+ return language[lang] || language.en;
+};
+
+export default language;
diff --git a/WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts
new file mode 100644
index 0000000..50ff50d
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts
@@ -0,0 +1,219 @@
+import { z } from "zod";
+import { PagePagination } from "@/components/validations/list/paginations";
+import { peopleList } from "@/apicalls/people/people";
+
+// Base schema with all possible fields
+const PeopleBaseSchema = z.object({
+ // Identification fields
+ uu_id: z.string().optional(),
+ person_tag: z.string().min(1, "Person tag is required"),
+
+ // Personal information fields
+ firstname: z.string().min(1, "First name is required"),
+ middle_name: z.string().optional(),
+ surname: z.string().min(1, "Surname is required"),
+ birth_date: z.string().optional(),
+ birth_place: z.string().optional(),
+ sex_code: z.string().optional(),
+
+ // Location fields
+ country_code: z.string().optional(),
+ tax_no: z.string().optional(),
+
+ // Status fields
+ active: z.boolean().default(true),
+ is_confirmed: z.boolean().default(false),
+ is_notification_send: z.boolean().default(false),
+ deleted: z.boolean().default(false),
+
+ // System fields
+ created_at: z.string().optional(),
+ updated_at: z.string().optional(),
+
+ // Expiry fields
+ expiry_starts: z.string().optional(),
+ expiry_ends: z.string().optional(),
+});
+
+// Schema for creating a new person
+export const CreatePeopleSchema = PeopleBaseSchema.omit({
+ uu_id: true,
+ created_at: true,
+ updated_at: true,
+ is_notification_send: true,
+ deleted: true
+});
+
+// Schema for updating an existing person
+export const UpdatePeopleSchema = PeopleBaseSchema.omit({
+ created_at: true,
+ updated_at: true,
+ is_notification_send: true,
+ deleted: true
+}).required({
+ uu_id: true
+});
+
+// Schema for viewing a person (all fields)
+export const ViewPeopleSchema = PeopleBaseSchema;
+
+// Default schema (used for validation)
+export const PeopleSchema = PeopleBaseSchema;
+
+export type PeopleFormData = z.infer;
+export type CreatePeopleFormData = z.infer;
+export type UpdatePeopleFormData = z.infer;
+export type ViewPeopleFormData = z.infer;
+
+// Base field definitions with common properties
+const baseFieldDefinitions = {
+ // Identification fields
+ uu_id: { type: "text", group: "identificationInfo", label: "UUID" },
+ person_tag: { type: "text", group: "identificationInfo", label: "Person Tag" },
+
+ // Personal information fields
+ firstname: { type: "text", group: "personalInfo", label: "First Name" },
+ middle_name: { type: "text", group: "personalInfo", label: "Middle Name" },
+ surname: { type: "text", group: "personalInfo", label: "Surname" },
+ birth_date: { type: "date", group: "personalInfo", label: "Birth Date" },
+ birth_place: { type: "text", group: "personalInfo", label: "Birth Place" },
+ sex_code: { type: "text", group: "personalInfo", label: "Sex Code" },
+
+ // Location fields
+ country_code: { type: "text", group: "locationInfo", label: "Country Code" },
+ tax_no: { type: "text", group: "locationInfo", label: "Tax Number" },
+
+ // Status fields
+ active: { type: "checkbox", group: "statusInfo", label: "Active" },
+ is_confirmed: { type: "checkbox", group: "statusInfo", label: "Confirmed" },
+ is_notification_send: { type: "checkbox", group: "statusInfo", label: "Notification Sent" },
+ deleted: { type: "checkbox", group: "statusInfo", label: "Deleted" },
+
+ // System fields
+ created_at: { type: "date", group: "systemInfo", label: "Created At" },
+ updated_at: { type: "date", group: "systemInfo", label: "Updated At" },
+
+ // Expiry fields
+ expiry_starts: { type: "date", group: "expiryInfo", label: "Expiry Start Date" },
+ expiry_ends: { type: "date", group: "expiryInfo", label: "Expiry End Date" },
+};
+
+// Field definitions for create mode
+export const createFieldDefinitions = {
+ person_tag: { ...baseFieldDefinitions.person_tag, readOnly: false, required: true, defaultValue: "" },
+ firstname: { ...baseFieldDefinitions.firstname, readOnly: false, required: true, defaultValue: "" },
+ middle_name: { ...baseFieldDefinitions.middle_name, readOnly: false, required: false, defaultValue: "" },
+ surname: { ...baseFieldDefinitions.surname, readOnly: false, required: true, defaultValue: "" },
+ birth_date: { ...baseFieldDefinitions.birth_date, readOnly: false, required: false, defaultValue: "" },
+ birth_place: { ...baseFieldDefinitions.birth_place, readOnly: false, required: false, defaultValue: "" },
+ sex_code: { ...baseFieldDefinitions.sex_code, readOnly: false, required: false, defaultValue: "" },
+ country_code: { ...baseFieldDefinitions.country_code, readOnly: false, required: false, defaultValue: "" },
+ tax_no: { ...baseFieldDefinitions.tax_no, readOnly: false, required: false, defaultValue: "" },
+ is_confirmed: { ...baseFieldDefinitions.is_confirmed, readOnly: false, required: false, defaultValue: false },
+};
+
+// Field definitions for update mode
+export const updateFieldDefinitions = {
+ person_tag: { ...baseFieldDefinitions.person_tag, readOnly: false, required: true, defaultValue: "" },
+ firstname: { ...baseFieldDefinitions.firstname, readOnly: false, required: true, defaultValue: "" },
+ middle_name: { ...baseFieldDefinitions.middle_name, readOnly: false, required: false, defaultValue: "" },
+ surname: { ...baseFieldDefinitions.surname, readOnly: false, required: true, defaultValue: "" },
+ birth_date: { ...baseFieldDefinitions.birth_date, readOnly: false, required: false, defaultValue: "" },
+ birth_place: { ...baseFieldDefinitions.birth_place, readOnly: false, required: false, defaultValue: "" },
+ sex_code: { ...baseFieldDefinitions.sex_code, readOnly: false, required: false, defaultValue: "" },
+ country_code: { ...baseFieldDefinitions.country_code, readOnly: false, required: false, defaultValue: "" },
+ tax_no: { ...baseFieldDefinitions.tax_no, readOnly: false, required: false, defaultValue: "" },
+ active: { ...baseFieldDefinitions.active, readOnly: false, required: false, defaultValue: false },
+ is_confirmed: { ...baseFieldDefinitions.is_confirmed, readOnly: false, required: false, defaultValue: false },
+};
+
+// Field definitions for view mode
+export const viewFieldDefinitions = {
+ uu_id: { ...baseFieldDefinitions.uu_id, readOnly: true, required: false, defaultValue: "" },
+ person_tag: { ...baseFieldDefinitions.person_tag, readOnly: true, required: false, defaultValue: "" },
+ firstname: { ...baseFieldDefinitions.firstname, readOnly: true, required: false, defaultValue: "" },
+ middle_name: { ...baseFieldDefinitions.middle_name, readOnly: true, required: false, defaultValue: "" },
+ surname: { ...baseFieldDefinitions.surname, readOnly: true, required: false, defaultValue: "" },
+ birth_date: { ...baseFieldDefinitions.birth_date, readOnly: true, required: false, defaultValue: "" },
+ birth_place: { ...baseFieldDefinitions.birth_place, readOnly: true, required: false, defaultValue: "" },
+ sex_code: { ...baseFieldDefinitions.sex_code, readOnly: true, required: false, defaultValue: "" },
+ country_code: { ...baseFieldDefinitions.country_code, readOnly: true, required: false, defaultValue: "" },
+ tax_no: { ...baseFieldDefinitions.tax_no, readOnly: true, required: false, defaultValue: "" },
+ active: { ...baseFieldDefinitions.active, readOnly: true, required: false, defaultValue: false },
+ is_confirmed: { ...baseFieldDefinitions.is_confirmed, readOnly: true, required: false, defaultValue: false },
+ is_notification_send: { ...baseFieldDefinitions.is_notification_send, readOnly: true, required: false, defaultValue: false },
+ deleted: { ...baseFieldDefinitions.deleted, readOnly: true, required: false, defaultValue: false },
+ created_at: { ...baseFieldDefinitions.created_at, readOnly: true, required: false, defaultValue: "" },
+ updated_at: { ...baseFieldDefinitions.updated_at, readOnly: true, required: false, defaultValue: "" },
+ expiry_starts: { ...baseFieldDefinitions.expiry_starts, readOnly: true, required: false, defaultValue: "" },
+ expiry_ends: { ...baseFieldDefinitions.expiry_ends, readOnly: true, required: false, defaultValue: "" },
+};
+
+// Combined field definitions for all modes
+export const fieldDefinitions = {
+ ...baseFieldDefinitions,
+ getDefinitionsByMode: (mode: "create" | "update" | "view") => {
+ switch (mode) {
+ case "create":
+ return createFieldDefinitions;
+ case "update":
+ return updateFieldDefinitions;
+ case "view":
+ return viewFieldDefinitions;
+ default:
+ return baseFieldDefinitions;
+ }
+ }
+};
+
+// Fields to show based on mode - dynamically generated from field definitions
+export const fieldsByMode = {
+ create: Object.keys(createFieldDefinitions),
+ update: Object.keys(updateFieldDefinitions),
+ view: Object.keys(viewFieldDefinitions),
+};
+
+export const fetchData = async ({
+ page = 1,
+ size = 10,
+ orderFields = ["createdAt"],
+ orderTypes = ["desc"],
+ query = {},
+}: {
+ page?: number;
+ size?: number;
+ orderFields?: string[];
+ orderTypes?: string[];
+ query?: Record;
+}) => {
+ // Call the actual API function
+ try {
+ const response = await peopleList({
+ page,
+ size,
+ orderField: orderFields,
+ orderType: orderTypes,
+ query,
+ });
+
+ return {
+ data: response.data,
+ pagination: response.pagination,
+ };
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ return {
+ data: [],
+ pagination: {
+ page,
+ size,
+ totalCount: 0,
+ allCount: 0,
+ totalPages: 0,
+ orderFields,
+ orderTypes,
+ pageCount: 0,
+ } as PagePagination,
+ };
+ }
+};
diff --git a/WebServices/client-frontend/src/components/Pages/index.ts b/WebServices/client-frontend/src/components/Pages/index.ts
new file mode 100644
index 0000000..d54673e
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/index.ts
@@ -0,0 +1,8 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import { peopleApplications } from "./people";
+import { dashboardApplications } from "./dashboard";
+
+export const PageNavigator: Record> = {
+ "/individual": peopleApplications,
+ "/dashboard": dashboardApplications,
+};
diff --git a/WebServices/client-frontend/src/components/Pages/people/index.ts b/WebServices/client-frontend/src/components/Pages/people/index.ts
new file mode 100644
index 0000000..bf752b6
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/people/index.ts
@@ -0,0 +1,6 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import PeopleSuperUserApp from "./superusers/app";
+
+export const peopleApplications: Record = {
+ app000003: PeopleSuperUserApp,
+};
diff --git a/WebServices/client-frontend/src/components/menu/menu.tsx b/WebServices/client-frontend/src/components/menu/menu.tsx
index bb43a08..d00fab1 100644
--- a/WebServices/client-frontend/src/components/menu/menu.tsx
+++ b/WebServices/client-frontend/src/components/menu/menu.tsx
@@ -7,6 +7,10 @@ import EmployeeProfileSection from "./EmployeeProfileSection";
import OccupantProfileSection from "./OccupantProfileSection";
import ProfileLoadingState from "./ProfileLoadingState";
import NavigationMenu from "./NavigationMenu";
+import {
+ ClientMenuProps,
+ UserSelection,
+} from "@/components/validations/menu/menu";
// Language definitions for dashboard title
const dashboardLanguage = {
@@ -20,16 +24,6 @@ const dashboardLanguage = {
},
};
-interface ClientMenuProps {
- siteUrls: string[];
- lang?: string;
-}
-
-interface UserSelection {
- userType: string;
- selected: any;
-}
-
const ClientMenu: React.FC = ({ siteUrls, lang = "en" }) => {
const transformedMenu = transformMenu(siteUrls);
const t =
diff --git a/WebServices/client-frontend/src/components/navigator/retriever.tsx b/WebServices/client-frontend/src/components/navigator/retriever.tsx
new file mode 100644
index 0000000..db43165
--- /dev/null
+++ b/WebServices/client-frontend/src/components/navigator/retriever.tsx
@@ -0,0 +1,28 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import { UnAuthorizedPage } from "@/components/navigator/unauthorizedpage";
+import { PageNavigator } from "../Pages";
+
+export function retrievePageByUrlAndPageId(
+ pageId: string,
+ pageUrl: string
+): PageComponent | typeof UnAuthorizedPage {
+ try {
+ const PageComponent =
+ PageNavigator[pageUrl as keyof typeof PageNavigator][
+ pageId as keyof (typeof PageNavigator)[typeof pageUrl]
+ ];
+ if (!PageComponent) {
+ console.log(`Page component not found for pageId: ${pageId}`);
+ return UnAuthorizedPage;
+ }
+ return PageComponent;
+ } catch (error) {
+ console.error(
+ `Error retrieving page component for pageId: ${pageId}`,
+ error
+ );
+ return UnAuthorizedPage;
+ }
+}
+
+export default retrievePageByUrlAndPageId;
diff --git a/WebServices/client-frontend/src/components/navigator/unauthorizedpage.tsx b/WebServices/client-frontend/src/components/navigator/unauthorizedpage.tsx
new file mode 100644
index 0000000..9ee42de
--- /dev/null
+++ b/WebServices/client-frontend/src/components/navigator/unauthorizedpage.tsx
@@ -0,0 +1,43 @@
+import { PageProps } from "../validations/translations/translation";
+
+// Language dictionary for internationalization
+const languageDictionary = {
+ en: {
+ title: "Unauthorized Access",
+ message1: "You do not have permission to access this page.",
+ message2: "Please contact the administrator.",
+ footer: `© ${new Date().getFullYear()} My Application`,
+ },
+ tr: {
+ title: "Yetkisiz Erişim",
+ message1: "Bu sayfaya erişim izniniz yok.",
+ message2: "Lütfen yönetici ile iletişime geçin.",
+ footer: `© ${new Date().getFullYear()} Uygulamam`,
+ },
+};
+
+export const UnAuthorizedPage: React.FC = ({
+ lang = "en",
+ queryParams,
+}) => {
+ // Use the language dictionary based on the lang prop, defaulting to English
+ const t =
+ languageDictionary[lang as keyof typeof languageDictionary] ||
+ languageDictionary.en;
+ return (
+ <>
+
+
+
+ {t.message1}
+ {t.message2}
+
+
+
+ >
+ );
+};
diff --git a/WebServices/client-frontend/src/components/validations/menu/menu.tsx b/WebServices/client-frontend/src/components/validations/menu/menu.tsx
new file mode 100644
index 0000000..4531fd7
--- /dev/null
+++ b/WebServices/client-frontend/src/components/validations/menu/menu.tsx
@@ -0,0 +1,11 @@
+interface ClientMenuProps {
+ siteUrls: string[];
+ lang?: string;
+}
+
+interface UserSelection {
+ userType: string;
+ selected: any;
+}
+
+export type { ClientMenuProps, UserSelection };
diff --git a/WebServices/client-frontend/src/components/validations/translations/translation.tsx b/WebServices/client-frontend/src/components/validations/translations/translation.tsx
new file mode 100644
index 0000000..c6952d7
--- /dev/null
+++ b/WebServices/client-frontend/src/components/validations/translations/translation.tsx
@@ -0,0 +1,61 @@
+// Define TypeScript interfaces for menu structure
+interface LanguageTranslation {
+ tr: string;
+ en: string;
+}
+
+interface MenuThirdLevel {
+ name: string;
+ lg: LanguageTranslation;
+ siteUrl: string;
+}
+
+interface MenuSecondLevel {
+ name: string;
+ lg: LanguageTranslation;
+ subList: MenuThirdLevel[];
+}
+
+interface MenuFirstLevel {
+ name: string;
+ lg: LanguageTranslation;
+ subList: MenuSecondLevel[];
+}
+
+// Define interfaces for the filtered menu structure
+interface FilteredMenuThirdLevel {
+ name: string;
+ lg: LanguageTranslation;
+ siteUrl: string;
+}
+
+interface FilteredMenuSecondLevel {
+ name: string;
+ lg: LanguageTranslation;
+ subList: FilteredMenuThirdLevel[];
+}
+
+interface FilteredMenuFirstLevel {
+ name: string;
+ lg: LanguageTranslation;
+ subList: FilteredMenuSecondLevel[];
+}
+
+interface PageProps {
+ lang: keyof LanguageTranslation;
+ queryParams: { [key: string]: string | undefined };
+}
+
+type PageComponent = React.ComponentType;
+
+export type {
+ PageComponent,
+ PageProps,
+ MenuFirstLevel,
+ MenuSecondLevel,
+ MenuThirdLevel,
+ FilteredMenuFirstLevel,
+ FilteredMenuSecondLevel,
+ FilteredMenuThirdLevel,
+ LanguageTranslation,
+};
diff --git a/WebServices/trash/menu/NavigatePages/index.tsx b/WebServices/trash/menu/NavigatePages/index.tsx
new file mode 100644
index 0000000..68192c2
--- /dev/null
+++ b/WebServices/trash/menu/NavigatePages/index.tsx
@@ -0,0 +1,8 @@
+import { PageComponent } from "@/components/validations/translations/translation";
+import PeopleSuperUserApp from "@/components/Pages/people/superusers/app";
+
+export const PageNavigator: Record> = {
+ "/individual": {
+ app000003: PeopleSuperUserApp,
+ },
+};
diff --git a/WebServices/client-frontend/src/components/NavigatePages/mock-data.ts b/WebServices/trash/menu/NavigatePages/mock-data.ts
similarity index 100%
rename from WebServices/client-frontend/src/components/NavigatePages/mock-data.ts
rename to WebServices/trash/menu/NavigatePages/mock-data.ts
diff --git a/WebServices/client-frontend/src/components/NavigatePages/page0001.tsx b/WebServices/trash/menu/NavigatePages/page0001.tsx
similarity index 100%
rename from WebServices/client-frontend/src/components/NavigatePages/page0001.tsx
rename to WebServices/trash/menu/NavigatePages/page0001.tsx
diff --git a/WebServices/trash/menu/runner.tsx b/WebServices/trash/menu/runner.tsx
index a1adbf3..5cf539d 100644
--- a/WebServices/trash/menu/runner.tsx
+++ b/WebServices/trash/menu/runner.tsx
@@ -5,51 +5,15 @@
* @returns {Array} - Filtered menu structure with only matching items
*/
import Menu from "@/components/menu/store"; // Assuming you have a menu structure imported
+import {
+ MenuFirstLevel,
+ MenuSecondLevel,
+ MenuThirdLevel,
+ FilteredMenuFirstLevel,
+ FilteredMenuSecondLevel,
+ FilteredMenuThirdLevel,
+} from "@/components/validations/translations/tr";
-// Define TypeScript interfaces for menu structure
-interface LanguageTranslation {
- tr: string;
- en: string;
-}
-
-interface MenuThirdLevel {
- name: string;
- lg: LanguageTranslation;
- siteUrl: string;
-}
-
-interface MenuSecondLevel {
- name: string;
- lg: LanguageTranslation;
- subList: MenuThirdLevel[];
-}
-
-interface MenuFirstLevel {
- name: string;
- lg: LanguageTranslation;
- subList: MenuSecondLevel[];
-}
-
-// Define interfaces for the filtered menu structure
-interface FilteredMenuThirdLevel {
- name: string;
- lg: LanguageTranslation;
- siteUrl: string;
-}
-
-interface FilteredMenuSecondLevel {
- name: string;
- lg: LanguageTranslation;
- subList: FilteredMenuThirdLevel[];
-}
-
-interface FilteredMenuFirstLevel {
- name: string;
- lg: LanguageTranslation;
- subList: FilteredMenuSecondLevel[];
-}
-
-export type { LanguageTranslation };
function transformMenu(siteUrls: string[]) {
// Process the menu structure
diff --git a/docker-compose.yml b/docker-compose.yml
index bb6f220..da6ad3f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,8 +6,6 @@ services:
dockerfile: WebServices/client-frontend/Dockerfile
networks:
- wag-services
- depends_on:
- - postgres-service
ports:
- "3000:3000"
environment:
@@ -15,34 +13,32 @@ services:
# volumes:
# - client-frontend:/WebServices/client-frontend
- # identity_service:
- # container_name: identity_service
- # build:
- # context: .
- # dockerfile: ApiServices/IdentityService/Dockerfile
- # networks:
- # - wag-services
- # env_file:
- # - api_env.env
- # environment:
- # - API_PATH=app:app
- # - API_HOST=0.0.0.0
- # - API_PORT=8002
- # - API_LOG_LEVEL=info
- # - API_RELOAD=1
- # - API_APP_NAME=evyos-identity-api-gateway
- # - API_TITLE=WAG API Identity Api Gateway
- # - API_FORGOT_LINK=https://identity_service/forgot-password
- # - API_DESCRIPTION=This api is serves as web identity api gateway only to evyos web services.
- # - API_APP_URL=https://identity_service
- # ports:
- # - "8002:8002"
- # depends_on:
- # - postgres-service
- # - mongo_service
- # - redis_service
- # mem_limit: 512M
- # cpus: 0.5
+ identity_service:
+ container_name: identity_service
+ build:
+ context: .
+ dockerfile: ApiServices/IdentityService/Dockerfile
+ networks:
+ - wag-services
+ depends_on:
+ - initializer_service
+ env_file:
+ - api_env.env
+ environment:
+ - API_PATH=app:app
+ - API_HOST=0.0.0.0
+ - API_PORT=8002
+ - API_LOG_LEVEL=info
+ - API_RELOAD=1
+ - API_APP_NAME=evyos-identity-api-gateway
+ - API_TITLE=WAG API Identity Api Gateway
+ - API_FORGOT_LINK=https://identity_service/forgot-password
+ - API_DESCRIPTION=This api is serves as web identity api gateway only to evyos web services.
+ - API_APP_URL=https://identity_service
+ ports:
+ - "8002:8002"
+ mem_limit: 4096m
+ cpus: 3
auth_service:
container_name: auth_service
@@ -53,6 +49,8 @@ services:
- wag-services
env_file:
- api_env.env
+ depends_on:
+ - initializer_service
environment:
- API_PATH=app:app
- API_HOST=0.0.0.0
@@ -66,40 +64,18 @@ services:
- API_APP_URL=https://auth_service
ports:
- "8001:8001"
- depends_on:
- - postgres-service
- - mongo_service
- - redis_service
- mem_limit: 512M
- cpus: 0.5
- # management_frontend:
- # container_name: management_frontend
- # build:
- # context: .
- # dockerfile: WebServices/management-frontend/Dockerfile
- # networks:
- # - wag-services
- # ports:
- # - "3001:3000" # Using different host port to avoid conflicts
- # # volumes:
- # # - management-frontend:/WebServices/management-frontend
- # environment:
- # - NODE_ENV=development
-
- # initializer_service:
- # container_name: initializer_service
- # build:
- # context: .
- # dockerfile: ApiServices/InitialService/Dockerfile
- # networks:
- # - wag-services
- # env_file:
- # - api_env.env
- # depends_on:
- # - postgres-service
- # - mongo_service
- # - redis_service
+ initializer_service:
+ container_name: initializer_service
+ build:
+ context: .
+ dockerfile: ApiServices/InitialService/Dockerfile
+ environment:
+ - set_alembic=0
+ networks:
+ - wag-services
+ env_file:
+ - api_env.env
dealer_service:
container_name: dealer_service
@@ -110,10 +86,25 @@ services:
- wag-services
env_file:
- api_env.env
- depends_on:
- - postgres-service
- - mongo_service
- - redis_service
+
+networks:
+ wag-services:
+# client-frontend:
+# management-frontend:
+
+# management_frontend:
+# container_name: management_frontend
+# build:
+# context: .
+# dockerfile: WebServices/management-frontend/Dockerfile
+# networks:
+# - wag-services
+# ports:
+# - "3001:3000" # Using different host port to avoid conflicts
+# # volumes:
+# # - management-frontend:/WebServices/management-frontend
+# environment:
+# - NODE_ENV=development
# template_service:
# container_name: template_service
@@ -141,7 +132,6 @@ services:
# - postgres-service
# - mongo_service
# - redis_service
-
# test_server:
# container_name: test_server
# build:
@@ -151,12 +141,3 @@ services:
# - api_env.env
# networks:
# - wag-services
-
-networks:
- wag-services:
-
-volumes:
- postgres-data:
- mongodb-data:
-# client-frontend:
-# management-frontend: