function ensureProjectDetailsModalOnBody() { var modalEl = document.getElementById('projectDetailsModal'); if (modalEl && modalEl.parentElement !== document.body) { document.body.appendChild(modalEl); } } $('#projectDetailsModal').on('show.bs.modal', function () { ensureProjectDetailsModalOnBody(); document.body.classList.add('project-details-modal-active'); }); $(document).on('click', '.details-modal-trigger', function(e) { if ($(e.target).closest('.pms-scheme-name-chevron').length) { return; } e.preventDefault(); var $t = $(this); var modalId = $t.data('modal-id'); if (modalId !== 'projectDetailsModal') return; var detailsUrl = $t.data('details-url'); var name = window.pmsReadEntityName ? window.pmsReadEntityName($t, 'Scheme') : ($t.attr('data-entity-name') || 'Scheme'); var projectId = $t.data('entity-id'); var $titleSpan = $('#' + modalId + '-title-name'); $titleSpan.text(name); var $modalTitle = $titleSpan.closest('.modal-title'); if (!$modalTitle.find('.scheme-title-text').length) { $modalTitle.contents().wrapAll(''); $modalTitle.append(''); $modalTitle.on('click', '.scheme-title-toggle', function() { var $h5 = $(this).closest('.modal-title'); var $icon = $(this).find('i'); if ($h5.hasClass('scheme-title-expanded')) { $h5.removeClass('scheme-title-expanded'); $icon.removeClass('fa-angle-up').addClass('fa-angle-down'); $(this).attr('title', 'View all'); } else { $h5.addClass('scheme-title-expanded'); $icon.removeClass('fa-angle-down').addClass('fa-angle-up'); $(this).attr('title', 'Collapse'); } }); } $modalTitle.removeClass('scheme-title-expanded'); $modalTitle.find('.scheme-title-toggle i').removeClass('fa-angle-up').addClass('fa-angle-down'); $modalTitle.find('.scheme-title-toggle').attr('title', 'View all'); $('#projectDetailsModal-project-id').val(projectId); $('#projectDetailsModal-create-pc').data('project-id', projectId); var $pc = $('#projectDetailsModal-create-pc'); if ($pc.length) $pc.attr('href', '{{ url("pc-forms/create") }}').show(); $('#' + modalId + '-body').html('

Loading project details...

'); ensureProjectDetailsModalOnBody(); $('#' + modalId).modal('show'); $.ajax({ url: detailsUrl, method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }, success: function(res) { if (res.status === 200 && res.data) { renderProjectDetails(res.data); } else { $('#projectDetailsModal-body').html('
Unable to load scheme details.
'); } }, error: function(xhr) { var msg = 'Failed to load scheme details.'; if (xhr.status === 403) msg = 'Permission denied.'; if (xhr.status === 404) msg = 'Scheme not found.'; $('#projectDetailsModal-body').html('
' + msg + '
'); } }); }); function renderProjectDetails(project) { schemeVisitHistoryLoadedFor = null; schemePcWorkflowLoadedFor = null; schemeDocWorkflowLoadedFor = null; schemeDocRepositoryLoadedFor = null; var tooltipText = { budget: 'Approved amount (fallback: estimated amount) shown as project budget.', spent: 'Cumulative expenditure used for financial progress (FP%).', cumulative_expenditure_for_fp: 'Total spent to date for FP%: actual expenditure upto previous plus revised allocations.', fp_percent: 'Financial progress % = cumulative expenditure ÷ scheme cost × 100.', total_expenditure_upto_current: 'Expenditure recorded from release/upload files (may differ from FP cumulative basis).', completion: 'Completion percentage from progress fields, fallback based on expenditure ratio.', project_id: 'Unique project identifier.', uid: 'Unique scheme UID/code.', adp_number: 'Annual Development Programme reference number.', project_type: 'Classification/category of the project.', status: 'Current active/inactive state.', published: 'Whether the scheme is marked as published.', approved: 'Whether the scheme has been officially approved.', approval_date: 'Date when scheme approval was recorded.', estimated_amount: 'Initial estimated cost.', approved_amount: 'Officially approved project cost.', actual_expenditure: 'Spent amount recorded up to previous period.', allocated_amount_final: 'Final allocated amount.', target_completion_date: 'Planned completion date.', completion_status: 'Current completion status.', created_by: 'User who created this record.', updated_by: 'User who last updated this record.', sdg_goals: 'Mapped Sustainable Development Goals.', ltbc: 'Less Than Budgetary Ceiling: Yes when remaining balance (throwforward minus current allocation) is ≤ 0.1 million and scheme is not under review.', under_revision: 'Whether the scheme is currently under review (UnderReview / UR). Revised history is shown separately.', revised: 'Whether the scheme has been revised (revised amount/date recorded in under review amounts).', last_revised_date: 'Date of the active revised allocation from under review amounts.', expenditure_last_uploaded: 'Date when expenditure data was last imported/uploaded for project allocations.', release_last_uploaded: 'Date when release data was last imported/uploaded for project allocations.', allocation_last_year: 'Budget allocation (capital + revenue) for the last (previous) budget year.', allocation_this_year: 'Budget allocation (capital + revenue) for the current budget year.', total_allocation: 'Sum of budget allocation (capital + revenue) across all budget years.', revised_allocation_last_year: 'Revised allocation total for the last (previous) budget year.' }; var escapeAttr = window.pmsEscapeAttr; var escapeHtml = window.pmsEscapeHtml; var withTooltip = function(label, key) { var tip = key && tooltipText[key] ? tooltipText[key] : ''; return tip ? '' + label + '' : label; }; var val = function(v) { return (v != null && v !== '') ? v : '-'; }; var dep = function(r) { return (r && r.name) ? r.name : '-'; }; var getName = function(obj) { return (obj && typeof obj === 'object' && obj.name) ? String(obj.name) : ''; }; var formatCurrency = function(amount) { if (amount == null || amount === '') return '-'; var n = Number(amount); if (!Number.isFinite(n)) return '-'; return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' M'; }; var formatDate = function(date) { if (!date) return '-'; return new Date(date).toLocaleDateString('en-PK', { year: 'numeric', month: 'long', day: 'numeric' }); }; var formatDateTime = function(dateStr) { if (!dateStr) return '—'; var d = new Date(dateStr); if (isNaN(d.getTime())) return '—'; return d.toLocaleString('en-PK', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }; var num = function(v) { if (v == null || v === '') return null; if (typeof v === 'number') return Number.isFinite(v) ? v : null; var n = Number(String(v).replace(/[^0-9.-]/g, '')); return Number.isFinite(n) ? n : null; }; var firstNonZero = function() { for (var i = 0; i < arguments.length; i++) { var n = num(arguments[i]); if (n != null && n !== 0) return n; } return 0; }; var firstPercent = function() { for (var i = 0; i < arguments.length; i++) { var n = num(arguments[i]); if (n != null) return n; } return null; }; var locStr = (project.locations && project.locations.length) ? project.locations.map(function(loc) { return (loc.name || '-') + (loc.district && loc.district.name ? ' (' + loc.district.name + ')' : ''); }).join(', ') : '-'; var sdgStr = Array.isArray(project.sdg_goals) && project.sdg_goals.length ? project.sdg_goals.join(', ') : '-'; var budget = firstNonZero(project.estimated_amount, project.approved_amount, project.alloted_amount, project.allocated_amount_final, project.capital_amount, project.revenue_amount); var spent = firstNonZero(project.actual_expenditure_upto_previous, project.allocated_amount_final, project.alloted_amount); var spentPct = budget > 0 ? Math.round((spent / budget) * 100) : null; var completionRaw = firstPercent(project.completion_percentage, project.completion_percent, project.physical_progress, project.progress); var completionBasis = spentPct != null ? spentPct : (completionRaw != null ? completionRaw : 0); var completionPct = Math.min(100, Math.max(0, Math.round(completionBasis))); var statusTag = project.status ? '• Active' : 'Inactive'; var boolTag = function(ok, yesLabel, noLabel) { yesLabel = yesLabel || 'Yes'; noLabel = noLabel || 'No'; return ok ? '' + yesLabel + '' : '' + noLabel + ''; }; var pcWorkflowStateLabel = (function(state, chairmanAdviceForwarded) { var key = String(state == null ? '' : state).trim().toLowerCase(); if (key === 'under approval' && chairmanAdviceForwarded) return 'Forwarded to Chairman'; if (key === 'draft') return 'Draft'; if (key === 'rejected') return 'Deferred'; if (key === 'requested') return 'Forwarded for Checking'; if (key === 'department_approval_pending') return 'Forwarded for Approval'; if (key === 'under approval') return 'Forwarded to P&D'; if (key === 'approved') return 'Approved'; if (key === '') return '—'; return key.charAt(0).toUpperCase() + key.slice(1); }); var pcStateTag = (function(state, stateLabel, chairmanAdviceForwarded) { if (state == null || state === '') { return 'NA'; } var normalized = String(state).trim().toLowerCase().replace(/\s+/g, '_'); var cssClass = 'scheme-tag-info'; if (normalized === 'approved') cssClass = 'scheme-tag-success'; else if (normalized === 'rejected') cssClass = 'scheme-tag-danger'; else if (normalized === 'requested' || normalized === 'checking' || normalized === 'department_approval_pending' || normalized === 'under_approval' || normalized === 'approving') cssClass = 'scheme-tag-warning'; var label = (stateLabel != null && String(stateLabel).trim() !== '') ? String(stateLabel).trim() : pcWorkflowStateLabel(state, !!chairmanAdviceForwarded); return '' + escapeAttr(label) + ''; }); var buildPcStatusSectionRows = function(project) { var rowsHtml = ''; var statuses = Array.isArray(project.pc_form_statuses) ? project.pc_form_statuses : []; if (statuses.length === 0) { rowsHtml += row('PC-I Status', pcStateTag(project.pc_i_state, null, project.pc_i_chairman_advice_forwarded), false, 'approved'); if (String(project.pc_i_state || '').trim().toLowerCase() === 'approved') { rowsHtml += row('PC-I Approval Date', (project.pc_i_approved_at ? formatDate(project.pc_i_approved_at) : '—'), false, 'approval_date'); } return rowsHtml; } statuses.forEach(function(pc) { var typeLabel = (pc && pc.label) ? String(pc.label) : 'PC-I'; var state = pc ? pc.state : null; var stateLabel = pc ? pc.state_label : null; var chairmanAdviceForwarded = !!(pc && pc.chairman_advice_forwarded); rowsHtml += row(typeLabel + ' Status', pcStateTag(state, stateLabel, chairmanAdviceForwarded), false, 'approved'); if (String(state || '').trim().toLowerCase() === 'approved') { rowsHtml += row(typeLabel + ' Approval Date', (pc.approved_at ? formatDate(pc.approved_at) : '—'), false, 'approval_date'); } }); return rowsHtml; }; var completionTag = project.completion_status ? '' + project.completion_status + '' : '-'; var row = function(label, value, emphasis, tooltipKey) { var lbl = tooltipKey ? withTooltip(label, tooltipKey) : label; return '
' + lbl + '' + value + '
'; }; var html = '
' + '
' + '
' + '
' + '
Basic Information
' + row('UID', '' + val(project.code) + '', false, 'uid') + row('ADP', val(project.adp_number), false, 'adp_number') + row('Scheme Start Year', val(project.start_year)) + // row('Scheme Type', val(project.scheme)) + row('Project Type', '' + val(project.project_type) + '', false, 'project_type') + row('Scheme Type', val(project.adp_type)) + row('Department', dep(project.department)) + row('Sector', dep(project.sector)) + row('Sub Sector', dep(project.subSector)) + row('Block', dep(project.blk)) + row('Location', locStr) + '
Status
' + row('Status', statusTag, false, 'status') + row('Published', boolTag(project.published), false, 'published') + buildPcStatusSectionRows(project) + // row('Approval Date', formatDate(project.approved_at), true, 'approval_date') + // row('Completion', '' + completionPct + '%', false, 'completion') + '
' + '
' + '
Financial Snapshot
' + row('Estimated Capital Cost', formatCurrency(project.capital_amount)) + row('Estimated Revenue Cost', formatCurrency(project.revenue_amount)) + row('Estimated Total Cost', formatCurrency(project.estimated_amount), true, 'estimated_amount') + '
Approved & Allocation
' + row('Approved Capital Cost', formatCurrency(project.approved_capital_amount)) + row('Approved Revenue Cost', formatCurrency(project.approved_revenue_amount)) + row('Approved Total Cost', formatCurrency(project.approved_amount), true, 'approved_amount') + '
' + (function() { var fe = project.financial_extra || {}; if (fe.last_budget_year_label) { tooltipText.allocation_last_year = 'Budget allocation (capital + revenue) for budget year ' + fe.last_budget_year_label + '.'; tooltipText.revised_allocation_last_year = 'Revised allocation total for budget year ' + fe.last_budget_year_label + '.'; } if (fe.current_budget_year_label) { tooltipText.allocation_this_year = 'Budget allocation (capital + revenue) for budget year ' + fe.current_budget_year_label + '.'; } var lastBudgetYearLbl = fe.last_budget_year_label ? String(fe.last_budget_year_label) : 'last Budget Year'; var currentBudgetYearLbl = fe.current_budget_year_label ? String(fe.current_budget_year_label) : 'This Year'; var isRevisedScheme = project.under_review === 'Revised'; var revisedAllocationRow = isRevisedScheme ? row('Revised Allocation (' + lastBudgetYearLbl + ')', formatCurrency(fe.revised_allocation_last_year), false, 'revised_allocation_last_year') : ''; return '
Expenditure / Release
' + row('Cumulative expenditure (FP)', formatCurrency(fe.cumulative_expenditure_for_fp), true, 'cumulative_expenditure_for_fp') + row('Financial progress (FP %)', fe.fp_percent_display || '—', true, 'fp_percent') + row('Expenditure (release uploads)', formatCurrency(fe.total_expenditure_upto_current), false, 'total_expenditure_upto_current') + row('Expenditure (Date last uploaded file)', fe.expenditure_last_uploaded_date ? formatDateTime(fe.expenditure_last_uploaded_date) : '—', false, 'expenditure_last_uploaded') + row('Release (Total)', formatCurrency(fe.total_release_upto_current), true) + row('Release (Date last uploaded file)', fe.release_last_uploaded_date ? formatDateTime(fe.release_last_uploaded_date) : '—', false, 'release_last_uploaded') + '
Allocation
' + row('Allocation (' + lastBudgetYearLbl + ')', formatCurrency(fe.allocation_last_year), false, 'allocation_last_year') + row('Allocation (' + currentBudgetYearLbl + ')', formatCurrency(fe.allocation_this_year), true, 'allocation_this_year') + row('Total Allocation', formatCurrency(fe.total_allocation), true, 'total_allocation') + revisedAllocationRow + '
'; })() + '
' + '
' + '
Dates
' + row('Target Completion Date', formatDate(project.target_completion_date), false, 'target_completion_date') + row('Approval Date', formatDate(project.approved_at), false, 'approval_date') + // row('Created At', formatDate(project.created_at)) + // row('Updated At', formatDate(project.updated_at)) + '
Progress
' + row('Completion Status', completionTag, false, 'completion_status') + row('LTBC (Yes/No)', (project.is_ltbc === true || project.is_ltbc === 1 ? 'Yes' : 'No'), true, 'ltbc') + row('Under Revision (Yes / No)', (project.under_review === 'UnderReview' ? 'Yes' : 'No'), false, 'under_revision') + row('Revised', (project.under_review === 'Revised' ? 'Yes' : 'No'), false, 'revised') + row('Last Revised Date', (project.revised_active && project.revised_active.revised_date ? formatDate(project.revised_active.revised_date) : '—'), false, 'last_revised_date') + '
' + '
' + '
' + '
' + @json(__('Open this tab to load consolidated visit history.')) + '
' + '
' + '
' + '
' + '
' + @json(__('Open this tab to load PC-I to PC-V workflow status details.')) + '
' + '
' + '
' + '
' + '
' + @json(__('Open this tab to load scheme documents.')) + '
' + '
' + '
' + '
' + '
' + @json(__('Open this tab to load Document Repository files for this UID.')) + '
' + '
' + '
'; $('#projectDetailsModal-body').html(html); if ($.fn && typeof $.fn.tooltip === 'function') { var $tips = $('#projectDetailsModal [data-toggle="tooltip"]'); $tips.tooltip('dispose'); $tips.tooltip({ container: 'body', trigger: 'hover focus' }); } // Keep "Generate PC" visible here; in-process schemes are handled in the next step (Select PC modal). var $createPcBtn = $('#projectDetailsModal-create-pc'); if ($createPcBtn.length) { $createPcBtn.show(); } var $createMeeting = $('#projectDetailsModal-create-meeting'); if ($createMeeting.length) { var pid = project.project_id || $('#projectDetailsModal-project-id').val(); var mwsSched = (project.mws_schedule_meeting && typeof project.mws_schedule_meeting === 'object') ? project.mws_schedule_meeting : null; if (pid && mwsSched && mwsSched.can_schedule) { var createBase = @json(route('meeting-workspace.meetings.create')); $createMeeting .attr('href', createBase) .data('project-id', pid) .attr('data-project-id', pid) .attr('data-mws-schedule-types', JSON.stringify(mwsSched.types || {})) .attr('data-pc-form-id', mwsSched.pc_form_id ? String(mwsSched.pc_form_id) : '') .attr('data-pc-form-kind', mwsSched.pc_form_kind ? String(mwsSched.pc_form_kind) : '') .show(); } else if (pid && !mwsSched) { // Legacy payload without eligibility: keep prior show behavior. $createMeeting .attr('href', '{{ route("meeting-workspace.meetings.create") }}?prefill_project_id=' + encodeURIComponent(pid)) .data('project-id', pid) .removeAttr('data-mws-schedule-types') .show(); } else { $createMeeting.hide().removeAttr('data-mws-schedule-types'); } } } $(document).on('click', '#projectDetailsModal-create-pc', function(e) { e.preventDefault(); e.stopPropagation(); var projectId = $(this).data('project-id') || $('#projectDetailsModal-project-id').val(); if (!projectId) { return; } ensureProjectDetailsModalOnBody(); if (typeof window.ensurePcTypeSelectionModalOnBody === 'function') { window.ensurePcTypeSelectionModalOnBody(); } else if (typeof ensurePcTypeSelectionModalOnBody === 'function') { ensurePcTypeSelectionModalOnBody(); } if (typeof window.pmsPrepareAndOpenPcTypeSelectionModal === 'function') { window.pmsPrepareAndOpenPcTypeSelectionModal(projectId); return; } if (typeof window.pmsOpenPcTypeSelectionModal === 'function') { $('#pcTypeSelectionProjectId').val(projectId); $('#pcTypeSelectionHelp').text(''); $('input[name="pc_type_choice"]').prop('checked', false); $('#pcTypeSelectionProceed').prop('disabled', true); if (typeof resetPcTypeSelectionModalLabels === 'function') { resetPcTypeSelectionModalLabels(); } window.pmsOpenPcTypeSelectionModal({ stacked: true }); return; } $('#pcTypeSelectionProjectId').val(projectId); $('#pcTypeSelectionModal').modal({ backdrop: true, keyboard: true, focus: true, show: true }); }); $(function() { ensureProjectDetailsModalOnBody(); }); var schemeVisitHistoryLoadedFor = null; var schemeVisitHistoryEscapeHtml = window.pmsEscapeHtml || function(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; var schemeVisitHistoryUserIcon = ''; var schemeVisitHistoryIconCheck = ''; var schemeVisitHistoryIconCross = ''; var schemeVisitHistoryIconDot = ''; function schemeVisitHistoryStatusKey(entry) { if (entry && entry.status_key) return entry.status_key; var status = entry && entry.status ? entry.status : entry; if (status === 'Satisfactory') return 'satisfactory'; if (status === 'Unsatisfactory') return 'unsatisfactory'; if (status === 'In Process') return 'in_process'; return 'neutral'; } function schemeVisitHistoryLabeledRow(label, value) { return '
' + '' + schemeVisitHistoryEscapeHtml(label) + '' + '' + schemeVisitHistoryEscapeHtml(value || '—') + '' + '
'; } function schemeVisitHistoryPlainRow(value) { return '
' + '' + schemeVisitHistoryEscapeHtml(value || '—') + '' + '
'; } function schemeVisitHistoryDownloadLink(download, defaultLabel) { if (!download || !download.url) return ''; var label = defaultLabel; if (download.filename && download.filename !== defaultLabel) { label = defaultLabel + ' (' + download.filename + ')'; } var pendingNote = download.file_exists === false ? ' title="' + schemeVisitHistoryEscapeHtml(@json(__('File pending upload'))) + '"' : ''; return '' + schemeVisitHistoryEscapeHtml(label) + ''; } function renderSchemeVisitHistoryTimeline(entries) { if (!entries || !entries.length) { return '
' + @json(__('No visit history found for this scheme.')) + '
'; } var html = '
' + '
' + '

' + @json(__('Visitation History Logs')) + '

' + '

' + @json(__('Published monitoring visits, newest first')) + '

' + '
' + '
'; entries.forEach(function(entry) { var statusKey = schemeVisitHistoryStatusKey(entry); var markerIcon = statusKey === 'satisfactory' ? schemeVisitHistoryIconCheck : (statusKey === 'unsatisfactory' ? schemeVisitHistoryIconCross : schemeVisitHistoryIconDot); var statusLabel = entry.status && entry.status !== '—' ? entry.status : 'In Process'; var downloads = entry.downloads || {}; var downloadHtml = ''; if (downloads.compliance) { downloadHtml += schemeVisitHistoryDownloadLink(downloads.compliance, @json(__('Compliance Replies'))); } if (downloads.published_report) { downloadHtml += schemeVisitHistoryDownloadLink(downloads.published_report, @json(__('Published Monitoring Report'))); } html += '
' + '' + markerIcon + '' + '
' + '
' + schemeVisitHistoryLabeledRow(@json(__('Visit Date')), entry.visit_date_display) + schemeVisitHistoryLabeledRow(@json(__('Published Date')), entry.published_date_display) + schemeVisitHistoryLabeledRow(@json(__('Reporting Officer')), entry.reporting_officer) + schemeVisitHistoryPlainRow(entry.frequency_label) + schemeVisitHistoryPlainRow(entry.data_source) + '
' + '
' + '
' + schemeVisitHistoryEscapeHtml(statusLabel) + '
' + schemeVisitHistoryLabeledRow(@json(__('Location')), entry.location) + schemeVisitHistoryLabeledRow(@json(__('Compliance Status')), entry.compliance_status) + (downloadHtml ? '
' + downloadHtml + '
' : '') + '
' + '
' + '
'; }); html += '
'; return html; } function loadSchemeVisitHistory(projectId) { var $panel = $('#scheme-visit-history-panel'); if (!$panel.length || !projectId) return; if (schemeVisitHistoryLoadedFor === projectId) return; $panel.html('
' + @json(__('Loading visit history...')) + '
'); $.get(@json(route('schemes.visit-history', ['project' => '__PID__'])).replace('__PID__', projectId)) .done(function(resp) { var entries = (resp && resp.data && resp.data.timeline) ? resp.data.timeline : []; $panel.html(renderSchemeVisitHistoryTimeline(entries)); schemeVisitHistoryLoadedFor = projectId; }) .fail(function() { $panel.html('
' + @json(__('Unable to load visit history.')) + '
'); }); } $(document).on('shown.bs.tab', '#projectDetailsTabs a[href="#tab-visit-history"]', function() { var projectId = $('#scheme-visit-history-panel').data('project-id'); if (projectId) { loadSchemeVisitHistory(String(projectId)); } }); var schemePcWorkflowLoadedFor = null; var schemeDocWorkflowLoadedFor = null; var schemeDocRepositoryLoadedFor = null; function schemePcWorkflowStatusBadge(statusKey, statusLabel) { // Match PC forms index STATUS column badge classes. var key = String(statusKey || '').trim().toLowerCase().replace(/\s+/g, '_'); var badgeClass = 'badge-warning'; if (key === 'approved') { badgeClass = 'badge-success'; } else if (key === 'rejected') { badgeClass = 'badge-danger'; } else if (key === 'draft' || key === '') { badgeClass = 'badge-secondary'; } return '' + schemeVisitHistoryEscapeHtml(statusLabel || '—') + ''; } function schemePcWorkflowDetailRowsHtml(rows) { if (!rows || !rows.length) return ''; var html = '
'; rows.forEach(function(row) { html += '
' + '' + schemeVisitHistoryEscapeHtml(row.label || '') + '' + '' + schemeVisitHistoryEscapeHtml(row.value || '—') + '' + '
'; }); html += '
'; return html; } function schemePcWorkflowSourceLabel(source) { var key = String(source || '').trim().toLowerCase(); if (key === 'meeting') return @json(__('Meeting')); if (key === 'working_paper') return @json(__('Working Paper')); if (key === 'pc_form') return @json(__('PC Form')); return ''; } function renderSchemePcWorkflowTimelineEntries(timeline) { if (!timeline || !timeline.length) { return '
' + @json(__('No workflow events recorded yet.')) + '
'; } var html = ''; return html; } function renderSchemePcWorkflowTimeline(data) { var forms = Array.isArray(data) ? data : (data && data.forms ? data.forms : []); if (!forms.length) { return '
' + @json(__('No PC forms found for this scheme.')) + '
'; } var accordionId = 'schemePcWorkflowAccordion'; var html = '
'; forms.forEach(function(form, formIndex) { var kindKey = String(form.kind || formIndex).replace(/[^a-z0-9_-]/gi, '-'); var collapseId = 'scheme-pc-workflow-collapse-' + kindKey; var headingId = 'scheme-pc-workflow-heading-' + kindKey; var isFirst = formIndex === 0; var titleText = schemeVisitHistoryEscapeHtml(form.label || 'PC Form'); html += '
' + '
' + '' + '
' + '
' + '
'; var timeline = Array.isArray(form.timeline) ? form.timeline : []; html += renderSchemePcWorkflowTimelineEntries(timeline); html += '
'; }); html += '
'; return html; } function loadSchemePcWorkflowStatus(projectId) { var $panel = $('#scheme-pc-workflow-panel'); if (!$panel.length || !projectId) return; if (schemePcWorkflowLoadedFor === String(projectId) && $panel.data('loaded') === 1) return; $panel.html('

' + @json(__('Loading PC workflow status...')) + '

'); $.ajax({ url: '{{ url("schemes") }}/' + encodeURIComponent(projectId) + '/pc-workflow-status', method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } }) .done(function(res) { if (res && res.status === 200 && res.data) { schemePcWorkflowLoadedFor = String(projectId); $panel.data('loaded', 1); $panel.html(renderSchemePcWorkflowTimeline(res.data || {})); } else { $panel.html('
' + @json(__('Unable to load PC workflow status.')) + '
'); } }) .fail(function(xhr) { var msg = @json(__('Unable to load PC workflow status.')); if (xhr && xhr.status === 403) msg = @json(__('Permission denied.')); if (xhr && xhr.status === 404) msg = @json(__('Scheme not found.')); $panel.html('
' + msg + '
'); }); } $(document).on('shown.bs.tab', '#projectDetailsTabs a[href="#tab-pc-workflow"]', function() { var projectId = $('#scheme-pc-workflow-panel').data('project-id'); if (projectId) { loadSchemePcWorkflowStatus(String(projectId)); } }); function schemeDocWfEsc(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function schemeDocWfRenderTile(doc) { var label = doc && doc.label ? String(doc.label) : '—'; var url = doc && doc.url ? String(doc.url) : ''; var available = !!(doc && doc.available && url); var dIcon = doc && doc.icon ? String(doc.icon) : 'file'; var inner = '' + '' + schemeDocWfEsc(label) + ''; if (available) { return '' + inner + ''; } return '' + inner + ''; } function renderSchemeDocWorkflowPanel(payload) { var title = (payload && payload.title) ? String(payload.title) : 'PC Documents & Workflow'; var rows = (payload && Array.isArray(payload.rows)) ? payload.rows : []; var html = '
' + '

' + schemeDocWfEsc(title) + '

' + '
'; if (!rows.length) { html += '
' + @json(__('No document stages were found for this scheme.')) + '
'; return html; } rows.forEach(function (row) { var theme = row && row.theme ? String(row.theme) : 'blue'; var icon = row && row.icon ? String(row.icon) : 'file-alt'; var stageTitle = row && row.title ? String(row.title) : '—'; var layout = row && row.layout ? String(row.layout) : 'simple'; html += '
' + '
' + '' + '

' + schemeDocWfEsc(stageTitle) + '

' + '
'; if (layout === 'forum') { if (row.preview) { html += '
' + schemeDocWfRenderTile(row.preview) + '
'; } var forumGroups = (row && Array.isArray(row.forum_groups)) ? row.forum_groups : []; if (forumGroups.length) { html += '
'; forumGroups.forEach(function (group) { var groupLabel = group && group.label ? String(group.label) : '—'; var groupDocs = (group && Array.isArray(group.docs)) ? group.docs : []; html += '
' + '

' + schemeDocWfEsc(groupLabel) + '

' + '
'; groupDocs.forEach(function (doc) { html += schemeDocWfRenderTile(doc); }); html += '
'; }); html += '
'; } } else { var docs = (row && Array.isArray(row.docs)) ? row.docs : []; html += '
'; docs.forEach(function (doc) { html += schemeDocWfRenderTile(doc); }); html += '
'; } html += '
'; }); return html; } function loadSchemeDocWorkflow(projectId) { var $panel = $('#scheme-doc-workflow-panel'); if (!$panel.length || !projectId) return; if (schemeDocWorkflowLoadedFor === String(projectId) && $panel.data('loaded') === 1) return; $panel.html('
' + @json(__('Loading scheme documents…')) + '
'); $.ajax({ url: @json(route('schemes.document-workflow', ['project' => '__ID__'])).replace('__ID__', encodeURIComponent(projectId)), method: 'GET', dataType: 'json', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }, timeout: 60000 }) .done(function (resp) { try { var payload = (resp && resp.data) ? resp.data : {}; $panel.html(renderSchemeDocWorkflowPanel(payload)); schemeDocWorkflowLoadedFor = String(projectId); $panel.data('loaded', 1); } catch (renderErr) { $panel.html('
' + schemeDocWfEsc(@json(__('Unable to display scheme documents.'))) + '
'); } }) .fail(function (xhr) { var msg = @json(__('Unable to load scheme documents.')); if (xhr && xhr.status === 403) msg = @json(__('You do not have permission to view these documents.')); else if (xhr && xhr.status === 404) msg = @json(__('Scheme not found.')); else if (xhr && xhr.statusText === 'timeout') msg = @json(__('Request timed out while loading scheme documents.')); $panel.html('
' + msg + '
'); }); } $(document).on('shown.bs.tab', '#projectDetailsTabs a[href="#tab-doc-workflow"]', function() { var projectId = $('#scheme-doc-workflow-panel').data('project-id'); if (projectId) { loadSchemeDocWorkflow(String(projectId)); } }); function schemeDocRepoEsc(value) { return escapeHtml(String(value == null ? '' : value)); } function renderSchemeDocRepositoryPanel(payload) { var uid = payload && payload.scheme_uid ? String(payload.scheme_uid) : ''; var disk = payload && Array.isArray(payload.disk_files) ? payload.disk_files : []; var linked = payload && Array.isArray(payload.linked_files) ? payload.linked_files : []; var folderExists = !!(payload && payload.folder_exists); var msg = payload && payload.message ? String(payload.message) : ''; var html = '
Document Repository
' + '

' + (uid ? ('UID ' + schemeDocRepoEsc(uid) + ' · ') : '') + @json(__('Files from the PCFMS attachments folder for this UID. They appear here even if nothing was imported yet.')) + '

'; if (msg) { html += '
' + schemeDocRepoEsc(msg) + '
'; } if (!folderExists && !disk.length) { html += '
' + @json(__('No UID folder found under the configured attachments root.')) + '
'; return html; } if (payload && payload.truncated) { html += '
' + @json(__('Showing the first files only (folder is large).')) + '
'; } html += '
' + @json(__('UID folder files')) + ' (' + disk.length + ')
'; if (!disk.length) { html += '
' + @json(__('Folder exists but has no files.')) + '
'; } else { html += '
' + '' + ''; disk.forEach(function (file) { var name = file && (file.title || file.file_name) ? String(file.title || file.file_name) : ''; var group = file && file.group_name ? String(file.group_name) : '—'; var url = file && file.url ? String(file.url) : '#'; var sizeLabel = file && file.size_label ? String(file.size_label) : '—'; var linkedFlag = file && file.is_linked ? @json(__('Yes')) : @json(__('No')); html += '' + '' + '' + '' + '' + ''; }); html += '
' + @json(__('Group')) + '' + @json(__('File')) + '' + @json(__('Size')) + '' + @json(__('Linked')) + '
' + schemeDocRepoEsc(group) + '' + schemeDocRepoEsc(name) + '' + '
' + schemeDocRepoEsc(file.file_name || '') + '
' + schemeDocRepoEsc(sizeLabel) + '' + schemeDocRepoEsc(linkedFlag) + '
'; } html += '
' + @json(__('Imported / linked on PC')) + ' (' + linked.length + ')
'; if (!linked.length) { html += '
' + @json(__('None linked yet. Submit from Admin → PCFMS Evidence Import to attach paths into the PC document repository.')) + '
'; } else { html += '
' + '' + ''; linked.forEach(function (file) { var name = file && (file.title || file.file_name) ? String(file.title || file.file_name) : ''; var group = file && file.group_name ? String(file.group_name) : '—'; var url = file && file.url ? String(file.url) : '#'; var linkedAt = file && file.linked_at ? String(file.linked_at) : '—'; html += '' + '' + '' + '' + ''; }); html += '
' + @json(__('Group')) + '' + @json(__('File')) + '' + @json(__('Linked')) + '
' + schemeDocRepoEsc(group) + '' + schemeDocRepoEsc(name) + '' + schemeDocRepoEsc(linkedAt) + '
'; } html += ''; return html; } var schemeDocRepositoryXhr = null; function loadSchemeDocRepository(projectId, forceReload) { var $panel = $('#scheme-doc-repository-panel'); if (!$panel.length || !projectId) return; if (!forceReload && schemeDocRepositoryLoadedFor === String(projectId) && $panel.data('loaded') === 1) return; if (schemeDocRepositoryXhr && schemeDocRepositoryXhr.readyState !== 4) { try { schemeDocRepositoryXhr.abort(); } catch (abortErr) {} } $panel.data('loaded', 0); $panel.html( '
' + '
' + '

' + @json(__('Loading Document Repository...')) + '

' + '
' ); var url = @json(url('/schemes')) + '/' + encodeURIComponent(String(projectId)) + '/document-repository' + (forceReload ? '?refresh=1' : ''); schemeDocRepositoryXhr = $.ajax({ url: url, method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }, timeout: 14000, cache: false }) .done(function (resp) { try { var payload = (resp && resp.data) ? resp.data : {}; $panel.html(renderSchemeDocRepositoryPanel(payload)); schemeDocRepositoryLoadedFor = String(projectId); $panel.data('loaded', 1); } catch (renderErr) { $panel.html('
' + schemeDocRepoEsc(@json(__('Unable to display Document Repository.'))) + '
'); } }) .fail(function (xhr) { if (xhr && xhr.statusText === 'abort') return; var msg = @json(__('Unable to load Document Repository.')); if (xhr && xhr.status === 403) msg = @json(__('You do not have permission to view these files.')); else if (xhr && xhr.status === 404) msg = @json(__('Scheme not found.')); else if (xhr && xhr.statusText === 'timeout') { msg = @json(__('Timed out reading the attachments USB/HDD. Wake the drive in Explorer, confirm the path, then retry.')); } $panel.html( '
' + msg + '
' + '' ); }); } $(document).on('click', '.scheme-doc-repo-retry', function () { var projectId = $(this).data('project-id'); if (projectId) { loadSchemeDocRepository(String(projectId), true); } }); $(document).on('shown.bs.tab', '#projectDetailsTabs a[href="#tab-doc-repository"]', function() { var projectId = $('#scheme-doc-repository-panel').data('project-id'); if (projectId) { loadSchemeDocRepository(String(projectId)); } }); $(document).on('click', '#projectDetailsModal-create-meeting', function(e) { var projectId = $(this).data('project-id'); if (!projectId) return; e.preventDefault(); var $btn = $(this); var base = ($btn.attr('href') || '').split('?')[0] || @json(route('meeting-workspace.meetings.create')); var typesRaw = $btn.attr('data-mws-schedule-types') || ''; var types = null; try { types = typesRaw ? JSON.parse(typesRaw) : null; } catch (errTypes) { types = null; } function goWithProfile(profileSlug) { var qs = 'mws_profile=' + encodeURIComponent(String(profileSlug)) + '&prefill_project_id=' + encodeURIComponent(String(projectId)); var pcFormId = parseInt($btn.attr('data-pc-form-id'), 10) || 0; var pcFormKind = String($btn.attr('data-pc-form-kind') || '').trim(); if (pcFormId > 0) { qs += '&prefill_pc_form_id=' + encodeURIComponent(String(pcFormId)); } if (pcFormKind) { qs += '&prefill_pc_form_kind=' + encodeURIComponent(pcFormKind); } window.location.href = base + '?' + qs; } if (typeof window.mwsPickScheduleMeetingType === 'function') { window.mwsPickScheduleMeetingType(types, goWithProfile); return; } document.cookie = 'create_meeting_scheme_project_id=' + encodeURIComponent(String(projectId)) + '; path=/; max-age=300; SameSite=Lax'; window.location.href = $btn.attr('href'); }); $('#projectDetailsModal').on('hidden.bs.modal', function() { if (!$('.modal.show').length) { document.body.classList.remove('project-details-modal-active'); } $('#projectDetailsModal-body').html('

Loading...

'); });