diff --git a/apps/web/app/[locale]/[...not-found]/page.tsx b/apps/web/app/[locale]/404.tsx similarity index 100% rename from apps/web/app/[locale]/[...not-found]/page.tsx rename to apps/web/app/[locale]/404.tsx diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 4c8bafc48..0310c07e5 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { KanbanTabs } from '@app/constants'; -import { useOrganizationTeams } from '@app/hooks'; +import { useAuthenticateUser, useModal, useOrganizationTeams } from '@app/hooks'; import { useKanban } from '@app/hooks/features/useKanban'; import KanbanBoardSkeleton from '@components/shared/skeleton/KanbanBoardSkeleton'; import { withAuthentication } from 'lib/app/authenticator'; @@ -17,9 +17,12 @@ import { clsxm } from '@app/utils'; import HeaderTabs from '@components/pages/main/header-tabs'; import { AddIcon, SearchNormalIcon, SettingFilterIcon, PeoplesIcon } from 'assets/svg'; import { Select, SelectContent, SelectItem, SelectTrigger } from '@components/ui/select'; +import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; +import { userTimezone } from '@app/helpers'; const Kanban = () => { const { data } = useKanban(); + const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); const params = useParams<{ locale: string }>(); @@ -47,6 +50,9 @@ const Kanban = () => { { name: t('common.YESTERDAY'), value: KanbanTabs.YESTERDAY }, { name: t('common.TOMORROW'), value: KanbanTabs.TOMORROW } ]; + const { user } = useAuthenticateUser(); + const { openModal, isOpen, closeModal } = useModal(); + const timezone = userTimezone(); return ( <> @@ -67,7 +73,10 @@ const Kanban = () => { {t('common.KANBAN')} {t('common.BOARD')}
- 08:00 ( UTC +04:30 ) + + {`(`} + {timezone.split('(')[1]} +
@@ -76,7 +85,10 @@ const Kanban = () => {
- @@ -180,6 +192,7 @@ const Kanban = () => { )} + ); }; diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 24e9ef2ba..fca662406 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -78,7 +78,7 @@ function MainPage() { -
+
{isTeamMember ? : null} {view === IssuesView.CARDS && isTeamMember ? ( diff --git a/apps/web/app/[locale]/settings/personal/page.tsx b/apps/web/app/[locale]/settings/personal/page.tsx index 4646d5c2f..1bc0a9065 100644 --- a/apps/web/app/[locale]/settings/personal/page.tsx +++ b/apps/web/app/[locale]/settings/personal/page.tsx @@ -11,7 +11,7 @@ const Personal = () => { const t = useTranslations(); return ( - <> +
); }; export default withAuthentication(Personal, { displayName: 'Personal' }); diff --git a/apps/web/app/[locale]/settings/team/page.tsx b/apps/web/app/[locale]/settings/team/page.tsx index f523a31de..f99f5bc3a 100644 --- a/apps/web/app/[locale]/settings/team/page.tsx +++ b/apps/web/app/[locale]/settings/team/page.tsx @@ -23,7 +23,7 @@ const Team = () => { const { isTeamMember, activeTeam } = useOrganizationTeams(); const { isTeamManager } = useIsMemberManager(user); return ( - <> +
{isTeamMember ? ( <> @@ -110,7 +110,7 @@ const Team = () => {
)} - +
); }; diff --git a/apps/web/components/ui/svgs/circular-progress.tsx b/apps/web/components/ui/svgs/circular-progress.tsx index c6f257ff7..b4dba1cd1 100644 --- a/apps/web/components/ui/svgs/circular-progress.tsx +++ b/apps/web/components/ui/svgs/circular-progress.tsx @@ -44,7 +44,7 @@ export default function CircularProgress({ className="absolute text-xs font-normal text-black dark:text-white rotate-90" x-text={`${percentage}%`} > - {percentage}H + {percentage}%{' '}
diff --git a/apps/web/lib/components/auto-complete-dropdown.tsx b/apps/web/lib/components/auto-complete-dropdown.tsx index 6de5192a0..a91b05ac5 100644 --- a/apps/web/lib/components/auto-complete-dropdown.tsx +++ b/apps/web/lib/components/auto-complete-dropdown.tsx @@ -59,6 +59,10 @@ export function AutoCompleteDropdown({ if (event.key === 'Enter' && handleAddNew && useHandleKeyUp) { handleAddNew(query); } + + if (event.key === 'Escape') { + handleAddNew(query); + } }, [query, handleAddNew, useHandleKeyUp] ); @@ -70,7 +74,7 @@ export function AutoCompleteDropdown({ placeholder={placeholder} onChange={(event) => setQuery(event.target.value)} className={clsxm( - 'input-border', + 'input-border ', 'w-full flex justify-between rounded-[0.625rem] px-3 py-2 text-sm items-center bg-transparent', 'bg-light--theme-light dark:bg-dark--theme-light outline-none', 'font-normal', @@ -97,9 +101,9 @@ export function AutoCompleteDropdown({ )} > {/* This should only show when New item needs to be created */} {query && handleAddNew && ( diff --git a/apps/web/lib/components/image-overlapper.tsx b/apps/web/lib/components/image-overlapper.tsx index 25c5bd942..08dfec156 100644 --- a/apps/web/lib/components/image-overlapper.tsx +++ b/apps/web/lib/components/image-overlapper.tsx @@ -1,8 +1,8 @@ -import Link from 'next/link'; +import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover'; import Image from 'next/image'; -import { Tooltip } from 'lib/components'; +import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; - +import { Tooltip } from './tooltip'; export interface ImageOverlapperProps { id: string; url: string; @@ -18,56 +18,81 @@ export default function ImageOverlapper({ radius?: number; displayImageCount?: number; }) { - const imageRadius = radius; + // Split the array into two arrays based on the display number + const firstArray = images.slice(0, displayImageCount); + const widthCalculate = images.slice(0, 5); + const secondArray = images.slice(displayImageCount); + const isMoreThanDisplay = images.length > displayImageCount; const imageLength = images.length; - const numberOfImagesDisplayed = displayImageCount; - const totalLength = (imageLength + 1) * imageRadius; - - const stackImages = (index: number, length: number) => { - const total_length = (length + 1) * imageRadius; - return { - zIndex: (index + 1).toString(), - right: `calc(${total_length - imageRadius * (index + 2)}px)` - }; - }; + if (imageLength == 0) { + return ; + } return ( -
- {imageLength > 0 ? ( -
- {images.map((image: ImageOverlapperProps, index: number) => { - if (index < numberOfImagesDisplayed) { - return ( - - - {`${image.alt} - - - ); - } - })} - {images.length > numberOfImagesDisplayed && ( +
+ {firstArray.map((image, index) => ( + +
+ + {`${image.alt} + +
+ + ))} + {secondArray.length > 0 && ( + +
- {imageLength - numberOfImagesDisplayed < 100 ? imageLength - numberOfImagesDisplayed : 99}+ + {secondArray.length < 100 ? secondArray.length : 99}+ +
+
+ +
+ {secondArray.map((image: ImageOverlapperProps, index: number) => { + return ( + +
+ {`${image.alt} +

{image.alt}

+
+ + ); + })}
- )} -
- ) : ( - + + )}
); diff --git a/apps/web/lib/components/kanban-card.tsx b/apps/web/lib/components/kanban-card.tsx index 66a8a788e..ff3c1aa71 100644 --- a/apps/web/lib/components/kanban-card.tsx +++ b/apps/web/lib/components/kanban-card.tsx @@ -6,17 +6,17 @@ import { useCollaborative, useOrganizationTeams, useTMCardTaskEdit, - useTeamMemberCard, - useTimerView + useTaskStatistics, + useTeamMemberCard } from '@app/hooks'; import ImageComponent, { ImageOverlapperProps } from './image-overlapper'; import { TaskInput, TaskIssueStatus } from 'lib/features'; import Link from 'next/link'; import CircularProgress from '@components/ui/svgs/circular-progress'; import { HorizontalSeparator } from './separator'; -import { pad } from '@app/helpers'; -import { TaskStatus } from '@app/constants'; +import { secondsToTime } from '@app/helpers'; import { UserTeamCardMenu } from 'lib/features/team/user-team-card/user-team-card-menu'; +import { TaskStatus } from '@app/constants'; function getStyle(provided: DraggableProvided, style: any) { if (!style) { @@ -118,23 +118,31 @@ type ItemProps = { isClone: boolean; index: number; }; + /** - * card that represent each task + * Card that represents each task * @param props * @returns */ export default function Item(props: ItemProps) { - const { item, isDragging, provided, style, index } = props; - - const { hours, minutes, seconds } = useTimerView(); + const { item, isDragging, provided, style, index } = props; 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 === item?.id) || null; + if (totalWorkedTasks) { + totalWorkedTasksTimer += totalWorkedTasks.duration; + } + }); const memberInfo = useTeamMemberCard(currentUser); const taskEdition = useTMCardTaskEdit(memberInfo.memberTask); + const taskAssignee: ImageOverlapperProps[] = item.members.map((member: any) => { return { id: member.user.id, @@ -145,7 +153,22 @@ export default function Item(props: ItemProps) { const { collaborativeSelect } = useCollaborative(memberInfo.memberUser); const menu = <>{!collaborativeSelect && }; + const progress = getEstimation( + null, + item, + totalWorkedTasksTimer || 1, + item.estimate || 0 + ); + const currentMember = activeTeam?.members.find((member) => member.id === memberInfo.member?.id || item?.id); + const { h, m, s } = secondsToTime( + (currentMember?.totalWorkedTasks && + currentMember?.totalWorkedTasks?.length && + currentMember?.totalWorkedTasks + .filter((t) => t.id === item?.id) + .reduce((previousValue, currentValue) => previousValue + currentValue.duration, 0)) || + 0 + ); return (
{ + // TODO: implement console.log(e); }} onEnterKey={() => { @@ -216,32 +240,31 @@ export default function Item(props: ItemProps) {
)}
- + +
-
-
+
+
{item.status === TaskStatus.INPROGRESS ? (
Live:

- {pad(hours)}:{pad(minutes)}:{pad(seconds)}{' '} + {h}h : {m}m : {s}s

) : (
Worked: -

- {pad(hours)}:{pad(minutes)}:{pad(seconds)}{' '} +

+ {h}h : {m}m : {s}s

)}
-
- -
+ {item.issueType && (
-
+
{/* Task information */} + ); } @@ -32,7 +27,7 @@ export function UserInfoCell({ cell }: { cell: any }) { const publicTeam = get(cell, 'column.columnDef.meta.publicTeam', false); const memberInfo = useTeamMemberCard(member); - return ; + return ; } export function WorkedOnTaskCell({ row }: { row: any }) { @@ -45,7 +40,7 @@ export function WorkedOnTaskCell({ row }: { row: any }) { memberInfo={memberInfo} task={memberInfo.memberTask} isAuthUser={memberInfo.isAuthUser} - className="2xl:w-32 3xl:w-[8rem] min-w-[15rem] w-52 lg:w-1/5 flex flex-col gap-y-[1.125rem] justify-center" + className="2xl:w-48 3xl:w-[12rem] w-1/5 lg:px-4 flex flex-col gap-y-[1.125rem] justify-center" /> ); } @@ -60,7 +55,7 @@ export function TaskEstimateInfoCell({ row }: { row: any }) { memberInfo={memberInfo} edition={taskEdition} activeAuthTask={true} - className="lg:px-3 2xl:w-52 3xl:w-64 min-w-[15rem] w-52 lg:w-1/5" + className="w-1/5 lg:px-3 2xl:w-52 3xl:w-64" /> ); } diff --git a/apps/web/lib/features/team-members.tsx b/apps/web/lib/features/team-members.tsx index 1119ef926..d480926fb 100644 --- a/apps/web/lib/features/team-members.tsx +++ b/apps/web/lib/features/team-members.tsx @@ -25,7 +25,11 @@ export function TeamMembers({ publicTeam = false, kanbanView: view = IssuesView. const orderedMembers = [...members].sort((a, b) => (sortByWorkStatus(a, b) ? -1 : 1)); const blockViewMembers = - activeFilter == 'all' ? orderedMembers : orderedMembers.filter((m) => m.timerStatus === activeFilter); + activeFilter == 'all' + ? orderedMembers + : activeFilter == 'idle' + ? orderedMembers.filter((m: OT_Member) => m.timerStatus == undefined || m.timerStatus == 'idle') + : orderedMembers.filter((m) => m.timerStatus === activeFilter); const currentUser = members.find((m) => m.employee.userId === user?.id); const $members = members @@ -112,7 +116,8 @@ const sortByWorkStatus = (user_a: OT_Member, user_b: OT_Member) => { return user_a.timerStatus == 'running' || (user_a.timerStatus == 'online' && user_b.timerStatus != 'running') || (user_a.timerStatus == 'pause' && user_b.timerStatus !== 'running' && user_b.timerStatus !== 'online') || - (user_a.timerStatus == 'idle' && user_b.timerStatus == 'suspended') + (user_a.timerStatus == 'idle' && user_b.timerStatus == 'suspended') || + (user_a.timerStatus === undefined && user_b.timerStatus == 'suspended') ? true : false; }; diff --git a/apps/web/lib/features/team/invite/invite-email-dropdown.tsx b/apps/web/lib/features/team/invite/invite-email-dropdown.tsx index 1afe2d05d..9de772ca1 100644 --- a/apps/web/lib/features/team/invite/invite-email-dropdown.tsx +++ b/apps/web/lib/features/team/invite/invite-email-dropdown.tsx @@ -43,7 +43,7 @@ export const InviteEmailDropdown = ({ return ( <> {/* Task information */} - {t('common.TASK_TITTLE')} + {t('common.TASK_TITTLE')} {/* TaskTime */} -
+
{t('common.TODAY')}: 0h : 0m
{/* TaskEstimateInfo */} -
+
{/* : @@ -89,7 +89,7 @@ export function InvitedCard({ invitation, className }: Props) { {/* Card menu */} -
+
0h : 0m
diff --git a/apps/web/lib/features/team/user-team-block/index.tsx b/apps/web/lib/features/team/user-team-block/index.tsx index 07ea97078..565173a7b 100644 --- a/apps/web/lib/features/team/user-team-block/index.tsx +++ b/apps/web/lib/features/team/user-team-block/index.tsx @@ -83,7 +83,7 @@ export function UserTeamBlock({ className, active, member, publicTeam = false }: shadow="bigger" className={clsxm( 'relative items-center py-3 !px-4 dark:bg-[#1E2025] min-h-[7rem]', - ['dark:border border-t-[6px] ', cardColorType[timerStatusValue]], + ['dark:border border-t-[6px] dark:border-t-[6px]', cardColorType[timerStatusValue]], className )} > diff --git a/apps/web/lib/features/team/user-team-block/user-team-block-header.tsx b/apps/web/lib/features/team/user-team-block/user-team-block-header.tsx index 13b912613..3ad755f58 100644 --- a/apps/web/lib/features/team/user-team-block/user-team-block-header.tsx +++ b/apps/web/lib/features/team/user-team-block/user-team-block-header.tsx @@ -41,6 +41,7 @@ export function UserTeamBlockHeader() { members?.map((item) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion membersStatusNumber[item.timerStatus!]++; + if (item.timerStatus === undefined) membersStatusNumber.idle += 1; }); return ( @@ -128,8 +129,7 @@ export function UserTeamBlockHeader() {

Paused

@@ -203,30 +203,6 @@ export function UserTeamBlockHeader() { )}
- {/*
- - {hook.filterType === 'search' && ( - { - hook.toggleFilterType('search'); - }} - /> - )} - -
*/} ); diff --git a/apps/web/lib/features/team/user-team-card/index.tsx b/apps/web/lib/features/team/user-team-card/index.tsx index 7c2fe52ad..f2eb407c2 100644 --- a/apps/web/lib/features/team/user-team-card/index.tsx +++ b/apps/web/lib/features/team/user-team-card/index.tsx @@ -132,7 +132,7 @@ export function UserTeamCard({ {/* Task information */} -
+
diff --git a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx index 68f815ae4..6c706626e 100644 --- a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx +++ b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx @@ -3,11 +3,11 @@ import React from 'react'; function UserTeamTableHeader() { return (
-

Name

-

Task

-

Worked on Task

-

Estimate

-

Action

+

Name

+

Task

+

Worked on Task

+

Estimate

+

Action

); } diff --git a/apps/web/lib/features/timer/timer.tsx b/apps/web/lib/features/timer/timer.tsx index 108a17ec9..861a6eaf3 100644 --- a/apps/web/lib/features/timer/timer.tsx +++ b/apps/web/lib/features/timer/timer.tsx @@ -60,7 +60,7 @@ export function Timer({ className }: IClassName) { return (
@@ -69,7 +69,7 @@ export function Timer({ className }: IClassName) {