239 lines
7.6 KiB
JavaScript
239 lines
7.6 KiB
JavaScript
export const REPORT_TIMEOUT = 120000;
|
|
|
|
const REPORT_START = new Date(2026, 3, 1);
|
|
|
|
export const ABM_IDENTITY = [
|
|
'ZBM',
|
|
'RBM',
|
|
'ABM HQ',
|
|
'HQ Div',
|
|
'ABM Name',
|
|
'Emp ID',
|
|
'Reporting Manager (RBM)',
|
|
'Email',
|
|
'Approved Count per ABM',
|
|
'Remarks',
|
|
];
|
|
|
|
export const ABM_SUMMARY = [
|
|
'Working Days',
|
|
'Daily Target (POBs = Approved Count)',
|
|
'Month Target (POBs)',
|
|
'Total TMs Qualified (≥₹15k POB)',
|
|
'Achievement %',
|
|
'Total Chemists Qualified (≥₹15k POB)',
|
|
'Days Daily Target Met',
|
|
'Target Status',
|
|
];
|
|
|
|
export const TOTAL_TMS = 'Total TMs Qualified (≥₹15k POB)';
|
|
export const TOTAL_CHEMISTS = 'Total Chemists Qualified (≥₹15k POB)';
|
|
|
|
export const TAB_LABELS = {
|
|
abm: 'Details ABM wise',
|
|
region: 'Region Daily Winners',
|
|
territory: 'Territory Daily Winners',
|
|
};
|
|
|
|
function monthLabel(date) {
|
|
return date.toLocaleString('en-US', { month: 'long', year: 'numeric' });
|
|
}
|
|
|
|
export function currentMonth() {
|
|
return monthLabel(new Date());
|
|
}
|
|
|
|
export function reportMonths(now = new Date()) {
|
|
const end = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
const months = [];
|
|
for (let cursor = new Date(REPORT_START); cursor <= end; cursor = new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)) {
|
|
months.push(monthLabel(cursor));
|
|
}
|
|
return months.length ? months : [currentMonth()];
|
|
}
|
|
|
|
export function canViewReport({ domain, permissions = [], designation }) {
|
|
if (domain !== 'team2') return false;
|
|
if (permissions.includes('all_data_access')) return true;
|
|
const value = String(designation ?? '').trim().toUpperCase();
|
|
return value === 'HO' || value === 'ADMIN';
|
|
}
|
|
|
|
export function exportFilename(month) {
|
|
const slug = String(month).replace(/[^A-Za-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
return `Adherence-Pack-POB-Drive-${slug || 'report'}.xlsx`;
|
|
}
|
|
|
|
function isNumeric(value) {
|
|
if (typeof value === 'number') return Number.isFinite(value);
|
|
if (typeof value !== 'string' || value.trim() === '') return false;
|
|
return Number.isFinite(Number(value));
|
|
}
|
|
|
|
export function field(row, key) {
|
|
if (!row || typeof row !== 'object') return '';
|
|
return row[key] ?? '';
|
|
}
|
|
|
|
function listRows(data, key) {
|
|
const rows = field(data, key);
|
|
if (Array.isArray(rows)) return rows;
|
|
if (rows && typeof rows === 'object') return Object.values(rows);
|
|
return [];
|
|
}
|
|
|
|
function orderedDays(days) {
|
|
if (!days || typeof days !== 'object') return [];
|
|
const list = [];
|
|
Object.entries(days).forEach(([label, day]) => {
|
|
if (!day || typeof day !== 'object') return;
|
|
list.push({
|
|
label: String(label),
|
|
date: String(day.date ?? ''),
|
|
type: String(day.type ?? 'W'),
|
|
value: day.value ?? 0,
|
|
chemistsQualified: day.chemistsQualified ?? 0,
|
|
});
|
|
});
|
|
return list.sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
|
|
}
|
|
|
|
function dayMap(days) {
|
|
const map = {};
|
|
orderedDays(days).forEach((day) => {
|
|
if (day.date !== '') map[day.date] = day;
|
|
});
|
|
return map;
|
|
}
|
|
|
|
export function dayValue(day, key) {
|
|
if (day?.type === 'H' || day?.[key] === 'Holiday') return 'Holiday';
|
|
const value = day?.[key] ?? 0;
|
|
return isNumeric(value) ? Number(value) : value;
|
|
}
|
|
|
|
export function dayHeader(label) {
|
|
const match = String(label).trim().match(/^(\S+)\s+([\s\S]+)$/);
|
|
return match ? `${match[1]}\n${match[2]}` : String(label);
|
|
}
|
|
|
|
export function isVacantName(name) {
|
|
return String(name ?? '').trim().toLowerCase().includes('vacant');
|
|
}
|
|
|
|
function dayTemplate(lists) {
|
|
for (const rows of lists) {
|
|
if (!rows.length) continue;
|
|
const days = orderedDays(field(rows[0], 'days'));
|
|
if (days.length) return days;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
export function regionColumns(template) {
|
|
const columns = [];
|
|
template.forEach((day) => {
|
|
const header = dayHeader(day.label);
|
|
if (day.type === 'H') {
|
|
columns.push({ kind: 'holiday', type: 'H', date: day.date, header, day });
|
|
return;
|
|
}
|
|
columns.push({ kind: 'tm', type: 'W', date: day.date, header, day });
|
|
columns.push({ kind: 'chemist', type: 'W', date: day.date, header, day });
|
|
});
|
|
columns.push({ kind: 'total-tm', type: '', header: TOTAL_TMS });
|
|
columns.push({ kind: 'total-chemist', type: '', header: TOTAL_CHEMISTS });
|
|
return columns;
|
|
}
|
|
|
|
export function regionCell(row, column, days) {
|
|
if (column.kind === 'total-tm') return field(row, TOTAL_TMS);
|
|
if (column.kind === 'total-chemist') return field(row, TOTAL_CHEMISTS);
|
|
if (column.kind === 'holiday') return 'Holiday';
|
|
const day = days[column.date] ?? column.day;
|
|
return dayValue(day, column.kind === 'chemist' ? 'chemistsQualified' : 'value');
|
|
}
|
|
|
|
export function buildReport(data) {
|
|
const abmRows = listRows(data, 'abmReport');
|
|
const rbmRows = listRows(data, 'rbmReport');
|
|
const tmRows = listRows(data, 'tmReport');
|
|
const template = dayTemplate([abmRows, rbmRows, tmRows]);
|
|
return { abmRows, rbmRows, tmRows, template, dayMap };
|
|
}
|
|
|
|
function abmSection(report) {
|
|
const { abmRows, template } = report;
|
|
const dayTypes = [
|
|
...ABM_IDENTITY.map(() => ''),
|
|
...template.map((day) => (day.type === 'H' ? 'H' : 'W')),
|
|
...ABM_SUMMARY.map(() => ''),
|
|
];
|
|
const headers = [...ABM_IDENTITY, ...template.map((day) => dayHeader(day.label)), ...ABM_SUMMARY];
|
|
const rows = abmRows.map((row) => {
|
|
const days = dayMap(field(row, 'days'));
|
|
return {
|
|
cells: [
|
|
...ABM_IDENTITY.map((key) => field(row, key)),
|
|
...template.map((day) => dayValue(days[day.date] ?? day, 'value')),
|
|
...ABM_SUMMARY.map((key) => field(row, key)),
|
|
],
|
|
vacant: isVacantName(field(row, 'ABM HQ')) || isVacantName(field(row, 'ABM Name')),
|
|
};
|
|
});
|
|
return { id: 'abm', dayTypes, headers, rows };
|
|
}
|
|
|
|
function regionSection(report) {
|
|
const columns = regionColumns(report.template);
|
|
const dayTypes = ['', '', '', ...columns.map((column) => column.type)];
|
|
const headers = ['Region (RBM)', 'RBM Name', 'RBM Employee Code', ...columns.map((column) => column.header)];
|
|
const rows = report.rbmRows.map((row) => {
|
|
const days = dayMap(field(row, 'days'));
|
|
const cells = [
|
|
field(row, 'Region (RBM)'),
|
|
field(row, 'RBM Name'),
|
|
field(row, 'RBM Employee Code'),
|
|
...columns.map((column) => regionCell(row, column, days)),
|
|
];
|
|
return { cells, vacant: isVacantName(cells[0]) || isVacantName(cells[1]) };
|
|
});
|
|
return { id: 'region', dayTypes, headers, rows };
|
|
}
|
|
|
|
function territorySection(report) {
|
|
const { tmRows, template } = report;
|
|
const dayTypes = ['', '', '', ...template.map((day) => (day.type === 'H' ? 'H' : 'W')), ''];
|
|
const headers = ['Territory', 'TM Name', 'TM Employee Code', ...template.map((day) => dayHeader(day.label)), TOTAL_CHEMISTS];
|
|
const rows = tmRows.map((row) => {
|
|
const days = dayMap(field(row, 'days'));
|
|
const cells = [
|
|
field(row, 'Territory'),
|
|
field(row, 'TM Name'),
|
|
field(row, 'TM Employee Code'),
|
|
...template.map((day) => dayValue(days[day.date] ?? day, 'value')),
|
|
field(row, TOTAL_CHEMISTS),
|
|
];
|
|
return { cells, vacant: isVacantName(cells[0]) || isVacantName(cells[1]) };
|
|
});
|
|
return { id: 'territory', dayTypes, headers, rows };
|
|
}
|
|
|
|
export function buildSections(report) {
|
|
return [abmSection(report), regionSection(report), territorySection(report)];
|
|
}
|
|
|
|
export function headerClass(section, index) {
|
|
const type = section.dayTypes[index] ?? '';
|
|
if (type === 'H') return 'pob-day-h';
|
|
if (type === 'W') return 'pob-day-w';
|
|
const firstDay = section.dayTypes.findIndex((value) => value === 'W' || value === 'H');
|
|
return firstDay !== -1 && index < firstDay ? 'pob-identity' : 'pob-summary';
|
|
}
|
|
|
|
export function cellClass(section, row, cell, index) {
|
|
if (row.vacant) return '';
|
|
const type = section.dayTypes[index] ?? '';
|
|
return type === 'H' || cell === 'Holiday' ? 'pob-day-h' : '';
|
|
}
|