JobAnalyticsCharts.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. "use client";
  2. import {
  3. BarChart,
  4. Bar,
  5. XAxis,
  6. YAxis,
  7. CartesianGrid,
  8. Tooltip,
  9. ResponsiveContainer,
  10. PieChart,
  11. Pie,
  12. Cell,
  13. Legend,
  14. LineChart,
  15. Line,
  16. } from "recharts";
  17. import type { DayOfWeekDistribution, ServiceTypeAnalytics, MonthlyTrend } from "../Schema/reportTypes";
  18. const CHART_COLORS = [
  19. "#3b82f6",
  20. "#10b981",
  21. "#f59e0b",
  22. "#8b5cf6",
  23. "#f43f5e",
  24. "#06b6d4",
  25. "#ec4899",
  26. "#84cc16",
  27. ];
  28. interface DayOfWeekChartProps {
  29. data: DayOfWeekDistribution[];
  30. labels?: { jobs: string };
  31. }
  32. export function DayOfWeekChart({ data, labels }: DayOfWeekChartProps) {
  33. if (data.length === 0) return null;
  34. return (
  35. <ResponsiveContainer width="100%" height={300}>
  36. <BarChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
  37. <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
  38. <XAxis
  39. dataKey="day"
  40. className="text-xs fill-muted-foreground"
  41. tick={{ fontSize: 12 }}
  42. tickFormatter={(v) => v.slice(0, 3)}
  43. />
  44. <YAxis className="text-xs fill-muted-foreground" tick={{ fontSize: 12 }} />
  45. <Tooltip
  46. wrapperStyle={{ outline: "none" }}
  47. contentStyle={{
  48. backgroundColor: "var(--popover)",
  49. border: "1px solid var(--border)",
  50. borderRadius: "8px",
  51. color: "var(--popover-foreground)",
  52. }}
  53. />
  54. <Bar dataKey="count" name={labels?.jobs ?? "Jobs"} fill="#3b82f6" radius={[4, 4, 0, 0]} />
  55. </BarChart>
  56. </ResponsiveContainer>
  57. );
  58. }
  59. interface ServiceTypeDonutProps {
  60. data: ServiceTypeAnalytics[];
  61. }
  62. export function ServiceTypeAnalyticsDonut({ data }: ServiceTypeDonutProps) {
  63. if (data.length === 0) return null;
  64. return (
  65. <ResponsiveContainer width="100%" height={300}>
  66. <PieChart>
  67. <Pie
  68. data={data}
  69. dataKey="count"
  70. nameKey="type"
  71. cx="50%"
  72. cy="50%"
  73. innerRadius={60}
  74. outerRadius={80}
  75. paddingAngle={2}
  76. >
  77. {data.map((_, i) => (
  78. <Cell key={i} fill={CHART_COLORS[i % CHART_COLORS.length]} />
  79. ))}
  80. </Pie>
  81. <Tooltip
  82. wrapperStyle={{ outline: "none" }}
  83. contentStyle={{
  84. backgroundColor: "var(--popover)",
  85. border: "1px solid var(--border)",
  86. borderRadius: "8px",
  87. color: "var(--popover-foreground)",
  88. }}
  89. />
  90. <Legend
  91. formatter={(value) => (
  92. <span className="text-xs text-muted-foreground">{value}</span>
  93. )}
  94. />
  95. </PieChart>
  96. </ResponsiveContainer>
  97. );
  98. }
  99. interface MonthlyTrendChartProps {
  100. data: MonthlyTrend[];
  101. formatCurrency: (value: number) => string;
  102. labels?: { jobs: string; revenue: string };
  103. }
  104. export function MonthlyTrendChart({ data, formatCurrency, labels }: MonthlyTrendChartProps) {
  105. if (data.length === 0) return null;
  106. const jobsLabel = labels?.jobs ?? "Jobs";
  107. const revenueLabel = labels?.revenue ?? "Revenue";
  108. return (
  109. <ResponsiveContainer width="100%" height={300}>
  110. <LineChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
  111. <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
  112. <XAxis
  113. dataKey="month"
  114. className="text-xs fill-muted-foreground"
  115. tick={{ fontSize: 12 }}
  116. />
  117. <YAxis
  118. yAxisId="left"
  119. className="text-xs fill-muted-foreground"
  120. tick={{ fontSize: 12 }}
  121. />
  122. <YAxis
  123. yAxisId="right"
  124. orientation="right"
  125. className="text-xs fill-muted-foreground"
  126. tick={{ fontSize: 12 }}
  127. tickFormatter={(v) => formatCurrency(v)}
  128. />
  129. <Tooltip
  130. formatter={(value, name) =>
  131. name === revenueLabel ? formatCurrency(Number(value)) : value
  132. }
  133. wrapperStyle={{ outline: "none" }}
  134. contentStyle={{
  135. backgroundColor: "var(--popover)",
  136. border: "1px solid var(--border)",
  137. borderRadius: "8px",
  138. color: "var(--popover-foreground)",
  139. }}
  140. />
  141. <Line yAxisId="left" type="monotone" dataKey="count" name={jobsLabel} stroke="#3b82f6" strokeWidth={2} dot={false} />
  142. <Line yAxisId="right" type="monotone" dataKey="revenue" name={revenueLabel} stroke="#10b981" strokeWidth={2} dot={false} />
  143. <Legend
  144. formatter={(value) => (
  145. <span className="text-xs text-muted-foreground">{value}</span>
  146. )}
  147. />
  148. </LineChart>
  149. </ResponsiveContainer>
  150. );
  151. }