From a0daf45530240762cf4a4ed7f32fc7710dde8db7 Mon Sep 17 00:00:00 2001 From: berkay Date: Sun, 20 Apr 2025 18:25:00 +0300 Subject: [PATCH] updated components and page navigator --- .../DealerService/init_applications.py | 3 +- ApiServices/InitialService/alembic.ini | 2 +- ApiServices/InitialService/alembic/env.py | 15 + ApiServices/InitialService/app.py | 3 +- .../src/apicalls/login/login.tsx | 2 +- .../app/(DashboardLayout)/dashboard/page.tsx | 7 +- .../app/(DashboardLayout)/individual/page.tsx | 4 +- .../management/accounting/page.tsx | 26 +- .../management/budget/page.tsx | 26 +- .../src/components/NavigatePages/index.tsx | 66 --- .../components/NavigatePages/interFaces.tsx | 6 - .../Pages/{people => dashboard}/a.txt | 0 .../src/components/Pages/dashboard/index.ts | 6 + .../superusers/ActionButtonsComponent.tsx | 30 + .../superusers/DataDisplayComponent.tsx | 58 ++ .../dashboard/superusers/FormComponent.tsx | 513 ++++++++++++++++++ .../superusers/ListInfoComponent.tsx | 141 +++++ .../superusers/PaginationToolsComponent.tsx | 86 +++ .../dashboard/superusers/SearchComponent.tsx | 143 +++++ .../dashboard/superusers/SortingComponent.tsx | 81 +++ .../Pages/dashboard/superusers/app.tsx | 112 ++++ .../Pages/dashboard/superusers/hooks.ts | 136 +++++ .../Pages/dashboard/superusers/language.ts | 140 +++++ .../Pages/dashboard/superusers/schema.ts | 219 ++++++++ .../src/components/Pages/index.ts | 8 + .../src/components/Pages/people/index.ts | 6 + .../src/components/menu/menu.tsx | 14 +- .../src/components/navigator/retriever.tsx | 28 + .../components/navigator/unauthorizedpage.tsx | 43 ++ .../src/components/validations/menu/menu.tsx | 11 + .../validations/translations/translation.tsx | 61 +++ .../trash/menu/NavigatePages/index.tsx | 8 + .../menu}/NavigatePages/mock-data.ts | 0 .../menu}/NavigatePages/page0001.tsx | 0 WebServices/trash/menu/runner.tsx | 52 +- docker-compose.yml | 135 ++--- 36 files changed, 1939 insertions(+), 252 deletions(-) delete mode 100644 WebServices/client-frontend/src/components/NavigatePages/index.tsx delete mode 100644 WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx rename WebServices/client-frontend/src/components/Pages/{people => dashboard}/a.txt (100%) create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/index.ts create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/ActionButtonsComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/DataDisplayComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/FormComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/ListInfoComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/PaginationToolsComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/SearchComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/SortingComponent.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/app.tsx create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/hooks.ts create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/language.ts create mode 100644 WebServices/client-frontend/src/components/Pages/dashboard/superusers/schema.ts create mode 100644 WebServices/client-frontend/src/components/Pages/index.ts create mode 100644 WebServices/client-frontend/src/components/Pages/people/index.ts create mode 100644 WebServices/client-frontend/src/components/navigator/retriever.tsx create mode 100644 WebServices/client-frontend/src/components/navigator/unauthorizedpage.tsx create mode 100644 WebServices/client-frontend/src/components/validations/menu/menu.tsx create mode 100644 WebServices/client-frontend/src/components/validations/translations/translation.tsx create mode 100644 WebServices/trash/menu/NavigatePages/index.tsx rename WebServices/{client-frontend/src/components => trash/menu}/NavigatePages/mock-data.ts (100%) rename WebServices/{client-frontend/src/components => trash/menu}/NavigatePages/page0001.tsx (100%) 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 */} -
-

{activePage}

-
- -
-
-
+
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 */} -
-

{activePage}

-
- -
-
-
+
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.title}

-
-
-

- {t.message1} -

-

{t.message2}

-
-
-

{t.footer}

-
-
- - ); -}; - -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} +

+ +
+
+ {/* Identification Information Section */} + {hasIdentificationFields && ( +
+

Identification

+
+ {fieldGroups.identificationInfo.map((field) => ( + ( + + + {field.label} + {field.required && ( + * + )} + + + {field.type === "checkbox" ? ( + + ) : field.type === "date" ? ( + + ) : ( + + )} + + + {field.name + ? (t as any)[`form.descriptions.${field.name}`] || + "" + : ""} + + + + )} + /> + ))} +
+
+ )} + + {/* Personal Information Section */} + {hasPersonalFields && ( +
+

Personal Information

+
+ {fieldGroups.personalInfo.map((field) => ( + ( + + + {field.label} + {field.required && ( + * + )} + + + {field.type === "checkbox" ? ( + + ) : field.type === "date" ? ( + + ) : ( + + )} + + + {field.name + ? (t as any)[`form.descriptions.${field.name}`] || + "" + : ""} + + + + )} + /> + ))} +
+
+ )} + + {/* Location Information Section */} + {hasLocationFields && ( +
+

Location Information

+
+ {fieldGroups.locationInfo.map((field) => ( + ( + + {field.label} + + + + + {field.name + ? (t as any)[`form.descriptions.${field.name}`] || + "" + : ""} + + + + )} + /> + ))} +
+
+ )} + + {/* Expiry Information Section */} + {hasExpiryFields && ( +
+

Expiry Information

+
+ {fieldGroups.expiryInfo.map((field) => ( + ( + + + {field.label} + {field.required && ( + * + )} + + + + + + {field.name + ? (t as any)[`form.descriptions.${field.name}`] || + "" + : ""} + + + + )} + /> + ))} +
+
+ )} + + {/* Status Information Section */} + {hasStatusFields && ( +
+

Status Information

+
+ {fieldGroups.statusInfo.map((field) => ( + ( + + + + +
+ + {field.label} + {field.required && ( + * + )} + + + {field.name + ? (t as any)[`form.descriptions.${field.name}`] || + "" + : ""} + +
+ +
+ )} + /> + ))} +
+
+ )} + +
+ + + {!readOnly && ( + + )} +
+
+
+
+ ); +} 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.title}

+
+
+

{t.message1}

+

{t.message2}

+
+
+

{t.footer}

+
+
+ + ); +}; 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: