turash/bugulma/frontend/services/discovery-api.ts
Damir Mukimov 673e8d4361
Some checks failed
CI/CD Pipeline / backend-lint (push) Failing after 31s
CI/CD Pipeline / backend-build (push) Has been skipped
CI/CD Pipeline / frontend-lint (push) Failing after 1m37s
CI/CD Pipeline / frontend-build (push) Has been skipped
CI/CD Pipeline / e2e-test (push) Has been skipped
fix: resolve all frontend lint errors (85 issues fixed)
- Replace all 'any' types with proper TypeScript interfaces
- Fix React hooks setState in useEffect issues with lazy initialization
- Remove unused variables and imports across all files
- Fix React Compiler memoization dependency issues
- Add comprehensive i18n translation keys for admin interfaces
- Apply consistent prettier formatting throughout codebase
- Clean up unused bulk editing functionality
- Improve type safety and code quality across frontend

Files changed: 39
- ImpactMetrics.tsx: Fixed any types and interfaces
- AdminVerificationQueuePage.tsx: Added i18n keys, removed unused vars
- LocalizationUIPage.tsx: Fixed memoization, added translations
- LocalizationDataPage.tsx: Added type safety and translations
- And 35+ other files with various lint fixes
2025-12-25 14:14:58 +01:00

275 lines
6.9 KiB
TypeScript

/**
* Discovery API Service
* Handles product, service, and community listing discovery/search
*/
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api/v1';
export interface SearchQuery {
query?: string;
categories?: string[];
latitude?: number;
longitude?: number;
radius_km?: number;
max_price?: number;
min_price?: number;
availability_status?: string;
tags?: string[];
limit?: number;
offset?: number;
}
export interface DiscoveryMatch {
product?: Product;
service?: Service;
community_listing?: CommunityListing;
match_type: 'product' | 'service' | 'community';
relevance_score: number;
text_match_score: number;
category_match_score: number;
distance_score: number;
price_match_score: number;
availability_score: number;
distance_km: number;
organization?: Organization;
site?: Site;
}
export interface Product {
ID: string;
Name: string;
Category: string;
Description: string;
UnitPrice: number;
MOQ: number;
AvailabilityStatus: string;
Images: string[];
Tags: string[];
OrganizationID: string;
SiteID?: string;
Location?: {
Latitude: number;
Longitude: number;
};
}
export interface Service {
ID: string;
Type: string;
Domain: string;
Description: string;
HourlyRate: number;
ServiceAreaKm: number;
AvailabilityStatus: string;
AvailabilitySchedule?: {
[day: string]: {
startTime: string;
endTime: string;
isAvailable: boolean;
};
};
Tags: string[];
OrganizationID: string;
SiteID?: string;
ServiceLocation?: {
Latitude: number;
Longitude: number;
};
}
export interface CommunityListing {
id: string;
user_id: string;
title: string;
description: string;
listing_type: 'product' | 'service' | 'tool' | 'skill' | 'need';
category: string;
price?: number;
price_type?: 'free' | 'sale' | 'rent' | 'trade' | 'borrow';
availability_status: string;
location?: {
latitude: number;
longitude: number;
};
images: string[];
tags: string[];
}
export interface Organization {
id: string;
name: string;
}
export interface Site {
id: string;
name: string;
latitude: number;
longitude: number;
}
export interface UniversalSearchResponse {
query: SearchQuery;
product_matches: DiscoveryMatch[];
service_matches: DiscoveryMatch[];
community_matches: DiscoveryMatch[];
total: number;
}
export interface SearchResponse {
matches: DiscoveryMatch[];
total: number;
}
/**
* Build query string from SearchQuery object
*/
function buildQueryString(params: SearchQuery): string {
const searchParams = new URLSearchParams();
if (params.query) searchParams.append('query', params.query);
if (params.categories) {
params.categories.forEach((cat) => searchParams.append('categories', cat));
}
if (params.latitude !== undefined) searchParams.append('latitude', params.latitude.toString());
if (params.longitude !== undefined) searchParams.append('longitude', params.longitude.toString());
if (params.radius_km !== undefined) searchParams.append('radius_km', params.radius_km.toString());
if (params.max_price !== undefined) searchParams.append('max_price', params.max_price.toString());
if (params.min_price !== undefined) searchParams.append('min_price', params.min_price.toString());
if (params.availability_status)
searchParams.append('availability_status', params.availability_status);
if (params.tags) {
params.tags.forEach((tag) => searchParams.append('tags', tag));
}
if (params.limit !== undefined) searchParams.append('limit', params.limit.toString());
if (params.offset !== undefined) searchParams.append('offset', params.offset.toString());
return searchParams.toString();
}
/**
* Universal search across products, services, and community listings
*/
export async function universalSearch(query: SearchQuery): Promise<UniversalSearchResponse> {
const queryString = buildQueryString(query);
const response = await fetch(`${API_BASE_URL}/discovery/search?${queryString}`);
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return response.json();
}
/**
* Search products
*/
export async function searchProducts(query: SearchQuery): Promise<SearchResponse> {
const queryString = buildQueryString(query);
const response = await fetch(`${API_BASE_URL}/discovery/products?${queryString}`);
if (!response.ok) {
throw new Error(`Product search failed: ${response.statusText}`);
}
return response.json();
}
/**
* Search services
*/
export async function searchServices(query: SearchQuery): Promise<SearchResponse> {
const queryString = buildQueryString(query);
const response = await fetch(`${API_BASE_URL}/discovery/services?${queryString}`);
if (!response.ok) {
throw new Error(`Service search failed: ${response.statusText}`);
}
return response.json();
}
/**
* Search community listings
*/
export async function searchCommunity(query: SearchQuery): Promise<SearchResponse> {
const queryString = buildQueryString(query);
const response = await fetch(`${API_BASE_URL}/discovery/community?${queryString}`);
if (!response.ok) {
throw new Error(`Community search failed: ${response.statusText}`);
}
return response.json();
}
/**
* Create community listing request/response types
*/
export interface CreateCommunityListingRequest {
title: string;
description?: string;
listing_type: 'product' | 'service' | 'tool' | 'skill' | 'need';
category: string;
subcategory?: string;
condition?: 'new' | 'like_new' | 'good' | 'fair' | 'needs_repair';
price?: number;
price_type?: 'free' | 'sale' | 'rent' | 'trade' | 'borrow';
quantity?: number;
service_type?: string;
rate?: number;
rate_type?: string;
latitude?: number;
longitude?: number;
pickup_available?: boolean;
delivery_available?: boolean;
delivery_radius_km?: number;
images?: string[];
tags?: string[];
}
/**
* Create a community listing
*/
export async function createCommunityListing(
listing: CreateCommunityListingRequest
): Promise<CommunityListing> {
const response = await fetch(`${API_BASE_URL}/discovery/community`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include auth cookies
body: JSON.stringify(listing),
});
if (!response.ok) {
throw new Error(`Failed to create community listing: ${response.statusText}`);
}
return response.json();
}
/**
* Get available categories for discovery search
*/
export interface CategoriesResponse {
products: string[];
services: string[];
community: string[];
}
export async function getCategories(): Promise<CategoriesResponse> {
const response = await fetch(`${API_BASE_URL}/discovery/categories`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch categories: ${response.statusText}`);
}
return response.json();
}