- {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length})
+
+
+
+ {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length})
+
+
@@ -30,20 +40,31 @@ export function PastTasks({ profile }: { profile: any }) {
{/* Plan tasks list */}
-
- {plan.tasks?.map((task) => (
-
- ))}
+
+ {plan.tasks?.map((task) =>
+ view === 'CARDS' ? (
+
+ ) : (
+
+ )
+ )}
diff --git a/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx b/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx
index c7b4de1e2..811f7cbf7 100644
--- a/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx
+++ b/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx
@@ -1,44 +1,44 @@
import { secondsToTime } from '@app/helpers';
import { IDailyPlan } from '@app/interfaces';
-import { VerticalSeparator } from 'lib/components'
-import React from 'react'
+import { VerticalSeparator } from 'lib/components';
-interface ITaskEstimatedcount {
- outstandingPlans: any[]
+interface ITaskEstimatedCount {
+ outstandingPlans: any[];
}
-export function TaskEstimatedcount({ outstandingPlans }: ITaskEstimatedcount) {
- const element = outstandingPlans?.map((plan: IDailyPlan) => plan.tasks?.map((task) => task));
- const { timesEstemated, totalTasks } = estimatedTotalTime(element || []);
- const { h: hour, m: minute } = secondsToTime((timesEstemated || 0));
- return (
-
-
- Estimated:
- {hour}h{minute}m
-
-
-
- Total tasks:
- {totalTasks}
-
-
- )
+export function TaskEstimatedCount({ outstandingPlans }: ITaskEstimatedCount) {
+ const element = outstandingPlans?.map((plan: IDailyPlan) => plan.tasks?.map((task) => task));
+ const { timesEstimated, totalTasks } = estimatedTotalTime(element || []);
+ const { h: hour, m: minute } = secondsToTime(timesEstimated || 0);
+ return (
+
+
+ Estimated:
+
+ {hour}h{minute}m
+
+
+
+
+ Total tasks:
+ {totalTasks}
+
+
+ );
}
-
export function estimatedTotalTime(data: any) {
- // Flatten the data and reduce to calculate the sum of estimates without duplicates
- const uniqueTasks = data.flat().reduce((acc: any, task: any) => {
- if (!acc[task.id]) {
- acc[task.id] = task.estimate;
- }
- return acc;
- }, {});
+ // Flatten the data and reduce to calculate the sum of estimates without duplicates
+ const uniqueTasks = data.flat().reduce((acc: any, task: any) => {
+ if (!acc[task.id]) {
+ acc[task.id] = task.estimate;
+ }
+ return acc;
+ }, {});
- // Calculate the total of estimates
- const timesEstemated = Object.values(uniqueTasks)?.reduce((total: number, estimate: any) => total + estimate, 0);
- // Calculate the total of tasks
- const totalTasks = Object.values(uniqueTasks)?.length;
+ // Calculate the total of estimates
+ const timesEstimated = Object.values(uniqueTasks)?.reduce((total: number, estimate: any) => total + estimate, 0);
+ // Calculate the total of tasks
+ const totalTasks = Object.values(uniqueTasks)?.length;
- return { timesEstemated, totalTasks };
+ return { timesEstimated, totalTasks };
}
diff --git a/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx b/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx
new file mode 100644
index 000000000..6f18de4ac
--- /dev/null
+++ b/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx
@@ -0,0 +1,47 @@
+import { IssuesView } from '@app/constants';
+import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs';
+import { clsxm } from '@app/utils';
+import { QueueListIcon, Squares2X2Icon, TableCellsIcon } from '@heroicons/react/20/solid';
+import { Tooltip } from 'lib/components';
+import { DottedLanguageObjectStringPaths, useTranslations } from 'next-intl';
+import { useRecoilState } from 'recoil';
+
+export default function ViewsHeaderTabs() {
+ const t = useTranslations();
+ const options = [
+ { label: 'CARDS', icon: QueueListIcon, view: IssuesView.CARDS },
+ { label: 'TABLE', icon: TableCellsIcon, view: IssuesView.TABLE },
+ { label: 'BLOCKS', icon: Squares2X2Icon, view: IssuesView.BLOCKS }
+ ];
+
+ const [view, setView] = useRecoilState(dailyPlanViewHeaderTabs);
+
+ return (
+
+ {options.map(({ label, icon: Icon, view: optionView }) => (
+
+ {
+ setView(optionView);
+ }}
+ >
+
+
+
+ ))}
+
+ );
+}
diff --git a/apps/web/lib/features/task/task-block-card.tsx b/apps/web/lib/features/task/task-block-card.tsx
new file mode 100644
index 000000000..3b6c7544b
--- /dev/null
+++ b/apps/web/lib/features/task/task-block-card.tsx
@@ -0,0 +1,155 @@
+import { ITeamTask } from '@app/interfaces';
+import { TaskAllStatusTypes } from './task-all-status-type';
+import MenuKanbanCard from '@components/pages/kanban/menu-kanban-card';
+import { TaskInput } from './task-input';
+import { useRecoilState } from 'recoil';
+import { activeTeamTaskId } from '@app/stores';
+import Link from 'next/link';
+import { useAuthenticateUser, useOrganizationTeams, useTaskStatistics, useTeamMemberCard } from '@app/hooks';
+import ImageComponent, { ImageOverlapperProps } from '../../components/image-overlapper';
+import { TaskIssueStatus } from './task-issue';
+import { Priority, setCommentIconColor } from 'lib/components/kanban-card';
+import CircularProgress from '@components/ui/svgs/circular-progress';
+import { HorizontalSeparator } from 'lib/components';
+import { TaskStatus } from '@app/constants';
+import { secondsToTime } from '@app/helpers';
+
+interface TaskItemProps {
+ task: ITeamTask;
+}
+
+export default function TaskBlockCard(props: TaskItemProps) {
+ const { task } = props;
+ const [activeTask, setActiveTask] = useRecoilState(activeTeamTaskId);
+ const { activeTeam } = useOrganizationTeams();
+ const { user } = useAuthenticateUser();
+ const { getEstimation } = useTaskStatistics(0);
+ const members = activeTeam?.members || [];
+ const currentUser = members.find((m) => m.employee.userId === user?.id);
+
+ let totalWorkedTasksTimer = 0;
+ activeTeam?.members?.forEach((member) => {
+ const totalWorkedTasks = member?.totalWorkedTasks?.find((i) => i.id === task?.id) || null;
+ if (totalWorkedTasks) {
+ totalWorkedTasksTimer += totalWorkedTasks.duration;
+ }
+ });
+
+ const memberInfo = useTeamMemberCard(currentUser);
+
+ const taskAssignee: ImageOverlapperProps[] = task.members?.map((member: any) => {
+ return {
+ id: member.user.id,
+ url: member.user.imageUrl,
+ alt: member.user.firstName
+ };
+ });
+
+ const progress = getEstimation(null, task, totalWorkedTasksTimer || 1, task.estimate || 0);
+
+ const currentMember = activeTeam?.members.find((member) => member.id === memberInfo.member?.id || task?.id);
+
+ const { h, m, s } = secondsToTime(
+ (currentMember?.totalWorkedTasks &&
+ currentMember?.totalWorkedTasks?.length &&
+ currentMember?.totalWorkedTasks
+ .filter((t) => t.id === task?.id)
+ .reduce((previousValue, currentValue) => previousValue + currentValue.duration, 0)) ||
+ 0
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {activeTask?.id == task.id ? (
+ <>
+
+ {
+ // TODO: implement
+ console.log(e);
+ }}
+ onEnterKey={() => {
+ setActiveTask({ id: '' });
+ }}
+ />
+
+ >
+ ) : (
+
+
+ {task.issueType && (
+
+
+
+
+
+ )}
+
#{task.number}
+ {task.title}
+
+ {task.priority && }
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {task.status === TaskStatus.INPROGRESS ? (
+
+
Live:
+
+ {h}h : {m}m : {s}s
+
+
+ ) : (
+
+
Worked:
+
+ {h}h : {m}m : {s}s
+
+
+ )}
+
+
+ {task.issueType && (
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx
index 0f093fce5..192c035fd 100644
--- a/apps/web/lib/features/task/task-card.tsx
+++ b/apps/web/lib/features/task/task-card.tsx
@@ -1,9 +1,10 @@
'use client';
-import { secondsToTime } from '@app/helpers';
+import { secondsToTime, tomorrowDate } from '@app/helpers';
import {
I_TeamMemberCardHook,
I_UserProfilePage,
+ useAuthenticateUser,
useCanSeeActivityScreen,
useDailyPlan,
useModal,
@@ -17,6 +18,7 @@ import {
} from '@app/hooks';
import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper';
import {
+ DailyPlanStatusEnum,
IClassName,
IDailyPlan,
IDailyPlanMode,
@@ -39,7 +41,7 @@ import {
} from 'lib/components';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
-import { useCallback, useState } from 'react';
+import { useCallback, useState, useTransition } from 'react';
import { SetterOrUpdater, useRecoilValue } from 'recoil';
import { TaskEstimateInfo } from '../team/user-team-card/task-estimate';
import { TimerButton } from '../timer/timer-button';
@@ -53,6 +55,7 @@ import { SixSquareGridIcon, ThreeCircleOutlineVerticalIcon } from 'assets/svg';
import { CreateDailyPlanFormModal } from '../daily-plan/create-daily-plan-form-modal';
import { AddTaskToPlan } from '../daily-plan/add-task-to-plan';
import { AddWorkTimeAndEstimatesToPlan } from '../daily-plan/plans-work-time-and-estimate';
+import { ReloadIcon } from '@radix-ui/react-icons';
type Props = {
active?: boolean;
@@ -632,7 +635,41 @@ export function PlanTask({
employeeId?: string;
chooseMember?: boolean;
}) {
+ const t = useTranslations();
+ const [isPending, startTransition] = useTransition();
const { closeModal, isOpen, openModal } = useModal();
+ const { createDailyPlan } = useDailyPlan();
+ const { user } = useAuthenticateUser();
+
+ const handleOpenModal = () => {
+ if (planMode === 'custom') {
+ openModal();
+ } else if (planMode === 'today') {
+ startTransition(() => {
+ createDailyPlan({
+ workTimePlanned: 0,
+ taskId,
+ date: new Date(),
+ status: DailyPlanStatusEnum.OPEN,
+ tenantId: user?.tenantId ?? '',
+ employeeId: employeeId,
+ organizationId: user?.employee.organizationId
+ });
+ });
+ } else {
+ startTransition(() => {
+ createDailyPlan({
+ workTimePlanned: 0,
+ taskId,
+ date: tomorrowDate,
+ status: DailyPlanStatusEnum.OPEN,
+ tenantId: user?.tenantId ?? '',
+ employeeId: employeeId,
+ organizationId: user?.employee.organizationId
+ });
+ });
+ }
+ };
return (
<>
@@ -641,7 +678,7 @@ export function PlanTask({
'font-normal whitespace-nowrap transition-all',
'hover:font-semibold hover:transition-all cursor-pointer'
)}
- onClick={openModal}
+ onClick={handleOpenModal}
>
- {planMode === 'today' && 'Plan for today'}
- {planMode === 'tomorow' && 'Plan for tomorow'}
- {planMode === 'custom' && 'Plan for some day'}
+ {planMode === 'today' && (
+
+ {isPending ? (
+
+ ) : (
+ t('dailyPlan.PLAN_FOR_TODAY')
+ )}
+
+ )}
+ {planMode === 'tomorow' && (
+
+ {isPending ? (
+
+ ) : (
+ t('dailyPlan.PLAN_FOR_TOMORROW')
+ )}
+
+ )}
+ {planMode === 'custom' && t('dailyPlan.PLAN_FOR_SOME_DAY')}
>
);
}
export function AddTaskToPlanComponent({ task, employee }: { task: ITeamTask; employee?: OT_Member }) {
+ const t = useTranslations();
const { closeModal, isOpen, openModal } = useModal();
return (
- Add this task to a plan
+ {t('dailyPlan.ADD_TASK_TO_PLAN')}
);
}
export function RemoveTaskFromPlan({ task, plan, member }: { task: ITeamTask; member?: OT_Member; plan?: IDailyPlan }) {
+ const t = useTranslations();
const { removeTaskFromPlan } = useDailyPlan();
const data: IDailyPlanTasksUpdate = { taskId: task.id, employeeId: member?.employeeId };
const onClick = () => {
@@ -689,7 +744,7 @@ export function RemoveTaskFromPlan({ task, plan, member }: { task: ITeamTask; me
)}
onClick={onClick}
>
- Remove from this plan
+ {t('dailyPlan.REMOVE_FROM_THIS_PLAN')}
);
}
diff --git a/apps/web/lib/features/team/team-item.tsx b/apps/web/lib/features/team/team-item.tsx
index 98373b8da..636a64557 100644
--- a/apps/web/lib/features/team/team-item.tsx
+++ b/apps/web/lib/features/team/team-item.tsx
@@ -152,3 +152,44 @@ export function TeamItem({
);
}
+
+export function AllTeamItem({ title, count }: { title: string; count: number }) {
+ return (
+