Update
This commit is contained in:
1 parent
62d8f537a8
commit
8a4cf3fc9b
4 files changed
+354
-247
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { ArrowDown, ArrowUp, CheckCircle2, ChevronsUpDown, Eye, EyeOff, MoreHorizontal, Pencil, Play, Plus, RotateCcw, Save, Search, SlidersHorizontal, Star, Trash2 } from "lucide-react";
|
||||
import { ArrowDown, ArrowUp, CheckCircle2, Eye, EyeOff, MoreHorizontal, Pencil, Play, Plus, RotateCcw, Save, Search, SlidersHorizontal, Star, Trash2 } from "lucide-react";
|
||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -9,10 +9,10 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { HelpLink } from "@/components/HelpLink";
|
||||
import { MultiFilterDropdown, type MultiFilterOption } from "@/components/MultiFilterDropdown";
|
||||
import { OrganizationSelect } from "@/components/OrganizationSelect";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { addFavoriteTicket, createProject, getProjects, hideProject, lookupTicket, unhideProject, updateProject, updateProjectStatus } from "../api";
|
||||
@@ -60,7 +60,7 @@ type ProjectTeamspaceEntryForm = {
|
||||
|
||||
type ProjectSortBy = "updated" | "ticket" | "title" | "organization" | "status" | "zammadStatus" | "zammadPriority" | "consumption" | "consumptionPercent" | "remaining" | "budget";
|
||||
type ProjectSortDirection = "asc" | "desc";
|
||||
type ProjectFilterOption = { value: string; label: string };
|
||||
type ProjectFilterOption = MultiFilterOption;
|
||||
|
||||
type ProjectViewSettings = {
|
||||
query: string;
|
||||
@@ -356,86 +356,6 @@ function SortDirectionIcon({ direction }: { direction: ProjectSortDirection }) {
|
||||
return direction === "asc" ? <ArrowUp className="size-3.5" /> : <ArrowDown className="size-3.5" />;
|
||||
}
|
||||
|
||||
function ProjectFilterDropdown({
|
||||
values,
|
||||
options,
|
||||
onChange,
|
||||
placeholder = "Alle Filter",
|
||||
countLabel = "Filter",
|
||||
emptyLabel = "Keine Filter."
|
||||
}: {
|
||||
values: string[];
|
||||
options: ProjectFilterOption[];
|
||||
onChange: (values: string[]) => void;
|
||||
placeholder?: string;
|
||||
countLabel?: string;
|
||||
emptyLabel?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const normalizedSearch = search.trim().toLocaleLowerCase("de-DE");
|
||||
const visibleOptions = normalizedSearch
|
||||
? options.filter((option) => option.label.toLocaleLowerCase("de-DE").includes(normalizedSearch) || option.value.toLocaleLowerCase("de-DE").includes(normalizedSearch))
|
||||
: options;
|
||||
const activeLabels = values.map((value) => options.find((option) => option.value === value)?.label).filter(Boolean);
|
||||
|
||||
function toggle(value: string) {
|
||||
onChange(values.includes(value) ? values.filter((filter) => filter !== value) : [...values, value]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" role="combobox" aria-expanded={open} className="h-8 w-full justify-between px-2 text-xs font-normal">
|
||||
<span className={cn("truncate", values.length === 0 && "text-muted-foreground")}>
|
||||
{activeLabels.length === 0 ? placeholder : activeLabels.length === 1 ? activeLabels[0] : `${activeLabels.length} ${countLabel}`}
|
||||
</span>
|
||||
<ChevronsUpDown className="size-3.5 opacity-60" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-[--radix-popover-trigger-width] gap-1 p-1.5">
|
||||
<Input className="h-7 text-xs" value={search} placeholder="Suchen..." onChange={(event) => setSearch(event.currentTarget.value)} />
|
||||
<div className="max-h-64 overflow-y-auto pr-1">
|
||||
{visibleOptions.length === 0 ? (
|
||||
<p className="px-2 py-3 text-center text-xs text-muted-foreground">{emptyLabel}</p>
|
||||
) : (
|
||||
<div className="space-y-0.5">
|
||||
{visibleOptions.map((option) => {
|
||||
const checked = values.includes(option.value);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={checked}
|
||||
className="flex h-7 w-full min-w-0 cursor-default items-center gap-2 rounded-md px-2 text-left text-xs outline-none hover:bg-muted focus-visible:bg-muted"
|
||||
onClick={() => toggle(option.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
toggle(option.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox checked={checked} tabIndex={-1} className="pointer-events-none" />
|
||||
<span className="truncate">{option.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{values.length > 0 ? (
|
||||
<Button type="button" size="sm" variant="ghost" className="h-6 justify-start px-2 text-[11px]" onClick={() => onChange([])}>
|
||||
Zurücksetzen
|
||||
</Button>
|
||||
) : null}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function parseHoursInput(value: string) {
|
||||
const trimmed = value.trim();
|
||||
|
||||
@@ -1384,7 +1304,7 @@ export function ProjectsPage({ onNavigate, onStartTimer }: ProjectsPageProps) {
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">Filter</label>
|
||||
<ProjectFilterDropdown
|
||||
<MultiFilterDropdown
|
||||
values={viewSettings.filterValues}
|
||||
options={projectStaticFilterOptions}
|
||||
onChange={(filterValues) => setViewSettings((current) => ({ ...current, filterValues }))}
|
||||
@@ -1392,7 +1312,7 @@ export function ProjectsPage({ onNavigate, onStartTimer }: ProjectsPageProps) {
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">Organisationen</label>
|
||||
<ProjectFilterDropdown
|
||||
<MultiFilterDropdown
|
||||
values={viewSettings.organizationValues}
|
||||
options={organizationOptions}
|
||||
onChange={(organizationValues) => setViewSettings((current) => ({ ...current, organizationValues }))}
|
||||
|
||||
Reference in new issue
Block a user