303 lines
7.8 KiB
TypeScript
303 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { serviceService, Service, ServiceListResponse, ServiceStats, ServiceSearchResponse } from '../api/serviceService';
|
|
|
|
// Hook for fetching all services
|
|
export const useServices = (params?: {
|
|
featured?: boolean;
|
|
category?: string;
|
|
min_price?: number;
|
|
max_price?: number;
|
|
search?: string;
|
|
ordering?: string;
|
|
page?: number;
|
|
}) => {
|
|
const [services, setServices] = useState<Service[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [pagination, setPagination] = useState<{
|
|
count: number;
|
|
next: string | null;
|
|
previous: string | null;
|
|
} | null>(null);
|
|
|
|
const fetchServices = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.getServices(params);
|
|
setServices(response.results);
|
|
setPagination({
|
|
count: response.count,
|
|
next: response.next,
|
|
previous: response.previous,
|
|
});
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch services');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [params]);
|
|
|
|
useEffect(() => {
|
|
fetchServices();
|
|
}, [fetchServices]);
|
|
|
|
return {
|
|
services,
|
|
loading,
|
|
error,
|
|
pagination,
|
|
refetch: fetchServices,
|
|
};
|
|
};
|
|
|
|
// Hook for fetching a single service by slug
|
|
export const useService = (slug: string) => {
|
|
const [service, setService] = useState<Service | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchService = useCallback(async () => {
|
|
if (!slug) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.getServiceBySlug(slug);
|
|
setService(response);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch service');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [slug]);
|
|
|
|
useEffect(() => {
|
|
fetchService();
|
|
}, [fetchService]);
|
|
|
|
return {
|
|
service,
|
|
loading,
|
|
error,
|
|
refetch: fetchService,
|
|
};
|
|
};
|
|
|
|
// Hook for fetching featured services
|
|
export const useFeaturedServices = () => {
|
|
const [services, setServices] = useState<Service[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchFeaturedServices = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.getFeaturedServices();
|
|
setServices(response.results);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch featured services');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchFeaturedServices();
|
|
}, [fetchFeaturedServices]);
|
|
|
|
return {
|
|
services,
|
|
loading,
|
|
error,
|
|
refetch: fetchFeaturedServices,
|
|
};
|
|
};
|
|
|
|
// Hook for searching services
|
|
export const useServiceSearch = (query: string) => {
|
|
const [results, setResults] = useState<Service[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [count, setCount] = useState(0);
|
|
|
|
const searchServices = useCallback(async (searchQuery: string) => {
|
|
if (!searchQuery.trim()) {
|
|
setResults([]);
|
|
setCount(0);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.searchServices(searchQuery);
|
|
setResults(response.results);
|
|
setCount(response.count);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to search services');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const timeoutId = setTimeout(() => {
|
|
searchServices(query);
|
|
}, 300); // Debounce search
|
|
|
|
return () => clearTimeout(timeoutId);
|
|
}, [query, searchServices]);
|
|
|
|
return {
|
|
results,
|
|
loading,
|
|
error,
|
|
count,
|
|
search: searchServices,
|
|
};
|
|
};
|
|
|
|
// Hook for fetching service statistics
|
|
export const useServiceStats = () => {
|
|
const [stats, setStats] = useState<ServiceStats | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchStats = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.getServiceStats();
|
|
setStats(response);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch service stats');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchStats();
|
|
}, [fetchStats]);
|
|
|
|
return {
|
|
stats,
|
|
loading,
|
|
error,
|
|
refetch: fetchStats,
|
|
};
|
|
};
|
|
|
|
// Hook for managing service state (for admin operations)
|
|
export const useServiceManagement = () => {
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const createService = useCallback(async (serviceData: Partial<Service>) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.createService(serviceData);
|
|
return response;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to create service');
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const updateService = useCallback(async (slug: string, serviceData: Partial<Service>) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.updateService(slug, serviceData);
|
|
return response;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to update service');
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const deleteService = useCallback(async (slug: string) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
await serviceService.deleteService(slug);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to delete service');
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const uploadServiceImage = useCallback(async (slug: string, imageFile: File) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await serviceService.uploadServiceImage(slug, imageFile);
|
|
return response;
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to upload service image');
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
return {
|
|
loading,
|
|
error,
|
|
createService,
|
|
updateService,
|
|
deleteService,
|
|
uploadServiceImage,
|
|
};
|
|
};
|
|
|
|
// Hook for navigation services (for header menu)
|
|
export const useNavigationServices = () => {
|
|
const [services, setServices] = useState<Service[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchNavigationServices = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
// Fetch only active services, ordered by display_order
|
|
const response = await serviceService.getServices({
|
|
ordering: 'display_order',
|
|
page: 1
|
|
});
|
|
// Filter only active services (handle null values as active)
|
|
const activeServices = response.results.filter(service => service.is_active !== false);
|
|
setServices(activeServices);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch navigation services');
|
|
// Set empty array on error to prevent navigation issues
|
|
setServices([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchNavigationServices();
|
|
}, [fetchNavigationServices]);
|
|
|
|
return {
|
|
services,
|
|
loading,
|
|
error,
|
|
refetch: fetchNavigationServices,
|
|
};
|
|
};
|