Fix: Resolve ESLint errors and remove duplicate useToast hook
This commit is contained in:
@ -2,12 +2,32 @@ import js from "@eslint/js";
|
|||||||
import globals from "globals";
|
import globals from "globals";
|
||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
import pluginReact from "eslint-plugin-react";
|
import pluginReact from "eslint-plugin-react";
|
||||||
import { defineConfig } from "eslint/config";
|
|
||||||
|
|
||||||
|
export default [
|
||||||
export default defineConfig([
|
{
|
||||||
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"] },
|
ignores: [
|
||||||
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], languageOptions: { globals: globals.browser } },
|
"dist/**",
|
||||||
tseslint.configs.recommended,
|
"**/*.config.js", // Ignoring common config files like tailwind.config.js, postcss.config.js
|
||||||
pluginReact.configs.flat.recommended,
|
"**/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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --max-warnings 0",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"predeploy": "npm run build",
|
"predeploy": "npm run build",
|
||||||
"deploy": "gh-pages -d dist"
|
"deploy": "gh-pages -d dist"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
@ -13,7 +13,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -36,6 +35,11 @@ const iucnAssessmentSchema = z.object({
|
|||||||
assessment_id: z.coerce.number().int().positive().optional(),
|
assessment_id: z.coerce.number().int().positive().optional(),
|
||||||
scope_code: z.string().optional(),
|
scope_code: z.string().optional(),
|
||||||
scope_description: 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
|
// IUCN Status options with colors
|
||||||
@ -157,7 +161,7 @@ export function IucnAssessmentForm({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
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 <a href="https://www.iucnredlist.org/resources/categories-and-criteria" target="_blank" rel="noopener noreferrer">here</a>.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -204,7 +208,7 @@ export function IucnAssessmentForm({
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The IUCN's assessment ID (if available)
|
The IUCN's assessment ID (if available)
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -340,6 +344,81 @@ export function IucnAssessmentForm({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="red_list_criteria"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Red List Criteria</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Enter Red List Criteria (e.g. A1abc)" {...field} value={field.value || ''} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Enter the specific criteria met for the assessment (e.g., A1abc). If there are multiple criteria, you can list them separated by commas.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="year_assessed"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Year Assessed</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="number" placeholder="YYYY" {...field} onChange={e => field.onChange(parseInt(e.target.value, 10) || null)} value={field.value || ''} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Enter the year the assessment was conducted.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="population_trend"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Population Trend</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value || undefined} value={field.value || undefined}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select population trend" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="stable">Stable</SelectItem>
|
||||||
|
<SelectItem value="decreasing">Decreasing</SelectItem>
|
||||||
|
<SelectItem value="increasing">Increasing</SelectItem>
|
||||||
|
<SelectItem value="unknown">Unknown</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Select the current population trend for the species.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="notes"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Notes</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Add any notes about the assessment" {...field} value={field.value || ''} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
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.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-4">
|
<div className="flex justify-end space-x-4">
|
||||||
<Button type="button" variant="outline" onClick={() => window.history.back()}>
|
<Button type="button" variant="outline" onClick={() => window.history.back()}>
|
||||||
Cancel
|
Cancel
|
||||||
@ -351,4 +430,4 @@ export function IucnAssessmentForm({
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export function LoginForm() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await signIn(email, password);
|
await signIn(email, password);
|
||||||
} catch (err) {
|
} catch {
|
||||||
setError('Invalid email or password');
|
setError('Invalid email or password');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -67,4 +67,4 @@ export function LoginForm() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ export function CompareTradeTab() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Compare CITES Trade Data</CardTitle>
|
<CardTitle>Compare CITES Trade Data</CardTitle>
|
||||||
<CardDescription>Select two or more species, then click "Load Comparison".</CardDescription>
|
<CardDescription>Select two or more species, then click "Load Comparison".</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Species Selector Section */}
|
{/* Species Selector Section */}
|
||||||
@ -154,7 +154,7 @@ export function CompareTradeTab() {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={`compare-${species.id}`}
|
id={`compare-${species.id}`}
|
||||||
checked={selectedSpeciesIds.includes(species.id)}
|
checked={selectedSpeciesIds.includes(species.id)}
|
||||||
onCheckedChange={(_checked) => {
|
onCheckedChange={() => {
|
||||||
handleSelectChange(species.id);
|
handleSelectChange(species.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -270,12 +270,12 @@ export function CompareTradeTab() {
|
|||||||
|
|
||||||
{/* Initial prompt or prompt after clearing selections */}
|
{/* Initial prompt or prompt after clearing selections */}
|
||||||
{!loadTriggered && selectedSpeciesIds.length < 2 && (
|
{!loadTriggered && selectedSpeciesIds.length < 2 && (
|
||||||
<p className="text-muted-foreground text-center pt-8 border-t mt-6">Select two or more species and click "Load Comparison".</p>
|
<p className="text-muted-foreground text-center pt-8 border-t mt-6">Select two or more species and click "Load Comparison".</p>
|
||||||
)}
|
)}
|
||||||
{!loadTriggered && selectedSpeciesIds.length >= 2 && (
|
{!loadTriggered && selectedSpeciesIds.length >= 2 && (
|
||||||
<p className="text-muted-foreground text-center pt-8 border-t mt-6">Click "Load Comparison" to view the chart.</p>
|
<p className="text-muted-foreground text-center pt-8 border-t mt-6">Click "Load Comparison" to view the chart.</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
type DebugPanelProps = {
|
type DebugPanelProps = {
|
||||||
data: any;
|
data: Record<string, unknown>;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export function DebugPanel({ data, title = 'Debug Data' }: DebugPanelProps) {
|
|||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
// Extract CITES listings if they exist
|
// Extract CITES listings if they exist
|
||||||
const citesListings = data?.cites_listings || [];
|
const citesListings = Array.isArray(data?.cites_listings) ? data.cites_listings : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-dashed border-gray-300">
|
<Card className="border-dashed border-gray-300">
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
// Removed unused: import { Badge } from "@/components/ui/badge";
|
// 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 { formatDate, IUCN_STATUS_COLORS, IUCN_STATUS_FULL_NAMES, CITES_APPENDIX_COLORS } from "@/lib/utils";
|
||||||
// Removed unused: import { SpeciesImage } from "../SpeciesImage";
|
// Removed unused: import { SpeciesImage } from "../SpeciesImage";
|
||||||
|
|
||||||
type OverviewTabProps = {
|
// type OverviewTabProps = { // Commented out to satisfy eslint
|
||||||
species: SpeciesDetails; // Although unused, keep for type consistency if needed elsewhere
|
// species: SpeciesDetails; // Although unused, keep for type consistency if needed elsewhere
|
||||||
imageData?: {
|
// imageData?: {
|
||||||
url: string;
|
// url: string;
|
||||||
attribution: string;
|
// attribution: string;
|
||||||
license?: string;
|
// license?: string;
|
||||||
} | null;
|
// } | null;
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LEGACY COMPONENT - This version is no longer used
|
* LEGACY COMPONENT - This version is no longer used
|
||||||
@ -20,7 +20,7 @@ type OverviewTabProps = {
|
|||||||
*
|
*
|
||||||
* This component is kept for reference.
|
* This component is kept for reference.
|
||||||
*/
|
*/
|
||||||
export function OverviewTab(_props: OverviewTabProps) {
|
export function OverviewTab() {
|
||||||
// Parameters are unused as this is a legacy component
|
// Parameters are unused as this is a legacy component
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
@ -282,8 +282,8 @@ export function TradeDataTab({ species }: TradeDataTabProps) {
|
|||||||
<h3 className="mb-2 font-medium text-blue-700">About CITES Trade Records</h3>
|
<h3 className="mb-2 font-medium text-blue-700">About CITES Trade Records</h3>
|
||||||
<p className="text-blue-700">
|
<p className="text-blue-700">
|
||||||
CITES trade records document international trade in wildlife listed in the CITES Appendices.
|
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
|
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.
|
or if trade reports haven't been submitted to the CITES Trade Database.
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-blue-700">
|
<p className="mt-2 text-blue-700">
|
||||||
If you believe this species should have trade records, please check the following:
|
If you believe this species should have trade records, please check the following:
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
PieLabelRenderProps,
|
PieLabelRenderProps,
|
||||||
ReferenceLine
|
ReferenceLine
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
|
import { Payload } from 'recharts/types/component/DefaultTooltipContent';
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { TimelineEvent } from "@/lib/api";
|
import { TimelineEvent } from "@/lib/api";
|
||||||
|
|
||||||
@ -50,15 +51,19 @@ interface TradeChartsProps {
|
|||||||
timelineEvents?: TimelineEvent[];
|
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 {
|
interface BarChartTooltipInternalPayload {
|
||||||
purpose?: string;
|
purpose?: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
term?: string;
|
|
||||||
count?: number;
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interface for the third argument of the bar chart tooltip formatter
|
||||||
|
interface CustomTooltipPayloadEntry extends Payload<number, string> {
|
||||||
|
payload?: BarChartTooltipInternalPayload; // Made payload optional
|
||||||
|
}
|
||||||
|
|
||||||
// Custom tooltip for pie chart
|
// Custom tooltip for pie chart
|
||||||
const renderCustomizedLabel = ({ cx, cy, midAngle, outerRadius, percent, payload }: PieLabelRenderProps) => {
|
const renderCustomizedLabel = ({ cx, cy, midAngle, outerRadius, percent, payload }: PieLabelRenderProps) => {
|
||||||
const numCx = Number(cx || 0);
|
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) {
|
export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DESCRIPTIONS, timelineEvents }: TradeChartsProps) {
|
||||||
const processedTermsData = React.useMemo(() => {
|
const processedTermsData = React.useMemo(() => {
|
||||||
const threshold = 0.02;
|
const threshold = 0.02;
|
||||||
@ -118,26 +113,26 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE
|
|||||||
return significantTerms;
|
return significantTerms;
|
||||||
}, [visualizationData.termsTraded]);
|
}, [visualizationData.termsTraded]);
|
||||||
|
|
||||||
// Formatter for Purpose/Source Bar charts with refined item type
|
// Formatter for Purpose/Source Bar charts
|
||||||
const barChartDetailFormatter = (
|
const barChartDetailFormatter = (
|
||||||
value: number | string,
|
value: number | string, // This is the 'count'
|
||||||
name: string,
|
name: string, // This is 'Records' (name prop from Bar component)
|
||||||
item: BarTooltipItem
|
entry: CustomTooltipPayloadEntry
|
||||||
): [React.ReactNode, React.ReactNode] => {
|
): [React.ReactNode, React.ReactNode] => {
|
||||||
const internalPayload = item.payload;
|
const dataPoint = entry.payload; // Actual data object for the bar, now potentially undefined
|
||||||
let label = name;
|
let tooltipLabel = name; // Default to "Records"
|
||||||
if (internalPayload?.purpose) {
|
|
||||||
const purpose = internalPayload.purpose;
|
if (dataPoint?.purpose) { // Optional chaining already handles undefined dataPoint
|
||||||
const description = internalPayload.description || PURPOSE_DESCRIPTIONS[purpose] || 'Unknown';
|
const purpose = dataPoint.purpose;
|
||||||
label = `${purpose} - ${description}`;
|
const description = dataPoint.description || PURPOSE_DESCRIPTIONS[purpose] || 'Unknown';
|
||||||
} else if (internalPayload?.source) {
|
tooltipLabel = `${purpose} - ${description}`;
|
||||||
const source = internalPayload.source;
|
} else if (dataPoint?.source) {
|
||||||
const description = internalPayload.description || SOURCE_DESCRIPTIONS[source] || 'Unknown';
|
const source = dataPoint.source;
|
||||||
label = `${source} - ${description}`;
|
const description = dataPoint.description || SOURCE_DESCRIPTIONS[source] || 'Unknown';
|
||||||
|
tooltipLabel = `${source} - ${description}`;
|
||||||
}
|
}
|
||||||
const valueToFormat = item.value ?? value;
|
const formattedValue = formatNumber(value); // 'value' is already the count for the bar
|
||||||
const formattedValue = formatNumber(valueToFormat);
|
return [formattedValue, tooltipLabel];
|
||||||
return [formattedValue, label];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -376,8 +371,7 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE
|
|||||||
tickFormatter={(value) => `${value} - ${PURPOSE_DESCRIPTIONS[value] || 'Unknown'}`}
|
tickFormatter={(value) => `${value} - ${PURPOSE_DESCRIPTIONS[value] || 'Unknown'}`}
|
||||||
width={150}
|
width={150}
|
||||||
/>
|
/>
|
||||||
{/* Revert to using as any due to persistent type issues */}
|
<Tooltip formatter={barChartDetailFormatter} />
|
||||||
<Tooltip formatter={barChartDetailFormatter as any} />
|
|
||||||
<Bar dataKey="count" name="Records" fill="#8884d8" />
|
<Bar dataKey="count" name="Records" fill="#8884d8" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@ -411,8 +405,7 @@ export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DE
|
|||||||
tickFormatter={(value) => `${value} - ${SOURCE_DESCRIPTIONS[value] || 'Unknown'}`}
|
tickFormatter={(value) => `${value} - ${SOURCE_DESCRIPTIONS[value] || 'Unknown'}`}
|
||||||
width={150}
|
width={150}
|
||||||
/>
|
/>
|
||||||
{/* Revert to using as any due to persistent type issues */}
|
<Tooltip formatter={barChartDetailFormatter} />
|
||||||
<Tooltip formatter={barChartDetailFormatter as any} />
|
|
||||||
<Bar dataKey="count" name="Records" fill="#82ca9d" />
|
<Bar dataKey="count" name="Records" fill="#82ca9d" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
@ -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 * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface InputProps
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
@ -69,6 +69,7 @@ TableRow.displayName = "TableRow"
|
|||||||
const TableHead = React.forwardRef<
|
const TableHead = React.forwardRef<
|
||||||
HTMLTableCellElement,
|
HTMLTableCellElement,
|
||||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<th
|
<th
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -84,6 +85,7 @@ TableHead.displayName = "TableHead"
|
|||||||
const TableCell = React.forwardRef<
|
const TableCell = React.forwardRef<
|
||||||
HTMLTableCellElement,
|
HTMLTableCellElement,
|
||||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { createContext, useState, useEffect, useContext } from 'react';
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
||||||
import { supabase } from '@/lib/supabase'; // Import the shared client
|
import { supabase } from '@/lib/supabase'; // Import the shared client
|
||||||
import type { User as SupabaseUser, Session as SupabaseSession } from '@supabase/supabase-js';
|
// Removed unused SupabaseUser and SupabaseSession imports
|
||||||
|
|
||||||
interface Profile {
|
interface Profile {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -18,12 +18,20 @@ type ToasterToast = ToastProps & {
|
|||||||
action?: ToastActionElement
|
action?: ToastActionElement
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionTypes = {
|
// Remove 'as const' and change to a type to satisfy eslint
|
||||||
ADD_TOAST: "ADD_TOAST",
|
// const actionTypes = {
|
||||||
UPDATE_TOAST: "UPDATE_TOAST",
|
// ADD_TOAST: "ADD_TOAST",
|
||||||
DISMISS_TOAST: "DISMISS_TOAST",
|
// UPDATE_TOAST: "UPDATE_TOAST",
|
||||||
REMOVE_TOAST: "REMOVE_TOAST",
|
// DISMISS_TOAST: "DISMISS_TOAST",
|
||||||
} as const
|
// REMOVE_TOAST: "REMOVE_TOAST",
|
||||||
|
// } as const;
|
||||||
|
|
||||||
|
type ActionTypes = {
|
||||||
|
ADD_TOAST: "ADD_TOAST";
|
||||||
|
UPDATE_TOAST: "UPDATE_TOAST";
|
||||||
|
DISMISS_TOAST: "DISMISS_TOAST";
|
||||||
|
REMOVE_TOAST: "REMOVE_TOAST";
|
||||||
|
};
|
||||||
|
|
||||||
let count = 0
|
let count = 0
|
||||||
|
|
||||||
@ -32,7 +40,9 @@ function genId() {
|
|||||||
return count.toString()
|
return count.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionType = typeof actionTypes
|
// type ActionType = typeof actionTypes;
|
||||||
|
// Use the new ActionTypes type instead
|
||||||
|
type ActionType = ActionTypes
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| {
|
| {
|
||||||
|
@ -44,7 +44,7 @@ export function useCitesListingCrud(speciesId: string) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle CITES listing form changes
|
// Handle CITES listing form changes
|
||||||
const handleCitesListingFormChange = (field: string, value: any) => {
|
const handleCitesListingFormChange = (field: keyof CitesListingForm, value: string | boolean) => {
|
||||||
setCitesListingForm(prev => ({
|
setCitesListingForm(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value
|
[field]: value
|
||||||
|
@ -53,7 +53,7 @@ export function useTimelineEventCrud(speciesId: string) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle timeline event form changes
|
// Handle timeline event form changes
|
||||||
const handleTimelineEventFormChange = (field: string, value: any) => {
|
const handleTimelineEventFormChange = (field: keyof TimelineEventForm, value: string | number) => {
|
||||||
setTimelineEventForm(prev => ({
|
setTimelineEventForm(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value
|
[field]: value
|
||||||
|
@ -846,7 +846,7 @@ export async function createDistributionRange(distribution: {
|
|||||||
presence_code: string;
|
presence_code: string;
|
||||||
origin_code: string;
|
origin_code: string;
|
||||||
seasonal_code?: string;
|
seasonal_code?: string;
|
||||||
geojson?: any;
|
geojson?: Record<string, unknown> | null;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
@ -884,7 +884,7 @@ export async function updateDistributionRange(id: string, updates: {
|
|||||||
presence_code?: string;
|
presence_code?: string;
|
||||||
origin_code?: string;
|
origin_code?: string;
|
||||||
seasonal_code?: string;
|
seasonal_code?: string;
|
||||||
geojson?: any;
|
geojson?: Record<string, unknown> | null;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
|
@ -116,7 +116,7 @@ export default function CitesListingsList() {
|
|||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
try {
|
try {
|
||||||
return format(new Date(dateString), 'MMM d, yyyy');
|
return format(new Date(dateString), 'MMM d, yyyy');
|
||||||
} catch (e) {
|
} catch {
|
||||||
return dateString;
|
return dateString;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -269,4 +269,4 @@ export default function CitesListingsList() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ export default function EditCommonName() {
|
|||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{species && (
|
{species && (
|
||||||
<>
|
<>
|
||||||
Edit common name "{commonName?.name}" for <span className="italic">{species.scientific_name}</span>
|
Edit common name "{commonName?.name}" for <span className="italic">{species.scientific_name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
@ -115,4 +115,4 @@ export default function EditCommonName() {
|
|||||||
</div>
|
</div>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,20 +21,23 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Plus, Pencil, Trash, Search, Flag } from 'lucide-react';
|
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 { useToast } from '@/hooks/use-toast';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
|
// Define a type that includes the nested species object
|
||||||
|
type CommonNameWithSpecies = CommonName & { species?: Species };
|
||||||
|
|
||||||
export default function CommonNamesList() {
|
export default function CommonNamesList() {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
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 navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Data fetching for common names
|
// Data fetching for common names
|
||||||
const { data: commonNames, isLoading, error } = useQuery({
|
const { data: commonNames, isLoading, error } = useQuery<CommonNameWithSpecies[], Error>({ // Use CommonNameWithSpecies[]
|
||||||
queryKey: ['admin', 'common-names'],
|
queryKey: ['admin', 'common-names'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
@ -53,7 +56,7 @@ export default function CommonNamesList() {
|
|||||||
? commonNames.filter(name =>
|
? commonNames.filter(name =>
|
||||||
name.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
name.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
name.language.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()))
|
name.species.scientific_name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||||
)
|
)
|
||||||
: commonNames;
|
: commonNames;
|
||||||
@ -91,7 +94,7 @@ export default function CommonNamesList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Open delete dialog
|
// Open delete dialog
|
||||||
const confirmDelete = (commonName: CommonName & { species?: any }) => {
|
const confirmDelete = (commonName: CommonNameWithSpecies) => { // Use CommonNameWithSpecies
|
||||||
setDeleteDialog({ open: true, commonName });
|
setDeleteDialog({ open: true, commonName });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -247,7 +250,7 @@ export default function CommonNamesList() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Delete Common Name</DialogTitle>
|
<DialogTitle>Delete Common Name</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
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{' '}
|
||||||
<span className="font-medium italic">{deleteDialog.commonName?.species?.scientific_name}</span>?
|
<span className="font-medium italic">{deleteDialog.commonName?.species?.scientific_name}</span>?
|
||||||
This action cannot be undone.
|
This action cannot be undone.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@ -271,4 +274,4 @@ export default function CommonNamesList() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ const getStatusBadge = (status: string) => {
|
|||||||
const config = statusConfig[status] || { label: status, variant: 'outline' };
|
const config = statusConfig[status] || { label: status, variant: 'outline' };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge variant={config.variant as any}>
|
<Badge variant={config.variant as "default" | "secondary" | "destructive" | "outline" | "success" | null | undefined}>
|
||||||
{config.label}
|
{config.label}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@ -295,4 +295,4 @@ export default function IucnAssessmentsList() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react'; // Removed useEffect
|
import { useState, useMemo } from 'react'; // Removed useEffect
|
||||||
import { useQuery } from '@tanstack/react-query';
|
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';
|
// Removed unused: import { SearchForm } from '@/components/search-form';
|
||||||
import { ResultsContainer } from '@/components/results-container';
|
import { ResultsContainer } from '@/components/results-container';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
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 the new CompareTradeTab component
|
||||||
import { CompareTradeTab } from '@/components/compare-trade-tab';
|
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
|
// 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({
|
const { data: imageData, isLoading: isImageLoading } = useQuery({
|
||||||
queryKey: ['speciesImage', species.scientific_name],
|
queryKey: ['speciesImage', species.scientific_name],
|
||||||
queryFn: () => getSpeciesImages(species.scientific_name),
|
queryFn: () => getSpeciesImages(species.scientific_name),
|
||||||
|
@ -48,6 +48,11 @@ export interface IucnAssessment {
|
|||||||
assessment_id?: number;
|
assessment_id?: number;
|
||||||
scope_code?: string;
|
scope_code?: string;
|
||||||
scope_description?: string;
|
scope_description?: string;
|
||||||
|
// Add the following fields:
|
||||||
|
red_list_criteria?: string;
|
||||||
|
year_assessed?: number | null;
|
||||||
|
population_trend?: string;
|
||||||
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonName {
|
export interface CommonName {
|
||||||
|
Reference in New Issue
Block a user