feat: enhance search functionality with crime data integration and improved suggestion handling
This commit is contained in:
parent
befda55e2f
commit
2aa2e609f1
|
@ -10,58 +10,35 @@ import ActionSearchBar from "@/app/_components/action-search-bar"
|
||||||
import { Card } from "@/app/_components/ui/card"
|
import { Card } from "@/app/_components/ui/card"
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { ITooltips } from "./tooltips"
|
import { ITooltips } from "./tooltips"
|
||||||
|
import { $Enums } from "@prisma/client"
|
||||||
|
|
||||||
// Expanded sample crime data with more entries for testing
|
// Define types based on the crime data structure
|
||||||
const SAMPLE_CRIME_DATA = [
|
interface CrimeIncident {
|
||||||
{ id: "CR-12345-2023", description: "Robbery at Main Street" },
|
id: string
|
||||||
{ id: "CR-23456-2023", description: "Assault in Central Park" },
|
timestamp: Date
|
||||||
{ id: "CI-7890-2023", description: "Burglary report at Downtown" },
|
description: string
|
||||||
{ id: "CI-4567-2024", description: "Vandalism at City Hall" },
|
status: string
|
||||||
{ id: "CI-4167-2024", description: "Graffiti at Public Library" },
|
latitude?: number
|
||||||
{ id: "CI-4067-2024", description: "Property damage at School" },
|
longitude?: number
|
||||||
{ id: "CR-34567-2024", description: "Car theft on 5th Avenue" },
|
address?: string
|
||||||
{ id: "CR-34517-2024", description: "Mugging at Central Station" },
|
crime_categories?: {
|
||||||
{ id: "CR-14517-2024", description: "Shoplifting at Mall" },
|
id: string
|
||||||
{ id: "CR-24517-2024", description: "Break-in at Office Building" },
|
name: string
|
||||||
];
|
|
||||||
|
|
||||||
// Generate additional sample data for testing scrolling
|
|
||||||
const generateSampleData = () => {
|
|
||||||
const additionalData = [];
|
|
||||||
for (let i = 1; i <= 90; i++) {
|
|
||||||
// Mix of crime and incident IDs
|
|
||||||
const prefix = i % 2 === 0 ? "CR-" : "CI-";
|
|
||||||
const id = `${prefix}${10000 + i}-${2022 + i % 3}`;
|
|
||||||
const descriptions = [
|
|
||||||
"Theft at residence",
|
|
||||||
"Traffic violation",
|
|
||||||
"Noise complaint",
|
|
||||||
"Suspicious activity",
|
|
||||||
"Drug related incident",
|
|
||||||
"Vandalism of public property",
|
|
||||||
"Illegal parking",
|
|
||||||
"Public disturbance",
|
|
||||||
"Domestic dispute",
|
|
||||||
"Assault case"
|
|
||||||
];
|
|
||||||
const description = descriptions[i % descriptions.length];
|
|
||||||
|
|
||||||
// Add more detailed properties for enhanced suggestions
|
|
||||||
additionalData.push({
|
|
||||||
id,
|
|
||||||
description: `${description} #${i}`,
|
|
||||||
coordinates: `${-6 - (i % 5) * 0.01}, ${106 + (i % 7) * 0.01}`,
|
|
||||||
address: `Jl. ${["Sudirman", "Thamrin", "Gatot Subroto", "Rasuna Said", "Asia Afrika"][i % 5]} No. ${i + 10}, Jakarta`,
|
|
||||||
date: new Date(2022 + (i % 3), i % 12, i % 28 + 1),
|
|
||||||
type: ["Theft", "Assault", "Vandalism", "Robbery", "Fraud"][i % 5],
|
|
||||||
category: ["Property Crime", "Violent Crime", "Public Disturbance", "White Collar", "Misdemeanor"][i % 5]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return [...SAMPLE_CRIME_DATA, ...additionalData];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXPANDED_SAMPLE_DATA = generateSampleData();
|
interface Crime {
|
||||||
|
id: string
|
||||||
|
district_id: string
|
||||||
|
month: number
|
||||||
|
year: number
|
||||||
|
crime_incidents: CrimeIncident[]
|
||||||
|
district: {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions for the search bar
|
||||||
const ACTIONS = [
|
const ACTIONS = [
|
||||||
{
|
{
|
||||||
id: "incident_id",
|
id: "incident_id",
|
||||||
|
@ -108,27 +85,32 @@ const ACTIONS = [
|
||||||
interface SearchTooltipProps {
|
interface SearchTooltipProps {
|
||||||
onControlChange?: (controlId: ITooltips) => void
|
onControlChange?: (controlId: ITooltips) => void
|
||||||
activeControl?: string
|
activeControl?: string
|
||||||
|
crimes?: Crime[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchTooltip({ onControlChange, activeControl }: SearchTooltipProps) {
|
export default function SearchTooltip({ onControlChange, activeControl, crimes = [] }: SearchTooltipProps) {
|
||||||
const [showSearch, setShowSearch] = useState(false)
|
const [showSearch, setShowSearch] = useState(false)
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [selectedSearchType, setSelectedSearchType] = useState<string | null>(null)
|
const [selectedSearchType, setSelectedSearchType] = useState<string | null>(null)
|
||||||
const [searchValue, setSearchValue] = useState("")
|
const [searchValue, setSearchValue] = useState("")
|
||||||
const [suggestions, setSuggestions] = useState<Array<{ id: string, description: string }>>([])
|
const [suggestions, setSuggestions] = useState<CrimeIncident[]>([])
|
||||||
const [isInputValid, setIsInputValid] = useState(true)
|
const [isInputValid, setIsInputValid] = useState(true)
|
||||||
const [selectedSuggestion, setSelectedSuggestion] = useState<{
|
const [selectedSuggestion, setSelectedSuggestion] = useState<CrimeIncident | null>(null)
|
||||||
id: string;
|
|
||||||
description: string;
|
|
||||||
latitude?: number;
|
|
||||||
longitude?: number;
|
|
||||||
timestamp?: Date;
|
|
||||||
category?: string;
|
|
||||||
type?: string;
|
|
||||||
address?: string;
|
|
||||||
} | null>(null)
|
|
||||||
const [showInfoBox, setShowInfoBox] = useState(false)
|
const [showInfoBox, setShowInfoBox] = useState(false)
|
||||||
|
|
||||||
|
// Limit results to prevent performance issues
|
||||||
|
const MAX_RESULTS = 50;
|
||||||
|
|
||||||
|
// Extract all incidents from crimes data
|
||||||
|
const allIncidents = crimes.flatMap(crime =>
|
||||||
|
crime.crime_incidents.map(incident => ({
|
||||||
|
...incident,
|
||||||
|
district: crime.district?.name || '',
|
||||||
|
year: crime.year,
|
||||||
|
month: crime.month
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showSearch && searchInputRef.current) {
|
if (showSearch && searchInputRef.current) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -146,22 +128,21 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
setSearchValue(prefix);
|
setSearchValue(prefix);
|
||||||
setIsInputValid(true);
|
setIsInputValid(true);
|
||||||
|
|
||||||
// Immediately filter and show suggestions based on the selected search type
|
// Initial suggestions based on the selected search type
|
||||||
let initialSuggestions: Array<{ id: string, description: string }> = [];
|
let initialSuggestions: CrimeIncident[] = [];
|
||||||
|
|
||||||
if (actionId === 'crime_id') {
|
if (actionId === 'incident_id') {
|
||||||
initialSuggestions = EXPANDED_SAMPLE_DATA.filter(item => item.id.startsWith('CR-'));
|
initialSuggestions = allIncidents.slice(0, MAX_RESULTS); // Limit to 50 results initially
|
||||||
} else if (actionId === 'incident_id') {
|
|
||||||
initialSuggestions = EXPANDED_SAMPLE_DATA.filter(item => item.id.startsWith('CI-'));
|
|
||||||
} else if (actionId === 'description' || actionId === 'address') {
|
} else if (actionId === 'description' || actionId === 'address') {
|
||||||
initialSuggestions = EXPANDED_SAMPLE_DATA;
|
initialSuggestions = allIncidents.slice(0, MAX_RESULTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force a re-render by setting suggestions in the next tick
|
// Set suggestions in the next tick
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSuggestions(initialSuggestions);
|
setSuggestions(initialSuggestions);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
// Focus and position cursor after prefix
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (searchInputRef.current) {
|
if (searchInputRef.current) {
|
||||||
searchInputRef.current.focus();
|
searchInputRef.current.focus();
|
||||||
|
@ -172,48 +153,49 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a helper function for filtering suggestions
|
// Filter suggestions based on search type and search text
|
||||||
const filterSuggestions = (searchType: string, searchText: string): Array<{ id: string, description: string }> => {
|
const filterSuggestions = (searchType: string, searchText: string): CrimeIncident[] => {
|
||||||
let filtered: Array<{ id: string, description: string }> = [];
|
let filtered: CrimeIncident[] = [];
|
||||||
|
|
||||||
if (searchType === 'crime_id') {
|
if (searchType === 'incident_id') {
|
||||||
if (!searchText || searchText === 'CR-') {
|
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item => item.id.startsWith('CR-'));
|
|
||||||
} else {
|
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item =>
|
|
||||||
item.id.startsWith('CR-') &&
|
|
||||||
(item.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
item.description.toLowerCase().includes(searchText.toLowerCase()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (searchType === 'incident_id') {
|
|
||||||
if (!searchText || searchText === 'CI-') {
|
if (!searchText || searchText === 'CI-') {
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item => item.id.startsWith('CI-'));
|
filtered = allIncidents.slice(0, MAX_RESULTS);
|
||||||
} else {
|
} else {
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item =>
|
filtered = allIncidents.filter(item =>
|
||||||
item.id.startsWith('CI-') &&
|
item.id.toLowerCase().includes(searchText.toLowerCase())
|
||||||
(item.id.toLowerCase().includes(searchText.toLowerCase()) ||
|
).slice(0, MAX_RESULTS);
|
||||||
item.description.toLowerCase().includes(searchText.toLowerCase()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (searchType === 'description') {
|
else if (searchType === 'description') {
|
||||||
if (!searchText) {
|
if (!searchText) {
|
||||||
filtered = EXPANDED_SAMPLE_DATA;
|
filtered = allIncidents.slice(0, MAX_RESULTS);
|
||||||
} else {
|
} else {
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item =>
|
filtered = allIncidents.filter(item =>
|
||||||
item.description.toLowerCase().includes(searchText.toLowerCase())
|
item.description.toLowerCase().includes(searchText.toLowerCase())
|
||||||
);
|
).slice(0, MAX_RESULTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (searchType === 'address') {
|
else if (searchType === 'address') {
|
||||||
if (!searchText) {
|
if (!searchText) {
|
||||||
filtered = EXPANDED_SAMPLE_DATA;
|
filtered = allIncidents.slice(0, MAX_RESULTS);
|
||||||
} else {
|
} else {
|
||||||
filtered = EXPANDED_SAMPLE_DATA.filter(item =>
|
filtered = allIncidents.filter(item =>
|
||||||
item.description.toLowerCase().includes(searchText.toLowerCase())
|
item.address && item.address.toLowerCase().includes(searchText.toLowerCase())
|
||||||
);
|
).slice(0, MAX_RESULTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (searchType === 'coordinates') {
|
||||||
|
if (!searchText) {
|
||||||
|
filtered = allIncidents.filter(item => item.latitude !== undefined && item.longitude !== undefined)
|
||||||
|
.slice(0, MAX_RESULTS);
|
||||||
|
} else {
|
||||||
|
// For coordinates, we'd typically do a proximity search
|
||||||
|
// This is a simple implementation for demo purposes
|
||||||
|
filtered = allIncidents.filter(item =>
|
||||||
|
item.latitude !== undefined &&
|
||||||
|
item.longitude !== undefined &&
|
||||||
|
`${item.latitude}, ${item.longitude}`.includes(searchText)
|
||||||
|
).slice(0, MAX_RESULTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +229,7 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the helper function to filter suggestions
|
// Filter suggestions based on search input
|
||||||
setSuggestions(filterSuggestions(selectedSearchType, value));
|
setSuggestions(filterSuggestions(selectedSearchType, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,33 +244,28 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSuggestionSelect = (item: { id: string, description: string }) => {
|
const handleSuggestionSelect = (incident: CrimeIncident) => {
|
||||||
setSearchValue(item.id);
|
setSearchValue(incident.id);
|
||||||
setSuggestions([]);
|
setSuggestions([]);
|
||||||
const fullIncidentData = {
|
setSelectedSuggestion(incident);
|
||||||
...item,
|
|
||||||
timestamp: new Date(),
|
|
||||||
latitude: -6.2088,
|
|
||||||
longitude: 106.8456,
|
|
||||||
category: selectedSearchType === 'crime_id' ? "Theft" : "Vandalism",
|
|
||||||
type: selectedSearchType === 'crime_id' ? "Property Crime" : "Public Disturbance",
|
|
||||||
address: "Jl. Sudirman No. 123, Jakarta"
|
|
||||||
};
|
|
||||||
setSelectedSuggestion(fullIncidentData);
|
|
||||||
setShowInfoBox(true);
|
setShowInfoBox(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFlyToIncident = () => {
|
const handleFlyToIncident = () => {
|
||||||
if (!selectedSuggestion || !selectedSuggestion.latitude || !selectedSuggestion.longitude) return;
|
if (!selectedSuggestion || !selectedSuggestion.latitude || !selectedSuggestion.longitude) return;
|
||||||
|
|
||||||
const flyToEvent = new CustomEvent('fly_to_incident', {
|
const flyToEvent = new CustomEvent('fly_to_incident', {
|
||||||
detail: {
|
detail: {
|
||||||
longitude: selectedSuggestion.longitude,
|
longitude: selectedSuggestion.longitude,
|
||||||
latitude: selectedSuggestion.latitude,
|
latitude: selectedSuggestion.latitude,
|
||||||
id: selectedSuggestion.id,
|
id: selectedSuggestion.id,
|
||||||
zoom: 15
|
zoom: 15,
|
||||||
|
description: selectedSuggestion.description,
|
||||||
|
status: selectedSuggestion.status
|
||||||
},
|
},
|
||||||
bubbles: true
|
bubbles: true
|
||||||
});
|
});
|
||||||
|
|
||||||
document.dispatchEvent(flyToEvent);
|
document.dispatchEvent(flyToEvent);
|
||||||
setShowInfoBox(false);
|
setShowInfoBox(false);
|
||||||
setSelectedSuggestion(null);
|
setSelectedSuggestion(null);
|
||||||
|
@ -299,7 +276,7 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
setShowInfoBox(false);
|
setShowInfoBox(false);
|
||||||
setSelectedSuggestion(null);
|
setSelectedSuggestion(null);
|
||||||
|
|
||||||
// Restore original suggestions for the current search type
|
// Restore original suggestions
|
||||||
if (selectedSearchType) {
|
if (selectedSearchType) {
|
||||||
const initialSuggestions = filterSuggestions(selectedSearchType, searchValue);
|
const initialSuggestions = filterSuggestions(selectedSearchType, searchValue);
|
||||||
setSuggestions(initialSuggestions);
|
setSuggestions(initialSuggestions);
|
||||||
|
@ -316,6 +293,18 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format date for display
|
||||||
|
const formatIncidentDate = (incident: CrimeIncident) => {
|
||||||
|
try {
|
||||||
|
if (incident.timestamp) {
|
||||||
|
return format(new Date(incident.timestamp), 'PPP p');
|
||||||
|
}
|
||||||
|
return 'N/A';
|
||||||
|
} catch (error) {
|
||||||
|
return 'Invalid date';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="z-10 bg-background rounded-md p-1 flex items-center space-x-1">
|
<div className="z-10 bg-background rounded-md p-1 flex items-center space-x-1">
|
||||||
|
@ -357,7 +346,7 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
exit={{ opacity: 0, scale: 0.9 }}
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
className="fixed transform top-1/4 left-1/4 z-50 w-full max-w-lg sm:max-w-xl md:max-w-3xl"
|
className="fixed top-1/4 left-1/4 transform -translate-x-1/4 -translate-y-1/4 z-50 w-full max-w-lg sm:max-w-xl md:max-w-3xl"
|
||||||
>
|
>
|
||||||
<div className="bg-background border border-border rounded-lg shadow-xl p-4">
|
<div className="bg-background border border-border rounded-lg shadow-xl p-4">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
|
@ -403,33 +392,33 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
<div className="sticky top-0 bg-muted/70 backdrop-blur-sm px-3 py-2 border-b border-border">
|
<div className="sticky top-0 bg-muted/70 backdrop-blur-sm px-3 py-2 border-b border-border">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{suggestions.length} results found
|
{suggestions.length} results found
|
||||||
|
{suggestions.length === 50 && " (showing top 50)"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ul className="py-1">
|
<ul className="py-1">
|
||||||
{suggestions.map((item, index) => (
|
{suggestions.map((incident, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="px-3 py-2 hover:bg-muted cursor-pointer flex items-center justify-between"
|
className="px-3 py-2 hover:bg-muted cursor-pointer flex items-center justify-between"
|
||||||
onClick={() => handleSuggestionSelect(item)}
|
onClick={() => handleSuggestionSelect(incident)}
|
||||||
>
|
>
|
||||||
<span className="font-medium">{item.id}</span>
|
<span className="font-medium">{incident.id}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Show different information based on search type */}
|
{selectedSearchType === 'incident_id' ? (
|
||||||
{selectedSearchType === 'crime_id' || selectedSearchType === 'incident_id' ? (
|
|
||||||
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
||||||
{item.description}
|
{incident.description}
|
||||||
</span>
|
</span>
|
||||||
) : selectedSearchType === 'coordinates' && 'coordinates' in item ? (
|
) : selectedSearchType === 'coordinates' ? (
|
||||||
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
||||||
{typeof item.coordinates === 'string' ? item.coordinates : 'N/A'} - {item.description}
|
{incident.latitude}, {incident.longitude} - {incident.description}
|
||||||
</span>
|
</span>
|
||||||
) : selectedSearchType === 'address' && 'address' in item ? (
|
) : selectedSearchType === 'address' ? (
|
||||||
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
||||||
{'address' in item && typeof item.address === 'string' ? item.address : 'N/A'}
|
{incident.address || 'N/A'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
<span className="text-muted-foreground text-sm truncate max-w-[300px]">
|
||||||
{item.description}
|
{incident.description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
|
@ -438,7 +427,7 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
className="h-6 w-6 p-0 rounded-full hover:bg-muted"
|
className="h-6 w-6 p-0 rounded-full hover:bg-muted"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleSuggestionSelect(item);
|
handleSuggestionSelect(incident);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
@ -491,7 +480,7 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
<div className="grid grid-cols-[20px_1fr] gap-2 items-start">
|
<div className="grid grid-cols-[20px_1fr] gap-2 items-start">
|
||||||
<Calendar className="h-4 w-4 mt-1 text-muted-foreground" />
|
<Calendar className="h-4 w-4 mt-1 text-muted-foreground" />
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{format(selectedSuggestion.timestamp, 'PPP p')}
|
{formatIncidentDate(selectedSuggestion)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -506,11 +495,11 @@ export default function SearchTooltip({ onControlChange, activeControl }: Search
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2">
|
<div className="grid grid-cols-2 gap-2 mt-2">
|
||||||
<div className="bg-muted/50 rounded p-2">
|
<div className="bg-muted/50 rounded p-2">
|
||||||
<p className="text-xs text-muted-foreground font-medium">Category</p>
|
<p className="text-xs text-muted-foreground font-medium">Category</p>
|
||||||
<p className="text-sm">{selectedSuggestion.category || 'N/A'}</p>
|
<p className="text-sm">{selectedSuggestion.crime_categories?.name || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-muted/50 rounded p-2">
|
<div className="bg-muted/50 rounded p-2">
|
||||||
<p className="text-xs text-muted-foreground font-medium">Type</p>
|
<p className="text-xs text-muted-foreground font-medium">Status</p>
|
||||||
<p className="text-sm">{selectedSuggestion.type || 'N/A'}</p>
|
<p className="text-sm">{selectedSuggestion.status || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ interface TooltipProps {
|
||||||
setSelectedCategory: (category: string | "all") => void
|
setSelectedCategory: (category: string | "all") => void
|
||||||
availableYears?: (number | null)[]
|
availableYears?: (number | null)[]
|
||||||
categories?: string[]
|
categories?: string[]
|
||||||
|
crimes?: any[] // Add this prop to receive crime data
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Tooltips({
|
export default function Tooltips({
|
||||||
|
@ -58,6 +59,7 @@ export default function Tooltips({
|
||||||
setSelectedCategory,
|
setSelectedCategory,
|
||||||
availableYears = [2022, 2023, 2024],
|
availableYears = [2022, 2023, 2024],
|
||||||
categories = [],
|
categories = [],
|
||||||
|
crimes = []
|
||||||
}: TooltipProps) {
|
}: TooltipProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [isClient, setIsClient] = useState(false)
|
const [isClient, setIsClient] = useState(false)
|
||||||
|
@ -83,7 +85,11 @@ export default function Tooltips({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Search Control Component */}
|
{/* Search Control Component */}
|
||||||
<SearchTooltip activeControl={activeControl} onControlChange={onControlChange} />
|
<SearchTooltip
|
||||||
|
activeControl={activeControl}
|
||||||
|
onControlChange={onControlChange}
|
||||||
|
crimes={crimes} // Pass crimes data here
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -109,21 +109,6 @@ export default function CrimeMap() {
|
||||||
})
|
})
|
||||||
}, [filteredByYearAndMonth, selectedCategory])
|
}, [filteredByYearAndMonth, selectedCategory])
|
||||||
|
|
||||||
// Handle incident marker click
|
|
||||||
// const handleIncidentClick = (incident: CrimeIncident) => {
|
|
||||||
// console.log("Incident clicked directly:", incident);
|
|
||||||
// if (!incident.longitude || !incident.latitude) {
|
|
||||||
// console.error("Invalid incident coordinates:", incident);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // When an incident is clicked, clear any selected district
|
|
||||||
// setSelectedDistrict(null);
|
|
||||||
|
|
||||||
// // Set the selected incident
|
|
||||||
// setSelectedIncident(incident);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Set up event listener for incident clicks from the district layer
|
// Set up event listener for incident clicks from the district layer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleIncidentClickEvent = (e: CustomEvent) => {
|
const handleIncidentClickEvent = (e: CustomEvent) => {
|
||||||
|
@ -222,17 +207,6 @@ export default function CrimeMap() {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle district click
|
|
||||||
// const handleDistrictClick = (feature: DistrictFeature) => {
|
|
||||||
// console.log("District clicked in CrimeMap:", feature.name);
|
|
||||||
|
|
||||||
// // When a district is clicked, clear any selected incident
|
|
||||||
// setSelectedIncident(null);
|
|
||||||
|
|
||||||
// // Set the selected district (for the sidebar or other components)
|
|
||||||
// setSelectedDistrict(feature);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Handle year-month timeline change
|
// Handle year-month timeline change
|
||||||
const handleTimelineChange = useCallback((year: number, month: number, progress: number) => {
|
const handleTimelineChange = useCallback((year: number, month: number, progress: number) => {
|
||||||
setSelectedYear(year)
|
setSelectedYear(year)
|
||||||
|
@ -345,22 +319,21 @@ export default function CrimeMap() {
|
||||||
{/* Components that are only visible in fullscreen mode */}
|
{/* Components that are only visible in fullscreen mode */}
|
||||||
{isFullscreen && (
|
{isFullscreen && (
|
||||||
<>
|
<>
|
||||||
{/* <Overlay position="top" className="m-0 bg-transparent shadow-none p-0 border-none"> */}
|
|
||||||
<div className="absolute flex w-full p-2">
|
<div className="absolute flex w-full p-2">
|
||||||
<Tooltips
|
<Tooltips
|
||||||
activeControl={activeControl}
|
activeControl={activeControl}
|
||||||
onControlChange={handleControlChange}
|
onControlChange={handleControlChange}
|
||||||
selectedYear={selectedYear}
|
selectedYear={selectedYear}
|
||||||
setSelectedYear={setSelectedYear}
|
setSelectedYear={setSelectedYear}
|
||||||
selectedMonth={selectedMonth}
|
selectedMonth={selectedMonth}
|
||||||
setSelectedMonth={setSelectedMonth}
|
setSelectedMonth={setSelectedMonth}
|
||||||
selectedCategory={selectedCategory}
|
selectedCategory={selectedCategory}
|
||||||
setSelectedCategory={setSelectedCategory}
|
setSelectedCategory={setSelectedCategory}
|
||||||
availableYears={availableYears || []}
|
availableYears={availableYears || []}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
/>
|
crimes={filteredCrimes}
|
||||||
</div>
|
/>
|
||||||
{/* </Overlay> */}
|
</div>
|
||||||
|
|
||||||
{/* Pass selectedCategory, selectedYear, and selectedMonth to the sidebar */}
|
{/* Pass selectedCategory, selectedYear, and selectedMonth to the sidebar */}
|
||||||
<CrimeSidebar
|
<CrimeSidebar
|
||||||
|
|
Loading…
Reference in New Issue