|
|
import { Dialog, Disclosure, Menu, Transition } from "@headlessui/react";
|
|
|
import {
|
|
|
ChevronDownIcon,
|
|
|
ChevronUpIcon,
|
|
|
} from "@heroicons/react/20/solid";
|
|
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
|
import { Fragment, useRef, useState } from "react";
|
|
|
|
|
|
import Map, { Layer, Popup, ScaleControl, Source } from "react-map-gl/maplibre";
|
|
|
import basemap from "./assets/basemap.json";
|
|
|
import mapstyle from "./countries.json";
|
|
|
|
|
|
|
|
|
mapstyle.sources.remap.url = import.meta.env.MODE === "localhost" ? "http://localhost:8080/capabilities/remap.json" : mapstyle.sources.remap.url
|
|
|
|
|
|
const paintedLayers = mapstyle.layers.reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {})
|
|
|
const toggleLayers = ["Подложка"]
|
|
|
|
|
|
|
|
|
const initialPaintFilters = [
|
|
|
{
|
|
|
id: "parameter_name",
|
|
|
name: "Показатель ВИЭ-генерации",
|
|
|
options: [
|
|
|
{
|
|
|
"value": "power_fact_mw",
|
|
|
"label": "Фактическая установленная мощность, МВт",
|
|
|
"checked": true
|
|
|
},
|
|
|
{
|
|
|
"value": "generation_gwh",
|
|
|
"label": "Производство электроэнергии, млн кВт⋅ч",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": "res_share_generation",
|
|
|
"label": "Доля в выработке электроэнергии, %",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": "res_share_power",
|
|
|
"label": "Доля в балансе мощности, %",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": "generation_to_population_kWh_pers",
|
|
|
"label": "Производство электроэнергии на душу населения, кВт⋅ч/чел.",
|
|
|
"checked": false
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
]
|
|
|
|
|
|
const initialFilterFilters = [
|
|
|
{
|
|
|
id: "id_tech",
|
|
|
name: "ВИЭ-технология",
|
|
|
options: [
|
|
|
{
|
|
|
"value": 1,
|
|
|
"label": "СЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2,
|
|
|
"label": "Наземные ВЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 3,
|
|
|
"label": "Офшорные ВЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 4,
|
|
|
"label": "ГЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 5,
|
|
|
"label": "БиоЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 6,
|
|
|
"label": "ГеоЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 8,
|
|
|
"label": "СЭС и ВЭС",
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 7,
|
|
|
"label": "Все ВИЭ",
|
|
|
"checked": true
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
"id": "year",
|
|
|
"name": "Год",
|
|
|
"options": [
|
|
|
{
|
|
|
"value": 2010,
|
|
|
"label": 2010,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2011,
|
|
|
"label": 2011,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2012,
|
|
|
"label": 2012,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2013,
|
|
|
"label": 2013,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2014,
|
|
|
"label": 2014,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2015,
|
|
|
"label": 2015,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2016,
|
|
|
"label": 2016,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2017,
|
|
|
"label": 2017,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2018,
|
|
|
"label": 2018,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2019,
|
|
|
"label": 2019,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2020,
|
|
|
"label": 2020,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2021,
|
|
|
"label": 2021,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2022,
|
|
|
"label": 2022,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2023,
|
|
|
"label": 2023,
|
|
|
"checked": false
|
|
|
},
|
|
|
{
|
|
|
"value": 2024,
|
|
|
"label": 2024,
|
|
|
"checked": true
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
]
|
|
|
|
|
|
export default function Countries({ mapOptions, onMapOptionClick }) {
|
|
|
const mapRef = useRef(null);
|
|
|
|
|
|
const [open, setOpen] = useState(false)
|
|
|
|
|
|
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
|
|
|
|
|
|
const [popupInfo, setPopupInfo] = useState(null);
|
|
|
|
|
|
const [paintFilters, setPaintFilters] = useState(initialPaintFilters);
|
|
|
const [filterFilters, setFilterFilters] = useState(initialFilterFilters);
|
|
|
|
|
|
|
|
|
const generatePopup = (info) => {
|
|
|
// console.log(info)
|
|
|
const [currentTech, currentYear, currentParameter] = filterFilters.map(f => f.options.find(o => o.checked)).concat(paintFilters[0].options.find(o => o.checked))
|
|
|
return (
|
|
|
<div>
|
|
|
<img src={`/flags/${info.id_country}.png`} className="mb-4 w-36 outline outline-gray-400" />
|
|
|
{info.area_km2 && <p className="text-gray-500">Площадь <span className="text-gray-900 font-medium">{(info.area_km2 / 1000).toFixed(1).replace(".", ",")} тыс. км²</span></p>}
|
|
|
{info.population_person && <p className="text-gray-500">Численность населения <span className="text-gray-900 font-medium">{(info.population_person / 1000).toFixed(1).replace(".", ",")} тыс. чел.</span></p>}
|
|
|
{info.population_densitiy_pers_km2 && <p className="text-gray-500">Плотность населения <span className="text-gray-900 font-medium">{info.population_densitiy_pers_km2.toFixed(1).replace(".", ",")} чел./км²</span></p>}
|
|
|
{info.year_carbon_neutrality && <p className="text-gray-500">Целевой год достижения углеродной нейтральности <span className="text-gray-900 font-medium">{info.year_carbon_neutrality}</span></p>}
|
|
|
|
|
|
{info[currentParameter.value] &&
|
|
|
<div className="relative overflow-x-auto py-6">
|
|
|
<p className="text-sm font-medium">{currentParameter.label} в {currentYear.label} году</p>
|
|
|
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
|
|
<tbody>
|
|
|
{info[currentParameter.value] != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
{currentTech.label}
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info[currentParameter.value].toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
|
|
|
<div className="relative overflow-x-auto pt-2">
|
|
|
<p className="text-sm font-medium">Совокупная установленная мощность ВИЭ по итогам {info.static_date_power} года, МВт</p>
|
|
|
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
|
|
<tbody>
|
|
|
{info.static_power_fact_solar_mw != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
Солнечные электростанции
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_power_fact_solar_mw.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
{info.static_power_fact_onshore_mw != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
Наземные ветроэлектростанции
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_power_fact_onshore_mw.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
{info.static_power_fact_offshore_mw != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
Офшорные ветроэлектростанции
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_power_fact_offshore_mw.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
{info.static_power_fact_res_mw != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-medium text-gray-900 dark:text-white uppercase">
|
|
|
Всего ВИЭ <span className="font-normal normal-case">(включая СЭС, ВЭС, ГЭС, БиоЭС, ГеоЭС)</span>
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_power_fact_res_mw.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
{info.static_res_share_power != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
Доля всей ВИЭ-генерации в балансе мощности страны, %
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_res_share_power.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
{info.static_solar_wind_share_power != null &&
|
|
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
|
<th scope="row" className="px-2 py-4 font-normal text-gray-900 dark:text-white">
|
|
|
Доля СЭС и ВЭС в балансе мощности страны, %
|
|
|
</th>
|
|
|
<td className="px-2 py-4 text-right">
|
|
|
{info.static_solar_wind_share_power.toFixed(2).replace(".", ",")}
|
|
|
</td>
|
|
|
</tr>
|
|
|
}
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
|
|
|
<p className="text-sm text-gray-400">Источник: IRENA</p>
|
|
|
{info.date_actualization && <p className="text-sm mt-4 text-gray-600">Дата актуализации: {info.date_actualization}</p>}
|
|
|
</div>
|
|
|
)
|
|
|
}
|
|
|
|
|
|
const generateLegend = function (parameter) {
|
|
|
const colors_and_breaks = mapstyle.layers.find(layer => layer.id == parameter.value).paint["fill-color"].slice(2)
|
|
|
const colors = colors_and_breaks.filter((cb, i) => i % 2 == 0)
|
|
|
const breaks = colors_and_breaks.filter((cb, i) => i % 2 == 1)
|
|
|
const labels = [`менее ${breaks[0]}`, ...breaks.map((brk, i, arr) => typeof arr[i + 1] !== 'undefined' ? `${brk} - ${arr[i + 1]}` : `более ${breaks.at(-1)}`)]
|
|
|
const legend_items = colors.map((col, i) => (
|
|
|
{
|
|
|
"color": col,
|
|
|
"label": labels[i]
|
|
|
}
|
|
|
)).reverse()
|
|
|
return (
|
|
|
<div>
|
|
|
<p>{parameter.label}</p>
|
|
|
{legend_items.map(item => <div className="flex items-center mt-3" key={item.color}><div className={"w-5 h-5 rounded-sm"} style={{ backgroundColor: item.color }}></div><p className="ml-3 text-sm text-gray-600">{item.label}</p></div>)}
|
|
|
</div>
|
|
|
)
|
|
|
}
|
|
|
|
|
|
const handleFilterToggle = function (event) {
|
|
|
|
|
|
if (paintFilters.filter((filter) => filter.id == event.target.name).length > 0) {
|
|
|
const newStatePaintFilters = paintFilters.map((filter) =>
|
|
|
filter.id == event.target.name
|
|
|
? {
|
|
|
...filter,
|
|
|
options: filter.options.map((option) =>
|
|
|
option.value == event.target.defaultValue
|
|
|
? { ...option, checked: true }
|
|
|
: { ...option, checked: false }
|
|
|
),
|
|
|
}
|
|
|
: filter
|
|
|
);
|
|
|
setPaintFilters(newStatePaintFilters);
|
|
|
} else {
|
|
|
const newStateFilterFilters = filterFilters.map((filter) =>
|
|
|
filter.id == event.target.name
|
|
|
? {
|
|
|
...filter,
|
|
|
options: filter.options.map((option) =>
|
|
|
option.value == event.target.defaultValue
|
|
|
? { ...option, checked: true }
|
|
|
: { ...option, checked: false }
|
|
|
),
|
|
|
}
|
|
|
: filter
|
|
|
);
|
|
|
setFilterFilters(newStateFilterFilters);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const applyPaintFilters = (filtersObj) => {
|
|
|
const parameter = filtersObj.find(filter => filter.id == "parameter_name").options.find(option => option.checked).value
|
|
|
const newPaint = paintedLayers[parameter].paint
|
|
|
return newPaint
|
|
|
};
|
|
|
|
|
|
const applyFilterFilters = (filtersObj) => {
|
|
|
const year_value = filtersObj.find(f => f.id == "year").options.find(option => option.checked).value
|
|
|
const tech_value = filtersObj.find(f => f.id == "id_tech").options.find(option => option.checked).value
|
|
|
return ["all", ["==", ["get", "year"], year_value], ["==", ["get", "id_tech_global"], tech_value]]
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleClick = (event) => {
|
|
|
if (event.features[0]) {
|
|
|
event.originalEvent.stopPropagation();
|
|
|
setPopupInfo({
|
|
|
lon: event.lngLat.lng,
|
|
|
lat: event.lngLat.lat,
|
|
|
...event.features[0].properties
|
|
|
})
|
|
|
setOpen(true)
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleMouseEnter = (event) => {
|
|
|
const feature = event.features[0];
|
|
|
if (!feature) return;
|
|
|
|
|
|
mapRef.current.getCanvas().style.cursor = "pointer";
|
|
|
};
|
|
|
|
|
|
const handleMouseLeave = (event) => {
|
|
|
const feature = event.features[0];
|
|
|
if (!feature) {
|
|
|
return;
|
|
|
}
|
|
|
mapRef.current.getCanvas().style.cursor = "";
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<div className="bg-white">
|
|
|
<div>
|
|
|
<main className="mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
<div className="flex items-baseline justify-between border-b border-gray-200 pt-24 pb-6">
|
|
|
<h1 className="text-4xl font-bold tracking-tight text-gray-900">
|
|
|
Развитие возобновляемой энергетики в странах мира
|
|
|
</h1>
|
|
|
<div className="flex items-center">
|
|
|
<Menu as="div" className="relative inline-block text-left">
|
|
|
<div>
|
|
|
<Menu.Button className="hidden group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
|
|
|
{mapOptions.find((option) => option.current).name}
|
|
|
<ChevronDownIcon
|
|
|
className="-mr-1 ml-1 h-5 w-5 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
</Menu.Button>
|
|
|
</div>
|
|
|
|
|
|
<Transition
|
|
|
as={Fragment}
|
|
|
enter="transition ease-out duration-100"
|
|
|
enterFrom="transform opacity-0 scale-95"
|
|
|
enterTo="transform opacity-100 scale-100"
|
|
|
leave="transition ease-in duration-75"
|
|
|
leaveFrom="transform opacity-100 scale-100"
|
|
|
leaveTo="transform opacity-0 scale-95"
|
|
|
show={true}
|
|
|
>
|
|
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
|
<div className="py-1">
|
|
|
{mapOptions.map((option) => (
|
|
|
<Menu.Item key={option.name}>
|
|
|
<a
|
|
|
href="#"
|
|
|
className={option.current ? 'font-medium text-gray-900 block px-4 py-2 text-sm' : 'text-gray-500 block px-4 py-2 text-sm'}
|
|
|
onClick={onMapOptionClick}
|
|
|
>
|
|
|
{option.name}
|
|
|
</a>
|
|
|
</Menu.Item>
|
|
|
))}
|
|
|
</div>
|
|
|
</Menu.Items>
|
|
|
</Transition>
|
|
|
</Menu>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<section aria-labelledby="products-heading" className="pt-6 pb-24">
|
|
|
<div className="grid grid-cols-1 gap-x-8 gap-y-10 md:grid-cols-4">
|
|
|
|
|
|
<form className="hidden md:block h-[65vh] px-1 py-1 overflow-y-scroll">
|
|
|
{/* Layers */}
|
|
|
<ul
|
|
|
role="list"
|
|
|
className="space-y-4 border-b border-gray-200 pb-6 text-sm font-medium text-gray-900"
|
|
|
>
|
|
|
{toggleLayers.map((layer_name) => (
|
|
|
<div key={layer_name} className="flex items-center">
|
|
|
<input
|
|
|
id={layer_name}
|
|
|
name={layer_name}
|
|
|
defaultValue={layer_name}
|
|
|
type="checkbox"
|
|
|
defaultChecked={true}
|
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
|
onChange={(event) => {
|
|
|
mapRef.current
|
|
|
.getMap()
|
|
|
.setLayoutProperty(
|
|
|
event.target.id,
|
|
|
"visibility",
|
|
|
event.target.checked ? "visible" : "none"
|
|
|
);
|
|
|
}}
|
|
|
/>
|
|
|
<label
|
|
|
htmlFor={layer_name}
|
|
|
className="ml-3 text-sm text-gray-600"
|
|
|
>
|
|
|
{layer_name}
|
|
|
</label>
|
|
|
</div>
|
|
|
))}
|
|
|
</ul>
|
|
|
|
|
|
{/* Legend */}
|
|
|
<Disclosure as="div" className="border-b border-gray-200 py-6">
|
|
|
{({ open }) => (
|
|
|
<>
|
|
|
<h3 className="-my-3 flow-root">
|
|
|
<Disclosure.Button className="flex w-full items-center justify-between bg-white py-3 text-sm text-gray-400 hover:text-gray-500">
|
|
|
<span className="font-medium text-gray-900">
|
|
|
Легенда
|
|
|
</span>
|
|
|
<span className="ml-6 flex items-center">
|
|
|
{open ? (
|
|
|
<ChevronUpIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
) : (
|
|
|
<ChevronDownIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
)}
|
|
|
</span>
|
|
|
</Disclosure.Button>
|
|
|
</h3>
|
|
|
<Disclosure.Panel className="pt-6">
|
|
|
<div className="space-y-4">
|
|
|
{generateLegend(paintFilters.find(f => f.id == "parameter_name").options.find(parameter => parameter.checked))}
|
|
|
</div>
|
|
|
</Disclosure.Panel>
|
|
|
</>
|
|
|
)}
|
|
|
</Disclosure>
|
|
|
|
|
|
{/* Filters */}
|
|
|
{paintFilters.concat(filterFilters).map((section) => (
|
|
|
<Disclosure
|
|
|
as="div"
|
|
|
key={section.id}
|
|
|
className="border-b border-gray-200 py-6"
|
|
|
>
|
|
|
{({ open }) => (
|
|
|
<>
|
|
|
<h3 className="-my-3 flow-root">
|
|
|
<Disclosure.Button className="flex w-full items-center justify-between bg-white py-3 text-sm text-gray-400 hover:text-gray-500">
|
|
|
<span className="font-medium text-gray-900">
|
|
|
{section.name}
|
|
|
</span>
|
|
|
<span className="ml-6 flex items-center">
|
|
|
{open ? (
|
|
|
<ChevronUpIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
) : (
|
|
|
<ChevronDownIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
)}
|
|
|
</span>
|
|
|
</Disclosure.Button>
|
|
|
</h3>
|
|
|
<Disclosure.Panel className="pt-6">
|
|
|
<div className="space-y-4">
|
|
|
{section.options.map((option, optionIdx) => (
|
|
|
<div
|
|
|
key={option.value}
|
|
|
className="flex items-center"
|
|
|
>
|
|
|
<input
|
|
|
id={`filter-${section.id}-${optionIdx}`}
|
|
|
name={`${section.id}`}
|
|
|
defaultValue={option.value}
|
|
|
type="radio"
|
|
|
defaultChecked={option.checked}
|
|
|
className="peer h-4 w-4 rounded-full border-gray-400 text-indigo-600 focus:ring-indigo-500 disabled:text-gray-400 disabled:border-gray-200"
|
|
|
onChange={handleFilterToggle}
|
|
|
disabled={
|
|
|
(paintFilters[0].options.find(o => o.checked).value == "res_share_generation" ||
|
|
|
paintFilters[0].options.find(o => o.checked).value == "res_share_power" ||
|
|
|
paintFilters[0].options.find(o => o.checked).value == "generation_to_population_kWh_pers") &&
|
|
|
section.id == "id_tech" && option.value < 7 ?
|
|
|
true
|
|
|
: false
|
|
|
}
|
|
|
/>
|
|
|
<label
|
|
|
htmlFor={`filter-${section.id}-${optionIdx}`}
|
|
|
className="ml-3 text-sm text-gray-600 peer-disabled:text-gray-300"
|
|
|
>
|
|
|
{option.label}
|
|
|
</label>
|
|
|
</div>
|
|
|
))}
|
|
|
</div>
|
|
|
</Disclosure.Panel>
|
|
|
</>
|
|
|
)}
|
|
|
</Disclosure>
|
|
|
))}
|
|
|
|
|
|
|
|
|
{/* Metadata */}
|
|
|
<Disclosure as="div" className="border-b border-gray-200 py-6">
|
|
|
{({ open }) => (
|
|
|
<>
|
|
|
<h3 className="-my-3 flow-root">
|
|
|
<Disclosure.Button className="flex w-full items-center justify-between bg-white py-3 text-sm text-gray-400 hover:text-gray-500">
|
|
|
<span className="font-medium text-gray-900">
|
|
|
Метаданные
|
|
|
</span>
|
|
|
<span className="ml-6 flex items-center">
|
|
|
{open ? (
|
|
|
<ChevronUpIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
) : (
|
|
|
<ChevronDownIcon
|
|
|
className="h-5 w-5"
|
|
|
aria-hidden="true"
|
|
|
/>
|
|
|
)}
|
|
|
</span>
|
|
|
</Disclosure.Button>
|
|
|
</h3>
|
|
|
<Disclosure.Panel className="pt-6">
|
|
|
<div className="space-y-4">
|
|
|
<p className="text-sm">
|
|
|
Границы показаны по состоянию на 2021 год
|
|
|
</p>
|
|
|
<p className="text-sm">
|
|
|
© Ассоциация развития возобновляемой энергетики
|
|
|
</p>
|
|
|
</div>
|
|
|
</Disclosure.Panel>
|
|
|
</>
|
|
|
)}
|
|
|
</Disclosure>
|
|
|
</form>
|
|
|
|
|
|
{/* Map itself */}
|
|
|
<div className="md:col-span-3">
|
|
|
<div className="h-[65vh]">
|
|
|
<Map
|
|
|
initialViewState={{
|
|
|
latitude: mapstyle.center[0],
|
|
|
longitude: mapstyle.center[1],
|
|
|
zoom: mapstyle.zoom,
|
|
|
}}
|
|
|
mapStyle={basemap}
|
|
|
minZoom={mapstyle.minZoom}
|
|
|
ref={mapRef}
|
|
|
interactiveLayerIds={["power_fact_mw"]}
|
|
|
onClick={handleClick}
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
maxZoom={6}
|
|
|
hash
|
|
|
>
|
|
|
|
|
|
{open && <Popup
|
|
|
longitude={popupInfo.lon}
|
|
|
latitude={popupInfo.lat}
|
|
|
closeButton={false}
|
|
|
>
|
|
|
{popupInfo.country_russian_name}
|
|
|
</Popup>}
|
|
|
<ScaleControl />
|
|
|
<Source
|
|
|
id="remap"
|
|
|
{...mapstyle.sources.remap}
|
|
|
>
|
|
|
<Layer {...paintedLayers["power_fact_mw"]} paint={applyPaintFilters(paintFilters)} filter={applyFilterFilters(filterFilters)} />
|
|
|
<Layer {...paintedLayers["Границы"]} />
|
|
|
</Source>
|
|
|
</Map>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
</main>
|
|
|
<Transition.Root show={open} as={Fragment}>
|
|
|
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
|
|
<Transition.Child
|
|
|
as={Fragment}
|
|
|
enter="ease-in-out duration-500"
|
|
|
enterFrom="opacity-0"
|
|
|
enterTo="opacity-100"
|
|
|
leave="ease-in-out duration-500"
|
|
|
leaveFrom="opacity-100"
|
|
|
leaveTo="opacity-0"
|
|
|
>
|
|
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
|
|
</Transition.Child>
|
|
|
|
|
|
<div className="fixed inset-0 overflow-hidden">
|
|
|
<div className="absolute inset-0 overflow-hidden">
|
|
|
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
|
|
|
<Transition.Child
|
|
|
as={Fragment}
|
|
|
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
|
|
enterFrom="translate-x-full"
|
|
|
enterTo="translate-x-0"
|
|
|
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
|
|
leaveFrom="translate-x-0"
|
|
|
leaveTo="translate-x-full"
|
|
|
>
|
|
|
<Dialog.Panel className="pointer-events-auto relative w-screen max-w-md">
|
|
|
<Transition.Child
|
|
|
as={Fragment}
|
|
|
enter="ease-in-out duration-500"
|
|
|
enterFrom="opacity-0"
|
|
|
enterTo="opacity-100"
|
|
|
leave="ease-in-out duration-500"
|
|
|
leaveFrom="opacity-100"
|
|
|
leaveTo="opacity-0"
|
|
|
>
|
|
|
<div className="absolute top-0 left-0 -ml-8 flex pt-4 pr-2 sm:-ml-10 sm:pr-4">
|
|
|
<button
|
|
|
type="button"
|
|
|
className="rounded-md text-gray-300 hover:text-white focus:outline-none focus:ring-2 focus:ring-white"
|
|
|
onClick={() => setOpen(false)}
|
|
|
>
|
|
|
<span className="sr-only">Close panel</span>
|
|
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
|
|
</button>
|
|
|
</div>
|
|
|
</Transition.Child>
|
|
|
<div className="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl">
|
|
|
<div className="px-4 sm:px-6">
|
|
|
<Dialog.Title className="text-lg font-medium text-gray-900">{popupInfo && popupInfo.country_russian_name}</Dialog.Title>
|
|
|
</div>
|
|
|
<div className="relative mt-6 flex-1 px-4 sm:px-6">
|
|
|
{/* Replace with your content */}
|
|
|
{popupInfo && generatePopup(popupInfo)}
|
|
|
{/* /End replace */}
|
|
|
</div>
|
|
|
</div>
|
|
|
</Dialog.Panel>
|
|
|
</Transition.Child>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</Dialog>
|
|
|
</Transition.Root>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|