Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/calculator/CaratEstimator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function CaratEstimator() {
const [sgCustom, setSgCustom] = useState('');
const [girdle, setGirdle] = useState<GirdleAdjustment>('medium');

const { shapeFactors, mineralsWithSG, fallbackShapeFactors } = useCalculatorData();
const { shapeFactors, mineralsWithSG, fallbackShapeFactors, initiate } = useCalculatorData();

// Use database shape factors if available, otherwise fallback
const shapes = useMemo(() => {
Expand Down Expand Up @@ -173,7 +173,7 @@ export function CaratEstimator() {
};

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={initiate}>
<div className="text-sm text-slate-600">
<p>Enter stone dimensions to estimate carat weight.</p>
<p className="mt-2 text-sm text-slate-600">
Expand Down
10 changes: 6 additions & 4 deletions src/components/calculator/DispersionCalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ function classifyDispersion(dispersion: number): { category: string; level: 'low
}

export function DispersionCalculator() {
const [loading, setLoading] = useState(true);
const [hasInitiated, setHasInitiated] = useState(false);
const [loading, setLoading] = useState(false);
const [paginatedData, setPaginatedData] = useState<PaginatedResult<Mineral> | null>(null);

const { params, onPageChange, onPageSizeChange } = usePagination<Mineral>({
initialPageSize: 15,
});

// Fetch paginated data from database
// Fetch paginated data only after first user interaction
useEffect(() => {
if (!hasInitiated) return;
async function loadData() {
setLoading(true);
try {
Expand All @@ -42,7 +44,7 @@ export function DispersionCalculator() {
}
}
loadData();
}, [params]);
}, [hasInitiated, params]);

const { values, errors, result, setValue } = useCalculatorForm({
fields: {
Expand Down Expand Up @@ -93,7 +95,7 @@ export function DispersionCalculator() {
};

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<div>
<p className="text-sm text-slate-600">
Enter the refractive index at red (C-line, 656nm) and violet (F-line, 486nm) wavelengths to calculate dispersion.
Expand Down
8 changes: 5 additions & 3 deletions src/components/calculator/HannemanRI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ const liquidOptions = CONTACT_LIQUIDS.map((l) => ({
}));

export function HannemanRI() {
const [hasInitiated, setHasInitiated] = useState(false);
const [minerals, setMinerals] = useState<Mineral[]>([]);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbError, setDbError] = useState<string | null>(null);

const [rows, setRows] = useState<Row[]>([
Expand All @@ -45,6 +46,7 @@ export function HannemanRI() {
]);

useEffect(() => {
if (!hasInitiated) return;
let mounted = true;
(async () => {
try {
Expand All @@ -61,7 +63,7 @@ export function HannemanRI() {
return () => {
mounted = false;
};
}, []);
}, [hasInitiated]);

const usable = useMemo(() => rows.filter((r) => r.relief !== 'unknown'), [rows]);
const band = useMemo(() => (usable.length === 0 ? null : combineBands(usable)), [usable]);
Expand Down Expand Up @@ -94,7 +96,7 @@ export function HannemanRI() {
setRows((rs) => rs.map((r, idx) => (idx === i ? { ...r, ...patch } : r)));

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<div className="text-sm text-slate-600">
<p>
For stones above the refractometer scale (RI {'>'} 1.81) or rough material with no
Expand Down
4 changes: 2 additions & 2 deletions src/components/calculator/RICalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function RICalculator() {
// Use the average of the two readings when in double mode, otherwise the single reading.
const lookupTarget = doubleReadingResult?.lookupRI ?? result?.ri ?? null;

const { matches, lookup } = useGemLookup({
const { matches, lookup, initiate } = useGemLookup({
type: 'ri',
tolerance: parseFloat(values.tolerance) || 0.01,
});
Expand All @@ -92,7 +92,7 @@ export function RICalculator() {
}, [lookupTarget, lookup]);

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={initiate}>
<div className="text-sm text-slate-600">
<p>Enter an RI reading to find matching gemstones. Toggle <strong>Double reading</strong> to enter both shadow-edge readings (ω/ε or α/γ) and infer birefringence + optic character automatically.</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/calculator/SGCalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function SGCalculator() {
});

// Gem lookup with debouncing
const { matches, lookup } = useGemLookup({
const { matches, lookup, initiate } = useGemLookup({
type: 'sg',
tolerance: 0.05,
});
Expand All @@ -106,7 +106,7 @@ export function SGCalculator() {
}, [result, lookup]);

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={initiate}>
<div className="text-sm text-slate-600">
<p>Enter the weight of your stone in air and water to calculate its specific gravity.</p>
<p className="mt-2 text-sm text-slate-600">
Expand Down
12 changes: 2 additions & 10 deletions src/components/identification/GemIdentifier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function GemIdentifier() {
const [showAdvanced, setShowAdvanced] = useState(false);

// Hook for matching
const { findMatches, crystalSystems, loading, error, mineralsCount, dbAvailable } = useGemIdentification();
const { findMatches, crystalSystems, loading, error, mineralsCount, dbAvailable, initiate } = useGemIdentification();

// Determine if we're using single or dual RI mode
const isDualRIMode = opticCharacter === 'uniaxial' || opticCharacter === 'biaxial';
Expand Down Expand Up @@ -245,14 +245,6 @@ export function GemIdentifier() {
setOpticSign('');
};

if (loading) {
return (
<div className="p-4 text-center text-slate-600">
Loading mineral database...
</div>
);
}

if (error && !dbAvailable) {
return (
<div className="p-4 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">
Expand All @@ -262,7 +254,7 @@ export function GemIdentifier() {
}

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={initiate}>
<div>
<p className="text-sm text-slate-600">
Enter measured gemmological properties to find matching gemstones.
Expand Down
10 changes: 6 additions & 4 deletions src/components/identification/HardnessReference.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,20 @@ const WEARABILITY_COLORS: Record<string, string> = {
};

export function HardnessReference() {
const [hasInitiated, setHasInitiated] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [filterWearability, setFilterWearability] = useState<string>('all');
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbAvailable, setDbAvailable] = useState(false);
const [paginatedData, setPaginatedData] = useState<PaginatedResult<Mineral> | null>(null);

const { params, getPaginationProps, resetPage } = usePagination<Mineral>({
initialPageSize: 20,
});

// Fetch paginated data from database
// Fetch paginated data only after first user interaction
useEffect(() => {
if (!hasInitiated) return;
async function loadData() {
setLoading(true);
try {
Expand All @@ -102,7 +104,7 @@ export function HardnessReference() {
}
}
loadData();
}, [params]);
}, [hasInitiated, params]);

// Reset to first page when search or filter changes
useEffect(() => {
Expand Down Expand Up @@ -135,7 +137,7 @@ export function HardnessReference() {
});

return (
<div className="space-y-5">
<div className="space-y-5" onPointerDown={() => setHasInitiated(true)}>
{/* Two-column layout: Mohs scale | Gem lookup */}
<div className="grid lg:grid-cols-[auto_1fr] gap-5">
{/* Left: Mohs scale — compact table */}
Expand Down
10 changes: 6 additions & 4 deletions src/components/lab/HeavyLiquidReference.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,19 @@ const FALLBACK_GEMS = [
];

export function HeavyLiquidReference() {
const [hasInitiated, setHasInitiated] = useState(false);
const [selectedSG, setSelectedSG] = useState<number | null>(null);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbAvailable, setDbAvailable] = useState(false);
const [paginatedData, setPaginatedData] = useState<PaginatedResult<Mineral> | null>(null);

const { params, onPageChange, onPageSizeChange } = usePagination<Mineral>({
initialPageSize: 15,
});

// Fetch paginated data from database
// Fetch paginated data only after first user interaction
useEffect(() => {
if (!hasInitiated) return;
async function loadData() {
setLoading(true);
try {
Expand All @@ -95,7 +97,7 @@ export function HeavyLiquidReference() {
}
}
loadData();
}, [params]);
}, [hasInitiated, params]);

// Convert database minerals to simple gem format
const gems = useMemo(() => {
Expand All @@ -116,7 +118,7 @@ export function HeavyLiquidReference() {
};

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<div>
<p className="text-sm text-slate-600">
Heavy liquids separate gems by specific gravity. Select a liquid to see which gems float or sink.
Expand Down
8 changes: 5 additions & 3 deletions src/components/lab/UvFluorescenceLookup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ function detectTreatmentFlag(family: MineralFamily, swuvColor: string): string |
}

export function UvFluorescenceLookup() {
const [hasInitiated, setHasInitiated] = useState(false);
const [families, setFamilies] = useState<MineralFamily[]>([]);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbError, setDbError] = useState<string | null>(null);

const [lwuvIntensity, setLwuvIntensity] = useState<UvIntensity>('strong');
Expand All @@ -72,6 +73,7 @@ export function UvFluorescenceLookup() {
const [swuvColor, setSwuvColor] = useState('');

useEffect(() => {
if (!hasInitiated) return;
let mounted = true;
(async () => {
try {
Expand All @@ -88,7 +90,7 @@ export function UvFluorescenceLookup() {
return () => {
mounted = false;
};
}, []);
}, [hasInitiated]);

const obs = { lwuvIntensity, lwuvColor, swuvIntensity, swuvColor };
const hasObservation = lwuvIntensity !== 'unknown' || swuvIntensity !== 'unknown';
Expand Down Expand Up @@ -131,7 +133,7 @@ export function UvFluorescenceLookup() {
};

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<p className="text-sm text-slate-600">
Observe the stone under both long-wave (365 nm) and short-wave (254 nm) UV in a darkened
cabinet and report what you see. The reasoner ranks species whose stored fluorescence text
Expand Down
10 changes: 6 additions & 4 deletions src/components/optical/DichroscopeResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,21 @@ const STRENGTH_COLORS: Record<string, string> = {
};

export function DichroscopeResults() {
const [hasInitiated, setHasInitiated] = useState(false);
const [color1, setColor1] = useState('');
const [color2, setColor2] = useState('');
const [strengthFilter, setStrengthFilter] = useState('all');
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbAvailable, setDbAvailable] = useState(false);
const [paginatedData, setPaginatedData] = useState<PaginatedResult<Mineral> | null>(null);

const { params, onPageChange, onPageSizeChange, resetPage } = usePagination<Mineral>({
initialPageSize: 15,
});

// Fetch paginated data from database
// Fetch paginated data only after first user interaction
useEffect(() => {
if (!hasInitiated) return;
async function loadData() {
setLoading(true);
try {
Expand All @@ -79,7 +81,7 @@ export function DichroscopeResults() {
}
}
loadData();
}, [params]);
}, [hasInitiated, params]);

// Reset to first page when filters change
useEffect(() => {
Expand Down Expand Up @@ -112,7 +114,7 @@ export function DichroscopeResults() {
});

return (
<div className="space-y-5">
<div className="space-y-5" onPointerDown={() => setHasInitiated(true)}>
{/* Filter bar — single row across full width */}
<div className="flex flex-wrap gap-3 items-end">
<div className="flex-1 min-w-[140px]">
Expand Down
8 changes: 5 additions & 3 deletions src/components/optical/OpticSignReasoner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ const SIGN_LABEL: Record<OpticSign, string> = {
};

export function OpticSignReasoner() {
const [hasInitiated, setHasInitiated] = useState(false);
const [minerals, setMinerals] = useState<Mineral[]>([]);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbError, setDbError] = useState<string | null>(null);

const [character, setCharacter] = useState<OpticCharacterKind>('uniaxial');
Expand All @@ -60,6 +61,7 @@ export function OpticSignReasoner() {
const tolerance = 0.005;

useEffect(() => {
if (!hasInitiated) return;
let mounted = true;
(async () => {
try {
Expand All @@ -76,7 +78,7 @@ export function OpticSignReasoner() {
return () => {
mounted = false;
};
}, []);
}, [hasInitiated]);

const computed = useMemo(() => {
if (character === 'isotropic') {
Expand Down Expand Up @@ -170,7 +172,7 @@ export function OpticSignReasoner() {
const { paged, page, setPage, totalPages } = usePagination(matches, 10);

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<p className="text-sm text-slate-600">
Pick what you saw in the polariscope. For uniaxial gems, enter ω and ε from the
refractometer; for biaxial, enter α and γ (β is optional). The reasoner derives optic
Expand Down
8 changes: 5 additions & 3 deletions src/components/optical/PleochroismReasoner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ const STRENGTH_BADGE: Record<string, string> = {
};

export function PleochroismReasoner() {
const [hasInitiated, setHasInitiated] = useState(false);
const [minerals, setMinerals] = useState<Mineral[]>([]);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [dbError, setDbError] = useState<string | null>(null);

const [colourCount, setColourCount] = useState<ObservedColourCount>(2);
Expand All @@ -52,6 +53,7 @@ export function PleochroismReasoner() {
const [strength, setStrength] = useState<PleochroismStrength>('unknown');

useEffect(() => {
if (!hasInitiated) return;
let mounted = true;
(async () => {
try {
Expand All @@ -68,7 +70,7 @@ export function PleochroismReasoner() {
return () => {
mounted = false;
};
}, []);
}, [hasInitiated]);

const interpretation = useMemo(() => interpretColourCount(colourCount), [colourCount]);

Expand Down Expand Up @@ -103,7 +105,7 @@ export function PleochroismReasoner() {
const observedColoursEntered = [c1, c2, c3].slice(0, colourCount).filter((s) => s.trim()).length;

return (
<div className="space-y-6">
<div className="space-y-6" onPointerDown={() => setHasInitiated(true)}>
<p className="text-sm text-slate-600">
Report what you saw through the dichroscope. The reasoner explains what your observation implies and ranks
candidate species from the mineral database.
Expand Down
Loading
Loading