Skip to content

Commit

Permalink
[Feature] Add time limits report filters (#3392)
Browse files Browse the repository at this point in the history
* feat: add reports filters

* fix typo in ITimeReportTableByMemberProps
  • Loading branch information
CREDO23 authored Dec 8, 2024
1 parent 88fc30c commit 94ec0de
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { formatIntegerToHour, formatTimeString } from '@/app/helpers';
import { ProgressBar } from '@/lib/components';

export type WeeklyLimitTableDataType = {
member: string;
indexValue: string;
timeSpent: number;
limit: number;
percentageUsed: number;
Expand All @@ -35,12 +35,18 @@ export type WeeklyLimitTableDataType = {
* @component
* @param {Object} props - The component props.
* @param {WeeklyLimitTableDataType[]} props.data - Array of data objects containing weekly time usage information.
* @param {boolean} props.showHeader - If false, hide the header.
*
* @returns {JSX.Element} A table showing member-wise weekly time limits, usage, and remaining time.
*
*/

export function DataTableWeeklyLimits(props: { data: WeeklyLimitTableDataType[] }) {
export function DataTableWeeklyLimits(props: {
data: WeeklyLimitTableDataType[];
indexTitle: string;
showHeader?: boolean;
}) {
const { data, indexTitle, showHeader = true } = props;
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
Expand Down Expand Up @@ -69,9 +75,9 @@ export function DataTableWeeklyLimits(props: { data: WeeklyLimitTableDataType[]
enableHiding: false
},
{
accessorKey: 'member',
header: () => <div className="">{t('common.MEMBER')}</div>,
cell: ({ row }) => <div className="capitalize">{row.getValue('member')}</div>
accessorKey: 'indexValue',
header: () => <div className="">{indexTitle}</div>,
cell: ({ row }) => <div className="capitalize">{row.getValue('indexValue')}</div>
},
{
accessorKey: 'timeSpent',
Expand Down Expand Up @@ -117,7 +123,7 @@ export function DataTableWeeklyLimits(props: { data: WeeklyLimitTableDataType[]
];

const table = useReactTable({
data: props.data,
data: data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
Expand All @@ -140,21 +146,27 @@ export function DataTableWeeklyLimits(props: { data: WeeklyLimitTableDataType[]
{table?.getRowModel()?.rows.length ? (
<div className="rounded-md">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
{showHeader && (
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead className=" capitalize" key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
)}

<TableBody>
{table?.getRowModel()?.rows.length ? (
table?.getRowModel().rows.map((row) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,120 @@
import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectItem } from '@components/ui/select';
import { DottedLanguageObjectStringPaths, useTranslations } from 'next-intl';
import { useMemo, useState, useCallback } from 'react';
import { Fragment } from 'react';
import { Listbox, Transition } from '@headlessui/react';
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';
import { Badge } from '@components/ui/badge';

export type TGroupByOption = 'date' | 'week' | 'member';

interface IProps {
onChange: (OPTION: TGroupByOption) => void;
defaultValue: TGroupByOption;
onChange: (options: TGroupByOption[]) => void;
defaultValues: TGroupByOption[];
}

/**
* GroupBySelect component provides a dropdown selector for grouping data by day, week, or member.
* [GroupBySelect] - A multi-select component that allows users to choose up to two options
* from a predefined list ("date", "Week", "Member").
*
* Rules enforced:
* - Only two options can be selected at a time.
* - "date" and "Week" cannot be selected together.
* - At least one option must remain selected.
*
* @component
* @param {IProps} props - The component props.
* @param {(option: TGroupByOption) => void} props.onChange - Function to handle changes in the selected grouping option.
* @param {TGroupByOption} props.defaultValue - The initial grouping option.
* @param {Object} props - The properties of the component.
* @param {TGroupByOption[]} props.defaultValues - Initial options selected when the component is rendered.
* @param {Function} props.onChange - Callback function invoked when the selection changes.
*
* @returns {JSX.Element} A custom multi-select dropdown with badges representing selected items.
*
* @returns {JSX.Element} A dropdown for selecting a grouping option.
*/

export type TGroupByOption = 'Day' | 'Week' | 'Member';
export function GroupBySelect(props: IProps) {
const { onChange, defaultValue } = props;
const options = useMemo<TGroupByOption[]>(() => ['Day', 'Week', 'Member'], []);
const [selected, setSelected] = useState<TGroupByOption>(defaultValue);
export function GroupBySelect({ defaultValues, onChange }: IProps) {
const options = useMemo<TGroupByOption[]>(() => ['date', 'week', 'member'], []);
const [selected, setSelected] = useState<TGroupByOption[]>(defaultValues);
const t = useTranslations();

const handleChange = useCallback(
(option: TGroupByOption) => {
setSelected(option);
onChange(option);
(options: TGroupByOption[]) => {
// Ensure 'date' and 'Week' cannot be selected together
let updatedOptions = options;

if (options.includes('date') && options.includes('week')) {
// If 'date' is newly selected, remove 'Week'
if (selected.includes('week')) {
updatedOptions = options.filter((option) => option !== 'week');
}
// If 'Week' is newly selected, remove 'date'
else if (selected.includes('date')) {
updatedOptions = options.filter((option) => option !== 'date');
}
}

setSelected(updatedOptions);
onChange(updatedOptions);
},
[onChange]
[onChange, selected]
);

return (
<Select value={selected} onValueChange={handleChange}>
<SelectTrigger className="w-36 overflow-hidden h-[2.2rem] text-clip border border-gray-200 dark:border-gray-700 bg-white dark:bg-dark--theme-light focus:ring-2 focus:ring-transparent">
<SelectValue placeholder="Group by" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{options.map((option) => {
return (
<SelectItem key={option} value={option}>
<Listbox multiple value={selected} onChange={handleChange}>
<div className="relative h-[2.2rem] w-[12rem]">
<Listbox.Button className="relative w-full h-full cursor-default rounded-lg bg-white py-2 pl-1 pr-6 text-left border focus:outline-none focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm">
<div className=" items-center w-full h-full flex gap-1">
{selected.map((option) => (
<Badge
key={option}
className=" capitalize rounded flex gap-1 font-light"
variant={'outline'}
>
{t(
`common.${(option == 'Day' ? 'Date' : option).toUpperCase()}` as DottedLanguageObjectStringPaths
`common.${(option == 'date' ? 'date' : option).toUpperCase()}` as DottedLanguageObjectStringPaths
)}
</Badge>
))}
</div>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full z-[999] overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
{options.map((option, index) => (
<Listbox.Option
disabled={
// Prevents users from clearing all selections, ensuring at least one option is always selected.
selected.includes(option) && selected.length == 1
}
key={index}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-primary/10 text-primary' : 'text-gray-900'
}`
}
value={option}
>
{({ selected }) => (
<>
<span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
{option}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-primary">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</SelectItem>
);
})}
</SelectGroup>
</SelectContent>
</Select>
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
);
}
Loading

0 comments on commit 94ec0de

Please sign in to comment.