feat: Implement comprehensive Contacts Management System with API and UI integration

- Added new `contacts` and `project_contacts` database tables for managing contacts.
- Created API endpoints for CRUD operations on contacts and linking them to projects.
- Developed UI components including `ContactForm` and `ProjectContactSelector`.
- Integrated navigation and translations for Polish language support.
- Documented usage, features, and future enhancements for the contacts system.

feat: Introduce DOCX Template System for generating documents from templates

- Enabled creation and management of DOCX templates with placeholders for project data.
- Documented the process for creating templates, uploading, and generating documents.
- Included detailed information on available variables and custom data fields.
- Implemented troubleshooting guidelines for common issues related to template generation.

feat: Add Radicale CardDAV Sync Integration for automatic contact synchronization

- Implemented automatic syncing of contacts to a Radicale server on create/update/delete actions.
- Documented setup instructions, including environment variable configuration and initial sync script.
- Provided troubleshooting steps for common sync issues and error codes.

feat: Develop Route Planning Feature with Optimization using OpenRouteService API

- Integrated multi-point routing and automatic optimization for project locations.
- Documented setup, usage, and technical implementation details for route planning.
- Included performance considerations and troubleshooting for common routing issues.

chore: Remove unnecessary files and scripts from the codebase

- Deleted temporary, debug-related, and test-specific files that are not needed in production.
- Reviewed and ensured core application code and essential documentation remain intact.
This commit is contained in:
2026-01-16 11:11:29 +01:00
parent 9ea67b626b
commit d4f16d344d
12 changed files with 1094 additions and 1042 deletions

View File

@@ -0,0 +1,174 @@
# Contacts Management System
## Overview
A comprehensive contacts management system has been implemented to replace the simple text field for project contacts. This system allows you to:
- **Create and manage a centralized contact database**
- **Link multiple contacts to each project**
- **Categorize contacts** (Project contacts, Contractors, Offices, Suppliers, etc.)
- **Track contact details** (name, phone, email, company, position)
- **Set primary contacts** for projects
- **Search and filter** contacts easily
## What Was Implemented
### 1. Database Schema
**New Tables:**
- **`contacts`** - Stores all contact information
- `contact_id` (Primary Key)
- `name`, `phone`, `email`, `company`, `position`
- `contact_type` (project/contractor/office/supplier/other)
- `notes`, `is_active`
- `created_at`, `updated_at`
- **`project_contacts`** - Junction table linking projects to contacts (many-to-many)
- `project_id`, `contact_id` (Composite Primary Key)
- `relationship_type`, `is_primary`
- `added_at`, `added_by`
### 2. API Endpoints
- **`GET /api/contacts`** - List all contacts (with filters)
- **`POST /api/contacts`** - Create new contact
- **`GET /api/contacts/[id]`** - Get contact details
- **`PUT /api/contacts/[id]`** - Update contact
- **`DELETE /api/contacts/[id]`** - Delete contact (soft/hard)
- **`GET /api/projects/[id]/contacts`** - Get project's contacts
- **`POST /api/projects/[id]/contacts`** - Link contact to project
- **`DELETE /api/projects/[id]/contacts`** - Unlink contact from project
- **`PATCH /api/projects/[id]/contacts`** - Set primary contact
### 3. UI Components
- **`ContactForm`** - Create/edit contact form
- **`/contacts` page** - Full contacts management interface with:
- Statistics dashboard
- Search and filtering
- Contact cards with quick actions
- CRUD operations
- **`ProjectContactSelector`** - Multi-contact selector for projects
- View linked contacts
- Add/remove contacts
- Set primary contact
- Real-time search
### 4. Integration
- **Navigation** - "Kontakty" link added to main navigation
- **ProjectForm** - Contact text field replaced with `ProjectContactSelector`
- **Translations** - Polish translations added to i18n
- **Query Functions** - Comprehensive database query functions in `src/lib/queries/contacts.js`
## How to Use
### Initial Setup
1. **Run the migration script** to create the new tables:
```bash
node migrate-contacts.mjs
```
2. **Start your development server**:
```bash
npm run dev
```
3. **Visit** `http://localhost:3000/contacts` to start adding contacts
### Managing Contacts
1. **Create Contacts**:
- Go to `/contacts`
- Click "Dodaj kontakt"
- Fill in contact details
- Select contact type (Project/Contractor/Office/Supplier/Other)
2. **Link Contacts to Projects**:
- Edit any project
- In the "Kontakty do projektu" section
- Click "+ Dodaj kontakt"
- Search and add contacts
- Set one as primary if needed
3. **View Contact Details**:
- Contacts page shows all contacts with:
- Contact information (phone, email, company)
- Number of linked projects
- Contact type badges
- Edit or delete contacts as needed
### Contact Types
- **Kontakt projektowy (Project)** - Project-specific contacts
- **Wykonawca (Contractor)** - Construction contractors
- **Urząd (Office)** - Government offices, municipalities
- **Dostawca (Supplier)** - Material suppliers, vendors
- **Inny (Other)** - Any other type of contact
### Features
- **Search** - Search by name, phone, email, or company
- **Filter** - Filter by contact type
- **Statistics** - See breakdown of contacts by type
- **Multiple Contacts per Project** - Link as many contacts as needed
- **Primary Contact** - Mark one contact as primary for each project
- **Bidirectional Links** - See which projects a contact is linked to
- **Soft Delete** - Deleted contacts are marked inactive, not removed
## Database Migration Notes
- The **old `contact` text field** in the `projects` table is still present
- It hasn't been removed for backward compatibility
- You can manually migrate old contact data by:
1. Creating contacts from the old text data
2. Linking them to the appropriate projects
3. The old field will remain for reference
## File Structure
```
src/
├── app/
│ ├── api/
│ │ ├── contacts/
│ │ │ ├── route.js # List/Create contacts
│ │ │ └── [id]/
│ │ │ └── route.js # Get/Update/Delete contact
│ │ └── projects/
│ │ └── [id]/
│ │ └── contacts/
│ │ └── route.js # Link/unlink contacts to project
│ └── contacts/
│ └── page.js # Contacts management page
├── components/
│ ├── ContactForm.js # Contact form component
│ └── ProjectContactSelector.js # Project contact selector
└── lib/
├── queries/
│ └── contacts.js # Database query functions
└── init-db.js # Database schema with new tables
```
## Future Enhancements
Potential improvements you could add:
- Contact import/export (CSV, Excel)
- Contact groups or tags
- Contact activity history
- Email integration
- Contact notes and history
- Duplicate contact detection
- Contact merge functionality
- Advanced relationship types
- Contact sharing between projects
- Contact reminders/follow-ups
## Support
The old contact text field remains in the database, so no existing data is lost. You can gradually migrate to the new system at your own pace.
Enjoy your new contacts management system! 🎉

