diff --git a/eslint.config.js b/eslint.config.js index 63d4e2f..feeeb3f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,12 +2,32 @@ import js from "@eslint/js"; import globals from "globals"; import tseslint from "typescript-eslint"; import pluginReact from "eslint-plugin-react"; -import { defineConfig } from "eslint/config"; - -export default defineConfig([ - { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"] }, - { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], languageOptions: { globals: globals.browser } }, - tseslint.configs.recommended, - pluginReact.configs.flat.recommended, -]); +export default [ + { + ignores: [ + "dist/**", + "**/*.config.js", // Ignoring common config files like tailwind.config.js, postcss.config.js + "**/vite-env.d.ts", // Ignoring Vite specific type declarations + "src/utils/supabase-admin.js" // Ignoring specific js file that seems to be a utility + ] + }, + { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], plugins: { js }, rules: js.configs.recommended.rules }, + { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.node } } }, + ...tseslint.configs.recommended, + { + ...pluginReact.configs.flat.recommended, + settings: { + react: { + version: "detect" + } + } + }, + { + files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + rules: { + "react/react-in-jsx-scope": "off", + "react/jsx-uses-react": "off", + } + } +]; diff --git a/package.json b/package.json index 8468be7..8b0e8d6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --max-warnings 0", "preview": "vite preview", "predeploy": "npm run build", "deploy": "gh-pages -d dist" diff --git a/src/components/admin/CitesListingForm.tsx b/src/components/admin/CitesListingForm.tsx index f35bf86..2f61f4f 100644 --- a/src/components/admin/CitesListingForm.tsx +++ b/src/components/admin/CitesListingForm.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; diff --git a/src/components/admin/CommonNameForm.tsx b/src/components/admin/CommonNameForm.tsx index 5637549..d162f76 100644 --- a/src/components/admin/CommonNameForm.tsx +++ b/src/components/admin/CommonNameForm.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; diff --git a/src/components/admin/IucnAssessmentForm.tsx b/src/components/admin/IucnAssessmentForm.tsx index 17f940d..04f9567 100644 --- a/src/components/admin/IucnAssessmentForm.tsx +++ b/src/components/admin/IucnAssessmentForm.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; @@ -13,7 +13,6 @@ import { FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, @@ -36,6 +35,11 @@ const iucnAssessmentSchema = z.object({ assessment_id: z.coerce.number().int().positive().optional(), scope_code: z.string().optional(), scope_description: z.string().optional(), + // Add the following fields: + red_list_criteria: z.string().optional(), + year_assessed: z.coerce.number().int().min(1900, 'Year must be at least 1900').optional().nullable(), + population_trend: z.string().optional(), + notes: z.string().optional(), }); // IUCN Status options with colors @@ -157,7 +161,7 @@ export function IucnAssessmentForm({ - The IUCN Red List conservation status + Select the IUCN status for this assessment. You can find more information about IUCN Red List Categories and Criteria here. @@ -204,7 +208,7 @@ export function IucnAssessmentForm({ /> - The IUCN's assessment ID (if available) + The IUCN's assessment ID (if available) @@ -340,6 +344,81 @@ export function IucnAssessmentForm({ )} + ( + + Red List Criteria + + + + + Enter the specific criteria met for the assessment (e.g., A1abc). If there are multiple criteria, you can list them separated by commas. + + + + )} + /> + ( + + Year Assessed + + field.onChange(parseInt(e.target.value, 10) || null)} value={field.value || ''} /> + + + Enter the year the assessment was conducted. + + + + )} + /> + ( + + Population Trend + + + Select the current population trend for the species. + + + + )} + /> + ( + + Notes + + + + + Provide any additional notes or comments regarding this IUCN assessment. This could include information on data quality, specific threats, or conservation actions. It's a good place to elaborate on any nuances not captured by the structured fields. + + + + )} + /> +
); -} \ No newline at end of file +} diff --git a/src/components/compare-trade-tab.tsx b/src/components/compare-trade-tab.tsx index acba52b..93995e2 100644 --- a/src/components/compare-trade-tab.tsx +++ b/src/components/compare-trade-tab.tsx @@ -138,7 +138,7 @@ export function CompareTradeTab() { Compare CITES Trade Data - Select two or more species, then click "Load Comparison". + Select two or more species, then click "Load Comparison". {/* Species Selector Section */} @@ -154,7 +154,7 @@ export function CompareTradeTab() { { + onCheckedChange={() => { handleSelectChange(species.id); }} /> @@ -270,12 +270,12 @@ export function CompareTradeTab() { {/* Initial prompt or prompt after clearing selections */} {!loadTriggered && selectedSpeciesIds.length < 2 && ( -

Select two or more species and click "Load Comparison".

+

Select two or more species and click "Load Comparison".

)} {!loadTriggered && selectedSpeciesIds.length >= 2 && ( -

Click "Load Comparison" to view the chart.

+

Click "Load Comparison" to view the chart.

)}
); -} \ No newline at end of file +} diff --git a/src/components/debug-panel.tsx b/src/components/debug-panel.tsx index f3b11cc..d1ae879 100644 --- a/src/components/debug-panel.tsx +++ b/src/components/debug-panel.tsx @@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; type DebugPanelProps = { - data: any; + data: Record; title?: string; }; @@ -11,7 +11,7 @@ export function DebugPanel({ data, title = 'Debug Data' }: DebugPanelProps) { const [isVisible, setIsVisible] = useState(false); // Extract CITES listings if they exist - const citesListings = data?.cites_listings || []; + const citesListings = Array.isArray(data?.cites_listings) ? data.cites_listings : []; return ( diff --git a/src/components/species/tabs/OverviewTab.tsx b/src/components/species/tabs/OverviewTab.tsx index 9d12f5b..b674d1c 100644 --- a/src/components/species/tabs/OverviewTab.tsx +++ b/src/components/species/tabs/OverviewTab.tsx @@ -1,17 +1,17 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; // Removed unused: import { Badge } from "@/components/ui/badge"; -import { SpeciesDetails } from "@/lib/api"; +// import { SpeciesDetails } from "@/lib/api"; // Commented out to satisfy eslint // Removed unused: import { formatDate, IUCN_STATUS_COLORS, IUCN_STATUS_FULL_NAMES, CITES_APPENDIX_COLORS } from "@/lib/utils"; // Removed unused: import { SpeciesImage } from "../SpeciesImage"; -type OverviewTabProps = { - species: SpeciesDetails; // Although unused, keep for type consistency if needed elsewhere - imageData?: { - url: string; - attribution: string; - license?: string; - } | null; -}; +// type OverviewTabProps = { // Commented out to satisfy eslint +// species: SpeciesDetails; // Although unused, keep for type consistency if needed elsewhere +// imageData?: { +// url: string; +// attribution: string; +// license?: string; +// } | null; +// }; /** * LEGACY COMPONENT - This version is no longer used @@ -20,7 +20,7 @@ type OverviewTabProps = { * * This component is kept for reference. */ -export function OverviewTab(_props: OverviewTabProps) { +export function OverviewTab() { // Parameters are unused as this is a legacy component return (
diff --git a/src/components/species/tabs/TradeDataTab.tsx b/src/components/species/tabs/TradeDataTab.tsx index 7ddbe39..2841ba7 100644 --- a/src/components/species/tabs/TradeDataTab.tsx +++ b/src/components/species/tabs/TradeDataTab.tsx @@ -282,8 +282,8 @@ export function TradeDataTab({ species }: TradeDataTabProps) {

About CITES Trade Records

CITES trade records document international trade in wildlife listed in the CITES Appendices. - Not all species have recorded trade data, particularly if they haven't been traded internationally - or if trade reports haven't been submitted to the CITES Trade Database. + Not all species have recorded trade data, particularly if they haven't been traded internationally + or if trade reports haven't been submitted to the CITES Trade Database.

If you believe this species should have trade records, please check the following: diff --git a/src/components/species/visualizations/TradeCharts.tsx b/src/components/species/visualizations/TradeCharts.tsx index 423044f..0940b20 100644 --- a/src/components/species/visualizations/TradeCharts.tsx +++ b/src/components/species/visualizations/TradeCharts.tsx @@ -17,6 +17,7 @@ import { PieLabelRenderProps, ReferenceLine } from "recharts"; +import { Payload } from 'recharts/types/component/DefaultTooltipContent'; import React from "react"; import { TimelineEvent } from "@/lib/api"; @@ -50,15 +51,19 @@ interface TradeChartsProps { timelineEvents?: TimelineEvent[]; } -// Expected structure within the 'payload' object from Recharts Tooltip item +// Expected structure within the 'payload' object from Recharts Tooltip item for Bar Charts interface BarChartTooltipInternalPayload { purpose?: string; source?: string; description?: string; - term?: string; count?: number; } +// Interface for the third argument of the bar chart tooltip formatter +interface CustomTooltipPayloadEntry extends Payload { + payload?: BarChartTooltipInternalPayload; // Made payload optional +} + // Custom tooltip for pie chart const renderCustomizedLabel = ({ cx, cy, midAngle, outerRadius, percent, payload }: PieLabelRenderProps) => { const numCx = Number(cx || 0); @@ -88,16 +93,6 @@ const renderCustomizedLabel = ({ cx, cy, midAngle, outerRadius, percent, payload ); }; -// Type for the 'item' argument passed to the complex bar chart formatter -// Aims to align with Recharts internal Payload structure for Tooltip -interface BarTooltipItem { - payload?: BarChartTooltipInternalPayload; - value?: number | string; - name?: string; - color?: string; - dataKey?: string; -} - export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DESCRIPTIONS, timelineEvents }: TradeChartsProps) { const processedTermsData = React.useMemo(() => { const threshold = 0.02; @@ -118,26 +113,26 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE return significantTerms; }, [visualizationData.termsTraded]); - // Formatter for Purpose/Source Bar charts with refined item type + // Formatter for Purpose/Source Bar charts const barChartDetailFormatter = ( - value: number | string, - name: string, - item: BarTooltipItem - ): [React.ReactNode, React.ReactNode] => { - const internalPayload = item.payload; - let label = name; - if (internalPayload?.purpose) { - const purpose = internalPayload.purpose; - const description = internalPayload.description || PURPOSE_DESCRIPTIONS[purpose] || 'Unknown'; - label = `${purpose} - ${description}`; - } else if (internalPayload?.source) { - const source = internalPayload.source; - const description = internalPayload.description || SOURCE_DESCRIPTIONS[source] || 'Unknown'; - label = `${source} - ${description}`; + value: number | string, // This is the 'count' + name: string, // This is 'Records' (name prop from Bar component) + entry: CustomTooltipPayloadEntry + ): [React.ReactNode, React.ReactNode] => { + const dataPoint = entry.payload; // Actual data object for the bar, now potentially undefined + let tooltipLabel = name; // Default to "Records" + + if (dataPoint?.purpose) { // Optional chaining already handles undefined dataPoint + const purpose = dataPoint.purpose; + const description = dataPoint.description || PURPOSE_DESCRIPTIONS[purpose] || 'Unknown'; + tooltipLabel = `${purpose} - ${description}`; + } else if (dataPoint?.source) { + const source = dataPoint.source; + const description = dataPoint.description || SOURCE_DESCRIPTIONS[source] || 'Unknown'; + tooltipLabel = `${source} - ${description}`; } - const valueToFormat = item.value ?? value; - const formattedValue = formatNumber(valueToFormat); - return [formattedValue, label]; + const formattedValue = formatNumber(value); // 'value' is already the count for the bar + return [formattedValue, tooltipLabel]; }; return ( @@ -376,8 +371,7 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE tickFormatter={(value) => `${value} - ${PURPOSE_DESCRIPTIONS[value] || 'Unknown'}`} width={150} /> - {/* Revert to using as any due to persistent type issues */} - + @@ -411,8 +405,7 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE tickFormatter={(value) => `${value} - ${SOURCE_DESCRIPTIONS[value] || 'Unknown'}`} width={150} /> - {/* Revert to using as any due to persistent type issues */} - + diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 22cbeb1..cb6762a 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -1,9 +1,9 @@ +/* eslint-disable react/prop-types */ // Disable prop-types rule for this file as props are inherited import * as React from "react" import { cn } from "@/lib/utils" -export interface InputProps - extends React.InputHTMLAttributes {} +export type InputProps = React.InputHTMLAttributes; const Input = React.forwardRef( ({ className, type, ...props }, ref) => { diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index c0df655..21ad4cd 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -69,6 +69,7 @@ TableRow.displayName = "TableRow" const TableHead = React.forwardRef< HTMLTableCellElement, React.ThHTMLAttributes + // eslint-disable-next-line react/prop-types >(({ className, ...props }, ref) => ( + // eslint-disable-next-line react/prop-types >(({ className, ...props }, ref) => ( { + const handleCitesListingFormChange = (field: keyof CitesListingForm, value: string | boolean) => { setCitesListingForm(prev => ({ ...prev, [field]: value diff --git a/src/hooks/useTimelineEventCrud.ts b/src/hooks/useTimelineEventCrud.ts index e0573f8..7621eae 100644 --- a/src/hooks/useTimelineEventCrud.ts +++ b/src/hooks/useTimelineEventCrud.ts @@ -53,7 +53,7 @@ export function useTimelineEventCrud(speciesId: string) { }; // Handle timeline event form changes - const handleTimelineEventFormChange = (field: string, value: any) => { + const handleTimelineEventFormChange = (field: keyof TimelineEventForm, value: string | number) => { setTimelineEventForm(prev => ({ ...prev, [field]: value diff --git a/src/lib/api.ts b/src/lib/api.ts index e69464b..376625c 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -846,7 +846,7 @@ export async function createDistributionRange(distribution: { presence_code: string; origin_code: string; seasonal_code?: string; - geojson?: any; + geojson?: Record | null; notes?: string; }) { try { @@ -884,7 +884,7 @@ export async function updateDistributionRange(id: string, updates: { presence_code?: string; origin_code?: string; seasonal_code?: string; - geojson?: any; + geojson?: Record | null; notes?: string; }) { try { diff --git a/src/pages/admin/cites-listings/list.tsx b/src/pages/admin/cites-listings/list.tsx index 5837757..d5658d5 100644 --- a/src/pages/admin/cites-listings/list.tsx +++ b/src/pages/admin/cites-listings/list.tsx @@ -116,7 +116,7 @@ export default function CitesListingsList() { const formatDate = (dateString: string) => { try { return format(new Date(dateString), 'MMM d, yyyy'); - } catch (e) { + } catch { return dateString; } }; @@ -269,4 +269,4 @@ export default function CitesListingsList() { ); -} \ No newline at end of file +} diff --git a/src/pages/admin/common-names/edit.tsx b/src/pages/admin/common-names/edit.tsx index 95ceea1..1b8f5b3 100644 --- a/src/pages/admin/common-names/edit.tsx +++ b/src/pages/admin/common-names/edit.tsx @@ -98,7 +98,7 @@ export default function EditCommonName() {

{species && ( <> - Edit common name "{commonName?.name}" for {species.scientific_name} + Edit common name "{commonName?.name}" for {species.scientific_name} )}

@@ -115,4 +115,4 @@ export default function EditCommonName() {
); -} \ No newline at end of file +} diff --git a/src/pages/admin/common-names/list.tsx b/src/pages/admin/common-names/list.tsx index 06d9317..d3eb728 100644 --- a/src/pages/admin/common-names/list.tsx +++ b/src/pages/admin/common-names/list.tsx @@ -21,20 +21,23 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { Plus, Pencil, Trash, Search, Flag } from 'lucide-react'; -import { commonNamesApi, speciesApi, CommonName } from '@/services/adminApi'; +import { commonNamesApi, CommonName, Species } from '@/services/adminApi'; // Added Species import import { useToast } from '@/hooks/use-toast'; import { Badge } from '@/components/ui/badge'; +// Define a type that includes the nested species object +type CommonNameWithSpecies = CommonName & { species?: Species }; + export default function CommonNamesList() { const [searchQuery, setSearchQuery] = useState(''); const [isSearching, setIsSearching] = useState(false); - const [deleteDialog, setDeleteDialog] = useState<{open: boolean, commonName?: CommonName & { species?: any }}>({open: false}); + const [deleteDialog, setDeleteDialog] = useState<{open: boolean, commonName?: CommonNameWithSpecies }>({open: false}); // Use CommonNameWithSpecies const navigate = useNavigate(); const queryClient = useQueryClient(); const { toast } = useToast(); // Data fetching for common names - const { data: commonNames, isLoading, error } = useQuery({ + const { data: commonNames, isLoading, error } = useQuery({ // Use CommonNameWithSpecies[] queryKey: ['admin', 'common-names'], queryFn: async () => { try { @@ -53,7 +56,7 @@ export default function CommonNamesList() { ? commonNames.filter(name => name.name.toLowerCase().includes(searchQuery.toLowerCase()) || name.language.toLowerCase().includes(searchQuery.toLowerCase()) || - (name.species?.scientific_name && + (name.species?.scientific_name && // Check if scientific_name exists name.species.scientific_name.toLowerCase().includes(searchQuery.toLowerCase())) ) : commonNames; @@ -91,7 +94,7 @@ export default function CommonNamesList() { }; // Open delete dialog - const confirmDelete = (commonName: CommonName & { species?: any }) => { + const confirmDelete = (commonName: CommonNameWithSpecies) => { // Use CommonNameWithSpecies setDeleteDialog({ open: true, commonName }); }; @@ -247,7 +250,7 @@ export default function CommonNamesList() { Delete Common Name - Are you sure you want to delete "{deleteDialog.commonName?.name}" ({getLanguageName(deleteDialog.commonName?.language || '')}) for{' '} + Are you sure you want to delete "{deleteDialog.commonName?.name}" ({getLanguageName(deleteDialog.commonName?.language || '')}) for{' '} {deleteDialog.commonName?.species?.scientific_name}? This action cannot be undone. @@ -271,4 +274,4 @@ export default function CommonNamesList() { ); -} \ No newline at end of file +} diff --git a/src/pages/admin/iucn-assessments/list.tsx b/src/pages/admin/iucn-assessments/list.tsx index cb9be57..971f39d 100644 --- a/src/pages/admin/iucn-assessments/list.tsx +++ b/src/pages/admin/iucn-assessments/list.tsx @@ -42,7 +42,7 @@ const getStatusBadge = (status: string) => { const config = statusConfig[status] || { label: status, variant: 'outline' }; return ( - + {config.label} ); @@ -295,4 +295,4 @@ export default function IucnAssessmentsList() { ); -} \ No newline at end of file +} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 225265d..1517011 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,6 +1,6 @@ import { useState, useMemo } from 'react'; // Removed useEffect import { useQuery } from '@tanstack/react-query'; -import { getAllSpecies, getSpeciesImages, getTotalTradeCount } from '@/lib/api'; +import { getAllSpecies, getSpeciesImages, getTotalTradeCount, Species } from '@/lib/api'; // Added Species // Removed unused: import { SearchForm } from '@/components/search-form'; import { ResultsContainer } from '@/components/results-container'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -13,8 +13,14 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; // Import the new CompareTradeTab component import { CompareTradeTab } from '@/components/compare-trade-tab'; +// Define a local type for the SpeciesCard props that includes optional latest_assessment +type SpeciesCardProps = { + species: Species & { latest_assessment?: { status?: string } }; // Make latest_assessment and its status optional + onClick: (id: string) => void; +}; + // Species Card Component with Image Support -function SpeciesCard({ species, onClick }: { species: any, onClick: (id: string) => void }) { +function SpeciesCard({ species, onClick }: SpeciesCardProps) { // Used local SpeciesCardProps type const { data: imageData, isLoading: isImageLoading } = useQuery({ queryKey: ['speciesImage', species.scientific_name], queryFn: () => getSpeciesImages(species.scientific_name), diff --git a/src/services/adminApi.ts b/src/services/adminApi.ts index 085e608..845d82a 100644 --- a/src/services/adminApi.ts +++ b/src/services/adminApi.ts @@ -48,6 +48,11 @@ export interface IucnAssessment { assessment_id?: number; scope_code?: string; scope_description?: string; + // Add the following fields: + red_list_criteria?: string; + year_assessed?: number | null; + population_trend?: string; + notes?: string; } export interface CommonName {