fix(reports): add controlled fetching + change chart buckets to localized weekday names

This commit is contained in:
2026-04-28 11:03:51 +03:30
parent 581cfab1ac
commit 599e25e836
4 changed files with 81 additions and 34 deletions

View File

@@ -105,7 +105,13 @@ const getPersianDateParts = (value: Date) => {
};
};
const getDailyAxisLabel = (date: Date, lang: "en" | "fa") => {
const getDailyAxisLabel = (date: Date, lang: "en" | "fa", period: string) => {
if (period === "this_week") {
return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR-u-ca-persian" : "en-US", {
weekday: "short",
}).format(date);
}
if (lang === "fa") {
return toPersianDigits(String(getPersianDateParts(date).day));
}
@@ -140,7 +146,13 @@ const getMonthlyTooltipLabel = (bucketKey: string, lang: "en" | "fa") => {
);
};
const buildDailyBuckets = (fromDate: string, toDate: string, existing: ReportChartBucket[], lang: "en" | "fa") => {
const buildDailyBuckets = (
fromDate: string,
toDate: string,
existing: ReportChartBucket[],
lang: "en" | "fa",
period: string,
) => {
const map = new Map(existing.map((bucket) => [bucket.bucket_key, bucket]));
const result: ReportChartBucket[] = [];
const cursor = parseIsoDate(fromDate);
@@ -152,7 +164,7 @@ const buildDailyBuckets = (fromDate: string, toDate: string, existing: ReportCha
result.push(
existingBucket ?? {
bucket_key: key,
bucket_label: getDailyAxisLabel(cursor, lang),
bucket_label: getDailyAxisLabel(cursor, lang, period),
total_seconds: 0,
total_duration: "00:00:00",
},
@@ -162,7 +174,7 @@ const buildDailyBuckets = (fromDate: string, toDate: string, existing: ReportCha
return result.map((bucket) => ({
...bucket,
bucket_label: getDailyAxisLabel(parseIsoDate(bucket.bucket_key), lang),
bucket_label: getDailyAxisLabel(parseIsoDate(bucket.bucket_key), lang, period),
}));
};
@@ -282,7 +294,7 @@ export function ReportsChartPanel({
const buckets = useMonthlyBuckets
? buildMonthlyBuckets(data.scope.from_date, data.scope.to_date, data.buckets, lang)
: buildDailyBuckets(data.scope.from_date, data.scope.to_date, data.buckets, lang);
: buildDailyBuckets(data.scope.from_date, data.scope.to_date, data.buckets, lang, data.scope.period);
const chartMinWidth = Math.max(640, buckets.length * (useMonthlyBuckets ? 92 : 44));
const interval = useMonthlyBuckets ? 0 : buckets.length > 20 ? Math.ceil(buckets.length / 10) - 1 : 0;

View File

@@ -116,6 +116,7 @@ export function ReportsFilterBar({
tags,
users,
canSelectUsers,
isLoadingUsers,
labels,
}: {
value: ReportsFilterDraft;
@@ -125,6 +126,7 @@ export function ReportsFilterBar({
tags: Tag[];
users: WorkspaceMembership[];
canSelectUsers: boolean;
isLoadingUsers: boolean;
labels: Record<string, string>;
}) {
const [draft, setDraft] = useState(value);
@@ -200,30 +202,36 @@ export function ReportsFilterBar({
</>
) : null}
{canSelectUsers ? (
{canSelectUsers || isLoadingUsers ? (
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
{labels.user}
</label>
<SearchableSelect
value={draft.user}
onChange={(user) => setDraft((current) => ({ ...current, user }))}
options={[
{ value: "", label: labels.allUsers },
...users.map((membership) => ({
value: membership.user.id,
label:
`${membership.user.first_name || ""} ${membership.user.last_name || ""}`.trim() ||
membership.user.email ||
membership.user.id,
searchText: membership.user.mobile || "",
})),
]}
placeholder={labels.allUsers}
searchPlaceholder={labels.searchUsers}
className="w-full"
buttonClassName="h-10 rounded-2xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"
/>
{isLoadingUsers ? (
<div className="flex h-10 w-full items-center rounded-2xl border border-slate-200 bg-slate-50 px-3 text-sm text-slate-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{labels.searchUsers || "Loading users..."}
</div>
) : (
<SearchableSelect
value={draft.user}
onChange={(user) => setDraft((current) => ({ ...current, user }))}
options={[
{ value: "", label: labels.allUsers },
...users.map((membership) => ({
value: membership.user.id,
label:
`${membership.user.first_name || ""} ${membership.user.last_name || ""}`.trim() ||
membership.user.email ||
membership.user.id,
searchText: membership.user.mobile || "",
})),
]}
placeholder={labels.allUsers}
searchPlaceholder={labels.searchUsers}
className="w-full"
buttonClassName="h-10 rounded-2xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"
/>
)}
</div>
) : null}