Skip to content

feat: Add comprehensive vCard import UI with organization, avatar, and notes support#7919

Open
cabraham2 wants to merge 1 commit intomonicahq:mainfrom
cabraham2:feature/vcard-import-enhancements
Open

feat: Add comprehensive vCard import UI with organization, avatar, and notes support#7919
cabraham2 wants to merge 1 commit intomonicahq:mainfrom
cabraham2:feature/vcard-import-enhancements

Conversation

@cabraham2
Copy link
Copy Markdown

@cabraham2 cabraham2 commented Jan 8, 2026

Summary

This PR implements a complete vCard import feature with a web UI, enabling users to import contacts from standard vCard files (.vcf) with full support for organizations, photos, notes, and all standard contact fields.

Motivation

Monica currently lacks a user-facing way to import contacts from vCard files (the standard format used by Google Contacts, iCloud, Outlook, etc.). Users migrating from other contact management systems cannot easily bring their data into Monica.

What This Adds

New Web UI

  • Import page at /vaults/{vault}/contacts/import
  • Upload vCard files and preview all contacts before import
  • Select which contacts to import (checkbox selection)
  • Progress bar with real-time percentage during import
  • Error details showing which contacts failed and why

Complete vCard Support

  • Organizations (ORG field): Links contacts to companies via company_id
  • Avatars (PHOTO field): Imports contact photos
  • Job Information (TITLE field): Preserves job titles
  • Notes (NOTE field): Imports all notes
  • Addresses: Full support with proper formatting (HOME/WORK/OTHER)
  • Contact Information: Phone, email, all types supported

Technical Implementation

  • Chunked Processing: Handles large files (1000+ contacts) without PHP timeout
    • Processes 50 contacts per batch
    • Real-time progress tracking
  • Robust Error Handling:
    • Detailed error messages for failed imports
    • Shows contact names and reasons for failure
    • Continues processing remaining contacts on error
  • Clean Architecture:
    • New DAV importers: ImportJobInformation, ImportAvatar, ImportNotes
    • Order attribute system for proper import sequence
    • Complete PHPDoc documentation

Real-World Testing

Tested with Google Contacts export (1,260 contacts, 2.7 MB):

  • 1,238 contacts imported successfully (98.3% success rate)
  • ❌ 22 failures = invalid vCards in source file (no names/empty entries)
  • ⚡ No timeouts with chunked processing
  • 🔄 Full progress feedback to users

Technical Documentation

See docs/VCARD_IMPORT_ENHANCEMENTS.md for:

  • Complete architecture overview
  • Import flow sequence diagrams
  • Order attribute system explanation
  • Error handling strategy

Files Added

Controllers & Services:

  • ContactImportController.php - Web endpoint for import
  • ParseVCardFile.php - vCard parsing service
  • ContactImportViewHelper.php - Data preparation for UI

DAV Importers:

  • ImportJobInformation.php - Organization/job import
  • ImportAvatar.php - Photo import
  • ImportNotes.php - Notes import

Frontend:

  • resources/js/Pages/Vault/Contact/Import/Index.vue - Import UI

Tests:

  • Unit tests for ParseVCardFile
  • Feature tests for ContactImportController

Breaking Changes

None. This is a new feature that doesn't affect existing functionality.

Checklist

  • Complete feature tested with 1,260 real contacts
  • All code has PHPDoc documentation
  • Unit and feature tests included
  • Follows Monica's architecture patterns
  • No breaking changes
  • Technical documentation provided

🏗️ Architecture - Diagramme de flux

┌─────────────────────────────────────────────────────────────────┐
│                    Import vCard (.vcf file)                     │
│                     ContactImportController                     │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│              Sabre\VObject\Reader::read($vcard)                 │
│                  Parse vCard format (RFC 6350)                  │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                    DAV Import Pipeline                          │
│              Multiple Importers (Order attribute)               │
└────────────────────────────┬────────────────────────────────────┘
                             │
         ┌───────────────────┼───────────────────┐
         │                   │                   │
         ▼                   ▼                   ▼
