tractatus/public/js/admin/submission-modal-old.js
TheFlow 2298d36bed fix(submissions): restructure Economist package and fix article display
- Create Economist SubmissionTracking package correctly:
  * mainArticle = full blog post content
  * coverLetter = 216-word SIR— letter
  * Links to blog post via blogPostId
- Archive 'Letter to The Economist' from blog posts (it's the cover letter)
- Fix date display on article cards (use published_at)
- Target publication already displaying via blue badge

Database changes:
- Make blogPostId optional in SubmissionTracking model
- Economist package ID: 68fa85ae49d4900e7f2ecd83
- Le Monde package ID: 68fa2abd2e6acd5691932150

Next: Enhanced modal with tabs, validation, export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 08:47:42 +13:00

379 lines
14 KiB
JavaScript

/**
* Manage Submission Modal
* Handles submission tracking for blog posts to external publications
*/
// Create the modal HTML structure dynamically
function createManageSubmissionModal() {
const modal = document.createElement('div');
modal.id = 'manage-submission-modal';
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden';
modal.innerHTML = `
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto mx-4">
<div class="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center">
<h2 class="text-2xl font-bold text-gray-900">Manage Submission</h2>
<button id="close-submission-modal" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="p-6 space-y-6">
<!-- Article Info -->
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r">
<h3 id="article-title" class="font-semibold text-gray-900 mb-1"></h3>
<p id="article-excerpt" class="text-sm text-gray-600"></p>
<div class="mt-2 text-xs text-gray-500">
<span id="article-word-count"></span> words
</div>
</div>
<!-- Publication Selection -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Target Publication
</label>
<select id="publication-select"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<option value="">Select a publication...</option>
</select>
<div id="publication-requirements" class="mt-3 hidden">
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-3 text-sm">
<h4 class="font-semibold text-gray-900 mb-2">Requirements</h4>
<div id="requirements-content"></div>
</div>
</div>
</div>
<!-- Progress Indicator -->
<div>
<div class="flex justify-between text-sm text-gray-600 mb-2">
<span>Submission Package Progress</span>
<span id="progress-percentage">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progress-bar" class="bg-green-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<!-- Checklist Items -->
<div class="space-y-4">
<h3 class="text-lg font-semibold text-gray-900">Submission Package</h3>
<!-- Cover Letter -->
<div class="border border-gray-200 rounded-lg p-4">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center">
<input type="checkbox" id="check-coverLetter"
class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
onchange="saveChecklistItem('coverLetter')">
<label for="check-coverLetter" class="ml-3 font-medium text-gray-900">
Cover Letter
</label>
</div>
<span id="saved-coverLetter" class="text-xs text-green-600 hidden">✓ Saved</span>
</div>
<textarea id="content-coverLetter"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows="4"
placeholder="Write your cover letter here..."
onblur="saveChecklistItem('coverLetter')"></textarea>
</div>
<!-- Pitch Email -->
<div class="border border-gray-200 rounded-lg p-4">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center">
<input type="checkbox" id="check-pitchEmail"
class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
onchange="saveChecklistItem('pitchEmail')">
<label for="check-pitchEmail" class="ml-3 font-medium text-gray-900">
Pitch Email
</label>
</div>
<span id="saved-pitchEmail" class="text-xs text-green-600 hidden">✓ Saved</span>
</div>
<textarea id="content-pitchEmail"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows="4"
placeholder="Write your pitch email here..."
onblur="saveChecklistItem('pitchEmail')"></textarea>
</div>
<!-- Notes to Editor -->
<div class="border border-gray-200 rounded-lg p-4">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center">
<input type="checkbox" id="check-notesToEditor"
class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
onchange="saveChecklistItem('notesToEditor')">
<label for="check-notesToEditor" class="ml-3 font-medium text-gray-900">
Notes to Editor
</label>
</div>
<span id="saved-notesToEditor" class="text-xs text-green-600 hidden">✓ Saved</span>
</div>
<textarea id="content-notesToEditor"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows="4"
placeholder="Any additional notes for the editor..."
onblur="saveChecklistItem('notesToEditor')"></textarea>
</div>
<!-- Author Bio -->
<div class="border border-gray-200 rounded-lg p-4">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center">
<input type="checkbox" id="check-authorBio"
class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
onchange="saveChecklistItem('authorBio')">
<label for="check-authorBio" class="ml-3 font-medium text-gray-900">
Author Bio
</label>
</div>
<span id="saved-authorBio" class="text-xs text-green-600 hidden">✓ Saved</span>
</div>
<textarea id="content-authorBio"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows="3"
placeholder="Your author biography..."
onblur="saveChecklistItem('authorBio')"></textarea>
</div>
</div>
</div>
</div>
`;
return modal;
}
// Open modal and load data
async function openManageSubmissionModal(articleId, submissionId) {
currentSubmissionArticleId = articleId;
currentSubmissionId = submissionId;
// Create modal if it doesn't exist
let modal = document.getElementById('manage-submission-modal');
if (!modal) {
modal = createManageSubmissionModal();
document.getElementById('modal-container').appendChild(modal);
// Add close handler
document.getElementById('close-submission-modal').addEventListener('click', closeManageSubmissionModal);
// Load publication targets
await loadPublicationTargets();
// Add publication change handler
document.getElementById('publication-select').addEventListener('change', onPublicationChange);
}
// Load article and submission data
await loadSubmissionData(articleId);
// Show modal
modal.classList.remove('hidden');
}
// Close modal
function closeManageSubmissionModal() {
const modal = document.getElementById('manage-submission-modal');
if (modal) {
modal.classList.add('hidden');
clearSubmissionForm();
}
currentSubmissionArticleId = null;
currentSubmissionId = null;
currentArticleData = null;
}
// Load publication targets into dropdown
async function loadPublicationTargets() {
try {
const response = await fetch('/api/publications');
const data = await response.json();
const select = document.getElementById('publication-select');
if (!select) return; // Modal not created yet
const publications = data.data || [];
publications.forEach(pub => {
const option = document.createElement('option');
option.value = pub.id;
option.textContent = `${pub.name} (${pub.type})`;
option.dataset.requirements = JSON.stringify(pub.requirements);
option.dataset.submission = JSON.stringify(pub.submission);
select.appendChild(option);
});
} catch (error) {
console.error('Failed to load publication targets:', error);
}
}
// Handle publication selection change
function onPublicationChange(e) {
const select = e.target;
const selectedOption = select.options[select.selectedIndex];
const requirementsDiv = document.getElementById('publication-requirements');
const requirementsContent = document.getElementById('requirements-content');
if (!selectedOption.value) {
requirementsDiv.classList.add('hidden');
return;
}
const requirements = JSON.parse(selectedOption.dataset.requirements || '{}');
const submission = JSON.parse(selectedOption.dataset.submission || '{}');
let html = '<ul class="space-y-1">';
if (requirements.wordCount) {
html += `<li><strong>Word Count:</strong> ${requirements.wordCount.min}-${requirements.wordCount.max} words</li>`;
}
if (requirements.exclusivity) {
html += '<li><strong>Exclusivity:</strong> Must not be published elsewhere</li>';
}
if (submission.method) {
html += `<li><strong>Method:</strong> ${submission.method}`;
if (submission.email) {
html += ` (${submission.email})`;
}
html += '</li>';
}
if (submission.responseTime) {
html += `<li><strong>Response Time:</strong> ${submission.responseTime.min}-${submission.responseTime.max} ${submission.responseTime.unit}</li>`;
}
html += '</ul>';
requirementsContent.innerHTML = html;
requirementsDiv.classList.remove('hidden');
}
// Load submission data
async function loadSubmissionData(articleId) {
try {
// Load article data
const articleResponse = await fetch(`/api/blog/${articleId}`);
const articleData = await articleResponse.json();
currentArticleData = articleData;
// Populate article info
document.getElementById('article-title').textContent = articleData.title;
document.getElementById('article-excerpt').textContent = articleData.excerpt || '';
document.getElementById('article-word-count').textContent = articleData.wordCount || 0;
// Load existing submission if any
const submissionResponse = await fetch(`/api/blog/${articleId}/submissions`);
if (submissionResponse.ok) {
const submissionData = await submissionResponse.json();
if (submissionData.submissions && submissionData.submissions.length > 0) {
const submission = submissionData.submissions[0];
currentSubmissionId = submission._id;
// Populate publication select
if (submission.publicationId) {
document.getElementById('publication-select').value = submission.publicationId;
onPublicationChange({ target: document.getElementById('publication-select') });
}
// Populate checklist items
const fields = ['coverLetter', 'pitchEmail', 'notesToEditor', 'authorBio'];
fields.forEach(field => {
const packageData = submission.submissionPackage?.[field];
if (packageData) {
document.getElementById(`check-${field}`).checked = packageData.completed || false;
document.getElementById(`content-${field}`).value = packageData.content || '';
}
});
updateProgress();
}
}
} catch (error) {
console.error('Failed to load submission data:', error);
}
}
// Clear submission form
function clearSubmissionForm() {
document.getElementById('publication-select').value = '';
document.getElementById('publication-requirements').classList.add('hidden');
const fields = ['coverLetter', 'pitchEmail', 'notesToEditor', 'authorBio'];
fields.forEach(field => {
document.getElementById(`check-${field}`).checked = false;
document.getElementById(`content-${field}`).value = '';
});
updateProgress();
}
// Save checklist item
async function saveChecklistItem(field) {
if (!currentSubmissionArticleId) return;
const publicationId = document.getElementById('publication-select').value;
if (!publicationId) {
alert('Please select a publication first');
return;
}
const completed = document.getElementById(`check-${field}`).checked;
const content = document.getElementById(`content-${field}`).value;
try {
const response = await fetch(`/api/blog/${currentSubmissionArticleId}/submissions`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
publicationId,
submissionPackage: {
[field]: {
completed,
content,
lastUpdated: new Date().toISOString()
}
}
})
});
if (response.ok) {
const data = await response.json();
currentSubmissionId = data.submission._id;
// Show saved indicator
const savedIndicator = document.getElementById(`saved-${field}`);
savedIndicator.classList.remove('hidden');
setTimeout(() => {
savedIndicator.classList.add('hidden');
}, 2000);
updateProgress();
} else {
console.error('Failed to save checklist item');
}
} catch (error) {
console.error('Error saving checklist item:', error);
}
}
// Update progress indicator
function updateProgress() {
const fields = ['coverLetter', 'pitchEmail', 'notesToEditor', 'authorBio'];
let completed = 0;
fields.forEach(field => {
if (document.getElementById(`check-${field}`).checked) {
completed++;
}
});
const percentage = Math.round((completed / fields.length) * 100);
document.getElementById('progress-percentage').textContent = `${percentage}%`;
document.getElementById('progress-bar').style.width = `${percentage}%`;
}