mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Extract tag serializer to its own file
This commit is contained in:
parent
449884796f
commit
1d07eb652d
11 changed files with 121 additions and 2990 deletions
|
|
@ -1,171 +0,0 @@
|
||||||
# Layer Control Upgrade - Leaflet.Control.Layers.Tree
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Successfully installed and integrated the `Leaflet.Control.Layers.Tree` plugin to replace the standard Leaflet layer control with a hierarchical tree-based control that better organizes map layers and styles.
|
|
||||||
|
|
||||||
## Changes Made
|
|
||||||
|
|
||||||
### 1. Installation
|
|
||||||
|
|
||||||
- **Plugin**: Installed `leaflet.control.layers.tree` via importmap
|
|
||||||
- **CSS**: Added plugin CSS file at `app/assets/stylesheets/leaflet.control.layers.tree.css`
|
|
||||||
|
|
||||||
### 2. Maps Controller Updates
|
|
||||||
|
|
||||||
#### File: `app/javascript/controllers/maps_controller.js`
|
|
||||||
|
|
||||||
**Import Changes:**
|
|
||||||
- Added import for `leaflet.control.layers.tree`
|
|
||||||
- Removed import for `createPlacesControl` (now integrated into tree control)
|
|
||||||
|
|
||||||
**Initialization Changes:**
|
|
||||||
- Removed standalone Places control initialization
|
|
||||||
- Added `this.userTags` property to store user tags for places filtering
|
|
||||||
- Updated layer control initialization to use `createTreeLayerControl()`
|
|
||||||
|
|
||||||
**New Methods:**
|
|
||||||
|
|
||||||
1. **`createTreeLayerControl(additionalLayers = {})`**
|
|
||||||
- Creates a hierarchical tree structure for map layers
|
|
||||||
- Organizes layers into two main groups:
|
|
||||||
- **Map Styles**: All available base map layers
|
|
||||||
- **Layers**: All overlay layers with nested groups
|
|
||||||
- Supports dynamic additional layers (e.g., Family Members)
|
|
||||||
|
|
||||||
Structure:
|
|
||||||
```
|
|
||||||
+ Map Styles
|
|
||||||
- OpenStreetMap
|
|
||||||
- OpenStreetMap.HOT
|
|
||||||
- ...
|
|
||||||
+ Layers
|
|
||||||
- Points
|
|
||||||
- Routes
|
|
||||||
- Tracks
|
|
||||||
- Heatmap
|
|
||||||
- Fog of War
|
|
||||||
- Scratch map
|
|
||||||
- Areas
|
|
||||||
- Photos
|
|
||||||
+ Visits
|
|
||||||
- Suggested
|
|
||||||
- Confirmed
|
|
||||||
+ Places
|
|
||||||
- All
|
|
||||||
- Untagged
|
|
||||||
- (each tag with icon)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Updated Methods:**
|
|
||||||
- **`updateLayerControl()`**: Simplified to just recreate the tree control with additional layers
|
|
||||||
- Updated all layer control recreations throughout the file to use `createTreeLayerControl()`
|
|
||||||
|
|
||||||
### 3. Places Manager Updates
|
|
||||||
|
|
||||||
#### File: `app/javascript/maps/places.js`
|
|
||||||
|
|
||||||
**New Methods:**
|
|
||||||
|
|
||||||
1. **`createFilteredLayer(tagIds)`**
|
|
||||||
- Creates a layer group for filtered places
|
|
||||||
- Returns a layer that loads places when added to the map
|
|
||||||
- Supports tag-based and untagged filtering
|
|
||||||
|
|
||||||
2. **`loadPlacesIntoLayer(layer, tagIds)`**
|
|
||||||
- Loads places into a specific layer with tag filtering
|
|
||||||
- Handles API calls with tag_ids or untagged parameters
|
|
||||||
- Creates markers using existing `createPlaceMarker()` method
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### Hierarchical Organization
|
|
||||||
- Map styles and layers are now clearly separated
|
|
||||||
- Related layers are grouped together (Visits, Places)
|
|
||||||
- Easy to expand/collapse sections
|
|
||||||
|
|
||||||
### Places Layer Integration
|
|
||||||
- No longer needs a separate control
|
|
||||||
- All places filters are now in the tree control
|
|
||||||
- Each tag gets its own layer in the tree
|
|
||||||
- Places group has "All", "Untagged", and individual tag layers regardless of tags
|
|
||||||
- "Untagged" shows only places without tags
|
|
||||||
|
|
||||||
### Dynamic Layer Support
|
|
||||||
- Family Members layer can be added dynamically
|
|
||||||
- Additional layers can be easily integrated
|
|
||||||
- Maintains compatibility with existing layer management
|
|
||||||
|
|
||||||
### Improved User Experience
|
|
||||||
- Cleaner UI with collapsible sections
|
|
||||||
- Better organization of many layers
|
|
||||||
- Consistent interface for all layer types
|
|
||||||
- Select All checkbox for grouped layers (Visits, Places)
|
|
||||||
|
|
||||||
## API Changes
|
|
||||||
|
|
||||||
### Places API
|
|
||||||
The Places API now supports an `untagged` parameter:
|
|
||||||
- `GET /api/v1/places?untagged=true` - Returns only untagged places
|
|
||||||
- `GET /api/v1/places?tag_ids=1,2,3` - Returns places with specified tags
|
|
||||||
|
|
||||||
## Testing Recommendations
|
|
||||||
|
|
||||||
1. **Basic Functionality**
|
|
||||||
- Verify all map styles load correctly
|
|
||||||
- Test all overlay layers (Points, Routes, Tracks, etc.)
|
|
||||||
- Confirm layer visibility persists correctly
|
|
||||||
|
|
||||||
2. **Places Integration**
|
|
||||||
- Test "All" layer shows all places
|
|
||||||
- Verify "Untagged" layer shows only untagged places
|
|
||||||
- Test individual tag layers show correct places
|
|
||||||
- Confirm places load when layer is enabled
|
|
||||||
|
|
||||||
3. **Visits Integration**
|
|
||||||
- Test Suggested and Confirmed visits layers
|
|
||||||
- Verify visits load correctly when enabled
|
|
||||||
|
|
||||||
4. **Family Members**
|
|
||||||
- Test Family Members layer appears when family is available
|
|
||||||
- Verify layer updates when family locations change
|
|
||||||
|
|
||||||
5. **Layer State Persistence**
|
|
||||||
- Verify enabled layers are saved to user settings
|
|
||||||
- Confirm layer state is restored on page load
|
|
||||||
|
|
||||||
## Migration Notes
|
|
||||||
|
|
||||||
### Removed Components
|
|
||||||
- Standalone Places control button (📍)
|
|
||||||
- `createPlacesControl` function no longer used in maps_controller
|
|
||||||
|
|
||||||
### Behavioral Changes
|
|
||||||
- Places layer is no longer managed by a separate control
|
|
||||||
- All places filtering is now done through the layer control
|
|
||||||
- Places markers are created on-demand when layer is enabled
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
1. **Layer Icons**: Add custom icons for each layer type
|
|
||||||
2. **Layer Counts**: Show number of items in each layer
|
|
||||||
3. **Custom Styling**: Theme the tree control to match app theme
|
|
||||||
4. **Layer Search**: Add search functionality for finding layers
|
|
||||||
5. **Layer Presets**: Allow saving custom layer combinations
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
1. `app/javascript/controllers/maps_controller.js` - Main map controller
|
|
||||||
2. `app/javascript/maps/places.js` - Places manager with new filtering methods
|
|
||||||
3. `config/importmap.rb` - Added tree control import (via bin/importmap)
|
|
||||||
4. `app/assets/stylesheets/leaflet.control.layers.tree.css` - Plugin CSS
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
If needed, to rollback:
|
|
||||||
1. Remove `import "leaflet.control.layers.tree"` from maps_controller.js
|
|
||||||
2. Restore `import { createPlacesControl }` from places_control
|
|
||||||
3. Revert `createTreeLayerControl()` to `L.control.layers()`
|
|
||||||
4. Restore Places control initialization
|
|
||||||
5. Remove `leaflet.control.layers.tree` from importmap
|
|
||||||
6. Remove CSS file
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
# Places Integration Checklist
|
|
||||||
|
|
||||||
## Files Modified:
|
|
||||||
- ✅ `app/javascript/controllers/stat_page_controller.js` - Added PlacesManager integration
|
|
||||||
- ✅ `app/javascript/maps/places.js` - Fixed API authentication headers
|
|
||||||
- ✅ `app/views/stats/_month.html.erb` - Added Places button and tag filters
|
|
||||||
- ✅ `app/views/shared/_place_creation_modal.html.erb` - Already exists
|
|
||||||
|
|
||||||
## What Should Appear:
|
|
||||||
|
|
||||||
### On Monthly Stats Page (`/stats/YYYY/MM`):
|
|
||||||
|
|
||||||
1. **Map Controls** (top right of map):
|
|
||||||
- [ ] "Heatmap" button
|
|
||||||
- [ ] "Points" button
|
|
||||||
- [ ] **"Places" button** ← NEW!
|
|
||||||
|
|
||||||
2. **Below the Map**:
|
|
||||||
- [ ] **"Filter Places by Tags"** section ← NEW!
|
|
||||||
- [ ] Checkboxes for each tag you've created
|
|
||||||
- [ ] Each checkbox shows: icon + name + color dot
|
|
||||||
|
|
||||||
## Troubleshooting Steps:
|
|
||||||
|
|
||||||
### Step 1: Restart Server
|
|
||||||
```bash
|
|
||||||
# Stop server (Ctrl+C)
|
|
||||||
bundle exec rails server
|
|
||||||
|
|
||||||
# Or with Docker:
|
|
||||||
docker-compose restart web
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Hard Refresh Browser
|
|
||||||
- Mac: `Cmd + Shift + R`
|
|
||||||
- Windows/Linux: `Ctrl + Shift + R`
|
|
||||||
|
|
||||||
### Step 3: Check Browser Console
|
|
||||||
1. Open Developer Tools (F12)
|
|
||||||
2. Go to Console tab
|
|
||||||
3. Look for errors (red text)
|
|
||||||
4. You should see: "StatPage controller connected"
|
|
||||||
|
|
||||||
### Step 4: Verify URL
|
|
||||||
Make sure you're on a monthly stats page:
|
|
||||||
- ✅ `/stats/2024/11` ← Correct
|
|
||||||
- ❌ `/stats` ← Wrong (main stats index)
|
|
||||||
- ❌ `/stats/2024` ← Wrong (yearly stats)
|
|
||||||
|
|
||||||
### Step 5: Check JavaScript Loading
|
|
||||||
In browser console, type:
|
|
||||||
```javascript
|
|
||||||
console.log(document.querySelector('[data-controller="stat-page"]'))
|
|
||||||
```
|
|
||||||
Should show the element, not null.
|
|
||||||
|
|
||||||
### Step 6: Verify Controller Registration
|
|
||||||
In browser console:
|
|
||||||
```javascript
|
|
||||||
console.log(application.controllers)
|
|
||||||
```
|
|
||||||
Should include "stat-page" in the list.
|
|
||||||
|
|
||||||
## Expected Behavior:
|
|
||||||
|
|
||||||
### When You Click "Places" Button:
|
|
||||||
1. Places layer toggles on/off
|
|
||||||
2. Button highlights when active
|
|
||||||
3. Map shows custom markers with tag icons
|
|
||||||
|
|
||||||
### When You Check Tag Filters:
|
|
||||||
1. Map updates immediately
|
|
||||||
2. Shows only places with selected tags
|
|
||||||
3. Unchecking all shows all places
|
|
||||||
|
|
||||||
## If Nothing Shows:
|
|
||||||
|
|
||||||
### Check if you have any places created:
|
|
||||||
```bash
|
|
||||||
bundle exec rails console
|
|
||||||
|
|
||||||
# In console:
|
|
||||||
user = User.find_by(email: 'your@email.com')
|
|
||||||
user.places.count # Should be > 0
|
|
||||||
user.tags.count # Should be > 0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create test data:
|
|
||||||
```bash
|
|
||||||
bundle exec rails console
|
|
||||||
|
|
||||||
user = User.first
|
|
||||||
tag = user.tags.create!(name: "Test", icon: "📍", color: "#FF5733")
|
|
||||||
|
|
||||||
# Create via API or console:
|
|
||||||
place = user.places.create!(
|
|
||||||
name: "Test Place",
|
|
||||||
latitude: 40.7128,
|
|
||||||
longitude: -74.0060,
|
|
||||||
source: :manual
|
|
||||||
)
|
|
||||||
place.tags << tag
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Script:
|
|
||||||
|
|
||||||
Run this in Rails console to verify everything:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
user = User.first
|
|
||||||
puts "Tags: #{user.tags.count}"
|
|
||||||
puts "Places: #{user.places.count}"
|
|
||||||
puts "Places with tags: #{user.places.joins(:tags).distinct.count}"
|
|
||||||
|
|
||||||
if user.tags.any?
|
|
||||||
puts "\nYour tags:"
|
|
||||||
user.tags.each do |tag|
|
|
||||||
puts " #{tag.icon} #{tag.name} (#{tag.places.count} places)"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.places.any?
|
|
||||||
puts "\nYour places:"
|
|
||||||
user.places.limit(5).each do |place|
|
|
||||||
puts " #{place.name} at (#{place.latitude}, #{place.longitude})"
|
|
||||||
puts " Tags: #{place.tags.map(&:name).join(', ')}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Still Having Issues?
|
|
||||||
|
|
||||||
Check these files exist and have the right content:
|
|
||||||
- `app/javascript/maps/places.js` - Should export PlacesManager class
|
|
||||||
- `app/javascript/controllers/stat_page_controller.js` - Should import PlacesManager
|
|
||||||
- `app/views/stats/_month.html.erb` - Should have Places button at line ~73
|
|
||||||
|
|
||||||
Look for JavaScript errors in browser console that might indicate:
|
|
||||||
- Import/export issues
|
|
||||||
- Syntax errors
|
|
||||||
- Missing dependencies
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
# Layer Control Upgrade - Testing Checklist
|
|
||||||
|
|
||||||
## Pre-Testing Setup
|
|
||||||
|
|
||||||
1. **Start the development server**
|
|
||||||
```bash
|
|
||||||
bin/dev
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Clear browser cache** to ensure new JavaScript and CSS are loaded
|
|
||||||
|
|
||||||
3. **Log in** to the application with demo credentials or your account
|
|
||||||
|
|
||||||
4. **Navigate to the Map page** (`/map`)
|
|
||||||
|
|
||||||
## Visual Verification
|
|
||||||
|
|
||||||
- [ ] Layer control appears in the top-right corner
|
|
||||||
- [ ] Layer control shows a hierarchical tree structure (not flat list)
|
|
||||||
- [ ] Control has two main sections: "Map Styles" and "Layers"
|
|
||||||
- [ ] Sections can be expanded/collapsed
|
|
||||||
- [ ] No standalone Places control button (📍) is visible
|
|
||||||
|
|
||||||
## Map Styles Testing
|
|
||||||
|
|
||||||
- [ ] Expand "Map Styles" section
|
|
||||||
- [ ] All map styles are listed (OpenStreetMap, OpenStreetMap.HOT, etc.)
|
|
||||||
- [ ] Selecting a different style changes the base map
|
|
||||||
- [ ] Only one map style can be selected at a time
|
|
||||||
- [ ] Selected style is indicated with a radio button
|
|
||||||
|
|
||||||
## Layers Testing
|
|
||||||
|
|
||||||
### Basic Layers
|
|
||||||
- [ ] Expand "Layers" section
|
|
||||||
- [ ] All basic layers are present:
|
|
||||||
- [ ] Points
|
|
||||||
- [ ] Routes
|
|
||||||
- [ ] Tracks
|
|
||||||
- [ ] Heatmap
|
|
||||||
- [ ] Fog of War
|
|
||||||
- [ ] Scratch map
|
|
||||||
- [ ] Areas
|
|
||||||
- [ ] Photos
|
|
||||||
|
|
||||||
- [ ] Toggle each layer on/off
|
|
||||||
- [ ] Verify each layer displays correctly when enabled
|
|
||||||
- [ ] Multiple layers can be enabled simultaneously
|
|
||||||
|
|
||||||
### Visits Group
|
|
||||||
- [ ] Expand "Visits" section
|
|
||||||
- [ ] Two sub-layers are present:
|
|
||||||
- [ ] Suggested
|
|
||||||
- [ ] Confirmed
|
|
||||||
- [ ] Enable "Suggested" - suggested visits appear on map
|
|
||||||
- [ ] Enable "Confirmed" - confirmed visits appear on map
|
|
||||||
- [ ] Disable both - no visits visible on map
|
|
||||||
- [ ] Select All checkbox works for Visits group
|
|
||||||
|
|
||||||
### Places Group
|
|
||||||
- [ ] Expand "Places" section
|
|
||||||
- [ ] At least these options are present:
|
|
||||||
- [ ] Places (top-level checkbox)
|
|
||||||
- [ ] Untagged
|
|
||||||
- [ ] (Individual tags if any exist)
|
|
||||||
|
|
||||||
**Testing "Places (top-level checkbox)":**
|
|
||||||
- [ ] Enable "Places (top-level checkbox)"
|
|
||||||
- [ ] All places appear on map regardless of tags
|
|
||||||
- [ ] Place markers are clickable
|
|
||||||
- [ ] Place popups show correct information
|
|
||||||
|
|
||||||
**Testing "Untagged":**
|
|
||||||
- [ ] Enable "Untagged" (disable "Places (top-level checkbox)" first)
|
|
||||||
- [ ] Only places without tags appear
|
|
||||||
- [ ] Verify by checking places that have tags don't appear
|
|
||||||
|
|
||||||
**Testing Individual Tags:**
|
|
||||||
(If you have tags created)
|
|
||||||
- [ ] Each tag appears as a separate layer
|
|
||||||
- [ ] Tag icon is displayed before tag name
|
|
||||||
- [ ] Enable a tag layer
|
|
||||||
- [ ] Only places with that tag appear
|
|
||||||
- [ ] Multiple tag layers can be enabled simultaneously
|
|
||||||
- [ ] Select All checkbox works for Places group
|
|
||||||
|
|
||||||
### Family Members (if applicable)
|
|
||||||
- [ ] If in a family, "Family Members" layer appears
|
|
||||||
- [ ] Enable Family Members layer
|
|
||||||
- [ ] Family member locations appear on map
|
|
||||||
- [ ] Family member markers are distinguishable from own markers
|
|
||||||
|
|
||||||
## Functional Testing
|
|
||||||
|
|
||||||
### Layer Persistence
|
|
||||||
- [ ] Enable several layers (e.g., Points, Routes, Suggested Visits, Places (top-level checkbox))
|
|
||||||
- [ ] Refresh the page
|
|
||||||
- [ ] Verify enabled layers remain enabled after refresh
|
|
||||||
- [ ] Verify disabled layers remain disabled after refresh
|
|
||||||
|
|
||||||
### Places API Integration
|
|
||||||
- [ ] Open browser console (F12)
|
|
||||||
- [ ] Enable "Network" tab
|
|
||||||
- [ ] Enable "Untagged" places layer
|
|
||||||
- [ ] Verify API call: `GET /api/v1/places?api_key=...&untagged=true`
|
|
||||||
- [ ] Enable a tag layer
|
|
||||||
- [ ] Verify API call: `GET /api/v1/places?api_key=...&tag_ids=<tag_id>`
|
|
||||||
- [ ] Verify no JavaScript errors in console
|
|
||||||
|
|
||||||
### Layer Interaction
|
|
||||||
- [ ] Enable Routes layer
|
|
||||||
- [ ] Click on a route segment
|
|
||||||
- [ ] Verify route details popup appears
|
|
||||||
- [ ] Enable Places "Places (top-level checkbox)" layer
|
|
||||||
- [ ] Click on a place marker
|
|
||||||
- [ ] Verify place details popup appears
|
|
||||||
- [ ] Verify layers don't interfere with each other
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- [ ] Enable all layers simultaneously
|
|
||||||
- [ ] Map remains responsive
|
|
||||||
- [ ] No significant lag when toggling layers
|
|
||||||
- [ ] No memory leaks (check browser dev tools)
|
|
||||||
|
|
||||||
## Edge Cases
|
|
||||||
|
|
||||||
### No Tags Scenario
|
|
||||||
- [ ] If no tags exist, Places section should show:
|
|
||||||
- [ ] Places (top-level checkbox)
|
|
||||||
- [ ] Untagged
|
|
||||||
- [ ] No error in console
|
|
||||||
|
|
||||||
### No Places Scenario
|
|
||||||
- [ ] Disable all place layers
|
|
||||||
- [ ] Enable "Untagged"
|
|
||||||
- [ ] Verify appropriate message or empty state
|
|
||||||
- [ ] No errors in console
|
|
||||||
|
|
||||||
### No Family Scenario
|
|
||||||
- [ ] If not in a family, "Family Members" layer shouldn't appear
|
|
||||||
- [ ] No errors in console
|
|
||||||
|
|
||||||
## Regression Testing
|
|
||||||
|
|
||||||
### Existing Functionality
|
|
||||||
- [ ] Routes/Tracks selector still works (if visible with `tracks_debug=true`)
|
|
||||||
- [ ] Settings panel still works
|
|
||||||
- [ ] Calendar panel still works
|
|
||||||
- [ ] Visit selection tool still works
|
|
||||||
- [ ] Add visit button still works
|
|
||||||
|
|
||||||
### Other Controllers
|
|
||||||
- [ ] Family members controller still works (if applicable)
|
|
||||||
- [ ] Photo markers still load correctly
|
|
||||||
- [ ] Area drawing still works
|
|
||||||
- [ ] Fog of war updates correctly
|
|
||||||
|
|
||||||
## Mobile Testing (if applicable)
|
|
||||||
|
|
||||||
- [ ] Layer control is accessible on mobile
|
|
||||||
- [ ] Tree structure expands/collapses on tap
|
|
||||||
- [ ] Layers can be toggled on mobile
|
|
||||||
- [ ] No layout issues on small screens
|
|
||||||
|
|
||||||
## Error Scenarios
|
|
||||||
|
|
||||||
- [ ] Disconnect internet, try to load a layer that requires API call
|
|
||||||
- [ ] Verify appropriate error handling
|
|
||||||
- [ ] Verify user gets feedback about the failure
|
|
||||||
- [ ] Verify app doesn't crash
|
|
||||||
|
|
||||||
## Console Checks
|
|
||||||
|
|
||||||
Throughout all testing, monitor the browser console for:
|
|
||||||
- [ ] No JavaScript errors
|
|
||||||
- [ ] No unexpected warnings
|
|
||||||
- [ ] No failed API requests (except during error scenario testing)
|
|
||||||
- [ ] Appropriate log messages for debugging
|
|
||||||
|
|
||||||
## Sign-off
|
|
||||||
|
|
||||||
- [ ] All critical tests pass
|
|
||||||
- [ ] Any failures are documented
|
|
||||||
- [ ] Ready for production deployment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
Record any issues, unexpected behavior, or suggestions for improvement:
|
|
||||||
|
|
||||||
```
|
|
||||||
[Your notes here]
|
|
||||||
```
|
|
||||||
|
|
@ -6,23 +6,7 @@ module Api
|
||||||
def privacy_zones
|
def privacy_zones
|
||||||
zones = current_api_user.tags.privacy_zones.includes(:places)
|
zones = current_api_user.tags.privacy_zones.includes(:places)
|
||||||
|
|
||||||
render json: zones.map { |tag|
|
render json: zones.map { |tag| TagSerializer.new(tag).call }
|
||||||
{
|
|
||||||
tag_id: tag.id,
|
|
||||||
tag_name: tag.name,
|
|
||||||
tag_icon: tag.icon,
|
|
||||||
tag_color: tag.color,
|
|
||||||
radius_meters: tag.privacy_radius_meters,
|
|
||||||
places: tag.places.map { |place|
|
|
||||||
{
|
|
||||||
id: place.id,
|
|
||||||
name: place.name,
|
|
||||||
latitude: place.latitude,
|
|
||||||
longitude: place.longitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
33
app/serializers/tag_serializer.rb
Normal file
33
app/serializers/tag_serializer.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TagSerializer
|
||||||
|
def initialize(tag)
|
||||||
|
@tag = tag
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
{
|
||||||
|
tag_id: tag.id,
|
||||||
|
tag_name: tag.name,
|
||||||
|
tag_icon: tag.icon,
|
||||||
|
tag_color: tag.color,
|
||||||
|
radius_meters: tag.privacy_radius_meters,
|
||||||
|
places: places
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :tag
|
||||||
|
|
||||||
|
def places
|
||||||
|
tag.places.map do |place|
|
||||||
|
{
|
||||||
|
id: place.id,
|
||||||
|
name: place.name,
|
||||||
|
latitude: place.latitude.to_f,
|
||||||
|
longitude: place.longitude.to_f
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
class AddUserIdToPlaces < ActiveRecord::Migration[8.0]
|
class AddUserIdToPlaces < ActiveRecord::Migration[8.0]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
# Add nullable for backward compatibility, will enforce later via data migration
|
# Add nullable for backward compatibility, will enforce later via data migration
|
||||||
add_reference :places, :user, null: true, index: {algorithm: :concurrently} unless foreign_key_exists?(:places, :users)
|
add_reference :places, :user, null: true, index: {algorithm: :concurrently} unless foreign_key_exists?(:places, :users)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ RSpec.describe User, type: :model do
|
||||||
it { is_expected.to have_many(:places).through(:visits) }
|
it { is_expected.to have_many(:places).through(:visits) }
|
||||||
it { is_expected.to have_many(:trips).dependent(:destroy) }
|
it { is_expected.to have_many(:trips).dependent(:destroy) }
|
||||||
it { is_expected.to have_many(:tracks).dependent(:destroy) }
|
it { is_expected.to have_many(:tracks).dependent(:destroy) }
|
||||||
|
it { is_expected.to have_many(:tags).dependent(:destroy) }
|
||||||
|
it { is_expected.to have_many(:visited_places).through(:visits) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'enums' do
|
describe 'enums' do
|
||||||
|
|
|
||||||
52
spec/requests/api/v1/tags_spec.rb
Normal file
52
spec/requests/api/v1/tags_spec.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Api::V1::Tags', type: :request do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:tag) { create(:tag, user: user, name: 'Home', icon: '🏠', color: '#4CAF50', privacy_radius_meters: 500) }
|
||||||
|
let!(:place) { create(:place, name: 'My Place', latitude: 10.0, longitude: 20.0) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
tag.places << place
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v1/tags/privacy_zones' do
|
||||||
|
context 'when authenticated' do
|
||||||
|
before do
|
||||||
|
user.create_api_key unless user.api_key.present?
|
||||||
|
get privacy_zones_api_v1_tags_path, params: { api_key: user.api_key }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns success' do
|
||||||
|
expect(response).to be_successful
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the correct JSON structure' do
|
||||||
|
json_response = JSON.parse(response.body)
|
||||||
|
expect(json_response).to be_an(Array)
|
||||||
|
expect(json_response.first).to include(
|
||||||
|
'tag_id' => tag.id,
|
||||||
|
'tag_name' => 'Home',
|
||||||
|
'tag_icon' => '🏠',
|
||||||
|
'tag_color' => '#4CAF50',
|
||||||
|
'radius_meters' => 500
|
||||||
|
)
|
||||||
|
expect(json_response.first['places']).to be_an(Array)
|
||||||
|
expect(json_response.first['places'].first).to include(
|
||||||
|
'id' => place.id,
|
||||||
|
'name' => 'My Place',
|
||||||
|
'latitude' => 10.0,
|
||||||
|
'longitude' => 20.0
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not authenticated' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get privacy_zones_api_v1_tags_path
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
32
spec/serializers/tag_serializer_spec.rb
Normal file
32
spec/serializers/tag_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe TagSerializer do
|
||||||
|
let(:tag) { create(:tag, name: 'Home', icon: '🏠', color: '#4CAF50', privacy_radius_meters: 500) }
|
||||||
|
let!(:place) { create(:place, name: 'My Place', latitude: 10.0, longitude: 20.0) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
tag.places << place
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { described_class.new(tag).call }
|
||||||
|
|
||||||
|
it 'returns the correct JSON structure' do
|
||||||
|
expect(subject).to eq({
|
||||||
|
tag_id: tag.id,
|
||||||
|
tag_name: 'Home',
|
||||||
|
tag_icon: '🏠',
|
||||||
|
tag_color: '#4CAF50',
|
||||||
|
radius_meters: 500,
|
||||||
|
places: [
|
||||||
|
{
|
||||||
|
id: place.id,
|
||||||
|
name: 'My Place',
|
||||||
|
latitude: 10.0,
|
||||||
|
longitude: 20.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
# Run with: bundle exec rails runner verify_places_integration.rb
|
|
||||||
|
|
||||||
puts "🔍 Verifying Places Integration..."
|
|
||||||
puts "=" * 50
|
|
||||||
|
|
||||||
# Check files exist
|
|
||||||
files_to_check = [
|
|
||||||
'app/javascript/maps/places.js',
|
|
||||||
'app/javascript/controllers/stat_page_controller.js',
|
|
||||||
'app/javascript/controllers/place_creation_controller.js',
|
|
||||||
'app/views/stats/_month.html.erb',
|
|
||||||
'app/views/shared/_place_creation_modal.html.erb'
|
|
||||||
]
|
|
||||||
|
|
||||||
puts "\n📁 Checking Files:"
|
|
||||||
files_to_check.each do |file|
|
|
||||||
if File.exist?(file)
|
|
||||||
puts " ✅ #{file}"
|
|
||||||
else
|
|
||||||
puts " ❌ MISSING: #{file}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check view has our changes
|
|
||||||
puts "\n🎨 Checking View Changes:"
|
|
||||||
month_view = File.read('app/views/stats/_month.html.erb')
|
|
||||||
|
|
||||||
if month_view.include?('placesBtn')
|
|
||||||
puts " ✅ Places button found in view"
|
|
||||||
else
|
|
||||||
puts " ❌ Places button NOT found in view"
|
|
||||||
end
|
|
||||||
|
|
||||||
if month_view.include?('Filter Places by Tags')
|
|
||||||
puts " ✅ Tag filter section found in view"
|
|
||||||
else
|
|
||||||
puts " ❌ Tag filter section NOT found in view"
|
|
||||||
end
|
|
||||||
|
|
||||||
if month_view.include?('place_creation_modal')
|
|
||||||
puts " ✅ Place creation modal included"
|
|
||||||
else
|
|
||||||
puts " ❌ Place creation modal NOT included"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check JavaScript has our changes
|
|
||||||
puts "\n💻 Checking JavaScript Changes:"
|
|
||||||
controller_js = File.read('app/javascript/controllers/stat_page_controller.js')
|
|
||||||
|
|
||||||
if controller_js.include?('PlacesManager')
|
|
||||||
puts " ✅ PlacesManager imported"
|
|
||||||
else
|
|
||||||
puts " ❌ PlacesManager NOT imported"
|
|
||||||
end
|
|
||||||
|
|
||||||
if controller_js.include?('togglePlaces()')
|
|
||||||
puts " ✅ togglePlaces() method found"
|
|
||||||
else
|
|
||||||
puts " ❌ togglePlaces() method NOT found"
|
|
||||||
end
|
|
||||||
|
|
||||||
if controller_js.include?('filterPlacesByTags')
|
|
||||||
puts " ✅ filterPlacesByTags() method found"
|
|
||||||
else
|
|
||||||
puts " ❌ filterPlacesByTags() method NOT found"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check database
|
|
||||||
puts "\n🗄️ Checking Database:"
|
|
||||||
user = User.first
|
|
||||||
if user
|
|
||||||
puts " ✅ Found user: #{user.email}"
|
|
||||||
puts " Tags: #{user.tags.count}"
|
|
||||||
puts " Places: #{user.places.count}"
|
|
||||||
|
|
||||||
if user.tags.any?
|
|
||||||
puts "\n 📌 Your Tags:"
|
|
||||||
user.tags.limit(5).each do |tag|
|
|
||||||
puts " #{tag.icon} #{tag.name} (#{tag.places.count} places)"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts " ⚠️ No tags created yet. Create some at /tags"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.places.any?
|
|
||||||
puts "\n 📍 Your Places:"
|
|
||||||
user.places.limit(5).each do |place|
|
|
||||||
puts " #{place.name} - #{place.tags.map(&:name).join(', ')}"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts " ⚠️ No places created yet. Use the API or create via console."
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts " ❌ No users found"
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "\n" + "=" * 50
|
|
||||||
puts "✅ Integration files are in place!"
|
|
||||||
puts "\n📋 Next Steps:"
|
|
||||||
puts " 1. Restart your Rails server"
|
|
||||||
puts " 2. Hard refresh your browser (Cmd+Shift+R)"
|
|
||||||
puts " 3. Navigate to /stats/#{Date.today.year}/#{Date.today.month}"
|
|
||||||
puts " 4. Look for 'Places' button next to 'Heatmap' and 'Points'"
|
|
||||||
puts " 5. Create tags at /tags if you haven't already"
|
|
||||||
puts " 6. Create places via API with those tags"
|
|
||||||
Loading…
Reference in a new issue