updated components common header layouts

This commit is contained in:
2025-05-03 13:51:02 +03:00
parent 1ce28ec5f0
commit 71c808a5c3
33 changed files with 769 additions and 809 deletions

View File

@@ -8,7 +8,12 @@ async function DashboardPage({
}: {
searchParams: Promise<{ [key: string]: string | undefined }>;
}) {
const { activePage, searchParamsInstance, lang, PageComponent } = await useDashboardPage({
const {
activePage,
searchParamsInstance,
lang,
PageComponent,
} = await useDashboardPage({
pageUrl: "/application",
searchParams,
});

View File

@@ -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<T>({
showFields,
data,

View File

@@ -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<string, any>;
className?: string;
}
import { Language, LanguageSelectionComponentProps } from "@/components/common/schemas";
export const LanguageSelectionComponent: React.FC<LanguageSelectionComponentProps> = ({
lang,

View File

@@ -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<PageProps>;
}
@@ -50,38 +28,29 @@ export async function useDashboardPage({
}: DashboardPageParams): Promise<DashboardPageResult> {
let searchParamsInstance: { [key: string]: string | undefined } = {};
const defaultLang = "en";
// Validate pageUrl
if (!pageUrl || typeof pageUrl !== "string") {
throw new Error(`Invalid page URL: ${pageUrl}`);
}
// 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,

View File

@@ -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<string, any>;
}
}
export type Language = "en" | "tr";
export interface LanguageSelectionComponentProps {
lang: Language;
setLang: (lang: Language) => void;
translations?: Record<string, any>;
className?: string;
}

View File

@@ -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<HeaderProps> = ({ lang }) => {
const Header: React.FC<HeaderProps> = ({ lang, setLang }) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
const [isMessagesOpen, setIsMessagesOpen] = useState(false);
@@ -185,222 +187,231 @@ const Header: React.FC<HeaderProps> = ({ lang }) => {
};
return (
<header className="sticky top-0 bg-white shadow-md z-10 p-4 flex justify-between items-center">
<h1 className="text-2xl font-semibold">{menuLanguage[lang]}</h1>
<div className="flex items-center space-x-4">
<input
type="text"
placeholder={searchPlaceholder[lang]}
className="border px-3 py-2 rounded-lg"
/>
<div className="w-full">
<header className="sticky top-0 bg-white shadow-md z-10 p-4 flex justify-between items-center w-full">
<h1 className="text-2xl font-semibold">{menuLanguage[lang]}</h1>
<div className="flex items-center space-x-4">
<input
type="text"
placeholder={searchPlaceholder[lang]}
className="border px-3 py-2 rounded-lg"
/>
{/* Notifications dropdown */}
<div className="relative" ref={notificationsRef}>
<div
className="flex items-center cursor-pointer relative"
onClick={() => {
setIsNotificationsOpen(!isNotificationsOpen);
setIsMessagesOpen(false);
setIsDropdownOpen(false);
}}
>
<Bell size={20} className="text-gray-600" />
{notifications.some((n) => !n.read) && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{notifications.filter((n) => !n.read).length}
</span>
)}
</div>
{/* Notifications dropdown menu */}
{isNotificationsOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white rounded-md shadow-lg py-1 z-20 border">
<div className="px-4 py-2 border-b flex justify-between items-center">
<h3 className="font-semibold">{t.notifications}</h3>
{notifications.some((n) => !n.read) && (
<button
onClick={markAllNotificationsAsRead}
className="text-xs text-blue-600 hover:text-blue-800"
>
{t.markAllAsRead}
</button>
)}
</div>
<div className="max-h-80 overflow-y-auto">
{notifications.length === 0 ? (
<div className="px-4 py-2 text-sm text-gray-500">
{t.noNotifications}
</div>
) : (
notifications.map((notification) => (
<div
key={notification.id}
className={`px-4 py-2 border-b last:border-b-0 ${
!notification.read ? "bg-blue-50" : ""
}`}
>
<div className="flex justify-between">
<h4 className="text-sm font-semibold">
{notification.title}
</h4>
{!notification.read && (
<button className="text-gray-400 hover:text-gray-600">
<X size={14} />
</button>
)}
</div>
<p className="text-xs text-gray-600 mt-1">
{notification.description}
</p>
<p className="text-xs text-gray-400 mt-1">
{formatDate(notification.time)}
</p>
</div>
))
)}
</div>
<div className="px-4 py-2 border-t text-center">
<a
href="/notifications"
className="text-sm text-blue-600 hover:text-blue-800"
>
{t.viewAll}
</a>
</div>
{/* Notifications dropdown */}
<div className="relative" ref={notificationsRef}>
<div
className="flex items-center cursor-pointer relative"
onClick={() => {
setIsNotificationsOpen(!isNotificationsOpen);
setIsMessagesOpen(false);
setIsDropdownOpen(false);
}}
>
<Bell size={20} className="text-gray-600" />
{notifications.some((n) => !n.read) && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{notifications.filter((n) => !n.read).length}
</span>
)}
</div>
)}
</div>
{/* Messages dropdown */}
<div className="relative" ref={messagesRef}>
<div
className="flex items-center cursor-pointer relative"
onClick={() => {
setIsMessagesOpen(!isMessagesOpen);
setIsNotificationsOpen(false);
setIsDropdownOpen(false);
}}
>
<MessageSquare size={20} className="text-gray-600" />
{messages.some((m) => !m.read) && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{messages.filter((m) => !m.read).length}
</span>
{/* Notifications dropdown menu */}
{isNotificationsOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white rounded-md shadow-lg py-1 z-20 border">
<div className="px-4 py-2 border-b flex justify-between items-center">
<h3 className="font-semibold">{t.notifications}</h3>
{notifications.some((n) => !n.read) && (
<button
onClick={markAllNotificationsAsRead}
className="text-xs text-blue-600 hover:text-blue-800"
>
{t.markAllAsRead}
</button>
)}
</div>
<div className="max-h-80 overflow-y-auto">
{notifications.length === 0 ? (
<div className="px-4 py-2 text-sm text-gray-500">
{t.noNotifications}
</div>
) : (
notifications.map((notification) => (
<div
key={notification.id}
className={`px-4 py-2 border-b last:border-b-0 ${!notification.read ? "bg-blue-50" : ""
}`}
>
<div className="flex justify-between">
<h4 className="text-sm font-semibold">
{notification.title}
</h4>
{!notification.read && (
<button className="text-gray-400 hover:text-gray-600">
<X size={14} />
</button>
)}
</div>
<p className="text-xs text-gray-600 mt-1">
{notification.description}
</p>
<p className="text-xs text-gray-400 mt-1">
{formatDate(notification.time)}
</p>
</div>
))
)}
</div>
<div className="px-4 py-2 border-t text-center">
<a
href="/notifications"
className="text-sm text-blue-600 hover:text-blue-800"
>
{t.viewAll}
</a>
</div>
</div>
)}
</div>
{/* Messages dropdown menu */}
{isMessagesOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white rounded-md shadow-lg py-1 z-20 border">
<div className="px-4 py-2 border-b flex justify-between items-center">
<h3 className="font-semibold">{t.messages}</h3>
{messages.some((m) => !m.read) && (
<button
onClick={markAllMessagesAsRead}
className="text-xs text-blue-600 hover:text-blue-800"
>
{t.markAllAsRead}
</button>
)}
</div>
{/* Messages dropdown */}
<div className="relative" ref={messagesRef}>
<div
className="flex items-center cursor-pointer relative"
onClick={() => {
setIsMessagesOpen(!isMessagesOpen);
setIsNotificationsOpen(false);
setIsDropdownOpen(false);
}}
>
<MessageSquare size={20} className="text-gray-600" />
{messages.some((m) => !m.read) && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{messages.filter((m) => !m.read).length}
</span>
)}
</div>
<div className="max-h-80 overflow-y-auto">
{messages.length === 0 ? (
<div className="px-4 py-2 text-sm text-gray-500">
{t.noMessages}
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`px-4 py-2 border-b last:border-b-0 ${
!message.read ? "bg-blue-50" : ""
}`}
{/* Messages dropdown menu */}
{isMessagesOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white rounded-md shadow-lg py-1 z-20 border">
<div className="px-4 py-2 border-b flex justify-between items-center">
<h3 className="font-semibold">{t.messages}</h3>
{messages.some((m) => !m.read) && (
<button
onClick={markAllMessagesAsRead}
className="text-xs text-blue-600 hover:text-blue-800"
>
<div className="flex items-start">
<div className="w-8 h-8 bg-gray-300 rounded-full flex items-center justify-center mr-2 flex-shrink-0">
<span className="text-xs font-semibold">
{message.avatar}
</span>
</div>
<div className="flex-1">
<div className="flex justify-between">
<h4 className="text-sm font-semibold">
{message.sender}
</h4>
<p className="text-xs text-gray-400">
{formatDate(message.time)}
{t.markAllAsRead}
</button>
)}
</div>
<div className="max-h-80 overflow-y-auto">
{messages.length === 0 ? (
<div className="px-4 py-2 text-sm text-gray-500">
{t.noMessages}
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`px-4 py-2 border-b last:border-b-0 ${!message.read ? "bg-blue-50" : ""
}`}
>
<div className="flex items-start">
<div className="w-8 h-8 bg-gray-300 rounded-full flex items-center justify-center mr-2 flex-shrink-0">
<span className="text-xs font-semibold">
{message.avatar}
</span>
</div>
<div className="flex-1">
<div className="flex justify-between">
<h4 className="text-sm font-semibold">
{message.sender}
</h4>
<p className="text-xs text-gray-400">
{formatDate(message.time)}
</p>
</div>
<p className="text-xs text-gray-600 mt-1">
{message.message}
</p>
</div>
<p className="text-xs text-gray-600 mt-1">
{message.message}
</p>
</div>
</div>
</div>
))
)}
</div>
))
)}
</div>
<div className="px-4 py-2 border-t text-center">
<a
href="/messages"
className="text-sm text-blue-600 hover:text-blue-800"
>
{t.viewAll}
</a>
<div className="px-4 py-2 border-t text-center">
<a
href="/messages"
className="text-sm text-blue-600 hover:text-blue-800"
>
{t.viewAll}
</a>
</div>
</div>
</div>
)}
</div>
{/* Profile dropdown */}
<div className="relative" ref={dropdownRef}>
<div
className="flex items-center cursor-pointer"
onClick={() => {
setIsDropdownOpen(!isDropdownOpen);
setIsNotificationsOpen(false);
setIsMessagesOpen(false);
}}
>
<div className="w-10 h-10 bg-gray-300 rounded-full flex items-center justify-center">
<User size={20} className="text-gray-600" />
</div>
<ChevronDown size={16} className="ml-1 text-gray-600" />
)}
</div>
{/* Dropdown menu */}
{isDropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20 border">
<a
href="/profile"
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<User size={16} className="mr-2" />
{t.profile}
</a>
<a
href="/settings"
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<Settings size={16} className="mr-2" />
{t.settings}
</a>
<button
onClick={handleLogout}
className="flex items-center w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<LogOut size={16} className="mr-2" />
{t.logout}
</button>
{/* Language selection */}
<div className="mr-4">
<LanguageSelectionComponent
lang={lang}
setLang={setLang}
className="border px-3 py-2 rounded-lg"
/>
</div>
{/* Profile dropdown */}
<div className="relative" ref={dropdownRef}>
<div
className="flex items-center cursor-pointer"
onClick={() => {
setIsDropdownOpen(!isDropdownOpen);
setIsNotificationsOpen(false);
setIsMessagesOpen(false);
}}
>
<div className="w-10 h-10 bg-gray-300 rounded-full flex items-center justify-center">
<User size={20} className="text-gray-600" />
</div>
<ChevronDown size={16} className="ml-1 text-gray-600" />
</div>
)}
{/* Dropdown menu */}
{isDropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20 border">
<a
href="/profile"
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<User size={16} className="mr-2" />
{t.profile}
</a>
<a
href="/settings"
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<Settings size={16} className="mr-2" />
{t.settings}
</a>
<button
onClick={handleLogout}
className="flex items-center w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
<LogOut size={16} className="mr-2" />
{t.logout}
</button>
</div>
)}
</div>
</div>
</div>
</header>
</header>
</div>
);
};

