Skip to content

Commit

Permalink
feat: fetch, create, delete custom filters
Browse files Browse the repository at this point in the history
  • Loading branch information
awinogradov committed Apr 20, 2023
1 parent a0dcad2 commit 1b9182c
Show file tree
Hide file tree
Showing 22 changed files with 420 additions and 5 deletions.
42 changes: 40 additions & 2 deletions graphql/resolvers/Filter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { arg, nonNull } from 'nexus';
import { ObjectDefinitionBlock } from 'nexus/dist/core';

import { Filter, FilterCreateInput } from '../types';
import { Filter, FilterCreateInput, FilterInput } from '../types';

export const query = (t: ObjectDefinitionBlock<'Query'>) => {};
export const query = (t: ObjectDefinitionBlock<'Query'>) => {
t.field('filter', {
type: Filter,
args: {
data: nonNull(arg({ type: FilterInput })),
},
resolve: async (_, { data }, { db, activity }) => {
if (!activity) return null;

try {
return db.filter.findUnique({
where: {
id: data.id,
},
});
} catch (error) {
throw Error(`${error}`);
}
},
});
};

export const mutation = (t: ObjectDefinitionBlock<'Mutation'>) => {
t.field('createFilter', {
Expand All @@ -26,4 +46,22 @@ export const mutation = (t: ObjectDefinitionBlock<'Mutation'>) => {
}
},
});

t.field('deleteFilter', {
type: Filter,
args: {
data: nonNull(arg({ type: FilterInput })),
},
resolve: async (_, { data }, { db, activity }) => {
if (!activity) return null;

try {
return db.filter.delete({
where: { id: data.id },
});
} catch (error) {
throw Error(`${error}`);
}
},
});
};
35 changes: 34 additions & 1 deletion graphql/resolvers/User.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { arg, nonNull } from 'nexus';
import { ObjectDefinitionBlock } from 'nexus/dist/core';

import { User, SortOrder, Ghost, UserUpdateInput, UserInvitesInput, Activity, FindActivityInput } from '../types';
import {
User,
SortOrder,
Ghost,
UserUpdateInput,
UserInvitesInput,
Activity,
Filter,
FindActivityInput,
} from '../types';

export const query = (t: ObjectDefinitionBlock<'Query'>) => {
t.list.field('users', {
Expand All @@ -15,6 +24,30 @@ export const query = (t: ObjectDefinitionBlock<'Query'>) => {
}),
});

t.list.field('userFilters', {
type: Filter,
resolve: async (_, __, { db, activity }) => {
if (!activity) return null;

return db.filter.findMany({
where: {
OR: [
{
activityId: activity.id,
},
{
stargizers: {
some: {
id: activity.id,
},
},
},
],
},
});
},
});

