feat(reports): add daily rate to report tables and exports

This commit is contained in:
2026-04-28 20:26:21 +03:30
parent 3efa04094d
commit 2b5ee2abf1
5 changed files with 30 additions and 11 deletions

View File

@@ -58,6 +58,7 @@ export interface DailyReportRow {
billable_duration: string; billable_duration: string;
non_billable_duration: string; non_billable_duration: string;
total_duration: string; total_duration: string;
latest_hourly_rate: CurrencyTotal | null;
income_totals: CurrencyTotal[]; income_totals: CurrencyTotal[];
} }

View File

@@ -43,6 +43,14 @@ const formatMoneyTotals = (totals: { currency: string; amount: string }[], lang:
return totals.map((item) => `${formatAmount(item.amount, lang)} ${currencyLabel(item.currency, lang)}`).join(" | "); return totals.map((item) => `${formatAmount(item.amount, lang)} ${currencyLabel(item.currency, lang)}`).join(" | ");
}; };
const formatHourlyRate = (
rate: { currency: string; amount: string } | null,
lang: "en" | "fa",
) => {
if (!rate) return "-";
return `${formatAmount(rate.amount, lang)} ${currencyLabel(rate.currency, lang)}`;
};
const formatDisplayDate = (value: string, lang: "en" | "fa") => { const formatDisplayDate = (value: string, lang: "en" | "fa") => {
const parsed = new Date(`${value}T00:00:00`); const parsed = new Date(`${value}T00:00:00`);
return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR" : "en-US", { return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR" : "en-US", {
@@ -209,6 +217,10 @@ export function ReportsTablePanel({
<div className="mb-1 text-[11px] uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">{labels.nonBillableHours}</div> <div className="mb-1 text-[11px] uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">{labels.nonBillableHours}</div>
<div className="font-medium">{localizeDigits(day.non_billable_duration, lang)}</div> <div className="font-medium">{localizeDigits(day.non_billable_duration, lang)}</div>
</div> </div>
<div>
<div className="mb-1 text-[11px] uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">{labels.hourlyRate}</div>
<div className="font-medium">{formatHourlyRate(day.latest_hourly_rate, lang)}</div>
</div>
<div className="col-span-2"> <div className="col-span-2">
<div className="mb-1 text-[11px] uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">{labels.totalIncome}</div> <div className="mb-1 text-[11px] uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">{labels.totalIncome}</div>
<div className="font-medium">{formatMoneyTotals(day.income_totals, lang)}</div> <div className="font-medium">{formatMoneyTotals(day.income_totals, lang)}</div>
@@ -249,9 +261,10 @@ export function ReportsTablePanel({
<thead> <thead>
<tr className="border-b border-slate-200 text-slate-500 dark:border-slate-800 dark:text-slate-400"> <tr className="border-b border-slate-200 text-slate-500 dark:border-slate-800 dark:text-slate-400">
<th className="w-[18%] px-3 py-3 text-start font-medium">{labels.date}</th> <th className="w-[18%] px-3 py-3 text-start font-medium">{labels.date}</th>
<th className="w-[16%] px-3 py-3 text-start font-medium">{labels.billableHours}</th> <th className="w-[14%] px-3 py-3 text-start font-medium">{labels.billableHours}</th>
<th className="w-[20%] px-3 py-3 text-start font-medium">{labels.nonBillableHours}</th> <th className="w-[14%] px-3 py-3 text-start font-medium">{labels.nonBillableHours}</th>
<th className="w-[36%] px-3 py-3 text-start font-medium">{labels.totalIncome}</th> <th className="w-[18%] px-3 py-3 text-start font-medium">{labels.hourlyRate}</th>
<th className="w-[26%] px-3 py-3 text-start font-medium">{labels.totalIncome}</th>
<th className="w-[10%] px-3 py-3 text-start font-medium">{labels.details}</th> <th className="w-[10%] px-3 py-3 text-start font-medium">{labels.details}</th>
</tr> </tr>
</thead> </thead>
@@ -264,6 +277,7 @@ export function ReportsTablePanel({
<td className="px-3 py-3 font-medium text-slate-900 dark:text-slate-100">{formatDisplayDate(day.date, lang)}</td> <td className="px-3 py-3 font-medium text-slate-900 dark:text-slate-100">{formatDisplayDate(day.date, lang)}</td>
<td className="px-3 py-3 text-slate-700 dark:text-slate-300">{localizeDigits(day.billable_duration, lang)}</td> <td className="px-3 py-3 text-slate-700 dark:text-slate-300">{localizeDigits(day.billable_duration, lang)}</td>
<td className="px-3 py-3 text-slate-700 dark:text-slate-300">{localizeDigits(day.non_billable_duration, lang)}</td> <td className="px-3 py-3 text-slate-700 dark:text-slate-300">{localizeDigits(day.non_billable_duration, lang)}</td>
<td className="px-3 py-3 text-slate-700 dark:text-slate-300">{formatHourlyRate(day.latest_hourly_rate, lang)}</td>
<td className="px-3 py-3 text-slate-700 dark:text-slate-300">{formatMoneyTotals(day.income_totals, lang)}</td> <td className="px-3 py-3 text-slate-700 dark:text-slate-300">{formatMoneyTotals(day.income_totals, lang)}</td>
<td className="px-3 py-3"> <td className="px-3 py-3">
<button <button
@@ -300,6 +314,7 @@ export function ReportsTablePanel({
<td className="px-3 py-3">{labels.total}</td> <td className="px-3 py-3">{labels.total}</td>
<td className="px-3 py-3">{localizeDigits(data.summary.billable_duration, lang)}</td> <td className="px-3 py-3">{localizeDigits(data.summary.billable_duration, lang)}</td>
<td className="px-3 py-3">{localizeDigits(data.summary.non_billable_duration, lang)}</td> <td className="px-3 py-3">{localizeDigits(data.summary.non_billable_duration, lang)}</td>
<td className="px-3 py-3">-</td>
<td className="px-3 py-3">{formatMoneyTotals(data.summary.income_totals, lang)}</td> <td className="px-3 py-3">{formatMoneyTotals(data.summary.income_totals, lang)}</td>
<td className="px-3 py-3" /> <td className="px-3 py-3" />
</tr> </tr>

View File

@@ -468,10 +468,11 @@ export const en = {
name: "Name", name: "Name",
clear: "Clear", clear: "Clear",
apply: "Apply", apply: "Apply",
totalHours: "Total hours", totalHours: "Total hours",
billableHours: "Billable hours", billableHours: "Billable hours",
nonBillableHours: "Non-billable hours", nonBillableHours: "Non-billable hours",
totalIncome: "Total income", hourlyRate: "Hourly rate",
totalIncome: "Total income",
chartTitle: "Activity chart", chartTitle: "Activity chart",
totalSeconds: "Total seconds", totalSeconds: "Total seconds",
exportExcel: "Export Excel", exportExcel: "Export Excel",

View File

@@ -464,10 +464,11 @@ export const fa = {
name: "نام", name: "نام",
clear: "پاک کردن", clear: "پاک کردن",
apply: "اعمال", apply: "اعمال",
totalHours: "مجموع ساعت", totalHours: "مجموع ساعت",
billableHours: "ساعات کاری", billableHours: "ساعات کاری",
nonBillableHours: "ساعات غیر کاری", nonBillableHours: "ساعات غیر کاری",
totalIncome: "مجموع درآمد", hourlyRate: "نرخ ساعتی",
totalIncome: "مجموع درآمد",
chartTitle: "نمودار فعالیت", chartTitle: "نمودار فعالیت",
totalSeconds: "مجموع ثانیه", totalSeconds: "مجموع ثانیه",
exportExcel: "خروجی Excel", exportExcel: "خروجی Excel",

View File

@@ -381,6 +381,7 @@ export default function Reports() {
billableHours: t.reports?.billableHours || "Billable hours", billableHours: t.reports?.billableHours || "Billable hours",
nonBillableHours: t.reports?.nonBillableHours || "Non-billable hours", nonBillableHours: t.reports?.nonBillableHours || "Non-billable hours",
totalHours: t.reports?.totalHours || "Total hours", totalHours: t.reports?.totalHours || "Total hours",
hourlyRate: t.reports?.hourlyRate || "Hourly rate",
totalIncome: t.reports?.totalIncome || "Total income", totalIncome: t.reports?.totalIncome || "Total income",
details: t.reports?.details || "Details", details: t.reports?.details || "Details",
total: t.reports?.total || "Total", total: t.reports?.total || "Total",