Swap the native <select> in CategoryZoomHeader for the reusable CategoryCombobox. Enhances the combobox with ARIA compliance (combobox, listbox, option roles + aria-expanded, aria-controls, aria-activedescendant) and hierarchy indentation based on parent_id depth. Adds reports.category.searchPlaceholder in FR/EN. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { getAllCategoriesWithCounts } from "../../services/categoryService";
|
|
import CategoryCombobox from "../shared/CategoryCombobox";
|
|
import type { Category } from "../../shared/types";
|
|
|
|
export interface CategoryZoomHeaderProps {
|
|
categoryId: number | null;
|
|
includeSubcategories: boolean;
|
|
onCategoryChange: (id: number | null) => void;
|
|
onIncludeSubcategoriesChange: (flag: boolean) => void;
|
|
}
|
|
|
|
export default function CategoryZoomHeader({
|
|
categoryId,
|
|
includeSubcategories,
|
|
onCategoryChange,
|
|
onIncludeSubcategoriesChange,
|
|
}: CategoryZoomHeaderProps) {
|
|
const { t } = useTranslation();
|
|
const [categories, setCategories] = useState<Category[]>([]);
|
|
|
|
useEffect(() => {
|
|
getAllCategoriesWithCounts()
|
|
.then((rows) =>
|
|
setCategories(
|
|
rows.map((r) => ({
|
|
id: r.id,
|
|
name: r.name,
|
|
parent_id: r.parent_id ?? undefined,
|
|
color: r.color ?? undefined,
|
|
icon: r.icon ?? undefined,
|
|
type: r.type,
|
|
is_active: r.is_active,
|
|
is_inputable: r.is_inputable,
|
|
sort_order: r.sort_order,
|
|
created_at: "",
|
|
})),
|
|
),
|
|
)
|
|
.catch(() => setCategories([]));
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex flex-col sm:flex-row sm:items-center gap-3 bg-[var(--card)] border border-[var(--border)] rounded-xl p-4">
|
|
<label className="flex flex-col gap-1 flex-1 min-w-0">
|
|
<span className="text-xs font-medium text-[var(--muted-foreground)] uppercase tracking-wide">
|
|
{t("reports.category.selectCategory")}
|
|
</span>
|
|
<CategoryCombobox
|
|
categories={categories}
|
|
value={categoryId}
|
|
onChange={onCategoryChange}
|
|
placeholder={t("reports.category.searchPlaceholder")}
|
|
ariaLabel={t("reports.category.selectCategory")}
|
|
/>
|
|
</label>
|
|
<label className="inline-flex items-center gap-2 text-sm">
|
|
<input
|
|
type="checkbox"
|
|
checked={includeSubcategories}
|
|
onChange={(e) => onIncludeSubcategoriesChange(e.target.checked)}
|
|
className="accent-[var(--primary)]"
|
|
/>
|
|
<span>
|
|
{includeSubcategories
|
|
? t("reports.category.includeSubcategories")
|
|
: t("reports.category.directOnly")}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
);
|
|
}
|