f080d51e63
- 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.
171 lines
5.5 KiB
TypeScript
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;
|