feat: add contact management functionality
- 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.
This commit is contained in:
188
migrate-project-contacts.mjs
Normal file
188
migrate-project-contacts.mjs
Normal file
@@ -0,0 +1,188 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user