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 = '';
$('#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 = '' +
'' +
'
';
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 = '';
timeline.forEach(function(entry, idx) {
var isLast = idx === timeline.length - 1;
var timestamp = '—';
if (entry.action_date) {
var d = new Date(entry.action_date);
if (!isNaN(d.getTime())) {
timestamp = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) +
' • ' + d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
}
}
var sourceLabel = schemePcWorkflowSourceLabel(entry.source);
html += '' +
'' +
'
' +
'
' +
'
' + schemeVisitHistoryEscapeHtml(entry.action_label || 'Workflow Action') +
(sourceLabel ? ' ' + schemeVisitHistoryEscapeHtml(sourceLabel) + ' ' : '') +
'
' +
'
' + schemeVisitHistoryEscapeHtml(entry.routing_line || '—') + '
' +
'
' + schemeVisitHistoryEscapeHtml(timestamp) + '
' +
schemePcWorkflowDetailRowsHtml(entry.detail_rows) +
(entry.remarks
? '
' + @json(__('Note:')) + ' ' + schemeVisitHistoryEscapeHtml(entry.remarks) + '
'
: '') +
'
' +
'
' +
' ';
});
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 = '';
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 += ''
+ '' + @json(__('Group')) + ' ' + @json(__('File')) + ' ' + @json(__('Size')) + ' ' + @json(__('Linked')) + ' '
+ ' ';
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 += ''
+ '' + schemeDocRepoEsc(group) + ' '
+ ' ' + schemeDocRepoEsc(name) + ' '
+ '' + schemeDocRepoEsc(file.file_name || '') + '
'
+ '' + schemeDocRepoEsc(sizeLabel) + ' '
+ '' + schemeDocRepoEsc(linkedFlag) + ' '
+ ' ';
});
html += '
';
}
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 += ''
+ '' + @json(__('Group')) + ' ' + @json(__('File')) + ' ' + @json(__('Linked')) + ' '
+ ' ';
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 += ''
+ '' + schemeDocRepoEsc(group) + ' '
+ '' + schemeDocRepoEsc(name) + ' '
+ '' + schemeDocRepoEsc(linkedAt) + ' '
+ ' ';
});
html += '
';
}
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 + '
'
+ '' + @json(__('Retry')) + ' '
);
});
}
$(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('');
});