diff --git a/src/app/(home)/_components/dashboard/closing-soon.tsx b/src/app/(home)/_components/dashboard/closing-soon.tsx
index c695c73..d0adbcb 100644
--- a/src/app/(home)/_components/dashboard/closing-soon.tsx
+++ b/src/app/(home)/_components/dashboard/closing-soon.tsx
@@ -1,14 +1,14 @@
"use client";
import { cn } from "@/lib/utils";
-import { TTenderDetails } from "@/lib/api";
+import { TDashboardClosingSoonItem } from "@/lib/api";
import gsap from "gsap";
import Link from "next/link";
import { useLayoutEffect, useRef } from "react";
import { ArrowRightIcon, ClockIcon, FireIcon } from "./icons";
type Props = {
- tenders: TTenderDetails[];
+ tenders: TDashboardClosingSoonItem[];
isLoading: boolean;
};
@@ -121,12 +121,10 @@ export function ClosingSoonCard({ tenders, isLoading }: Props) {
day: "numeric",
})
: "No deadline"}
- {t.buyer_organization?.name && (
+ {t.buyer_name && (
<>
•
-
- {t.buyer_organization.name}
-
+ {t.buyer_name}
>
)}
diff --git a/src/app/(home)/_components/dashboard/fake-data.ts b/src/app/(home)/_components/dashboard/fake-data.ts
deleted file mode 100644
index 648d2a5..0000000
--- a/src/app/(home)/_components/dashboard/fake-data.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { TTenderDetails } from "@/lib/api";
-
-const NOW = Math.floor(Date.now() / 1000);
-const DAY = 86_400;
-const HOUR = 3600;
-
-const COUNTRIES = [
- "DE",
- "FR",
- "IT",
- "ES",
- "NL",
- "BE",
- "PT",
- "PL",
- "SE",
- "AT",
- "AE",
- "GB",
-];
-
-const NOTICE_TYPES = [
- "CONTRACT_NOTICE",
- "PRIOR_INFORMATION",
- "CONTRACT_AWARD",
- "MODIFICATION",
- "CONCESSION",
- "DESIGN_CONTEST",
-];
-
-const FORM_TYPES = ["PLANNING", "COMPETITION", "RESULT", "DIRECTIVE"];
-
-const STATUSES = [
- "active",
- "active",
- "active",
- "active",
- "awarded",
- "awarded",
- "expired",
- "cancelled",
- "draft",
-];
-
-const BUYERS = [
- "Ministerio de Hacienda",
- "Comune di Milano",
- "City of Rotterdam",
- "Bundesministerium für Verkehr",
- "Région Île-de-France",
- "Stockholm Stad",
- "Generalitat de Catalunya",
- "Wiener Stadtwerke",
- "Servicio Andaluz de Salud",
- "Dubai Municipality",
- "Camara Municipal de Lisboa",
- "Polska Grupa Energetyczna",
- "Ayuntamiento de Madrid",
- "Deutsche Bahn AG",
- "Aéroports de Paris",
-];
-
-const TITLES = [
- "Construction of intermodal logistics hub",
- "Supply of energy-efficient LED street lighting",
- "Modernization of municipal water treatment plant",
- "IT consultancy for public sector cloud migration",
- "Renovation of historic city center facades",
- "Procurement of electric public transit buses",
- "Maintenance services for highway infrastructure",
- "Hospital medical equipment and consumables",
- "Smart city traffic monitoring system rollout",
- "Cybersecurity audit and penetration testing",
- "Educational software licenses for public schools",
- "Green roof installation across municipal buildings",
- "Catering services for government cafeterias",
- "Construction supervision of metro line extension",
- "Renewable energy plant feasibility study",
- "Digital archive platform for national library",
- "Waste collection and recycling vehicles supply",
- "Emergency response communications upgrade",
- "Cleaning and sanitation services framework",
- "Urban park redevelopment design contest",
-];
-
-const CURRENCIES = ["EUR", "EUR", "EUR", "USD", "GBP", "AED"];
-
-function rand(min: number, max: number) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
-}
-
-function pick(arr: T[]): T {
- return arr[rand(0, arr.length - 1)];
-}
-
-/**
- * Deterministic seed so the dashboard looks stable across reloads
- * within the same calendar day (titles/timestamps shuffle once per day).
- */
-function seededShuffle(arr: T[], seed: number) {
- const out = [...arr];
- let s = seed;
- for (let i = out.length - 1; i > 0; i--) {
- s = (s * 9301 + 49297) % 233280;
- const j = Math.floor((s / 233280) * (i + 1));
- [out[i], out[j]] = [out[j], out[i]];
- }
- return out;
-}
-
-export function buildFakeTenders(count = 80): TTenderDetails[] {
- const today = new Date();
- const seed =
- today.getFullYear() * 10000 +
- (today.getMonth() + 1) * 100 +
- today.getDate();
-
- const titlePool = seededShuffle(TITLES, seed);
- const buyerPool = seededShuffle(BUYERS, seed + 1);
-
- const tenders: TTenderDetails[] = [];
-
- for (let i = 0; i < count; i++) {
- const country = COUNTRIES[i % COUNTRIES.length];
- const isUrgentDeadline = i < 8;
- const isFuture = i < 30;
- const status = isUrgentDeadline ? "active" : pick(STATUSES);
-
- const created = NOW - rand(0, 14) * DAY - rand(0, 23) * HOUR;
- const published = created - rand(0, 2) * DAY;
- const submissionDeadline = isUrgentDeadline
- ? NOW + rand(2, 6) * HOUR + i * DAY * 0.3
- : isFuture
- ? NOW + rand(2, 21) * DAY
- : NOW - rand(1, 60) * DAY;
- const tenderDeadline = submissionDeadline + rand(0, 5) * DAY;
-
- tenders.push({
- id: `fake-${i + 1}`,
- notice_publication_id: `PUB-${100000 + i}`,
- tender_id: `T-${2026000 + i}`,
- title: titlePool[i % titlePool.length],
- description:
- "Demonstration tender record generated for the dashboard preview. Replace with backend data when the dashboard API is wired up.",
- procurement_type_code: pick(["WORKS", "SUPPLIES", "SERVICES"]),
- procedure_code: pick(["OPEN", "RESTRICTED", "NEGOTIATED"]),
- main_classification: "45000000",
- estimated_value: rand(50_000, 12_000_000),
- currency: pick(CURRENCIES),
- publication_date: published,
- tender_deadline: tenderDeadline,
- submission_deadline: submissionDeadline,
- application_deadline: submissionDeadline,
- submission_url: "https://example.com/tender",
- country_code: country,
- buyer_organization: {
- name: buyerPool[i % buyerPool.length],
- contact_email: "procurement@example.com",
- },
- status,
- form_type: pick(FORM_TYPES),
- notice_type_code: pick(NOTICE_TYPES),
- created_at: created,
- } as unknown as TTenderDetails);
- }
-
- return tenders;
-}
diff --git a/src/app/(home)/_components/dashboard/use-dashboard-data.ts b/src/app/(home)/_components/dashboard/use-dashboard-data.ts
index 5cf7eb8..9ad5569 100644
--- a/src/app/(home)/_components/dashboard/use-dashboard-data.ts
+++ b/src/app/(home)/_components/dashboard/use-dashboard-data.ts
@@ -1,24 +1,24 @@
"use client";
-import { useTendersQuery } from "@/hooks/queries";
+import {
+ useDashboardClosingSoonQuery,
+ useDashboardCountriesQuery,
+ useDashboardNoticeTypesQuery,
+ useDashboardSummaryQuery,
+ useDashboardTrendQuery,
+ useTendersQuery,
+} from "@/hooks/queries";
import { useMemo } from "react";
-import { buildFakeTenders } from "./fake-data";
-
-const NOW_SECONDS = () => Math.floor(Date.now() / 1000);
-const DAY = 86_400;
-
-/**
- * Toggle to `false` once the aggregate dashboard API documented in
- * `docs/backend/dashboard-api.md` is available — the hook will then
- * source summary/trend/country metrics from the backend instead of
- * the bundled demo set. The "Recent Tenders" list always uses the
- * real `useTendersQuery` source.
- */
-const USE_FAKE_AGGREGATES = true;
export type DashboardMetrics = ReturnType;
export function useDashboardData() {
+ const summaryQuery = useDashboardSummaryQuery();
+ const trendQuery = useDashboardTrendQuery({ days: 14, metric: "created" });
+ const countriesQuery = useDashboardCountriesQuery({ limit: 6 });
+ const noticeTypesQuery = useDashboardNoticeTypesQuery();
+ const closingSoonQuery = useDashboardClosingSoonQuery({ limit: 5 });
+
const {
data: recentData,
isPending: recentPending,
@@ -31,136 +31,88 @@ export function useDashboardData() {
limit: 5,
});
- const realRecent = recentData?.data?.tenders ?? [];
+ const summary = summaryQuery.data?.data;
+ const trendResponse = trendQuery.data?.data;
+ const countriesResponse = countriesQuery.data?.data;
+ const noticeTypesResponse = noticeTypesQuery.data?.data;
+ const closingSoonList = closingSoonQuery.data?.data?.items ?? [];
- const tenders = useMemo(
- () => (USE_FAKE_AGGREGATES ? buildFakeTenders(80) : []),
- [],
+ const isPending =
+ summaryQuery.isPending ||
+ trendQuery.isPending ||
+ countriesQuery.isPending ||
+ noticeTypesQuery.isPending ||
+ closingSoonQuery.isPending;
+
+ const isError =
+ summaryQuery.isError ||
+ trendQuery.isError ||
+ countriesQuery.isError ||
+ noticeTypesQuery.isError ||
+ closingSoonQuery.isError ||
+ recentError;
+
+ const refetch = () => {
+ summaryQuery.refetch();
+ trendQuery.refetch();
+ countriesQuery.refetch();
+ noticeTypesQuery.refetch();
+ closingSoonQuery.refetch();
+ refetchRecent();
+ };
+
+ const trend = useMemo(() => {
+ const series = trendResponse?.series ?? [];
+ return series.map((point) => {
+ const [y, m, d] = point.date.split("-").map(Number);
+ const date = new Date(Date.UTC(y, (m ?? 1) - 1, d ?? 1));
+ return {
+ date: point.date,
+ label: date.toLocaleDateString("en", {
+ month: "short",
+ day: "numeric",
+ }),
+ count: point.count,
+ };
+ });
+ }, [trendResponse]);
+
+ const countries = useMemo(
+ () =>
+ (countriesResponse?.items ?? []).map((item) => ({
+ code: item.country_code,
+ count: item.count,
+ })),
+ [countriesResponse],
);
- const isPending = USE_FAKE_AGGREGATES ? false : recentPending;
- const isError = recentError;
- const refetch = refetchRecent;
+ const noticeTypes = useMemo(
+ () =>
+ (noticeTypesResponse?.items ?? []).map((item) => ({
+ type: item.type,
+ count: item.count,
+ })),
+ [noticeTypesResponse],
+ );
- const derived = useMemo(() => {
- const now = NOW_SECONDS();
- const total = tenders.length;
-
- let active = 0;
- let closingSoon = 0;
- let awarded = 0;
- let totalValue = 0;
- let valueCurrency = "EUR";
-
- const countryCounts = new Map();
- const noticeTypeCounts = new Map();
- const dailyCounts = new Map();
-
- for (const t of tenders) {
- const status = (t.status || "").toLowerCase();
- if (status === "active") active += 1;
- if (status === "awarded") awarded += 1;
-
- const deadline = t.submission_deadline || t.tender_deadline || 0;
- if (deadline > now && deadline - now < 7 * DAY) closingSoon += 1;
-
- if (typeof t.estimated_value === "number" && t.estimated_value > 0) {
- totalValue += t.estimated_value;
- if (t.currency) valueCurrency = t.currency;
- }
-
- if (t.country_code) {
- countryCounts.set(
- t.country_code,
- (countryCounts.get(t.country_code) ?? 0) + 1,
- );
- }
-
- const noticeKey =
- t.form_type || t.notice_type_code || t.procurement_type_code || "OTHER";
- noticeTypeCounts.set(
- noticeKey,
- (noticeTypeCounts.get(noticeKey) ?? 0) + 1,
- );
-
- const created = t.created_at || t.publication_date;
- if (created) {
- const d = new Date(created * 1000);
- const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
- dailyCounts.set(key, (dailyCounts.get(key) ?? 0) + 1);
- }
- }
-
- const countries = Array.from(countryCounts.entries())
- .map(([code, count]) => ({ code, count }))
- .sort((a, b) => b.count - a.count);
-
- const noticeTypes = Array.from(noticeTypeCounts.entries())
- .map(([type, count]) => ({ type, count }))
- .sort((a, b) => b.count - a.count)
- .slice(0, 6);
-
- const trendDays = 14;
- const trend: { date: string; label: string; count: number }[] = [];
- for (let i = trendDays - 1; i >= 0; i--) {
- const d = new Date();
- d.setHours(0, 0, 0, 0);
- d.setDate(d.getDate() - i);
- const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
- trend.push({
- date: key,
- label: d.toLocaleDateString("en", { month: "short", day: "numeric" }),
- count: dailyCounts.get(key) ?? 0,
- });
- }
-
- const fakeRecent = [...tenders]
- .sort(
- (a, b) =>
- (b.created_at || b.publication_date || 0) -
- (a.created_at || a.publication_date || 0),
- )
- .slice(0, 5);
-
- const closingSoonList = tenders
- .filter((t) => {
- const dl = t.submission_deadline || t.tender_deadline || 0;
- return dl > now;
- })
- .sort(
- (a, b) =>
- (a.submission_deadline || a.tender_deadline || 0) -
- (b.submission_deadline || b.tender_deadline || 0),
- )
- .slice(0, 5);
-
- return {
- total,
- active,
- awarded,
- closingSoon,
- totalValue,
- valueCurrency,
- countries,
- noticeTypes,
- trend,
- fakeRecent,
- closingSoonList,
- };
- }, [tenders]);
-
- const recent = realRecent.length ? realRecent : derived.fakeRecent;
- const recentIsLoading = recentPending && realRecent.length === 0;
-
- const { fakeRecent: _ignored, ...rest } = derived;
+ const recent = recentData?.data?.tenders ?? [];
+ const recentIsLoading = recentPending && recent.length === 0;
return {
isPending,
isError,
refetch,
- tenders,
+ total: summary?.total_tenders ?? 0,
+ active: summary?.active_tenders ?? 0,
+ awarded: summary?.awarded_tenders ?? 0,
+ closingSoon: summary?.closing_soon ?? 0,
+ totalValue: summary?.total_estimated_value ?? 0,
+ valueCurrency: summary?.value_currency ?? "EUR",
+ countries,
+ noticeTypes,
+ trend,
recent,
recentIsLoading,
- ...rest,
+ closingSoonList,
};
}
diff --git a/src/app/tenders/[details]/page.tsx b/src/app/tenders/[details]/page.tsx
index a41ba50..7af2307 100644
--- a/src/app/tenders/[details]/page.tsx
+++ b/src/app/tenders/[details]/page.tsx
@@ -16,6 +16,7 @@ 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<{
@@ -43,11 +44,14 @@ const TenderDetails = ({ params }: IProps) => {
];
const tenderForNav = data?.data;
+ const hasValidAiSummary = tenderForNav
+ ? shouldShowAiSummarySection(tenderForNav)
+ : false;
const { data: documentsResponse, isPending: isDocumentsLoading } =
- useGetTenderDocumentsQuery(details);
+ useGetTenderDocumentsQuery(details, { enabled: hasValidAiSummary });
const documents = documentsResponse?.data ?? [];
const showDocumentsSection =
- isDocumentsLoading || documents.length > 0;
+ hasValidAiSummary && (isDocumentsLoading || documents.length > 0);
const tenderNavItems = useMemo(
() =>
diff --git a/src/components/FormElements/select.tsx b/src/components/FormElements/select.tsx
index 19d1ea6..a5da356 100644
--- a/src/components/FormElements/select.tsx
+++ b/src/components/FormElements/select.tsx
@@ -335,7 +335,7 @@ export function Select({
id={listboxId}
role="listbox"
aria-label={label}
- className="z-[200] max-h-[min(22rem,calc(100vh-6rem))] overflow-hidden rounded-xl border border-stroke/70 bg-white shadow-[0_18px_50px_-12px_rgba(15,23,42,0.25)] dark:border-white/10 dark:bg-dark-2 dark:shadow-[0_24px_60px_-12px_rgba(0,0,0,0.65)]"
+ className="z-[1000000] max-h-[min(22rem,calc(100vh-6rem))] overflow-hidden rounded-xl border border-stroke/70 bg-white shadow-[0_18px_50px_-12px_rgba(15,23,42,0.25)] dark:border-white/10 dark:bg-dark-2 dark:shadow-[0_24px_60px_-12px_rgba(0,0,0,0.65)]"
style={{
position: "fixed",
top: panelRect.top,
diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts
index 2993eef..ab221cc 100644
--- a/src/hooks/queries/index.ts
+++ b/src/hooks/queries/index.ts
@@ -15,3 +15,4 @@ export * from "./useTenders";
export * from "./useUsersQueries";
export * from "./useAdminListQuery";
export * from "./useSelectSearchQuery";
+export * from "./useDashboardQueries";
diff --git a/src/hooks/queries/useDashboardQueries.ts b/src/hooks/queries/useDashboardQueries.ts
new file mode 100644
index 0000000..0dca227
--- /dev/null
+++ b/src/hooks/queries/useDashboardQueries.ts
@@ -0,0 +1,62 @@
+import { API_ENDPOINTS, dashboardService } from "@/lib/api";
+import { useQuery } from "@tanstack/react-query";
+import { useMemo } from "react";
+
+export const useDashboardSummaryQuery = (params?: {
+ closing_window?: number;
+}) => {
+ const queryKey = useMemo(
+ () => [API_ENDPOINTS.DASHBOARD.SUMMARY, params],
+ [params],
+ );
+ return useQuery({
+ queryKey,
+ queryFn: () => dashboardService.summary(params),
+ });
+};
+
+export const useDashboardTrendQuery = (params?: {
+ days?: number;
+ metric?: "created" | "published" | "awarded";
+}) => {
+ const queryKey = useMemo(
+ () => [API_ENDPOINTS.DASHBOARD.TREND, params],
+ [params],
+ );
+ return useQuery({
+ queryKey,
+ queryFn: () => dashboardService.trend(params),
+ });
+};
+
+export const useDashboardCountriesQuery = (params?: { limit?: number }) => {
+ const queryKey = useMemo(
+ () => [API_ENDPOINTS.DASHBOARD.COUNTRIES, params],
+ [params],
+ );
+ return useQuery({
+ queryKey,
+ queryFn: () => dashboardService.countries(params),
+ });
+};
+
+export const useDashboardNoticeTypesQuery = () => {
+ return useQuery({
+ queryKey: [API_ENDPOINTS.DASHBOARD.NOTICE_TYPES],
+ queryFn: () => dashboardService.noticeTypes(),
+ });
+};
+
+export const useDashboardClosingSoonQuery = (params?: {
+ limit?: number;
+ window?: number;
+}) => {
+ const queryKey = useMemo(
+ () => [API_ENDPOINTS.DASHBOARD.CLOSING_SOON, params],
+ [params],
+ );
+ return useQuery({
+ queryKey,
+ queryFn: () => dashboardService.closingSoon(params),
+ });
+};
diff --git a/src/hooks/queries/useTenders.ts b/src/hooks/queries/useTenders.ts
index 7d3de6b..d390427 100644
--- a/src/hooks/queries/useTenders.ts
+++ b/src/hooks/queries/useTenders.ts
@@ -48,7 +48,10 @@ export const useTranslateTenderMutation = () => {
},
});
};
-export const useGetTenderDocumentsQuery = (id: string) => {
+export const useGetTenderDocumentsQuery = (
+ id: string,
+ options?: { enabled?: boolean },
+) => {
const queryKey = useMemo(
() => [API_ENDPOINTS.TENDERS.DOCUMENTS(id)],
[id],
@@ -56,5 +59,6 @@ export const useGetTenderDocumentsQuery = (id: string) => {
return useQuery({
queryKey,
queryFn: () => tendersService.getDocuments(id),
+ enabled: options?.enabled ?? true,
});
};
\ No newline at end of file
diff --git a/src/lib/api/axios.ts b/src/lib/api/axios.ts
index 23c6d8e..03c8642 100644
--- a/src/lib/api/axios.ts
+++ b/src/lib/api/axios.ts
@@ -10,7 +10,7 @@ import axios from "axios";
const api = axios.create({
baseURL: "/api/proxy/",
- timeout: 10000,
+ timeout: 60000,
headers: {
"Content-Type": "application/json",
},
diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts
index 4f9999c..61fe7ac 100644
--- a/src/lib/api/endpoints.ts
+++ b/src/lib/api/endpoints.ts
@@ -76,4 +76,13 @@ export const API_ENDPOINTS = {
BASE: (id?: string) => (id ? `inquiries/${id}` : "inquiries"),
STATUS: (id: string) => `inquiries/${id}/status`,
},
+
+ DASHBOARD: {
+ SUMMARY: "dashboard/summary",
+ TREND: "dashboard/trend",
+ COUNTRIES: "dashboard/countries",
+ NOTICE_TYPES: "dashboard/notice-types",
+ CLOSING_SOON: "dashboard/closing-soon",
+ RECENT: "dashboard/recent",
+ },
} as const;
diff --git a/src/lib/api/services/dashboard-service.ts b/src/lib/api/services/dashboard-service.ts
new file mode 100644
index 0000000..a4def45
--- /dev/null
+++ b/src/lib/api/services/dashboard-service.ts
@@ -0,0 +1,74 @@
+import api from "../axios";
+import { API_ENDPOINTS } from "../endpoints";
+import {
+ ApiResponse,
+ TDashboardClosingSoon,
+ TDashboardCountries,
+ TDashboardNoticeTypes,
+ TDashboardSummary,
+ TDashboardTrend,
+} from "../types";
+
+export const dashboardService = {
+ summary: async (params?: {
+ closing_window?: number;
+ }): Promise> => {
+ try {
+ return (await api.get(API_ENDPOINTS.DASHBOARD.SUMMARY, { params })).data;
+ } catch (error) {
+ console.error("ERROR caught in Dashboard Service => summary:", error);
+ throw error;
+ }
+ },
+
+ trend: async (params?: {
+ days?: number;
+ metric?: "created" | "published" | "awarded";
+ }): Promise> => {
+ try {
+ return (await api.get(API_ENDPOINTS.DASHBOARD.TREND, { params })).data;
+ } catch (error) {
+ console.error("ERROR caught in Dashboard Service => trend:", error);
+ throw error;
+ }
+ },
+
+ countries: async (params?: {
+ limit?: number;
+ }): Promise> => {
+ try {
+ return (await api.get(API_ENDPOINTS.DASHBOARD.COUNTRIES, { params })).data;
+ } catch (error) {
+ console.error("ERROR caught in Dashboard Service => countries:", error);
+ throw error;
+ }
+ },
+
+ noticeTypes: async (): Promise> => {
+ try {
+ return (await api.get(API_ENDPOINTS.DASHBOARD.NOTICE_TYPES)).data;
+ } catch (error) {
+ console.error(
+ "ERROR caught in Dashboard Service => notice-types:",
+ error,
+ );
+ throw error;
+ }
+ },
+
+ closingSoon: async (params?: {
+ limit?: number;
+ window?: number;
+ }): Promise> => {
+ try {
+ return (await api.get(API_ENDPOINTS.DASHBOARD.CLOSING_SOON, { params }))
+ .data;
+ } catch (error) {
+ console.error(
+ "ERROR caught in Dashboard Service => closing-soon:",
+ error,
+ );
+ throw error;
+ }
+ },
+};
diff --git a/src/lib/api/services/index.ts b/src/lib/api/services/index.ts
index 5149056..23d11a4 100644
--- a/src/lib/api/services/index.ts
+++ b/src/lib/api/services/index.ts
@@ -1,4 +1,5 @@
export * from "./companies-service";
+export * from "./dashboard-service";
export * from "./inquiries-service";
export * from "./profile-service";
export * from "./tenders-service";
diff --git a/src/lib/api/types/Dashboard.ts b/src/lib/api/types/Dashboard.ts
new file mode 100644
index 0000000..f239fd5
--- /dev/null
+++ b/src/lib/api/types/Dashboard.ts
@@ -0,0 +1,59 @@
+export type TDashboardSummary = {
+ total_tenders: number;
+ active_tenders: number;
+ awarded_tenders: number;
+ expired_tenders: number;
+ cancelled_tenders: number;
+ closing_soon: number;
+ total_estimated_value: number;
+ value_currency: string;
+ generated_at: number;
+};
+
+export type TDashboardTrendPoint = {
+ date: string;
+ count: number;
+};
+
+export type TDashboardTrend = {
+ metric: "created" | "published" | "awarded";
+ days: number;
+ series: TDashboardTrendPoint[];
+};
+
+export type TDashboardCountryItem = {
+ country_code: string;
+ count: number;
+};
+
+export type TDashboardCountries = {
+ total: number;
+ items: TDashboardCountryItem[];
+ other_count: number;
+};
+
+export type TDashboardNoticeTypeItem = {
+ type: string;
+ count: number;
+};
+
+export type TDashboardNoticeTypes = {
+ total: number;
+ items: TDashboardNoticeTypeItem[];
+};
+
+export type TDashboardClosingSoonItem = {
+ id: string;
+ title: string;
+ country_code: string;
+ buyer_name?: string;
+ submission_deadline: number;
+ tender_deadline?: number;
+ status: string;
+ estimated_value?: number;
+ currency?: string;
+};
+
+export type TDashboardClosingSoon = {
+ items: TDashboardClosingSoonItem[];
+};
diff --git a/src/lib/api/types/index.ts b/src/lib/api/types/index.ts
index 6a0ab72..6d02388 100644
--- a/src/lib/api/types/index.ts
+++ b/src/lib/api/types/index.ts
@@ -10,3 +10,4 @@ export * from "./TInquiries";
export * from "./TCms";
export * from "./Feedback";
export * from "./Tenders";
+export * from "./Dashboard";