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:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
NEXT_PUBLIC_APP_VERSION=2.0.3
|
NEXT_PUBLIC_APP_VERSION=2.1.0
|
||||||
NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500
|
NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500
|
||||||
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0
|
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0
|
||||||
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=opplens-270d1.firebaseapp.com
|
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=opplens-270d1.firebaseapp.com
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ type TenderDetailsContentProps = {
|
|||||||
tenderId: string;
|
tenderId: string;
|
||||||
documents: TTenderDocumentsResponse[];
|
documents: TTenderDocumentsResponse[];
|
||||||
documentsLoading: boolean;
|
documentsLoading: boolean;
|
||||||
|
/** False once documents have loaded and the list is empty — section and nav are omitted. */
|
||||||
|
showDocumentsSection: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TenderDetailsContent({
|
export function TenderDetailsContent({
|
||||||
@@ -30,6 +32,7 @@ export function TenderDetailsContent({
|
|||||||
tenderId,
|
tenderId,
|
||||||
documents,
|
documents,
|
||||||
documentsLoading,
|
documentsLoading,
|
||||||
|
showDocumentsSection,
|
||||||
}: TenderDetailsContentProps) {
|
}: TenderDetailsContentProps) {
|
||||||
const showLocation = shouldShowLocationSection(tender);
|
const showLocation = shouldShowLocationSection(tender);
|
||||||
|
|
||||||
@@ -54,11 +57,13 @@ export function TenderDetailsContent({
|
|||||||
{showLocation && <TenderLocationSection tender={tender} />}
|
{showLocation && <TenderLocationSection tender={tender} />}
|
||||||
|
|
||||||
<TenderTranslationSection tender={tender} />
|
<TenderTranslationSection tender={tender} />
|
||||||
|
{showDocumentsSection && (
|
||||||
<TenderDocumentsSection
|
<TenderDocumentsSection
|
||||||
tenderId={tenderId}
|
tenderId={tenderId}
|
||||||
documents={documents}
|
documents={documents}
|
||||||
isLoading={documentsLoading}
|
isLoading={documentsLoading}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<TenderDescriptionSection tender={tender} />
|
<TenderDescriptionSection tender={tender} />
|
||||||
<TenderAiSummarySection tender={tender} />
|
<TenderAiSummarySection tender={tender} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,9 +41,20 @@ const TenderDetails = ({ params }: IProps) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const tenderForNav = data?.data;
|
const tenderForNav = data?.data;
|
||||||
|
const { data: documentsResponse, isPending: isDocumentsLoading } =
|
||||||
|
useGetTenderDocumentsQuery(details);
|
||||||
|
const documents = documentsResponse?.data ?? [];
|
||||||
|
const showDocumentsSection =
|
||||||
|
isDocumentsLoading || documents.length > 0;
|
||||||
|
|
||||||
const tenderNavItems = useMemo(
|
const tenderNavItems = useMemo(
|
||||||
() => (tenderForNav ? getTenderNavItems(tenderForNav) : []),
|
() =>
|
||||||
[tenderForNav],
|
tenderForNav
|
||||||
|
? getTenderNavItems(tenderForNav, {
|
||||||
|
showDocuments: showDocumentsSection,
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
[tenderForNav, showDocumentsSection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sectionIds = useMemo(
|
const sectionIds = useMemo(
|
||||||
@@ -51,8 +62,6 @@ const TenderDetails = ({ params }: IProps) => {
|
|||||||
[tenderNavItems],
|
[tenderNavItems],
|
||||||
);
|
);
|
||||||
const activeSectionId = useTenderSectionScrollSpy(sectionIds);
|
const activeSectionId = useTenderSectionScrollSpy(sectionIds);
|
||||||
const { data: documentsResponse, isPending: isDocumentsLoading } =
|
|
||||||
useGetTenderDocumentsQuery(details);
|
|
||||||
const handleLanguageChange = useCallback(
|
const handleLanguageChange = useCallback(
|
||||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const nextParams = new URLSearchParams(searchParams.toString());
|
const nextParams = new URLSearchParams(searchParams.toString());
|
||||||
@@ -108,8 +117,9 @@ const TenderDetails = ({ params }: IProps) => {
|
|||||||
tenderNavItems={tenderNavItems}
|
tenderNavItems={tenderNavItems}
|
||||||
activeSectionId={activeSectionId}
|
activeSectionId={activeSectionId}
|
||||||
tenderId={details}
|
tenderId={details}
|
||||||
documents={documentsResponse?.data ?? []}
|
documents={documents}
|
||||||
documentsLoading={isDocumentsLoading}
|
documentsLoading={isDocumentsLoading}
|
||||||
|
showDocumentsSection={showDocumentsSection}
|
||||||
/>
|
/>
|
||||||
</ShowcaseSection>
|
</ShowcaseSection>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,8 +18,12 @@ function analyzeLocationVisibility(tender: TTenderDetails) {
|
|||||||
return { showLocation };
|
return { showLocation };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTenderNavItems(tender: TTenderDetails): TenderNavItem[] {
|
export function getTenderNavItems(
|
||||||
|
tender: TTenderDetails,
|
||||||
|
options?: { showDocuments?: boolean },
|
||||||
|
): TenderNavItem[] {
|
||||||
const { showLocation } = analyzeLocationVisibility(tender);
|
const { showLocation } = analyzeLocationVisibility(tender);
|
||||||
|
const showDocuments = options?.showDocuments ?? true;
|
||||||
|
|
||||||
const items: TenderNavItem[] = [
|
const items: TenderNavItem[] = [
|
||||||
{ id: TENDER_SECTION_IDS.procurement, label: "Procurement" },
|
{ id: TENDER_SECTION_IDS.procurement, label: "Procurement" },
|
||||||
@@ -31,9 +35,11 @@ export function getTenderNavItems(tender: TTenderDetails): TenderNavItem[] {
|
|||||||
label: "Location & currency",
|
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(
|
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.description, label: "Description" },
|
||||||
{ id: TENDER_SECTION_IDS.aiSummary, label: "AI summary" },
|
{ id: TENDER_SECTION_IDS.aiSummary, label: "AI summary" },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ export const useAdminsPresenter = () => {
|
|||||||
setParams(newParams);
|
setParams(newParams);
|
||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
const { data, isPending } = useGetAdminListQuery({ ...params, ...apiDefaultParams });
|
const { data, isPending } = useGetAdminListQuery({
|
||||||
|
...apiDefaultParams,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
const { mutate: deleteAdmin } = useDeleteAdmin(() => {
|
const { mutate: deleteAdmin } = useDeleteAdmin(() => {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { GlobeIcon } from "@/assets/icons";
|
||||||
import DatePicker from "@/components/FormElements/DatePicker/DatePicker";
|
import DatePicker from "@/components/FormElements/DatePicker/DatePicker";
|
||||||
import InputGroup from "@/components/FormElements/InputGroup";
|
import InputGroup from "@/components/FormElements/InputGroup";
|
||||||
|
import { Select } from "@/components/FormElements/select";
|
||||||
import { Button } from "@/components/ui-elements/button";
|
import { Button } from "@/components/ui-elements/button";
|
||||||
|
import { Countries } from "@/constants/countries";
|
||||||
import {
|
import {
|
||||||
Control,
|
Control,
|
||||||
FieldValues,
|
FieldValues,
|
||||||
@@ -55,11 +58,16 @@ const TenderListFilters = ({
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputGroup
|
<Select<FieldValues>
|
||||||
{...filterRegister("country")}
|
{...filterRegister("country")}
|
||||||
name="country"
|
name="country"
|
||||||
label="country"
|
label="country"
|
||||||
type="text"
|
items={Countries}
|
||||||
|
placeholder="Country"
|
||||||
|
clearable
|
||||||
|
prefixIcon={<GlobeIcon />}
|
||||||
|
value={watch("country")}
|
||||||
|
onClear={() => setValue("country", "")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
|||||||
Reference in New Issue
Block a user