mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
upload
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
# Contact Management & Map Integration
|
||||
|
||||
Comprehensive contact information management system with interactive map support using Leaflet.js (open source).
|
||||
|
||||
## Features
|
||||
|
||||
### Contact Management
|
||||
- **Contact Categories**: Organize contacts by department (Management, Coaches, Office, etc.)
|
||||
- **Contact Persons**: Store detailed information for each contact
|
||||
- Name, Position, Email, Phone
|
||||
- Optional photo/image
|
||||
- Description
|
||||
- Category assignment
|
||||
- Display order customization
|
||||
- Active/inactive status
|
||||
|
||||
### Location & Map
|
||||
- **Address Information**: Full address with city, ZIP, country
|
||||
- **GPS Coordinates**: Latitude/Longitude for precise location
|
||||
- **Interactive Map**: Leaflet.js-based map display
|
||||
- Custom markers
|
||||
- Configurable zoom level
|
||||
- Multiple map styles (default, dark, satellite)
|
||||
- Can be shown on homepage
|
||||
- **Contact Details**: Phone and email for the main location
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Tables Created
|
||||
1. **contact_categories**
|
||||
- id, name, description, display_order, is_active
|
||||
- Unique constraint on name
|
||||
|
||||
2. **contacts**
|
||||
- id, category_id (FK), name, position, email, phone
|
||||
- image_url, description, display_order, is_active
|
||||
- Foreign key to contact_categories
|
||||
|
||||
3. **settings** (extended)
|
||||
- contact_address, contact_city, contact_zip, contact_country
|
||||
- contact_phone, contact_email
|
||||
- location_latitude, location_longitude
|
||||
- map_zoom_level, map_style
|
||||
- show_map_on_homepage
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints (No Auth Required)
|
||||
|
||||
#### Get All Contacts (Grouped by Category)
|
||||
```
|
||||
GET /api/v1/contacts
|
||||
```
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"categories": {
|
||||
"Management": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "John Doe",
|
||||
"position": "President",
|
||||
"email": "john@club.com",
|
||||
"phone": "+420 123 456 789",
|
||||
"image_url": "/uploads/john.jpg",
|
||||
"description": "Club president since 2020",
|
||||
"display_order": 0,
|
||||
"is_active": true,
|
||||
"category": {...}
|
||||
}
|
||||
],
|
||||
"Coaches": [...]
|
||||
},
|
||||
"uncategorized": [...]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Contact Categories
|
||||
```
|
||||
GET /api/v1/contacts/categories
|
||||
```
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Management",
|
||||
"description": "Club management and board",
|
||||
"display_order": 0,
|
||||
"is_active": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Admin Endpoints (Require Auth + Admin Role)
|
||||
|
||||
#### Contact Categories Management
|
||||
|
||||
**List Categories**
|
||||
```
|
||||
GET /api/v1/admin/contacts/categories
|
||||
```
|
||||
|
||||
**Create Category**
|
||||
```
|
||||
POST /api/v1/admin/contacts/categories
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Management",
|
||||
"description": "Club management team",
|
||||
"display_order": 0,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
**Update Category**
|
||||
```
|
||||
PUT /api/v1/admin/contacts/categories/:id
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Updated Name",
|
||||
"description": "New description",
|
||||
"display_order": 5,
|
||||
"is_active": false
|
||||
}
|
||||
```
|
||||
|
||||
**Delete Category**
|
||||
```
|
||||
DELETE /api/v1/admin/contacts/categories/:id
|
||||
```
|
||||
*Note: Cannot delete if contacts are assigned to it*
|
||||
|
||||
#### Contacts Management
|
||||
|
||||
**List All Contacts**
|
||||
```
|
||||
GET /api/v1/admin/contacts
|
||||
```
|
||||
|
||||
**Create Contact**
|
||||
```
|
||||
POST /api/v1/admin/contacts
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"category_id": 1,
|
||||
"name": "John Doe",
|
||||
"position": "President",
|
||||
"email": "john@club.com",
|
||||
"phone": "+420 123 456 789",
|
||||
"image_url": "/uploads/john.jpg",
|
||||
"description": "Club president since 2020",
|
||||
"display_order": 0,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
**Update Contact**
|
||||
```
|
||||
PUT /api/v1/admin/contacts/:id
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Updated Name",
|
||||
"position": "Vice President",
|
||||
"email": "new@email.com",
|
||||
"phone": "+420 987 654 321",
|
||||
"category_id": 2,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
*Note: All fields are optional. Set `category_id: null` to remove category assignment*
|
||||
|
||||
**Delete Contact**
|
||||
```
|
||||
DELETE /api/v1/admin/contacts/:id
|
||||
```
|
||||
|
||||
### Settings API (Location/Map Configuration)
|
||||
|
||||
**Update Settings (Admin Only)**
|
||||
```
|
||||
PUT /api/v1/admin/settings
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"contact_address": "Main Street 123",
|
||||
"contact_city": "Prague",
|
||||
"contact_zip": "110 00",
|
||||
"contact_country": "Czech Republic",
|
||||
"contact_phone": "+420 123 456 789",
|
||||
"contact_email": "info@club.com",
|
||||
"location_latitude": 50.0755,
|
||||
"location_longitude": 14.4378,
|
||||
"map_zoom_level": 15,
|
||||
"map_style": "default",
|
||||
"show_map_on_homepage": true
|
||||
}
|
||||
```
|
||||
|
||||
**Get Public Settings**
|
||||
```
|
||||
GET /api/v1/settings
|
||||
```
|
||||
Returns all public settings including contact/map information.
|
||||
|
||||
## Map Integration
|
||||
|
||||
### Using Leaflet.js
|
||||
|
||||
The frontend should use **Leaflet.js** (open source alternative to Google Maps):
|
||||
|
||||
```html
|
||||
<!-- Include Leaflet CSS and JS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
|
||||
<!-- Map container -->
|
||||
<div id="map" style="height: 400px;"></div>
|
||||
|
||||
<script>
|
||||
// Initialize map
|
||||
const map = L.map('map').setView([50.0755, 14.4378], 15);
|
||||
|
||||
// Add tile layer (OpenStreetMap)
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
// Add custom marker
|
||||
const customIcon = L.icon({
|
||||
iconUrl: '/dist/img/marker-icon.png',
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [0, -32]
|
||||
});
|
||||
|
||||
const marker = L.marker([50.0755, 14.4378], { icon: customIcon })
|
||||
.addTo(map)
|
||||
.bindPopup('<b>FC Example</b><br>Main Street 123<br>Prague');
|
||||
</script>
|
||||
```
|
||||
|
||||
### Map Styles
|
||||
|
||||
The `map_style` field supports:
|
||||
- **"default"**: Standard OpenStreetMap
|
||||
- **"dark"**: Dark theme map
|
||||
- **"satellite"**: Satellite imagery (requires API key for some providers)
|
||||
- **Custom URL**: Full tile URL template
|
||||
|
||||
Example tile URLs:
|
||||
```javascript
|
||||
// Default OpenStreetMap
|
||||
https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
|
||||
// CartoDB Dark Matter
|
||||
https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png
|
||||
|
||||
// Stadia Maps (requires API key)
|
||||
https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png?api_key=YOUR_API_KEY
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
Run the migration to create tables:
|
||||
|
||||
```bash
|
||||
# Apply migration
|
||||
make migrate-up
|
||||
|
||||
# Or manually
|
||||
migrate -path database/migrations -database "postgres://..." up
|
||||
```
|
||||
|
||||
The migration file: `000006_create_contact_tables.up.sql`
|
||||
|
||||
## Frontend Implementation Guide
|
||||
|
||||
### Contact Page Structure
|
||||
|
||||
```jsx
|
||||
// Fetch contacts
|
||||
const response = await fetch('/api/v1/contacts');
|
||||
const data = await response.json();
|
||||
|
||||
// Display grouped by category
|
||||
{Object.entries(data.categories).map(([categoryName, contacts]) => (
|
||||
<div key={categoryName}>
|
||||
<h2>{categoryName}</h2>
|
||||
{contacts.map(contact => (
|
||||
<div key={contact.id} className="contact-card">
|
||||
{contact.image_url && <img src={contact.image_url} alt={contact.name} />}
|
||||
<h3>{contact.name}</h3>
|
||||
<p>{contact.position}</p>
|
||||
<a href={`mailto:${contact.email}`}>{contact.email}</a>
|
||||
<a href={`tel:${contact.phone}`}>{contact.phone}</a>
|
||||
<p>{contact.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
```
|
||||
|
||||
### Map Component
|
||||
|
||||
```jsx
|
||||
import { useEffect, useRef } from 'react';
|
||||
import L from 'leaflet';
|
||||
|
||||
function ContactMap({ latitude, longitude, zoom, address, clubName }) {
|
||||
const mapRef = useRef(null);
|
||||
const mapInstanceRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapRef.current || mapInstanceRef.current) return;
|
||||
|
||||
// Initialize map
|
||||
const map = L.map(mapRef.current).setView([latitude, longitude], zoom || 15);
|
||||
mapInstanceRef.current = map;
|
||||
|
||||
// Add tile layer
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
// Add marker
|
||||
const marker = L.marker([latitude, longitude]).addTo(map);
|
||||
marker.bindPopup(`<b>${clubName}</b><br>${address}`);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
if (mapInstanceRef.current) {
|
||||
mapInstanceRef.current.remove();
|
||||
mapInstanceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [latitude, longitude, zoom, address, clubName]);
|
||||
|
||||
return <div ref={mapRef} style={{ height: '400px', width: '100%' }} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Admin Panel Integration
|
||||
|
||||
### Settings Form Fields
|
||||
|
||||
Add to your admin settings form:
|
||||
|
||||
```jsx
|
||||
// Contact Information Section
|
||||
<section>
|
||||
<h3>Contact Information</h3>
|
||||
<input name="contact_address" placeholder="Address" />
|
||||
<input name="contact_city" placeholder="City" />
|
||||
<input name="contact_zip" placeholder="ZIP Code" />
|
||||
<input name="contact_country" placeholder="Country" />
|
||||
<input name="contact_phone" placeholder="Phone" />
|
||||
<input name="contact_email" type="email" placeholder="Email" />
|
||||
</section>
|
||||
|
||||
// Map Configuration Section
|
||||
<section>
|
||||
<h3>Map Configuration</h3>
|
||||
<input name="location_latitude" type="number" step="any" placeholder="Latitude" />
|
||||
<input name="location_longitude" type="number" step="any" placeholder="Longitude" />
|
||||
<input name="map_zoom_level" type="number" min="1" max="20" placeholder="Zoom Level (1-20)" />
|
||||
<select name="map_style">
|
||||
<option value="default">Default</option>
|
||||
<option value="dark">Dark Theme</option>
|
||||
<option value="satellite">Satellite</option>
|
||||
</select>
|
||||
<label>
|
||||
<input name="show_map_on_homepage" type="checkbox" />
|
||||
Show map on homepage
|
||||
</label>
|
||||
</section>
|
||||
```
|
||||
|
||||
### Contact Management UI
|
||||
|
||||
Create admin pages for:
|
||||
1. **Contact Categories List** - View, create, edit, delete categories
|
||||
2. **Contacts List** - View all contacts with category filter
|
||||
3. **Contact Form** - Create/edit contact with category dropdown
|
||||
4. **Drag-and-drop ordering** - Reorder contacts within categories
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Images**: Upload contact photos to `/uploads/` and use relative paths
|
||||
2. **Display Order**: Use increments of 10 (0, 10, 20...) to allow easy reordering
|
||||
3. **Categories**: Create logical groups (Management, Coaches, Medical Staff, Office)
|
||||
4. **Map Markers**: Use club colors for custom marker icons
|
||||
5. **Privacy**: Only display active contacts on public pages
|
||||
6. **Mobile**: Ensure map is responsive and touch-friendly
|
||||
7. **Performance**: Use lazy loading for contact images
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Admin endpoints are protected by JWT authentication + admin role check
|
||||
- Public endpoints expose only active contacts and categories
|
||||
- Email addresses are visible (consider adding spam protection or obfuscation)
|
||||
- Phone numbers are displayed as-is (consider formatting based on locale)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Contact form directly on contact page
|
||||
- [ ] vCard download for contacts
|
||||
- [ ] Office hours/availability scheduling
|
||||
- [ ] Multiple locations/maps support
|
||||
- [ ] Department-specific contact forms
|
||||
- [ ] Social media links per contact
|
||||
- [ ] Search/filter functionality
|
||||
- [ ] Map clustering for multiple locations
|
||||
Reference in New Issue
Block a user