Skip to content

Commit

Permalink
feat(EstimateFilterDropdown): user can filter goals via estimates
Browse files Browse the repository at this point in the history
  • Loading branch information
awinogradov committed Apr 4, 2023
1 parent 64eeb53 commit a9e044b
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 303 deletions.
26 changes: 26 additions & 0 deletions graphql/queries/goals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const goalsFilter = (
priority: string[];
states: string[];
tags: string[];
estimates: string[];
owner: string[];
},
extra: any = {},
Expand Down Expand Up @@ -34,6 +35,23 @@ export const goalsFilter = (
}
: {};

const estimateFilter = data.estimates.length
? {
estimate: {
some: {
OR: data.estimates.map((e) => {
const [q, y] = e.split('/');

return {
q,
y,
};
}),
},
},
}
: {};

const ownerFilter = data.owner.length
? {
owner: {
Expand Down Expand Up @@ -96,6 +114,7 @@ export const goalsFilter = (
...priorityFilter,
...statesFilter,
...tagsFilter,
...estimateFilter,
...ownerFilter,
...extra,
},
Expand Down Expand Up @@ -244,6 +263,7 @@ export const calcGoalsMeta = (goals: GoalModel[]) => {
const uniqStates = new Map();
const uniqProjects = new Map();
const uniqTeams = new Map();
const uniqEstimates = new Map();

goals.forEach((goal: GoalModel) => {
goal.state && uniqStates.set(goal.state?.id, goal.state);
Expand All @@ -267,6 +287,11 @@ export const calcGoalsMeta = (goals: GoalModel[]) => {
p && uniqParticipants.set(p.id, p);
});
goal.activity && uniqIssuers.set(goal.activity.id, goal.activity);

goal.estimate &&
goal.estimate.forEach((e) => {
uniqEstimates.set(`${e?.q}/${e?.y}`, e);
});
});

return {
Expand All @@ -278,6 +303,7 @@ export const calcGoalsMeta = (goals: GoalModel[]) => {
states: Array.from(uniqStates.values()),
projects: Array.from(uniqProjects.values()),
teams: Array.from(uniqTeams.values()),
estimates: Array.from(uniqEstimates.values()),
count: goals.length,
};
};
1 change: 1 addition & 0 deletions graphql/resolvers/Team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const goalsQuery = async (
priority: string[];
states: string[];
tags: string[];
estimates: string[];
owner: string[];
},
) => {
Expand Down
4 changes: 4 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ input GoalUpdateInput {

type GoalsMetaOutput {
count: Int!
estimates: [Estimate!]
issuers: [Activity!]
owners: [Activity!]
participants: [Activity!]
Expand Down Expand Up @@ -266,6 +267,7 @@ input ProjectDelete {
}

input ProjectGoalsInput {
estimates: [String!]!
key: String!
owner: [String!]!
priority: [String!]!
Expand Down Expand Up @@ -415,6 +417,7 @@ input TeamDelete {
}

input TeamGoalsInput {
estimates: [String!]!
key: String!
owner: [String!]!
priority: [String!]!
Expand Down Expand Up @@ -455,6 +458,7 @@ type User {
}

input UserGoalsInput {
estimates: [String!]!
owner: [String!]!
priority: [String!]!
query: String!
Expand Down
4 changes: 4 additions & 0 deletions graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ export const ProjectGoalsInput = inputObjectType({
t.nonNull.list.nonNull.string('priority');
t.nonNull.list.nonNull.string('states');
t.nonNull.list.nonNull.string('tags');
t.nonNull.list.nonNull.string('estimates');
t.nonNull.list.nonNull.string('owner');
t.nonNull.string('query');
},
Expand All @@ -480,6 +481,7 @@ export const UserGoalsInput = inputObjectType({
t.nonNull.list.nonNull.string('priority');
t.nonNull.list.nonNull.string('states');
t.nonNull.list.nonNull.string('tags');
t.nonNull.list.nonNull.string('estimates');
t.nonNull.list.nonNull.string('owner');
t.nonNull.string('query');
},
Expand Down Expand Up @@ -525,6 +527,7 @@ export const TeamGoalsInput = inputObjectType({
t.nonNull.list.nonNull.string('priority');
t.nonNull.list.nonNull.string('states');
t.nonNull.list.nonNull.string('tags');
t.nonNull.list.nonNull.string('estimates');
t.nonNull.list.nonNull.string('owner');
t.nonNull.string('query');
},
Expand Down Expand Up @@ -554,6 +557,7 @@ export const GoalsMetaOutput = objectType({
t.list.field('states', { type: nonNull(State) });
t.list.field('projects', { type: nonNull(Project) });
t.list.field('teams', { type: nonNull(Team) });
t.list.field('estimates', { type: nonNull(Estimate) });
t.list.nonNull.string('priority');
t.nonNull.int('count');
},
Expand Down
3 changes: 2 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@
"Owner": "Owner",
"Limit": "Limit",
"Tags": "Tags",
"Priority": "Priority"
"Priority": "Priority",
"Estimate": "Estimate"
},
"GoalParentComboBox": {
"project": "Project",
Expand Down
3 changes: 2 additions & 1 deletion i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,8 @@
"Owner": "Ответственный",
"Limit": "Лимит",
"Tags": "Теги",
"Priority": "Приоритет"
"Priority": "Приоритет",
"Estimate": "Срок"
},
"GoalParentComboBox": {
"project": "Project",
Expand Down
75 changes: 75 additions & 0 deletions src/components/EstimateFilterDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useCallback, useEffect, useState } from 'react';
import dynamic from 'next/dynamic';

import { Estimate } from '../../graphql/@generated/genql';

import { FiltersMenuItem } from './FiltersMenuItem';
import { MenuItem } from './MenuItem';

const Dropdown = dynamic(() => import('./Dropdown'));

interface EstimateFilterDropdownProps {
text: React.ComponentProps<typeof Dropdown>['text'];
value?: string[];
estimates: Estimate[];
disabled?: React.ComponentProps<typeof Dropdown>['disabled'];

onChange?: (selected: string[]) => void;
}

const estimateToString = (estimate: Estimate) => `${estimate.q}/${estimate.y}`;

export const EstimateFilterDropdown: React.FC<EstimateFilterDropdownProps> = React.forwardRef<
HTMLDivElement,
EstimateFilterDropdownProps
>(({ text, estimates, value, disabled, onChange }, ref) => {
const [selected, setSelected] = useState(new Set(value));

useEffect(() => {
setSelected(new Set(value));
}, [value]);

const onEstimateClick = useCallback(
(e: Estimate) => {
selected.has(estimateToString(e))
? selected.delete(estimateToString(e))
: selected.add(estimateToString(e));
const newSelected = new Set(selected);
setSelected(newSelected);

onChange?.(Array.from(newSelected));
},
[onChange, selected],
);

return (
<Dropdown
ref={ref}
text={text}
value={value}
onChange={onEstimateClick}
items={estimates}
disabled={disabled}
renderTrigger={(props) => (
<FiltersMenuItem
ref={props.ref}
active={Boolean(Array.from(selected).length)}
disabled={props.disabled}
onClick={props.onClick}
>
{props.text}
</FiltersMenuItem>
)}
renderItem={(props) => (
<MenuItem
ghost
key={estimateToString(props.item)}
selected={selected.has(estimateToString(props.item))}
onClick={props.onClick}
>
{estimateToString(props.item)}
</MenuItem>
)}
/>
);
});
22 changes: 20 additions & 2 deletions src/components/FiltersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { UserFilterDropdown } from './UserFilterDropdown';
import { TagsFilterDropdown } from './TagsFilterDropdown';
import { LimitFilterDropdown } from './LimitFilterDropdown';
import { PriorityFilterDropdown } from './PriorityFilterDropdown';
import { EstimateFilterDropdown } from './EstimateFilterDropdown';
import { Text } from './Text';

interface FiltersPanelProps {
Expand All @@ -22,14 +23,16 @@ interface FiltersPanelProps {
states?: React.ComponentProps<typeof StateFilterDropdown>['states'];
users?: React.ComponentProps<typeof UserFilterDropdown>['activity'];
tags?: React.ComponentProps<typeof TagsFilterDropdown>['tags'];
filterValues: [string[], string[], string[], string[], string, number];
estimates?: React.ComponentProps<typeof EstimateFilterDropdown>['estimates'];
filterValues: [string[], string[], string[], string[], string[], string, number];
children?: React.ReactNode;

onSearchChange: (search: string) => void;
onPriorityChange?: React.ComponentProps<typeof PriorityFilterDropdown>['onChange'];
onStateChange?: React.ComponentProps<typeof StateFilterDropdown>['onChange'];
onUserChange?: React.ComponentProps<typeof UserFilterDropdown>['onChange'];
onTagChange?: React.ComponentProps<typeof TagsFilterDropdown>['onChange'];
onEstimateChange?: React.ComponentProps<typeof EstimateFilterDropdown>['onChange'];
onLimitChange?: React.ComponentProps<typeof LimitFilterDropdown>['onChange'];
}

Expand Down Expand Up @@ -66,18 +69,21 @@ export const FiltersPanel: React.FC<FiltersPanelProps> = ({
priority,
users,
tags,
estimates,
filterValues,
onPriorityChange,
onSearchChange,
onStateChange,
onUserChange,
onTagChange,
onEstimateChange,
onLimitChange,
children,
}) => {
const t = useTranslations('FiltersPanel');

const [priorityFilter, stateFilter, tagsFilter, ownerFilter, searchFilter, limitFilter] = filterValues;
const [priorityFilter, stateFilter, tagsFilter, estimateFilter, ownerFilter, searchFilter, limitFilter] =
filterValues;

const onSearchInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -125,6 +131,7 @@ export const FiltersPanel: React.FC<FiltersPanelProps> = ({
onChange={onStateChange}
/>
))}

{nullable(users, (u) => (
<UserFilterDropdown
text={t('Owner')}
Expand All @@ -133,9 +140,20 @@ export const FiltersPanel: React.FC<FiltersPanelProps> = ({
onChange={onUserChange}
/>
))}

{nullable(tags, (ta) => (
<TagsFilterDropdown text={t('Tags')} tags={ta} value={tagsFilter} onChange={onTagChange} />
))}

{nullable(estimates, (e) => (
<EstimateFilterDropdown
text={t('Estimate')}
estimates={e}
value={estimateFilter}
onChange={onEstimateChange}
/>
))}

{nullable(onLimitChange, (olc) => (
<LimitFilterDropdown text={t('Limit')} value={limitFilter} onChange={olc} />
))}
Expand Down
13 changes: 12 additions & 1 deletion src/components/pages/GoalsPage/GoalsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import { tr } from './GoalsPage.i18n';

const GoalPreview = dynamic(() => import('../../GoalPreview'));

const fetcher = createFetcher((_, priority = [], states = [], tags = [], owner = [], query = '') => ({
const fetcher = createFetcher((_, priority = [], states = [], tags = [], estimates = [], owner = [], query = '') => ({
userGoals: [
{
data: {
priority,
states,
tags,
estimates,
owner,
query,
},
Expand Down Expand Up @@ -98,6 +99,7 @@ const fetcher = createFetcher((_, priority = [], states = [], tags = [], owner =
priority: [],
states: [],
tags: [],
estimates: [],
owner: [],
query: '',
},
Expand Down Expand Up @@ -133,6 +135,12 @@ const fetcher = createFetcher((_, priority = [], states = [], tags = [], owner =
key: true,
title: true,
},
estimates: {
id: true,
q: true,
y: true,
date: true,
},
priority: true,
count: true,
},
Expand All @@ -159,6 +167,7 @@ export const GoalsPage = ({ user, ssrTime, locale, fallback }: ExternalPageProps
setStateFilter,
setTagsFilter,
setTagsFilterOutside,
setEstimateFilter,
setOwnerFilter,
setFulltextFilter,
} = useUrlFilterParams();
Expand Down Expand Up @@ -206,12 +215,14 @@ export const GoalsPage = ({ user, ssrTime, locale, fallback }: ExternalPageProps
states={meta?.states}
users={meta?.owners}
tags={meta?.tags}
estimates={meta?.estimates}
filterValues={filterValues}
onSearchChange={setFulltextFilter}
onPriorityChange={setPriorityFilter}
onStateChange={setStateFilter}
onUserChange={setOwnerFilter}
onTagChange={setTagsFilter}
onEstimateChange={setEstimateFilter}
/>

<PageContent>
Expand Down
Loading

0 comments on commit a9e044b

Please sign in to comment.