diff --git a/src/components/CriteriaForm/CriteriaForm.tsx b/src/components/CriteriaForm/CriteriaForm.tsx index de7cfa86b..74ea59458 100644 --- a/src/components/CriteriaForm/CriteriaForm.tsx +++ b/src/components/CriteriaForm/CriteriaForm.tsx @@ -15,32 +15,33 @@ import { } from '@taskany/bricks/harmony'; import { GoalSelect } from '../GoalSelect/GoalSelect'; -import { getStateProps, GoalBadge } from '../GoalBadge'; +import { GoalBadge } from '../GoalBadge'; import { FilterAutoCompleteInput } from '../FilterAutoCompleteInput/FilterAutoCompleteInput'; import { AddInlineTrigger } from '../AddInlineTrigger/AddInlineTrigger'; import { StateDot } from '../StateDot/StateDot'; -import { TaskBadge, TaskBadgeIcon } from '../TaskBadge/TaskBadge'; +import { JiraTaskBadge, JiraTaskBadgeIcon } from '../JiraTaskBadge/JiraTaskBadge'; import { tr } from './CriteriaForm.i18n'; import s from './CriteriaForm.module.css'; -type GoalStateProps = ComponentProps['state']; -type TaskTypeProps = NonNullable['type']>; -type TaskStateProps = NonNullable['state']>; - -interface SuggestItem { - id: string; - title: string; - state?: GoalStateProps | TaskStateProps | null; - type?: TaskTypeProps | null; - _shortId: string; -} - -const isGoalStateProps = (props: unknown): props is GoalStateProps => - typeof props === 'object' && props != null && 'lightForeground' in props && 'darkForeground' in props; - -const isTaskStateProps = (props: unknown): props is TaskStateProps => - typeof props === 'object' && props != null && 'src' in props && 'title' in props; +type GoalStateProps = NonNullable['state']>; +type TaskTypeProps = NonNullable['type']>; + +type SuggestItem = + | { + itemType: 'goal'; + id: string; + title: string; + state: GoalStateProps | null; + _shortId: string; + } + | { + itemType: 'task'; + id: string; + title: string; + type: TaskTypeProps; + key: string; + }; interface ValidityData { title: string[]; @@ -101,7 +102,7 @@ function patchZodSchema( mode: z.literal('task'), id: z.string(), selected: z.object({ - externalKey: z.string(), + key: z.string(), }), }), ]) @@ -241,12 +242,9 @@ const CriteriaTitleField: React.FC = ({ const { selected, title } = errors; const icon = useMemo(() => { - if (selectedItem == null) { - return null; - } - + const existItem = selectedItem != null; if (mode === 'goal') { - if (isGoalStateProps(selectedItem.state)) { + if (existItem && selectedItem?.itemType === 'goal') { return ; } @@ -254,8 +252,8 @@ const CriteriaTitleField: React.FC = ({ } if (mode === 'task') { - if (isTaskStateProps(selectedItem.type)) { - return ; + if (existItem && selectedItem?.itemType === 'task') { + return ; } return ; @@ -303,14 +301,6 @@ const CriteriaTitleField: React.FC = ({ ); }; -const isGoalProps = (props: unknown): props is React.ComponentProps => { - return typeof props === 'object' && props != null && '_shortId' in props; -}; - -const isTaskProps = (props: unknown): props is React.ComponentProps => { - return typeof props === 'object' && props != null && 'type' in props; -}; - export const CriteriaForm = ({ onInputChange, onItemChange, @@ -444,18 +434,12 @@ export const CriteriaForm = ({ renderItem={(props) => { const renderData = props.item; - if (mode === 'goal' && isGoalProps(renderData)) { - return ( - - ); + if (mode === 'goal' && renderData.itemType === 'goal') { + return ; } - if (mode === 'task' && isTaskProps(renderData)) { - return ; + if (mode === 'task' && renderData.itemType === 'task') { + return ; } }} > diff --git a/src/components/GoalCriteria/GoalCriteria.tsx b/src/components/GoalCriteria/GoalCriteria.tsx index e7a450d5a..6088bb959 100644 --- a/src/components/GoalCriteria/GoalCriteria.tsx +++ b/src/components/GoalCriteria/GoalCriteria.tsx @@ -34,8 +34,8 @@ import { ActivityFeedItem } from '../ActivityFeed/ActivityFeed'; import { IssueMeta } from '../IssueMeta/IssueMeta'; import { Circle } from '../Circle/Circle'; import { routes } from '../../hooks/router'; -import { GoalBadge } from '../GoalBadge'; -import { TaskBadge } from '../TaskBadge/TaskBadge'; +import { getStateProps, GoalBadge } from '../GoalBadge'; +import { JiraTaskBadge } from '../JiraTaskBadge/JiraTaskBadge'; import { safeUserData } from '../../utils/getUserName'; import { UserBadge } from '../UserBadge/UserBadge'; import { ActivityByIdReturnType } from '../../../trpc/inferredTypes'; @@ -48,7 +48,7 @@ import { tr } from './GoalCriteria.i18n'; type GoalCriteriaSuggestProps = React.ComponentProps; type GoalStateProps = NonNullable['state']>; -type ExternalTaskTypeProps = React.ComponentProps['type']; +type ExternalTaskTypeProps = NonNullable['type']>; type CriteriaUpdateDataMode = NonNullable; interface CriteriaProps { title: string; @@ -70,10 +70,11 @@ interface GoalCriteriaProps extends CriteriaProps { interface ExternalTaskCriteriaProps extends CriteriaProps { externalTask: { + id: string; title: string; externalKey: string; project: string; - type?: ExternalTaskTypeProps; + type: ExternalTaskTypeProps; state?: { color: string | null; title: string; @@ -85,7 +86,7 @@ interface ExternalTaskCriteriaProps extends CriteriaProps { type UnionCriteria = CriteriaProps | GoalCriteriaProps | ExternalTaskCriteriaProps; -type CanBeNullableValue = { +type CanBeNullableValue = { [K in keyof T]: T[K] | null; }; @@ -224,7 +225,7 @@ const ExternalTaskCriteria = ({ title, externalTask, weight, isDone }: Omit - { - if (goal.state == null) { - return null; - } - - const { - state: { lightForeground, darkForeground }, - } = goal; - - if (lightForeground && darkForeground) { - return { - lightForeground, - darkForeground, - }; - } - - return null; - }, [goal]); - return ( <> ['items']; @@ -41,6 +42,7 @@ interface GoalCriteriaSuggestProps { validityData: React.ComponentProps['validityData']; onGoalSelect?: (goal: { id: string }) => void; } +type SuggestItem = React.ComponentProps['items'][number]; export const GoalCriteriaSuggest: React.FC = ({ id, @@ -83,7 +85,7 @@ export const GoalCriteriaSuggest: React.FC = ({ { enabled: mode === 'goal' && shouldEnabledQuery, cacheTime: 0 }, ); - const { data: issues = [] } = trpc.external.search.useQuery( + const { data: issues = [] } = trpc.jira.search.useQuery( { value: query as string, limit: 5, @@ -94,7 +96,7 @@ export const GoalCriteriaSuggest: React.FC = ({ }, ); - const itemsToRender = useMemo(() => { + const itemsToRender = useMemo(() => { if (selectedGoal?.title === query || mode === 'simple') { return []; } @@ -103,20 +105,15 @@ export const GoalCriteriaSuggest: React.FC = ({ return goals.map(({ id, title, state, _shortId }) => ({ id, title, - state: - state?.lightForeground != null && state?.darkForeground != null - ? { - lightForeground: state.lightForeground, - darkForeground: state.darkForeground, - } - : null, + state: getStateProps(state), _shortId, + itemType: 'goal', })); } return issues.map((issue) => ({ ...issue, - _shortId: issue.externalKey, + key: issue.externalKey, state: null, id: issue.externalId, title: issue.title, @@ -124,6 +121,7 @@ export const GoalCriteriaSuggest: React.FC = ({ title: issue.type, src: issue.typeIconUrl, }, + itemType: 'task' as const, })); }, [goals, selectedGoal, query, mode, issues]); diff --git a/src/components/TaskBadge/TaskBadge.module.css b/src/components/JiraTaskBadge/JiraTaskBadge.module.css similarity index 83% rename from src/components/TaskBadge/TaskBadge.module.css rename to src/components/JiraTaskBadge/JiraTaskBadge.module.css index 2b266fc18..ded009049 100644 --- a/src/components/TaskBadge/TaskBadge.module.css +++ b/src/components/JiraTaskBadge/JiraTaskBadge.module.css @@ -1,14 +1,14 @@ -.TaskBadgeType { +.JiraTaskBadgeType { --badge-icon-size: 16px; font-size: 16px; } -.TaskBadgeProjectName { +.JiraTaskBadgeProjectName { /* must be used of current font-size, but not root font-size */ margin-left: 0.25em; } -.TaskBadgeState { +.JiraTaskBadgeState { color: white; background-color: var(--task-badge-color, var(--gray-500)); width: var(--font-size-m); @@ -21,7 +21,7 @@ border-radius: var(--radius-s); } -.TaskBadgeStateSymbol { +.JiraTaskBadgeStateSymbol { vertical-align: 0.125em; line-height: 1rem; } \ No newline at end of file diff --git a/src/components/TaskBadge/TaskBadge.tsx b/src/components/JiraTaskBadge/JiraTaskBadge.tsx similarity index 58% rename from src/components/TaskBadge/TaskBadge.tsx rename to src/components/JiraTaskBadge/JiraTaskBadge.tsx index e83af4f48..6ec476d01 100644 --- a/src/components/TaskBadge/TaskBadge.tsx +++ b/src/components/JiraTaskBadge/JiraTaskBadge.tsx @@ -5,9 +5,9 @@ import { BaseIcon } from '@taskany/icons'; import { NextLink } from '../NextLink'; -import styles from './TaskBadge.module.css'; +import styles from './JiraTaskBadge.module.css'; -interface TaskBadgeProps extends Omit, 'color' | 'title'> { +interface JiraTaskBadgeProps extends Omit, 'color' | 'title'> { title: React.ReactNode; href?: string; type?: { @@ -25,7 +25,7 @@ interface TaskBadgeProps extends Omit, 'co project?: string; } -export const TaskBadgeIcon: React.FC<{ src: string }> = ({ src }) => ( +export const JiraTaskBadgeIcon: React.FC<{ src: string }> = ({ src }) => ( = ({ src }) => ( /> ); -export const TaskBadgeState: React.FC<{ state: string; color?: string | null }> = ({ state, color }) => { +export const JiraTaskBadgeState: React.FC<{ state: string; color?: string | null }> = ({ state, color }) => { const style = color ? ({ '--task-badge-color': color.split('-')[0] } as React.CSSProperties) : {}; return ( - + + {state.slice(0, 1).toUpperCase()} @@ -55,7 +55,20 @@ export const TaskBadgeState: React.FC<{ state: string; color?: string | null }> ); }; -export const TaskBadge: React.FC = ({ +const JiraTaskBadgeLabel: React.FC> = ({ title, project }) => { + return ( + <> + {title} + {nullable(project, (p) => ( + + ({p}) + + ))} + + ); +}; + +export const JiraTaskBadge: React.FC = ({ href, title, children, @@ -72,35 +85,19 @@ export const TaskBadge: React.FC = ({ iconLeft={nullable( type?.src, (src) => ( - + ), - nullable(state, (s) => ), + nullable(state, (s) => ), )} iconRight={children} text={nullable( href, (h) => ( - {title} - {nullable(project, (p) => ( - <> - - ({p}) - - - ))} + ), - <> - {title} - {nullable(project, (p) => ( - <> - - ({p}) - - - ))} - , + , )} action="dynamic" {...attrs} diff --git a/src/utils/integration/jira.ts b/src/utils/integration/jira.ts index bad369111..9fab7bde8 100644 --- a/src/utils/integration/jira.ts +++ b/src/utils/integration/jira.ts @@ -110,7 +110,7 @@ class JiraService extends JiraApi { ); } - /** start overriding private instamce methods */ + /** start overriding private instance methods */ // @ts-ignore private async doRequest(options: any): Promise { if (isDebugEnabled) { @@ -134,14 +134,7 @@ class JiraService extends JiraApi { // @ts-ignore return this.makeRequestHeader(url, options); } - /** end overriding private instamce methods */ - - public async statusesByCategory(categoryId: number) { - const uri = this.getUri({ pathname: '/status' }); - - const res = await this.doRequest(this.getRequestHeaders(uri)); - return res.filter(({ statusCategory }) => statusCategory.id === categoryId); - } + /** end overriding private instance methods */ public checkStatusIsFinished(status: JiraIssueStatus) { return ( @@ -222,5 +215,3 @@ export const searchIssue = async (params: { value: string; limit: number }): Pro ...val.fields, })) as Array; }; - -jiraService.listStatus; diff --git a/trpc/router/index.ts b/trpc/router/index.ts index faa07cc28..a127b6ef4 100644 --- a/trpc/router/index.ts +++ b/trpc/router/index.ts @@ -16,7 +16,7 @@ import { crew } from './crew'; import { appConfig } from './appConfig'; import { project as projectV2 } from './projectV2'; import { goal as goalV2 } from './goalV2'; -import { external } from './external'; +import { jira } from './jira'; export const trpcRouter = router({ filter, @@ -37,7 +37,7 @@ export const trpcRouter = router({ project: projectV2, goal: goalV2, }), - external, + jira, }); export type TrpcRouter = typeof trpcRouter; diff --git a/trpc/router/external.ts b/trpc/router/jira.ts similarity index 98% rename from trpc/router/external.ts rename to trpc/router/jira.ts index c0c7c3a5d..89472e9a8 100644 --- a/trpc/router/external.ts +++ b/trpc/router/jira.ts @@ -29,7 +29,7 @@ const jiraIssueToExternalTask = ( }; }; -export const external = router({ +export const jira = router({ search: protectedProcedure .input( z.object({