Files
tm_panel/src/app/tenders/[details]/page.tsx
T
AmirReza Jamali f080d51e63 feat(dashboard): integrate new dashboard queries and refactor closing soon component
- Replaced TTenderDetails with TDashboardClosingSoonItem in the ClosingSoonCard component for better type accuracy.
- Removed the fake-data.ts file as it is no longer needed with the new dashboard queries.
- Introduced new hooks for fetching dashboard data, including summary, trend, countries, notice types, and closing soon items.
- Updated useDashboardData to utilize the new dashboard queries, enhancing data retrieval and management.
- Enhanced the API service layer with new endpoints for dashboard functionalities, improving overall architecture.
2026-05-20 13:01:36 +03:30

171 lines
5.5 KiB
TypeScript

"use client";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Loading from "@/components/loading";
import {
useGetTenderDocumentsQuery,
useTenderDetailQuery,
useTranslateTenderMutation,
} from "@/hooks/queries";
import type { TTenderDetails } from "@/lib/api/types/Tenders";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { use, useCallback, useEffect, useMemo, useRef } from "react";
import { TenderDetailsContent } from "./components/tender-details-content";
import { TenderDetailsError } from "./components/tender-details-error";
import { TenderDetailsHeader } from "./components/tender-details-header";
import { useTenderSectionScrollSpy } from "./hooks/use-tender-section-scroll-spy";
import { getTenderNavItems } from "./tender-nav-utils";
import { shouldShowAiSummarySection } from "./tender-display-utils";
interface IProps {
params: Promise<{
details: string;
}>;
}
const TenderDetails = ({ params }: IProps) => {
const { details } = use(params);
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const selectedLanguage = searchParams.get("lang") || "en";
const { mutate: translateTender, isPending: isTranslating } =
useTranslateTenderMutation();
const { data, isLoading, isError, isFetching } = useTenderDetailQuery(
details,
{ language: selectedLanguage },
);
const breadcrumbItems = [
{ name: "Dashboard", href: "/" },
{ name: "Tenders", href: "/tenders" },
{ name: "Tender details", href: `/tenders/${details}` },
];
const tenderForNav = data?.data;
const hasValidAiSummary = tenderForNav
? shouldShowAiSummarySection(tenderForNav)
: false;
const { data: documentsResponse, isPending: isDocumentsLoading } =
useGetTenderDocumentsQuery(details, { enabled: hasValidAiSummary });
const documents = documentsResponse?.data ?? [];
const showDocumentsSection =
hasValidAiSummary && (isDocumentsLoading || documents.length > 0);
const tenderNavItems = useMemo(
() =>
tenderForNav
? getTenderNavItems(tenderForNav, {
showDocuments: showDocumentsSection,
})
: [],
[tenderForNav, showDocumentsSection],
);
const sectionIds = useMemo(
() => tenderNavItems.map((item) => item.id),
[tenderNavItems],
);
const activeSectionId = useTenderSectionScrollSpy(sectionIds);
// Only translate when the user explicitly picks a language from the dropdown.
// The ref is set inside handleLanguageChange and consumed by the effect once
// the GET refetch resolves; initial page loads never trigger a translate.
const userPendingLangRef = useRef<string | null>(null);
const attemptedRef = useRef<Set<string>>(new Set());
const handleLanguageChange = useCallback(
(event: React.ChangeEvent<HTMLSelectElement>) => {
const nextLanguage = event.target.value;
if (!nextLanguage) return;
const prevLanguage = searchParams.get("lang") || "en";
if (nextLanguage === prevLanguage) return;
userPendingLangRef.current = nextLanguage;
const nextParams = new URLSearchParams(searchParams.toString());
nextParams.set("lang", nextLanguage);
const queryString = nextParams.toString();
router.replace(queryString ? `${pathname}?${queryString}` : pathname);
},
[pathname, router, searchParams],
);
// ensureTranslation flow (per backend integration doc):
// 1) User picks a language → handleLanguageChange marks it as pending.
// 2) GET detail with ?language={selected} refetches.
// 3) If the refetched data.language doesn't match, POST ai-translate.
// The mutation's onSuccess invalidates the detail query → refetch binds title/description.
// attemptedRef prevents a retry loop if translation fails.
useEffect(() => {
const fetched = data?.data;
if (!fetched) return;
if (isFetching || isTranslating) return;
const pending = userPendingLangRef.current;
if (!pending || pending !== selectedLanguage) return;
if (fetched.language === selectedLanguage) {
userPendingLangRef.current = null;
return;
}
const key = `${details}::${selectedLanguage}`;
if (attemptedRef.current.has(key)) {
userPendingLangRef.current = null;
return;
}
attemptedRef.current.add(key);
userPendingLangRef.current = null;
translateTender({ id: details, language: selectedLanguage });
}, [
data,
details,
isFetching,
isTranslating,
selectedLanguage,
translateTender,
]);
if (isLoading) return <Loading />;
if (isError) {
return <TenderDetailsError breadcrumbItems={breadcrumbItems} />;
}
const tender: TTenderDetails = data!.data;
return (
<>
<Breadcrumb items={breadcrumbItems} />
<ShowcaseSection
rootClassName="overflow-clip"
title={
<TenderDetailsHeader
title={tender.title}
selectedLanguage={selectedLanguage}
onLanguageChange={handleLanguageChange}
isTranslating={isTranslating}
status={tender.status}
/>
}
className="!p-0"
>
<TenderDetailsContent
tender={tender}
tenderNavItems={tenderNavItems}
activeSectionId={activeSectionId}
tenderId={details}
documents={documents}
documentsLoading={isDocumentsLoading}
showDocumentsSection={showDocumentsSection}
/>
</ShowcaseSection>
</>
);
};
export default TenderDetails;