t.list.field('findActivity', {
type: Activity,
args: {
Expand Down
7 changes: 7 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ input FilterCreateInput {
title: String!
}

input FilterInput {
id: ID!
}

enum FilterMode {
Global
Project
Expand Down Expand Up @@ -225,6 +229,7 @@ type Mutation {
createProject(data: ProjectCreateInput!): Project
createTag(description: String, title: String!): Tag
deleteComment(data: CommentDeleteInput!): Comment
deleteFilter(data: FilterInput!): Filter
deleteProject(data: ProjectDelete!): Project
toggleGoalArchive(data: GoalArchiveInput!): Activity
toggleGoalDependency(data: GoalDependencyToggleInput!): Goal
Expand Down Expand Up @@ -310,6 +315,7 @@ input ProjectUpdateInput {
}

type Query {
filter(data: FilterInput!): Filter
findActivity(data: FindActivityInput!): [Activity]
findGoal(query: String!): [Goal]
flow(id: String!): Flow
Expand All @@ -324,6 +330,7 @@ type Query {
projects: [Project]
settings: Settings
tagCompletion(query: String!, sortBy: SortOrder): [Tag]
userFilters: [Filter]
userGoals(data: UserGoalsInput!): UserGoalsOutput
users(sortBy: SortOrder): [User]
}
Expand Down
7 changes: 7 additions & 0 deletions graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ export const Filter = objectType({
},
});

export const FilterInput = inputObjectType({
name: 'FilterInput',
definition(t) {
t.field(FilterModel.id);
},
});

export const FilterCreateInput = inputObjectType({
name: 'FilterCreateInput',
definition(t) {
Expand Down
6 changes: 6 additions & 0 deletions src/components/FilterCreateForm/FilterCreateForm.i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Title": "",
"Description": "",
"Create filter": "",
"New filters preset": ""
}
17 changes: 17 additions & 0 deletions src/components/FilterCreateForm/FilterCreateForm.i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable */
// Do not edit, use generator to update
import { i18n, fmt, I18nLangSet } from 'easy-typed-intl';
import getLang from '../../../i18n/getLang';

import ru from './ru.json';
import en from './en.json';

type I18nKey = keyof typeof ru & keyof typeof en;
type I18nLang = 'ru' | 'en';

const keyset: I18nLangSet<I18nKey> = {};

keyset['ru'] = ru;
keyset['en'] = en;

export const tr = i18n<I18nLang, I18nKey>(keyset, fmt, getLang);
6 changes: 6 additions & 0 deletions src/components/FilterCreateForm/FilterCreateForm.i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Title": "",
"Description": "",
"Create filter": "",
"New filters preset": ""
}
106 changes: 106 additions & 0 deletions src/components/FilterCreateForm/FilterCreateForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Button,
Form,
FormActions,
FormAction,
FormTextarea,
FormInput,
FormTitle,
ModalHeader,
ModalContent,
} from '@taskany/bricks';

import { submitKeys } from '../../utils/hotkeys';
import { errorsProvider } from '../../utils/forms';
import { CreateFormType, createSchema } from '../../schema/filter';
import { Filter, FilterCreateInput } from '../../../graphql/@generated/genql';
import { useFilterResource } from '../../hooks/useFilterResource';
import { Void } from '../../types/void';

import { tr } from './FilterCreateForm.i18n';

interface FilterCreateFormProps {
mode: FilterCreateInput['mode'];
params: FilterCreateInput['params'];

onSubmit?: Void<Partial<Filter>>;
}

const FilterCreateForm: React.FC<FilterCreateFormProps> = ({ mode, params, onSubmit }) => {
const { createFilter } = useFilterResource();
// const [formBusy, setFormBusy] = useState(false);

const {
register,
handleSubmit,
formState: { errors, isValid, isSubmitted },
} = useForm<CreateFormType>({
resolver: zodResolver(createSchema),
mode: 'onChange',
reValidateMode: 'onChange',
shouldFocusError: true,
defaultValues: {
mode,
params,
},
});

const errorsResolver = errorsProvider(errors, isSubmitted);

const onPending = useCallback(
async (form: CreateFormType) => {
// setFormBusy(true);

const [data, err] = await createFilter(form);

if (data && data.createFilter && !err) {
onSubmit?.(data.createFilter);
}
},
[createFilter, onSubmit],
);

const onError = useCallback((err: typeof errors) => {
// TODO: Sentry event
}, []);

return (
<>
<ModalHeader>
<FormTitle>{tr('New filters preset')}</FormTitle>
</ModalHeader>

<ModalContent>
{/* TODO: pass disabled via formBusy */}
<Form submitHotkey={submitKeys} onSubmit={handleSubmit(onPending, onError)}>
<FormInput
{...register('title')}
placeholder={tr('Title')}
flat="bottom"
brick="right"
error={errorsResolver('title')}
/>

<FormTextarea
{...register('description')}
placeholder={tr('Description')}
flat="both"
error={errorsResolver('description')}
/>

<FormActions flat="top">
<FormAction left inline />
<FormAction right inline>
<Button view="primary" outline={!isValid} type="submit" text={tr('Create filter')} />
</FormAction>
</FormActions>
</Form>
</ModalContent>
</>
);
};

export default FilterCreateForm;
6 changes: 6 additions & 0 deletions src/components/FilterDeleteForm/FilterDeleteForm.i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"You are trying to delete filters preset": "You are trying to delete filters preset",
"Are you sure to delete filters preset {preset}?": "Are you sure to delete filters preset {preset}?",
"Cancel": "Cancel",
"Yes, delete it": "Yes, delete it"
}
17 changes: 17 additions & 0 deletions src/components/FilterDeleteForm/FilterDeleteForm.i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable */
// Do not edit, use generator to update
import { i18n, fmt, I18nLangSet } from 'easy-typed-intl';
import getLang from '../../../i18n/getLang';

import ru from './ru.json';
import en from './en.json';

type I18nKey = keyof typeof ru & keyof typeof en;
type I18nLang = 'ru' | 'en';

const keyset: I18nLangSet<I18nKey> = {};

keyset['ru'] = ru;
keyset['en'] = en;

export const tr = i18n<I18nLang, I18nKey>(keyset, fmt, getLang);
6 changes: 6 additions & 0 deletions src/components/FilterDeleteForm/FilterDeleteForm.i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"You are trying to delete filters preset": "Ты собираешься удалить пресет с фильтрами",
"Are you sure to delete filters preset {preset}?": "Уверен, что хочешь удалить пересет {preset}?",
"Cancel": "Отмена",
"Yes, delete it": "Да, удаляем"
}
62 changes: 62 additions & 0 deletions src/components/FilterDeleteForm/FilterDeleteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useCallback, useMemo } from 'react';
import { Button, Form, FormAction, FormActions, FormTitle, ModalContent, ModalHeader, Text } from '@taskany/bricks';
import { warn0 } from '@taskany/colors';

import { Filter } from '../../../graphql/@generated/genql';
import { useFilterResource } from '../../hooks/useFilterResource';

import { tr } from './FilterDeleteForm.i18n';

interface FilterDeleteFormProps {
preset: Filter;

onSubmit: (preset: Filter) => void;
onCancel: () => void;
}

const FilterDeleteForm: React.FC<FilterDeleteFormProps> = ({ preset, onSubmit, onCancel }) => {
const { deleteFilter } = useFilterResource();

const onSubmitProvider = useCallback(
(preset: Filter) => async () => {
const [data, err] = await deleteFilter({ id: preset.id });

if (data && data.deleteFilter && !err) {
onSubmit(preset);
}
},
[onSubmit, deleteFilter],
);

const onSubmitClick = useMemo(() => onSubmitProvider(preset), [onSubmitProvider, preset]);

return (
<>
<ModalHeader>
<FormTitle color={warn0}>{tr('You are trying to delete filters preset')}</FormTitle>
</ModalHeader>

<ModalContent>
<Text>
{tr.raw('Are you sure to delete filters preset {preset}?', {
preset: <b key={preset.title}>{preset.title}</b>,
})}
</Text>

<br />

<Form>
<FormActions flat="top">
<FormAction left />
<FormAction right inline>
<Button size="m" text={tr('Cancel')} onClick={onCancel} />
<Button size="m" view="warning" onClick={onSubmitClick} text={tr('Yes, delete it')} />
</FormAction>
</FormActions>
</Form>
</ModalContent>
</>
);
};

export default FilterDeleteForm;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"We are saving your filter...": "We are saving your filter...",
"Voila! Saved successfully 🎉! Use and share it with teammates 😉": "Voila! Saved successfully 🎉! Use and share it with teammates 😉",
"Something went wrong 😿": "Something went wrong 😿"
"Something went wrong 😿": "Something went wrong 😿",
"We are deleting your filter...": "We are deleting your filter...",
"Deleted successfully 🎉!": "Deleted successfully 🎉!"
}
Loading

0 comments on commit 1b9182c

Please sign in to comment.