refactor: filter components (#2359)

* fix: calendar layout dividers

* refactor: filter selection components

* fix: dropdown closing after selection

* refactor: filters components
This commit is contained in:
Aaryan Khandelwal 2023-10-04 12:04:55 +05:30 committed by GitHub
parent 41fd9ce6e8
commit 7be038ac5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 280 additions and 326 deletions

View File

@ -6,7 +6,7 @@ import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues";
import { AppliedFiltersRoot, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues";
export const AllViews: React.FC = observer(() => {
const router = useRouter();
@ -35,7 +35,7 @@ export const AllViews: React.FC = observer(() => {
return (
<div className="relative w-full h-full flex flex-col overflow-auto">
<AppliedFiltersList />
<AppliedFiltersRoot />
{activeLayout === "kanban" ? (
<KanBanLayout />
) : activeLayout === "calendar" ? (

View File

@ -38,7 +38,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
<CalendarWeekHeader />
<div className="h-full w-full overflow-y-auto">
{calendarLayout === "month" ? (
<div className="h-full w-full grid grid-cols-1">
<div className="h-full w-full grid grid-cols-1 divide-y-[0.5px] divide-custom-border-200">
{allWeeksOfActiveMonth &&
Object.values(allWeeksOfActiveMonth).map((week: ICalendarWeek, weekIndex) => (
<CalendarWeekDays key={weekIndex} week={week} issues={issues} />

View File

@ -4,9 +4,10 @@ import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { ChevronLeft, ChevronRight } from "lucide-react";
// constants
import { MONTHS_LIST } from "constants/calendar";
import { ChevronLeft, ChevronRight } from "lucide-react";
export const CalendarMonthsDropdown: React.FC = observer(() => {
const { calendar: calendarStore, issueFilter: issueFilterStore } = useMobxStore();
@ -46,73 +47,63 @@ export const CalendarMonthsDropdown: React.FC = observer(() => {
return (
<Popover className="relative">
{({ close }) => (
<>
<Popover.Button className="outline-none text-xl font-semibold" disabled={calendarLayout === "week"}>
{calendarLayout === "month"
? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}`
: getWeekLayoutHeader()}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel>
<div className="absolute left-0 z-10 mt-1 bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded w-56 p-3 divide-y divide-custom-border-200">
<div className="flex items-center justify-between gap-2 pb-3">
<button
type="button"
className="grid place-items-center"
onClick={() => {
const previousYear = new Date(activeMonthDate.getFullYear() - 1, activeMonthDate.getMonth(), 1);
handleDateChange(previousYear);
close();
}}
>
<ChevronLeft size={14} />
</button>
<span className="text-xs">{activeMonthDate.getFullYear()}</span>
<button
type="button"
className="grid place-items-center"
onClick={() => {
const nextYear = new Date(activeMonthDate.getFullYear() + 1, activeMonthDate.getMonth(), 1);
handleDateChange(nextYear);
close();
}}
>
<ChevronRight size={14} />
</button>
</div>
<div className="grid grid-cols-4 gap-4 items-stretch justify-items-stretch pt-3">
{Object.values(MONTHS_LIST).map((month, index) => (
<button
key={month.shortTitle}
type="button"
className="text-xs hover:bg-custom-background-80 rounded py-0.5"
onClick={() => {
const newDate = new Date(activeMonthDate.getFullYear(), index, 1);
handleDateChange(newDate);
close();
}}
>
{month.shortTitle}
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
<Popover.Button className="outline-none text-xl font-semibold" disabled={calendarLayout === "week"}>
{calendarLayout === "month"
? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}`
: getWeekLayoutHeader()}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel>
<div className="absolute left-0 z-10 mt-1 bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded w-56 p-3 divide-y divide-custom-border-200">
<div className="flex items-center justify-between gap-2 pb-3">
<button
type="button"
className="grid place-items-center"
onClick={() => {
const previousYear = new Date(activeMonthDate.getFullYear() - 1, activeMonthDate.getMonth(), 1);
handleDateChange(previousYear);
}}
>
<ChevronLeft size={14} />
</button>
<span className="text-xs">{activeMonthDate.getFullYear()}</span>
<button
type="button"
className="grid place-items-center"
onClick={() => {
const nextYear = new Date(activeMonthDate.getFullYear() + 1, activeMonthDate.getMonth(), 1);
handleDateChange(nextYear);
}}
>
<ChevronRight size={14} />
</button>
</div>
<div className="grid grid-cols-4 gap-4 items-stretch justify-items-stretch pt-3">
{Object.values(MONTHS_LIST).map((month, index) => (
<button
key={month.shortTitle}
type="button"
className="text-xs hover:bg-custom-background-80 rounded py-0.5"
onClick={() => {
const newDate = new Date(activeMonthDate.getFullYear(), index, 1);
handleDateChange(newDate);
}}
>
{month.shortTitle}
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
});

View File

@ -86,7 +86,7 @@ export const CalendarHeader: React.FC = observer(() => {
<div className="flex items-center gap-1.5">
<button
type="button"
className="px-2.5 py-1 text-xs bg-custom-background-80 rounded font-medium"
className="px-2.5 py-1 text-xs bg-custom-background-80 rounded font-medium text-custom-text-200 hover:text-custom-text-100"
onClick={handleToday}
>
Today

View File

@ -27,9 +27,9 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
return (
<div
className={`grid divide-x-[0.5px] divide-y-[0.5px] divide-custom-border-200 ${
showWeekends ? "grid-cols-7" : "grid-cols-5"
} ${calendarLayout === "month" ? "" : "h-full"}`}
className={`grid divide-x-[0.5px] divide-custom-border-200 ${showWeekends ? "grid-cols-7" : "grid-cols-5"} ${
calendarLayout === "month" ? "" : "h-full"
}`}
>
{Object.values(week).map((date: ICalendarDate) => {
if (!showWeekends && (date.date.getDay() === 0 || date.date.getDay() === 6)) return null;

View File

@ -1,8 +1,5 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import {
AppliedDateFilters,
@ -17,65 +14,19 @@ import { X } from "lucide-react";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { IIssueFilterOptions } from "types";
import { IIssueFilterOptions, IIssueLabels, IStateResponse, IUserLite } from "types";
export const AppliedFiltersList: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
type Props = {
appliedFilters: IIssueFilterOptions;
handleClearAllFilters: () => void;
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
labels: IIssueLabels[] | undefined;
members: IUserLite[] | undefined;
states: IStateResponse | undefined;
};
const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();
const userFilters = issueFilterStore.userFilters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => {
if (!value) return;
if (Array.isArray(value) && value.length === 0) return;
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;
// remove all values of the key if value is null
if (!value) {
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: null,
},
});
return;
}
// remove the passed value from the key
let newValues = issueFilterStore.userFilters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: newValues,
},
});
};
const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: { ...newFilters },
});
};
// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, states } = props;
return (
<div className="flex items-stretch gap-2 flex-wrap bg-custom-background-100 p-4">
@ -91,7 +42,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{(filterKey === "assignees" || filterKey === "created_by" || filterKey === "subscriber") && (
<AppliedMembersFilters
handleRemove={(val) => handleRemoveFilter(filterKey, val)}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={members}
values={value}
/>
)}
@ -101,7 +52,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{filterKey === "labels" && (
<AppliedLabelsFilters
handleRemove={(val) => handleRemoveFilter("labels", val)}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
labels={labels}
values={value}
/>
)}
@ -111,7 +62,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{filterKey === "state" && (
<AppliedStateFilters
handleRemove={(val) => handleRemoveFilter("state", val)}
states={projectStore.states?.[projectId?.toString() ?? ""]}
states={states}
values={value}
/>
)}

View File

@ -3,5 +3,6 @@ export * from "./filters-list";
export * from "./label";
export * from "./members";
export * from "./priority";
export * from "./root";
export * from "./state";
export * from "./state-group";

View File

@ -0,0 +1,79 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";
export const AppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();
const userFilters = issueFilterStore.userFilters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => {
if (!value) return;
if (Array.isArray(value) && value.length === 0) return;
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;
// remove all values of the key if value is null
if (!value) {
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: null,
},
});
return;
}
// remove the passed value from the key
let newValues = issueFilterStore.userFilters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: newValues,
},
});
};
const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: { ...newFilters },
});
};
// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;
return (
<AppliedFiltersList
appliedFilters={appliedFilters}
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]}
/>
);
});

View File

@ -1,34 +1,30 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Avatar, Loader } from "components/ui";
// types
import { IUserLite } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
projectId: string;
members: IUserLite[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterAssignees: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props;
export const FilterAssignees: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, members, searchQuery, viewButtons } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { project: projectStore } = store;
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) =>
member.member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
const filteredOptions = members?.filter((member) =>
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
@ -45,11 +41,11 @@ export const FilterAssignees: React.FC<Props> = observer((props) => {
<>
{filteredOptions.slice(0, itemsToRender).map((member) => (
<FilterOption
key={`assignees-${member?.member?.id}`}
isChecked={appliedFilters?.includes(member.member?.id) ? true : false}
onClick={() => handleUpdate(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
key={`assignees-${member.id}`}
isChecked={appliedFilters?.includes(member.id) ? true : false}
onClick={() => handleUpdate(member.id)}
icon={<Avatar user={member} height="18px" width="18px" />}
title={member.display_name}
/>
))}
{viewButtons}
@ -68,4 +64,4 @@ export const FilterAssignees: React.FC<Props> = observer((props) => {
)}
</>
);
});
};

View File

@ -1,34 +1,30 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Avatar, Loader } from "components/ui";
// types
import { IUserLite } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
projectId: string;
members: IUserLite[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterCreatedBy: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props;
export const FilterCreatedBy: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, members, searchQuery, viewButtons } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { project: projectStore } = store;
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) =>
member.member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
const filteredOptions = members?.filter((member) =>
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
@ -45,11 +41,11 @@ export const FilterCreatedBy: React.FC<Props> = observer((props) => {
<>
{filteredOptions.slice(0, itemsToRender).map((member) => (
<FilterOption
key={`created-by-${member.member?.id}`}
isChecked={appliedFilters?.includes(member.member?.id) ? true : false}
onClick={() => handleUpdate(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
key={`created-by-${member.id}`}
isChecked={appliedFilters?.includes(member.id) ? true : false}
onClick={() => handleUpdate(member.id)}
icon={<Avatar user={member} height="18px" width="18px" />}
title={member.display_name}
/>
))}
{viewButtons}
@ -68,4 +64,4 @@ export const FilterCreatedBy: React.FC<Props> = observer((props) => {
)}
</>
);
});
};

View File

@ -31,6 +31,29 @@ type Props = {
projectId: string;
};
type ViewButtonProps = {
handleLess: () => void;
handleMore: () => void;
isViewLessVisible: boolean;
isViewMoreVisible: boolean;
};
const ViewButtons = ({ handleLess, handleMore, isViewLessVisible, isViewMoreVisible }: ViewButtonProps) => (
<div className="flex items-center gap-2 ml-7 mt-1">
{/* TODO: handle view more and less in a better way */}
{isViewMoreVisible && (
<button className="text-custom-primary-100 text-xs font-medium" onClick={handleMore}>
View more
</button>
)}
{isViewLessVisible && (
<button className="text-custom-primary-100 text-xs font-medium" onClick={handleLess}>
View less
</button>
)}
</div>
);
export const FilterSelection: React.FC<Props> = observer((props) => {
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, projectId } = props;
@ -157,25 +180,12 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
itemsToRender={filtersToRender.priority?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
viewButtons={
<div className="flex items-center gap-2 ml-7 mt-1">
{/* TODO: handle view more and less in a better way */}
{isViewMoreVisible("priority") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewMore("priority")}
>
View more
</button>
)}
{isViewLessVisible("priority") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewLess("priority")}
>
View less
</button>
)}
</div>
<ViewButtons
isViewLessVisible={isViewLessVisible("priority")}
isViewMoreVisible={isViewMoreVisible("priority")}
handleLess={() => handleViewLess("priority")}
handleMore={() => handleViewMore("priority")}
/>
}
/>
</div>
@ -189,25 +199,15 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
handleUpdate={(val) => handleFiltersUpdate("state_group", val)}
itemsToRender={filtersToRender.state_group?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("state_group")}
isViewMoreVisible={isViewMoreVisible("state_group")}
handleLess={() => handleViewLess("state_group")}
handleMore={() => handleViewMore("state_group")}
/>
}
/>
<div className="flex items-center gap-2 ml-7 mt-1">
{isViewMoreVisible("state_group") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("state_group")}
>
View more
</button>
)}
{isViewLessVisible("state_group") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewLess("state_group")}
>
View less
</button>
)}
</div>
</div>
)}
@ -219,23 +219,16 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
handleUpdate={(val) => handleFiltersUpdate("state", val)}
itemsToRender={filtersToRender.state?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
projectId={projectId}
states={projectStore.states?.[projectId]}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("state")}
isViewMoreVisible={isViewMoreVisible("state")}
handleLess={() => handleViewLess("state")}
handleMore={() => handleViewMore("state")}
/>
}
/>
<div className="flex items-center gap-2 ml-7 mt-1">
{isViewMoreVisible("state") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("state")}
>
View more
</button>
)}
{isViewLessVisible("state") && (
<button className="text-custom-primary-100 text-xs font-medium" onClick={() => handleViewLess("state")}>
View less
</button>
)}
</div>
</div>
)}
@ -246,27 +239,15 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
appliedFilters={filters.assignees ?? null}
handleUpdate={(val) => handleFiltersUpdate("assignees", val)}
itemsToRender={filtersToRender.assignees?.currentLength ?? 0}
projectId={projectId}
members={projectStore.members?.[projectId]?.map((m) => m.member) ?? undefined}
searchQuery={filtersSearchQuery}
viewButtons={
<div className="flex items-center gap-2 ml-7 mt-1">
{isViewMoreVisible("assignees") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("assignees")}
>
View more
</button>
)}
{isViewLessVisible("assignees") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewLess("assignees")}
>
View less
</button>
)}
</div>
<ViewButtons
isViewLessVisible={isViewLessVisible("assignees")}
isViewMoreVisible={isViewMoreVisible("assignees")}
handleLess={() => handleViewLess("assignees")}
handleMore={() => handleViewMore("assignees")}
/>
}
/>
</div>
@ -279,27 +260,15 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
appliedFilters={filters.created_by ?? null}
handleUpdate={(val) => handleFiltersUpdate("created_by", val)}
itemsToRender={filtersToRender.created_by?.currentLength ?? 0}
projectId={projectId}
members={projectStore.members?.[projectId]?.map((m) => m.member) ?? undefined}
searchQuery={filtersSearchQuery}
viewButtons={
<div className="flex items-center gap-2 ml-7 mt-1">
{isViewMoreVisible("created_by") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("created_by")}
>
View more
</button>
)}
{isViewLessVisible("created_by") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewLess("created_by")}
>
View less
</button>
)}
</div>
<ViewButtons
isViewLessVisible={isViewLessVisible("created_by")}
isViewMoreVisible={isViewMoreVisible("created_by")}
handleLess={() => handleViewLess("created_by")}
handleMore={() => handleViewMore("created_by")}
/>
}
/>
</div>
@ -312,27 +281,15 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
appliedFilters={filters.labels ?? null}
handleUpdate={(val) => handleFiltersUpdate("labels", val)}
itemsToRender={filtersToRender.labels?.currentLength ?? 0}
projectId={projectId}
labels={projectStore.labels?.[projectId] ?? undefined}
searchQuery={filtersSearchQuery}
viewButtons={
<div className="flex items-center gap-2 ml-7 mt-1">
{isViewMoreVisible("labels") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewMore("labels")}
>
View more
</button>
)}
{isViewLessVisible("labels") && (
<button
className="text-custom-primary-100 text-xs font-medium"
onClick={() => handleViewLess("labels")}
>
View less
</button>
)}
</div>
<ViewButtons
isViewLessVisible={isViewLessVisible("labels")}
isViewMoreVisible={isViewMoreVisible("labels")}
handleLess={() => handleViewLess("labels")}
handleMore={() => handleViewMore("labels")}
/>
}
/>
</div>

