feat(tender-details): enhance document handling and navigation in tender details

- Added a new documents section to the tender details page, improving organization and visibility of related documents.
- Updated the tender navigation items to conditionally include the documents section based on the loading state and document availability.
- Refactored the TenderDetailsContent component to manage document loading states and conditionally render the documents section.
- Enhanced the getTenderNavItems utility to accept options for showing the documents section, improving flexibility in navigation rendering.
This commit is contained in:
AmirReza Jamali
2026-05-13 08:55:40 +03:30
parent d65f8cdf48
commit 2178f25af5
6 changed files with 49 additions and 17 deletions
@@ -21,6 +21,8 @@ type TenderDetailsContentProps = {
tenderId: string;
documents: TTenderDocumentsResponse[];
documentsLoading: boolean;
/** False once documents have loaded and the list is empty — section and nav are omitted. */
showDocumentsSection: boolean;
};
export function TenderDetailsContent({
@@ -30,6 +32,7 @@ export function TenderDetailsContent({
tenderId,
documents,
documentsLoading,
showDocumentsSection,
}: TenderDetailsContentProps) {
const showLocation = shouldShowLocationSection(tender);
@@ -54,11 +57,13 @@ export function TenderDetailsContent({
{showLocation && <TenderLocationSection tender={tender} />}
<TenderTranslationSection tender={tender} />
<TenderDocumentsSection
tenderId={tenderId}
documents={documents}
isLoading={documentsLoading}
/>
{showDocumentsSection && (
<TenderDocumentsSection
tenderId={tenderId}
documents={documents}
isLoading={documentsLoading}
/>
)}
<TenderDescriptionSection tender={tender} />
<TenderAiSummarySection tender={tender} />
</div>
+15 -5
View File
@@ -41,9 +41,20 @@ const TenderDetails = ({ params }: IProps) => {
];
const tenderForNav = data?.data;
const { data: documentsResponse, isPending: isDocumentsLoading } =
useGetTenderDocumentsQuery(details);
const documents = documentsResponse?.data ?? [];
const showDocumentsSection =
isDocumentsLoading || documents.length > 0;
const tenderNavItems = useMemo(
() => (tenderForNav ? getTenderNavItems(tenderForNav) : []),
[tenderForNav],
() =>
tenderForNav
? getTenderNavItems(tenderForNav, {
showDocuments: showDocumentsSection,
})
: [],
[tenderForNav, showDocumentsSection],
);
const sectionIds = useMemo(
@@ -51,8 +62,6 @@ const TenderDetails = ({ params }: IProps) => {
[tenderNavItems],
);
const activeSectionId = useTenderSectionScrollSpy(sectionIds);
const { data: documentsResponse, isPending: isDocumentsLoading } =
useGetTenderDocumentsQuery(details);
const handleLanguageChange = useCallback(
(event: React.ChangeEvent<HTMLSelectElement>) => {
const nextParams = new URLSearchParams(searchParams.toString());
@@ -108,8 +117,9 @@ const TenderDetails = ({ params }: IProps) => {
tenderNavItems={tenderNavItems}
activeSectionId={activeSectionId}
tenderId={details}
documents={documentsResponse?.data ?? []}
documents={documents}
documentsLoading={isDocumentsLoading}
showDocumentsSection={showDocumentsSection}
/>
</ShowcaseSection>
</>
@@ -18,8 +18,12 @@ function analyzeLocationVisibility(tender: TTenderDetails) {
return { showLocation };
}
export function getTenderNavItems(tender: TTenderDetails): TenderNavItem[] {
export function getTenderNavItems(
tender: TTenderDetails,
options?: { showDocuments?: boolean },
): TenderNavItem[] {
const { showLocation } = analyzeLocationVisibility(tender);
const showDocuments = options?.showDocuments ?? true;
const items: TenderNavItem[] = [
{ id: TENDER_SECTION_IDS.procurement, label: "Procurement" },
@@ -31,9 +35,11 @@ export function getTenderNavItems(tender: TTenderDetails): TenderNavItem[] {
label: "Location & currency",
});
}
items.push({ id: TENDER_SECTION_IDS.translation, label: "Translation" });
if (showDocuments) {
items.push({ id: TENDER_SECTION_IDS.documents, label: "Documents" });
}
items.push(
{ id: TENDER_SECTION_IDS.translation, label: "Translation" },
{ id: TENDER_SECTION_IDS.documents, label: "Documents" },
{ id: TENDER_SECTION_IDS.description, label: "Description" },
{ id: TENDER_SECTION_IDS.aiSummary, label: "AI summary" },
);
@@ -58,7 +58,10 @@ export const useAdminsPresenter = () => {
setParams(newParams);
filterFormReset(newParams);
}, [searchParams, filterFormReset]);
const { data, isPending } = useGetAdminListQuery({ ...params, ...apiDefaultParams });
const { data, isPending } = useGetAdminListQuery({
...apiDefaultParams,
...params,
});
const shouldShowPagination =
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
const { mutate: deleteAdmin } = useDeleteAdmin(() => {
@@ -1,6 +1,9 @@
import { GlobeIcon } from "@/assets/icons";
import DatePicker from "@/components/FormElements/DatePicker/DatePicker";
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { Button } from "@/components/ui-elements/button";
import { Countries } from "@/constants/countries";
import {
Control,
FieldValues,
@@ -55,11 +58,16 @@ const TenderListFilters = ({
type="text"
/>
<InputGroup
<Select<FieldValues>
{...filterRegister("country")}
name="country"
label="country"
type="text"
items={Countries}
placeholder="Country"
clearable
prefixIcon={<GlobeIcon />}
value={watch("country")}
onClear={() => setValue("country", "")}
/>
<InputGroup