Skip to content

Commit

Permalink
Merge pull request #2261 from ever-co/2257-feature-kanban-page-modifi…
Browse files Browse the repository at this point in the history
…cations

[Fix]: working on kanban section functionality
  • Loading branch information
evereq authored Mar 5, 2024
2 parents c63e6fb + 0ed6dd3 commit a781bff
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 68 deletions.
19 changes: 16 additions & 3 deletions apps/web/app/[locale]/kanban/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 }>();
Expand Down Expand Up @@ -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 (
<>
Expand All @@ -67,7 +73,10 @@ const Kanban = () => {
{t('common.KANBAN')} {t('common.BOARD')}
</h1>
<div className="flex w-fit items-center space-x-2">
<strong className="text-gray-400">08:00 ( UTC +04:30 )</strong>
<strong className="text-gray-400">
{`(`}
{timezone.split('(')[1]}
</strong>
<div className="mt-1">
<Separator />
</div>
Expand All @@ -76,7 +85,10 @@ const Kanban = () => {
<Separator />
</div>

<button className="p-2 rounded-full border-2 border-[#0000001a] dark:border-white">
<button
onClick={openModal}
className="p-2 rounded-full border-2 border-[#0000001a] dark:border-white"
>
{/* <AddIcon width={24} height={24} className={'dark:stroke-white'} /> */}
<AddIcon className="w-6 h-6 text-foreground" />
</button>
Expand Down Expand Up @@ -180,6 +192,7 @@ const Kanban = () => {
)}
</div>
</MainLayout>
<InviteFormModal open={isOpen && !!user?.isEmailVerified} closeModal={closeModal} />
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/ui/svgs/circular-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}%{' '}
</span>
</div>
</>
Expand Down
119 changes: 72 additions & 47 deletions apps/web/lib/components/image-overlapper.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 <Skeleton height={40} width={40} borderRadius={100} className="rounded-full dark:bg-[#353741]" />;
}
return (
<div className="relative ">
{imageLength > 0 ? (
<div
className="flex h-fit flex-row justify-end items-center relative"
style={{
width: `${totalLength}px`
}}
>
{images.map((image: ImageOverlapperProps, index: number) => {
if (index < numberOfImagesDisplayed) {
return (
<Link href={`/profile/${image.id}`} className="relative w-[40px] h-[40px]" key={index}>
<Tooltip label={image.alt}>
<Image
src={image.url}
alt={`${image.alt} avatar`}
fill={true}
className="absolute rounded-full border-2 border-white"
style={stackImages(index, imageLength)}
/>
</Tooltip>
</Link>
);
}
})}
{images.length > numberOfImagesDisplayed && (
<div
style={{
width:
imageLength == 1 ? 40 : isMoreThanDisplay ? widthCalculate.length * 33 : widthCalculate.length * 35
}}
className="relative "
>
{firstArray.map((image, index) => (
<Link key={index} href={`/profile/${image.id}`}>
<div
className="absolute hover:!z-20 transition-all hover:scale-110"
style={{ zIndex: index + 1, left: index * 30, top: isMoreThanDisplay ? -8 : -16 }}
>
<Tooltip label={image.alt} placement="top">
<Image
src={image.url}
alt={`${image.alt} avatar`}
width={80}
height={80}
style={{ borderRadius: radius }}
className="!h-10 !w-10 border-2 border-white"
/>
</Tooltip>
</div>
</Link>
))}
{secondArray.length > 0 && (
<Popover>
<PopoverTrigger>
<div
className="flex flex-row text-sm text-[#282048] dark:text-white font-semibold items-center justify-center absolute h-[40px] w-[40px] rounded-full border-2 border-[#0000001a] dark:border-white bg-white dark:bg-[#191A20]"
style={stackImages(numberOfImagesDisplayed, imageLength)}
style={{
top: isMoreThanDisplay ? -8 : -16,
borderRadius: radius
}}
className="flex absolute left-28 z-[6] flex-row text-sm text-[#282048] dark:text-white font-semibold items-center justify-center !h-10 !w-10 border-2 border-[#0000001a] dark:border-white bg-white dark:bg-[#191A20]"
>
{imageLength - numberOfImagesDisplayed < 100 ? imageLength - numberOfImagesDisplayed : 99}+
{secondArray.length < 100 ? secondArray.length : 99}+
</div>
</PopoverTrigger>
<PopoverContent className="!p-0 bg-white dark:bg-dark--theme max-h-40 overflow-y-auto ">
<div className="flex flex-col space-y-2 m-2">
{secondArray.map((image: ImageOverlapperProps, index: number) => {
return (
<Link
href={`/profile/${image.id}`}
className="relative hover:bg-gray-300 hover:dark:bg-[#24262c] p-1 rounded-md"
key={index}
>
<div className="flex items-center">
<Image
src={image.url}
alt={`${image.alt} avatar`}
width={80}
height={80}
className="!h-10 !w-10 rounded-full border-2 border-white"
/>
<p className="ml-2">{image.alt}</p>
</div>
</Link>
);
})}
</div>
)}
</div>
) : (
<Skeleton height={40} width={40} borderRadius={100} className="rounded-full dark:bg-[#353741]" />
</PopoverContent>
</Popover>
)}
</div>
);
Expand Down
57 changes: 40 additions & 17 deletions apps/web/lib/components/kanban-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -145,7 +153,22 @@ export default function Item(props: ItemProps) {
const { collaborativeSelect } = useCollaborative(memberInfo.memberUser);

const menu = <>{!collaborativeSelect && <UserTeamCardMenu memberInfo={memberInfo} edition={taskEdition} />}</>;
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 (
<div
draggable={isDragging}
Expand Down Expand Up @@ -207,6 +230,7 @@ export default function Item(props: ItemProps) {
autoFocus={true}
autoInputSelectText={true}
onTaskClick={(e) => {
// TODO: implement
console.log(e);
}}
onEnterKey={() => {
Expand All @@ -216,32 +240,31 @@ export default function Item(props: ItemProps) {
</div>
)}
</div>
<CircularProgress percentage={10} />

<CircularProgress percentage={progress} />
</div>
<div className="my-2">
<HorizontalSeparator />
</div>
<div className="w-full flex items-center justify-between">
<div className="mt-1">
<div className="w-full h-10 flex items-center justify-between">
<div>
{item.status === TaskStatus.INPROGRESS ? (
<div className="flex items-center gap-2">
<small className="text-grey text-xs text-normal">Live:</small>
<p className="text-[#219653] font-medium text-sm">
{pad(hours)}:{pad(minutes)}:{pad(seconds)}{' '}
{h}h : {m}m : {s}s
</p>
</div>
) : (
<div className="flex items-center gap-2">
<small className="text-grey text-xs text-normal">Worked:</small>
<p className="text-black dark:text-white font-medium text-sm">
{pad(hours)}:{pad(minutes)}:{pad(seconds)}{' '}
<p className="text-black dark:text-white font-medium w-20 text-sm">
{h}h : {m}m : {s}s
</p>
</div>
)}
</div>
<div className="w-56 flex justify-end">
<ImageComponent radius={30} images={taskAssignee} />
</div>
<ImageComponent radius={30} images={taskAssignee} />
{item.issueType && (
<div className="flex flex-row items-center justify-center rounded-full w-5 h-5 z-10 bg-[#e5e7eb] dark:bg-[#181920] absolute top-0 right-0">
<div
Expand Down

0 comments on commit a781bff

Please sign in to comment.