refactor(tender-details): enhance translation and summary features

- Integrated language selection and translation functionality in TenderDetails, allowing users to choose display language and translate tender metadata.
- Added AI summary section to display overall summary of the tender.
- Updated API service to support translation requests and handle responses effectively.
- Improved pagination handling in AdminsTable for better user experience.
- Refactored various components to ensure consistent data flow and state management.
This commit is contained in:
AmirReza Jamali
2026-05-10 08:44:03 +03:30
parent f48cb7e249
commit f01632e38d
14 changed files with 378 additions and 33 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
"use client";
import Loading from "@/components/loading";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import CreateAdminForm from "@/components/forms/admins/CreateAdmin";
import Loading from "@/components/loading";
import { useAdminDetails } from "@/hooks/queries";
import React, { use } from "react";
import { use } from "react";
const AdminEditPage = ({ params }: { params: Promise<{ id: string }> }) => {
const { id } = use(params);
+105 -17
View File
@@ -1,10 +1,16 @@
"use client";
import { LocationIcon } from "@/assets/icons";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Loading from "@/components/loading";
import Status from "@/components/ui/Status";
import { useTenderDetailQuery } from "@/hooks/queries";
import { Button } from "@/components/ui-elements/button";
import { Languages } from "@/constants/languages";
import {
useTenderDetailQuery,
useTranslateTenderMutation,
} from "@/hooks/queries";
import { useGetFlagQuery } from "@/hooks/queries/useFlagsQueries";
import { cn } from "@/lib/utils";
import {
@@ -14,6 +20,7 @@ import {
} from "@/utils/shared";
import getSymbolFromCurrency from "currency-symbol-map";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { use } from "react";
interface IProps {
@@ -64,7 +71,15 @@ const SHOWCASE_CENTERED_BODY = {
const TenderDetails = ({ params }: IProps) => {
const { details } = use(params);
const { data, isLoading, isError } = useTenderDetailQuery(details);
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, refetch } = useTenderDetailQuery(details, {
lang: selectedLanguage,
});
const countryCodeNormalized =
data?.data.country_code?.trim().toUpperCase() ?? "";
const hasValidCountryCodeForDisplay =
@@ -130,21 +145,71 @@ const TenderDetails = ({ params }: IProps) => {
{tender.title}
</h1>
</div>
<div className="flex shrink-0 flex-col gap-3 sm:flex-row sm:items-center sm:justify-end">
<Status status={tender.status ?? "Unknown"}>
{tender.status}
</Status>
{submissionUrl && (
<Link
href={submissionUrl}
target="_blank"
rel="noopener noreferrer"
className="shadow-theme-xs inline-flex items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-white transition hover:bg-primary/90 hover:shadow-md"
>
<span>Open submission</span>
<ExternalLinkIcon className="opacity-90" />
</Link>
)}
<div className="flex w-full min-w-0 shrink-0 flex-col gap-3 lg:max-w-2xl lg:items-end">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-dark-5 dark:text-dark-6 lg:w-full lg:text-end">
Language & translation
</p>
<div className="flex w-full flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-end">
<Select
name="lang"
label="Display language"
items={Languages}
className={cn(
"w-full shrink-0 space-y-0 sm:w-[min(100%,11.5rem)]",
"[&_label]:sr-only",
)}
value={selectedLanguage}
onChange={async (event) => {
const nextParams = new URLSearchParams(
searchParams.toString(),
);
const nextLanguage = event.target.value;
if (nextLanguage) {
nextParams.set("lang", nextLanguage);
} else {
nextParams.delete("lang");
}
const queryString = nextParams.toString();
router.replace(
queryString ? `${pathname}?${queryString}` : pathname,
);
}}
/>
<Button
type="button"
size="small"
shape="rounded"
className="w-full shrink-0 whitespace-nowrap sm:w-auto sm:min-h-[3.375rem]"
label={isTranslating ? "Translating..." : "Translate metadata"}
disabled={isTranslating}
onClick={() => {
translateTender(
{ id: details, language: selectedLanguage },
{
onSuccess: async () => {
await refetch();
},
},
);
}}
/>
<div className="flex shrink-0 items-center sm:min-h-[3.375rem]">
<Status status={tender.status ?? "Unknown"}>
{tender.status}
</Status>
</div>
{submissionUrl && (
<Link
href={submissionUrl}
target="_blank"
rel="noopener noreferrer"
className="shadow-theme-xs inline-flex min-h-[3.375rem] w-full shrink-0 items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-white transition hover:bg-primary/90 hover:shadow-md sm:w-auto"
>
<span>Open submission</span>
<ExternalLinkIcon className="opacity-90" />
</Link>
)}
</div>
</div>
</div>
}
@@ -332,6 +397,20 @@ const TenderDetails = ({ params }: IProps) => {
</div>
)}
<div>
<SectionHeading>Translation</SectionHeading>
<div className="grid gap-4 sm:grid-cols-2">
<div className="rounded-2xl border border-stroke/60 bg-gray-2/40 p-6 shadow-inner dark:border-dark-3 dark:bg-dark-3/30">
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-dark-5 dark:text-dark-6">
Resolved language
</p>
<p className="text-sm text-dark dark:text-white">
{tender.language || "original"}
</p>
</div>
</div>
</div>
<div>
<SectionHeading>Description</SectionHeading>
<div className="rounded-2xl border border-stroke/60 bg-gray-2/40 p-6 shadow-inner dark:border-dark-3 dark:bg-dark-3/30">
@@ -340,6 +419,15 @@ const TenderDetails = ({ params }: IProps) => {
</p>
</div>
</div>
<div>
<SectionHeading>AI summary</SectionHeading>
<div className="rounded-2xl border border-stroke/60 bg-gray-2/40 p-6 shadow-inner dark:border-dark-3 dark:bg-dark-3/30">
<p className="whitespace-pre-wrap text-sm leading-relaxed text-dark dark:text-white/90">
{tender.overall_summary || "Summary not ready yet"}
</p>
</div>
</div>
</div>
</ShowcaseSection>
</>