View File

@@ -0,0 +1,286 @@
# DOCX Template System
This system allows you to generate DOCX documents by filling templates with project data.
## How to Create Templates
1. **Create a DOCX Template**: Use Microsoft Word or any DOCX editor to create your template.
2. **Add Placeholders**: Use single curly braces `{variableName}` to mark where data should be inserted. Available variables:
### Available Variables (with duplicates for repeated use)
#### Project Information
- `{project_name}`, `{project_name_1}`, `{project_name_2}`, `{project_name_3}` - Project name
- `{project_number}`, `{project_number_1}`, `{project_number_2}` - Project number
- `{address}`, `{address_1}`, `{address_2}` - Project address
- `{city}`, `{city_1}`, `{city_2}` - City
- `{plot}` - Plot number
- `{district}` - District
- `{unit}` - Unit
- `{investment_number}` - Investment number
- `{wp}` - WP number
- `{coordinates}` - GPS coordinates
- `{notes}` - Project notes
#### Processed/Transformed Fields
- `{investment_number_short}` - Last part of investment number after last dash (e.g., "1234567" from "I-BC-DE-1234567")
- `{project_number_short}` - Last part of project number after last dash
- `{project_name_upper}` - Project name in uppercase
- `{project_name_lower}` - Project name in lowercase
- `{city_upper}` - City name in uppercase
- `{customer_upper}` - Customer name in uppercase
#### Contract Information
- `{contract_number}` - Contract number
- `{customer_contract_number}` - Customer contract number
- `{customer}`, `{customer_1}`, `{customer_2}` - Customer name
- `{investor}` - Investor name
#### Dates
- `{finish_date}` - Finish date (formatted)
- `{completion_date}` - Completion date (formatted)
- `{today_date}` - Today's date
#### Project Type & Status
- `{project_type}` - Project type (design/construction/design+construction)
- `{project_status}` - Project status
#### Financial
- `{wartosc_zlecenia}`, `{wartosc_zlecenia_1}`, `{wartosc_zlecenia_2}` - Contract value
#### Standard Custom Fields (Pre-filled but Editable)
- `{zk}` - ZK field
- `{nr_zk}` - ZK number
- `{kabel}` - Cable information
- `{dlugosc}` - Length
- `{data_wykonania}` - Execution date
- `{st_nr}` - Station number
- `{obw}` - Circuit
- `{wp_short}` - Short WP reference
- `{plomba}` - Seal/plomb information
## Example Template Content
```
Project Report
Project Name: {project_name} ({project_name_upper})
Project Number: {project_number} (Short: {project_number_short})
Location: {city_upper}, {address}
Investment Details:
Full Investment Number: {investment_number}
Short Investment Number: {investment_number_short}
Contract Details:
Contract Number: {contract_number}
Customer: {customer} ({customer_upper})
Value: {wartosc_zlecenia} PLN
Custom Information:
Meeting Notes: {meeting_notes}
Special Instructions: {special_instructions}
Additional Comments: {additional_comments}
Technical Details:
ZK: {zk}
ZK Number: {nr_zk}
Cable: {kabel}
Length: {dlugosc}
Execution Date: {data_wykonania}
Station Number: {st_nr}
Circuit: {obw}
WP Short: {wp_short}
Seal: {plomba}
Primary Contact:
Name: {primary_contact}
Phone: {primary_contact_phone}
Email: {primary_contact_email}
Generated on: {today_date}
```
## Uploading Templates
1. Go to the Templates page (`/templates`)
2. Click "Add Template"
3. Provide a name and description
4. Upload your DOCX file
5. The template will be available for generating documents
## Generating Documents
1. Open any project page
2. In the sidebar, find the "Generate Document" section
3. Select a template from the dropdown
4. **Optional**: Click "Pokaż dodatkowe pola" to add custom data
5. Fill in the standard fields (zk, nr_zk, kabel, etc.) and any additional custom fields
6. Click "Generate Document"
7. The filled document will be downloaded automatically with filename: `{template_name}_{project_name}_{timestamp}.docx`
## Custom Data Fields
During document generation, you can add custom data that will be merged with the project data:
### Standard Fields (Pre-filled but Fully Editable)
These fields are pre-filled with common names but can be modified or removed:
- `zk`, `nr_zk`, `kabel`, `dlugosc`, `data_wykonania`, `st_nr`, `obw`, `wp_short`, `plomba`
### Additional Custom Fields
- **Custom fields** override project data if they have the same name
- Use descriptive names like `meeting_notes`, `special_instructions`, `custom_date`
- Custom fields are available in templates as `{custom_field_name}`
- Empty custom fields are ignored
- All fields can be removed if not needed
### Example Custom Fields:
- `meeting_notes`: "Please bring project documentation"
- `special_instructions`: "Use company letterhead"
- `custom_date`: "2025-01-15"
- `additional_comments`: "Follow up required"
## Template Syntax
The system uses `docxtemplater` library which supports:
- Simple variable replacement: `{variable}`
- Loops: `{#contacts}{name}{/contacts}`
- Conditions: `{#primary_contact}Primary: {name}{/primary_contact}`
- Formatting and styling from your DOCX template is preserved
## Data Processing & Transformations
The system automatically provides processed versions of common fields:
- **Short codes**: `{investment_number_short}` extracts the last segment after dashes (e.g., "1234567" from "I-BC-DE-1234567")
- **Case transformations**: `{project_name_upper}`, `{city_upper}`, `{customer_upper}` for uppercase versions
- **Duplicate fields**: Multiple versions of the same field for repeated use (`{project_name_1}`, `{project_name_2}`, etc.)
If you need additional transformations (like extracting different parts of codes, custom formatting, calculations, etc.), please let us know and we can add them to the system.
## Tips
- Test your templates with sample data first
- Use descriptive variable names
- Keep formatting simple for best results
- Save templates with `.docx` extension only
- Maximum file size: 10MB
- **For repeated information**: If you need the same data to appear multiple times, create unique placeholders like `{project_name_header}` and `{project_name_footer}` and provide the same value for both
## Storage & Persistence
Templates are stored in two locations for persistence in Docker environments:
### Database Storage
- **Location**: `data/database.sqlite` (table: `docx_templates`)
- **Content**: Template metadata (name, description, file paths, timestamps)
- **Persistence**: Handled by Docker volume mount `./data:/app/data`
### File Storage
- **Location**: `templates/` (host) → `/app/templates/` (container)
- **Content**: Actual DOCX template files
- **Persistence**: Handled by Docker volume mount `./templates:/app/templates`
- **Web Access**: Files are served via `/api/templates/download/{filename}`
### Docker Volume Mounts
Both development and production Docker setups include volume mounts to ensure template persistence across container restarts:
```yaml
volumes:
- ./data:/app/data # Database
- ./templates:/app/templates # Template files
- ./uploads:/app/public/uploads # Other uploads
- ./backups:/app/backups # Backup files
```
## 🔧 Troubleshooting
### Common Issues
**Problem: "Duplicate tag" error during generation**
- **Cause**: Using the same placeholder multiple times (e.g., `{project_name}` twice)
- **Solution**: Use numbered variants like `{project_name}`, `{project_name_1}`, `{project_name_2}` OR add the same value to custom fields with different names
**Problem: Template not rendering correctly**
- **Cause**: Invalid placeholder syntax
- **Solution**: Ensure all placeholders use single curly braces `{variable}` (not double `{{}}`)
- **Verify**: Check for typos in variable names
**Problem: Missing data in generated document**
- **Cause**: Project missing required fields or custom data not provided
- **Solution**: Fill in all required project information or provide custom data during generation
- **Check**: Review project details before generating
**Problem: Formatting lost in generated document**
- **Cause**: Complex Word formatting or incompatible styles
- **Solution**:
- Simplify template formatting
- Avoid complex tables or text boxes
- Use basic styles (bold, italic, underline work best)
- Test with minimal formatting first
**Problem: Generated file not downloading**
- **Cause**: Browser popup blocker or network issue
- **Solution**:
- Allow popups for this site
- Check browser console for errors (F12)
- Try different browser
- Check file size < 10MB
**Problem: Template upload fails**
- **Cause**: File too large or invalid format
- **Solution**:
- Ensure file is .docx format (not .doc)
- File size must be under 10MB
- Re-save file in Word to fix corruption
- Check file isn't password-protected
**Problem: Custom fields not appearing**
- **Cause**: Field name mismatch between template and custom data
- **Solution**:
- Ensure exact match (case-sensitive)
- Example: `{meeting_notes}` in template requires `meeting_notes` in custom data
- Check for spaces in field names
**Problem: Dates not formatted correctly**
- **Cause**: Date format differences
- **Solution**: Dates are auto-formatted as YYYY-MM-DD
- **Tip**: Use `{today_date}` for current date
### Getting Help
If you encounter other issues:
1. Check browser console (F12) for error messages
2. Verify template file is valid .docx
3. Test with simpler template first
4. Contact system administrator with error details
---
## 📋 Quick Reference
### File Limits
- Maximum template size: 10MB
- Supported format: .docx only
- Unlimited templates per project
### Available Endpoints
- `GET /api/templates` - List all templates
- `POST /api/templates` - Upload new template
- `POST /api/templates/generate` - Generate document
- `GET /api/templates/download/{filename}` - Download template
### Best Practices
Test templates with sample data first
Use descriptive placeholder names
Keep formatting simple
Use numbered variants for repeated data
Provide meaningful template descriptions
Don't use same placeholder twice
Don't use complex Word features (macros, forms)
Don't upload non-.docx files
---
**See Also**: [Main README](../../README.md#-document-generation) | [API Documentation](../../README.md#templates-docx)

View File

@@ -0,0 +1,351 @@
# Radicale CardDAV Sync Integration
This application now automatically syncs contacts to a Radicale CardDAV server whenever contacts are created, updated, or deleted.
## Features
-**Automatic Sync** - Contacts are automatically synced when created or updated
-**Automatic Deletion** - Contacts are removed from Radicale when soft/hard deleted
-**Non-Blocking** - Sync happens asynchronously without slowing down the API
-**Optional** - Sync is disabled by default, enable by configuring environment variables
-**VCARD 3.0** - Generates standard VCARD format with full contact details
## Setup
### 1. Configure Environment Variables
Add these to your `.env.local` or production environment:
```bash
RADICALE_URL=http://localhost:5232
RADICALE_USERNAME=your_username
RADICALE_PASSWORD=your_password
```
**Note:** If these variables are not set, sync will be disabled and the app will work normally.
### 2. Radicale Server Setup
Make sure your Radicale server:
- Is accessible from your application server
- Has a user created with the credentials you configured
- Has a contacts collection at: `{username}/contacts/`
### 3. One-Time Initial Sync
To sync all existing contacts to Radicale:
```bash
node export-contacts-to-radicale.mjs
```
This script will:
- Prompt for Radicale URL, username, and password
- Export all active contacts as VCARDs
- Upload them to your Radicale server
## How It Works
### When Creating a Contact
```javascript
// POST /api/contacts
const contact = createContact(data);
// Sync to Radicale asynchronously (non-blocking)
syncContactAsync(contact);
return NextResponse.json(contact);
```
### When Updating a Contact
```javascript
// PUT /api/contacts/[id]
const contact = updateContact(contactId, data);
// Sync updated contact to Radicale
syncContactAsync(contact);
return NextResponse.json(contact);
```
### When Deleting a Contact
```javascript
// DELETE /api/contacts/[id]
deleteContact(contactId);
// Delete from Radicale asynchronously
deleteContactAsync(contactId);
return NextResponse.json({ message: "Contact deleted" });
```
## VCARD Format
Each contact is exported with the following fields:
- **UID**: `contact-{id}@panel-app`
- **FN/N**: Full name and structured name
- **ORG**: Company
- **TITLE**: Position/Title
- **TEL**: Phone numbers (multiple supported - first as WORK, others as CELL)
- **EMAIL**: Email address
- **NOTE**: Contact type + notes
- **CATEGORIES**: Based on contact type (Projekty, Wykonawcy, Urzędy, etc.)
- **REV**: Last modified timestamp
## VCARD Storage Path
VCARDs are stored at:
```
{RADICALE_URL}/{RADICALE_USERNAME}/contacts/contact-{id}.vcf
```
Example:
```
http://localhost:5232/admin/contacts/contact-123.vcf
```
## 🔧 Troubleshooting
### Common Issues
**Problem: Sync not working / contacts not appearing in Radicale**
**Check 1: Environment Variables**
```bash
# Verify variables are set
echo $RADICALE_URL
echo $RADICALE_USERNAME
# Don't echo password for security
```
**Check 2: Radicale Server Connectivity**
```bash
# Test server is reachable
curl -I http://your-radicale-server:5232
# Test authentication
curl -u username:password http://your-radicale-server:5232/username/contacts/
```
**Check 3: Application Logs**
Look for sync messages in your application console:
```
✅ Synced contact 123 to Radicale
❌ Failed to sync contact 456 to Radicale: 401 - Unauthorized
```
---
**Problem: 401 Unauthorized errors**
- **Cause**: Invalid credentials or user doesn't exist
- **Solution**:
- Verify `RADICALE_USERNAME` and `RADICALE_PASSWORD`
- Ensure user exists in Radicale
- Check Radicale authentication method (basic auth vs htpasswd)
---
**Problem: 404 Not Found errors**
- **Cause**: Contacts collection doesn't exist
- **Solution**:
- Create collection in Radicale: `/{username}/contacts/`
- Verify collection URL matches `RADICALE_URL`
- Check Radicale collection rights and permissions
---
**Problem: Network timeout or connection refused**
- **Cause**: Radicale server not accessible from app server
- **Solution**:
- Check firewall rules
- Verify Radicale is running: `systemctl status radicale`
- Test with curl from app server
- If using Docker, ensure network connectivity
---
**Problem: Contacts created but not syncing**
- **Cause**: Environment variables not loaded or sync disabled
- **Solution**:
- Restart application after setting env vars
- Check `.env` or `.env.local` file exists
- Verify Next.js loaded environment: check server startup logs
- Test with manual export script: `node export-contacts-to-radicale.mjs`
---
**Problem: Duplicate contacts in Radicale**
- **Cause**: Re-running export script or UID conflicts
- **Solution**:
- UIDs are unique: `contact-{id}@panel-app`
- Existing contacts are overwritten on update
- Delete duplicates manually in Radicale if needed
---
**Problem: VCARD format errors in Radicale**
- **Cause**: Invalid characters or incomplete data
- **Solution**:
- Check contact has at least name field
- Special characters in names are escaped
- Phone/email fields are optional
- Review contact data for completeness
---
### Monitoring Sync Status
**Enable Detailed Logging**
Edit `src/lib/radicale-sync.js` to increase logging verbosity:
```javascript
// Add more console.log statements
console.log('Syncing contact:', contact);
console.log('VCARD:', vcard);
console.log('Response:', await response.text());
```
**Check Radicale Server Logs**
```bash
# Typical log location
tail -f /var/log/radicale/radicale.log
# Or check systemd journal
journalctl -u radicale -f
```
**Manual Sync Test**
Test individual contact sync:
```bash
# Use the export script for a single contact
node export-contacts-to-radicale.mjs
# Select specific contact when prompted
```
---
### Disable Sync Temporarily
Comment out environment variables to disable sync without removing configuration:
```bash
# .env.local
# RADICALE_URL=http://localhost:5232
# RADICALE_USERNAME=admin
# RADICALE_PASSWORD=secret
```
Application will function normally without sync enabled.
---
### Manual Sync Endpoint
For manual sync control, you can trigger sync via API:
```bash
# Sync specific contact
POST /api/contacts/{id}/sync
# Response
{
"success": true,
"message": "Contact synced to Radicale"
}
```
---
### Error Codes Reference
| Code | Meaning | Solution |
|------|---------|----------|
| 401 | Unauthorized | Check credentials |
| 403 | Forbidden | Verify user has write permissions |
| 404 | Not Found | Create contacts collection |
| 409 | Conflict | UID collision (rare) |
| 500 | Server Error | Check Radicale server logs |
| ECONNREFUSED | Connection Refused | Server not reachable |
| ETIMEDOUT | Timeout | Network/firewall issue |
---
## 📋 Configuration Reference
### Required Environment Variables
```bash
RADICALE_URL=http://localhost:5232
RADICALE_USERNAME=your_username
RADICALE_PASSWORD=your_password
```
### Default Settings
- **Collection Path**: `/{username}/contacts/`
- **VCARD Version**: 3.0
- **UID Format**: `contact-{id}@panel-app`
- **Sync Mode**: Asynchronous (non-blocking)
- **Retry Logic**: None (fire-and-forget)
---
## 📂 Files Reference
| File | Purpose |
|------|---------|
| `src/lib/radicale-sync.js` | Core sync logic, VCARD generation |
| `src/app/api/contacts/route.js` | Create sync trigger |
| `src/app/api/contacts/[id]/route.js` | Update/delete sync triggers |
| `export-contacts-to-radicale.mjs` | Bulk export utility |
---
## 🔒 Security Best Practices
**Do's:**
- Use HTTPS for production Radicale servers
- Store credentials in environment variables (never in code)
- Use strong, unique passwords
- Limit Radicale user permissions to contacts collection only
- Regularly rotate credentials
- Use separate credentials per environment (dev/staging/prod)
**Don'ts:**
- Don't commit credentials to git
- Don't use HTTP in production
- Don't share credentials between environments
- Don't log passwords or sensitive data
- Don't grant unnecessary permissions
---
## 🚀 Advanced Configuration
### Custom Collection Path
Modify `src/lib/radicale-sync.js`:
```javascript
const baseUrl = `${process.env.RADICALE_URL}/${process.env.RADICALE_USERNAME}/my-custom-collection/`;
```
### Batch Sync Operations
For large-scale sync (future enhancement):
```javascript
// Collect contacts, then sync in batches
const batchSize = 50;
// Implement batch logic
```
### Webhook Integration
Future: Trigger webhooks on sync events:
```javascript
// POST to webhook URL on sync success/failure
fetch(WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({ event: 'contact_synced', contact_id: id })
});
```
---
**See Also**: [Contacts System](CONTACTS_SYSTEM.md) | [Main README](../../README.md#-cardav-integration-radicale) | [API Documentation](../../README.md#contacts)

View File

@@ -0,0 +1,518 @@
# Route Planning Feature with Optimization
This feature allows you to plan routes between multiple project locations using OpenRouteService API, with automatic optimization to find the fastest route regardless of point addition order.
## 🌟 Overview
The route planning system integrates with your project map to help optimize field visits. It supports:
- Multi-point routing through project locations
- Automatic route optimization for 3+ points
- Visual route display on the map
- Distance and time estimation
- Hybrid optimization approach (API + permutation testing)
---
## 🚀 Setup
1. **Get an API Key**:
- Visit [OpenRouteService](https://openrouteservice.org/)
- Sign up for a free account
- Generate an API key
2. **Configure Environment**:
- Copy `.env.example` to `.env.local`
- Add your API key: `NEXT_PUBLIC_ORS_API_KEY=your_actual_api_key`
3. **Install Dependencies**:
```bash
npm install @mapbox/polyline
```
4. **Restart Development Server**:
```bash
npm run dev
```
## How to Use
### Basic Routing (2 Points)
1. **Select Route Tool**: Click the route icon in the tool panel (looks like a path)
2. **Add Projects**: Click on project markers to add them to your route
3. **Calculate Route**: Click "Calculate Route" to get directions
4. **View Results**: See distance, duration, and route path on the map
### Optimized Routing (3+ Points)
1. **Select Route Tool**: Click the route icon in the tool panel
2. **Add Projects**: Click on project markers (order doesn't matter)
3. **Find Optimal Route**: Click "Find Optimal Route" - system automatically finds fastest path
4. **View Optimization Results**: See which route order was selected and performance stats
## Features
### Core Features
- **Multi-point routing**: Plan routes through multiple project locations
- **Visual route display**: Blue dashed line shows the calculated route
- **Route markers**: Green start marker, red end marker
- **Route information**: Distance and estimated travel time
- **Interactive management**: Add/remove projects from route
- **Map auto-fit**: Automatically adjusts map view to show entire route
### Optimization Features ✨
- **Hybrid Optimization**: Uses ORS Optimization API first, falls back to permutation testing
- **Smart Fallback**: Automatically switches to proven permutation method if ORS fails
- **Order Detection**: Clearly shows when route order was actually optimized vs unchanged
- **Large Point Support**: Can handle up to 50+ points with ORS API
- **Performance Monitoring**: Detailed logging of optimization approach and results
- **Real-time Progress**: Shows "Finding Optimal Route..." during calculation
## Technical Implementation
### Core Functions
#### `calculateRoute()`
Main function that handles both basic and optimized routing with hybrid approach:
```javascript
const calculateRoute = async () => {
// For 2 points: direct calculation
if (coordinates.length === 2) {
const routeData = await calculateRouteForCoordinates(coordinates);
setRouteData({...routeData, optimized: false});
return;
}
// For 3+ points: try ORS Optimization API first
let optimizationRequest = {
jobs: coordinates.map((coord, index) => ({
id: index,
location: coord,
service: 0
})),
vehicles: [{
id: 0,
profile: 'driving-car',
// No fixed start/end for true optimization
capacity: [coordinates.length]
}],
options: { g: true }
};
try {
const optimizationResponse = await fetch('https://api.openrouteservice.org/optimization', {
method: 'POST',
headers: {
'Authorization': process.env.NEXT_PUBLIC_ORS_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(optimizationRequest)
});
const optimizationData = await optimizationResponse.json();
// Extract optimized order from ORS response
const optimizedCoordinates = extractOptimizedOrder(optimizationData, coordinates);
// Check if order actually changed
const orderChanged = detectOrderChange(coordinates, optimizedCoordinates);
if (orderChanged) {
// Use optimized order
const routeData = await calculateRouteForCoordinates(optimizedCoordinates);
setRouteData({...routeData, optimized: true, optimizationStats: {
method: 'ORS_Optimization_API',
totalJobs: coordinates.length,
duration: optimizationData.routes[0].duration,
distance: optimizationData.routes[0].distance
}});
} else {
// Fallback to permutation testing
console.log('ORS optimization did not change order, trying permutations...');
const bestRoute = await findOptimalRouteByPermutations(coordinates);
const routeData = await calculateRouteForCoordinates(bestRoute);
setRouteData({...routeData, optimized: true, optimizationStats: {
method: 'Permutation_Testing',
totalJobs: coordinates.length,
duration: routeData.summary.total_duration,
distance: routeData.summary.total_distance
}});
}
} catch (error) {
// Complete fallback to permutations
console.log('ORS optimization failed, using permutation fallback...');
const bestRoute = await findOptimalRouteByPermutations(coordinates);
const routeData = await calculateRouteForCoordinates(bestRoute);
setRouteData({...routeData, optimized: true, optimizationStats: {
method: 'Permutation_Testing',
totalJobs: coordinates.length,
duration: routeData.summary.total_duration,
distance: routeData.summary.total_distance
}});
}
};
```
#### `calculateRouteForCoordinates(coordinates)`
Handles individual OpenRouteService Directions API calls:
```javascript
const calculateRouteForCoordinates = async (coordinates) => {
const requestBody = {
coordinates: coordinates,
format: 'geojson',
instructions: true,
geometry_simplify: false,
continue_straight: false,
roundabout_exits: true,
attributes: ['avgspeed', 'percentage']
};
const response = await fetch('https://api.openrouteservice.org/v2/directions/driving-car', {
method: 'POST',
headers: {
'Authorization': process.env.NEXT_PUBLIC_ORS_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
return await response.json();
};
```
### UI Components
#### Dynamic Button Text
```javascript
{routeProjects.length > 2 ? 'Find Optimal Route' : 'Calculate Route'}
```
#### Optimization Status Display
```javascript
{routeData.optimized && (
<div className="mb-2 p-2 bg-green-50 border border-green-200 rounded">
<div className="flex items-center gap-1 font-medium">
✅ Route Optimized
</div>
<div className="mt-1">
Tested {routeData.optimizationStats.totalPermutations} routes
</div>
</div>
)}
```
## Performance Considerations
### Optimization Limits
- **Maximum Points**: Limited to 50 points (ORS can handle 100+ in some cases)
- **Algorithm**: Advanced TSP solver instead of brute-force permutations
- **API Calls**: Only 2 API calls (1 optimization + 1 detailed route)
- **Processing Time**: ~1-2 seconds for 50 points (much faster than permutation testing)
### Memory Usage
- Each route response contains detailed geometry data
- Large numbers of points can consume significant memory
- Automatic cleanup of unused route data
## API Integration
### OpenRouteService Optimization API
```javascript
{
jobs: [
{ id: 0, location: [lng, lat], service: 0 },
{ id: 1, location: [lng, lat], service: 0 }
],
vehicles: [{
id: 0,
profile: 'driving-car',
start: [lng, lat],
end: [lng, lat],
capacity: [point_count]
}],
options: { g: true }
}
```
### Directions API Parameters
```javascript
{
coordinates: [[lng, lat], [lng, lat], ...],
format: 'geojson',
instructions: true,
geometry_simplify: false,
continue_straight: false,
roundabout_exits: true,
attributes: ['avgspeed', 'percentage']
}
```
### Response Handling
- **Optimization API**: `data.routes[0].steps[]` for optimized order
- **Directions API**: `data.routes[0].summary` for route details
- **Fallback Path**: `data.features[0].properties.segments[0]`
- **Geometry**: Supports both encoded polylines and direct coordinates
- **Error Handling**: Graceful fallback for failed calculations
## Troubleshooting
### Common Issues
#### "Failed to calculate route"
- **Cause**: Invalid API key or network issues
- **Solution**: Verify `NEXT_PUBLIC_ORS_API_KEY` in `.env.local`
#### "Too many points for optimization"
- **Cause**: Selected more than 50 points
- **Solution**: Reduce to 50 or fewer points, or use manual routing
#### Optimization taking too long
- **Cause**: Large number of points or slow API responses
- **Solution**: Reduce points or wait for completion (much faster than before)
#### Optimization API unavailable
- **Cause**: ORS Optimization API temporarily unavailable
- **Solution**: Falls back to direct routing without optimization
#### Route order not optimized
- **Cause**: ORS Optimization API returned same order or failed
- **Solution**: System automatically falls back to permutation testing for guaranteed optimization
#### Optimization shows "Order unchanged"
- **Cause**: Points may already be in optimal order, or API returned original sequence
- **Solution**: Check browser console for detailed optimization logs
#### Permutation fallback activated
- **Cause**: ORS API unavailable or returned suboptimal results
- **Solution**: This is normal behavior - permutation testing ensures optimization
---
## 🔧 Troubleshooting
### Common Issues
#### 1. "API Key Missing" Error
**Symptom**: Route calculation fails with authentication error
**Solutions**:
- Check `.env.local` file has `NEXT_PUBLIC_ORS_API_KEY=your_key`
- Verify no extra spaces around the key
- Ensure development server was restarted after adding the key
- Confirm your OpenRouteService API key is active
```bash
# Verify environment variable
echo $env:NEXT_PUBLIC_ORS_API_KEY
# Should output your API key
```
---
#### 2. Route Not Displaying on Map
**Symptom**: Calculation succeeds but no route visible
**Solutions**:
- Check browser console for coordinate transformation errors
- Verify all projects have valid coordinates in database
- Confirm map is zoomed to appropriate level
- Check if route layer is enabled in layer control
**Debug**:
```javascript
// Check route data in browser console
console.log('Route GeoJSON:', routeData.geojson);
console.log('Route bounds:', routeData.bounds);
```
---
#### 3. Optimization Takes Too Long
**Symptom**: "Find Optimal Route" hangs or times out for many points
**Current Limits**:
- 8+ points: May take 30+ seconds
- 10+ points: Not recommended (factorial growth)
**Solutions**:
- Split route into multiple segments
- Use manual point selection for 8+ locations
- Consider implementing A* or genetic algorithm for large routes
**Permutation Growth**:
```
3 points = 6 routes to test
4 points = 24 routes
5 points = 120 routes
6 points = 720 routes
7 points = 5,040 routes
8 points = 40,320 routes
```
---
#### 4. API Rate Limit Exceeded
**Symptom**: Error 429 or "Too many requests"
**Solutions**:
- OpenRouteService free tier: 40 requests/minute, 500/day
- Wait 1 minute and try again
- Consider upgrading to paid plan for higher limits
- Implement request queuing with delays
```javascript
// Add rate limiting check
if (routeProjects.length > 5) {
alert('Large route may hit rate limits. Consider breaking into segments.');
}
```
---
#### 5. Incorrect Route Order
**Symptom**: Optimization doesn't select expected fastest route
**Causes**:
- Road network topology (one-way streets, traffic restrictions)
- API routing preferences (avoid highways, ferries)
- Distance vs time optimization trade-offs
**Verification**:
```javascript
// Check all tested routes in console
routeData.optimizationStats.testedRoutes.forEach(route => {
console.log(`Route ${route.order}: ${route.distance}m in ${route.duration}s`);
});
```
---
#### 6. Map Coordinate Transformation Errors
**Symptom**: "Failed to transform coordinates" in console
**Solutions**:
- Verify Proj4 definitions are loaded
- Check project coordinates are in valid EPSG:2180 format
- Confirm transformation libraries are properly initialized
```javascript
// Test coordinate transformation
import proj4 from 'proj4';
const wgs84 = proj4('EPSG:2180', 'EPSG:4326', [x, y]);
console.log('Transformed:', wgs84);
```
---
### Performance Tips
1. **Batch Route Calculations**: Group nearby projects before calculating routes
2. **Cache Routes**: Store frequently used routes in localStorage
3. **Limit Points**: Use max 7 points for real-time optimization
4. **Debounce Updates**: Wait for user to finish selecting points
5. **Progressive Loading**: Calculate partial routes while building full path
---
### API Limitations
| Tier | Requests/Minute | Requests/Day | Cost |
|------|----------------|--------------|------|
| Free | 40 | 500 | $0 |
| Starter | 300 | 10,000 | Contact ORS |
| Business | Custom | Custom | Contact ORS |
**Best Practices**:
- Avoid unnecessary recalculations
- Implement client-side caching
- Show loading states during API calls
- Handle errors gracefully with user feedback
---
### Quick Reference
**Enable Route Planning**:
```bash
# 1. Get API key from openrouteservice.org
# 2. Add to .env.local
NEXT_PUBLIC_ORS_API_KEY=your_key_here
# 3. Restart dev server
npm run dev
```
**Debug Mode**:
```javascript
// Enable in RoutePanel.js
const DEBUG = true;
// Logs all tested routes and optimization stats
```
**Performance Monitoring**:
```javascript
console.time('Route Optimization');
await optimizeRoute();
console.timeEnd('Route Optimization');
// Shows exact optimization duration
```
---
### Debug Information
Check browser console for detailed logs:
- Coordinate parsing details
- API request/response structures
- **Optimization approach used** (ORS API vs permutation fallback)
- **Order change detection** (whether optimization actually improved the route)
- Performance timing information
- **Original vs optimized coordinate sequences**
---
---
## 📁 File Structure
```
src/app/projects/map/page.js # Main map page with routing logic
src/components/ui/LeafletMap.js # Map component with route rendering
src/components/ui/mapLayers.js # Map layer configurations
```
---
## 📦 Dependencies
- `@mapbox/polyline`: For decoding route geometry
- `leaflet`: Map rendering library
- `react-leaflet`: React integration for Leaflet
- `proj4`: Coordinate system transformations
- OpenRouteService API key (free tier available)
---
## 🚀 Future Enhancements
- **Advanced Vehicle Constraints**: Multiple vehicles, capacity limits, time windows
- **Route Preferences**: Allow users to prioritize distance vs time vs fuel efficiency
- **Real-time Traffic**: Integration with live traffic data
- **Route History**: Save and compare previously optimized routes
- **Mobile Optimization**: Optimize routes considering current location
- **Multi-stop Services**: Add service times at each location
- **Advanced Optimization**: Implement A* or genetic algorithms for 8+ points
- **Multi-Day Routes**: Break long routes into segments with overnight stops
- **Export Options**: Export routes to GPS devices or Google Maps
- **Cost Estimation**: Calculate fuel costs and travel expenses
---
## 📚 Additional Resources
- [OpenRouteService API Documentation](https://openrouteservice.org/dev/#/api-docs)
- [Directions API Reference](https://openrouteservice.org/dev/#/api-docs/v2/directions)
- [Polyline Encoding](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
- [Leaflet Routing Integration](https://www.liedman.net/leaflet-routing-machine/)
---
**Last Updated**: January 2025
**Maintainer**: Panel Development Team