diff --git a/apps/mobile/app/components/Accordion.tsx b/apps/mobile/app/components/Accordion.tsx index ca6dc53a9..7feca63e4 100644 --- a/apps/mobile/app/components/Accordion.tsx +++ b/apps/mobile/app/components/Accordion.tsx @@ -45,7 +45,7 @@ export default Accordion const styles = StyleSheet.create({ accordContainer: { borderRadius: 8, - marginVertical: 5, + marginVertical: 6, width: "100%", }, accordHeader: { diff --git a/apps/mobile/app/components/Task/EstimateBlock/components/ProfileInfoWithTime.tsx b/apps/mobile/app/components/Task/EstimateBlock/components/ProfileInfoWithTime.tsx index d8e8a5707..469f8b27c 100644 --- a/apps/mobile/app/components/Task/EstimateBlock/components/ProfileInfoWithTime.tsx +++ b/apps/mobile/app/components/Task/EstimateBlock/components/ProfileInfoWithTime.tsx @@ -18,6 +18,7 @@ interface IProfileInfo { userId?: string largerProfileInfo?: boolean estimationsBlock?: boolean + time?: string } const ProfileInfoWithTime: React.FC = ({ @@ -25,6 +26,7 @@ const ProfileInfoWithTime: React.FC = ({ names, userId, largerProfileInfo, + time, }) => { const { colors } = useAppTheme() @@ -38,7 +40,13 @@ const ProfileInfoWithTime: React.FC = ({ }, 50) } return ( - + {profilePicSrc ? ( = ({ })} - 6 h: 40 m + + {time ? time : "6 h: 40 m"} + ) } diff --git a/apps/mobile/app/components/Task/TimeBlock/index.tsx b/apps/mobile/app/components/Task/TimeBlock/index.tsx new file mode 100644 index 000000000..edd5f8b19 --- /dev/null +++ b/apps/mobile/app/components/Task/TimeBlock/index.tsx @@ -0,0 +1,336 @@ +/* eslint-disable camelcase */ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ +import { StyleSheet, Text, TouchableOpacity, View } from "react-native" +import React, { useCallback, useEffect, useState } from "react" +import Accordion from "../../Accordion" +import { useStores } from "../../../models" +import { useOrganizationTeam } from "../../../services/hooks/useOrganization" +import useAuthenticateUser from "../../../services/hooks/features/useAuthentificateUser" +import { IOrganizationTeamList, OT_Member } from "../../../services/interfaces/IOrganizationTeam" +import { secondsToTime } from "../../../helpers/date" +import { ITasksTimesheet } from "../../../services/interfaces/ITimer" +import TaskRow from "../DetailsBlock/components/TaskRow" +import { ProgressBar } from "react-native-paper" +import { ITeamTask } from "../../../services/interfaces/ITask" +import { Feather } from "@expo/vector-icons" +import { useAppTheme } from "../../../theme" +import ProfileInfoWithTime from "../EstimateBlock/components/ProfileInfoWithTime" +import { translate } from "../../../i18n" + +export interface ITime { + hours: number + minutes: number +} + +const TimeBlock = () => { + const { + TaskStore: { detailedTask: task }, + } = useStores() + const { currentTeam: activeTeam } = useOrganizationTeam() + const { user } = useAuthenticateUser() + const { colors } = useAppTheme() + + const [userTotalTime, setUserTotalTime] = useState({ + hours: 0, + minutes: 0, + }) + const [userTotalTimeToday, setUserTotalTimeToday] = useState({ + hours: 0, + minutes: 0, + }) + const [timeRemaining, setTimeRemaining] = useState({ + hours: 0, + minutes: 0, + }) + const [groupTotalTime, setGroupTotalTime] = useState({ + hours: 0, + minutes: 0, + }) + + const members = activeTeam?.members || [] + + const currentUser: OT_Member | undefined = members.find((m) => { + return m.employee.user?.id === user?.id + }) + + const userTotalTimeOnTask = useCallback((): void => { + const totalOnTaskInSeconds: number = + currentUser?.totalWorkedTasks?.find((object) => object.id === task?.id)?.duration || 0 + + const { h, m } = secondsToTime(totalOnTaskInSeconds) + + setUserTotalTime({ hours: h, minutes: m }) + }, [currentUser?.totalWorkedTasks, task?.id]) + + useEffect(() => { + userTotalTimeOnTask() + }, [userTotalTimeOnTask]) + + const userTotalTimeOnTaskToday = useCallback((): void => { + const totalOnTaskInSeconds: number = + currentUser?.totalTodayTasks?.find((object) => object.id === task?.id)?.duration || 0 + + const { h, m } = secondsToTime(totalOnTaskInSeconds) + + setUserTotalTimeToday({ hours: h, minutes: m }) + }, [currentUser?.totalTodayTasks, task?.id]) + + useEffect(() => { + userTotalTimeOnTaskToday() + }, [userTotalTimeOnTaskToday]) + + useEffect(() => { + const matchingMembers: OT_Member[] | undefined = activeTeam?.members.filter((member) => + task?.members.some((taskMember) => taskMember.id === member.employeeId), + ) + + const usersTaskArray: ITasksTimesheet[] | undefined = matchingMembers + ?.flatMap((obj) => obj.totalWorkedTasks) + .filter((taskObj) => taskObj?.id === task?.id) + + const usersTotalTimeInSeconds: number | undefined = usersTaskArray?.reduce( + (totalDuration, item) => totalDuration + item.duration, + 0, + ) + + const usersTotalTime: number = + usersTotalTimeInSeconds === null || usersTotalTimeInSeconds === undefined + ? 0 + : usersTotalTimeInSeconds + + const timeObj = secondsToTime(usersTotalTime) + const { h: hoursTotal, m: minutesTotal } = timeObj + setGroupTotalTime({ hours: hoursTotal, minutes: minutesTotal }) + + const remainingTime: number = + task?.estimate === null || + task?.estimate === 0 || + task?.estimate === undefined || + usersTotalTimeInSeconds === undefined + ? 0 + : task?.estimate - usersTotalTimeInSeconds + + const { h, m } = secondsToTime(remainingTime) + setTimeRemaining({ hours: h, minutes: m }) + if (remainingTime <= 0) { + setTimeRemaining({ hours: 0, minutes: 0 }) + } + }, [task?.members, task?.id, task?.estimate]) + + const getTimePercentage = () => { + if (task) { + if (!task.estimate) { + return 0 + } + + let taskTotalDuration = 0 + activeTeam?.members?.forEach((member) => { + const totalWorkedTasks = + member?.totalWorkedTasks?.find((item) => item.id === task?.id) || null + + if (totalWorkedTasks) { + taskTotalDuration += totalWorkedTasks.duration + } + }) + + return taskTotalDuration ? taskTotalDuration / task?.estimate : 0 + } else { + return 0 + } + } + + return ( + + + {/* Progress Bar */} + + + {translate("taskDetailsScreen.progress")} + + + } + > + + + {/* Total Time */} + + + {translate("tasksScreen.totalTimeLabel")} + + + } + > + + {userTotalTime.hours}h : {userTotalTime.minutes}m + + + {/* Total Time Today */} + + + {translate("taskDetailsScreen.timeToday")} + + + } + > + + {userTotalTimeToday.hours}h : {userTotalTimeToday.minutes}m + + + {/* Total Group Time */} + {/* TODO */} + + + {translate("taskDetailsScreen.totalGroupTime")} + + + } + > + + + {/* Time Remaining */} + + + {translate("taskDetailsScreen.timeRemaining")} + + + } + > + + {timeRemaining.hours}h : {timeRemaining.minutes}m + + + + + ) +} + +export default TimeBlock + +interface IProgress { + task: ITeamTask + percent: () => number +} + +const Progress: React.FC = ({ task, percent }) => { + return ( + + + 0 ? "#27AE60" : "#F0F0F0"} + /> + + {Math.floor(percent() * 100)}% + + ) +} + +interface ITotalGroupTime { + totalTime: string + task: ITeamTask + activeTeam: IOrganizationTeamList +} + +const TotalGroupTime: React.FC = ({ totalTime, activeTeam, task }) => { + const [expanded, setExpanded] = useState(false) + const { colors } = useAppTheme() + + function toggleItem() { + setExpanded(!expanded) + } + + const matchingMembers: OT_Member[] | undefined = activeTeam?.members.filter((member) => + task?.members.some((taskMember) => taskMember.id === member.employeeId), + ) + + const findUserTotalWorked = (user: OT_Member, id: string | undefined) => { + return user?.totalWorkedTasks.find((task: any) => task?.id === id)?.duration || 0 + } + + return ( + + + {totalTime} + + + {expanded && } + + {expanded && + matchingMembers?.map((member, idx) => { + const taskDurationInSeconds = findUserTotalWorked(member, task?.id) + + const { h, m } = secondsToTime(taskDurationInSeconds) + + const time = `${h}h : ${m}m` + + return ( + + ) + })} + + + ) +} + +const styles = StyleSheet.create({ + accordContainer: { + paddingRight: 12, + width: "100%", + }, + accordHeader: { + alignItems: "center", + flexDirection: "row", + justifyContent: "space-between", + }, + accordTitle: { + fontSize: 12, + fontWeight: "600", + }, + labelComponent: { + alignItems: "center", + flexDirection: "row", + gap: 7, + }, + labelText: { + color: "#A5A2B2", + fontSize: 12, + }, + progressBar: { backgroundColor: "#E9EBF8", borderRadius: 3, height: 6, width: "100%" }, + progressBarContainer: { + alignItems: "center", + flexDirection: "row", + justifyContent: "space-between", + paddingRight: 12, + }, + timeValues: { fontSize: 12, fontWeight: "600" }, +}) diff --git a/apps/mobile/app/i18n/ar.ts b/apps/mobile/app/i18n/ar.ts index fcd416baa..4deeb445a 100644 --- a/apps/mobile/app/i18n/ar.ts +++ b/apps/mobile/app/i18n/ar.ts @@ -121,6 +121,11 @@ const ar: Translations = { items: "أغراض", estimate: "تقدير", estimations: "التقديرات", + time: "الوقت", + progress: "التقدم", + timeToday: "الوقت اليوم", + totalGroupTime: "إجمالي وقت المجموعة", + timeRemaining: "الوقت المتبقي", }, tasksScreen: { name: "مهام", diff --git a/apps/mobile/app/i18n/bg.ts b/apps/mobile/app/i18n/bg.ts index 37a63138f..693c66f19 100644 --- a/apps/mobile/app/i18n/bg.ts +++ b/apps/mobile/app/i18n/bg.ts @@ -117,6 +117,11 @@ const bg = { items: "Items", estimate: "Estimate", estimations: "Estimations", + time: "Time", + progress: "Progress", + timeToday: "Time Today", + totalGroupTime: "Total Group Time", + timeRemaining: "Time Remaining", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/en.ts b/apps/mobile/app/i18n/en.ts index 5ab5ff50a..049d24a33 100644 --- a/apps/mobile/app/i18n/en.ts +++ b/apps/mobile/app/i18n/en.ts @@ -118,6 +118,11 @@ const en = { items: "Items", estimate: "Estimate", estimations: "Estimations", + time: "Time", + progress: "Progress", + timeToday: "Time Today", + totalGroupTime: "Total Group Time", + timeRemaining: "Time Remaining", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/es.ts b/apps/mobile/app/i18n/es.ts index 55db16e63..1c06928ed 100644 --- a/apps/mobile/app/i18n/es.ts +++ b/apps/mobile/app/i18n/es.ts @@ -117,6 +117,11 @@ const es = { items: "Items", estimate: "Estimate", estimations: "Estimations", + time: "Time", + progress: "Progress", + timeToday: "Time Today", + totalGroupTime: "Total Group Time", + timeRemaining: "Time Remaining", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/fr.ts b/apps/mobile/app/i18n/fr.ts index fe7faa4a4..8a036f42e 100644 --- a/apps/mobile/app/i18n/fr.ts +++ b/apps/mobile/app/i18n/fr.ts @@ -120,6 +120,11 @@ const fr = { items: "Articles", estimate: "Estimation", estimations: "Estimations", + time: "Temps", + progress: "Progrès", + timeToday: "Temps Aujourd'hui", + totalGroupTime: "Temps Total du Groupe", + timeRemaining: "Temps Restant", }, tasksScreen: { name: "Tâches", diff --git a/apps/mobile/app/i18n/he.ts b/apps/mobile/app/i18n/he.ts index 5ca6214f5..8aa967b7d 100644 --- a/apps/mobile/app/i18n/he.ts +++ b/apps/mobile/app/i18n/he.ts @@ -117,6 +117,11 @@ const he = { items: "Items", estimate: "Estimate", estimations: "Estimations", + time: "Time", + progress: "Progress", + timeToday: "Time Today", + totalGroupTime: "Total Group Time", + timeRemaining: "Time Remaining", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/i18n/ko.ts b/apps/mobile/app/i18n/ko.ts index 3671b1d64..3b3f7f9da 100644 --- a/apps/mobile/app/i18n/ko.ts +++ b/apps/mobile/app/i18n/ko.ts @@ -120,6 +120,11 @@ const ko: Translations = { items: "품목", estimate: "견적", estimations: "견적들", + time: "시간", + progress: "진행", + timeToday: "오늘의 시간", + totalGroupTime: "총 그룹 시간", + timeRemaining: "남은 시간", }, tasksScreen: { name: "작업", diff --git a/apps/mobile/app/i18n/ru.ts b/apps/mobile/app/i18n/ru.ts index d6ebf4def..cb1f15efe 100644 --- a/apps/mobile/app/i18n/ru.ts +++ b/apps/mobile/app/i18n/ru.ts @@ -117,6 +117,11 @@ const ru = { items: "Items", estimate: "Estimate", estimations: "Estimations", + time: "Time", + progress: "Progress", + timeToday: "Time Today", + totalGroupTime: "Total Group Time", + timeRemaining: "Time Remaining", }, tasksScreen: { name: "Tasks", diff --git a/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx b/apps/mobile/app/screens/Authenticated/TaskScreen/index.tsx index 8a3dde007..47b47f733 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 TimeBlock from "../../../components/Task/TimeBlock" export const AuthenticatedTaskScreen: FC> = ( _props, @@ -54,6 +55,7 @@ export const AuthenticatedTaskScreen: FC +