From 71c808a5c3284d62f25282b21f2026d9c7d7840f Mon Sep 17 00:00:00 2001 From: Berkay Date: Sat, 3 May 2025 13:51:02 +0300 Subject: [PATCH] updated components common header layouts --- .../menu/NavigatePages/index.tsx | 0 .../menu/NavigatePages/mock-data.ts | 0 .../menu/NavigatePages/page0001.tsx | 0 .../trash => Trash}/menu/leftMenu.tsx | 0 {WebServices/trash => Trash}/menu/runner.tsx | 0 {WebServices/trash => Trash}/menu/store.tsx | 0 .../common/CardDisplay/CardDisplay.tsx | 2 - .../common/FormDisplay/CreateComponent.tsx | 57 ++- .../common/FormDisplay/FormDisplay.tsx | 45 +- .../common/FormDisplay/UpdateComponent.tsx | 104 ++++- .../LanguageSelectionComponent.tsx | 10 +- .../common/hooks/useDashboardPage.ts | 37 +- .../src/components/common/schemas.ts | 12 +- .../src/components/header/Header.tsx | 407 ++++++++--------- .../components/layouts/DashboardLayout.tsx | 37 +- .../src/components/layouts/PageTemplate.tsx | 30 +- .../src/components/layouts/schema.ts | 7 + .../(DashboardLayout)/application/page.tsx | 7 +- .../common/CardDisplay/CardDisplay.tsx | 2 - .../LanguageSelectionComponent.tsx | 10 +- .../common/hooks/useDashboardPage.ts | 37 +- .../src/components/common/schemas.ts | 12 +- .../src/components/header/Header.tsx | 409 +++++++++--------- .../components/layouts/DashboardLayout.tsx | 65 +-- .../src/components/layouts/schema.ts | 7 + .../src/components/menu/NavigationMenu.tsx | 55 +-- .../src/components/menu/handler.tsx | 109 ----- .../src/components/menu/menu.tsx | 50 +-- .../src/components/menu/type.ts | 21 + .../src/eventRouters/application/page.tsx | 25 +- .../src/eventRouters/pageRetriever.tsx | 9 +- .../validations/translations/translation.tsx | 2 +- WebServices/management-frontend/tsconfig.json | 10 +- 33 files changed, 769 insertions(+), 809 deletions(-) rename {WebServices/trash => Trash}/menu/NavigatePages/index.tsx (100%) rename {WebServices/trash => Trash}/menu/NavigatePages/mock-data.ts (100%) rename {WebServices/trash => Trash}/menu/NavigatePages/page0001.tsx (100%) rename {WebServices/trash => Trash}/menu/leftMenu.tsx (100%) rename {WebServices/trash => Trash}/menu/runner.tsx (100%) rename {WebServices/trash => Trash}/menu/store.tsx (100%) create mode 100644 WebServices/client-frontend/src/components/layouts/schema.ts create mode 100644 WebServices/management-frontend/src/components/layouts/schema.ts delete mode 100644 WebServices/management-frontend/src/components/menu/handler.tsx create mode 100644 WebServices/management-frontend/src/components/menu/type.ts diff --git a/WebServices/trash/menu/NavigatePages/index.tsx b/Trash/menu/NavigatePages/index.tsx similarity index 100% rename from WebServices/trash/menu/NavigatePages/index.tsx rename to Trash/menu/NavigatePages/index.tsx diff --git a/WebServices/trash/menu/NavigatePages/mock-data.ts b/Trash/menu/NavigatePages/mock-data.ts similarity index 100% rename from WebServices/trash/menu/NavigatePages/mock-data.ts rename to Trash/menu/NavigatePages/mock-data.ts diff --git a/WebServices/trash/menu/NavigatePages/page0001.tsx b/Trash/menu/NavigatePages/page0001.tsx similarity index 100% rename from WebServices/trash/menu/NavigatePages/page0001.tsx rename to Trash/menu/NavigatePages/page0001.tsx diff --git a/WebServices/trash/menu/leftMenu.tsx b/Trash/menu/leftMenu.tsx similarity index 100% rename from WebServices/trash/menu/leftMenu.tsx rename to Trash/menu/leftMenu.tsx diff --git a/WebServices/trash/menu/runner.tsx b/Trash/menu/runner.tsx similarity index 100% rename from WebServices/trash/menu/runner.tsx rename to Trash/menu/runner.tsx diff --git a/WebServices/trash/menu/store.tsx b/Trash/menu/store.tsx similarity index 100% rename from WebServices/trash/menu/store.tsx rename to Trash/menu/store.tsx diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx index 8b4b3ce..584e098 100644 --- a/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx +++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx @@ -5,8 +5,6 @@ import { CardSkeleton } from "./CardSkeleton"; import { getFieldValue, getGridClasses } from "./utils"; import { CardDisplayProps } from "./schema"; -// Interface moved to schema.ts - export function CardDisplay({ showFields, data, diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx index 7deeb92..1a339bf 100644 --- a/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx +++ b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx @@ -64,6 +64,7 @@ export function CreateComponent({ formState: { errors }, setValue, watch, + reset, } = useForm>({ defaultValues, resolver: validationSchema ? zodResolver(validationSchema) : undefined, @@ -71,6 +72,33 @@ export function CreateComponent({ const formValues = watch(); + // Get language-specific validation schema if available + useEffect(() => { + if (formProps.schemaPath) { + const loadLanguageValidationSchema = async () => { + try { + // Dynamic import of the schema module + const schemaModule = await import(formProps.schemaPath); + + // Check if language-specific schema functions are available + if (schemaModule.getCreateApplicationSchema) { + const langValidationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr"); + + // Reset the form with the current values + reset(defaultValues); + + // Update the validation schema in formProps for future validations + formProps.validationSchema = langValidationSchema; + } + } catch (error) { + console.error("Error loading language-specific validation schema:", error); + } + }; + + loadLanguageValidationSchema(); + } + }, [lang, formProps.schemaPath, reset, defaultValues]); + // Handle form submission const onSubmit: SubmitHandler> = async (data) => { try { @@ -112,23 +140,17 @@ export function CreateComponent({ // Translate group names for display dynamically const getGroupTitle = (groupName: string) => { - // First check if there's a translation for the exact group key + // Check if we have a translation for this group name if (t[groupName]) { return t[groupName]; } - // Try to format the group name in a more readable way if no translation exists - // Convert camelCase or snake_case to Title Case with spaces + // If no translation is found, just format the name as a fallback const formattedName = groupName - // Insert space before capital letters and uppercase the first letter .replace(/([A-Z])/g, ' $1') - // Replace underscores with spaces .replace(/_/g, ' ') - // Capitalize first letter .replace(/^./, (str) => str.toUpperCase()) - // Capitalize each word .replace(/\b\w/g, (c) => c.toUpperCase()); - return formattedName; }; @@ -230,9 +252,17 @@ export function CreateComponent({ return null; } }; - + return (
+
+ + +
{t.create || "Create"} @@ -265,14 +295,7 @@ export function CreateComponent({ ))} -
- - -
+
diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx index df0cf57..8f96737 100644 --- a/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx +++ b/WebServices/client-frontend/src/components/common/FormDisplay/FormDisplay.tsx @@ -19,9 +19,9 @@ export function FormDisplay({ }: FormDisplayProps) { const [enhancedFormProps, setEnhancedFormProps] = useState(formProps); - // Dynamically import schema definitions if provided in formProps + // Update form props when language or mode changes useEffect(() => { - const loadSchemaDefinitions = async () => { + const updateFormProps = async () => { try { // Check if schemaPath is provided in formProps if (formProps.schemaPath) { @@ -40,12 +40,14 @@ export function FormDisplay({ fieldDefs = schemaModule.viewFieldDefinitions; } - // Get the appropriate validation schema based on mode + // Get the appropriate validation schema based on mode and language let validationSchema; - if (mode === "create" && schemaModule.CreateApplicationSchema) { - validationSchema = schemaModule.CreateApplicationSchema; - } else if (mode === "update" && schemaModule.UpdateApplicationSchema) { - validationSchema = schemaModule.UpdateApplicationSchema; + if (mode === "create" && schemaModule.getCreateApplicationSchema) { + // Use language-aware schema factory function + validationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr"); + } else if (mode === "update" && schemaModule.getUpdateApplicationSchema) { + // Use language-aware schema factory function + validationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr"); } else if (mode === "view" && schemaModule.ViewApplicationSchema) { validationSchema = schemaModule.ViewApplicationSchema; } else if (schemaModule.ApplicationSchema) { @@ -55,22 +57,40 @@ export function FormDisplay({ // Get the grouped field definitions structure if available const groupedFieldDefs = schemaModule.baseFieldDefinitions || {}; - // Update form props with schema information + // Update form props with schema information and current language setEnhancedFormProps({ ...formProps, fieldDefinitions: fieldDefs || {}, validationSchema, fieldsByMode: schemaModule.fieldsByMode || {}, groupedFieldDefinitions: groupedFieldDefs, + // Add current language to force child components to recognize changes + currentLang: lang, + // Add schema path for dynamic imports in child components + schemaPath: formProps.schemaPath + }); + } else { + // If no schema path, just update with current language + setEnhancedFormProps({ + ...formProps, + currentLang: lang }); } } catch (error) { console.error("Error loading schema definitions:", error); + // Even on error, update the language + setEnhancedFormProps({ + ...formProps, + currentLang: lang + }); } }; - loadSchemaDefinitions(); - }, [formProps, mode, lang]); // Added lang as a dependency to ensure re-fetch when language changes + updateFormProps(); + }, [formProps, mode, lang]); // Lang dependency ensures re-fetch when language changes + + // Debug the props received by FormDisplay + // FormDisplay component renders different form modes based on the mode prop // Render the appropriate component based on the mode switch (mode) { @@ -89,9 +109,12 @@ export function FormDisplay({ /> ); case "update": + // Create a stable key for the component to ensure proper re-rendering + const updateKey = `update-${lang}-${(initialData as any)?.uu_id || 'new'}`; + return initialData ? ( - key={`update-${lang}`} // Add key with lang to force re-render on language change + key={updateKey} // Add key with lang and item ID to force re-render initialData={initialData} refetch={refetch} setMode={setMode} diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx index 8a044a8..4875fbe 100644 --- a/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx +++ b/WebServices/client-frontend/src/components/common/FormDisplay/UpdateComponent.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { UpdateComponentProps, FieldDefinition } from "./types"; @@ -26,29 +26,48 @@ export function UpdateComponent({ }: UpdateComponentProps) { const t = translations[lang as keyof typeof translations] || {}; + // Get field definitions from formProps if available const fieldDefinitions = formProps.fieldDefinitions || {}; const validationSchema = formProps.validationSchema; + // Ensure field definitions are processed only once + const processedFieldDefinitions = useMemo(() => { + const processed = { ...fieldDefinitions }; + // Make all fields editable except system fields + Object.entries(processed).forEach(([fieldName, definition]) => { + if (fieldName !== 'uu_id' && fieldName !== 'created_at' && fieldName !== 'updated_at') { + (processed[fieldName] as FieldDefinition).readOnly = false; + } + }); + return processed; + }, [fieldDefinitions]); + const [groupedFields, setGroupedFields] = useState>({}); useEffect(() => { - if (Object.keys(fieldDefinitions).length > 0) { + if (Object.keys(processedFieldDefinitions).length > 0) { const groups: Record = {}; - Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => { + // Group the processed field definitions + Object.entries(processedFieldDefinitions).forEach(([fieldName, definition]) => { + // Convert to FieldDefinition type const def = definition as FieldDefinition; + + // Add the field name to the definition + const fieldDef = { ...def, name: fieldName }; + + // Add to the appropriate group if (!groups[def.group]) { groups[def.group] = []; } - groups[def.group].push({ ...def, name: fieldName }); + groups[def.group].push(fieldDef); }); - setGroupedFields(groups); } - }, [fieldDefinitions]); + }, [processedFieldDefinitions]); const defaultValues: Record = {}; - Object.entries(fieldDefinitions).forEach(([key, def]) => { + Object.entries(processedFieldDefinitions).forEach(([key, def]) => { const fieldDef = def as FieldDefinition; defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : ""; }); @@ -57,6 +76,9 @@ export function UpdateComponent({ Object.assign(defaultValues, initialData as Record); } + // Track the current language to detect changes + const [currentLang, setCurrentLang] = useState(lang); + const { register, handleSubmit, @@ -83,6 +105,53 @@ export function UpdateComponent({ } }, [initialData, reset]); + // Detect language changes and update validation schema + useEffect(() => { + // If language has changed, update the form + if (currentLang !== lang || formProps.currentLang !== lang) { + const updateValidationForLanguage = async () => { + try { + // If we have a schema path, dynamically load the schema for the current language + if (formProps.schemaPath) { + // Dynamic import of the schema module + const schemaModule = await import(formProps.schemaPath); + + // Check if language-specific schema functions are available + if (schemaModule.getUpdateApplicationSchema) { + // Get the schema for the current language + const langValidationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr"); + + // Save current form values + const formValues = watch(); + + // Reset the form with current values but clear errors + reset(formValues, { + keepDirty: true, + keepValues: true, + keepErrors: false + }); + + // Manually trigger validation after reset + setTimeout(() => { + // Trigger validation for all fields to show updated error messages + Object.keys(formValues).forEach(fieldName => { + trigger(fieldName); + }); + }, 0); + + // Update our tracked language + setCurrentLang(lang); + } + } + } catch (error) { + console.error("Error updating validation schema for language:", error); + } + }; + + updateValidationForLanguage(); + } + }, [lang, formProps.currentLang, currentLang, formProps.schemaPath, reset, watch, trigger]); + const formValues = watch(); // Handle form submission @@ -149,10 +218,12 @@ export function UpdateComponent({ // Translate group names for display dynamically const getGroupTitle = (groupName: string) => { + // Check if we have a translation for this group name if (t[groupName]) { return t[groupName]; } + // If no translation is found, just format the name as a fallback const formattedName = groupName .replace(/([A-Z])/g, ' $1') .replace(/_/g, ' ') @@ -289,6 +360,7 @@ export function UpdateComponent({ {t.updateDescription || "Update existing item"} + {/* Display validation errors summary if any */} {Object.keys(errors).length > 0 && ( @@ -306,6 +378,15 @@ export function UpdateComponent({ )} +
+ + +
+ {/* Render fields grouped by their group property */}
{Object.entries(groupedFields).map(([groupName, fields]) => ( @@ -322,14 +403,7 @@ export function UpdateComponent({ ))}
-
- - -
+
diff --git a/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx b/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx index 7b9a4a2..34f6972 100644 --- a/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx +++ b/WebServices/client-frontend/src/components/common/HeaderSelections/LanguageSelectionComponent.tsx @@ -1,14 +1,6 @@ "use client"; import React from "react"; - -export type Language = "en" | "tr"; - -interface LanguageSelectionComponentProps { - lang: Language; - setLang: (lang: Language) => void; - translations?: Record; - className?: string; -} +import { Language, LanguageSelectionComponentProps } from "@/components/common/schemas"; export const LanguageSelectionComponent: React.FC = ({ lang, diff --git a/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts b/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts index 0d3b5ab..e37d716 100644 --- a/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts +++ b/WebServices/client-frontend/src/components/common/hooks/useDashboardPage.ts @@ -1,38 +1,16 @@ import { retrievePageByUrl } from "@/eventRouters/pageRetriever"; import { PageProps } from "@/validations/translations/translation"; -import React, { ReactElement } from "react"; +import React from "react"; export interface DashboardPageParams { - /** - * The active page path, e.g., "/application", "/dashboard" - */ pageUrl: string; - - /** - * The search parameters from Next.js - */ searchParams: Promise<{ [key: string]: string | undefined }>; } export interface DashboardPageResult { - /** - * The active page path - */ activePage: string; - - /** - * The resolved search parameters - */ searchParamsInstance: { [key: string]: string | undefined }; - - /** - * The current language, either from search params or default - */ lang: "en" | "tr"; - - /** - * The page component to render - */ PageComponent: React.FC; } @@ -50,38 +28,29 @@ export async function useDashboardPage({ }: DashboardPageParams): Promise { let searchParamsInstance: { [key: string]: string | undefined } = {}; const defaultLang = "en"; - // Validate pageUrl + if (!pageUrl || typeof pageUrl !== "string") { throw new Error(`Invalid page URL: ${pageUrl}`); } - // Resolve search params try { searchParamsInstance = await searchParams; } catch (err) { console.error("Error resolving search parameters:", err); - // Still throw the error to be caught by Next.js error boundary throw err; } - // Determine language const lang = (searchParamsInstance?.lang as "en" | "tr") || defaultLang; - - // Validate language if (lang !== "en" && lang !== "tr") { console.warn( `Invalid language "${lang}" specified, falling back to "${defaultLang}"` ); } - // Get page component - const PageComponent = retrievePageByUrl(pageUrl); - - // Check if page component exists + const PageComponent = retrievePageByUrl(pageUrl, lang); if (!PageComponent) { throw new Error(`Page component not found for URL: ${pageUrl}`); } - return { activePage: pageUrl, searchParamsInstance, diff --git a/WebServices/client-frontend/src/components/common/schemas.ts b/WebServices/client-frontend/src/components/common/schemas.ts index b67cc86..17d62d1 100644 --- a/WebServices/client-frontend/src/components/common/schemas.ts +++ b/WebServices/client-frontend/src/components/common/schemas.ts @@ -1,4 +1,3 @@ - // Carried schemas from any request and response // Common request parameters interface @@ -34,4 +33,13 @@ export interface PagePagination { orderField: string[]; orderType: string[]; query: Record; -} \ No newline at end of file +} + +export type Language = "en" | "tr"; + +export interface LanguageSelectionComponentProps { + lang: Language; + setLang: (lang: Language) => void; + translations?: Record; + className?: string; +} diff --git a/WebServices/client-frontend/src/components/header/Header.tsx b/WebServices/client-frontend/src/components/header/Header.tsx index 3e5c442..cd870fd 100644 --- a/WebServices/client-frontend/src/components/header/Header.tsx +++ b/WebServices/client-frontend/src/components/header/Header.tsx @@ -12,9 +12,11 @@ import { import { searchPlaceholder, menuLanguage } from "@/app/commons/pageDefaults"; import { logoutActiveSession } from "@/apicalls/login/login"; import { useRouter } from "next/navigation"; +import { LanguageSelectionComponent } from "../common/HeaderSelections/LanguageSelectionComponent"; interface HeaderProps { lang: "en" | "tr"; + setLang: (lang: "en" | "tr") => void; } // Language dictionary for the dropdown menu @@ -96,7 +98,7 @@ const mockMessages = [ }, ]; -const Header: React.FC = ({ lang }) => { +const Header: React.FC = ({ lang, setLang }) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isNotificationsOpen, setIsNotificationsOpen] = useState(false); const [isMessagesOpen, setIsMessagesOpen] = useState(false); @@ -185,220 +187,231 @@ const Header: React.FC = ({ lang }) => { }; return ( -
-

{menuLanguage[lang]}

-
- +
+
+

{menuLanguage[lang]}

+
+ - {/* Notifications dropdown */} -
-
{ - setIsNotificationsOpen(!isNotificationsOpen); - setIsMessagesOpen(false); - setIsDropdownOpen(false); - }} - > - - {notifications.some((n) => !n.read) && ( - - {notifications.filter((n) => !n.read).length} - - )} -
- - {/* Notifications dropdown menu */} - {isNotificationsOpen && ( -
-
-

{t.notifications}

- {notifications.some((n) => !n.read) && ( - - )} -
- -
- {notifications.length === 0 ? ( -
- {t.noNotifications} -
- ) : ( - notifications.map((notification) => ( -
-
-

- {notification.title} -

- {!notification.read && ( - - )} -
-

- {notification.description} -

-

- {formatDate(notification.time)} -

-
- )) - )} -
- - + {/* Notifications dropdown */} +
+
{ + setIsNotificationsOpen(!isNotificationsOpen); + setIsMessagesOpen(false); + setIsDropdownOpen(false); + }} + > + + {notifications.some((n) => !n.read) && ( + + {notifications.filter((n) => !n.read).length} + + )}
- )} -
- {/* Messages dropdown */} -
-
{ - setIsMessagesOpen(!isMessagesOpen); - setIsNotificationsOpen(false); - setIsDropdownOpen(false); - }} - > - - {messages.some((m) => !m.read) && ( - - {messages.filter((m) => !m.read).length} - + {/* Notifications dropdown menu */} + {isNotificationsOpen && ( +
+
+

{t.notifications}

+ {notifications.some((n) => !n.read) && ( + + )} +
+ +
+ {notifications.length === 0 ? ( +
+ {t.noNotifications} +
+ ) : ( + notifications.map((notification) => ( +
+
+

+ {notification.title} +

+ {!notification.read && ( + + )} +
+

+ {notification.description} +

+

+ {formatDate(notification.time)} +

+
+ )) + )} +
+ + +
)}
- {/* Messages dropdown menu */} - {isMessagesOpen && ( -
-
-

{t.messages}

- {messages.some((m) => !m.read) && ( - - )} -
+ {/* Messages dropdown */} +
+
{ + setIsMessagesOpen(!isMessagesOpen); + setIsNotificationsOpen(false); + setIsDropdownOpen(false); + }} + > + + {messages.some((m) => !m.read) && ( + + {messages.filter((m) => !m.read).length} + + )} +
-
- {messages.length === 0 ? ( -
- {t.noMessages} -
- ) : ( - messages.map((message) => ( -
+
+

{t.messages}

+ {messages.some((m) => !m.read) && ( + + )} +
+ +
+ {messages.length === 0 ? ( +
+ {t.noMessages} +
+ ) : ( + messages.map((message) => ( +
+
+
+ + {message.avatar} + +
+
+
+

+ {message.sender} +

+

+ {formatDate(message.time)} +

+
+

+ {message.message}

-

- {message.message} -

-
- )) - )} -
+ )) + )} +
- -
- )} -
- - {/* Profile dropdown */} -
-
{ - setIsDropdownOpen(!isDropdownOpen); - setIsNotificationsOpen(false); - setIsMessagesOpen(false); - }} - > -
- -
- + )}
- {/* Dropdown menu */} - {isDropdownOpen && ( -
- - - {t.profile} - - - - {t.settings} - - + {/* Language selection */} +
+ +
+ + {/* Profile dropdown */} +
+
{ + setIsDropdownOpen(!isDropdownOpen); + setIsNotificationsOpen(false); + setIsMessagesOpen(false); + }} + > +
+ +
+
- )} + + {/* Dropdown menu */} + {isDropdownOpen && ( + + )} +
-
-
+
+ ); }; diff --git a/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx b/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx index f186a90..541414f 100644 --- a/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx +++ b/WebServices/client-frontend/src/components/layouts/DashboardLayout.tsx @@ -1,41 +1,44 @@ "use client"; -import React, { ReactNode } from "react"; +import React, { useState, useEffect, ReactNode } from "react"; import Header from "@/components/header/Header"; import ClientMenu from "@/components/menu/menu"; +import { DashboardLayoutProps } from "./schema"; +import { Language } from "@/components/common/schemas"; -interface DashboardLayoutProps { +// Page Content component to wrap the children +interface PageContentProps { children: ReactNode; - lang: "en" | "tr"; - activePage: string; - siteUrls: string[]; + lang: Language; } -/** - * A reusable dashboard layout component that provides consistent structure - * for all dashboard pages with sidebar, header, and content area. - */ +const PageContent: React.FC = ({ children, lang }) => { + return ( +
+ {React.cloneElement(children as React.ReactElement, { lang })} +
+ ); +}; + export const DashboardLayout: React.FC = ({ children, lang, activePage, - siteUrls, }) => { + const [language, setLanguage] = useState(lang as Language); + return (
{/* Sidebar */} {/* Main Content Area */}
- {/* Header Component */} -
- + {/* Header Component - Either custom or default */} +
{/* Page Content */} -
- {children} -
+ {children}
); diff --git a/WebServices/client-frontend/src/components/layouts/PageTemplate.tsx b/WebServices/client-frontend/src/components/layouts/PageTemplate.tsx index 2210ba1..fd0daee 100644 --- a/WebServices/client-frontend/src/components/layouts/PageTemplate.tsx +++ b/WebServices/client-frontend/src/components/layouts/PageTemplate.tsx @@ -11,10 +11,10 @@ interface PageTemplateProps { title: string; lang: "en" | "tr"; translations: Record; - + // Search section searchSection?: ReactNode; - + // Data and pagination data: any[]; pagination: any; @@ -22,20 +22,20 @@ interface PageTemplateProps { loading: boolean; error: any; refetch: () => void; - + // Content display contentDisplay: ReactNode; - + // Form handling formComponent?: ReactNode; mode: FormMode; setMode: (mode: FormMode) => void; handleCreateClick: () => void; handleCancel: () => void; - + // Language handling setLang?: (lang: Language) => void; - + // Optional components headerActions?: ReactNode; additionalActions?: ReactNode; @@ -77,18 +77,18 @@ export const PageTemplate: React.FC = ({

{title}

{/* Grid Selection */} - - + {/* Language Selection */} - { })} + setLang={setLang || (() => {})} /> - + {/* Additional header actions */} {headerActions}
@@ -139,7 +139,7 @@ export const PageTemplate: React.FC = ({ formComponent || (

Form component not provided

- - )} -
- -
- {notifications.length === 0 ? ( -
- {t.noNotifications} -
- ) : ( - notifications.map((notification) => ( -
-
-

- {notification.title} -

- {!notification.read && ( - - )} -
-

- {notification.description} -

-

- {formatDate(notification.time)} -

-
- )) - )} -
- - + {/* Notifications dropdown */} +
+
{ + setIsNotificationsOpen(!isNotificationsOpen); + setIsMessagesOpen(false); + setIsDropdownOpen(false); + }} + > + + {notifications.some((n) => !n.read) && ( + + {notifications.filter((n) => !n.read).length} + + )}
- )} -
- {/* Messages dropdown */} -
-
{ - setIsMessagesOpen(!isMessagesOpen); - setIsNotificationsOpen(false); - setIsDropdownOpen(false); - }} - > - - {messages.some((m) => !m.read) && ( - - {messages.filter((m) => !m.read).length} - + {/* Notifications dropdown menu */} + {isNotificationsOpen && ( +
+
+

{t.notifications}

+ {notifications.some((n) => !n.read) && ( + + )} +
+ +
+ {notifications.length === 0 ? ( +
+ {t.noNotifications} +
+ ) : ( + notifications.map((notification) => ( +
+
+

+ {notification.title} +

+ {!notification.read && ( + + )} +
+

+ {notification.description} +

+

+ {formatDate(notification.time)} +

+
+ )) + )} +
+ + +
)}
- {/* Messages dropdown menu */} - {isMessagesOpen && ( -
-
-

{t.messages}

- {messages.some((m) => !m.read) && ( - - )} -
+ {/* Messages dropdown */} +
+
{ + setIsMessagesOpen(!isMessagesOpen); + setIsNotificationsOpen(false); + setIsDropdownOpen(false); + }} + > + + {messages.some((m) => !m.read) && ( + + {messages.filter((m) => !m.read).length} + + )} +
-
- {messages.length === 0 ? ( -
- {t.noMessages} -
- ) : ( - messages.map((message) => ( -
+
+

{t.messages}

+ {messages.some((m) => !m.read) && ( + + )} +
+ +
+ {messages.length === 0 ? ( +
+ {t.noMessages} +
+ ) : ( + messages.map((message) => ( +
+
+
+ + {message.avatar} + +
+
+
+

+ {message.sender} +

+

+ {formatDate(message.time)} +

+
+

+ {message.message}

-

- {message.message} -

-
- )) - )} -
+ )) + )} +
- -
- )} -
- - {/* Profile dropdown */} -
-
{ - setIsDropdownOpen(!isDropdownOpen); - setIsNotificationsOpen(false); - setIsMessagesOpen(false); - }} - > -
- -
- + )}
- {/* Dropdown menu */} - {isDropdownOpen && ( -
- - - {t.profile} - - - - {t.settings} - - + {/* Language selection */} +
+ +
+ + {/* Profile dropdown */} +
+
{ + setIsDropdownOpen(!isDropdownOpen); + setIsNotificationsOpen(false); + setIsMessagesOpen(false); + }} + > +
+ +
+
- )} + + {/* Dropdown menu */} + {isDropdownOpen && ( + + )} +
-
- + +
); }; diff --git a/WebServices/management-frontend/src/components/layouts/DashboardLayout.tsx b/WebServices/management-frontend/src/components/layouts/DashboardLayout.tsx index 2891cdf..541414f 100644 --- a/WebServices/management-frontend/src/components/layouts/DashboardLayout.tsx +++ b/WebServices/management-frontend/src/components/layouts/DashboardLayout.tsx @@ -1,69 +1,44 @@ "use client"; -import React, { ReactNode } from "react"; +import React, { useState, useEffect, ReactNode } from "react"; import Header from "@/components/header/Header"; import ClientMenu from "@/components/menu/menu"; +import { DashboardLayoutProps } from "./schema"; +import { Language } from "@/components/common/schemas"; -interface DashboardLayoutProps { +// Page Content component to wrap the children +interface PageContentProps { children: ReactNode; - lang: "en" | "tr"; - activePage: string; - - // Optional props for client-frontend application - sidebarContent?: ReactNode; - customHeader?: ReactNode; - pageInfo?: Record; - searchPlaceholder?: Record; + lang: Language; } -/** - * A reusable dashboard layout component that provides consistent structure - * for all dashboard pages with sidebar, header, and content area. - */ +const PageContent: React.FC = ({ children, lang }) => { + return ( +
+ {React.cloneElement(children as React.ReactElement, { lang })} +
+ ); +}; + export const DashboardLayout: React.FC = ({ children, lang, activePage, - sidebarContent, - customHeader, - pageInfo, - searchPlaceholder, }) => { + const [language, setLanguage] = useState(lang as Language); + return ( -
+
{/* Sidebar */} {/* Main Content Area */}
{/* Header Component - Either custom or default */} - {customHeader ? ( - customHeader - ) : pageInfo && searchPlaceholder ? ( -
-

{pageInfo[lang]}

-
- -
-
-
- ) : ( -
- )} - +
{/* Page Content */} -
- {children} -
+ {children}
); diff --git a/WebServices/management-frontend/src/components/layouts/schema.ts b/WebServices/management-frontend/src/components/layouts/schema.ts new file mode 100644 index 0000000..7e5018e --- /dev/null +++ b/WebServices/management-frontend/src/components/layouts/schema.ts @@ -0,0 +1,7 @@ +import { ReactNode } from "react"; + +export interface DashboardLayoutProps { + children: ReactNode; + lang: "en" | "tr"; + activePage: string; +} diff --git a/WebServices/management-frontend/src/components/menu/NavigationMenu.tsx b/WebServices/management-frontend/src/components/menu/NavigationMenu.tsx index 1f14b79..d11353b 100644 --- a/WebServices/management-frontend/src/components/menu/NavigationMenu.tsx +++ b/WebServices/management-frontend/src/components/menu/NavigationMenu.tsx @@ -1,53 +1,32 @@ "use client"; -import React from "react"; import Link from "next/link"; - -const NavigationLanguage = { - en: { - "/dashboard": "Dashboard", - "/append/event": "Event Append", - "/append/service": "Service Append", - "/application": "Application", - "/employee": "Employee", - "/ocuppant": "Ocuppant", - }, - tr: { - "/dashboard": "Kontrol Paneli", - "/append/event": "Event Append", - "/append/service": "Service Append", - "/application": "Application", - "/employee": "Employee", - "/ocuppant": "Ocuppant", - }, -}; +import React, { JSX } from "react"; +import { getNavigationMenu } from "./type"; function NavigationMenu({ lang, activePage, }: { - lang: string; + lang: "en" | "tr"; activePage: string; }) { - // Get the navigation items based on the selected language - const navItems = - NavigationLanguage[lang as keyof typeof NavigationLanguage] || - NavigationLanguage.en; + const navItems = getNavigationMenu(lang); + + function createLinkComponent(url: string, title: string): JSX.Element { + return ( + + {title} + + ); + } return ( ); } diff --git a/WebServices/management-frontend/src/components/menu/handler.tsx b/WebServices/management-frontend/src/components/menu/handler.tsx deleted file mode 100644 index d39db23..0000000 --- a/WebServices/management-frontend/src/components/menu/handler.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client"; - -import Menu from "./store"; - -// Define TypeScript interfaces for menu structure -export interface LanguageTranslation { - tr: string; - en: string; -} - -export interface MenuThirdLevel { - name: string; - lg: LanguageTranslation; - siteUrl: string; -} - -export interface MenuSecondLevel { - name: string; - lg: LanguageTranslation; - subList: MenuThirdLevel[]; -} - -export interface MenuFirstLevel { - name: string; - lg: LanguageTranslation; - subList: MenuSecondLevel[]; -} - -// Define interfaces for the filtered menu structure -export interface FilteredMenuThirdLevel { - name: string; - lg: LanguageTranslation; - siteUrl: string; -} - -export interface FilteredMenuSecondLevel { - name: string; - lg: LanguageTranslation; - subList: FilteredMenuThirdLevel[]; -} - -export interface FilteredMenuFirstLevel { - name: string; - lg: LanguageTranslation; - subList: FilteredMenuSecondLevel[]; -} - -/** - * Filters the menu structure based on intersections with provided URLs - * @param {string[]} siteUrls - Array of site URLs to check for intersection - * @returns {Array} - Filtered menu structure with only matching items - */ -export function transformMenu(siteUrls: string[]) { - // Process the menu structure - const filteredMenu: FilteredMenuFirstLevel[] = Menu.reduce( - (acc: FilteredMenuFirstLevel[], firstLevel: MenuFirstLevel) => { - // Create a new first level item with empty subList - const newFirstLevel: FilteredMenuFirstLevel = { - name: firstLevel.name, - lg: { ...firstLevel.lg }, - subList: [], - }; - - // Process second level items - firstLevel.subList.forEach((secondLevel: MenuSecondLevel) => { - // Create a new second level item with empty subList - const newSecondLevel: FilteredMenuSecondLevel = { - name: secondLevel.name, - lg: { ...secondLevel.lg }, - subList: [], - }; - - // Process third level items - secondLevel.subList.forEach((thirdLevel: MenuThirdLevel) => { - // Check if the third level's siteUrl matches exactly - if ( - thirdLevel.siteUrl && - siteUrls.some((url) => url === thirdLevel.siteUrl) - ) { - // Create a modified third level item - const newThirdLevel: FilteredMenuThirdLevel = { - name: thirdLevel.name, - lg: { ...thirdLevel.lg }, - siteUrl: thirdLevel.siteUrl, - }; - - // Add the modified third level to the second level's subList - newSecondLevel.subList.push(newThirdLevel); - } - }); - - // Only add the second level to the first level if it has any matching third level items - if (newSecondLevel.subList.length > 0) { - newFirstLevel.subList.push(newSecondLevel); - } - }); - - // Only add the first level to the result if it has any matching second level items - if (newFirstLevel.subList.length > 0) { - acc.push(newFirstLevel); - } - - return acc; - }, - [] - ); - - return filteredMenu; -} \ No newline at end of file diff --git a/WebServices/management-frontend/src/components/menu/menu.tsx b/WebServices/management-frontend/src/components/menu/menu.tsx index f8fea68..1a3e5f8 100644 --- a/WebServices/management-frontend/src/components/menu/menu.tsx +++ b/WebServices/management-frontend/src/components/menu/menu.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState, Suspense } from "react"; +import React, { useEffect, useState, Suspense, JSX } from "react"; import { retrieveUserSelection } from "@/apicalls/cookies/token"; import EmployeeProfileSection from "./EmployeeProfileSection"; import OccupantProfileSection from "./OccupantProfileSection"; @@ -23,18 +23,13 @@ const dashboardLanguage = { }, }; -const ClientMenu: React.FC = ({ lang = "en", activePage }) => { - const t = - dashboardLanguage[lang as keyof typeof dashboardLanguage] || - dashboardLanguage.en; +const ClientMenu: React.FC = ({ lang, activePage }) => { + const t = dashboardLanguage[lang as keyof typeof dashboardLanguage] || dashboardLanguage.en; - // State for loading indicator, user type, and user selection data const [loading, setLoading] = useState(true); const [userType, setUserType] = useState(null); - const [userSelectionData, setUserSelectionData] = - useState(null); + const [userSelectionData, setUserSelectionData] = useState(null); - // Fetch user selection data useEffect(() => { setLoading(true); @@ -52,6 +47,21 @@ const ClientMenu: React.FC = ({ lang = "en", activePage }) => { }) .finally(() => setLoading(false)); }, []); + + function createProfileComponent(): JSX.Element { + return ( + loading ? ( + + ) : userType === "employee" && userSelectionData ? ( + + ) : userType === "occupant" && userSelectionData ? ( + + ) : ( +
{t.loading}
+ ) + ) + } + return (
@@ -60,32 +70,14 @@ const ClientMenu: React.FC = ({ lang = "en", activePage }) => {
- {/* Profile Section with Suspense */}
{t.loading}
} > - {loading ? ( - - ) : userType === "employee" && userSelectionData ? ( - - ) : userType === "occupant" && userSelectionData ? ( - - ) : ( -
{t.loading}
- )} + {createProfileComponent()}
- - {/* Navigation Menu - */} - +
); }; diff --git a/WebServices/management-frontend/src/components/menu/type.ts b/WebServices/management-frontend/src/components/menu/type.ts new file mode 100644 index 0000000..392949d --- /dev/null +++ b/WebServices/management-frontend/src/components/menu/type.ts @@ -0,0 +1,21 @@ +export const NavigationLanguage = { + en: { + "/dashboard": "Dashboard", + "/append/event": "Event Board", + "/append/service": "Service Board", + "/application": "Application Board", + }, + tr: { + "/dashboard": "Kontrol Paneli", + "/append/event": "Event Paneli", + "/append/service": "Servis Paneli", + "/application": "Uygulama Paneli", + }, +}; + +export function getNavigationMenu(lang: string) { + return ( + NavigationLanguage[lang as keyof typeof NavigationLanguage] || + NavigationLanguage.en + ); +} diff --git a/WebServices/management-frontend/src/eventRouters/application/page.tsx b/WebServices/management-frontend/src/eventRouters/application/page.tsx index 33133e7..0dc32ad 100644 --- a/WebServices/management-frontend/src/eventRouters/application/page.tsx +++ b/WebServices/management-frontend/src/eventRouters/application/page.tsx @@ -12,17 +12,15 @@ import { CardDisplay } from "@/components/common/CardDisplay/CardDisplay"; import { FormMode } from "@/components/common/FormDisplay/types"; import { FormDisplay } from "@/components/common/FormDisplay/FormDisplay"; import { GridSelectionComponent, GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent"; -import { LanguageSelectionComponent, Language } from "@/components/common/HeaderSelections/LanguageSelectionComponent"; +import { LanguageSelectionComponent } from "@/components/common/HeaderSelections/LanguageSelectionComponent"; import { getCreateApplicationSchema, getUpdateApplicationSchema } from "./schema"; import { translations } from "./language"; import { PageProps } from "@/validations/translations/translation"; import { useApiData } from "@/components/common"; +import { Language } from "@/components/common/schemas"; -const ApplicationPage: React.FC = ({ lang: initialLang = "en" }) => { - // Add local state for language to ensure it persists when changed - const [lang, setLang] = useState(initialLang as Language); +const ApplicationPage: React.FC = ({ lang }: { lang: Language }) => { - // Use the API data hook directly const { data, pagination, @@ -45,8 +43,8 @@ const ApplicationPage: React.FC = ({ lang: initialLang = "en" }) => { ); const [validationSchema, setValidationSchema] = useState(() => - mode === 'create' ? getCreateApplicationSchema(lang as "en" | "tr") : - mode === 'update' ? getUpdateApplicationSchema(lang as "en" | "tr") : + mode === 'create' ? getCreateApplicationSchema(lang) : + mode === 'update' ? getUpdateApplicationSchema(lang) : schema.ViewApplicationSchema ); @@ -63,8 +61,8 @@ const ApplicationPage: React.FC = ({ lang: initialLang = "en" }) => { // Update validation schema when mode or language changes useEffect(() => { setValidationSchema( - mode === 'create' ? getCreateApplicationSchema(lang as "en" | "tr") : - mode === 'update' ? getUpdateApplicationSchema(lang as "en" | "tr") : + mode === 'create' ? getCreateApplicationSchema(lang) : + mode === 'update' ? getUpdateApplicationSchema(lang) : schema.ViewApplicationSchema ); }, [mode, lang]); @@ -98,7 +96,7 @@ const ApplicationPage: React.FC = ({ lang: initialLang = "en" }) => { additionalFields: [ // { // name: "status", - // label: translations[lang as "en" | "tr"].status, + // label: translations[lang].status, // type: "select" as const, // options: [ // { value: "active", label: "Active" }, @@ -170,13 +168,6 @@ const ApplicationPage: React.FC = ({ lang: initialLang = "en" }) => { gridCols={gridCols} setGridCols={setGridCols} /> - - {/* Language Selection */} - diff --git a/WebServices/management-frontend/src/eventRouters/pageRetriever.tsx b/WebServices/management-frontend/src/eventRouters/pageRetriever.tsx index b09a45a..604adbd 100644 --- a/WebServices/management-frontend/src/eventRouters/pageRetriever.tsx +++ b/WebServices/management-frontend/src/eventRouters/pageRetriever.tsx @@ -2,9 +2,12 @@ import { PageProps } from "../validations/translations/translation"; import { UnAuthorizedPage } from "./unauthorizedpage"; import menuPages from "./index"; -export function retrievePageByUrl(url: string): React.FC { +export function retrievePageByUrl(url: string, lang: "en" | "tr"): React.FC { if (url in menuPages) { - return menuPages[url as keyof typeof menuPages]; + const PageComponent = menuPages[url as keyof typeof menuPages]; + // Return a new component that passes the lang prop to the original component + return (props: PageProps) => ; } - return UnAuthorizedPage; + // Also pass lang to UnAuthorizedPage + return (props: PageProps) => ; } diff --git a/WebServices/management-frontend/src/validations/translations/translation.tsx b/WebServices/management-frontend/src/validations/translations/translation.tsx index 43d7e97..b2512a1 100644 --- a/WebServices/management-frontend/src/validations/translations/translation.tsx +++ b/WebServices/management-frontend/src/validations/translations/translation.tsx @@ -43,7 +43,7 @@ interface FilteredMenuFirstLevel { interface PageProps { lang: keyof LanguageTranslation; - queryParams: { [key: string]: string | undefined }; + queryParams?: { [key: string]: string | undefined }; } type PageComponent = React.ComponentType; diff --git a/WebServices/management-frontend/tsconfig.json b/WebServices/management-frontend/tsconfig.json index c133409..a75080d 100644 --- a/WebServices/management-frontend/tsconfig.json +++ b/WebServices/management-frontend/tsconfig.json @@ -22,6 +22,14 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "../../menu/EmployeeProfileSection.tsx", + "src/components/menu/NavigationMenu.tsx", + "../../menu/menu.tsx" + ], "exclude": ["node_modules"] }