diff --git a/WebServices/client-frontend/src/apicalls/cookies/token.tsx b/WebServices/client-frontend/src/apicalls/cookies/token.tsx index 194472b..92aa9db 100644 --- a/WebServices/client-frontend/src/apicalls/cookies/token.tsx +++ b/WebServices/client-frontend/src/apicalls/cookies/token.tsx @@ -17,14 +17,14 @@ async function checkAccessTokenIsValid() { } async function retrievePageList() { - const response = await fetchDataWithToken(siteUrls, {}, "GET", false); + const response: any = await fetchDataWithToken(siteUrls, {}, "GET", false); return response?.status === 200 || response?.status === 202 ? response.data?.sites : null; } -async function retrievePagebyUrl(pageUrl: string) { - const response = await fetchDataWithToken( +async function retrieveApplicationbyUrl(pageUrl: string) { + const response: any = await fetchDataWithToken( pageValid, { page_url: pageUrl, @@ -142,7 +142,7 @@ export { retrieveUserType, retrieveAccessObjects, retrieveUserSelection, - retrievePagebyUrl, + retrieveApplicationbyUrl, retrievePageList, // retrieveavailablePages, }; diff --git a/WebServices/client-frontend/src/app/(AuthLayout)/auth/login/page.tsx b/WebServices/client-frontend/src/app/(AuthLayout)/auth/login/page.tsx index 2d20e55..5d6e483 100644 --- a/WebServices/client-frontend/src/app/(AuthLayout)/auth/login/page.tsx +++ b/WebServices/client-frontend/src/app/(AuthLayout)/auth/login/page.tsx @@ -1,16 +1,6 @@ -import React from "react"; -import Login from "@/components/auth/login"; -import { Metadata } from "next"; +'use server'; +import Login from "@/webPages/auth/Login/page"; -export const metadata: Metadata = { - title: "WAG Login", - description: "Login to WAG system", -}; +const LoginPage = async () => { return }; -export default function LoginPage() { - return ( - <> - - - ); -} +export default LoginPage; diff --git a/WebServices/client-frontend/src/app/(AuthLayout)/auth/select/page.tsx b/WebServices/client-frontend/src/app/(AuthLayout)/auth/select/page.tsx index d079ba3..23f102f 100644 --- a/WebServices/client-frontend/src/app/(AuthLayout)/auth/select/page.tsx +++ b/WebServices/client-frontend/src/app/(AuthLayout)/auth/select/page.tsx @@ -5,38 +5,17 @@ import { retrieveUserType, } from "@/apicalls/cookies/token"; import { redirect } from "next/navigation"; -import LoginEmployee from "@/components/auth/LoginEmployee"; -import LoginOccupant from "@/components/auth/LoginOccupant"; +import Select from "@/webPages/auth/Select/page"; -async function SelectPage() { +const SelectPage = async () => { const token_is_valid = await checkAccessTokenIsValid(); const selection = await retrieveUserType(); - console.log("selection", selection); - const isEmployee = selection?.userType == "employee"; const isOccupant = selection?.userType == "occupant"; - const selectionList = selection?.selectionList; - if (!selectionList || !token_is_valid) { - redirect("/auth/login"); - } - - return ( - <> -
-
- {isEmployee && Array.isArray(selectionList) && ( - - )} - - {isOccupant && !Array.isArray(selectionList) && ( - - )} -
-
- - ); + if (!selectionList || !token_is_valid) { redirect("/auth/login") } + return -
- - - - - - + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/annual/meeting/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/annual/meeting/page.tsx index da8f2c9..ab99d27 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/annual/meeting/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/annual/meeting/page.tsx @@ -1,51 +1,27 @@ "use server"; import React from "react"; -import LeftMenu from "@/components/menu/leftMenu"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import { retrievePage } from "@/components/NavigatePages"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/annual/meeting"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePage(pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/annual/meeting", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
-

{activePage}

-
- -
-
-
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/area/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/area/page.tsx new file mode 100644 index 0000000..9173986 --- /dev/null +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/area/page.tsx @@ -0,0 +1,27 @@ +"use server"; +import React from "react"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; + +export default async function Dashboard({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | undefined }>; +}) { + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/build/area", + searchParams + }); + + return ( + + + + ); +} diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/page.tsx new file mode 100644 index 0000000..a45f7a3 --- /dev/null +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/buildings/page.tsx @@ -0,0 +1,27 @@ +"use server"; +import React from "react"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; + +export default async function Dashboard({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | undefined }>; +}) { + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/build", + searchParams + }); + + return ( + + + + ); +} diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx index ec00dc8..c353ae8 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/dashboard/page.tsx @@ -1,38 +1,27 @@ "use server"; import React from "react"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; -import ClientMenu from "@/components/menu/menu"; -import Header from "@/components/header/Header"; -import retrievePageByUrlAndPageId from "@/components/navigator/retriever"; - -export default async function DashboardLayout({ +export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const activePage = "/dashboard"; - const siteUrlsList = (await retrievePageList()) || []; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePageByUrlAndPageId(pageToDirect, activePage); - const searchParamsInstance = await searchParams; - const lang = (searchParamsInstance?.lang as "en" | "tr") || "en"; + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/dashboard", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Header Component */} -
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/close/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/close/page.tsx index c24cb00..3d69095 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/close/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/close/page.tsx @@ -1,51 +1,27 @@ "use server"; import React from "react"; -import LeftMenu from "@/components/menu/leftMenu"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import { retrievePage } from "@/components/NavigatePages"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/emergency/meeting/close"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePage(pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/emergency/meeting/close", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
-

{activePage}

-
- -
-
-
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/page.tsx index fdbab01..45a31b8 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/emergency/meeting/page.tsx @@ -1,51 +1,27 @@ "use server"; import React from "react"; -import LeftMenu from "@/components/menu/leftMenu"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import { retrievePage } from "@/components/NavigatePages"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/emergency/meeting"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePage(pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/emergency/meeting", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
-

{activePage}

-
- -
-
-
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx index 2c99949..320ecbc 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/individual/page.tsx @@ -8,20 +8,19 @@ export default async function Dashboard({ }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - // Use the enhanced dashboard hook to get all necessary data const { activePage, searchParamsInstance, lang, PageComponent, - siteUrlsList + siteUrlsList, } = await useDashboardPage({ pageUrl: "/individual", searchParams }); return ( - + ); 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 c1ae5aa..daee2a4 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/management/accounting/page.tsx @@ -1,37 +1,27 @@ "use server"; import React from "react"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import retrievePageByUrlAndPageId from "@/components/navigator/retriever"; -import ClientMenu from "@/components/menu/menu"; -import Header from "@/components/header/Header"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/management/accounting"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePageByUrlAndPageId(activePage, pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/management/accounting", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx index c9b5db1..aa763f4 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/management/budget/page.tsx @@ -1,37 +1,27 @@ "use server"; import React from "react"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import retrievePageByUrlAndPageId from "@/components/navigator/retriever"; -import ClientMenu from "@/components/menu/menu"; -import Header from "@/components/header/Header"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/management/budget"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePageByUrlAndPageId(activePage, pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/management/budget", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/meeting/participation/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/meeting/participation/page.tsx index 5fcda4a..d4fe465 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/meeting/participation/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/meeting/participation/page.tsx @@ -1,52 +1,27 @@ "use server"; import React from "react"; -import LeftMenu from "@/components/menu/leftMenu"; - -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import { retrievePage } from "@/components/NavigatePages"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/meeting/participation"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePage(pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/meeting/participation", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
-

{activePage}

-
- -
-
-
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/template/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/template/page.tsx index 9ada5ac..863e1ff 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/template/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/template/page.tsx @@ -1,6 +1,6 @@ "use server"; import React from "react"; -import Template from "@/components/Pages/template/app"; +import Template from "@/eventRouters/Pages/template/app"; import ClientMenu from "@/components/menu/menu"; import { retrievePage } from "@/components/NavigatePages"; import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/accounting/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/accounting/page.tsx new file mode 100644 index 0000000..bd798d1 --- /dev/null +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/accounting/page.tsx @@ -0,0 +1,27 @@ +"use server"; +import React from "react"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; + +export default async function Dashboard({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | undefined }>; +}) { + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/tenant/accounting", + searchParams + }); + + return ( + + + + ); +} \ No newline at end of file diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToBM/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToBM/page.tsx new file mode 100644 index 0000000..8312c0d --- /dev/null +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToBM/page.tsx @@ -0,0 +1,27 @@ +"use server"; +import React from "react"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; + +export default async function Dashboard({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | undefined }>; +}) { + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/tenant/messageToBM", + searchParams + }); + + return ( + + + + ); +} \ No newline at end of file diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToOwner/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToOwner/page.tsx new file mode 100644 index 0000000..627165a --- /dev/null +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/tenant/messageToOwner/page.tsx @@ -0,0 +1,27 @@ +"use server"; +import React from "react"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; + +export default async function Dashboard({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | undefined }>; +}) { + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/tenant/messageToOwner", + searchParams + }); + + return ( + + + + ); +} \ No newline at end of file diff --git a/WebServices/client-frontend/src/app/(DashboardLayout)/user/page.tsx b/WebServices/client-frontend/src/app/(DashboardLayout)/user/page.tsx index 772483e..69389a0 100644 --- a/WebServices/client-frontend/src/app/(DashboardLayout)/user/page.tsx +++ b/WebServices/client-frontend/src/app/(DashboardLayout)/user/page.tsx @@ -1,51 +1,27 @@ "use server"; import React from "react"; -import LeftMenu from "@/components/menu/leftMenu"; -import { retrievePageList, retrievePagebyUrl } from "@/apicalls/cookies/token"; -import { retrievePage } from "@/components/NavigatePages"; +import DashboardLayout from "@/components/layouts/DashboardLayout"; +import { useDashboardPage } from "@/components/common/hooks/useDashboardPage"; export default async function Dashboard({ searchParams, }: { searchParams: Promise<{ [key: string]: string | undefined }>; }) { - const siteUrlsList = (await retrievePageList()) || []; - const lang = "tr"; - const searchParamsInstance = await searchParams; - const activePage = "/user"; - const pageToDirect = await retrievePagebyUrl(activePage); - const PageComponent = retrievePage(pageToDirect); + const { + activePage, + searchParamsInstance, + lang, + PageComponent, + siteUrlsList, + } = await useDashboardPage({ + pageUrl: "/user", + searchParams + }); return ( - <> -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Sticky Header */} -
-

{activePage}

-
- -
-
-
- -
-
- + + + ); } diff --git a/WebServices/client-frontend/src/app/api/cookies/selection/route.ts b/WebServices/client-frontend/src/app/api/cookies/selection/route.ts new file mode 100644 index 0000000..2d37fe2 --- /dev/null +++ b/WebServices/client-frontend/src/app/api/cookies/selection/route.ts @@ -0,0 +1,20 @@ +import { retrieveUserSelection } from "@/apicalls/cookies/token"; +import { NextResponse } from "next/server"; + +export async function POST(): Promise { + try { + const userSelection = await retrieveUserSelection(); + console.log("userSelection", userSelection); + if (userSelection) { + return NextResponse.json({ + status: 200, + message: "User selection found", + data: userSelection, + }); + } + } catch (error) {} + return NextResponse.json({ + status: 500, + message: "User selection not found", + }); +} diff --git a/WebServices/client-frontend/src/app/api/login/email/route.ts b/WebServices/client-frontend/src/app/api/login/email/route.ts new file mode 100644 index 0000000..797b0e0 --- /dev/null +++ b/WebServices/client-frontend/src/app/api/login/email/route.ts @@ -0,0 +1,39 @@ +import { loginViaAccessKeys } from "@/apicalls/login/login"; +import { NextResponse } from "next/server"; +import { loginSchemaEmail } from "@/webPages/auth/Login/schemas"; + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + accessKey: body.email, + password: body.password, + rememberMe: body.rememberMe, + }; + const validatedLoginBody = loginSchemaEmail.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginViaAccessKeys(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Login successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/WebServices/client-frontend/src/app/api/selection/employee/route.ts b/WebServices/client-frontend/src/app/api/selection/employee/route.ts new file mode 100644 index 0000000..2e26a52 --- /dev/null +++ b/WebServices/client-frontend/src/app/api/selection/employee/route.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import { loginSelectEmployee } from "@/apicalls/login/login"; +import { NextResponse } from "next/server"; + +const loginSchemaEmployee = z.object({ + company_uu_id: z.string(), +}); + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + company_uu_id: body.company_uu_id, + }; + const validatedLoginBody = loginSchemaEmployee.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginSelectEmployee(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Selection successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/WebServices/client-frontend/src/app/api/selection/occupant/route.ts b/WebServices/client-frontend/src/app/api/selection/occupant/route.ts new file mode 100644 index 0000000..37901e9 --- /dev/null +++ b/WebServices/client-frontend/src/app/api/selection/occupant/route.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import { loginSelectOccupant } from "@/apicalls/login/login"; +import { NextResponse } from "next/server"; + +const loginSchemaOccupant = z.object({ + build_living_space_uu_id: z.string(), +}); + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + build_living_space_uu_id: body.build_living_space_uu_id, + }; + const validatedLoginBody = loginSchemaOccupant.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginSelectOccupant(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Selection successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/WebServices/client-frontend/src/app/page.tsx b/WebServices/client-frontend/src/app/page.tsx index 89ed8a0..64fc604 100644 --- a/WebServices/client-frontend/src/app/page.tsx +++ b/WebServices/client-frontend/src/app/page.tsx @@ -1,7 +1,7 @@ "use server"; +import Link from "next/link"; export default async function Home() { - // Server-side rendering const currentDate = new Date().toLocaleString("tr-TR", { timeZone: "Europe/Istanbul", }); @@ -19,70 +19,12 @@ export default async function Home() { {/* Login Section */}
-

- Login to Your Account -

- -
-
- - -
- -
- - -
- -
-
- - -
- - -
- - -
+ + Go to Sign In +
diff --git a/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx b/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx deleted file mode 100644 index 3c925ff..0000000 --- a/WebServices/client-frontend/src/components/auth/LoginEmployee.tsx +++ /dev/null @@ -1,146 +0,0 @@ -"use client"; -import React from "react"; -import { loginSelectEmployee } from "@/apicalls/login/login"; -import { useRouter } from "next/navigation"; -import { Company } from "./types"; - -interface LoginEmployeeProps { - selectionList: Company[]; - lang?: "en" | "tr"; - onSelect?: (uu_id: string) => void; -} - -// Language dictionary for internationalization -const languageDictionary = { - tr: { - companySelection: "Şirket Seçimi", - loggedInAs: "Çalışan olarak giriş yaptınız", - duty: "Görev", - id: "Kimlik", - noSelections: "Seçenek bulunamadı", - }, - en: { - companySelection: "Select your company", - loggedInAs: "You are logged in as an employee", - duty: "Duty", - id: "ID", - noSelections: "No selections available", - }, -}; - -function LoginEmployee({ - selectionList, - lang = "en", - onSelect, -}: LoginEmployeeProps) { - const t = languageDictionary[lang] || languageDictionary.en; - const router = useRouter(); - - const handleSelect = (uu_id: string) => { - console.log("Selected employee uu_id:", uu_id); - - // If an external onSelect handler is provided, use it - if (onSelect) { - onSelect(uu_id); - return; - } - - // Otherwise use the internal handler - loginSelectEmployee({ company_uu_id: uu_id }) - .then((responseData: any) => { - if (responseData?.status === 200 || responseData?.status === 202) { - router.push("/dashboard"); - } - }) - .catch((error) => { - console.error(error); - }); - }; - - return ( - <> -
{t.companySelection}
-
{t.loggedInAs}
- - {Array.isArray(selectionList) && selectionList.length === 0 && ( -
{t.noSelections}
- )} - - {Array.isArray(selectionList) && selectionList.length === 1 && ( -
-
-
- - {selectionList[0].public_name} - - {selectionList[0].company_type && ( - - {selectionList[0].company_type} - - )} -
- {selectionList[0].duty && ( -
- - {t.duty}: {selectionList[0].duty} - -
- )} -
- - - {t.id}: {selectionList[0].uu_id} - - -
-
- -
-
-
- )} - - {Array.isArray(selectionList) && - selectionList.length > 1 && - selectionList.map((item: Company, index: number) => ( -
handleSelect(item.uu_id)} - > -
-
- {item.public_name} - {item.company_type && ( - - {item.company_type} - - )} -
- {item.duty && ( -
- - {t.duty}: {item.duty} - -
- )} -
- - - {t.id}: {item.uu_id} - - -
-
-
- ))} - - ); -} - -export default LoginEmployee; diff --git a/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx b/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx deleted file mode 100644 index 493e27c..0000000 --- a/WebServices/client-frontend/src/components/auth/LoginOccupant.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; -import React from "react"; -import { loginSelectOccupant } from "@/apicalls/login/login"; -import { useRouter } from "next/navigation"; -import { BuildingMap } from "./types"; - -interface LoginOccupantProps { - selectionList: BuildingMap; - lang?: "en" | "tr"; -} - -// Language dictionary for internationalization -const languageDictionary = { - tr: { - occupantSelection: "Daire Seçimi", - loggedInAs: "Kiracı olarak giriş yaptınız", - buildingInfo: "Bina Bilgisi", - level: "Kat", - noSelections: "Seçenek bulunamadı", - }, - en: { - occupantSelection: "Select your occupant type", - loggedInAs: "You are logged in as an occupant", - buildingInfo: "Building Info", - level: "Level", - noSelections: "No selections available", - }, -}; - -function LoginOccupant({ - selectionList, - lang = "en" -}: LoginOccupantProps) { - const t = languageDictionary[lang] || languageDictionary.en; - const router = useRouter(); - - const handleSelect = (uu_id: string) => { - console.log("Selected occupant uu_id:", uu_id); - - loginSelectOccupant({ - build_living_space_uu_id: uu_id, - }) - .then((responseData: any) => { - if (responseData?.status === 200 || responseData?.status === 202) { - router.push("/dashboard"); - } - }) - .catch((error) => { - console.error(error); - }); - }; - - return ( - <> -
{t.occupantSelection}
-
- {t.loggedInAs} -
- {selectionList && Object.keys(selectionList).length > 0 ? ( - Object.keys(selectionList).map((buildKey: string) => { - const building = selectionList[buildKey]; - return ( -
-
-

- {t.buildingInfo}: - {building.build_name} - No: {building.build_no} -

-
- -
- {building.occupants.map((occupant: any, idx: number) => ( -
handleSelect(occupant.build_living_space_uu_id)} - > -
-
- - {occupant.description} - - - {occupant.code} - -
-
- - {occupant.part_name} - -
-
- - {t.level}: {occupant.part_level} - -
-
-
- ))} -
-
- ); - }) - ) : ( -
{t.noSelections}
- )} - - ); -} - -export default LoginOccupant; diff --git a/WebServices/client-frontend/src/components/auth/login.tsx b/WebServices/client-frontend/src/components/auth/login.tsx deleted file mode 100644 index f468d19..0000000 --- a/WebServices/client-frontend/src/components/auth/login.tsx +++ /dev/null @@ -1,152 +0,0 @@ -"use client"; -import { useState, useTransition } from "react"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { loginViaAccessKeys } from "@/apicalls/login/login"; -import { z } from "zod"; - -const loginSchema = z.object({ - email: z.string().email("Invalid email address"), - password: z.string().min(5, "Password must be at least 5 characters"), - remember_me: z.boolean().optional().default(false), -}); - -type LoginFormData = { - email: string; - password: string; - remember_me?: boolean; -}; - -function Login() { - // Open transition for form login - const [isPending, startTransition] = useTransition(); - const [error, setError] = useState(null); - const [jsonText, setJsonText] = useState(null); - - const Router = useRouter(); - - const { - register, - formState: { errors }, - handleSubmit, - } = useForm({ - resolver: zodResolver(loginSchema), - }); - - const onSubmit = async (data: LoginFormData) => { - try { - startTransition(() => { - try { - loginViaAccessKeys({ - accessKey: data.email, - password: data.password, - rememberMe: false, - }) - .then((result: any) => { - const dataResponse = result?.data; - if (dataResponse?.access_token) { - setJsonText(JSON.stringify(dataResponse)); - setTimeout(() => { - Router.push("/auth/select"); - }, 2000); - } - return dataResponse; - }) - .catch(() => {}); - } catch (error) {} - }); - } catch (error) { - console.error("Login error:", error); - setError("An error occurred during login"); - } - }; - return ( - <> -
-
-

- Login -

-
-
- - - {errors.email && ( -

- {errors.email.message} -

- )} -
- -
- - - {errors.password && ( -

- {errors.password.message} -

- )} -
- - {error && ( -
-

{error}

-
- )} - - -
-
- - {jsonText && ( -
-

- Response Data -

-
- {Object.entries(JSON.parse(jsonText)).map(([key, value]) => ( -
- {key}: - - {typeof value === "object" - ? JSON.stringify(value) - : value?.toString() || "N/A"} - -
- ))} -
-
- )} -
- - ); -} - -export default Login; diff --git a/WebServices/client-frontend/src/components/auth/select.tsx b/WebServices/client-frontend/src/components/auth/select.tsx deleted file mode 100644 index 4077251..0000000 --- a/WebServices/client-frontend/src/components/auth/select.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; -import React from "react"; -import { - loginSelectEmployee, - loginSelectOccupant, -} from "@/apicalls/login/login"; -import { useRouter } from "next/navigation"; -import LoginEmployee from "./LoginEmployee"; -import LoginOccupant from "./LoginOccupant"; -import { SelectListProps, Company, BuildingMap } from "./types"; - -function SelectList({ - selectionList, - isEmployee, - isOccupant, - lang = "en", -}: SelectListProps) { - const router = useRouter(); - - // Log the complete selectionList object and its structure - console.log("selectionList (complete):", selectionList); - console.log( - "selectionList (type):", - Array.isArray(selectionList) ? "Array" : "Object" - ); - - if (isEmployee && Array.isArray(selectionList)) { - console.log("Employee companies:", selectionList); - } else if (isOccupant && !Array.isArray(selectionList)) { - // Log each building and its occupants - Object.entries(selectionList).forEach(([buildingKey, building]) => { - console.log(`Building ${buildingKey}:`, building); - console.log(`Occupants for building ${buildingKey}:`, building.occupants); - }); - } - - const setSelectionHandler = (uu_id: string) => { - if (isEmployee) { - console.log("Selected isEmployee uu_id:", uu_id); - loginSelectEmployee({ company_uu_id: uu_id }) - .then((responseData: any) => { - if (responseData?.status === 200 || responseData?.status === 202) { - router.push("/dashboard"); - } - }) - .catch((error) => { - console.error(error); - }); - } else if (isOccupant) { - console.log("Selected isOccupant uu_id:", uu_id); - // For occupants, the uu_id is a composite of buildKey|partUuid - loginSelectOccupant({ - build_living_space_uu_id: uu_id, - }) - .then((responseData: any) => { - if (responseData?.status === 200 || responseData?.status === 202) { - router.push("/dashboard"); - } - }) - .catch((error) => { - console.error(error); - }); - } - }; - - return ( - <> - {isEmployee && Array.isArray(selectionList) && ( - - )} - - {isOccupant && !Array.isArray(selectionList) && ( - - )} - - ); -} - -export default SelectList; diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx new file mode 100644 index 0000000..2716947 --- /dev/null +++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CreateButton.tsx @@ -0,0 +1,25 @@ +"use client"; +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; + +interface CreateButtonProps { + onClick: () => void; + translations: Record; + lang: string; +} + +export const CreateButton: React.FC = ({ + onClick, + translations, + lang, +}) => { + const t = translations[lang] || {}; + + return ( + + ); +}; diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx new file mode 100644 index 0000000..2d86b8b --- /dev/null +++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/CustomButtonComponent.tsx @@ -0,0 +1,31 @@ +"use client"; +import React from "react"; +import { Button } from "@/components/ui/button"; +import { CustomButton } from "./types"; +import { cn } from "@/lib/utils"; + +interface CustomButtonComponentProps { + button: CustomButton; + isSelected: boolean; + onClick: () => void; +} + +export const CustomButtonComponent: React.FC = ({ + button, + isSelected, + onClick, +}) => { + return ( + + ); +}; diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts new file mode 100644 index 0000000..5b1e2de --- /dev/null +++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/index.ts @@ -0,0 +1,3 @@ +export * from './CreateButton'; +export * from './CustomButtonComponent'; +export * from './types'; diff --git a/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts new file mode 100644 index 0000000..270f86a --- /dev/null +++ b/WebServices/client-frontend/src/components/common/ActionButtonsDisplay/types.ts @@ -0,0 +1,17 @@ +import { ReactNode } from "react"; + +export interface CustomButton { + id: string; + label: string; + onClick: () => void; + variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"; + icon?: ReactNode; +} + +export interface ActionButtonsProps { + onCreateClick: () => void; + translations: Record; + lang: string; + customButtons?: CustomButton[]; + defaultSelectedButtonId?: string; +} diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx new file mode 100644 index 0000000..2dc2a49 --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardDisplay.tsx @@ -0,0 +1,74 @@ +"use client"; +import React from "react"; +import { CardItem } from "./CardItem"; +import { CardSkeleton } from "./CardSkeleton"; +import { getFieldValue, getGridClasses } from "./utils"; +import { CardDisplayProps } from "./schema"; +import { GridSize } from "../HeaderSelections/GridSelectionComponent"; + +export function CardDisplay({ + showFields, + data, + lang, + translations, + error, + loading, + titleField, + onCardClick, + renderCustomField, + gridCols = 4, + showViewIcon = false, + showUpdateIcon = false, + onViewClick, + onUpdateClick, + size = "lg", +}: CardDisplayProps) { + if (error) { + return ( +
+ {error.message || "An error occurred while fetching data."} +
+ ); + } + + return ( +
+ {loading ? ( + // Loading skeletons + Array.from({ length: 10 }).map((_, index) => ( + + )) + ) : data.length === 0 ? ( +
+ {translations[lang].noData || "No data found"} +
+ ) : ( + data.map((item: T, index: number) => ( + + )) + )} +
+ ); +} diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx new file mode 100644 index 0000000..d28e89e --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardItem.tsx @@ -0,0 +1,245 @@ +"use client"; +import React from "react"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Eye, Edit } from "lucide-react"; +import { CardItemProps, CardActionsProps, CardFieldProps } from "./schema"; + +export function CardItem({ + item, + index, + showFields, + titleField, + lang, + translations, + onCardClick, + renderCustomField, + showViewIcon, + showUpdateIcon, + onViewClick, + onUpdateClick, + getFieldValue, + size = "lg", +}: CardItemProps) { + + const getCardHeight = () => { + switch (size) { + case "xs": return "h-16 max-h-16"; + case "sm": return "h-20 max-h-20"; + case "md": return "h-24 max-h-24"; + case "lg": + default: return "h-full"; + } + }; + + const getCardStyle = () => { + switch (size) { + case "xs": return "!py-0 !gap-0 !flex !flex-col"; + case "sm": return "!py-1 !gap-1 !flex !flex-col"; + case "md": return "!py-2 !gap-2 !flex !flex-col"; + case "lg": + default: return ""; + } + }; + + const getTitleSize = () => { + switch (size) { + case "xs": return "text-xs"; + case "sm": return "text-sm"; + case "md": return "text-base"; + case "lg": + default: return "text-lg"; + } + }; + + const getContentPadding = () => { + switch (size) { + case "xs": return "p-1 py-1"; + case "sm": return "p-1 py-1"; + case "md": return "p-2 py-1"; + case "lg": + default: return "p-3"; + } + }; + + if (size === "xs" || size === "sm") { + return ( +
+
onCardClick(item) : undefined} + > + {showViewIcon && ( + + )} + {showUpdateIcon && ( + + )} +
+

{getFieldValue(item, titleField)}

+
+ {showFields.map((field) => ( + + ))} +
+
+
+
+ ); + } + + return ( +
+ onCardClick(item) : undefined} + > + +

