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

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">
<FiltersList
filters={filters}
setFilters={setFilters}
setFilters={(updatedFilter) => setFilters(updatedFilter, !Boolean(viewId))}
labels={labels}
members={members?.map((m) => m.member)}
states={states}

View File

@ -200,17 +200,15 @@ export const MyIssuesView: React.FC<Props> = ({
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
const filtersToShow = { ...filters };
delete filtersToShow?.assignees;
delete filtersToShow?.created_by;
const nullFilters = Object.keys(filtersToShow).filter(
(key) => filtersToShow[key as keyof IIssueFilterOptions] === null
const filtersToDisplay = { ...filters, assignees: null, created_by: null };
const nullFilters = Object.keys(filtersToDisplay).filter(
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
);
const areFiltersApplied =
Object.keys(filtersToShow).length > 0 &&
nullFilters.length !== Object.keys(filtersToShow).length;
Object.keys(filtersToDisplay).length > 0 &&
nullFilters.length !== Object.keys(filtersToDisplay).length;
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">
<FiltersList
filters={filtersToShow}
filters={filtersToDisplay}
setFilters={setFilters}
labels={labels}
members={undefined}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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 {
readonly id: string;
@ -9,7 +9,7 @@ export interface IState {
readonly created_by: string;
default: boolean;
description: string;
group: TStateGroup;
group: TStateGroups;
name: string;
project: string;
readonly project_detail: IProjectLite;
@ -23,7 +23,7 @@ export interface IState {
export interface IStateLite {
color: string;
group: TStateGroup;
group: TStateGroups;
id: string;
name: string;
}

View File

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