refactor: filters list component (#1687)

* refactor: filters list component

* fix: build error
This commit is contained in:
Aaryan Khandelwal 2023-07-27 00:57:12 +05:30 committed by GitHub
parent 0b86080166
commit c54b8b9a15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 196 deletions

View File

@ -1,7 +1,5 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router";
// icons // icons
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import { getPriorityIcon, getStateGroupIcon } from "components/icons"; import { getPriorityIcon, getStateGroupIcon } from "components/icons";
@ -12,11 +10,13 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// helpers // helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types // types
import { IIssueFilterOptions, IIssueLabels, IState, IUserLite } from "types"; import { IIssueFilterOptions, IIssueLabels, IState, IUserLite, TStateGroups } from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
type Props = { type Props = {
filters: any; filters: Partial<IIssueFilterOptions>;
setFilters: any; setFilters: (updatedFilter: Partial<IIssueFilterOptions>) => void;
clearAllFilters: (...args: any) => void; clearAllFilters: (...args: any) => void;
labels: IIssueLabels[] | undefined; labels: IIssueLabels[] | undefined;
members: IUserLite[] | undefined; members: IUserLite[] | undefined;
@ -31,9 +31,6 @@ export const FiltersList: React.FC<Props> = ({
members, members,
states, states,
}) => { }) => {
const router = useRouter();
const { viewId } = router.query;
if (!filters) return <></>; if (!filters) return <></>;
const nullFilters = Object.keys(filters).filter( const nullFilters = Object.keys(filters).filter(
@ -42,8 +39,11 @@ export const FiltersList: React.FC<Props> = ({
return ( return (
<div className="flex flex-1 flex-wrap items-center gap-2 text-xs"> <div className="flex flex-1 flex-wrap items-center gap-2 text-xs">
{Object.keys(filters).map((key) => { {Object.keys(filters).map((filterKey) => {
if (filters[key as keyof typeof filters] !== null) const key = filterKey as keyof typeof filters;
if (filters[key] === null) return null;
return ( return (
<div <div
key={key} key={key}
@ -52,14 +52,13 @@ export const FiltersList: React.FC<Props> = ({
<span className="capitalize text-custom-text-200"> <span className="capitalize text-custom-text-200">
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}: {key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
</span> </span>
{filters[key as keyof IIssueFilterOptions] === null || {filters[key] === null || (filters[key]?.length ?? 0) <= 0 ? (
(filters[key as keyof IIssueFilterOptions]?.length ?? 0) <= 0 ? (
<span className="inline-flex items-center px-2 py-0.5 font-medium">None</span> <span className="inline-flex items-center px-2 py-0.5 font-medium">None</span>
) : Array.isArray(filters[key as keyof IIssueFilterOptions]) ? ( ) : Array.isArray(filters[key]) ? (
<div className="space-x-2"> <div className="space-x-2">
{key === "state" ? (
<div className="flex flex-wrap items-center gap-1"> <div className="flex flex-wrap items-center gap-1">
{filters.state?.map((stateId: any) => { {key === "state"
? filters.state?.map((stateId: string) => {
const state = states?.find((s) => s.id === stateId); const state = states?.find((s) => s.id === stateId);
return ( return (
@ -83,33 +82,46 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{
state: filters.state?.filter((s: any) => s !== stateId), state: filters.state?.filter((s: any) => s !== stateId),
}, })
!Boolean(viewId)
)
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</span> </span>
</p> </p>
); );
})} })
<button : key === "state_group"
type="button" ? filters.state_group?.map((stateGroup) => {
const group = stateGroup as TStateGroups;
return (
<p
key={group}
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize"
style={{
color: STATE_GROUP_COLORS[group],
backgroundColor: `${STATE_GROUP_COLORS[group]}20`,
}}
>
<span>{getStateGroupIcon(group, "16", "16")}</span>
<span>{group}</span>
<span
className="cursor-pointer"
onClick={() => onClick={() =>
setFilters({ setFilters({
state: null, state_group: filters.state_group?.filter((g) => g !== group),
}) })
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</button> </span>
</div> </p>
) : key === "priority" ? ( );
<div className="flex flex-wrap items-center gap-1"> })
{filters.priority?.map((priority: any) => ( : key === "priority"
? filters.priority?.map((priority: any) => (
<p <p
key={priority} key={priority}
className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${ className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${
@ -129,32 +141,17 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{
priority: filters.priority?.filter((p: any) => p !== priority), priority: filters.priority?.filter((p: any) => p !== priority),
}, })
!Boolean(viewId)
)
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</span> </span>
</p> </p>
))} ))
<button : key === "assignees"
type="button" ? filters.assignees?.map((memberId: string) => {
onClick={() =>
setFilters({
priority: null,
})
}
>
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : key === "assignees" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.assignees?.map((memberId: string) => {
const member = members?.find((m) => m.id === memberId); const member = members?.find((m) => m.id === memberId);
return ( return (
@ -167,35 +164,18 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{ assignees: filters.assignees?.filter((p: any) => p !== memberId),
assignees: filters.assignees?.filter( })
(p: any) => p !== memberId
),
},
!Boolean(viewId)
)
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</span> </span>
</div> </div>
); );
})}
<button
type="button"
onClick={() =>
setFilters({
assignees: null,
}) })
} : key === "created_by"
> ? filters.created_by?.map((memberId: string) => {
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : key === "created_by" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.created_by?.map((memberId: string) => {
const member = members?.find((m) => m.id === memberId); const member = members?.find((m) => m.id === memberId);
return ( return (
@ -208,35 +188,20 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{
created_by: filters.created_by?.filter( created_by: filters.created_by?.filter(
(p: any) => p !== memberId (p: any) => p !== memberId
), ),
}, })
!Boolean(viewId)
)
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</span> </span>
</div> </div>
); );
})}
<button
type="button"
onClick={() =>
setFilters({
created_by: null,
}) })
} : key === "labels"
> ? filters.labels?.map((labelId: string) => {
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : key === "labels" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.labels?.map((labelId: string) => {
const label = labels?.find((l) => l.id === labelId); const label = labels?.find((l) => l.id === labelId);
if (!label) return null; if (!label) return null;
@ -260,12 +225,9 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{
labels: filters.labels?.filter((l: any) => l !== labelId), labels: filters.labels?.filter((l: any) => l !== labelId),
}, })
!Boolean(viewId)
)
} }
> >
<XMarkIcon <XMarkIcon
@ -277,22 +239,10 @@ export const FiltersList: React.FC<Props> = ({
</span> </span>
</div> </div>
); );
})}
<button
type="button"
onClick={() =>
setFilters({
labels: null,
}) })
} : key === "target_date"
> ? filters.target_date?.map((date: string) => {
<XMarkIcon className="h-3 w-3" /> if (filters.target_date && filters.target_date.length <= 0) return null;
</button>
</div>
) : key === "target_date" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.target_date?.map((date: string) => {
if (filters.target_date.length <= 0) return null;
const splitDate = date.split(";"); const splitDate = date.split(";");
@ -308,35 +258,28 @@ export const FiltersList: React.FC<Props> = ({
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters( setFilters({
{ target_date: filters.target_date?.filter((d: any) => d !== date),
target_date: filters.target_date?.filter( })
(d: any) => d !== date
),
},
!Boolean(viewId)
)
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</span> </span>
</div> </div>
); );
})} })
: (filters[key] as any)?.join(", ")}
<button <button
type="button" type="button"
onClick={() => onClick={() =>
setFilters({ setFilters({
target_date: null, [key]: null,
}) })
} }
> >
<XMarkIcon className="h-3 w-3" /> <XMarkIcon className="h-3 w-3" />
</button> </button>
</div> </div>
) : (
(filters[key as keyof IIssueFilterOptions] as any)?.join(", ")
)}
</div> </div>
) : ( ) : (
<div className="flex items-center gap-x-1 capitalize"> <div className="flex items-center gap-x-1 capitalize">

View File

@ -466,7 +466,7 @@ export const IssuesView: React.FC<Props> = ({
<div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0"> <div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0">
<FiltersList <FiltersList
filters={filters} filters={filters}
setFilters={setFilters} setFilters={(updatedFilter) => setFilters(updatedFilter, !Boolean(viewId))}
labels={labels} labels={labels}
members={members?.map((m) => m.member)} members={members?.map((m) => m.member)}
states={states} states={states}

View File

@ -200,17 +200,15 @@ export const MyIssuesView: React.FC<Props> = ({
}, },
[makeIssueCopy, handleEditIssue, handleDeleteIssue] [makeIssueCopy, handleEditIssue, handleDeleteIssue]
); );
const filtersToShow = { ...filters };
delete filtersToShow?.assignees;
delete filtersToShow?.created_by;
const nullFilters = Object.keys(filtersToShow).filter( const filtersToDisplay = { ...filters, assignees: null, created_by: null };
(key) => filtersToShow[key as keyof IIssueFilterOptions] === null
const nullFilters = Object.keys(filtersToDisplay).filter(
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
); );
const areFiltersApplied = const areFiltersApplied =
Object.keys(filtersToShow).length > 0 && Object.keys(filtersToDisplay).length > 0 &&
nullFilters.length !== Object.keys(filtersToShow).length; nullFilters.length !== Object.keys(filtersToDisplay).length;
return ( return (
<> <>
@ -242,7 +240,7 @@ export const MyIssuesView: React.FC<Props> = ({
<> <>
<div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0"> <div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0">
<FiltersList <FiltersList
filters={filtersToShow} filters={filtersToDisplay}
setFilters={setFilters} setFilters={setFilters}
labels={labels} labels={labels}
members={undefined} members={undefined}

View File

@ -91,6 +91,7 @@ export const initialState: StateType = {
assignees: null, assignees: null,
labels: null, labels: null,
state: null, state: null,
state_group: null,
created_by: null, created_by: null,
target_date: null, target_date: null,
}, },

View File

@ -88,18 +88,24 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
); );
const issueView = (myWorkspace?.view_props ?? initialValues).issueView; const issueView = (myWorkspace?.view_props ?? initialValues).issueView;
const groupBy = (myWorkspace?.view_props ?? initialValues).groupByProperty;
const orderBy = (myWorkspace?.view_props ?? initialValues).orderBy;
const showEmptyGroups = (myWorkspace?.view_props ?? initialValues).showEmptyGroups;
const filters = (myWorkspace?.view_props ?? initialValues).filters;
const setIssueView = useCallback( const setIssueView = useCallback(
(newView: TIssueViewOptions) => { (newView: TIssueViewOptions) => {
console.log("newView", newView); const payload: Partial<IWorkspaceViewProps> = {
saveData({
issueView: newView, issueView: newView,
}); };
if (newView === "kanban" && groupBy === null) payload.groupByProperty = "state_detail.group";
saveData(payload);
}, },
[saveData] [groupBy, saveData]
); );
const groupBy = (myWorkspace?.view_props ?? initialValues).groupByProperty;
const setGroupBy = useCallback( const setGroupBy = useCallback(
(newGroup: TIssueGroupByOptions) => { (newGroup: TIssueGroupByOptions) => {
saveData({ saveData({
@ -109,7 +115,6 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
[saveData] [saveData]
); );
const orderBy = (myWorkspace?.view_props ?? initialValues).orderBy;
const setOrderBy = useCallback( const setOrderBy = useCallback(
(newOrderBy: TIssueOrderByOptions) => { (newOrderBy: TIssueOrderByOptions) => {
saveData({ saveData({
@ -119,7 +124,6 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
[saveData] [saveData]
); );
const showEmptyGroups = (myWorkspace?.view_props ?? initialValues).showEmptyGroups;
const setShowEmptyGroups = useCallback(() => { const setShowEmptyGroups = useCallback(() => {
if (!myWorkspace) return; if (!myWorkspace) return;
@ -142,9 +146,8 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
[myWorkspace, saveData] [myWorkspace, saveData]
); );
const filters = (myWorkspace?.view_props ?? initialValues).filters;
const setFilters = useCallback( const setFilters = useCallback(
(updatedFilter: Partial<IIssueFilterOptions & { state_group: string[] | null }>) => { (updatedFilter: Partial<IIssueFilterOptions>) => {
if (!myWorkspace) return; if (!myWorkspace) return;
saveData({ saveData({

View File

@ -8,6 +8,7 @@ import type {
IProjectLite, IProjectLite,
IWorkspaceLite, IWorkspaceLite,
IStateLite, IStateLite,
TStateGroups,
} from "types"; } from "types";
export interface IIssueCycle { export interface IIssueCycle {
@ -231,6 +232,7 @@ export interface IIssueFilterOptions {
assignees: string[] | null; assignees: string[] | null;
target_date: string[] | null; target_date: string[] | null;
state: string[] | null; state: string[] | null;
state_group: TStateGroups[] | null;
labels: string[] | null; labels: string[] | null;
priority: string[] | null; priority: string[] | null;
created_by: string[] | null; created_by: string[] | null;

View File

@ -6,7 +6,7 @@ import type {
TIssueGroupByOptions, TIssueGroupByOptions,
TIssueOrderByOptions, TIssueOrderByOptions,
TIssueViewOptions, TIssueViewOptions,
TStateGroup, TStateGroups,
} from "./"; } from "./";
export interface IProject { export interface IProject {
@ -140,7 +140,7 @@ export interface ISearchIssueResponse {
project__name: string; project__name: string;
sequence_id: number; sequence_id: number;
state__color: string; state__color: string;
state__group: TStateGroup; state__group: TStateGroups;
state__name: string; state__name: string;
workspace__slug: string; workspace__slug: string;
} }

View File

@ -1,6 +1,6 @@
import { IProject, IProjectLite, IWorkspaceLite } from "types"; import { IProject, IProjectLite, IWorkspaceLite } from "types";
export type TStateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled"; export type TStateGroups = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
export interface IState { export interface IState {
readonly id: string; readonly id: string;
@ -9,7 +9,7 @@ export interface IState {
readonly created_by: string; readonly created_by: string;
default: boolean; default: boolean;
description: string; description: string;
group: TStateGroup; group: TStateGroups;
name: string; name: string;
project: string; project: string;
readonly project_detail: IProjectLite; readonly project_detail: IProjectLite;
@ -23,7 +23,7 @@ export interface IState {
export interface IStateLite { export interface IStateLite {
color: string; color: string;
group: TStateGroup; group: TStateGroups;
id: string; id: string;
name: string; name: string;
} }

View File

@ -67,11 +67,7 @@ export interface IWorkspaceViewProps {
issueView: TIssueViewOptions; issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions; groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions; orderBy: TIssueOrderByOptions;
filters: Partial< filters: Partial<IIssueFilterOptions>;
IIssueFilterOptions & {
state_group: string[] | null;
}
>;
showEmptyGroups: boolean; showEmptyGroups: boolean;
} }