View File

@@ -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<string, string>;
searchPlaceholder?: Record<string, 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<PageContentProps> = ({ children, lang }) => {
return (
<div className="container mx-auto p-4">
{React.cloneElement(children as React.ReactElement<any>, { lang })}
</div>
);
};
export const DashboardLayout: React.FC<DashboardLayoutProps> = ({
children,
lang,
activePage,
sidebarContent,
customHeader,
pageInfo,
searchPlaceholder,
}) => {
const [language, setLanguage] = useState<Language>(lang as Language);
return (
<div className="min-h-screen min-w-screen flex h-screen w-screen overflow-y-auto">
<div className="min-h-screen min-w-screen flex h-screen w-screen">
{/* Sidebar */}
<aside className="w-1/4 border-r p-4 overflow-y-auto">
{sidebarContent ? (
sidebarContent
) : (
<ClientMenu lang={lang} activePage={activePage} />
)}
<ClientMenu lang={language} activePage={activePage} />
</aside>
{/* Main Content Area */}
<div className="flex flex-col w-3/4 overflow-y-auto">
{/* Header Component - Either custom or default */}
{customHeader ? (
customHeader
) : pageInfo && searchPlaceholder ? (
<header className="sticky top-0 bg-white shadow-md z-10 p-4 flex justify-between items-center">
<h1 className="text-2xl font-semibold">{pageInfo[lang]}</h1>
<div className="flex items-center space-x-4">
<input
type="text"
placeholder={searchPlaceholder[lang]}
className="border px-3 py-2 rounded-lg"
/>
<div className="w-10 h-10 bg-gray-300 rounded-full"></div>
</div>
</header>
) : (
<Header lang={lang} />
)}
<Header lang={language} setLang={setLanguage} />
{/* Page Content */}
<div className={`${customHeader ? 'p-4 overflow-y-auto' : 'container mx-auto p-4'}`}>
{children}
</div>
<PageContent lang={language}>{children}</PageContent>
</div>
</div>
);

View File

@@ -0,0 +1,7 @@
import { ReactNode } from "react";
export interface DashboardLayoutProps {
children: ReactNode;
lang: "en" | "tr";
activePage: string;
}

View File

@@ -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 (
<Link
key={url}
href={url}
className={`px-4 py-2 rounded-md transition-colors duration-200 ${url === activePage ? "bg-emerald-500 text-white" : "bg-white hover:bg-gray-100"}`}
>
{title}
</Link>
);
}
return (
<nav className="flex flex-col space-y-2 p-4">
{Object.entries(navItems).map(([url, title]) => (
<Link
key={url}
href={url}
className={`px-4 py-2 rounded-md transition-colors duration-200 ${
url === activePage
? "bg-emerald-500 text-white"
: "bg-white hover:bg-gray-100"
}`}
>
{title}
</Link>
))}
{Object.entries(navItems).map(([url, title]) => createLinkComponent(url, title))}
</nav>
);
}

