From 2833413e79b1dcd11457204d25da93718c410367 Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Wed, 8 Nov 2023 16:54:07 +0200 Subject: [PATCH 01/18] added linked issues components --- apps/mobile/app/components/Accordion.tsx | 48 +++++++++++++++---- .../LinkedIssuesBlock/blocks/ChildIssues.tsx | 32 +++++++++++++ .../blocks/RelatedIssues.tsx | 32 +++++++++++++ .../Task/LinkedIssuesBlock/index.tsx | 15 ++++++ .../Authenticated/TaskScreen/index.tsx | 2 + 5 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/index.tsx diff --git a/apps/mobile/app/components/Accordion.tsx b/apps/mobile/app/components/Accordion.tsx index ca6dc53a9..99064f976 100644 --- a/apps/mobile/app/components/Accordion.tsx +++ b/apps/mobile/app/components/Accordion.tsx @@ -1,11 +1,25 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ import { View, Text, StyleSheet, TouchableOpacity } from "react-native" -import React, { useState } from "react" +import React, { ReactElement, useState } from "react" import { Feather } from "@expo/vector-icons" import { useAppTheme } from "../theme" -const Accordion = ({ children, title }) => { +interface IAccordion { + title: string + children: ReactElement | ReactElement[] + titleFontSize?: number + arrowSize?: number + headerElement?: ReactElement +} + +const Accordion: React.FC = ({ + children, + title, + arrowSize, + titleFontSize, + headerElement, +}) => { const [expanded, setExpanded] = useState(true) const { colors } = useAppTheme() @@ -17,12 +31,29 @@ const Accordion = ({ children, title }) => { return ( - {title} - + + {title} + + + {headerElement} + + {expanded && ( @@ -55,7 +86,6 @@ const styles = StyleSheet.create({ padding: 12, }, accordTitle: { - fontSize: 20, fontWeight: "600", }, }) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx new file mode 100644 index 000000000..195abf36c --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx @@ -0,0 +1,32 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { StyleSheet, Text, View } from "react-native" +import React from "react" +import Accordion from "../../../Accordion" +import { AntDesign, Entypo } from "@expo/vector-icons" + +const ChildIssues = () => { + return ( + + + + + + } + > + ChildIssues + + ) +} + +export default ChildIssues + +const styles = StyleSheet.create({ + headerElement: { alignItems: "center", flexDirection: "row", gap: 10 }, + verticalSeparator: { borderRightColor: "#B1AEBC", borderRightWidth: 1, height: 20 }, +}) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx new file mode 100644 index 000000000..a5dd6a14e --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx @@ -0,0 +1,32 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { StyleSheet, Text, View } from "react-native" +import React from "react" +import Accordion from "../../../Accordion" +import { AntDesign, Entypo } from "@expo/vector-icons" + +const RelatedIssues = () => { + return ( + + + + + + } + > + ChildIssues + + ) +} + +export default RelatedIssues + +const styles = StyleSheet.create({ + headerElement: { alignItems: "center", flexDirection: "row", gap: 10 }, + verticalSeparator: { borderRightColor: "#B1AEBC", borderRightWidth: 1, height: 20 }, +}) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/index.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/index.tsx new file mode 100644 index 000000000..c29c38da5 --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/index.tsx @@ -0,0 +1,15 @@ +import React from "react" +import Accordion from "../../Accordion" +import ChildIssues from "./blocks/ChildIssues" +import RelatedIssues from "./blocks/RelatedIssues" + +const LinkedIssuesBlock = () => { + return ( + + + + + ) +} + +export default LinkedIssuesBlock diff --git a/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx b/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx index 8a3dde007..55c4e22a0 100644 --- a/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx +++ b/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx @@ -10,6 +10,7 @@ import TaskTitleBlock from "../../../components/Task/TitleBlock" import DetailsBlock from "../../../components/Task/DetailsBlock" import { translate } from "../../../i18n" import EstimateBlock from "../../../components/Task/EstimateBlock" +import LinkedIssuesBlock from "../../../components/Task/LinkedIssuesBlock" export const AuthenticatedTaskScreen: FC> = ( _props, @@ -54,6 +55,7 @@ export const AuthenticatedTaskScreen: FC + From fa05150d5aa4cc1fcc55048be56ff0a48f11a587 Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 11:21:18 +0200 Subject: [PATCH 02/18] displaying the child issues --- apps/mobile/app/components/IssuesModal.tsx | 22 ++++++++--- .../LinkedIssuesBlock/blocks/ChildIssues.tsx | 34 ++++++++++++++-- .../blocks/RelatedIssues.tsx | 2 +- .../components/TaskLinkedIssue.tsx | 39 +++++++++++++++++++ apps/mobile/app/components/TaskStatus.tsx | 13 +++++-- 5 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx diff --git a/apps/mobile/app/components/IssuesModal.tsx b/apps/mobile/app/components/IssuesModal.tsx index b1d015c4c..b516a9936 100644 --- a/apps/mobile/app/components/IssuesModal.tsx +++ b/apps/mobile/app/components/IssuesModal.tsx @@ -25,9 +25,16 @@ interface IssuesModalProps { readonly?: boolean nameIncluded?: boolean smallFont?: boolean + relatedIssueIconDimension?: boolean } -const IssuesModal: FC = ({ task, readonly = false, nameIncluded, smallFont }) => { +const IssuesModal: FC = ({ + task, + readonly = false, + nameIncluded, + smallFont, + relatedIssueIconDimension, +}) => { const { allTaskIssues } = useTaskIssue() const [isModalOpen, setIsModalOpen] = useState(false) const { updateTask } = useTeamTasks() @@ -51,6 +58,9 @@ const IssuesModal: FC = ({ task, readonly = false, nameInclude const iconDimension: number = currentIssue?.name === "Bug" ? 15 : currentIssue?.name === "Story" ? 14 : 13 + const smallerIconDimension: number = + currentIssue?.name === "Bug" ? 10 : currentIssue?.name === "Story" ? 10 : 8 + return ( = ({ task, readonly = false, nameInclude styles.wrapButton, { backgroundColor: currentIssue?.color, - height: nameIncluded ? 24 : 20, - width: nameIncluded ? 65 : 20, + height: nameIncluded ? 24 : relatedIssueIconDimension ? 16 : 20, + width: nameIncluded ? 65 : relatedIssueIconDimension ? 16 : 20, paddingVertical: nameIncluded && 2, paddingHorizontal: nameIncluded && 10, flexDirection: "row", alignItems: "center", gap: 2, + borderRadius: relatedIssueIconDimension ? 40 : 3, }, ]} onTouchStart={() => { @@ -74,8 +85,8 @@ const IssuesModal: FC = ({ task, readonly = false, nameInclude }} > @@ -215,7 +226,6 @@ const styles = StyleSheet.create({ }, wrapButton: { alignItems: "center", - borderRadius: 3, justifyContent: "center", }, }) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx index 195abf36c..600c62ee1 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx @@ -1,11 +1,33 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ -import { StyleSheet, Text, View } from "react-native" -import React from "react" +import { StyleSheet, TouchableWithoutFeedback, View } from "react-native" +import React, { useMemo } from "react" import Accordion from "../../../Accordion" import { AntDesign, Entypo } from "@expo/vector-icons" +import { useStores } from "../../../../models" +import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import TaskLinkedIssue from "../components/TaskLinkedIssue" const ChildIssues = () => { + const { + TaskStore: { detailedTask: task }, + } = useStores() + + const { teamTasks: tasks } = useTeamTasks() + + const childTasks = useMemo(() => { + const children = task?.children?.reduce((acc, item) => { + const $item = tasks.find((ts) => ts.id === item.id) || item + if ($item) { + acc.push($item) + } + return acc + }, [] as ITeamTask[]) + + return children || [] + }, [task, tasks]) + return ( { title="Child Issues" headerElement={ - + + + } > - ChildIssues + {childTasks.map((task) => ( + + ))} ) } diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx index a5dd6a14e..1537bb616 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx @@ -19,7 +19,7 @@ const RelatedIssues = () => { } > - ChildIssues + Related Issues ) } diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx new file mode 100644 index 000000000..81138e290 --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx @@ -0,0 +1,39 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { Text, TouchableOpacity, View } from "react-native" +import React from "react" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import IssuesModal from "../../../IssuesModal" +import TaskStatus from "../../../TaskStatus" + +interface ITaskLinkedIssue { + task: ITeamTask +} + +const TaskLinkedIssue: React.FC = ({ task }) => { + return ( + + + + + #{task?.number}- + + {task?.title} + + + + ) +} + +export default TaskLinkedIssue diff --git a/apps/mobile/app/components/TaskStatus.tsx b/apps/mobile/app/components/TaskStatus.tsx index e28f4f9c9..d1d0ffd97 100644 --- a/apps/mobile/app/components/TaskStatus.tsx +++ b/apps/mobile/app/components/TaskStatus.tsx @@ -17,12 +17,13 @@ interface TaskStatusProps { containerStyle?: ViewStyle statusTextSyle?: TextStyle iconsOnly?: boolean + labelOnly?: boolean status?: string setStatus?: (status: string) => unknown } const TaskStatus: FC = observer( - ({ task, containerStyle, status, setStatus, iconsOnly }) => { + ({ task, containerStyle, status, setStatus, iconsOnly, labelOnly }) => { const { colors, dark } = useAppTheme() const { updateTask } = useTeamTasks() const [openModal, setOpenModal] = useState(false) @@ -74,11 +75,15 @@ const TaskStatus: FC = observer( > {statusItem ? ( - {statusItem.icon} + {!labelOnly && statusItem.icon} {iconsOnly ? null : ( {limitTextCharaters({ text: statusItem?.name, @@ -98,7 +103,7 @@ const TaskStatus: FC = observer( )} From 49dbe18f452dc93c903d30a53da24356a2e8c37e Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 12:31:17 +0200 Subject: [PATCH 03/18] displaying the related issues, created hook for action types --- .../components/Task/EstimateBlock/index.tsx | 2 +- .../blocks/RelatedIssues.tsx | 41 ++++- .../components/TaskLinkedIssue.tsx | 151 +++++++++++++++++- apps/mobile/app/i18n/ar.ts | 7 + apps/mobile/app/i18n/bg.ts | 7 + apps/mobile/app/i18n/en.ts | 7 + apps/mobile/app/i18n/es.ts | 7 + apps/mobile/app/i18n/fr.ts | 7 + apps/mobile/app/i18n/he.ts | 7 + apps/mobile/app/i18n/ko.ts | 7 + apps/mobile/app/i18n/ru.ts | 7 + apps/mobile/app/services/interfaces/ITask.ts | 10 ++ 12 files changed, 246 insertions(+), 14 deletions(-) diff --git a/apps/mobile/app/components/Task/EstimateBlock/index.tsx b/apps/mobile/app/components/Task/EstimateBlock/index.tsx index d29fbfad6..704389069 100644 --- a/apps/mobile/app/components/Task/EstimateBlock/index.tsx +++ b/apps/mobile/app/components/Task/EstimateBlock/index.tsx @@ -30,7 +30,7 @@ const EstimateBlock = () => { { + const { + TaskStore: { detailedTask: task }, + } = useStores() + + const { teamTasks: tasks } = useTeamTasks() + + const linkedTasks = useMemo(() => { + const issues = task?.linkedIssues?.reduce((acc, item) => { + const $item = tasks.find((ts) => ts.id === item.taskFrom.id) || item.taskFrom + + if ($item /* && item.action === actionType?.data?.value */) { + acc.push({ + issue: item, + task: $item, + }) + } + + return acc + }, [] as { issue: LinkedTaskIssue; task: ITeamTask }[]) + + return issues || [] + }, [task, tasks]) + return ( - + + + } > - Related Issues + {linkedTasks.map(({ task, issue }) => ( + + ))} ) } diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx index 81138e290..6eaede739 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx @@ -1,19 +1,36 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ -import { Text, TouchableOpacity, View } from "react-native" -import React from "react" -import { ITeamTask } from "../../../../services/interfaces/ITask" +import { StyleSheet, Text, TouchableOpacity, View } from "react-native" +import React, { useCallback, useMemo, useState } from "react" +import { + ITeamTask, + LinkedTaskIssue, + TaskRelatedIssuesRelationEnum, +} from "../../../../services/interfaces/ITask" import IssuesModal from "../../../IssuesModal" import TaskStatus from "../../../TaskStatus" +import { translate } from "../../../../i18n" interface ITaskLinkedIssue { task: ITeamTask + issue?: LinkedTaskIssue + relatedTaskModal?: boolean } -const TaskLinkedIssue: React.FC = ({ task }) => { +const TaskLinkedIssue: React.FC = ({ task, issue, relatedTaskModal }) => { + const { actionType, actionTypeItems, onChange } = useActionType( + issue?.action || TaskRelatedIssuesRelationEnum.RELATES_TO, + issue, + ) + return ( @@ -22,13 +39,14 @@ const TaskLinkedIssue: React.FC = ({ task }) => { {task?.title} + {relatedTaskModal && issue && ( + {actionType.data.name} + )} @@ -37,3 +55,120 @@ const TaskLinkedIssue: React.FC = ({ task }) => { } export default TaskLinkedIssue + +type ActionType = { name: string; value: TaskRelatedIssuesRelationEnum } +export type DropdownItem> = { + key: React.Key + Label: (props: { active?: boolean; selected?: boolean }) => JSX.Element + selectedLabel?: React.ReactNode + itemTitle?: string + disabled?: boolean + data?: D +} +type ActionTypeItem = DropdownItem + +function mapToActionType(items: ActionType[] = []) { + return items.map((item) => { + return { + key: item.value, + Label: () => { + return ( + + {item.name} + + ) + }, + selectedLabel: {item.name}, + data: item, + } + }) +} + +function useActionType( + defaultValue: TaskRelatedIssuesRelationEnum, + issue: LinkedTaskIssue | undefined, +) { + // const { queryCall } = useQuery(updateTaskLinkedIssueAPI) + + const actionsTypes = useMemo( + () => [ + { + name: translate("taskDetailsScreen.blocks"), + value: TaskRelatedIssuesRelationEnum.BLOCKS, + }, + { + name: translate("taskDetailsScreen.clones"), + value: TaskRelatedIssuesRelationEnum.CLONES, + }, + { + name: translate("taskDetailsScreen.duplicates"), + value: TaskRelatedIssuesRelationEnum.DUPLICATES, + }, + { + name: translate("taskDetailsScreen.isBlockedBy"), + value: TaskRelatedIssuesRelationEnum.IS_BLOCKED_BY, + }, + { + name: translate("taskDetailsScreen.isClonedBy"), + value: TaskRelatedIssuesRelationEnum.IS_CLONED_BY, + }, + { + name: translate("taskDetailsScreen.isDuplicatedBy"), + value: TaskRelatedIssuesRelationEnum.IS_DUPLICATED_BY, + }, + { + name: translate("taskDetailsScreen.relatesTo"), + value: TaskRelatedIssuesRelationEnum.RELATES_TO, + }, + ], + + [], + ) + + const actionTypeItems = useMemo(() => mapToActionType(actionsTypes), [actionsTypes]) + + const relatedToItem = useMemo( + () => actionTypeItems.find((t) => t.key === defaultValue), + [actionTypeItems, defaultValue], + ) + + const [actionType, setActionType] = useState(relatedToItem || null) + + const onChange = useCallback( + (item: ActionTypeItem) => { + if (!issue || !item.data?.value) { + return + } + setActionType(item) + + // queryCall({ + // ...issue, + // action: item.data?.value, + // }) + }, + [setActionType, issue], + ) + + return { + actionTypeItems, + actionType, + onChange, + } +} + +const styles = StyleSheet.create({ + button: { + borderBottomColor: "#00000014", + borderBottomWidth: 1, + flexDirection: "column", + marginBottom: 10, + paddingVertical: 5, + }, + labelText: { + fontSize: 16, + }, + selectedLabelText: { + fontSize: 10, + }, + taskStatus: { borderRadius: 3, minHeight: 20, width: 80 }, +}) diff --git a/apps/mobile/app/i18n/ar.ts b/apps/mobile/app/i18n/ar.ts index 58147df14..60798ce06 100644 --- a/apps/mobile/app/i18n/ar.ts +++ b/apps/mobile/app/i18n/ar.ts @@ -126,6 +126,13 @@ const ar: Translations = { timeToday: "الوقت اليوم", totalGroupTime: "إجمالي وقت المجموعة", timeRemaining: "الوقت المتبقي", + blocks: "كتل", + clones: "استنساخ", + duplicates: "تكرار", + isBlockedBy: "محظور بواسطة", + isClonedBy: "مستنسخ بواسطة", + isDuplicatedBy: "مكرر بواسطة", + relatesTo: "يتصل بـ", }, tasksScreen: { name: "مهام", diff --git a/apps/mobile/app/i18n/bg.ts b/apps/mobile/app/i18n/bg.ts index c8d19d716..9e7d6b2f4 100644 --- a/apps/mobile/app/i18n/bg.ts +++ b/apps/mobile/app/i18n/bg.ts @@ -122,6 +122,13 @@ const bg = { timeToday: "Time Today", totalGroupTime: "Total Group Time", timeRemaining: "Time Remaining", + blocks: "Blocks", + clones: "Clones", + duplicates: "Duplicates", + isBlockedBy: "Is Blocked By", + isClonedBy: "Is Cloned By", + isDuplicatedBy: "Is Duplicated By", + relatesTo: "Relates To", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/en.ts b/apps/mobile/app/i18n/en.ts index d5b3f878e..e5ba0dd91 100644 --- a/apps/mobile/app/i18n/en.ts +++ b/apps/mobile/app/i18n/en.ts @@ -123,6 +123,13 @@ const en = { timeToday: "Time Today", totalGroupTime: "Total Group Time", timeRemaining: "Time Remaining", + blocks: "Blocks", + clones: "Clones", + duplicates: "Duplicates", + isBlockedBy: "Is Blocked By", + isClonedBy: "Is Cloned By", + isDuplicatedBy: "Is Duplicated By", + relatesTo: "Relates To", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/es.ts b/apps/mobile/app/i18n/es.ts index 5ee607ce1..ad803defe 100644 --- a/apps/mobile/app/i18n/es.ts +++ b/apps/mobile/app/i18n/es.ts @@ -122,6 +122,13 @@ const es = { timeToday: "Time Today", totalGroupTime: "Total Group Time", timeRemaining: "Time Remaining", + blocks: "Blocks", + clones: "Clones", + duplicates: "Duplicates", + isBlockedBy: "Is Blocked By", + isClonedBy: "Is Cloned By", + isDuplicatedBy: "Is Duplicated By", + relatesTo: "Relates To", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/fr.ts b/apps/mobile/app/i18n/fr.ts index daced36d4..88aa23217 100644 --- a/apps/mobile/app/i18n/fr.ts +++ b/apps/mobile/app/i18n/fr.ts @@ -125,6 +125,13 @@ const fr = { timeToday: "Temps Aujourd'hui", totalGroupTime: "Temps Total du Groupe", timeRemaining: "Temps Restant", + blocks: "Blocs", + clones: "Clones", + duplicates: "Doublons", + isBlockedBy: "Est bloqué par", + isClonedBy: "Est cloné par", + isDuplicatedBy: "Est dupliqué par", + relatesTo: "Se rapporte à", }, tasksScreen: { name: "Tâches", diff --git a/apps/mobile/app/i18n/he.ts b/apps/mobile/app/i18n/he.ts index cc12cdced..62f442d9f 100644 --- a/apps/mobile/app/i18n/he.ts +++ b/apps/mobile/app/i18n/he.ts @@ -122,6 +122,13 @@ const he = { timeToday: "Time Today", totalGroupTime: "Total Group Time", timeRemaining: "Time Remaining", + blocks: "Blocks", + clones: "Clones", + duplicates: "Duplicates", + isBlockedBy: "Is Blocked By", + isClonedBy: "Is Cloned By", + isDuplicatedBy: "Is Duplicated By", + relatesTo: "Relates To", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/ko.ts b/apps/mobile/app/i18n/ko.ts index 99bdb9215..79641be6e 100644 --- a/apps/mobile/app/i18n/ko.ts +++ b/apps/mobile/app/i18n/ko.ts @@ -125,6 +125,13 @@ const ko: Translations = { timeToday: "오늘의 시간", totalGroupTime: "총 그룹 시간", timeRemaining: "남은 시간", + blocks: "블록", + clones: "복제", + duplicates: "중복", + isBlockedBy: "차단됨", + isClonedBy: "복제됨", + isDuplicatedBy: "복제됨", + relatesTo: "관련이 있다", }, tasksScreen: { name: "작업", diff --git a/apps/mobile/app/i18n/ru.ts b/apps/mobile/app/i18n/ru.ts index 93e91f3ad..e19f1bd05 100644 --- a/apps/mobile/app/i18n/ru.ts +++ b/apps/mobile/app/i18n/ru.ts @@ -122,6 +122,13 @@ const ru = { timeToday: "Time Today", totalGroupTime: "Total Group Time", timeRemaining: "Time Remaining", + blocks: "Blocks", + clones: "Clones", + duplicates: "Duplicates", + isBlockedBy: "Is Blocked By", + isClonedBy: "Is Cloned By", + isDuplicatedBy: "Is Duplicated By", + relatesTo: "Relates To", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/services/interfaces/ITask.ts b/apps/mobile/app/services/interfaces/ITask.ts index b6c13fc88..941e6fa4c 100644 --- a/apps/mobile/app/services/interfaces/ITask.ts +++ b/apps/mobile/app/services/interfaces/ITask.ts @@ -161,3 +161,13 @@ export interface IParamsStatistic { tenantId: string activeTask: boolean } + +export enum TaskRelatedIssuesRelationEnum { + IS_BLOCKED_BY = 1, + BLOCKS = 2, + IS_CLONED_BY = 3, + CLONES = 4, + IS_DUPLICATED_BY = 5, + DUPLICATES = 6, + RELATES_TO = 7, +} From 4a4abe384617e57d6109b986fb15d677d486de8c Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 13:51:48 +0200 Subject: [PATCH 04/18] added action types modal --- .../components/ActionTypesModal.tsx | 150 ++++++++++++++++++ .../components/TaskLinkedIssue.tsx | 45 ++++-- 2 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx new file mode 100644 index 000000000..0f77c5de1 --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx @@ -0,0 +1,150 @@ +/* eslint-disable react-native/no-color-literals */ +/* eslint-disable react-native/no-inline-styles */ +import { + View, + Text, + TouchableWithoutFeedback, + Animated, + Modal, + ViewStyle, + TouchableOpacity, + StyleSheet, +} from "react-native" +import React, { useState } from "react" +import { BlurView } from "expo-blur" +import { ActionTypeItem } from "./TaskLinkedIssue" +import { useAppTheme } from "../../../../theme" +import { AntDesign } from "@expo/vector-icons" + +interface IActionTypesModal { + actionType: ActionTypeItem + actionItems: ActionTypeItem[] + onChange: (item: ActionTypeItem) => void +} + +const ActionTypesModal: React.FC = ({ actionType, actionItems, onChange }) => { + const [modalOpen, setModalOpen] = useState(false) + + const { colors } = useAppTheme() + + return ( + + setModalOpen(true)} + > + + {actionType.data.name} + + + + + setModalOpen(false)}> + + {actionItems.map((actionItem, idx) => ( + + ))} + + + + ) +} + +export default ActionTypesModal + +interface IItem { + actionItem: ActionTypeItem + onChange: (item: ActionTypeItem) => void +} + +const Item: React.FC = ({ actionItem, onChange }) => { + const { colors } = useAppTheme() + return ( + + onChange(actionItem)} + > + {actionItem.data.name} + + + ) +} + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + return ( + + + onDismiss()}> + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + button: { + alignItems: "center", + borderRadius: 3, + borderWidth: 1, + flexDirection: "row", + gap: 5, + height: 20, + justifyContent: "space-between", + paddingHorizontal: 7, + paddingVertical: 4, + }, + container: { + alignSelf: "center", + backgroundColor: "#fff", + borderRadius: 20, + gap: 5, + maxHeight: 396, + padding: 10, + width: "50%", + }, +}) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx index 6eaede739..c93127f2c 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx @@ -10,6 +10,8 @@ import { import IssuesModal from "../../../IssuesModal" import TaskStatus from "../../../TaskStatus" import { translate } from "../../../../i18n" +import ActionTypesModal from "./ActionTypesModal" +import { limitTextCharaters } from "../../../../helpers/sub-text" interface ITaskLinkedIssue { task: ITeamTask @@ -37,26 +39,43 @@ const TaskLinkedIssue: React.FC = ({ task, issue, relatedTaskM #{task?.number}- - {task?.title} + + {limitTextCharaters({ + text: task?.title, + numChars: relatedTaskModal ? 23 : 30, + })} + - {relatedTaskModal && issue && ( - {actionType.data.name} - )} - + > + {relatedTaskModal && issue && ( + + )} + + ) } export default TaskLinkedIssue -type ActionType = { name: string; value: TaskRelatedIssuesRelationEnum } +export type ActionType = { name: string; value: TaskRelatedIssuesRelationEnum } export type DropdownItem> = { key: React.Key Label: (props: { active?: boolean; selected?: boolean }) => JSX.Element @@ -65,7 +84,7 @@ export type DropdownItem> = { disabled?: boolean data?: D } -type ActionTypeItem = DropdownItem +export type ActionTypeItem = DropdownItem function mapToActionType(items: ActionType[] = []) { return items.map((item) => { From 6413ee3c026ef1bb0a39e2622c1a880314be7779 Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 15:25:05 +0200 Subject: [PATCH 05/18] created linked issues modal and added it to child issues block --- .../LinkedIssuesBlock/blocks/ChildIssues.tsx | 45 ++- .../components/ActionTypesModal.tsx | 3 +- .../components/CreateLinkedIssueModal.tsx | 257 ++++++++++++++++++ .../Task/TitleBlock/CreateParentTaskModal.tsx | 2 +- .../TimerScreen/components/ComboBox.tsx | 11 +- .../TimerScreen/components/IndividualTask.tsx | 12 +- 6 files changed, 320 insertions(+), 10 deletions(-) create mode 100644 apps/mobile/app/components/Task/LinkedIssuesBlock/components/CreateLinkedIssueModal.tsx diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx index 600c62ee1..d55795c35 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx @@ -1,21 +1,23 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ import { StyleSheet, TouchableWithoutFeedback, View } from "react-native" -import React, { useMemo } from "react" +import React, { useCallback, useMemo, useState } from "react" import Accordion from "../../../Accordion" import { AntDesign, Entypo } from "@expo/vector-icons" import { useStores } from "../../../../models" import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" import { ITeamTask } from "../../../../services/interfaces/ITask" import TaskLinkedIssue from "../components/TaskLinkedIssue" +import CreateLinkedIssueModal from "../components/CreateLinkedIssueModal" const ChildIssues = () => { const { TaskStore: { detailedTask: task }, } = useStores() - const { teamTasks: tasks } = useTeamTasks() + const [modalOpen, setModalOpen] = useState(false) + const childTasks = useMemo(() => { const children = task?.children?.reduce((acc, item) => { const $item = tasks.find((ts) => ts.id === item.id) || item @@ -28,6 +30,33 @@ const ChildIssues = () => { return children || [] }, [task, tasks]) + const onTaskSelect = useCallback((childTask: ITeamTask | undefined) => { + console.log(childTask) + setModalOpen(false) + }, []) + + const isTaskEpic = task?.issueType === "Epic" + const isTaskStory = task?.issueType === "Story" + const childrenTasks = task?.children?.map((t) => t.id) || [] + + const unchildTasks = tasks.filter((childTask) => { + const hasChild = () => { + if (isTaskEpic) { + return childTask.issueType !== "Epic" + } else if (isTaskStory) { + return childTask.issueType !== "Epic" && childTask.issueType !== "Story" + } else { + return ( + childTask.issueType === "Bug" || + childTask.issueType === "Task" || + childTask.issueType === null + ) + } + } + + return childTask.id !== task.id && !childrenTasks.includes(childTask.id) && hasChild() + }) + return ( { title="Child Issues" headerElement={ - + setModalOpen(true)}> + + {task && ( + setModalOpen(false)} + /> + )} } > diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx index 0f77c5de1..cbd7c87c4 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/ActionTypesModal.tsx @@ -40,7 +40,7 @@ const ActionTypesModal: React.FC = ({ actionType, actionItems setModalOpen(false)}> - + {actionItems.map((actionItem, idx) => ( ))} @@ -140,7 +140,6 @@ const styles = StyleSheet.create({ }, container: { alignSelf: "center", - backgroundColor: "#fff", borderRadius: 20, gap: 5, maxHeight: 396, diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/CreateLinkedIssueModal.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/CreateLinkedIssueModal.tsx new file mode 100644 index 000000000..fd99c7b71 --- /dev/null +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/CreateLinkedIssueModal.tsx @@ -0,0 +1,257 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { + View, + Text, + Animated, + Modal, + TouchableWithoutFeedback, + ViewStyle, + StyleSheet, + ActivityIndicator, + Pressable, + TextInput, +} from "react-native" +import React, { useCallback, useEffect, useRef, useState } from "react" +import ComboBox from "../../../../screens/Authenticated/TimerScreen/components/ComboBox" +import { translate } from "../../../../i18n" +import IssuesModal from "../../../IssuesModal" +import { useStores } from "../../../../models" +import { useTaskInput } from "../../../../services/hooks/features/useTaskInput" +import { Feather } from "@expo/vector-icons" +import { ITeamTask } from "../../../../services/interfaces/ITask" +import { BlurView } from "expo-blur" +import { useAppTheme, typography } from "../../../../theme" + +interface ICreateLinkedIssueModal { + visible: boolean + onDismiss: () => void + task: ITeamTask + taskItems?: ITeamTask[] + onTaskPress?: (childTask: ITeamTask) => void +} + +const CreateLinkedIssueModal: React.FC = ({ + visible, + onDismiss, + task, + taskItems, + onTaskPress, +}) => { + const { colors } = useAppTheme() + + const taskInput = useTaskInput() + const { + setEditMode, + setQuery, + activeTask, + editMode, + // isModalOpen, + hasCreateForm, + handleTaskCreation, + createLoading, + } = taskInput + + const [combxShow, setCombxShow] = useState(true) + const inputRef = useRef(null) + const { + TimerStore: { localTimerStatus }, + } = useStores() + + const closeCombox = useCallback(() => { + setCombxShow(false) + }, [setCombxShow]) + + useEffect(() => { + setEditMode(true) + setCombxShow(true) + }, [editMode, combxShow]) + + useEffect(() => { + if (!editMode) { + inputRef.current?.blur() + } + }, [editMode]) + + return ( + + + + + + + {!editMode && activeTask ? `#${activeTask.taskNumber} ` : ""} + + setEditMode(true)} + // onBlur={() => setEditMode(false)} + onChangeText={(newText) => setQuery(newText)} + /> + {hasCreateForm && editMode && !createLoading ? ( + { + handleTaskCreation() + // setEditMode(false) + }} + > + + + ) : null} + + {createLoading ? ( + + ) : null} + + {combxShow && ( + + )} + + + ) +} + +export default CreateLinkedIssueModal + +const ModalPopUp = ({ visible, children, onDismiss }) => { + const [showModal, setShowModal] = React.useState(visible) + const scaleValue = React.useRef(new Animated.Value(0)).current + const modalRef = useRef(null) + + React.useEffect(() => { + toggleModal() + }, [visible]) + const toggleModal = () => { + if (visible) { + setShowModal(true) + Animated.spring(scaleValue, { + toValue: 1, + useNativeDriver: true, + }).start() + } else { + setTimeout(() => setShowModal(false), 200) + Animated.timing(scaleValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start() + } + } + + const handlePressOutside = (event) => { + const { locationX, locationY } = event.nativeEvent + + if (modalRef.current) { + modalRef.current.measureInWindow((x, y, width, height) => { + if ( + locationX < x || + locationX > x + width || + locationY < y || + locationY > y + height + ) { + onDismiss() + } + }) + } + } + return ( + + + + + + {children} + + + + + ) +} + +const $modalBackGround: ViewStyle = { + flex: 1, + justifyContent: "center", +} + +const styles = StyleSheet.create({ + container: { + alignSelf: "center", + borderRadius: 20, + padding: 20, + width: "90%", + }, + loading: { + right: 10, + }, + taskNumberStyle: { + color: "#7B8089", + fontFamily: typography.primary.semiBold, + fontSize: 14, + marginLeft: 5, + }, + textInput: { + backgroundColor: "#fff", + borderRadius: 10, + color: "rgba(40, 32, 72, 0.4)", + fontFamily: typography.fonts.PlusJakartaSans.semiBold, + fontSize: 12, + height: 43, + paddingHorizontal: 6, + paddingVertical: 13, + width: "80%", + }, + wrapInput: { + backgroundColor: "#fff", + borderColor: "rgba(0, 0, 0, 0.1)", + borderRadius: 10, + borderWidth: 1, + height: 45, + paddingHorizontal: 16, + paddingVertical: 2, + width: "100%", + }, +}) diff --git a/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx b/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx index c3ffcc69d..fb5f9e993 100644 --- a/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx +++ b/apps/mobile/app/components/Task/TitleBlock/CreateParentTaskModal.tsx @@ -192,7 +192,7 @@ const ModalPopUp = ({ visible, children, onDismiss }) => { }} /> - + {children} diff --git a/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx b/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx index ab670ff95..f6a3c294a 100644 --- a/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx +++ b/apps/mobile/app/screens/Authenticated/TimerScreen/components/ComboBox.tsx @@ -20,6 +20,8 @@ export interface Props { parentTasksFilter?: boolean childTask?: ITeamTask onDismiss?: () => void + linkedTaskItems?: ITeamTask[] + onTaskPress?: (relatedTask: ITeamTask) => void } const ComboBox: FC = observer(function ComboBox({ @@ -29,6 +31,8 @@ const ComboBox: FC = observer(function ComboBox({ parentTasksFilter, childTask, onDismiss, + linkedTaskItems, + onTaskPress, }) { const { colors } = useAppTheme() const [isScrolling, setIsScrolling] = useState(false) @@ -70,13 +74,16 @@ const ComboBox: FC = observer(function ComboBox({ onScrollEndDrag={() => setIsScrolling(false)} showsVerticalScrollIndicator={false} data={ - parentTasksFilter + linkedTaskItems || + (parentTasksFilter ? tasksHandler.filteredEpicTasks - : tasksHandler.filteredTasks + : tasksHandler.filteredTasks) } renderItem={({ item, index }) => ( void + onTaskPress?: (relatedTask: ITeamTask) => void + isLinkedTasks: boolean } const IndividualTask: FC = observer( @@ -39,6 +41,8 @@ const IndividualTask: FC = observer( parentTasksFilter, childTask, onDismiss, + onTaskPress, + isLinkedTasks, }) => { const { colors } = useAppTheme() const [showDel, setShowDel] = useState(false) @@ -93,7 +97,9 @@ const IndividualTask: FC = observer( > { - !parentTasksFilter + isLinkedTasks + ? onTaskPress(task) + : !parentTasksFilter ? !isScrolling && handleActiveTask(task) : !isScrolling && setParent(task, childTask, onDismiss) }} // added it here because doesn't work when assigned to the parent @@ -117,7 +123,9 @@ const IndividualTask: FC = observer( { - !parentTasksFilter + isLinkedTasks + ? onTaskPress(task) + : !parentTasksFilter ? !isScrolling && handleActiveTask(task) : !isScrolling && setParent(task, childTask, onDismiss) }} // added it here because doesn't work when assigned to the parent From 2e1fa306c52394dfd316a5fd84665470b085cfcf Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 15:43:50 +0200 Subject: [PATCH 06/18] added the create linked issues modal to related tasks block --- .../blocks/RelatedIssues.tsx | 44 ++++++++++++++++++- .../TimerScreen/components/IndividualTask.tsx | 4 +- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx index 1ebb897c3..fb56b22e1 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/RelatedIssues.tsx @@ -1,13 +1,14 @@ /* eslint-disable react-native/no-color-literals */ /* eslint-disable react-native/no-inline-styles */ import { StyleSheet, TouchableWithoutFeedback, View } from "react-native" -import React, { useMemo } from "react" +import React, { useCallback, useMemo, useState } from "react" import Accordion from "../../../Accordion" import { AntDesign, Entypo } from "@expo/vector-icons" import { useStores } from "../../../../models" import { useTeamTasks } from "../../../../services/hooks/features/useTeamTasks" import { ITeamTask, LinkedTaskIssue } from "../../../../services/interfaces/ITask" import TaskLinkedIssue from "../components/TaskLinkedIssue" +import CreateLinkedIssueModal from "../components/CreateLinkedIssueModal" const RelatedIssues = () => { const { @@ -16,6 +17,8 @@ const RelatedIssues = () => { const { teamTasks: tasks } = useTeamTasks() + const [modalOpen, setModalOpen] = useState(false) + const linkedTasks = useMemo(() => { const issues = task?.linkedIssues?.reduce((acc, item) => { const $item = tasks.find((ts) => ts.id === item.taskFrom.id) || item.taskFrom @@ -33,6 +36,33 @@ const RelatedIssues = () => { return issues || [] }, [task, tasks]) + const onTaskSelect = useCallback((childTask: ITeamTask | undefined) => { + console.log(childTask) + setModalOpen(false) + }, []) + + const isTaskEpic = task?.issueType === "Epic" + const isTaskStory = task?.issueType === "Story" + const linkedTasksItems = task?.linkedIssues?.map((t) => t.taskFrom.id) || [] + + const unlinkedTasks = tasks.filter((childTask) => { + const hasChild = () => { + if (isTaskEpic) { + return childTask.issueType !== "Epic" + } else if (isTaskStory) { + return childTask.issueType !== "Epic" && childTask.issueType !== "Story" + } else { + return ( + childTask.issueType === "Bug" || + childTask.issueType === "Task" || + childTask.issueType === null + ) + } + } + + return childTask.id !== task.id && !linkedTasksItems.includes(childTask.id) && hasChild() + }) + return ( { title="Related Issues" headerElement={ - + setModalOpen(true)}> + + {task && ( + setModalOpen(false)} + /> + )} } > diff --git a/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx b/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx index 8ee6ef189..c08a4cd0e 100644 --- a/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx +++ b/apps/mobile/app/screens/Authenticated/TimerScreen/components/IndividualTask.tsx @@ -98,7 +98,7 @@ const IndividualTask: FC = observer( { isLinkedTasks - ? onTaskPress(task) + ? !isScrolling && onTaskPress(task) : !parentTasksFilter ? !isScrolling && handleActiveTask(task) : !isScrolling && setParent(task, childTask, onDismiss) @@ -124,7 +124,7 @@ const IndividualTask: FC = observer( { isLinkedTasks - ? onTaskPress(task) + ? !isScrolling && onTaskPress(task) : !parentTasksFilter ? !isScrolling && handleActiveTask(task) : !isScrolling && setParent(task, childTask, onDismiss) From 638b0e968c42f856a92188d0ea373734a335fd7f Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 17:56:14 +0200 Subject: [PATCH 07/18] added function to add children to parent, also added loading effect --- .../LinkedIssuesBlock/blocks/ChildIssues.tsx | 18 +++++++++++---- .../components/CreateLinkedIssueModal.tsx | 23 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx index d55795c35..4419bd955 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/blocks/ChildIssues.tsx @@ -14,9 +14,10 @@ const ChildIssues = () => { const { TaskStore: { detailedTask: task }, } = useStores() - const { teamTasks: tasks } = useTeamTasks() + const { teamTasks: tasks, updateTask } = useTeamTasks() const [modalOpen, setModalOpen] = useState(false) + const [isLoading, setIsLoading] = useState(false) const childTasks = useMemo(() => { const children = task?.children?.reduce((acc, item) => { @@ -30,9 +31,17 @@ const ChildIssues = () => { return children || [] }, [task, tasks]) - const onTaskSelect = useCallback((childTask: ITeamTask | undefined) => { - console.log(childTask) - setModalOpen(false) + const onTaskSelect = useCallback(async (childTask: ITeamTask | undefined) => { + setIsLoading(true) + const updatedTask: ITeamTask = { + ...childTask, + parentId: task?.id, + parent: task, + } + await updateTask(updatedTask, childTask.id).finally(() => { + setIsLoading(false) + setModalOpen(false) + }) }, []) const isTaskEpic = task?.issueType === "Epic" @@ -72,6 +81,7 @@ const ChildIssues = () => { {task && ( void + isLoading?: boolean } const CreateLinkedIssueModal: React.FC = ({ @@ -37,6 +38,7 @@ const CreateLinkedIssueModal: React.FC = ({ task, taskItems, onTaskPress, + isLoading, }) => { const { colors } = useAppTheme() @@ -74,7 +76,7 @@ const CreateLinkedIssueModal: React.FC = ({ }, [editMode]) return ( - + = ({ export default CreateLinkedIssueModal -const ModalPopUp = ({ visible, children, onDismiss }) => { +interface IModal { + visible: boolean + children: ReactElement[] | ReactElement + onDismiss: () => void + isLoading: boolean +} + +const ModalPopUp: React.FC = ({ visible, children, onDismiss, isLoading }) => { const [showModal, setShowModal] = React.useState(visible) const scaleValue = React.useRef(new Animated.Value(0)).current const modalRef = useRef(null) + const { colors } = useAppTheme() React.useEffect(() => { toggleModal() @@ -199,8 +209,13 @@ const ModalPopUp = ({ visible, children, onDismiss }) => { position: "absolute", width: "100%", height: "100%", + justifyContent: "center", + alignItems: "center", + zIndex: isLoading && 100, }} - /> + > + {isLoading && } + From 4ba5c7698b6bd9322b8a102773586cf6783b8efb Mon Sep 17 00:00:00 2001 From: desperado1802 Date: Thu, 9 Nov 2023 20:06:03 +0200 Subject: [PATCH 08/18] added the task link api's, hok to use them and implemented the update linked --- .../components/TaskLinkedIssue.tsx | 14 +++--- .../client/requests/task-linked-issue.ts | 31 +++++++++++++ .../hooks/features/useTaskLinkedIssue.ts | 44 +++++++++++++++++++ apps/mobile/app/services/interfaces/ITask.ts | 19 ++++++++ 4 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 apps/mobile/app/services/client/requests/task-linked-issue.ts create mode 100644 apps/mobile/app/services/hooks/features/useTaskLinkedIssue.ts diff --git a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx index c93127f2c..3d83500f8 100644 --- a/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx +++ b/apps/mobile/app/components/Task/LinkedIssuesBlock/components/TaskLinkedIssue.tsx @@ -12,6 +12,7 @@ import TaskStatus from "../../../TaskStatus" import { translate } from "../../../../i18n" import ActionTypesModal from "./ActionTypesModal" import { limitTextCharaters } from "../../../../helpers/sub-text" +import { useTaskLinkedIssues } from "../../../../services/hooks/features/useTaskLinkedIssue" interface ITaskLinkedIssue { task: ITeamTask @@ -109,6 +110,8 @@ function useActionType( ) { // const { queryCall } = useQuery(updateTaskLinkedIssueAPI) + const { updateTaskLinkedIssue } = useTaskLinkedIssues() + const actionsTypes = useMemo( () => [ { @@ -154,16 +157,17 @@ function useActionType( const [actionType, setActionType] = useState(relatedToItem || null) const onChange = useCallback( - (item: ActionTypeItem) => { + async (item: ActionTypeItem) => { if (!issue || !item.data?.value) { return } setActionType(item) - // queryCall({ - // ...issue, - // action: item.data?.value, - // }) + const updatedAction = { + ...issue, + action: item.data?.value, + } + await updateTaskLinkedIssue(updatedAction) }, [setActionType, issue], ) diff --git a/apps/mobile/app/services/client/requests/task-linked-issue.ts b/apps/mobile/app/services/client/requests/task-linked-issue.ts new file mode 100644 index 000000000..e14a8575d --- /dev/null +++ b/apps/mobile/app/services/client/requests/task-linked-issue.ts @@ -0,0 +1,31 @@ +/* eslint-disable camelcase */ +import { ITaskLinkedIssue, ITaskLinkedIssueResponse, LinkedTaskIssue } from "../../interfaces/ITask" +import { serverFetch } from "../fetch" + +export function createTaskLinkedIsssue( + data: ITaskLinkedIssue, + bearer_token: string, + tenantId?: string, +) { + return serverFetch({ + path: "/task-linked-issue", + method: "POST", + body: data, + bearer_token, + tenantId, + }) +} + +export function updateTaskLinkedIssue( + data: LinkedTaskIssue, + bearer_token: string, + tenantId?: string, +) { + return serverFetch({ + path: `/task-linked-issue/${data.id}`, + method: "PUT", + body: data, + bearer_token, + tenantId, + }) +} diff --git a/apps/mobile/app/services/hooks/features/useTaskLinkedIssue.ts b/apps/mobile/app/services/hooks/features/useTaskLinkedIssue.ts new file mode 100644 index 000000000..6eef2c714 --- /dev/null +++ b/apps/mobile/app/services/hooks/features/useTaskLinkedIssue.ts @@ -0,0 +1,44 @@ +import { useState } from "react" +import { + createTaskLinkedIsssue, + updateTaskLinkedIssue, +} from "../../client/requests/task-linked-issue" +import { useStores } from "../../../models" + +export const useTaskLinkedIssues = () => { + const { + authenticationStore: { authToken, tenantId }, + } = useStores() + + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const createTaskLinkedIssueRequest = async (data) => { + try { + setLoading(true) + await createTaskLinkedIsssue(data, authToken, tenantId) + } catch (err) { + setError(err) + } finally { + setLoading(false) + } + } + + const updateTaskLinkedIssueRequest = async (data) => { + try { + setLoading(true) + await updateTaskLinkedIssue(data, authToken, tenantId) + } catch (err) { + setError(err) + } finally { + setLoading(false) + } + } + + return { + loading, + error, + createTaskLinkedIssue: createTaskLinkedIssueRequest, + updateTaskLinkedIssue: updateTaskLinkedIssueRequest, + } +} diff --git a/apps/mobile/app/services/interfaces/ITask.ts b/apps/mobile/app/services/interfaces/ITask.ts index 941e6fa4c..25776315b 100644 --- a/apps/mobile/app/services/interfaces/ITask.ts +++ b/apps/mobile/app/services/interfaces/ITask.ts @@ -88,6 +88,25 @@ export type LinkedTaskIssue = { taskFrom: Omit } +export interface ITaskLinkedIssue { + organizationId: string + taskToId: string + taskFromId: string + action: number +} + +export interface ITaskLinkedIssueResponse { + tenantId: string + organizationId: string + action: number + taskFromId: string + taskToId: string + tenant: { id: string } + id: string + createdAt: string + updatedAt: string +} + export type ITaskPriority = "Highest" | "High" | "Medium" | "Low" | "Lowest" export type IVersionProperty = "Version 1" | "Version 2" From ba5b72fd7c2dc92dca6b7ce2b32e9aa784ae2424 Mon Sep 17 00:00:00 2001 From: Badal Khatri <81486442+badalkhatri0924@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:30:08 +0530 Subject: [PATCH 09/18] Fix/updated GitHub integration UI (#1754) * Updated github integration UI * Updated Text * fix: Build issue --- apps/web/lib/settings/integration-setting.tsx | 215 ++++++++++-------- apps/web/pages/settings/team.tsx | 2 +- apps/web/public/locales/ar/common.json | 5 +- apps/web/public/locales/bg/common.json | 5 +- apps/web/public/locales/de/common.json | 5 +- apps/web/public/locales/en/common.json | 6 +- apps/web/public/locales/es/common.json | 5 +- apps/web/public/locales/fr/common.json | 5 +- apps/web/public/locales/he/common.json | 5 +- apps/web/public/locales/it/common.json | 5 +- apps/web/public/locales/nl/common.json | 5 +- apps/web/public/locales/pl/common.json | 5 +- apps/web/public/locales/pt/common.json | 5 +- apps/web/public/locales/ru/common.json | 5 +- apps/web/public/locales/zh/common.json | 5 +- 15 files changed, 171 insertions(+), 112 deletions(-) diff --git a/apps/web/lib/settings/integration-setting.tsx b/apps/web/lib/settings/integration-setting.tsx index 602087332..03c3e990d 100644 --- a/apps/web/lib/settings/integration-setting.tsx +++ b/apps/web/lib/settings/integration-setting.tsx @@ -5,13 +5,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { useGitHubIntegration, useIntegrationTenant, useIntegrationTypes, useOrganizationTeams } from '@app/hooks'; import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { GITHUB_APP_NAME } from '@app/constants'; -import Link from 'next/link'; import { useOrganizationProjects } from '@app/hooks'; import { TrashIcon } from 'lib/components/svgs'; -import { Button, InputField, Text } from 'lib/components'; +import { Button, InputField } from 'lib/components'; import { getActiveProjectIdCookie } from '@app/helpers'; import { Switch } from '@headlessui/react'; import debounce from 'lodash/debounce'; +import Link from 'next/link'; export const IntegrationSetting = () => { const { t } = useTranslation(); @@ -164,37 +164,128 @@ export const IntegrationSetting = () => { }, [integrationTenant, deleteIntegrationTenant, getIntegrationTenant]); return ( -
-
-
-
+
+ <> +
+
-
+ {/* Divider */} +
+ +
{t('pages.settingsTeam.GITHUB')}
- {integrationGithubRepositories?.total_count === 0 && ( + +
+ {integrationGithubRepositories?.total_count === 0 && ( +
+ {t('pages.settingsTeam.GITHUB_INTEGRATION_DESCRIPTION')} +
+ )} +
- {t('pages.settingsTeam.GITHUB_INTEGRATION_DESCRIPTION')} + {t('common.GITHUB_INTEGRATION_SUBTITLE_TEXT')}
- )} +
-
- {integrationGithubRepositories && integrationGithubRepositories?.total_count > 0 && ( -
+ {integrationGithubRepositories && integrationGithubRepositories?.total_count > 0 && ( + + )} + + {(!integrationGithubRepositories || integrationGithubRepositories?.total_count === 0) && ( + + {t('pages.settingsTeam.INSTALL')} + + )} +
+ + {integrationGithubRepositories && integrationGithubRepositories?.total_count > 0 && ( +
+
+
+

Auto Synk Tasks

+

{t('common.GITHUB_INTEGRATION_AUTO_SYNC_TASK_TEXT')}

+
+
+ { + handleEditOrganizationProject({ + autoSync: !isTasksAutoSync + }); + setIsTasksAutoSync(!isTasksAutoSync); + }} + className={`${isTasksAutoSync ? 'bg-[#DBD3FA]' : 'bg-[#80808061]'} + relative inline-flex h-[38px] w-[74px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`} + > + Use setting + +
+
+ +
+
+

Sync Tasks with specific Label

+

{t('common.GITHUB_INTEGRATION_LABEL_SYNC_TASK_TEXT')}

+
+
+ { + handleEditOrganizationProject({ + autoSyncOnLabel: !isTasksAutoSyncOnLabel + }); + setIsTasksAutoSyncOnLabel(!isTasksAutoSyncOnLabel); + }} + className={`${isTasksAutoSyncOnLabel ? 'bg-[#DBD3FA]' : 'bg-[#80808061]'} + relative inline-flex h-[38px] w-[74px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`} + > + Use setting + +
+
+ +
+