┌──────────────────┐  ┌─────────────┐  ┌────────────────────┐
│  ImportAvatar    │  │ ImportNotes │  │ ImportJobInform..  │
│  Order(20)       │  │ Order(30)   │  │ Order(41) - NEW    │
│                  │  │             │  │                    │
│ PHOTO field →    │  │ NOTE field  │  │ ORG field →        │
│ storage/photos   │  │ → notes tbl │  │ Company + Link     │
└──────────────────┘  └─────────────┘  └────────────────────┘
         │                   │                   │
         │                   ▼                   ▼
         │          ┌──────────────┐   ┌──────────────────┐
         │          │ CreateNote() │   │ CreateCompany()  │
         │          └──────────────┘   │ or find existing │
         │                             │                  │
         │                             │ UpdateJobInfo()  │
         │                             │ → contacts.      │
         │                             │   company_id     │
         │                             └──────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│                        ImportAddress                            │
│                         Order(40)                               │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
         ┌───────────────────┴───────────────────┐
         │                                       │
         ▼                                       ▼
┌─────────────────────┐              ┌─────────────────────────┐
│  getAddressType()   │              │ formatStreetAddress()   │
│                     │              │ - NEW FUNCTION          │
│ HOME,pref → home    │              │                         │
│ Create if missing   │              │ "61\nRue" → "61, Rue"   │
│                     │              │ "61Rue" → "61, Rue"     │
└─────────────────────┘              └─────────────────────────┘
         │                                       │
         └───────────────────┬───────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                  vCard ADR field mapping                        │
│                                                                 │
│  parts[0]: PO Box (unused)                                      │
│  parts[1]: Extended (apartment/suite) → line_2                  │
│  parts[2]: Street → line_1 (via formatStreetAddress)            │
│  parts[3]: City                                                 │
│  parts[4]: Province                                             │
│  parts[5]: Postal Code                                          │
│  parts[6]: Country                                              │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│            UpdateAddress() or CreateAddress()                   │
│                 + AssociateAddressToContact()                   │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Contact Feed Display                         │
│                  ActionFeedAddress helper                       │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
         ┌───────────────────┴───────────────────┐
         │                                       │
         ▼                                       ▼
┌─────────────────────┐              ┌─────────────────────────┐
│  Map Image (opt.)   │              │  Address Display        │
│                     │              │                         │
│ MapHelper::         │              │ line_1: "61, Rue..."    │
│ getStaticImage()    │              │ line_2: "Apt 2"         │
│                     │              │ city, province, etc.    │
│ If Mapbox config:   │              │                         │
│ → Generate URL      │              │ Type: 🏡 Home           │
│                     │              │                         │
│ If not configured:  │              │ View on map (OSM link)  │
│ → Return null       │              │                         │
│ → No broken image   │              └─────────────────────────┘
└─────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│      ContactModuleAddressImageController::show()                │
│                                                                 │
│   If MapHelper returns null → abort(404)                        │
│   Else → Proxy Mapbox API request → Stream image                │
└─────────────────────────────────────────────────────────────────┘

Made with ❤️ for Monica CRM

…nd error details

- Add ImportJobInformation for proper ORG field handling (company_id)
- Add ImportAvatar and ImportNotes for photo and note import
- Implement chunked import (50 contacts/batch) to avoid timeouts
- Add detailed error reporting with contact names and error types
- Fix address formatting (newlines, spacing, type mapping)
- Fix map image 500 errors (graceful 404 when Mapbox not configured)
- Remove trailing semicolons from names and organizations
- Add comprehensive PHPDoc documentation

Real-world tested: 1,238/1,260 contacts imported (98.3% success rate)
22 failures were invalid vCards in source file (no name/empty entries)

Documentation:
- PULL_REQUEST.md: GitHub PR description
- docs/VCARD_IMPORT_ENHANCEMENTS.md: Technical documentation
- PROJECT_SUMMARY.md: Complete feature overview
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jan 8, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants