csv-export.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import type {
  2. RevenueReport,
  3. ServiceReport,
  4. CustomerReport,
  5. InventoryReport,
  6. TechnicianReport,
  7. PartsUsageReport,
  8. JobAnalyticsReport,
  9. CustomerRetentionReport,
  10. } from "../Schema/reportTypes";
  11. import { formatCurrency } from "@/lib/format";
  12. function downloadCsv(
  13. filename: string,
  14. headers: string[],
  15. rows: Record<string, unknown>[],
  16. keys: string[],
  17. ) {
  18. const escapeCsv = (v: unknown) => {
  19. const s = String(v ?? "");
  20. return s.includes(",") || s.includes('"') || s.includes("\n")
  21. ? `"${s.replace(/"/g, '""')}"`
  22. : s;
  23. };
  24. const lines = [
  25. headers.map(escape).join(","),
  26. ...rows.map((r) => keys.map((k) => escapeCsv(r[k])).join(",")),
  27. ];
  28. const blob = new Blob([lines.join("\n")], { type: "text/csv;charset=utf-8;" });
  29. const url = URL.createObjectURL(blob);
  30. const a = document.createElement("a");
  31. a.href = url;
  32. a.download = filename;
  33. a.click();
  34. URL.revokeObjectURL(url);
  35. }
  36. export function exportRevenueCsv(data: RevenueReport, currencyCode: string) {
  37. const fmt = (n: number) => formatCurrency(n, currencyCode);
  38. const rows = data.monthly.map((m) => ({
  39. month: m.month,
  40. revenue: fmt(m.revenue),
  41. collected: fmt(m.collected),
  42. count: m.count,
  43. }));
  44. downloadCsv(
  45. "revenue-report.csv",
  46. ["Month", "Revenue", "Collected", "Count"],
  47. rows,
  48. ["month", "revenue", "collected", "count"],
  49. );
  50. }
  51. export function exportServicesCsv(data: ServiceReport) {
  52. const rows = [
  53. ...data.byStatus.map((s) => ({ category: "Status", label: s.status, count: s.count })),
  54. ...data.byType.map((t) => ({ category: "Type", label: t.type, count: t.count })),
  55. ];
  56. downloadCsv(
  57. "services-report.csv",
  58. ["Category", "Label", "Count"],
  59. rows,
  60. ["category", "label", "count"],
  61. );
  62. }
  63. export function exportCustomersCsv(data: CustomerReport, currencyCode: string) {
  64. const fmt = (n: number) => formatCurrency(n, currencyCode);
  65. const rows = data.topCustomers.map((c) => ({
  66. name: c.name,
  67. company: c.company ?? "",
  68. serviceCount: c.serviceCount,
  69. totalSpent: fmt(c.totalSpent),
  70. }));
  71. downloadCsv(
  72. "customers-report.csv",
  73. ["Name", "Company", "Services", "Total Spent"],
  74. rows,
  75. ["name", "company", "serviceCount", "totalSpent"],
  76. );
  77. }
  78. export function exportInventoryCsv(data: InventoryReport, currencyCode: string) {
  79. const fmt = (n: number) => formatCurrency(n, currencyCode);
  80. const rows = data.lowStock.map((p) => ({
  81. name: p.name,
  82. partNumber: p.partNumber ?? "",
  83. quantity: p.quantity,
  84. minQuantity: p.minQuantity ?? "",
  85. unitCost: p.unitCost != null ? fmt(p.unitCost) : "",
  86. }));
  87. downloadCsv(
  88. "inventory-report.csv",
  89. ["Name", "Part #", "Quantity", "Min Quantity", "Unit Cost"],
  90. rows,
  91. ["name", "partNumber", "quantity", "minQuantity", "unitCost"],
  92. );
  93. }
  94. export function exportTechniciansCsv(data: TechnicianReport, currencyCode: string) {
  95. const fmt = (n: number) => formatCurrency(n, currencyCode);
  96. const rows = data.technicians.map((t) => ({
  97. techName: t.techName,
  98. jobCount: t.jobCount,
  99. totalRevenue: fmt(t.totalRevenue),
  100. avgRevenue: fmt(t.avgRevenue),
  101. totalHours: t.totalLaborHours.toFixed(1),
  102. avgHours: t.avgHours.toFixed(1),
  103. }));
  104. downloadCsv(
  105. "technicians-report.csv",
  106. ["Technician", "Jobs", "Total Revenue", "Avg Revenue", "Total Hours", "Avg Hours"],
  107. rows,
  108. ["techName", "jobCount", "totalRevenue", "avgRevenue", "totalHours", "avgHours"],
  109. );
  110. }
  111. export function exportPartsCsv(data: PartsUsageReport, currencyCode: string) {
  112. const fmt = (n: number) => formatCurrency(n, currencyCode);
  113. const rows = data.parts.map((p) => ({
  114. name: p.name,
  115. partNumber: p.partNumber ?? "",
  116. usageCount: p.usageCount,
  117. totalQuantity: p.totalQuantity,
  118. totalRevenue: fmt(p.totalRevenue),
  119. }));
  120. downloadCsv(
  121. "parts-usage-report.csv",
  122. ["Part Name", "Part #", "Usage Count", "Total Qty", "Total Revenue"],
  123. rows,
  124. ["name", "partNumber", "usageCount", "totalQuantity", "totalRevenue"],
  125. );
  126. }
  127. export function exportJobAnalyticsCsv(data: JobAnalyticsReport, currencyCode: string) {
  128. const fmt = (n: number) => formatCurrency(n, currencyCode);
  129. const rows = data.topServiceTypes.map((t) => ({
  130. type: t.type,
  131. count: t.count,
  132. avgValue: fmt(t.avgValue),
  133. avgHours: t.avgHours.toFixed(1),
  134. }));
  135. downloadCsv(
  136. "job-analytics-report.csv",
  137. ["Service Type", "Count", "Avg Value", "Avg Hours"],
  138. rows,
  139. ["type", "count", "avgValue", "avgHours"],
  140. );
  141. }
  142. export function exportRetentionCsv(data: CustomerRetentionReport, currencyCode: string) {
  143. const fmt = (n: number) => formatCurrency(n, currencyCode);
  144. const rows = data.topReturning.map((c) => ({
  145. name: c.name,
  146. company: c.company ?? "",
  147. visitCount: c.visitCount,
  148. totalSpent: fmt(c.totalSpent),
  149. avgDaysBetweenVisits: c.avgTimeBetweenVisits ?? "",
  150. }));
  151. downloadCsv(
  152. "retention-report.csv",
  153. ["Customer", "Company", "Visits", "Total Spent", "Avg Days Between Visits"],
  154. rows,
  155. ["name", "company", "visitCount", "totalSpent", "avgDaysBetweenVisits"],
  156. );
  157. }