- Implemented ContactForm component for creating and editing contacts. - Added ProjectContactSelector component to manage project-specific contacts. - Updated ProjectForm to include ProjectContactSelector for associating contacts with projects. - Enhanced Card component with a new CardTitle subcomponent for better structure. - Updated Navigation to include a link to the contacts page. - Added translations for contact-related terms in the i18n module. - Initialized contacts database schema and created necessary tables for contact management. - Developed queries for CRUD operations on contacts, including linking and unlinking contacts to projects. - Created a test script to validate contact queries against the database.
189 lines
6.0 KiB
JavaScript
189 lines
6.0 KiB
JavaScript
import db from './src/lib/db.js';
|
||
import initializeDatabase from './src/lib/init-db.js';
|
||
|
||
console.log('🚀 Migrating contact data from projects...\n');
|
||
|
||
try {
|
||
// Run database initialization to ensure tables exist
|
||
initializeDatabase();
|
||
|
||
console.log('✅ Database tables verified\n');
|
||
|
||
// Get all projects with contact data
|
||
const projectsWithContacts = db.prepare(`
|
||
SELECT project_id, project_name, contact
|
||
FROM projects
|
||
WHERE contact IS NOT NULL AND contact != '' AND TRIM(contact) != ''
|
||
`).all();
|
||
|
||
if (projectsWithContacts.length === 0) {
|
||
console.log('ℹ️ No contact data found in projects to migrate.\n');
|
||
process.exit(0);
|
||
}
|
||
|
||
console.log(`📋 Found ${projectsWithContacts.length} projects with contact information\n`);
|
||
|
||
let created = 0;
|
||
let linked = 0;
|
||
let skipped = 0;
|
||
|
||
const createContact = db.prepare(`
|
||
INSERT INTO contacts (name, phone, email, contact_type, notes, is_active)
|
||
VALUES (?, ?, ?, 'project', ?, 1)
|
||
`);
|
||
|
||
const linkContact = db.prepare(`
|
||
INSERT OR IGNORE INTO project_contacts (project_id, contact_id, is_primary, relationship_type)
|
||
VALUES (?, ?, 1, 'general')
|
||
`);
|
||
|
||
// Process each project
|
||
for (const project of projectsWithContacts) {
|
||
try {
|
||
const contactText = project.contact.trim();
|
||
|
||
// Parse contact information - common formats:
|
||
// "Jan Kowalski, tel. 123-456-789"
|
||
// "Jan Kowalski 123-456-789"
|
||
// "123-456-789"
|
||
// "Jan Kowalski"
|
||
|
||
let name = '';
|
||
let phone = '';
|
||
let email = '';
|
||
let notes = '';
|
||
|
||
// Try to extract email
|
||
const emailPattern = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/;
|
||
const emailMatch = contactText.match(emailPattern);
|
||
if (emailMatch) {
|
||
email = emailMatch[1].trim();
|
||
}
|
||
|
||
// Try to extract phone number (various formats)
|
||
const phonePatterns = [
|
||
/(?:\+?48)?[\s-]?(\d{3}[\s-]?\d{3}[\s-]?\d{3})/, // Polish: 123-456-789, 123 456 789, +48 123456789
|
||
/(?:\+?48)?[\s-]?(\d{9})/, // 9 digits
|
||
/tel\.?\s*[:.]?\s*([+\d\s-]+)/i, // tel. 123-456-789
|
||
/phone\s*[:.]?\s*([+\d\s-]+)/i, // phone: 123-456-789
|
||
/(\d{3}[-\s]?\d{3}[-\s]?\d{3})/, // Generic phone pattern
|
||
];
|
||
|
||
for (const pattern of phonePatterns) {
|
||
const match = contactText.match(pattern);
|
||
if (match) {
|
||
phone = match[1] || match[0];
|
||
phone = phone.replace(/\s+/g, ' ').trim();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Extract name (text before phone/email or comma)
|
||
let textForName = contactText;
|
||
|
||
if (phone) {
|
||
// Remove phone from text to get name
|
||
textForName = textForName.replace(phone, '');
|
||
}
|
||
if (email) {
|
||
// Remove email from text to get name
|
||
textForName = textForName.replace(email, '');
|
||
}
|
||
|
||
// Remove common prefixes like "tel.", "phone:", "email:", commas, etc.
|
||
name = textForName.replace(/tel\.?|phone:?|email:?|e-mail:?|,/gi, '').trim();
|
||
|
||
// Clean up name
|
||
name = name.replace(/^[,\s-]+|[,\s-]+$/g, '').trim();
|
||
|
||
// If we couldn't extract structured data, use project name and put original text in notes
|
||
if (!phone && !email) {
|
||
// No structured contact info found, put everything in notes
|
||
notes = `Original contact info: ${contactText}`;
|
||
name = project.project_name;
|
||
} else if (!name) {
|
||
// We have phone/email but no clear name
|
||
name = project.project_name;
|
||
}
|
||
|
||
// Check if this contact already exists (by name, phone, or email)
|
||
let existingContact = null;
|
||
if (phone) {
|
||
existingContact = db.prepare(`
|
||
SELECT contact_id FROM contacts
|
||
WHERE phone LIKE ? OR phone LIKE ?
|
||
`).get(`%${phone}%`, `%${phone.replace(/\s/g, '')}%`);
|
||
}
|
||
|
||
if (!existingContact && email) {
|
||
existingContact = db.prepare(`
|
||
SELECT contact_id FROM contacts
|
||
WHERE LOWER(email) = LOWER(?)
|
||
`).get(email);
|
||
}
|
||
|
||
if (!existingContact && name && name !== project.project_name) {
|
||
existingContact = db.prepare(`
|
||
SELECT contact_id FROM contacts
|
||
WHERE LOWER(name) = LOWER(?)
|
||
`).get(name);
|
||
}
|
||
|
||
let contactId;
|
||
|
||
if (existingContact) {
|
||
contactId = existingContact.contact_id;
|
||
console.log(` ♻️ Using existing contact "${name}" for project "${project.project_name}"`);
|
||
} else {
|
||
// Create new contact
|
||
const result = createContact.run(
|
||
name,
|
||
phone || null,
|
||
email || null,
|
||
notes || `Migrated from project: ${project.project_name}`
|
||
);
|
||
contactId = result.lastInsertRowid;
|
||
created++;
|
||
|
||
const contactInfo = [];
|
||
if (phone) contactInfo.push(`📞 ${phone}`);
|
||
if (email) contactInfo.push(`📧 ${email}`);
|
||
const infoStr = contactInfo.length > 0 ? ` (${contactInfo.join(', ')})` : '';
|
||
|
||
console.log(` ✨ Created contact "${name}"${infoStr} for project "${project.project_name}"`);
|
||
}
|
||
|
||
// Link contact to project
|
||
linkContact.run(project.project_id, contactId);
|
||
linked++;
|
||
|
||
} catch (error) {
|
||
console.error(` ❌ Error processing project "${project.project_name}":`, error.message);
|
||
skipped++;
|
||
}
|
||
}
|
||
|
||
console.log('\n📊 Migration Summary:');
|
||
console.log(` - Contacts created: ${created}`);
|
||
console.log(` - Project-contact links created: ${linked}`);
|
||
console.log(` - Projects skipped: ${skipped}`);
|
||
console.log(` - Total projects processed: ${projectsWithContacts.length}`);
|
||
|
||
// Show final statistics
|
||
const contactsCount = db.prepare('SELECT COUNT(*) as count FROM contacts').get();
|
||
const projectContactsCount = db.prepare('SELECT COUNT(*) as count FROM project_contacts').get();
|
||
|
||
console.log('\n📈 Current Database Statistics:');
|
||
console.log(` - Total contacts: ${contactsCount.count}`);
|
||
console.log(` - Total project-contact links: ${projectContactsCount.count}`);
|
||
|
||
console.log('\n✨ Migration complete!');
|
||
console.log(' - Visit /contacts to view and manage your contacts');
|
||
console.log(' - Edit projects to see linked contacts');
|
||
console.log(' - The old contact text field is preserved for reference\n');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error during migration:', error);
|
||
process.exit(1);
|
||
}
|