Function bodies 106 total
buildSnapshot function · javascript · L34-L49 (16 LOC)fixtures/generate-golden.js
function buildSnapshot(deals) {
return deals
.map(d => ({
deal_key: d.dealKey,
deal_owner: d.dealOwner,
stage: d.stage,
acv: d.acv,
closing_date: d.closingDate ? d.closingDate.toISOString().slice(0, 10) : null,
modified_date: d.modifiedDate ? d.modifiedDate.toISOString().slice(0, 10) : null,
notes_count: d.notesCount,
notes_hash: d.notesHash,
notes_summary_length: (d.notesSummary || '').length,
health_score: d.healthScore != null ? d.healthScore : null
}))
.sort((a, b) => a.deal_key.localeCompare(b.deal_key));
}initSupabase function · javascript · L55-L79 (25 LOC)js/app.js
function initSupabase() {
try {
if (window.UI_ONLY) {
console.log('UI-only mode: Supabase disabled. Using localStorage + CSV only.');
return;
}
if (typeof SUPABASE_URL === 'undefined' || typeof SUPABASE_ANON_KEY === 'undefined') {
console.warn('Supabase config not loaded. Running in offline/localStorage mode.');
return;
}
if (SUPABASE_URL === 'https://YOUR_PROJECT.supabase.co' || SUPABASE_ANON_KEY === 'YOUR_ANON_KEY_HERE') {
console.warn('Supabase not configured (still has placeholder values). Running in offline/localStorage mode.');
return;
}
if (typeof window.supabase === 'undefined' || typeof window.supabase.createClient !== 'function') {
console.error('Supabase JS library not loaded. Check CDN script tag.');
return;
}
supabaseClient = winfetchUploadDates function · javascript · L82-L94 (13 LOC)js/app.js
async function fetchUploadDates() {
if (!supabaseClient) return [];
const { data, error } = await supabaseClient
.from('uploads')
.select('id, generated_date, deal_count, uploaded_at, filename')
.order('generated_date', { ascending: false })
.order('uploaded_at', { ascending: false });
if (error) {
console.error('Error fetching uploads:', error);
return [];
}
return data || [];
}fetchDealsByUploadId function · javascript · L96-L110 (15 LOC)js/app.js
async function fetchDealsByUploadId(uploadId, signal) {
if (!supabaseClient) return [];
let query = supabaseClient
.from('deals')
.select('*')
.eq('upload_id', uploadId);
if (signal) query = query.abortSignal(signal);
const { data, error } = await query;
if (error) {
if (error.name === 'AbortError' || signal?.aborted) throw new DOMException('Aborted', 'AbortError');
console.error('Error fetching deals:', error);
return [];
}
return data || [];
}deleteUpload function · javascript · L112-L132 (21 LOC)js/app.js
async function deleteUpload(uploadId) {
if (!supabaseClient) return false;
// Delete deals first (foreign key dependency)
const { error: dealsError } = await supabaseClient
.from('deals')
.delete()
.eq('upload_id', uploadId);
if (dealsError) {
console.error('Error deleting deals:', dealsError);
return false;
}
const { error: uploadError } = await supabaseClient
.from('uploads')
.delete()
.eq('id', uploadId);
if (uploadError) {
console.error('Error deleting upload:', uploadError);
return false;
}
return true;
}insertUpload function · javascript · L134-L153 (20 LOC)js/app.js
async function insertUpload(generatedDate, filename, dealCount, scoringConfig) {
if (!supabaseClient) return null;
const payload = {
generated_date: generatedDate,
filename: filename,
deal_count: dealCount,
scoring_config: scoringConfig || null
};
console.log('insertUpload payload:', JSON.stringify(payload));
const { data, error } = await supabaseClient
.from('uploads')
.insert(payload)
.select()
.single();
if (error) {
console.error('Error inserting upload:', error);
return null;
}
return data;
}insertDealsBatch function · javascript · L155-L191 (37 LOC)js/app.js
async function insertDealsBatch(uploadId, deals) {
if (!supabaseClient) return false;
const rows = deals.map(deal => ({
upload_id: uploadId,
deal_owner: deal.dealOwner,
deal_name: deal.dealName,
stage: deal.stage,
acv: deal.acv || 0,
closing_date: deal.closingDate ? deal.closingDate.toISOString().slice(0, 10) : null,
modified_date: deal.modifiedDate ? deal.modifiedDate.toISOString().slice(0, 10) : null,
note_content: deal.noteContent,
description: deal.description,
notes_summary: deal.notesSummary,
deal_key: deal.dealKey || null,
notes_canonical: deal.notesCanonical || null,
notes_hash: deal.notesHash || null,
notes_count: deal.notesCount || 0,
health_score: deal.healthScore != null ? deal.healthScore : null,
hs_stage_probability: deal.healthComponents?.stageProbability ?? nullHi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
fetchAllOwnerContacts function · javascript · L194-L205 (12 LOC)js/app.js
async function fetchAllOwnerContacts() {
if (!supabaseClient) return [];
const { data, error } = await supabaseClient
.from('deal_owners')
.select('*')
.order('owner_name', { ascending: true });
if (error) {
console.error('Error fetching owner contacts:', error);
return [];
}
return data || [];
}upsertOwnerContact function · javascript · L207-L224 (18 LOC)js/app.js
async function upsertOwnerContact(ownerName, email, phone, slackMemberId) {
if (!supabaseClient) return null;
const { data, error } = await supabaseClient
.from('deal_owners')
.upsert({
owner_name: ownerName,
email: email || null,
phone: phone || null,
slack_member_id: slackMemberId || null
}, { onConflict: 'owner_name' })
.select()
.single();
if (error) {
console.error('Error upserting owner contact:', error);
return null;
}
return data;
}loadOwnerContacts function · javascript · L233-L239 (7 LOC)js/app.js
async function loadOwnerContacts() {
const contacts = await fetchAllOwnerContacts();
ownerContactsCache = {};
for (const contact of contacts) {
ownerContactsCache[contact.owner_name.toLowerCase().trim()] = contact;
}
}getOwnerContact function · javascript · L241-L244 (4 LOC)js/app.js
function getOwnerContact(ownerName) {
if (!ownerName) return null;
return ownerContactsCache[ownerName.toLowerCase().trim()] || null;
}updateOwnerContactCache function · javascript · L246-L248 (3 LOC)js/app.js
function updateOwnerContactCache(contact) {
ownerContactsCache[contact.owner_name.toLowerCase().trim()] = contact;
}showLoading function · javascript · L287-L290 (4 LOC)js/app.js
function showLoading(text) {
document.getElementById('loading-text').textContent = text || 'Loading...';
elements.loadingOverlay.classList.remove('hidden');
}hideLoading function · javascript · L292-L294 (3 LOC)js/app.js
function hideLoading() {
elements.loadingOverlay.classList.add('hidden');
}generateAISummaries function · javascript · L296-L349 (54 LOC)js/app.js
async function generateAISummaries(deals, notesMap) {
if (!supabaseClient) return null;
// Build cache-first payload with deal_key, notes_hash, notes_canonical
const payload = deals
.filter(deal => deal.notesCanonical && deal.notesCanonical.length > 0)
.map(deal => ({
deal_key: deal.dealKey,
notes_hash: deal.notesHash,
notes_canonical: deal.notesCanonical,
dealName: deal.dealName
}));
if (payload.length === 0) return null;
try {
console.log(`Requesting AI summaries for ${payload.length} deals...`);
const { data, error } = await supabaseClient.functions.invoke('summarize-notes', {
body: { deals: payload }
});
if (error) {
console.warn('AI summary failed, using fallback:', error.message);
return null;
}
console.log('AI summCitation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
diffDeals function · javascript · L352-L410 (59 LOC)js/app.js
function diffDeals(oldDeals, newDeals) {
// Build lookup from old deals
const oldMap = new Map();
for (const deal of oldDeals) {
oldMap.set(deal.dealKey || makeDealKey(deal.dealName, deal.dealOwner), deal);
}
let newCount = 0;
let updatedCount = 0;
let unchangedCount = 0;
// Tag each new deal
const newKeys = new Set();
for (const deal of newDeals) {
const key = deal.dealKey || makeDealKey(deal.dealName, deal.dealOwner);
newKeys.add(key);
const old = oldMap.get(key);
if (!old) {
deal.changeType = 'new';
deal.changes = [];
newCount++;
} else {
const changes = [];
if (deal.stage !== old.stage) {
changes.push(`Stage: ${old.stage || '-'} \u2192 ${deal.stage || '-'}`);
}
if (Math.round(deal.acv || 0) !== MloadScoringConfig function · javascript · L413-L419 (7 LOC)js/app.js
function loadScoringConfig() {
try {
const raw = localStorage.getItem(SCORING_CONFIG_KEY);
if (raw) return JSON.parse(raw);
} catch (e) { /* ignore */ }
return null;
}saveScoringConfig function · javascript · L421-L427 (7 LOC)js/app.js
function saveScoringConfig(config) {
try {
localStorage.setItem(SCORING_CONFIG_KEY, JSON.stringify(config));
} catch (e) {
console.error('Failed to save scoring config:', e);
}
}buildScoringConfigArg function · javascript · L429-L438 (10 LOC)js/app.js
function buildScoringConfigArg() {
const saved = loadScoringConfig();
if (!saved) return undefined;
const config = {};
if (saved.weights) config.weights = saved.weights;
if (saved.stageScoreMap) config.stageScoreMap = saved.stageScoreMap;
if (saved.positiveKeywords) config.positiveKeywords = saved.positiveKeywords;
if (saved.negativeKeywords) config.negativeKeywords = saved.negativeKeywords;
return config;
}attachHealthScores function · javascript · L440-L449 (10 LOC)js/app.js
function attachHealthScores(deals) {
const context = buildHealthContext(deals);
const config = buildScoringConfigArg();
for (const deal of deals) {
const result = computeDealHealthScore(deal, context, config);
deal.healthScore = result.score;
deal.healthComponents = result.components;
deal.healthDebug = result.debug;
}
}attachHealthScoresIfMissing function · javascript · L451-L467 (17 LOC)js/app.js
function attachHealthScoresIfMissing(deals) {
const needsScoring = deals.filter(d => d.healthScore == null);
if (needsScoring.length === 0) return;
if (needsScoring.length === deals.length) {
attachHealthScores(deals);
return;
}
// Partial: recompute only missing, using full dataset for context
const context = buildHealthContext(deals);
const config = buildScoringConfigArg();
for (const deal of needsScoring) {
const result = computeDealHealthScore(deal, context, config);
deal.healthScore = result.score;
deal.healthComponents = result.components;
deal.healthDebug = result.debug;
}
}saveToStorage function · javascript · L470-L476 (7 LOC)js/app.js
function saveToStorage(deals) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(deals));
} catch (e) {
console.error('Failed to save to localStorage:', e);
}
}loadFromStorage function · javascript · L478-L504 (27 LOC)js/app.js
function loadFromStorage() {
try {
const data = localStorage.getItem(STORAGE_KEY);
if (data) {
const deals = JSON.parse(data);
// Restore Date objects
return deals.map(deal => {
const closingDate = deal.closingDate ? new Date(deal.closingDate) : null;
const modifiedDate = deal.modifiedDate ? new Date(deal.modifiedDate) : null;
const daysSince = calculateDaysSince(modifiedDate);
const daysUntilClosing = calculateDaysUntilClosing(closingDate);
return {
...deal,
closingDate,
modifiedDate,
daysSince,
urgency: getUrgencyLevel(daysSince),
daysUntilClosing,
closingStatus: getClosingStatus(daysUntilClosing)
};
})About: code-quality intelligence by Repobility · https://repobility.com
checkSchemaVersion function · javascript · L506-L524 (19 LOC)js/app.js
function checkSchemaVersion() {
const stored = localStorage.getItem(SCHEMA_VERSION_KEY);
if (stored === null) {
// Fresh install or pre-versioning data — clear stale data, set version
localStorage.removeItem(STORAGE_KEY);
localStorage.setItem(SCHEMA_VERSION_KEY, String(SCHEMA_VERSION));
return;
}
const version = parseInt(stored, 10);
if (version < SCHEMA_VERSION) {
console.warn(`Schema upgraded ${version} → ${SCHEMA_VERSION}. Clearing local cache.`);
localStorage.removeItem(STORAGE_KEY);
localStorage.setItem(SCHEMA_VERSION_KEY, String(SCHEMA_VERSION));
return;
}
if (version > SCHEMA_VERSION) {
console.warn(`Local data has schema v${version}, app expects v${SCHEMA_VERSION}. Data left untouched.`);
}
}clearLocalData function · javascript · L526-L531 (6 LOC)js/app.js
function clearLocalData() {
if (!confirm('Clear all locally stored deal data? This cannot be undone.')) return;
localStorage.removeItem(STORAGE_KEY);
localStorage.setItem(SCHEMA_VERSION_KEY, String(SCHEMA_VERSION));
location.reload();
}populateDatePicker function · javascript · L534-L568 (35 LOC)js/app.js
async function populateDatePicker() {
const primarySelect = elements.dateSelectPrimary;
const compareSelect = elements.dateSelectCompare;
if (!isOnline) {
primarySelect.innerHTML = '<option value="">Supabase not connected</option>';
primarySelect.disabled = true;
compareSelect.innerHTML = '<option value="">Supabase not connected</option>';
compareSelect.disabled = true;
return;
}
primarySelect.disabled = false;
compareSelect.disabled = false;
const uploads = await fetchUploadDates();
primarySelect.innerHTML = '<option value="">Select a date...</option>';
compareSelect.innerHTML = '<option value="">None (no comparison)</option>';
for (const upload of uploads) {
const label = formatUploadLabel(upload);
const opt1 = document.createElement('option');
opt1.value = upload.id;
opt1.textContent = laformatUploadLabel function · javascript · L570-L582 (13 LOC)js/app.js
function formatUploadLabel(upload) {
const date = new Date(upload.generated_date + 'T00:00:00');
const formatted = date.toLocaleDateString('en-CA', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
const uploadTime = new Date(upload.uploaded_at).toLocaleTimeString('en-CA', {
hour: 'numeric',
minute: '2-digit'
});
return `${formatted} (${upload.deal_count} deals) - uploaded ${uploadTime}`;
}supabaseRowToInternal function · javascript · L584-L625 (42 LOC)js/app.js
function supabaseRowToInternal(row) {
const closingDate = row.closing_date ? new Date(row.closing_date + 'T00:00:00') : null;
const modifiedDate = row.modified_date ? new Date(row.modified_date + 'T00:00:00') : null;
const daysSince = calculateDaysSince(modifiedDate);
const daysUntilClosing = calculateDaysUntilClosing(closingDate);
const deal = {
dealOwner: row.deal_owner,
dealName: row.deal_name,
dealKey: row.deal_key || null,
stage: row.stage,
acv: parseFloat(row.acv) || 0,
acvFormatted: formatCurrency(parseFloat(row.acv) || 0),
closingDate,
modifiedDate,
daysSince,
urgency: getUrgencyLevel(daysSince),
daysUntilClosing,
closingStatus: getClosingStatus(daysUntilClosing),
noteContent: row.note_content || '',
notesCanonical: row.notes_canonical || '',
notesHash: row.loadUploadById function · javascript · L627-L630 (4 LOC)js/app.js
async function loadUploadById(uploadId, signal) {
const rawDeals = await fetchDealsByUploadId(uploadId, signal);
return rawDeals.map(supabaseRowToInternal);
}handleDateSelection function · javascript · L632-L678 (47 LOC)js/app.js
async function handleDateSelection() {
const primaryId = elements.dateSelectPrimary.value;
const compareId = elements.dateSelectCompare.value;
if (!primaryId) return;
// Abort any pending date selection fetch
if (dateSelectionController) dateSelectionController.abort();
dateSelectionController = new AbortController();
const signal = dateSelectionController.signal;
showLoading();
try {
const primaryDeals = await loadUploadById(primaryId, signal);
attachHealthScoresIfMissing(primaryDeals);
if (compareId) {
const compareDeals = await loadUploadById(compareId, signal);
// Compare: "compare" is the baseline, "primary" is the newer
changesSummary = diffDeals(compareDeals, primaryDeals);
} else {
changesSummary = null;
for (const deal of primaryDeals) {
deal.changeTypeformatDate function · javascript · L681-L688 (8 LOC)js/app.js
function formatDate(date) {
if (!date) return '-';
return date.toLocaleDateString('en-CA', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}Repobility · open methodology · https://repobility.com/research/
renderStats function · javascript · L690-L711 (22 LOC)js/app.js
function renderStats(deals) {
const totalDeals = deals.length;
const totalACV = deals.reduce((sum, d) => sum + (d.acv || 0), 0);
const avgDays = deals.length > 0
? Math.round(deals.reduce((sum, d) => sum + d.daysSince, 0) / deals.length)
: 0;
const staleDeals = deals.filter(d => d.daysSince > 30).length;
const overdueDeals = deals.filter(d => d.closingStatus === 'overdue').length;
const avgHealth = deals.length > 0
? Math.round(deals.reduce((sum, d) => sum + (d.healthScore || 0), 0) / deals.length)
: 0;
elements.statTotal.textContent = totalDeals.toLocaleString();
elements.statAcv.textContent = formatCurrency(totalACV);
elements.statAvgDays.textContent = avgDays;
elements.statStale.textContent = staleDeals;
elements.statOverdue.textContent = overdueDeals;
elements.statAvgHealth.textContent = deals.length > 0 ? avgHealth : '-';
// renderOwnerCards function · javascript · L713-L786 (74 LOC)js/app.js
function renderOwnerCards(deals) {
// Group deals by owner
const ownerStats = {};
for (const deal of deals) {
const owner = deal.dealOwner || 'Unknown';
if (!ownerStats[owner]) {
ownerStats[owner] = {
name: owner,
deals: 0,
totalACV: 0,
totalDays: 0,
overdue: 0
};
}
ownerStats[owner].deals++;
ownerStats[owner].totalACV += deal.acv || 0;
ownerStats[owner].totalDays += deal.daysSince || 0;
if (deal.closingStatus === 'overdue') ownerStats[owner].overdue++;
}
// Convert to array and sort by total ACV descending
const owners = Object.values(ownerStats)
.map(o => ({
...o,
avgDays: o.deals > 0 ? Math.round(o.totalDays / o.deals) : 0
}))
.sort((a, b) => b.totalACupdateOwnerCardActiveState function · javascript · L788-L793 (6 LOC)js/app.js
function updateOwnerCardActiveState() {
const activeOwner = elements.filterOwner.value;
elements.ownerCards.querySelectorAll('.owner-card').forEach(card => {
card.classList.toggle('owner-card--active', card.dataset.owner === activeOwner);
});
}formatCurrencyCompact function · javascript · L795-L802 (8 LOC)js/app.js
function formatCurrencyCompact(value) {
if (value >= 1000000) {
return '$' + (value / 1000000).toFixed(1) + 'M';
} else if (value >= 1000) {
return '$' + (value / 1000).toFixed(0) + 'K';
}
return '$' + value.toFixed(0);
}renderTable function · javascript · L804-L857 (54 LOC)js/app.js
function renderTable(deals) {
elements.tbody.innerHTML = '';
if (deals.length === 0) {
elements.noResults.classList.remove('hidden');
return;
}
elements.noResults.classList.add('hidden');
for (const deal of deals) {
const row = document.createElement('tr');
row.style.cursor = 'pointer';
if (deal.changeType === 'new' || deal.changeType === 'updated') {
row.classList.add(`change--${deal.changeType}`);
}
const changeDetail = deal.changeType === 'updated' && deal.changes && deal.changes.length > 0
? `<div class="text-[0.6875rem] text-blue-600 mt-1">${deal.changes.map(c => escapeHTML(c)).join(' · ')}</div>`
: deal.changeType === 'new'
? '<div class="text-[0.6875rem] text-blue-600 mt-1">New deal</div>'
: '';
const closingBadge = deal.closingStatus && CLOSING_CLASSES[openDealModal function · javascript · L862-L896 (35 LOC)js/app.js
function openDealModal(deal) {
currentModalDeal = deal;
document.getElementById('modal-deal-name').textContent = deal.dealName || '-';
document.getElementById('modal-deal-owner').textContent = deal.dealOwner || '-';
document.getElementById('modal-stage').textContent = deal.stage || '-';
document.getElementById('modal-acv').textContent = deal.acvFormatted || '-';
const modalClosingBadge = deal.closingStatus && CLOSING_CLASSES[deal.closingStatus]
? ` <span class="${CLOSING_CLASSES[deal.closingStatus]}">${deal.closingStatus === 'overdue' ? 'Overdue' : 'Closing Soon'}</span>`
: '';
document.getElementById('modal-closing-date').innerHTML = formatDate(deal.closingDate) + modalClosingBadge;
document.getElementById('modal-modified-date').textContent = formatDate(deal.modifiedDate) || '-';
document.getElementById('modal-days-since').innerHTML =
`<span class="${URGENCY_CLASSES[deal.urgcloseDealModal function · javascript · L898-L901 (4 LOC)js/app.js
function closeDealModal() {
document.getElementById('deal-modal').classList.add('hidden');
currentModalDeal = null;
}buildDealMessageBody function · javascript · L903-L927 (25 LOC)js/app.js
function buildDealMessageBody(deal) {
const firstName = (deal.dealOwner || '').split(' ')[0];
const lines = [
`Hi ${firstName},`,
'',
'Could you please provide an update on the following deal?',
'',
'---',
`Deal Name: ${deal.dealName}`,
`Deal Owner: ${deal.dealOwner}`,
`Stage: ${deal.stage || '-'}`,
`ACV (CAD): ${deal.acvFormatted || '-'}`,
`Closing Date: ${formatDate(deal.closingDate)}${deal.closingStatus === 'overdue' ? ' (Overdue)' : deal.closingStatus === 'soon' ? ' (Closing Soon)' : ''}`,
`Modified Date: ${formatDate(deal.modifiedDate)}`,
`Days Since Update: ${deal.daysSince} days`,
'',
`Description: ${deal.description || 'No description available.'}`,
'',
`Notes: ${deal.noteContent || 'No notes available.'}`,
'---',
'',
'Thanks,'
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
emailDealOwner function · javascript · L929-L947 (19 LOC)js/app.js
function emailDealOwner() {
if (!currentModalDeal) return;
const deal = currentModalDeal;
const contact = getOwnerContact(deal.dealOwner);
const toAddress = contact?.email || '';
if (!toAddress) {
if (confirm(`No email address found for ${deal.dealOwner}.\n\nWould you like to open Manage Contacts to add their info?`)) {
closeDealModal();
openContactsModal(deal.dealOwner);
}
return;
}
const subject = `Update Request - ${deal.dealName}`;
const body = buildDealMessageBody(deal);
const mailto = `mailto:${encodeURIComponent(toAddress)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.open(mailto, '_blank');
}slackDealOwner function · javascript · L949-L968 (20 LOC)js/app.js
function slackDealOwner() {
if (!currentModalDeal) return;
const deal = currentModalDeal;
const contact = getOwnerContact(deal.dealOwner);
const slackId = contact?.slack_member_id || '';
if (!slackId) {
if (confirm(`No Slack Member ID found for ${deal.dealOwner}.\n\nWould you like to open Manage Contacts to add their info?`)) {
closeDealModal();
openContactsModal(deal.dealOwner);
}
return;
}
const message = buildDealMessageBody(deal);
const SLACK_TEAM_ID = 'T02FCU97B';
navigator.clipboard.writeText(message).then(() => {
window.open(`https://slack.com/app_redirect?channel=${encodeURIComponent(slackId)}&team=${SLACK_TEAM_ID}`, '_blank');
});
}copyDealMessage function · javascript · L970-L980 (11 LOC)js/app.js
function copyDealMessage() {
if (!currentModalDeal) return;
const message = buildDealMessageBody(currentModalDeal);
const btn = document.getElementById('modal-copy-btn');
navigator.clipboard.writeText(message).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => {
btn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy Info';
}, 1500);
});
}openContactsModal function · javascript · L983-L998 (16 LOC)js/app.js
function openContactsModal(focusOwnerName) {
renderContactsList();
document.getElementById('contacts-modal').classList.remove('hidden');
document.getElementById('contacts-search-input').value = '';
if (focusOwnerName) {
const normalizedName = focusOwnerName.toLowerCase().trim();
const row = document.querySelector(
`[data-role="contact-row"][data-owner-normalized="${CSS.escape(normalizedName)}"]`
);
if (row) {
row.scrollIntoView({ block: 'center' });
startEditingContactRow(row);
}
}
}closeContactsModal function · javascript · L1000-L1002 (3 LOC)js/app.js
function closeContactsModal() {
document.getElementById('contacts-modal').classList.add('hidden');
}renderContactsList function · javascript · L1004-L1081 (78 LOC)js/app.js
function renderContactsList(filter) {
const container = document.getElementById('contacts-list');
const ownerNames = [...new Set(allDeals.map(d => d.dealOwner).filter(Boolean))].sort();
const filterLower = (filter || '').toLowerCase();
const filtered = filterLower
? ownerNames.filter(name => name.toLowerCase().includes(filterLower))
: ownerNames;
if (filtered.length === 0) {
container.innerHTML = '<div class="py-8 text-center text-slate-400 text-sm">No owners found.</div>';
return;
}
container.innerHTML = filtered.map(name => {
const contact = getOwnerContact(name);
const normalizedName = name.toLowerCase().trim();
const email = contact?.email || '';
const phone = contact?.phone || '';
const slackMemberId = contact?.slack_member_id || '';
return `
<div data-role="contact-row" data-owner="${escstartEditingContactRow function · javascript · L1083-L1087 (5 LOC)js/app.js
function startEditingContactRow(row) {
row.querySelector('[data-role="row-display"]').classList.add('hidden');
row.querySelector('[data-role="row-form"]').classList.remove('hidden');
row.querySelector('[data-role="email-input"]').focus();
}cancelEditingContactRow function · javascript · L1089-L1096 (8 LOC)js/app.js
function cancelEditingContactRow(row) {
const contact = getOwnerContact(row.dataset.owner);
row.querySelector('[data-role="email-input"]').value = contact?.email || '';
row.querySelector('[data-role="phone-input"]').value = contact?.phone || '';
row.querySelector('[data-role="slack-input"]').value = contact?.slack_member_id || '';
row.querySelector('[data-role="row-display"]').classList.remove('hidden');
row.querySelector('[data-role="row-form"]').classList.add('hidden');
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
setContactRowDisplayMode function · javascript · L1098-L1113 (16 LOC)js/app.js
function setContactRowDisplayMode(row, contact) {
const emailEl = row.querySelector('[data-role="owner-email"]');
const phoneEl = row.querySelector('[data-role="owner-phone"]');
const slackEl = row.querySelector('[data-role="owner-slack"]');
emailEl.innerHTML = contact.email
? escapeHTML(contact.email)
: '<span class="text-slate-400 italic">No email</span>';
phoneEl.innerHTML = contact.phone
? escapeHTML(contact.phone)
: '<span class="text-slate-400 italic">No phone</span>';
slackEl.innerHTML = contact.slack_member_id
? escapeHTML(contact.slack_member_id)
: '<span class="text-slate-400 italic">No Slack ID</span>';
row.querySelector('[data-role="row-display"]').classList.remove('hidden');
row.querySelector('[data-role="row-form"]').classList.add('hidden');
}saveOwnerContactRow function · javascript · L1115-L1130 (16 LOC)js/app.js
async function saveOwnerContactRow(ownerName, emailInput, phoneInput, slackInput, row) {
const email = emailInput.value.trim();
const phone = phoneInput.value.trim();
const slackMemberId = slackInput.value.trim();
const result = await upsertOwnerContact(ownerName, email, phone, slackMemberId);
if (result) {
updateOwnerContactCache(result);
setContactRowDisplayMode(row, result);
if (allDeals.length > 0) {
renderOwnerCards(allDeals);
}
} else {
alert('Failed to save contact info. Check browser console for details.');
}
}escapeHTML function · javascript · L1132-L1137 (6 LOC)js/app.js
function escapeHTML(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}page 1 / 3next ›