{getFieldValue(item, titleField)}

+ +
+ +
+ {showFields.map((field) => ( + + ))} +
+
+
+
+ ); +} + +function CardActions({ + item, + showViewIcon, + showUpdateIcon, + onViewClick, + onUpdateClick, +}: CardActionsProps) { + if (!showViewIcon && !showUpdateIcon) return null; + + return ( +
+ {showViewIcon && ( + + )} + {showUpdateIcon && ( + + )} +
+ ); +} + +function CardField({ + item, + field, + lang, + translations, + renderCustomField, + getFieldValue, + size = "lg", +}: CardFieldProps) { + const getTextSize = () => { + switch (size) { + case "xs": return "text-xs"; + case "sm": return "text-xs"; + case "md": return "text-sm"; + case "lg": + default: return "text-base"; + } + }; + + const getLabelWidth = () => { + switch (size) { + case "xs": return "w-16"; + case "sm": return "w-20"; + case "md": return "w-24"; + case "lg": + default: return "w-32"; + } + }; + + if (renderCustomField) { + return renderCustomField(item, field); + } + + const label = translations?.[field]?.[lang] || field; + const value = getFieldValue(item, field); + + if (size === "xs" || size === "sm") { + return ( +
+ {label}: + {value} +
+ ); + } + + return ( +
+ {label}: + {value} +
+ ); +} diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx new file mode 100644 index 0000000..73c0620 --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/CardSkeleton.tsx @@ -0,0 +1,46 @@ +"use client"; +import React from "react"; +import { + Card, + CardContent, + CardHeader, +} from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import { CardSkeletonProps } from "./schema"; + +// Interface moved to schema.ts + +export function CardSkeleton({ + index, + showFields, + showViewIcon, + showUpdateIcon, +}: CardSkeletonProps) { + return ( +
+ + + +
+ {showViewIcon && ( + + )} + {showUpdateIcon && ( + + )} +
+
+ +
+ {showFields.map((field, fieldIndex) => ( +
+ + +
+ ))} +
+
+
+
+ ); +} diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx b/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx new file mode 100644 index 0000000..b3dba2c --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/index.tsx @@ -0,0 +1 @@ +export { CardDisplay } from './CardDisplay'; diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts b/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts new file mode 100644 index 0000000..5ff2294 --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/schema.ts @@ -0,0 +1,62 @@ +import { GridSize } from "../HeaderSelections/GridSelectionComponent"; + +export type CardSize = "xs" | "sm" | "md" | "lg"; + +export interface CardDisplayProps { + showFields: string[]; + data: T[]; + lang: string; + translations: Record; + error: Error | null; + loading: boolean; + titleField: string; + onCardClick?: (item: T) => void; + renderCustomField?: (item: T, field: string) => React.ReactNode; + gridCols?: number | GridSize; + showViewIcon?: boolean; + showUpdateIcon?: boolean; + onViewClick?: (item: T) => void; + onUpdateClick?: (item: T) => void; + size?: CardSize; +} + +export interface CardItemProps { + item: T; + index: number; + showFields: string[]; + titleField: string; + lang: string; + translations: Record; + onCardClick?: (item: T) => void; + renderCustomField?: (item: T, field: string) => React.ReactNode; + showViewIcon: boolean; + showUpdateIcon: boolean; + onViewClick?: (item: T) => void; + onUpdateClick?: (item: T) => void; + getFieldValue: (item: any, field: string) => any; + size?: CardSize; +} + +export interface CardActionsProps { + item: T; + showViewIcon: boolean; + showUpdateIcon: boolean; + onViewClick?: (item: T) => void; + onUpdateClick?: (item: T) => void; +} +export interface CardFieldProps { + item: T; + field: string; + lang: string; + translations: Record; + renderCustomField?: (item: T, field: string) => React.ReactNode; + getFieldValue: (item: any, field: string) => any; + size?: CardSize; +} + +export interface CardSkeletonProps { + index: number; + showFields: string[]; + showViewIcon: boolean; + showUpdateIcon: boolean; +} diff --git a/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts b/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts new file mode 100644 index 0000000..8f690cb --- /dev/null +++ b/WebServices/client-frontend/src/components/common/CardDisplay/utils.ts @@ -0,0 +1,39 @@ +export function getFieldValue(item: any, field: string): any { + if (!item) return ""; + if (field.includes(".")) { + const parts = field.split("."); + let value = item; + for (const part of parts) { + if (value === null || value === undefined) return ""; + value = value[part]; + } + return value; + } + return item[field]; +} + +export function getFieldLabel( + field: string, + translations: Record, + lang: string +): string { + const t = translations[lang] || {}; + return ( + t[field] || + field.charAt(0).toUpperCase() + field.slice(1).replace(/_/g, " ") + ); +} + +export function getGridClasses(gridCols: 1 | 2 | 3 | 4 | 5 | 6): string { + const baseClass = "grid grid-cols-1 gap-4"; + const colClasses: Record = { + 1: "", + 2: "sm:grid-cols-2", + 3: "sm:grid-cols-2 md:grid-cols-3", + 4: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4", + 5: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5", + 6: "sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6", + }; + + return `${baseClass} ${colClasses[gridCols]}`; +} diff --git a/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx new file mode 100644 index 0000000..1a339bf --- /dev/null +++ b/WebServices/client-frontend/src/components/common/FormDisplay/CreateComponent.tsx @@ -0,0 +1,303 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { CreateComponentProps, FieldDefinition } from "./types"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Checkbox } from "@/components/ui/checkbox"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { AlertCircle } from "lucide-react"; + +export function CreateComponent({ + refetch, + setMode, + setSelectedItem, + onCancel, + lang, + translations, + formProps = {}, + apiUrl, +}: CreateComponentProps) { + const t = translations[lang as keyof typeof translations] || {}; + + // Get field definitions from formProps if available + const fieldDefinitions = formProps.fieldDefinitions || {}; + const validationSchema = formProps.validationSchema; + + // Group fields by their group property + const [groupedFields, setGroupedFields] = useState>({}); + + // Process field definitions to group them + useEffect(() => { + if (Object.keys(fieldDefinitions).length > 0) { + const groups: Record = {}; + + // Group fields by their group property + Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => { + const def = definition as FieldDefinition; + if (!groups[def.group]) { + groups[def.group] = []; + } + groups[def.group].push({ ...def, name: fieldName }); + }); + + setGroupedFields(groups); + } + }, [fieldDefinitions]); + + // Initialize form with default values from field definitions + const defaultValues: Record = {}; + Object.entries(fieldDefinitions).forEach(([key, def]) => { + const fieldDef = def as FieldDefinition; + defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : ""; + }); + + // Setup form with validation schema if available + const { + register, + handleSubmit, + formState: { errors }, + setValue, + watch, + reset, + } = useForm>({ + defaultValues, + resolver: validationSchema ? zodResolver(validationSchema) : undefined, + }); + + const formValues = watch(); + + // Get language-specific validation schema if available + useEffect(() => { + if (formProps.schemaPath) { + const loadLanguageValidationSchema = async () => { + try { + // Dynamic import of the schema module + const schemaModule = await import(formProps.schemaPath); + + // Check if language-specific schema functions are available + if (schemaModule.getCreateApplicationSchema) { + const langValidationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr"); + + // Reset the form with the current values + reset(defaultValues); + + // Update the validation schema in formProps for future validations + formProps.validationSchema = langValidationSchema; + } + } catch (error) { + console.error("Error loading language-specific validation schema:", error); + } + }; + + loadLanguageValidationSchema(); + } + }, [lang, formProps.schemaPath, reset, defaultValues]); + + // Handle form submission + const onSubmit: SubmitHandler> = async (data) => { + try { + if (apiUrl) { + const createUrl = `${apiUrl}/create`; + const response = await fetch(createUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + console.log("Response:", response.ok); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + + const createdItem = await response.json(); + console.log("Created item:", createdItem); + } + + if (refetch) refetch(); + setMode("list"); + setSelectedItem(null); + } catch (error) { + console.error("Error saving form:", error); + } + }; + + // Handle select changes + const handleSelectChange = (name: string, value: string) => { + setValue(name, value); + }; + + // Handle checkbox changes + const handleCheckboxChange = (name: string, checked: boolean) => { + setValue(name, checked); + }; + + // Translate group names for display dynamically + const getGroupTitle = (groupName: string) => { + // Check if we have a translation for this group name + if (t[groupName]) { + return t[groupName]; + } + + // If no translation is found, just format the name as a fallback + const formattedName = groupName + .replace(/([A-Z])/g, ' $1') + .replace(/_/g, ' ') + .replace(/^./, (str) => str.toUpperCase()) + .replace(/\b\w/g, (c) => c.toUpperCase()); + return formattedName; + }; + + // Render a field based on its type + const renderField = (fieldName: string, field: FieldDefinition) => { + const errorMessage = errors[fieldName]?.message as string; + + switch (field.type) { + case "text": + return ( +
+ + + {errorMessage && ( +

{errorMessage}

+ )} +
+ ); + + case "textarea": + return ( +
+ +