Files
MyClub/DOCS/CONTACT_MANAGEMENT.md
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

10 KiB

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:

{
  "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:

[
  {
    "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):

<!-- 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:

// 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:

# 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

// 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

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:

// 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