import type { Placemark } from "@/entities/geodata";
import { useGeodataLayer } from "@/entities/geodata";
import { Loader } from "@/shared/ui/Loader";
import { Expandable } from "@/shared/ui/expandable/ui/Expandable";
import { ExpandableContent } from "@/shared/ui/expandable/ui/ExpandableContent";
import { ExpandableTrigger } from "@/shared/ui/expandable/ui/ExpandableTrigger";
import { useMap } from "@vis.gl/react-google-maps";
import { ChevronRight, Eye, EyeOff } from "lucide-react";
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import { getPlacemarkPosition } from "../lib/getCenterPosition.ts";
import { getThumbUrl } from "../lib/getThumbUrl.ts";
import { useMapState } from "../model/slice.ts";
type LayerControlProps = {
layerName: string;
showInfo: (placemark: Placemark) => void;
closeGroups: () => void;
};
export const LayerControl: React.FC<LayerControlProps> = ({
layerName: value,
showInfo,
closeGroups,
}) => {
const map = useMap();
const { toggleGroup, displayedGroups } = useMapState();
const [isListExpanded, setIsListExpanded] = useState(false);
// Use TanStack Query to get layer data from cache
// This will be instant if data was preloaded by usePreloadGeodataLayers
const { data: rawLayerData, isLoading, isError } = useGeodataLayer(value);
// Cast to Placemark type (API returns camelCase, internal types use snake_case)
const layerData = rawLayerData as Placemark[] | undefined;
const isDisplayed = displayedGroups.includes(value);
const handleClick = (placemark: Placemark) => {
if (!map) {
return;
}
const coordinates = getPlacemarkPosition(placemark);
if (!coordinates) {
return;
}
map.panTo(coordinates);
map.setZoom(18);
showInfo(placemark);
closeGroups();
};
const displayName = value.replace(/_/, " ");
return (
<Expandable
expanded={isListExpanded}
setExpanded={setIsListExpanded}
className="mb-2"
>
<div className="flex items-center gap-2 p-2 rounded-lg hover:bg-accent/50 transition-colors group">
{/* Eye icon toggle */}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
toggleGroup(value);
}}
className={twMerge(
"h-8 w-8 rounded-full flex items-center justify-center transition-all",
isDisplayed
? "bg-primary/10 text-primary"
: "text-muted-foreground/50 hover:text-muted-foreground hover:bg-accent",
)}
>
{isDisplayed ? (
<Eye className="h-4 w-4" />
) : (
<EyeOff className="h-4 w-4" />
)}
</button>
{/* Expand trigger */}
<ExpandableTrigger className="flex-1 flex items-center gap-2 text-left">
{({ isCurrentExpanded }) => (
<>
<ChevronRight
className={twMerge(
"h-5 w-5 text-muted-foreground transition-transform duration-200",
isCurrentExpanded && "rotate-90",
)}
/>
<span
className={twMerge(
"capitalize text-lg font-medium transition-opacity",
!isDisplayed && "opacity-50",
)}
>
{displayName}
</span>
</>
)}
</ExpandableTrigger>
</div>
<ExpandableContent>
<LayerItemsList
placemarks={layerData || []}
isLoading={isLoading}
isError={isError}
onItemClick={handleClick}
/>
</ExpandableContent>
</Expandable>
);
};
// Sub-component for layer items list
interface LayerItemsListProps {
placemarks: Placemark[];
isLoading: boolean;
isError: boolean;
onItemClick: (placemark: Placemark) => void;
}
const LayerItemsList: React.FC<LayerItemsListProps> = ({
placemarks,
isLoading,
isError,
onItemClick,
}) => {
if (isLoading) {
return <Loader containerClassName="h-10" className="h-4" />;
}
if (isError) {
return (
<p className="text-sm text-destructive py-2">
Couldn't load the layer
</p>
);
}
if (!placemarks.length) {
return null;
}
return (
<div className="flex flex-col gap-1.5">
{placemarks.map((placemark, idx) => (
<button
type="button"
className="flex items-center gap-2 text-left w-full rounded transition-colors py-1.5 px-2 hover:bg-accent text-base"
key={`${placemark.name}-${idx}`}
onClick={() => onItemClick(placemark)}
>
<img
className="aspect-square h-5 flex-shrink-0 object-contain"
alt={placemark.name}
src={getThumbUrl(placemark)}
/>
<span className="truncate">{placemark.name}</span>
</button>
))}
</div>
);
};