/* HR Candidate Matcher — Application Entry Point */ /* global window, document, HRMatcher */ (function () { 'use strict'; var APP = window.HRMatcher; var $ = function (sel) { return document.querySelector(sel); }; var $$ = function (sel) { return Array.prototype.slice.call(document.querySelectorAll(sel)); }; var state = APP.createAppState(); function saveState() { var data = { currentScreen: state.currentScreen, vacancy: state.vacancy, resumeText: state.isDemo ? '' : state.resumeText, matchResults: state.matchResults, isDemo: state.isDemo }; APP.storage.save(data); } function loadState() { var data = APP.storage.load(); if (!data) return false; state.currentScreen = data.currentScreen || 'welcome'; state.vacancy = data.vacancy || null; state.resumeText = data.resumeText || ''; state.matchResults = data.matchResults || []; state.isDemo = data.isDemo || false; return true; } function navigateTo(screen) { state.currentScreen = screen; APP.ui.showScreen(screen); if (screen === 'input') { var vacInput = $('#input-vacancy'); var resInput = $('#input-resume'); if (vacInput && state.vacancy) vacInput.value = state.vacancy.rawText || ''; if (resInput) resInput.value = state.resumeText || ''; } else if (screen === 'criteria') { if (state.vacancy) { APP.ui.renderCriteriaTable(state.vacancy.criteria || []); } } else if (screen === 'match') { if (state.vacancy) { currentFilter = 'all'; APP.ui.updateFilterChips('all'); APP.ui.renderMatchTable(state.vacancy.criteria, state.matchResults, 'all'); APP.ui.renderCoverage(APP.aiAdapter.computeCoverage(state.vacancy.criteria, state.matchResults)); } } else if (screen === 'interview') { if (state.vacancy) { var interviewData = APP.logic.generateInterviewData(state.vacancy.criteria, state.matchResults); APP.ui.renderInterview(interviewData); } } saveState(); } var currentFilter = 'all'; function bindEvents() { var btnStartEmpty = $('#btn-start-empty'); var btnStartDemo = $('#btn-start-demo'); var btnBackInput = $('#btn-back-from-input'); var btnBackCriteria = $('#btn-back-from-criteria'); var btnBackMatch = $('#btn-back-from-match'); var btnBackInterview = $('#btn-back-from-interview'); var formInput = $('#form-input'); var btnAddCriterion = $('#btn-add-criterion'); var btnToMatch = $('#btn-to-match'); var btnToInterview = $('#btn-to-interview'); var btnViewSource = $('#btn-view-source'); var btnCloseSource = $('#btn-close-source'); var btnCopyQuestions = $('#btn-copy-questions'); var btnExportSummary = $('#btn-export-summary'); var btnStartOver = $('#btn-start-over'); var btnClearAll = $('#btn-clear-all'); var btnConfirmOk = $('#btn-confirm-ok'); var btnConfirmCancel = $('#btn-confirm-cancel'); var btnResetFilter = $('#btn-reset-filter'); if (btnStartEmpty) { btnStartEmpty.addEventListener('click', function () { state = APP.createAppState(); state.currentScreen = 'input'; navigateTo('input'); }); } if (btnStartDemo) { btnStartDemo.addEventListener('click', function () { state = APP.createAppState(); state.isDemo = true; state.vacancy = APP.createVacancy('Таргетолог', APP.DEMO_VACANCY_TEXT, []); state.resumeText = APP.DEMO_RESUME_TEXT; state.vacancy.criteria = APP.aiAdapter.extractRequirements(APP.DEMO_VACANCY_TEXT); state.matchResults = APP.logic.runMatch(state.vacancy.criteria, state.resumeText); state.currentScreen = 'criteria'; navigateTo('criteria'); APP.ui.showToast('Загружен пример. Проверьте критерии.', 'success'); }); } if (btnBackInput) btnBackInput.addEventListener('click', function () { navigateTo('welcome'); }); if (btnBackCriteria) btnBackCriteria.addEventListener('click', function () { navigateTo('input'); }); if (btnBackMatch) btnBackMatch.addEventListener('click', function () { navigateTo('criteria'); }); if (btnBackInterview) btnBackInterview.addEventListener('click', function () { navigateTo('match'); }); if (formInput) { formInput.addEventListener('submit', function (e) { e.preventDefault(); APP.ui.clearAllErrors(); var vacancyText = $('#input-vacancy').value; var resumeText = $('#input-resume').value; var validation = APP.logic.validateInput(vacancyText, resumeText); if (!validation.valid) { if (validation.errors.vacancy) { APP.ui.showError('vacancy', validation.errors.vacancy); $('#input-vacancy').focus(); } else if (validation.errors.resume) { APP.ui.showError('resume', validation.errors.resume); $('#input-resume').focus(); } return; } var title = vacancyText.split('\n')[0].substring(0, 80).trim(); state.vacancy = APP.createVacancy(title, vacancyText, []); state.resumeText = resumeText; state.isDemo = false; state.vacancy.criteria = APP.aiAdapter.extractRequirements(vacancyText); state.matchResults = APP.logic.runMatch(state.vacancy.criteria, resumeText); navigateTo('criteria'); APP.ui.showToast('Извлечено требований: ' + state.vacancy.criteria.length, 'success'); }); } if (btnAddCriterion) { btnAddCriterion.addEventListener('click', function () { var criteria = APP.ui.readCriteriaFromTable(); criteria.push(APP.createCriterion('', true, 3, [])); state.vacancy.criteria = criteria; APP.ui.renderCriteriaTable(criteria); var rows = $$('#criteria-tbody tr'); var lastRow = rows[rows.length - 1]; if (lastRow) { var input = lastRow.querySelector('[data-field="label"]'); if (input) input.focus(); } }); } if (btnToMatch) { btnToMatch.addEventListener('click', function () { var criteria = APP.ui.readCriteriaFromTable(); var validation = APP.logic.validateCriteria(criteria); if (!validation.valid) { APP.ui.showToast(validation.message, 'error'); return; } state.vacancy.criteria = criteria; state.matchResults = APP.logic.runMatch(criteria, state.resumeText); navigateTo('match'); }); } var criteriaTbody = $('#criteria-tbody'); if (criteriaTbody) { criteriaTbody.addEventListener('click', function (e) { var btn = e.target.closest('[data-action="delete-criterion"]'); if (!btn) return; var row = btn.closest('tr'); if (!row) return; var id = row.getAttribute('data-id'); var criteria = APP.ui.readCriteriaFromTable(); var label = ''; for (var i = 0; i < criteria.length; i++) { if (criteria[i].id === id) { label = criteria[i].label; break; } } APP.ui.showConfirm('Удалить требование' + (label ? ' «' + label + '»' : '') + '?', function () { var updated = criteria.filter(function (c) { return c.id !== id; }); state.vacancy.criteria = updated; APP.ui.renderCriteriaTable(updated); APP.ui.hideConfirm(); }); }); } var matchTbody = $('#match-tbody'); if (matchTbody) { matchTbody.addEventListener('change', function (e) { var target = e.target; var row = target.closest('tr'); if (!row) return; var criterionId = row.getAttribute('data-criterion-id'); if (target.getAttribute('data-action') === 'change-status') { for (var i = 0; i < state.matchResults.length; i++) { if (state.matchResults[i].criterionId === criterionId) { state.matchResults[i].status = target.value; break; } } APP.ui.renderCoverage(APP.aiAdapter.computeCoverage(state.vacancy.criteria, state.matchResults)); saveState(); } }); matchTbody.addEventListener('input', function (e) { var target = e.target; if (target.getAttribute('data-action') !== 'change-note') return; var row = target.closest('tr'); if (!row) return; var criterionId = row.getAttribute('data-criterion-id'); for (var i = 0; i < state.matchResults.length; i++) { if (state.matchResults[i].criterionId === criterionId) { state.matchResults[i].note = target.value; break; } } saveState(); }); } var filterBar = $('#match-filter-bar'); if (filterBar) { filterBar.addEventListener('click', function (e) { var chip = e.target.closest('.chip'); if (!chip) return; currentFilter = chip.getAttribute('data-filter') || 'all'; APP.ui.updateFilterChips(currentFilter); APP.ui.renderMatchTable(state.vacancy.criteria, state.matchResults, currentFilter); }); } if (btnResetFilter) { btnResetFilter.addEventListener('click', function () { currentFilter = 'all'; APP.ui.updateFilterChips('all'); APP.ui.renderMatchTable(state.vacancy.criteria, state.matchResults, 'all'); }); } if (btnToInterview) { btnToInterview.addEventListener('click', function () { navigateTo('interview'); }); } if (btnViewSource) { btnViewSource.addEventListener('click', function () { var modal = $('#modal-source'); var vacSource = $('#source-vacancy'); var resSource = $('#source-resume'); if (vacSource) vacSource.textContent = state.vacancy ? state.vacancy.rawText : ''; if (resSource) resSource.textContent = state.resumeText || ''; if (modal) modal.hidden = false; }); } if (btnCloseSource) { btnCloseSource.addEventListener('click', function () { var modal = $('#modal-source'); if (modal) modal.hidden = true; }); } if (btnCopyQuestions) { btnCopyQuestions.addEventListener('click', function () { var interviewData = APP.logic.generateInterviewData(state.vacancy.criteria, state.matchResults); if (interviewData.length === 0) { APP.ui.showToast('Нет вопросов для копирования', 'error'); return; } var text = ''; var num = 1; interviewData.forEach(function (section) { text += section.criterion.label + ':\n'; section.questions.forEach(function (q) { text += num + '. ' + q + '\n'; num++; }); text += '\n'; }); copyToClipboard(text.trim()); }); } if (btnExportSummary) { btnExportSummary.addEventListener('click', function () { var summary = APP.logic.exportSummary(state.vacancy, state.vacancy.criteria, state.matchResults); downloadFile('candidate-summary.txt', summary, 'text/plain;charset=utf-8'); APP.ui.showToast('Сводка экспортирована', 'success'); }); } if (btnStartOver) { btnStartOver.addEventListener('click', function () { APP.ui.showConfirm('Начать заново? Текущие данные будут очищены.', function () { state = APP.createAppState(); APP.storage.clear(); navigateTo('welcome'); APP.ui.hideConfirm(); }); }); } if (btnClearAll) { btnClearAll.addEventListener('click', function () { APP.ui.showConfirm('Очистить все данные? Это действие нельзя отменить.', function () { state = APP.createAppState(); APP.storage.clear(); navigateTo('welcome'); APP.ui.hideConfirm(); APP.ui.showToast('Все данные очищены', 'success'); }); }); } if (btnConfirmOk) { btnConfirmOk.addEventListener('click', function () { APP.ui.resolveConfirm(); }); } if (btnConfirmCancel) { btnConfirmCancel.addEventListener('click', function () { APP.ui.hideConfirm(); }); } document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { var sourceModal = $('#modal-source'); var confirmModal = $('#modal-confirm'); if (sourceModal && !sourceModal.hidden) { sourceModal.hidden = true; } else if (confirmModal && !confirmModal.hidden) { APP.ui.hideConfirm(); } } }); var sourceModal = $('#modal-source'); if (sourceModal) { sourceModal.addEventListener('click', function (e) { if (e.target === sourceModal) sourceModal.hidden = true; }); } var confirmModal = $('#modal-confirm'); if (confirmModal) { confirmModal.addEventListener('click', function (e) { if (e.target === confirmModal) APP.ui.hideConfirm(); }); } window.addEventListener('beforeunload', function () { if (state.currentScreen !== 'welcome' && !state.isDemo) { saveState(); } }); } function copyToClipboard(text) { if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { APP.ui.showToast('Скопировано в буфер обмена', 'success'); }).catch(function () { fallbackCopy(text); }); } else { fallbackCopy(text); } } function fallbackCopy(text) { var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); APP.ui.showToast('Скопировано в буфер обмена', 'success'); } catch (e) { APP.ui.showToast('Не удалось скопировать. Выделите текст вручную.', 'error'); } document.body.removeChild(ta); } function downloadFile(filename, content, mimeType) { var blob = new Blob([content], { type: mimeType }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(url); document.body.removeChild(a); }, 100); } function init() { bindEvents(); var hasSaved = loadState(); if (hasSaved && state.currentScreen !== 'welcome') { navigateTo(state.currentScreen); if (state.isDemo) { APP.ui.showToast('Загружены данные примера', 'success'); } } else { APP.ui.showScreen('welcome'); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();