dev1/src/components/CareerSearch.js

131 lines
3.8 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { Check } from 'lucide-react'; // any icon lib you use
import { Button } from './ui/button.js';
/* ---------- helpers ---------- */
const normalize = (s = '') =>
s
.toLowerCase()
.replace(/\s*&\s*/g, ' and ')
.replace(/[–—]/g, '-') // long dash → hyphen
.replace(/\s+/g, ' ')
.trim();
/* ---------- component ---------- */
const CareerSearch = ({ onCareerSelected, required, disabled: externallyDisabled = false }) => {
const [careerObjects, setCareerObjects] = useState([]);
const [searchInput, setSearchInput] = useState('');
const [selectedObj, setSelectedObj] = useState(null); // ✓ state
const computedDisabled = externallyDisabled || !!selectedObj;
/* fetch & de-dupe once */
useEffect(() => {
(async () => {
try {
const raw = await fetch('/careers_with_ratings.json').then(r => r.json());
const map = new Map();
for (const c of raw) {
if (c.title && c.soc_code && c.cip_codes) {
const key = normalize(c.title);
if (!map.has(key)) {
map.set(key, {
title: c.title,
soc_code: c.soc_code,
cip_code: c.cip_codes,
limited_data: c.limited_data,
ratings: c.ratings
});
}
}
}
setCareerObjects([...map.values()]);
} catch (err) {
console.error('Career list load failed:', err);
}
})();
}, []);
/* whenever input changes, auto-commit if it matches */
useEffect(() => {
const match = careerObjects.find(
(o) => normalize(o.title) === normalize(searchInput)
);
if (match && match !== selectedObj) {
setSelectedObj(match);
onCareerSelected(match); // notify parent immediately
}
}, [searchInput, careerObjects, selectedObj, onCareerSelected]);
/* allow “Enter” to commit first suggestion */
const handleKeyDown = (e) => {
if (computedDisabled) return;
if (e.key === 'Enter') {
const first = careerObjects.find(o =>
normalize(o.title).startsWith(normalize(searchInput))
);
if (first) {
setSearchInput(first.title); // triggers auto-commit
e.preventDefault();
}
}
};
/* clear & edit again */
const reset = () => {
setSelectedObj(null);
setSearchInput('');
};
return (
<div className="mb-4">
<label className="block font-medium mb-1">
Search for Career <span className="text-red-600">*</span>
</label>
<div className="relative">
<input
type="text"
list="career-titles"
value={searchInput}
required={required}
disabled={computedDisabled} // lock when chosen
onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={handleKeyDown}
className={`w-full border rounded p-2
${computedDisabled ? 'bg-gray-100 cursor-not-allowed opacity-60' : ''}`}
placeholder="Start typing a career..."
/>
{!computedDisabled && (
<datalist id="career-titles">
{careerObjects.map((o) => (
<option key={o.soc_code} value={o.title} />
))}
</datalist>
)}
</div>
{!selectedObj && (
<datalist id="career-titles">
{careerObjects.map((o) => (
<option key={o.soc_code} value={o.title} />
))}
</datalist>
)}
{/* change / clear link */}
{selectedObj && !externallyDisabled && (
<button
type="button"
onClick={reset}
className="text-blue-600 underline text-sm mt-1"
>
Change
</button>
)}
</div>
);
};
export default CareerSearch;