Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
"locked": "Locked"
},
"placeholders": {
"filterUsers": "Filter users..."
"filterUsers": "Filter users...",
"noUsersFound": "No users found"
}
},
"LogEditPage": {
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
"locked": "鎖定"
},
"placeholders": {
"filterUsers": "篩選使用者..."
"filterUsers": "篩選使用者...",
"noUsersFound": "找不到使用者"
}
},
"LogEditPage": {
Expand Down
53 changes: 16 additions & 37 deletions src/app/logs/LogsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";
import { useState } from "react";
import { useState, useMemo } from "react";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Icon, Badge, ButtonGroup, IconButton, Text, Button, CloseButton, Dialog, HStack, Portal, Stack, ClientOnly, SkeletonText, Pagination, Switch, Combobox, useListCollection, useFilter } from "@chakra-ui/react";
import { Icon, Badge, ButtonGroup, IconButton, Text, Button, CloseButton, Dialog, HStack, Portal, Stack, ClientOnly, SkeletonText, Pagination, Switch } from "@chakra-ui/react";
import { LuArrowDown01, LuArrowUp10, LuLock, LuTimer, LuPen, LuTrash2, LuArrowDownAZ, LuArrowUpZA, LuClipboardPlus } from "react-icons/lu";

import { Tooltip } from "@/components/ui/tooltip";
import UserCombobox from "@/components/UserCombobox";
import GenericTable, { Column } from "@/components/GenericTable";
import GenericClipboard from "@/components/Clipboard";
import TimeLogStatusBadge from "@/components/TimeLogStatusBadge";
Expand Down Expand Up @@ -178,45 +179,23 @@ export default function LogsTable(props: {
});

const [finishedOnly, setFinishedOnly] = useState(true);

const { contains } = useFilter({ sensitivity: "base" });
const { collection: userFilterCollection, filter } = useListCollection({
initialItems: Object.entries(userInfo).map(([id, user]) => ({ value: id, label: user.name })),
filter: contains,
});

const [userFilterSelected, setUserFilterSelected] = useState<string | null>(null);

const userComboboxUsers = useMemo(
() => Object.entries(userInfo).map(([id, user]) => ({ id, name: user.name })),
[userInfo]
);

const topRightElement = showAdminActions && <HStack flexWrap="wrap" w="full">
<Combobox.Root
collection={userFilterCollection}
onInputValueChange={(e) => filter(e.inputValue)}
width={{ base: "full", md: "200px" }}
<UserCombobox
users={userComboboxUsers}
value={userFilterSelected ?? ""}
onValueChange={(v) => setUserFilterSelected(v || null)}
placeholder={t("placeholders.filterUsers")}
emptyText={t("placeholders.noUsersFound")}
size="xs"
value={userFilterSelected ? [userFilterSelected] : []}
onValueChange={(e) => e.value ? setUserFilterSelected(e.value[0]) : setUserFilterSelected(null)}
>
<Combobox.Control>
<Combobox.Input placeholder={t("placeholders.filterUsers")} />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>No items found</Combobox.Empty>
{userFilterCollection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator />
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
width={{ base: "full", md: "200px" }}
/>

<HStack justifyContent="space-around">
<Button size="sm" variant="ghost" asChild>
Expand Down
44 changes: 10 additions & 34 deletions src/app/report/user/UserReportContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import { useState, useCallback, useEffect, useMemo } from "react";
import {
Box, HStack, Stat, Heading, Center, Spinner, IconButton,
Table, Portal, Select, createListCollection, SegmentGroup, Text,
Table, SegmentGroup, Text,
} from "@chakra-ui/react";
import { LuArrowLeft, LuArrowRight, LuRefreshCcw } from "react-icons/lu";

import StatRangeSelector from "@/components/StatRangeSelector";
import UserCombobox from "@/components/UserCombobox";
import StatValueTextDuration from "@/components/StatValueTextDuration";
import Heatmap from "@/components/HeatMap";
import { getUserReportData, UserReportData } from "@/lib/data/report";
Expand Down Expand Up @@ -56,10 +57,6 @@ export default function UserReportContent({
const [selectedYear, setSelectedYear] = useState(() => new Date().getFullYear());
const [groupBy, setGroupBy] = useState<GroupBy>("day");

const userCollection = useMemo(() => createListCollection({
items: users.map(u => ({ label: u.name, value: u.id })),
}), [users]);

const handleRefresh = useCallback(() => {
if (!selectedUserId) return;
setLoading(true);
Expand Down Expand Up @@ -205,36 +202,15 @@ export default function UserReportContent({
<HStack justify="space-between" mb={4} flexWrap="wrap" gap={2}>
<Heading as="h2" size="2xl">User Report</Heading>
<HStack gap={2} flexWrap="wrap">
<Select.Root
collection={userCollection}
<UserCombobox
users={users}
value={selectedUserId}
onValueChange={setSelectedUserId}
placeholder="Select user"
size="xs"
w="auto"
minW="44"
value={selectedUserId ? [selectedUserId] : []}
onValueChange={({ value }) => setSelectedUserId(value[0] ?? "")}
>
<Select.HiddenSelect />
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select user" />
</Select.Trigger>
<Select.IndicatorGroup>
<Select.Indicator />
</Select.IndicatorGroup>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{userCollection.items.map(item => (
<Select.Item item={item} key={item.value}>
{item.label}
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Content>
</Select.Positioner>
</Portal>
</Select.Root>
width="auto"
minWidth="44"
/>

<StatRangeSelector
onSelectAction={(start, end) => setDateRange([start, end])}
Expand Down
75 changes: 75 additions & 0 deletions src/components/UserCombobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";

import { useEffect, useMemo } from "react";
import { Combobox, Portal, useFilter, useListCollection } from "@chakra-ui/react";

interface UserComboboxProps {
users: { id: string; name: string }[];
value: string;
onValueChange: (value: string) => void;
placeholder?: string;
emptyText?: string;
size?: "xs" | "sm" | "md";
width?: string | object;
minWidth?: string;
}

export default function UserCombobox({
users,
value,
onValueChange,
placeholder = "Select user",
emptyText = "No users found",
size = "xs",
width,
minWidth,
}: UserComboboxProps) {
const { contains } = useFilter({ sensitivity: "base" });

const items = useMemo(
() => users.map((u) => ({ label: u.name, value: u.id })),
[users]
);

const { collection, filter, set } = useListCollection({
initialItems: items,
filter: contains,
});

useEffect(() => {
set(items);
}, [set, items]);

return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
value={value ? [value] : []}
onValueChange={(e) => onValueChange(e.value[0] ?? "")}
size={size}
width={width}
minWidth={minWidth}
>
<Combobox.Control>
<Combobox.Input placeholder={placeholder} />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>{emptyText}</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator />
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
);
}
Loading