diff --git a/admin/app/controllers/maps_controller.ts b/admin/app/controllers/maps_controller.ts index 31035ee..dd93a8b 100644 --- a/admin/app/controllers/maps_controller.ts +++ b/admin/app/controllers/maps_controller.ts @@ -167,11 +167,19 @@ export default class MapsController { vine.object({ name: vine.string().trim().minLength(1).maxLength(255).optional(), color: vine.string().trim().maxLength(20).optional(), + longitude: vine.number().min(-180).max(180).optional(), + latitude: vine.number().min(-90).max(90).optional(), + notes: vine.string().trim().nullable().optional(), + marker_type: vine.string().trim().maxLength(20).optional(), }) ) ) if (payload.name !== undefined) marker.name = payload.name if (payload.color !== undefined) marker.color = payload.color + if (payload.longitude !== undefined) marker.longitude = payload.longitude + if (payload.latitude !== undefined) marker.latitude = payload.latitude + if (payload.notes !== undefined) marker.notes = payload.notes + if (payload.marker_type !== undefined) marker.marker_type = payload.marker_type await marker.save() return marker } diff --git a/admin/inertia/components/maps/MapComponent.tsx b/admin/inertia/components/maps/MapComponent.tsx index d738638..1fe5208 100644 --- a/admin/inertia/components/maps/MapComponent.tsx +++ b/admin/inertia/components/maps/MapComponent.tsx @@ -16,7 +16,7 @@ import MarkerPin from './MarkerPin' import MarkerPanel from './MarkerPanel' import ViewMapMarkerPopup from './ViewMapMarkerPopup' import MapMarkerFormPopup from './MapMarkerFormPopup' -import ScaleUnitControl from './ScaleUnitControl' +import ScaleUnitToggle from './ScaleUnitToggle' type ScaleUnit = 'imperial' | 'metric' @@ -94,7 +94,7 @@ export default function MapComponent() { - + {markers.map((marker) => ( )} - {selectedMarker && editingMarkerId !== selectedMarker.id && ( - setSelectedMarkerId(null)} - onEdit={() => setEditingMarkerId(selectedMarker.id)} - /> - )} - {selectedMarker && editingMarkerId === selectedMarker.id && ( { - const preset = PIN_COLORS.find((pinColor) => pinColor.id === color) - - if (preset) return preset.label - - return color.replace('#', '').toLowerCase() -} - type SortField = 'name' | 'color' type SortDirection = 'asc' | 'desc' +const normalizeColorHex = (color: string) => { + const preset = PIN_COLORS.find((pinColor) => pinColor.id === color) + return preset?.hex ?? color +} + +const getHueSortValue = (color: string) => { + const hex = normalizeColorHex(color).replace('#', '') + + if (!/^[0-9a-fA-F]{6}$/.test(hex)) return 0 + + const r = parseInt(hex.slice(0, 2), 16) / 255 + const g = parseInt(hex.slice(2, 4), 16) / 255 + const b = parseInt(hex.slice(4, 6), 16) / 255 + + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const delta = max - min + + if (delta === 0) return 0 + + let hue = 0 + + if (max === r) { + hue = ((g - b) / delta) % 6 + } else if (max === g) { + hue = (b - r) / delta + 2 + } else { + hue = (r - g) / delta + 4 + } + + return Math.round(hue * 60 + 360) % 360 +} + export default function MarkerPanel({ markers, onDelete, @@ -35,6 +60,15 @@ export default function MarkerPanel({ const [sortField, setSortField] = useState('name') const [sortDirection, setSortDirection] = useState('asc') + const sortDirectionLabel = + sortField === 'name' + ? sortDirection === 'asc' + ? 'A → Z' + : 'Z → A' + : sortDirection === 'asc' + ? 'Hue ↑' + : 'Hue ↓' + const visibleMarkers = useMemo(() => { const query = searchQuery.trim().toLowerCase() @@ -44,12 +78,10 @@ export default function MarkerPanel({ return marker.name.toLowerCase().includes(query) }) .sort((a, b) => { - const aValue = sortField === 'name' ? a.name : getColorSortValue(a.color) - const bValue = sortField === 'name' ? b.name : getColorSortValue(b.color) - - const result = aValue.localeCompare(bValue, undefined, { - sensitivity: 'base', - }) + const result = + sortField === 'name' + ? a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) + : getHueSortValue(a.color) - getHueSortValue(b.color) return sortDirection === 'asc' ? result : -result }) @@ -116,7 +148,7 @@ export default function MarkerPanel({ className="flex-1 rounded border border-border-default bg-surface-primary px-2 py-1 text-xs text-text-primary focus:border-desert-green focus:outline-none" > - + @@ -154,7 +186,7 @@ export default function MarkerPanel({ size={16} className="shrink-0" style={{ - color: PIN_COLORS.find((color) => color.id === marker.color)?.hex ?? '#a84a12', + color: normalizeColorHex(marker.color), }} /> diff --git a/admin/inertia/components/maps/ScaleUnitControl.tsx b/admin/inertia/components/maps/ScaleUnitToggle.tsx similarity index 93% rename from admin/inertia/components/maps/ScaleUnitControl.tsx rename to admin/inertia/components/maps/ScaleUnitToggle.tsx index 06d94f9..32cb788 100644 --- a/admin/inertia/components/maps/ScaleUnitControl.tsx +++ b/admin/inertia/components/maps/ScaleUnitToggle.tsx @@ -5,7 +5,7 @@ type ScaleUnitControlProps = { onChange: (unit: ScaleUnit) => void } -export default function ScaleUnitControl({ scaleUnit, onChange }: ScaleUnitControlProps) { +export default function ScaleUnitToggle({ scaleUnit, onChange }: ScaleUnitControlProps) { return (
- + ( + + {children} + + ), + }} + > {marker.notes}