← back to elcasillas__DealUpdates

Function bodies 106 total

All specs Real LLM only Function bodies
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 = win
fetchUploadDates 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 ?? null
Hi, 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 summ
Citation: 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) !== M
loadScoringConfig 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 = la
formatUploadLabel 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.changeType
formatDate 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.totalAC
updateOwnerCardActiveState 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(' &middot; ')}</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.urg
closeDealModal 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="${esc
startEditingContactRow 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 ›