feat(timesheet): add searchable tag selectors
This commit is contained in:
@@ -531,9 +531,10 @@ function TagMultiSelect({
|
||||
title: string;
|
||||
compact?: boolean;
|
||||
portalOwnerId?: string;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
|
||||
@@ -585,11 +586,21 @@ function TagMultiSelect({
|
||||
window.removeEventListener("resize", closeOnViewportChange);
|
||||
window.removeEventListener("scroll", closeOnViewportChange, true);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const selectedLabels = tags.filter((tag) => selectedTags.includes(tag.id)).map((tag) => tag.name);
|
||||
const joinedSelectedLabels = selectedLabels.join(" | ");
|
||||
const buttonLabel = compact
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSearchQuery("");
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const selectedLabels = tags.filter((tag) => selectedTags.includes(tag.id)).map((tag) => tag.name);
|
||||
const joinedSelectedLabels = selectedLabels.join(" | ");
|
||||
const normalizedSearch = searchQuery.trim().toLowerCase();
|
||||
const filteredTags = normalizedSearch
|
||||
? tags.filter((tag) => tag.name.toLowerCase().includes(normalizedSearch))
|
||||
: tags;
|
||||
const buttonLabel = compact
|
||||
? selectedTags.length > 0
|
||||
? joinedSelectedLabels
|
||||
: ""
|
||||
@@ -629,19 +640,32 @@ function TagMultiSelect({
|
||||
|
||||
{isOpen && (
|
||||
createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
style={dropdownStyle}
|
||||
data-entry-editor-owner={portalOwnerId}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-2 shadow-xl dark:border-slate-700 dark:bg-slate-800"
|
||||
>
|
||||
{tags.length === 0 ? (
|
||||
<p className="px-2 py-2 text-sm text-slate-500 dark:text-slate-400">{emptyHint}</p>
|
||||
) : (
|
||||
<div className="max-h-56 space-y-1 overflow-y-auto">
|
||||
{tags.map((tag) => {
|
||||
const selected = selectedTags.includes(tag.id);
|
||||
return (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
style={dropdownStyle}
|
||||
data-entry-editor-owner={portalOwnerId}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-2 shadow-xl dark:border-slate-700 dark:bg-slate-800"
|
||||
>
|
||||
{tags.length === 0 ? (
|
||||
<p className="px-2 py-2 text-sm text-slate-500 dark:text-slate-400">{emptyHint}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="border-b border-slate-200 p-2 dark:border-slate-700">
|
||||
<div className="relative">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
placeholder="Search tags..."
|
||||
className="h-8 w-full rounded-md border border-slate-200 bg-slate-50 pl-8 pr-2 text-xs text-slate-900 outline-none transition focus:border-sky-400 focus:bg-white focus:ring-2 focus:ring-sky-500/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white dark:focus:border-sky-500 dark:focus:bg-slate-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-72 space-y-1 overflow-y-auto p-2">
|
||||
{filteredTags.map((tag) => {
|
||||
const selected = selectedTags.includes(tag.id);
|
||||
return (
|
||||
<button
|
||||
key={tag.id}
|
||||
type="button"
|
||||
@@ -2306,8 +2330,8 @@ export default function Timesheet() {
|
||||
client: t.projects?.clientLabel || "Client",
|
||||
tags: t.tags?.title || "Tags",
|
||||
clear: extendedTimesheet.clearFilters || "Clear filters",
|
||||
customFrom: extendedTimesheet.customFromLabel || "From",
|
||||
customTo: extendedTimesheet.customToLabel || "To",
|
||||
customFrom: extendedTimesheet.customFromLabel || "From date",
|
||||
customTo: extendedTimesheet.customToLabel || "To date",
|
||||
allClients: extendedTimesheet.allClientsLabel || "All clients",
|
||||
allProjects: extendedTimesheet.allProjectsLabel || "All projects",
|
||||
allTags: extendedTimesheet.allTagsLabel || "All tags",
|
||||
|
||||
Reference in New Issue
Block a user