View File

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

View File

@@ -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<ClientMenuProps> = ({ lang = "en", activePage }) => {
const t =
dashboardLanguage[lang as keyof typeof dashboardLanguage] ||
dashboardLanguage.en;
const ClientMenu: React.FC<ClientMenuProps> = ({ 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<boolean>(true);
const [userType, setUserType] = useState<string | null>(null);
const [userSelectionData, setUserSelectionData] =
useState<UserSelection | null>(null);
const [userSelectionData, setUserSelectionData] = useState<UserSelection | null>(null);
// Fetch user selection data
useEffect(() => {
setLoading(true);
@@ -52,6 +47,21 @@ const ClientMenu: React.FC<ClientMenuProps> = ({ lang = "en", activePage }) => {
})
.finally(() => setLoading(false));
}, []);
function createProfileComponent(): JSX.Element {
return (
loading ? (
<ProfileLoadingState loadingText={t.loading} />
) : userType === "employee" && userSelectionData ? (
<EmployeeProfileSection userSelectionData={userSelectionData} lang={lang as "en" | "tr"} />
) : userType === "occupant" && userSelectionData ? (
<OccupantProfileSection userSelectionData={userSelectionData} lang={lang as "en" | "tr"} />
) : (
<div className="text-center text-gray-500">{t.loading}</div>
)
)
}
return (
<div className="w-full bg-white shadow-sm rounded-lg overflow-hidden">
<div className="p-4 border-b border-gray-200">
@@ -60,32 +70,14 @@ const ClientMenu: React.FC<ClientMenuProps> = ({ lang = "en", activePage }) => {
</h2>
</div>
{/* Profile Section with Suspense */}
<div className="p-4 border-b border-gray-200 bg-gray-50">
<Suspense
fallback={<div className="text-center py-4">{t.loading}</div>}
>
{loading ? (
<ProfileLoadingState loadingText={t.loading} />
) : userType === "employee" && userSelectionData ? (
<EmployeeProfileSection
userSelectionData={userSelectionData}
lang={lang as "en" | "tr"}
/>
) : userType === "occupant" && userSelectionData ? (
<OccupantProfileSection
userSelectionData={userSelectionData}
lang={lang as "en" | "tr"}
/>
) : (
<div className="text-center text-gray-500">{t.loading}</div>
)}
{createProfileComponent()}
</Suspense>
</div>
{/* Navigation Menu
<NavigationMenu transformedMenu={transformedMenu} lang={lang} /> */}
<NavigationMenu activePage={activePage} lang={lang} />
<NavigationMenu activePage={activePage} lang={lang as "en" | "tr"} />
</div>
);
};

View File

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

View File

@@ -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<PageProps> = ({ lang: initialLang = "en" }) => {
// Add local state for language to ensure it persists when changed
const [lang, setLang] = useState<Language>(initialLang as Language);
const ApplicationPage: React.FC<PageProps> = ({ lang }: { lang: Language }) => {
// Use the API data hook directly
const {
data,
pagination,
@@ -45,8 +43,8 @@ const ApplicationPage: React.FC<PageProps> = ({ 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<PageProps> = ({ 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<PageProps> = ({ 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<PageProps> = ({ lang: initialLang = "en" }) => {
gridCols={gridCols}
setGridCols={setGridCols}
/>
{/* Language Selection */}
<LanguageSelectionComponent
lang={lang}
translations={translations}
setLang={setLang}
/>
</div>
</div>

View File

@@ -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<PageProps> {
export function retrievePageByUrl(url: string, lang: "en" | "tr"): React.FC<PageProps> {
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) => <PageComponent {...props} lang={lang} />;
}
return UnAuthorizedPage;
// Also pass lang to UnAuthorizedPage
return (props: PageProps) => <UnAuthorizedPage {...props} lang={lang} />;
}

View File

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

View File

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