105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import { getYearToDateRange } from '../utils/dateUtils';
|
|
|
|
export const TECHNICIAN_WORKING_HOURS_REPORT = 'Technicians working Hours';
|
|
|
|
export type TechnicianWorkingHoursRow = {
|
|
engineer?: string;
|
|
technician_name?: string;
|
|
total_hours?: number | string;
|
|
is_total_row?: number | boolean;
|
|
};
|
|
|
|
export type TechnicianWorkingHoursFilters = {
|
|
work_order_type?: string;
|
|
from_date?: string;
|
|
to_date?: string;
|
|
company?: string;
|
|
site_name?: string;
|
|
};
|
|
|
|
export type TechnicianWorkingHoursChartData = {
|
|
labels: string[];
|
|
datasets: Array<{ name: string; values: number[]; colors?: string[] }>;
|
|
totalHours: number;
|
|
rows: TechnicianWorkingHoursRow[];
|
|
};
|
|
|
|
export const getDefaultTechnicianHoursDateRange = () => getYearToDateRange();
|
|
|
|
export const getTechnicianHoursLabel = (row: TechnicianWorkingHoursRow): string =>
|
|
(row.technician_name || row.engineer || 'Unknown').trim() || 'Unknown';
|
|
|
|
export const parseTechnicianHours = (value: unknown): number => {
|
|
if (value == null || value === '') return 0;
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
};
|
|
|
|
export const buildTechnicianWorkingHoursChartData = (
|
|
rows: TechnicianWorkingHoursRow[]
|
|
): TechnicianWorkingHoursChartData | null => {
|
|
const dataRows = rows.filter(row => row && !row.is_total_row);
|
|
if (!dataRows.length) return null;
|
|
|
|
const labels = dataRows.map(getTechnicianHoursLabel);
|
|
const values = dataRows.map(row => parseTechnicianHours(row.total_hours));
|
|
const totalHours = values.reduce((sum, value) => sum + value, 0);
|
|
|
|
return {
|
|
labels,
|
|
datasets: [{ name: 'Total Hours', values, colors: ['#6366F1'] }],
|
|
totalHours,
|
|
rows: dataRows,
|
|
};
|
|
};
|
|
|
|
export const runTechnicianWorkingHoursReport = async (
|
|
filters: TechnicianWorkingHoursFilters
|
|
): Promise<TechnicianWorkingHoursRow[]> => {
|
|
const csrfToken =
|
|
typeof window !== 'undefined' && (window as any).csrf_token && (window as any).csrf_token !== 'None'
|
|
? (window as any).csrf_token
|
|
: null;
|
|
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
};
|
|
if (csrfToken) {
|
|
headers['X-Frappe-CSRF-Token'] = csrfToken;
|
|
}
|
|
|
|
const cleanedFilters: Record<string, string> = {};
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value) cleanedFilters[key] = value;
|
|
});
|
|
|
|
const response = await fetch('/api/method/frappe.desk.query_report.run', {
|
|
method: 'POST',
|
|
headers,
|
|
credentials: 'include',
|
|
body: JSON.stringify({
|
|
report_name: TECHNICIAN_WORKING_HOURS_REPORT,
|
|
filters: cleanedFilters,
|
|
ignore_prepared_report: 1,
|
|
}),
|
|
});
|
|
|
|
const payload = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(payload?.message || payload?.exc || 'Failed to load technician working hours');
|
|
}
|
|
|
|
const result = payload?.message?.result || payload?.message?.message?.result || [];
|
|
if (!Array.isArray(result)) return [];
|
|
|
|
return result.filter(row => row && !row.is_total_row);
|
|
};
|
|
|
|
export const fetchTechnicianWorkingHoursChart = async (
|
|
filters: TechnicianWorkingHoursFilters
|
|
): Promise<TechnicianWorkingHoursChartData | null> => {
|
|
const rows = await runTechnicianWorkingHoursReport(filters);
|
|
return buildTechnicianWorkingHoursChartData(rows);
|
|
};
|