diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..9662b54 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g pnpm && pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm exec playwright test + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc5299a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..46116bc --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "nyxb.vscode-styled-colors" + ] +} \ No newline at end of file diff --git a/next-js/.eslintrc.js b/next-js/.eslintrc.js new file mode 100644 index 0000000..6c411cb --- /dev/null +++ b/next-js/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + extends: ["custom"], + globals: { + NODE_ENV: "readonly" + }, + env: { + node: true + }, + plugins: [ + 'import' + ], + rules: { + 'turbo/no-undeclared-env-vars': 'off', + 'import/named': 'error', + }, +}; + + +// module.exports = { +// // extends: ["eslint:recommended", "next"], +// extends: ["custom"], + +// // extends: ["next", "next/core-web-vitals"], +// }; + + diff --git a/next-js/.gitignore b/next-js/.gitignore new file mode 100644 index 0000000..8393d85 --- /dev/null +++ b/next-js/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env.production +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +/.examples/ +/public/copydb/ \ No newline at end of file diff --git a/next-js/.vscode/launch.json b/next-js/.vscode/launch.json new file mode 100644 index 0000000..b982cdc --- /dev/null +++ b/next-js/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "Attach to Chrome", + "port": 9222, + "request": "attach", + "type": "chrome", + "webRoot": "${workspaceFolder}" + }, + + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "cwd": "${workspaceFolder}/apps/web", + "serverReadyAction": { + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} diff --git a/next-js/.vscode/settings.json b/next-js/.vscode/settings.json new file mode 100644 index 0000000..3f4a09a --- /dev/null +++ b/next-js/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.exclude": { + ".next": true, + ".vercel": true, + ".vscode": true + } +} \ No newline at end of file diff --git a/next-js/README.md b/next-js/README.md new file mode 100644 index 0000000..498d162 --- /dev/null +++ b/next-js/README.md @@ -0,0 +1,18 @@ +# Configuration +- `pnpm install` to install dependencies +- `pnpm add next react react-dom` to add next.js and react dependencies in the Playwright test project +- `pnpm add react@canary react-dom@canary` to add the latest version of React and React DOM + + + +# Prisma with MongoDB +- `pnpx prisma generate` to generate the Prisma client +- `pnpx prisma studio` to open the Prisma Studio +- `pnpx prisma db push` Whenever you update your Prisma schema, you will need to run the + + + +# Clean up the cache and node_modules +pnpm store prune +pnpm rm --recursive node_modules +pnpm install diff --git a/next-js/app/api/index/route.ts b/next-js/app/api/index/route.ts new file mode 100644 index 0000000..dba6592 --- /dev/null +++ b/next-js/app/api/index/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; +import { getIndexReport } from "../../db/actions/getReportIndexAction"; + +export async function GET() { + const index = await getIndexReport(); + + return NextResponse.json({ + indexData: index, + }); +} diff --git a/next-js/app/api/reports/route.ts b/next-js/app/api/reports/route.ts new file mode 100644 index 0000000..34366c5 --- /dev/null +++ b/next-js/app/api/reports/route.ts @@ -0,0 +1,14 @@ +"use server"; + +import { NextResponse } from "next/server"; +import { getReportAction } from "../../db/actions/getReportAction"; + +export async function GET() { + const reports = await getReportAction(); + + // const reportName = reports?.filter((report: any) => report?.tests.some((test: any) => test.dirName === params.reportName)); + + return NextResponse.json({ + reports, + }); +} diff --git a/next-js/app/components/layout/Footer.tsx b/next-js/app/components/layout/Footer.tsx new file mode 100644 index 0000000..10220e0 --- /dev/null +++ b/next-js/app/components/layout/Footer.tsx @@ -0,0 +1,29 @@ +"use client"; + +import React from "react"; +import { Flex, Text, Box } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; +import Image from "next/image"; +import Link from "next/link"; + +const Footer = () => { + const year = new Date().getFullYear() || ""; + const isTabletSize = useMediaQuery("(max-width: 1039px)"); + + return ( + + + + {"TBardini + + + + © {year} + + + + + ); +}; + +export default Footer; diff --git a/next-js/app/components/layout/Navbar.tsx b/next-js/app/components/layout/Navbar.tsx new file mode 100644 index 0000000..e310924 --- /dev/null +++ b/next-js/app/components/layout/Navbar.tsx @@ -0,0 +1,139 @@ +"use client"; + +import React, { useRef, useEffect } from "react"; +import { usePathname } from "next/navigation"; +import { useAppContext } from "@/context"; +import { useMediaQuery, useDisclosure } from "@mantine/hooks"; +import Image from "next/image"; +import { Box, Flex, Text, Transition, Burger, Paper, Anchor } from "@mantine/core"; +import SelectPreviousReport from "../shared/SelectPreviousReport"; +import HistoryBanner from "@/app/components/shared/HistoryBanner"; +import Link from "next/link"; + +const Navbar = () => { + const pathname = usePathname(); + const { selectReportContext } = useAppContext(); + const [opened, { toggle: toggleDropdown, close: closeDropdown }] = useDisclosure(false); + + const isTabletSize = useMediaQuery("(max-width: 1039px)"); + + const dropdownRef = useRef(null); // Ref for the dropdown + const burgerRef = useRef(null); // Ref for the burger icon + + // Setup event listener for outside clicks + useEffect(() => { + // Specify the type for 'event' as 'MouseEvent' + const handleOutsideClick = (event: MouseEvent) => { + // Ensure the elements exist before calling 'contains' + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node) && burgerRef.current && !burgerRef.current.contains(event.target as Node)) { + closeDropdown(); + } + }; + + if (opened) { + document.addEventListener("mousedown", handleOutsideClick); + } + + // Cleanup the event listener + return () => { + document.removeEventListener("mousedown", handleOutsideClick); + }; + }, [opened, closeDropdown]); + + const isHistoryPage = pathname.endsWith("/e2e/history"); + const isReportPage = pathname.endsWith("/e2e") && !isHistoryPage; + + return ( + <> + + + + + + + + {"TBardini + + + DEMO REPORT + + + + {!isTabletSize && ( + + + {/* Link to the history page of the current report */} + {isReportPage && ( + + + History View + + + )} + {isHistoryPage && ( + + + Back to Report + + + )} + + + + + )} + {isTabletSize && ( + <> + + + + {(styles) => ( + + + + {isReportPage && ( + + + History View + + + )} + {isHistoryPage && ( + + + Back to Report + + + )} + + + )} + + + )} + + + + {selectReportContext !== 0 && ( + + + + )} + + + ); +}; + +export default Navbar; diff --git a/next-js/app/components/shared/ChangesDetected.tsx b/next-js/app/components/shared/ChangesDetected.tsx new file mode 100644 index 0000000..426ff72 --- /dev/null +++ b/next-js/app/components/shared/ChangesDetected.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState, useCallback } from "react"; +import { Text, Paper, Badge, Box, Title, Anchor, Accordion, Group } from "@mantine/core"; +import { ChangesDetectedProps, ChangesTestDetails } from "@/app/utils/interfaces"; +import Loading from "./Loading"; + +interface ChangesDetectedComponentProps { + testName: string | undefined; + compact?: boolean; + reportIdContextProps: string; +} + +const ChangesDetected = ({ reportIdContextProps, testName, compact }: ChangesDetectedComponentProps) => { + const [changes, setChanges] = useState(null); + const [loading, setLoading] = useState(true); + + const isCompact = compact ?? false; + + const loadChanges = useCallback(() => { + setChanges(null); // Reset changes + if (!reportIdContextProps || !testName) return; + + const fileName = `${testName}-changes-${reportIdContextProps}.json`; + setLoading(true); + fetch(`/data/${testName}/history/${fileName}`) + .then((res) => (res.ok ? res.json() : Promise.reject(res))) + .then((result) => setChanges(result)) + .catch((error) => { + console.error("Failed to load the JSON file ", error); + }) + .finally(() => setLoading(false)); + }, [reportIdContextProps, testName]); + + useEffect(() => { + loadChanges(); + }, [loadChanges]); + + if (loading) + return ( +
+ +
+ ); + + const changesDetected = changes?.changesDetected ?? []; + + const renderChanges = (changes: ChangesTestDetails[]) => { + return changes.map((change, index) => { + const { testId, title, newTest, reporter, previous, actual } = change.testDetails; + const itemBackground = index % 2 === 0 ? "#ffffff" : "#f0f0f0"; + + const textStyle = isCompact ? "xs" : "sm"; + const paddingStyle = isCompact ? "2px" : "10px"; + const boxMarginBottom = isCompact ? "md" : "md"; + const badgeVariant = isCompact ? "light" : "filled"; + + const changeContent = ( + + + + {testId} + {" "} + Reported by:{" "} + + {reporter} + {" "} + -{" "} + + {title} + + : + {!newTest && previous && ( + <> + + {previous.statusChange && ( + <> + Status changed from + + {previous.statusChange} + + to + {actual?.statusChange}. + + )} + {previous.messageChange && ( + <> + Description changed from + + {previous.messageChange} + + to + + {actual?.messageChange}. + + + )} + + + )} + {newTest && ( + + New test reported: + + {actual?.messageChange} . + + + )} + + + ); + return !isCompact ? ( + + {changeContent} + + ) : ( + changeContent + ); + }); + }; + + // Changes detected style + const backGroundColor = !isCompact ? "#f8f9fa" : "#ffffff"; + const paddingXStyle = !isCompact ? "10px" : "0"; + const paddingYStyle = !isCompact ? 2 : 0; + const boxShadowStyle = !isCompact ? "0px 1px 2px 0px rgba(0, 0, 0, 0.1)" : undefined; + const borderRadiusStyle = !isCompact ? "8px" : 0; + const marginStyle = !isCompact ? "sm" : 0; + const boxMarginBottom = isCompact ? "md" : "md"; + + // No changes detected style + const textStyle = isCompact ? "sm" : "sm"; + const paddingStyle = isCompact ? "sm" : "sm"; + const boxMargin = isCompact ? 0 : 0; + const paddingXYStyle = isCompact ? 0 : "xs"; + + return ( + <> + {changesDetected && changesDetected.length === 0 ? ( + + + + No changes have been detected from the previous test. + + + + ) : ( + + <> + + + + +
+ Changes Detected: {changes?.changesDetected ? changes.changesDetected.length : 0} + + Report id: {reportIdContextProps} + +
+
+
+ {renderChanges(changesDetected)} +
+
+ +
+ )} + + ); +}; + +export default ChangesDetected; diff --git a/next-js/app/components/shared/HistoryBanner.tsx b/next-js/app/components/shared/HistoryBanner.tsx new file mode 100644 index 0000000..3a3969e --- /dev/null +++ b/next-js/app/components/shared/HistoryBanner.tsx @@ -0,0 +1,74 @@ +"use client"; + +import React, { useEffect } from "react"; +import useSWR from "swr"; +import { Flex, Text, CloseButton } from "@mantine/core"; +import { IconXboxX } from "@tabler/icons-react"; +import { ApiResponse } from "../../utils/interfaces"; +import { useAppContext } from "@/context"; +import { useParams, useRouter } from "next/navigation"; + +interface HistoryBannerProps { + closeDropdown: () => void; +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()); + +const HistoryBanner: React.FC = ({ closeDropdown }) => { + const { data } = useSWR("/api/index", fetcher); + const { setSelectReportContext, setReportIdContext, reportIdContext } = useAppContext(); + + const router = useRouter(); + const params = useParams<{ reportId: string; reportName: string }>(); + + useEffect(() => { + if (params.reportId) { + setReportIdContext(params.reportId); + } + }, [params.reportId, setReportIdContext, setSelectReportContext, reportIdContext]); + + if (!data || !Array.isArray(data.indexData) || data.indexData.length === 0) { + return
No report data available
; + } + + const handleClose = () => { + setReportIdContext(data.indexData[0]?.reportId); + setSelectReportContext(0); + + // Logic to find the report name based on ID if necessary + const reportName = params.reportName; + // Refresh the URL with the new reportId and reportName + if (params.reportId && params.reportName) { + router.push(`/reports/${data.indexData[0]?.reportId}/${reportName}/e2e`); + } + + closeDropdown(); + }; + + return ( + +
+ + This is a Previous Report + +
+ + } /> +
+ ); +}; + +export default HistoryBanner; diff --git a/next-js/app/components/shared/ImageAccordionModal.tsx b/next-js/app/components/shared/ImageAccordionModal.tsx new file mode 100644 index 0000000..42c9985 --- /dev/null +++ b/next-js/app/components/shared/ImageAccordionModal.tsx @@ -0,0 +1,75 @@ +import React, { useState } from "react"; +import { useAppContext } from "@/context"; +import { useDisclosure } from "@mantine/hooks"; +import { Modal, Accordion, Button, Text, Popover } from "@mantine/core"; +import Image from "next/image"; +import { ImageAccordionModalProps } from "../../utils/interfaces"; +import Loading from "./Loading"; + +const ImageAccordionModal: React.FC = ({ imageUrl, message, title }) => { + const [opened, setOpened] = useState(false); + const [openedPopover, { close, open }] = useDisclosure(false); + const [imageLoaded, setImageLoaded] = useState(false); + + const { selectReportContext } = useAppContext(); + + return ( + <> + + +
+ +
+
+ + {selectReportContext === 0 ? "Click to view image" : "Cannot view images from previous reports."} + +
+ + setOpened(false)} + size='lg' + title={title} + centered + styles={{ + title: { + fontWeight: 700, + textAlign: "center", + fontSize: "1.2rem", + }, + inner: { + marginTop: "10rem", + }, + }} + > +
+ {!imageLoaded && ( +
+ +
+ )} + Screenshot setImageLoaded(true)} /> +
+ + + + {message} + + + +
+ + ); +}; + +export default ImageAccordionModal; diff --git a/next-js/app/components/shared/Loading.tsx b/next-js/app/components/shared/Loading.tsx new file mode 100644 index 0000000..19e67d6 --- /dev/null +++ b/next-js/app/components/shared/Loading.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { Loader, Center } from "@mantine/core"; + +function Loading() { + return ( +
+ +
+ ); +} + +export default Loading; diff --git a/next-js/app/components/shared/MissingTests.tsx b/next-js/app/components/shared/MissingTests.tsx new file mode 100644 index 0000000..ed09f4f --- /dev/null +++ b/next-js/app/components/shared/MissingTests.tsx @@ -0,0 +1,205 @@ +import React, { useEffect, useState } from "react"; +import { Accordion, Card, Table, TextInput, Title, Box, Badge, Flex } from "@mantine/core"; +import ImageAccordionModal from "./ImageAccordionModal"; +import { Report, Spec } from "../../utils/interfaces"; +import Loading from "./Loading"; + +const MissingTests = () => { + const [latestJsonData, setLatestJsonData] = useState(null); + const [previousJsonData, setPreviousJsonData] = useState(null); + const [currentReportId, setCurrentReportId] = useState(null); + const [previousReportId, setPreviousReportId] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [search, setSearch] = useState(""); + const [filteredTests, setFilteredTests] = useState([]); + + useEffect(() => { + fetch("/data/reportId.json") + .then((res) => { + if (!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }) + .then((data) => { + const numericId = parseInt(data.reportId, 10); + const adjustedReportNumericId = numericId - 1; + const adjustedReportId = adjustedReportNumericId.toString().padStart(3, "0"); + setCurrentReportId(adjustedReportId); // Current report id + const previousAdjustedNumericId = numericId - 2; + const previousAdjustedReportId = previousAdjustedNumericId.toString().padStart(3, "0"); + setPreviousReportId(previousAdjustedReportId); // Previous report id + }) + .catch((error) => { + console.error("Failed to load reportId.json", error); + setError(`Failed to load - fetch("/data/reportId.json"): ${error.message}`); + setLoading(false); + }); + }, []); + + useEffect(() => { + if (currentReportId) { + setLoading(true); + fetch(`/public/data/WordsServedEstimatePage/WordsServedEstimatePage-${currentReportId}.json`) + .then((res) => { + if (!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }) + .then( + (result) => { + setLatestJsonData(result); + setLoading(false); + }, + (error) => { + console.error("Failed to load the JSON file", error); + setError(`Failed to load - fetch(/public/data/WordsServedEstimatePage/WordsServedEstimatePage-${currentReportId}.json): ${error.message}`); + setLoading(false); + } + ); + } + }, [currentReportId]); + + useEffect(() => { + if (previousReportId) { + setLoading(true); + fetch(`/public/data/WordsServedEstimatePage/WordsServedEstimatePage-${previousReportId}.json`) + .then((res) => { + if (!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }) + .then( + (result) => { + setPreviousJsonData(result); + setLoading(false); + }, + (error) => { + console.error("Failed to load the JSON file", error); + setError("Failed to load data"); + setLoading(false); + } + ); + } + }, [previousReportId]); + + // This function compares two sets of tests and returns the ones that are missing + function findMissingTests(currentTests: Spec[], previousTests: Spec[]): Spec[] { + const currentTestIds = new Set(currentTests.map((test) => test.testId)); + return previousTests.filter((test) => !currentTestIds.has(test.testId)); + } + + // Define the missingTests array and populate it with the missing tests + let missingTests: Spec[] = []; + if (latestJsonData && previousJsonData) { + missingTests = findMissingTests(latestJsonData.specs, previousJsonData.specs); + } + + // useEffect(() => { + // if (search) { + // setFilteredTests( + // missingTests.filter( + // (test) => + // test.title.toLowerCase().includes(search.toLowerCase()) || + // test.testId.toLowerCase().includes(search.toLowerCase()) || + // test.status.toLowerCase().includes(search.toLowerCase()) || + // test.message.toLowerCase().includes(search.toLowerCase()) || + // test.jiraTicket.toLowerCase().includes(search.toLowerCase()) || + // test.reporter.toLowerCase().includes(search.toLowerCase()) + // ) + // ); + // } else { + // setFilteredTests(missingTests); + // } + // }, [latestJsonData, previousJsonData, search]); + + useEffect(() => { + let missingTests: Spec[] = []; + + if (latestJsonData && previousJsonData) { + missingTests = findMissingTests(latestJsonData.specs, previousJsonData.specs); + } + + if (search) { + setFilteredTests( + missingTests.filter( + (test) => + test.title.toLowerCase().includes(search.toLowerCase()) || + test.testId.toLowerCase().includes(search.toLowerCase()) || + test.status.toLowerCase().includes(search.toLowerCase()) || + test.message.toLowerCase().includes(search.toLowerCase()) || + test.jiraTicket.toLowerCase().includes(search.toLowerCase()) || + test.reporter.toLowerCase().includes(search.toLowerCase()) + ) + ); + } else { + setFilteredTests(missingTests); + } + }, [latestJsonData, previousJsonData, search]); + + + if (loading) return
; + if (error) return
Error WordsServedEstimatePage: {error}
; + + const accordionContent = ( + <> + + + + + Missing Tests: {missingTests.length} + + + + setSearch(event.currentTarget.value)} mb='md' style={{ width: "16rem" }} /> + + + + Id + Status + Title + Reporter + Jira Ref + Screenshot + Description + + + + {filteredTests.map((test, index) => ( + + {test?.testId} + + {test?.status} + + {test?.title} + {test?.reporter} + {test?.jiraTicket} + + + + {test.message} + + ))} + +
+
+
+
+ + ); + + return ( + <> + {missingTests.length > 0 && ( + + {accordionContent} + + )} + + ); +}; + +export default MissingTests; diff --git a/next-js/app/components/shared/PageHeader.tsx b/next-js/app/components/shared/PageHeader.tsx new file mode 100644 index 0000000..e61f293 --- /dev/null +++ b/next-js/app/components/shared/PageHeader.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { Flex, Text } from "@mantine/core"; + +const PageHeader = ({ slogan, title, dotColor, textColor, titleFontWeight }: { slogan: string; title: string; dotColor: string; textColor?: string; titleFontWeight?: number }) => ( + + + + + + {slogan} + + + {title} + + +); + +export default PageHeader; diff --git a/next-js/app/components/shared/SelectPreviousReport.tsx b/next-js/app/components/shared/SelectPreviousReport.tsx new file mode 100644 index 0000000..71e1a7d --- /dev/null +++ b/next-js/app/components/shared/SelectPreviousReport.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from "react"; +import useSWR from "swr"; +import { Select } from "@mantine/core"; +import Loading from "./Loading"; +import { useAppContext } from "@/context"; +import { ApiResponse } from "../../utils/interfaces"; +import { useParams, useRouter, usePathname } from "next/navigation"; + +interface SelectReportProps { + closeDropdown: () => void; +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()); + +const SelectPreviousReport: React.FC = ({ closeDropdown }) => { + const [isSelectorDisabled, setSelectorDisabled] = useState(false); + const { data, error, isLoading } = useSWR("/api/index", fetcher); + const { selectReportContext, setSelectReportContext, setReportIdContext, reportIdContext } = useAppContext(); + + const router = useRouter(); + const pathname = usePathname(); + + const params = useParams<{ reportId: string; reportName: string }>(); + const reportIdParam = params.reportId; + + // if the pathname includes '/history' then disable the selector + useEffect(() => { + const isHistoryPage = pathname.includes("/history"); + setSelectorDisabled(isHistoryPage); + + if (isHistoryPage && data?.indexData && data.indexData.length > 0) { + setSelectReportContext(0); + setReportIdContext(data.indexData[0].reportId); + router.push(`/reports/${data.indexData[0]?.reportId}/${params.reportName}/e2e/history`); + closeDropdown(); + } + }, [pathname, data, setReportIdContext, closeDropdown]); + + useEffect(() => { + if (reportIdParam && data?.indexData) { + const report = data.indexData.find((report) => report.reportId === reportIdParam); + if (report) { + setReportIdContext(reportIdParam); + } else { + setReportIdContext(data.indexData[0].reportId); + } + } + }, [reportIdParam, data, reportIdContext, selectReportContext]); + + if (error) return
Failed to load
; + if (isLoading) return ; + if (!data || !Array.isArray(data.indexData) || data.indexData.length === 0) { + return
No report data available
; + } + + const selectedReport = () => { + if (reportIdContext) { + return data.indexData.find((report) => report.reportId === reportIdContext); + } else { + return data.indexData[0]; + } + }; + + if (!selectedReport) { + console.error("Selected report is undefined"); + setReportIdContext(data.indexData[0].reportId); + setSelectReportContext(0); + return
Report not found
; + } + + const handleReportChange = (value: string | null) => { + if (value) { + // Find the index of the selected report so you can display Previous Reports Banner + const reportIndex = data.indexData.findIndex((report) => report.reportId === value); + setSelectReportContext(reportIndex); + + setReportIdContext(value); + + // Logic to find the report name based on ID if necessary + const reportName = params.reportName || "Report"; + + // Refresh the URL with the new reportId and reportName + if (params.reportId && params.reportName) { + router.push(`/reports/${value}/${reportName}/e2e`); + } + } + }; + + const defaultValue = `id: ${data.indexData[selectReportContext].reportId} - ${data.indexData[selectReportContext].reportDate.split(" - ")[0]}`; + return ( +
+