Magnus Smari Smarason ab433a1d8d
Some checks failed
Build, Lint, and Deploy Arctic Species Portal / test-and-build (push) Failing after 1m6s
Build, Lint, and Deploy Arctic Species Portal / deploy (push) Has been skipped
Fix: Resolve ESLint errors and remove duplicate useToast hook
2025-05-17 21:46:31 +00:00

1296 lines
35 KiB
TypeScript

import { supabase } from './supabase';
import { Database } from '../types/supabase';
import { v4 as uuidv4 } from 'uuid';
export type Species = Database['public']['Tables']['species']['Row'] & {
common_names?: CommonName[];
primary_common_name?: string;
};
export type CommonName = Database['public']['Tables']['common_names']['Row'];
export type Subpopulation = Database['public']['Tables']['subpopulations']['Row'];
export type IucnAssessment = Database['public']['Tables']['iucn_assessments']['Row'];
// Update the type definition to match the actual database structure
export type CitesListing = Omit<Database['public']['Tables']['cites_listings']['Row'], 'listing_date'> & {
listing_date: string;
};
export type CitesTradeRecord = Database['public']['Tables']['cites_trade_records']['Row'];
export type TimelineEvent = Database['public']['Tables']['timeline_events']['Row'];
export type SpeciesDetails = Species & {
common_names: CommonName[];
subpopulations: Subpopulation[];
iucn_assessments: IucnAssessment[];
cites_listings: CitesListing[];
latest_assessment?: IucnAssessment;
current_cites_listing?: CitesListing;
};
// Manually define CatchRecord based on README as generated types seem outdated
export interface CatchRecord {
id: number; // Assuming BIGINT maps to number
species_id: string; // UUID
country: string | null;
year: number;
catch_total: number | null;
quota: number | null;
source: string | null;
}
export async function getAllSpecies() {
try {
console.log("Fetching all species from database...");
// First get all species
const { data: allSpecies, error: speciesError } = await supabase
.from('species')
.select('*')
.order('scientific_name');
if (speciesError) {
console.error("Error fetching species:", speciesError);
throw speciesError;
}
if (!allSpecies || allSpecies.length === 0) {
console.warn("No species found in database!");
return [];
}
console.log(`Fetched ${allSpecies.length} total species records`);
// Manually filter out duplicates by scientific_name
const uniqueSpeciesMap = new Map();
allSpecies.forEach(species => {
if (!uniqueSpeciesMap.has(species.scientific_name)) {
uniqueSpeciesMap.set(species.scientific_name, species);
}
});
const distinctSpecies = Array.from(uniqueSpeciesMap.values());
console.log(`Filtered to ${distinctSpecies.length} distinct species`);
// Then get common names for all species
const { data: commonNames, error: commonNamesError } = await supabase
.from('common_names')
.select('*')
.in('species_id', distinctSpecies.map(s => s.id));
if (commonNamesError) {
console.error("Error fetching common names:", commonNamesError);
throw commonNamesError;
}
console.log(`Fetched ${commonNames?.length || 0} common names`);
// Group common names by species_id
const commonNamesBySpecies = new Map();
commonNames?.forEach(cn => {
if (!commonNamesBySpecies.has(cn.species_id)) {
commonNamesBySpecies.set(cn.species_id, []);
}
commonNamesBySpecies.get(cn.species_id).push(cn);
});
// Transform the data to include primary_common_name
const transformedSpecies = distinctSpecies.map(species => ({
...species,
common_names: commonNamesBySpecies.get(species.id) || [],
primary_common_name: commonNamesBySpecies.get(species.id)?.[0]?.name || species.common_name || species.scientific_name
}));
return transformedSpecies;
} catch (error) {
console.error("Error in getAllSpecies:", error);
return []; // Return empty array instead of throwing to avoid breaking the UI
}
}
export async function getSpeciesById(id: string): Promise<SpeciesDetails | null> {
try {
// Get species basic info
const { data: species, error: speciesError } = await supabase
.from('species')
.select('*')
.eq('id', id)
.single();
if (speciesError) throw speciesError;
if (!species) return null;
// Get common names
const { data: commonNames, error: commonNamesError } = await supabase
.from('common_names')
.select('*')
.eq('species_id', id);
if (commonNamesError) throw commonNamesError;
// Get subpopulations
const { data: subpopulations, error: subpopulationsError } = await supabase
.from('subpopulations')
.select('*')
.eq('species_id', id);
if (subpopulationsError) throw subpopulationsError;
// Get IUCN assessments
const { data: iucnAssessments, error: iucnError } = await supabase
.from('iucn_assessments')
.select('*')
.eq('species_id', id)
.order('year_published', { ascending: false });
if (iucnError) throw iucnError;
// Approach 1: Just get all CITES listings without any filtering
console.log('Getting all CITES listings');
const { data: allListings, error: allListingsError } = await supabase
.from('cites_listings')
.select('*');
if (allListingsError) {
console.error('Error getting ALL listings:', allListingsError);
throw allListingsError;
}
console.log('All listings in database:', allListings);
// Now manually filter to this species
const citesListings = allListings.filter(listing =>
listing.species_id === id
);
console.log(`Found ${citesListings.length} listings for species ID ${id}:`, citesListings);
// Find latest assessment
const latestAssessment = iucnAssessments?.find(a => a.is_latest) ||
(iucnAssessments && iucnAssessments.length > 0 ? iucnAssessments[0] : undefined);
// Set current CITES listing
const currentCitesListing = citesListings.find(l => l.is_current) ||
(citesListings.length > 0 ? citesListings[0] : undefined);
// Construct the response
return {
...species,
common_names: commonNames || [],
subpopulations: subpopulations || [],
iucn_assessments: iucnAssessments || [],
cites_listings: citesListings,
latest_assessment: latestAssessment,
current_cites_listing: currentCitesListing,
};
} catch (error) {
console.error('Error in getSpeciesById:', error);
throw error;
}
}
/**
* Get the total count of CITES trade records
*/
export async function getTotalTradeCount() {
console.log('Fetching total trade count...');
try {
const { count, error } = await supabase
.from('cites_trade_records')
.select('*', { count: 'exact', head: true }); // Use head: true for count only
if (error) {
console.error('Error fetching total trade count:', error);
throw error;
}
console.log('Total trade count:', count);
return { count: count ?? 0 }; // Return count, defaulting to 0 if null
} catch (error) {
console.error('Error in getTotalTradeCount:', error);
return { count: 0 }; // Return 0 on error
}
}
export async function getTimelineEvents(speciesId: string) {
const { data, error } = await supabase
.from('timeline_events')
.select('*')
.eq('species_id', speciesId)
.not('event_type', 'eq', 'cites_trade')
.order('event_date', { ascending: false });
if (error) throw error;
return data as TimelineEvent[];
}
export async function getCitesTradeRecords(speciesId: string) {
try {
console.log('getCitesTradeRecords called with species ID:', speciesId);
// First get the species details to have the scientific name as fallback
const { data: speciesData, error: speciesError } = await supabase
.from('species')
.select('scientific_name, common_name, family, genus, species_name')
.eq('id', speciesId)
.single();
if (speciesError) {
console.error('Error fetching species info:', speciesError);
} else {
console.log('Found species for trade lookup:', speciesData);
}
console.log('Fetching all CITES trade records...');
// Initialize array to store all records
let allRecords: CitesTradeRecord[] = [];
let page = 0;
const pageSize = 1000;
let hasMore = true;
// Fetch records in batches until we have all of them
while (hasMore) {
const { data: records, error: directError } = await supabase
.from('cites_trade_records')
.select('*')
.eq('species_id', speciesId)
.order('year', { ascending: false })
.range(page * pageSize, (page + 1) * pageSize - 1);
if (directError) {
console.error('Error with direct species_id query:', directError);
break;
}
if (!records || records.length === 0) {
hasMore = false;
break;
}
allRecords = [...allRecords, ...records];
console.log(`Fetched batch ${page + 1}, total records so far: ${allRecords.length}`);
// If we got less than the page size, we've reached the end
if (records.length < pageSize) {
hasMore = false;
} else {
page++;
}
}
if (allRecords.length > 0) {
console.log(`Found total of ${allRecords.length} records with direct species_id query`);
console.log('First few records:', allRecords.slice(0, 3));
console.log('Year range:', Math.min(...allRecords.map(r => r.year)),
'to', Math.max(...allRecords.map(r => r.year)));
return allRecords;
}
// If no direct matches found, try the fallback approach
console.log('No records found with direct query, trying fallback matching...');
// Reset pagination for fallback approach
allRecords = [];
page = 0;
hasMore = true;
while (hasMore) {
const { data: records, error: fallbackError } = await supabase
.from('cites_trade_records')
.select('*')
.order('year', { ascending: false })
.range(page * pageSize, (page + 1) * pageSize - 1);
if (fallbackError) {
console.error('Error with fallback query:', fallbackError);
break;
}
if (!records || records.length === 0) {
hasMore = false;
break;
}
// Filter records for this batch
const filteredRecords = records.filter(record => {
// Try scientific name match with the taxon field
if (record.taxon &&
speciesData?.scientific_name &&
record.taxon.toLowerCase() === speciesData.scientific_name.toLowerCase()) {
return true;
}
// Try family match
if (record.family &&
speciesData?.family &&
record.family.toLowerCase() === speciesData.family.toLowerCase()) {
// For family matches, also check genus if available
if (record.genus &&
speciesData.genus &&
record.genus.toLowerCase() === speciesData.genus.toLowerCase()) {
return true;
}
}
return false;
});
allRecords = [...allRecords, ...filteredRecords];
console.log(`Fetched batch ${page + 1}, total filtered records so far: ${allRecords.length}`);
if (records.length < pageSize) {
hasMore = false;
} else {
page++;
}
}
if (allRecords.length > 0) {
console.log(`Found total of ${allRecords.length} trade records after filtering`);
console.log('Year range:', Math.min(...allRecords.map(r => r.year)),
'to', Math.max(...allRecords.map(r => r.year)));
}
// Sort by year descending
allRecords.sort((a, b) => b.year - a.year);
return allRecords;
} catch (error) {
console.error('Error in getCitesTradeRecords:', error);
return [];
}
}
// Function to search species by scientific or common name
export async function searchSpecies(query: string) {
if (!query || query.length < 3) return [];
// First search in scientific_name and common_name fields
const { data: speciesResults, error: speciesError } = await supabase
.from('species')
.select('*')
.or(`scientific_name.ilike.%${query}%,common_name.ilike.%${query}%`)
.limit(20);
if (speciesError) throw speciesError;
// Also search in common_names table
const { data: commonNamesResults, error: commonNamesError } = await supabase
.from('common_names')
.select('*, species!inner(*)')
.ilike('name', `%${query}%`)
.limit(20);
if (commonNamesError) throw commonNamesError;
// Combine the results, removing duplicates
const speciesMap = new Map<string, Species>();
if (speciesResults) {
for (const species of speciesResults) {
speciesMap.set(species.id, species);
}
}
if (commonNamesResults) {
for (const result of commonNamesResults) {
if (result.species) {
speciesMap.set(result.species.id, result.species);
}
}
}
return Array.from(speciesMap.values());
}
export async function updateSpeciesImage(speciesId: string, imageUrl: string) {
const { error } = await supabase
.from('species')
.update({ default_image_url: imageUrl })
.eq('id', speciesId);
if (error) {
console.error('Error updating species image:', error);
}
}
export async function getSpeciesImages(scientificName: string) {
try {
console.log('Fetching images for species:', scientificName);
// First try to get from iNaturalist API
const response = await fetch(
`https://api.inaturalist.org/v1/taxa?q=${encodeURIComponent(scientificName)}&order=desc&order_by=observations_count`
);
const data = await response.json();
if (data.results && data.results[0] && data.results[0].default_photo) {
console.log('Found image from iNaturalist:', data.results[0].default_photo);
return {
url: data.results[0].default_photo.medium_url,
attribution: data.results[0].default_photo.attribution,
license: data.results[0].default_photo.license_code
};
}
console.log('No image found for species:', scientificName);
return null;
} catch (error) {
console.error('Error fetching species images:', error);
return null;
}
}
// CRUD functions for CITES listings
export async function createCitesListing(listing: Omit<Database['public']['Tables']['cites_listings']['Insert'], 'id'>) {
try {
const newListing = {
...listing,
id: uuidv4()
};
console.log('Creating new CITES listing:', newListing);
const { data, error } = await supabase
.from('cites_listings')
.insert(newListing)
.select()
.single();
if (error) {
console.error('Error creating CITES listing:', error);
throw error;
}
// If this is set as current, update other listings to not be current
if (newListing.is_current) {
await updateCitesListingsCurrent(newListing.species_id, newListing.id);
}
return data;
} catch (error) {
console.error('Error in createCitesListing:', error);
throw error;
}
}
export async function updateCitesListing(id: string, updates: Database['public']['Tables']['cites_listings']['Update']) {
try {
console.log(`Updating CITES listing ${id}:`, updates);
// First get the current listing to check if we need to update current status
const { data: currentListing, error: fetchError } = await supabase
.from('cites_listings')
.select('species_id')
.eq('id', id)
.single();
if (fetchError) {
console.error('Error fetching CITES listing for update:', fetchError);
throw fetchError;
}
// Update the listing
const { data, error } = await supabase
.from('cites_listings')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating CITES listing:', error);
throw error;
}
// If this is set as current, update other listings to not be current
if (updates.is_current && currentListing) {
await updateCitesListingsCurrent(currentListing.species_id, id);
}
return data;
} catch (error) {
console.error('Error in updateCitesListing:', error);
throw error;
}
}
export async function deleteCitesListing(id: string) {
try {
console.log(`Deleting CITES listing ${id}`);
// First get the current listing to check if we need to update current status
const { data: currentListing, error: fetchError } = await supabase
.from('cites_listings')
.select('species_id, is_current')
.eq('id', id)
.single();
if (fetchError) {
console.error('Error fetching CITES listing for deletion:', fetchError);
throw fetchError;
}
// Delete the listing
const { error } = await supabase
.from('cites_listings')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting CITES listing:', error);
throw error;
}
// If this was the current listing, set another one as current
if (currentListing && currentListing.is_current) {
const { data: remainingListings, error: listingsError } = await supabase
.from('cites_listings')
.select('id')
.eq('species_id', currentListing.species_id)
.order('listing_date', { ascending: false })
.limit(1);
if (listingsError) {
console.error('Error fetching remaining listings:', listingsError);
} else if (remainingListings && remainingListings.length > 0) {
await updateCitesListing(remainingListings[0].id, { is_current: true });
}
}
return true;
} catch (error) {
console.error('Error in deleteCitesListing:', error);
throw error;
}
}
// Helper function to update current status of CITES listings
async function updateCitesListingsCurrent(speciesId: string, currentId: string) {
try {
console.log(`Setting listing ${currentId} as current for species ${speciesId}`);
const { error } = await supabase
.from('cites_listings')
.update({ is_current: false })
.eq('species_id', speciesId)
.neq('id', currentId);
if (error) {
console.error('Error updating CITES listings current status:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in updateCitesListingsCurrent:', error);
throw error;
}
}
// CRUD functions for timeline events
export async function createTimelineEvent(event: Omit<Database['public']['Tables']['timeline_events']['Insert'], 'id'>) {
try {
const newEvent = {
...event,
id: uuidv4()
};
console.log('Creating new timeline event:', newEvent);
const { data, error } = await supabase
.from('timeline_events')
.insert(newEvent)
.select()
.single();
if (error) {
console.error('Error creating timeline event:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in createTimelineEvent:', error);
throw error;
}
}
export async function updateTimelineEvent(id: string, updates: Database['public']['Tables']['timeline_events']['Update']) {
try {
console.log(`Updating timeline event ${id}:`, updates);
const { data, error } = await supabase
.from('timeline_events')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating timeline event:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in updateTimelineEvent:', error);
throw error;
}
}
export async function deleteTimelineEvent(id: string) {
try {
console.log(`Deleting timeline event ${id}`);
const { error } = await supabase
.from('timeline_events')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting timeline event:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in deleteTimelineEvent:', error);
throw error;
}
}
// CRUD functions for IUCN assessments
export async function createIucnAssessment(assessment: Omit<Database['public']['Tables']['iucn_assessments']['Insert'], 'id'>) {
try {
const newAssessment = {
...assessment,
id: uuidv4()
};
console.log('Creating new IUCN assessment:', newAssessment);
const { data, error } = await supabase
.from('iucn_assessments')
.insert(newAssessment)
.select()
.single();
if (error) {
console.error('Error creating IUCN assessment:', error);
throw error;
}
// If this is set as latest, update other assessments to not be latest
if (newAssessment.is_latest) {
await updateIucnAssessmentsLatest(newAssessment.species_id, newAssessment.id);
}
return data;
} catch (error) {
console.error('Error in createIucnAssessment:', error);
throw error;
}
}
export async function updateIucnAssessment(id: string, updates: Database['public']['Tables']['iucn_assessments']['Update']) {
try {
console.log(`Updating IUCN assessment ${id}:`, updates);
// First get the current assessment to check if we need to update latest status
const { data: currentAssessment, error: fetchError } = await supabase
.from('iucn_assessments')
.select('species_id')
.eq('id', id)
.single();
if (fetchError) {
console.error('Error fetching IUCN assessment for update:', fetchError);
throw fetchError;
}
// Update the assessment
const { data, error } = await supabase
.from('iucn_assessments')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating IUCN assessment:', error);
throw error;
}
// If this is set as latest, update other assessments to not be latest
if (updates.is_latest && currentAssessment) {
await updateIucnAssessmentsLatest(currentAssessment.species_id, id);
}
return data;
} catch (error) {
console.error('Error in updateIucnAssessment:', error);
throw error;
}
}
export async function deleteIucnAssessment(id: string) {
try {
console.log(`Deleting IUCN assessment ${id}`);
// First get the current assessment to check if we need to update latest status
const { data: currentAssessment, error: fetchError } = await supabase
.from('iucn_assessments')
.select('species_id, is_latest')
.eq('id', id)
.single();
if (fetchError) {
console.error('Error fetching IUCN assessment for deletion:', fetchError);
throw fetchError;
}
// Delete the assessment
const { error } = await supabase
.from('iucn_assessments')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting IUCN assessment:', error);
throw error;
}
// If this was the latest assessment, set another one as latest
if (currentAssessment && currentAssessment.is_latest) {
const { data: remainingAssessments, error: assessmentsError } = await supabase
.from('iucn_assessments')
.select('id')
.eq('species_id', currentAssessment.species_id)
.order('year_published', { ascending: false })
.limit(1);
if (assessmentsError) {
console.error('Error fetching remaining assessments:', assessmentsError);
} else if (remainingAssessments && remainingAssessments.length > 0) {
await updateIucnAssessment(remainingAssessments[0].id, { is_latest: true });
}
}
return true;
} catch (error) {
console.error('Error in deleteIucnAssessment:', error);
throw error;
}
}
// Helper function to update latest status of IUCN assessments
async function updateIucnAssessmentsLatest(speciesId: string, latestId: string) {
try {
console.log(`Setting assessment ${latestId} as latest for species ${speciesId}`);
const { error } = await supabase
.from('iucn_assessments')
.update({ is_latest: false })
.eq('species_id', speciesId)
.neq('id', latestId);
if (error) {
console.error('Error updating IUCN assessments latest status:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in updateIucnAssessmentsLatest:', error);
throw error;
}
}
// New API functions to support the enhanced species detail page
/**
* Get species distribution and range data
*/
export async function getSpeciesDistribution(speciesId: string) {
console.log('Fetching distribution data for species:', speciesId);
try {
const { data, error } = await supabase
.from('distribution_ranges')
.select('*')
.eq('species_id', speciesId)
.order('region');
if (error) {
console.error('Error fetching distribution ranges:', error);
throw error;
}
return data || [];
} catch (error) {
console.error('Error in getSpeciesDistribution:', error);
return [];
}
}
/**
* Create a new distribution range entry
*/
export async function createDistributionRange(distribution: {
species_id: string;
region: string;
presence_code: string;
origin_code: string;
seasonal_code?: string;
geojson?: Record<string, unknown> | null;
notes?: string;
}) {
try {
const newDistribution = {
...distribution,
id: uuidv4(),
created_at: new Date().toISOString()
};
console.log('Creating new distribution range:', newDistribution);
const { data, error } = await supabase
.from('distribution_ranges')
.insert(newDistribution)
.select()
.single();
if (error) {
console.error('Error creating distribution range:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in createDistributionRange:', error);
throw error;
}
}
/**
* Update an existing distribution range entry
*/
export async function updateDistributionRange(id: string, updates: {
region?: string;
presence_code?: string;
origin_code?: string;
seasonal_code?: string;
geojson?: Record<string, unknown> | null;
notes?: string;
}) {
try {
console.log(`Updating distribution range ${id}:`, updates);
const { data, error } = await supabase
.from('distribution_ranges')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating distribution range:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in updateDistributionRange:', error);
throw error;
}
}
/**
* Delete a distribution range entry
*/
export async function deleteDistributionRange(id: string) {
try {
console.log(`Deleting distribution range ${id}`);
const { error } = await supabase
.from('distribution_ranges')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting distribution range:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in deleteDistributionRange:', error);
throw error;
}
}
/**
* Get threats for a specific species
*/
export async function getSpeciesThreats(speciesId: string) {
console.log('Fetching threats data for species:', speciesId);
try {
const { data, error } = await supabase
.from('species_threats')
.select('*')
.eq('species_id', speciesId)
.order('threat_type');
if (error) {
console.error('Error fetching species threats:', error);
throw error;
}
return data || [];
} catch (error) {
console.error('Error in getSpeciesThreats:', error);
return [];
}
}
/**
* Create a new species threat entry
*/
export async function createSpeciesThreat(threat: {
species_id: string;
threat_type: string;
threat_code: string;
severity?: string;
scope?: string;
timing?: string;
description?: string;
}) {
try {
const newThreat = {
...threat,
id: uuidv4(),
created_at: new Date().toISOString()
};
console.log('Creating new species threat:', newThreat);
const { data, error } = await supabase
.from('species_threats')
.insert(newThreat)
.select()
.single();
if (error) {
console.error('Error creating species threat:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in createSpeciesThreat:', error);
throw error;
}
}
/**
* Update an existing species threat entry
*/
export async function updateSpeciesThreat(id: string, updates: {
threat_type?: string;
threat_code?: string;
severity?: string;
scope?: string;
timing?: string;
description?: string;
}) {
try {
console.log(`Updating species threat ${id}:`, updates);
const { data, error } = await supabase
.from('species_threats')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating species threat:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in updateSpeciesThreat:', error);
throw error;
}
}
/**
* Delete a species threat entry
*/
export async function deleteSpeciesThreat(id: string) {
try {
console.log(`Deleting species threat ${id}`);
const { error } = await supabase
.from('species_threats')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting species threat:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in deleteSpeciesThreat:', error);
throw error;
}
}
/**
* Get conservation measures for a specific species
*/
export async function getConservationMeasures(speciesId: string) {
console.log('Fetching conservation measures for species:', speciesId);
try {
const { data, error } = await supabase
.from('conservation_measures')
.select('*')
.eq('species_id', speciesId)
.order('measure_type');
if (error) {
console.error('Error fetching conservation measures:', error);
throw error;
}
return data || [];
} catch (error) {
console.error('Error in getConservationMeasures:', error);
return [];
}
}
/**
* Create a new conservation measure entry
*/
export async function createConservationMeasure(measure: {
species_id: string;
measure_type: string;
measure_code: string;
status?: string;
implementing_organizations?: string[];
start_date?: string;
end_date?: string;
description?: string;
effectiveness?: string;
}) {
try {
const newMeasure = {
...measure,
id: uuidv4(),
created_at: new Date().toISOString()
};
console.log('Creating new conservation measure:', newMeasure);
const { data, error } = await supabase
.from('conservation_measures')
.insert(newMeasure)
.select()
.single();
if (error) {
console.error('Error creating conservation measure:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in createConservationMeasure:', error);
throw error;
}
}
/**
* Update an existing conservation measure entry
*/
export async function updateConservationMeasure(id: string, updates: {
measure_type?: string;
measure_code?: string;
status?: string;
implementing_organizations?: string[];
start_date?: string;
end_date?: string;
description?: string;
effectiveness?: string;
}) {
try {
console.log(`Updating conservation measure ${id}:`, updates);
const { data, error } = await supabase
.from('conservation_measures')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) {
console.error('Error updating conservation measure:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in updateConservationMeasure:', error);
throw error;
}
}
/**
* Delete a conservation measure entry
*/
export async function deleteConservationMeasure(id: string) {
try {
console.log(`Deleting conservation measure ${id}`);
const { error } = await supabase
.from('conservation_measures')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting conservation measure:', error);
throw error;
}
return true;
} catch (error) {
console.error('Error in deleteConservationMeasure:', error);
throw error;
}
}
/**
* Get extended species description
* This will fetch the additional fields from the species table
*/
export async function getSpeciesExtendedInfo(speciesId: string) {
console.log('Fetching extended info for species:', speciesId);
try {
const { data, error } = await supabase
.from('species')
.select(`
description,
habitat_description,
population_trend,
population_size,
generation_length,
movement_patterns,
use_and_trade,
threats_overview,
conservation_overview
`)
.eq('id', speciesId)
.single();
if (error) {
console.error('Error fetching extended species info:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in getSpeciesExtendedInfo:', error);
return {
description: null,
habitat_description: null,
population_trend: null,
population_size: null,
generation_length: null,
movement_patterns: null,
use_and_trade: null,
threats_overview: null,
conservation_overview: null
};
}
}
/**
* Update extended info for a species
*/
export async function updateSpeciesExtendedInfo(speciesId: string, updates: {
description?: string;
habitat_description?: string;
population_trend?: string;
population_size?: string;
generation_length?: number;
movement_patterns?: string;
use_and_trade?: string;
threats_overview?: string;
conservation_overview?: string;
}) {
try {
console.log(`Updating extended info for species ${speciesId}:`, updates);
const { data, error } = await supabase
.from('species')
.update(updates)
.eq('id', speciesId)
.select(`
description,
habitat_description,
population_trend,
population_size,
generation_length,
movement_patterns,
use_and_trade,
threats_overview,
conservation_overview
`)
.single();
if (error) {
console.error('Error updating extended species info:', error);
throw error;
}
return data;
} catch (error) {
console.error('Error in updateSpeciesExtendedInfo:', error);
throw error;
}
}
// Add function to get catch records by species ID
export async function getCatchRecords(speciesId: string): Promise<CatchRecord[]> {
console.log('Fetching catch records for species ID:', speciesId);
try {
const { data, error } = await supabase
.from('catch_records')
.select('*')
.eq('species_id', speciesId)
.order('year', { ascending: true }); // Order by year ascending for charting
if (error) {
console.error('Error fetching catch records:', error);
throw error;
}
console.log(`Fetched ${data?.length ?? 0} catch records`);
return data || [];
} catch (error) {
console.error('Error in getCatchRecords:', error);
return []; // Return empty array on error
}
}