- Added dark mode styles to TaskStatusDropdown, TaskStatusDropdownDebug, and TaskStatusDropdownSimple components. - Introduced ThemeProvider and useTheme hook for managing theme state. - Updated Button, Card, Input, Loading, Navigation, PageContainer, PageHeader, ProjectCalendarWidget, ProjectMap, SearchBar, States, Tooltip, and other UI components to support dark mode. - Created ThemeToggle component for switching between light and dark modes. - Enhanced i18n translations for settings related to theme and language preferences. - Configured Tailwind CSS to support dark mode with class-based toggling.
177 lines
5.3 KiB
JavaScript
177 lines
5.3 KiB
JavaScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import dynamic from "next/dynamic";
|
|
|
|
// Dynamically import the map component to avoid SSR issues
|
|
const DynamicMap = dynamic(() => import("./LeafletMap"), {
|
|
ssr: false,
|
|
loading: () => (
|
|
<div className="w-full h-64 bg-gray-100 dark:bg-gray-700 animate-pulse rounded-lg flex items-center justify-center">
|
|
<span className="text-gray-500 dark:text-gray-400">Loading map...</span>
|
|
</div>
|
|
),
|
|
});
|
|
|
|
export default function ProjectMap({
|
|
coordinates,
|
|
projectName,
|
|
projectStatus = "registered",
|
|
showLayerControl = true,
|
|
mapHeight = "h-64",
|
|
defaultLayer = "OpenStreetMap",
|
|
showOverlays = true,
|
|
}) {
|
|
const [coords, setCoords] = useState(null);
|
|
|
|
// Status configuration matching the main map
|
|
const statusConfig = {
|
|
registered: { color: "#6B7280", label: "Registered" },
|
|
in_progress_design: { color: "#3B82F6", label: "In Progress (Design)" },
|
|
in_progress_construction: {
|
|
color: "#F59E0B",
|
|
label: "In Progress (Construction)",
|
|
},
|
|
fulfilled: { color: "#10B981", label: "Completed" },
|
|
cancelled: { color: "#EF4444", label: "Cancelled" },
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (coordinates) {
|
|
// Parse coordinates string (e.g., "49.622958,20.629562")
|
|
const [lat, lng] = coordinates
|
|
.split(",")
|
|
.map((coord) => parseFloat(coord.trim()));
|
|
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
setCoords({ lat, lng });
|
|
}
|
|
}
|
|
}, [coordinates]);
|
|
if (!coords) {
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
Lokalizacja projektu
|
|
</h3>
|
|
<div className="text-xs text-gray-500 dark:text-gray-400">No coordinates available</div>
|
|
</div>
|
|
<div
|
|
className={`w-full ${mapHeight} rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center`}
|
|
>
|
|
<div className="text-center text-gray-500 dark:text-gray-400">
|
|
<svg
|
|
className="w-8 h-8 mx-auto mb-2"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
<p className="text-sm">Map unavailable</p>
|
|
<p className="text-xs">No coordinates provided</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const statusInfo = statusConfig[projectStatus] || statusConfig.registered;
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
Lokalizacja projektu
|
|
</h3>
|
|
<div
|
|
className="w-3 h-3 rounded-full border border-white dark:border-gray-800 shadow-sm"
|
|
style={{ backgroundColor: statusInfo.color }}
|
|
title={`Status: ${statusInfo.label}`}
|
|
></div>
|
|
</div>
|
|
{showLayerControl && (
|
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
{/* Use the layer control (📚) to switch map views */}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div
|
|
className={`w-full ${mapHeight} rounded-lg border border-gray-200 dark:border-gray-600`}
|
|
>
|
|
<DynamicMap
|
|
center={[coords.lat, coords.lng]}
|
|
zoom={15}
|
|
markers={[
|
|
{
|
|
position: [coords.lat, coords.lng],
|
|
color: statusInfo.color,
|
|
popup: (
|
|
<div className="min-w-48">
|
|
<div className="mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
|
|
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
|
|
{projectName || "Project Location"}
|
|
</h4>
|
|
<span
|
|
className="inline-block px-2 py-1 rounded-full text-xs font-medium text-white"
|
|
style={{ backgroundColor: statusInfo.color }}
|
|
>
|
|
{statusInfo.label}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
<div className="flex items-center gap-2">
|
|
<svg
|
|
className="w-4 h-4 text-gray-400 dark:text-gray-500"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
|
/>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
|
/>
|
|
</svg>
|
|
<span className="font-mono text-xs">
|
|
{coords.lat.toFixed(6)}, {coords.lng.toFixed(6)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
),
|
|
},
|
|
]}
|
|
showLayerControl={showLayerControl}
|
|
defaultLayer={defaultLayer}
|
|
showOverlays={showOverlays}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-xs text-gray-500">
|
|
{coords.lat.toFixed(6)}, {coords.lng.toFixed(6)}
|
|
</p>
|
|
<div className="flex items-center gap-1 text-xs text-gray-500">
|
|
<div
|
|
className="w-2 h-2 rounded-full"
|
|
style={{ backgroundColor: statusInfo.color }}
|
|
></div>
|
|
<span>{statusInfo.label}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|