View File

@ -1,12 +1,11 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Loader } from "components/ui";
// types
import { IIssueLabels } from "types";
const LabelIcons = ({ color }: { color: string }) => (
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: color }} />
@ -16,24 +15,19 @@ type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
projectId: string;
labels: IIssueLabels[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterLabels: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props;
export const FilterLabels: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, labels, searchQuery, viewButtons } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { project: projectStore } = store;
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = projectStore.labels?.[projectId?.toString() ?? ""]?.filter((label) =>
label.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const filteredOptions = labels?.filter((label) => label.name.toLowerCase().includes(searchQuery.toLowerCase()));
return (
<>
@ -72,4 +66,4 @@ export const FilterLabels: React.FC<Props> = observer((props) => {
)}
</>
);
});
};

View File

@ -49,15 +49,10 @@ export const FilterStartDate: React.FC<Props> = observer((props) => {
isChecked={appliedFilters?.includes(option.value) ? true : false}
onClick={() => handleUpdate(option.value)}
title={option.name}
multiple={false}
multiple
/>
))}
<FilterOption
isChecked={false}
onClick={() => setIsDateFilterModalOpen(true)}
title="Custom"
multiple={false}
/>
<FilterOption isChecked={false} onClick={() => setIsDateFilterModalOpen(true)} title="Custom" multiple />
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -13,10 +13,11 @@ type Props = {
handleUpdate: (val: string) => void;
itemsToRender: number;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterStateGroup: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props;
const { appliedFilters, handleUpdate, itemsToRender, searchQuery, viewButtons } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -34,9 +35,8 @@ export const FilterStateGroup: React.FC<Props> = observer((props) => {
{previewEnabled && (
<div>
{filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((stateGroup) => (
<>
{filteredOptions.slice(0, itemsToRender).map((stateGroup) => (
<FilterOption
key={stateGroup.key}
isChecked={appliedFilters?.includes(stateGroup.key) ? true : false}
@ -44,7 +44,9 @@ export const FilterStateGroup: React.FC<Props> = observer((props) => {
icon={<StateGroupIcon stateGroup={stateGroup.key} />}
title={stateGroup.title}
/>
))
))}
{viewButtons}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)}

View File

@ -1,8 +1,5 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
@ -11,25 +8,24 @@ import { Loader } from "components/ui";
import { StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { IStateResponse } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
projectId: string;
searchQuery: string;
states: IStateResponse | undefined;
viewButtons: React.ReactNode;
};
export const FilterState: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props;
export const FilterState: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery, states, viewButtons } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { project: projectStore } = store;
const statesByGroups = projectStore.states?.[projectId?.toString() ?? ""];
const statesList = getStatesList(statesByGroups);
const statesList = getStatesList(states);
const appliedFiltersCount = appliedFilters?.length ?? 0;
@ -56,6 +52,7 @@ export const FilterState: React.FC<Props> = observer((props) => {
title={state.name}
/>
))}
{viewButtons}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
@ -71,4 +68,4 @@ export const FilterState: React.FC<Props> = observer((props) => {
)}
</>
);
});
};

View File

@ -49,15 +49,10 @@ export const FilterTargetDate: React.FC<Props> = observer((props) => {
isChecked={appliedFilters?.includes(option.value) ? true : false}
onClick={() => handleUpdate(option.value)}
title={option.name}
multiple={false}
multiple
/>
))}
<FilterOption
isChecked={false}
onClick={() => setIsDateFilterModalOpen(true)}
title="Custom"
multiple={false}
/>
<FilterOption isChecked={false} onClick={() => setIsDateFilterModalOpen(true)} title="Custom" multiple />
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>