From 9ab591b29daf9a697c8c5441370dbecfe1372bb4 Mon Sep 17 00:00:00 2001 From: Arick Bulakali <85836702+NdekoCode@users.noreply.github.com> Date: Wed, 20 Nov 2024 23:42:59 +0200 Subject: [PATCH] Fix/layout size and scroll effect (#3351) * refactor(web): [Layout] add app container component * refactor(web): [Header] make the global header fixed on scroll * refactor(web): [Footer] make the global footer fixed dynamically on scroll and setup on the layout * refactor(web): [Layout] update specific position of elements using z-index * fix(web): layout size * refactor(web): [Layout] update layout height * refactor(web): [UserTeamCard] fix size on resizing and w-max * fix(web): add scroll effect on team-members-card-view * refactor(web): [Layout] add resize component to global layout * refactor(web): fix body bg in light mode, make main header fixed on tasks pages and profile page * fix(web): [Setting] setting page layout * fix(web): [Eslint] error and add an environnement variable for checking build without eslint errors * refactor(web): fixed task detail header * docs(web): add main-layout docs * docs(web): add main-layout docs * refactor(web): fix my-tasks page * fix(web): [Profile] fix outstanding task overflow * fix(web): fix footer fixed on timesheet, settings and kanban pages * refactor(web): [Kanban] add a dynamic height on kanban item * fix(web): [Kanban] fix kanban overscroll * fix(web): [Dashboard] fix breadcum size * fix(web): [Team's Tasks'] fix space on team's tasks page * refactor(web): [Profile] optimize space on profile page * refactor(web): [Timesheet] optimize spaces on reports/timesheet page * fix(web): [Setting] double scroll on setting page * test: build --- apps/web/.env | 4 + apps/web/.env.sample | 4 + apps/web/app/[locale]/kanban/page.tsx | 525 ++++---- apps/web/app/[locale]/layout.tsx | 6 +- apps/web/app/[locale]/page-component.tsx | 91 +- .../app/[locale]/profile/[memberId]/page.tsx | 131 +- apps/web/app/[locale]/settings/layout.tsx | 90 +- .../app/[locale]/settings/personal/page.tsx | 7 +- apps/web/app/[locale]/task/[id]/component.tsx | 4 +- apps/web/app/[locale]/task/[id]/page.tsx | 122 +- apps/web/app/[locale]/team/tasks/page.tsx | 127 +- .../[memberId]/components/EditTaskModal.tsx | 428 +++---- .../components/FilterWithStatus.tsx | 82 +- .../components/RejectSelectedModal.tsx | 167 +-- .../components/TimeSheetFilterPopover.tsx | 278 +++-- .../components/TimesheetFilterDate.tsx | 616 +++++----- .../[memberId]/components/TimesheetView.tsx | 36 +- .../[locale]/timesheet/[memberId]/page.tsx | 365 +++--- apps/web/app/hooks/features/useKanban.ts | 263 ++-- apps/web/app/layout.tsx | 2 +- .../components/pages/team/tasks/TaskTable.tsx | 26 +- .../pages/team/tasks/tasks-data-table.tsx | 204 +--- apps/web/components/ui/data-table.tsx | 19 +- apps/web/components/ui/scroll-area.tsx | 72 +- apps/web/lib/components/Kanban.tsx | 81 +- apps/web/lib/components/accordian.tsx | 2 +- apps/web/lib/components/container.tsx | 2 +- apps/web/lib/components/sidebar-accordian.tsx | 4 +- .../features/all-teams-members-card-view.tsx | 6 +- .../users-teams-card/member-infos.tsx | 5 +- .../all-teams/users-teams-card/user-card.tsx | 35 +- .../user-team-active-task-times.tsx | 8 +- .../user-team-active-task.tsx | 10 +- .../user-team-task-estimate.tsx | 8 +- .../user-team-today-worked.tsx | 13 +- .../integrations/activity-calendar/index.tsx | 253 ++-- .../calendar/table-time-sheet.tsx | 1064 ++++++++--------- .../features/task/daily-plan/future-tasks.tsx | 12 +- .../task/daily-plan/outstanding-all.tsx | 3 +- .../task/daily-plan/outstanding-date.tsx | 5 +- .../features/task/daily-plan/past-tasks.tsx | 1 + apps/web/lib/features/task/task-card.tsx | 58 +- apps/web/lib/features/task/task-filters.tsx | 18 +- apps/web/lib/features/task/task-status.tsx | 4 +- apps/web/lib/features/team-member-cell.tsx | 152 ++- apps/web/lib/features/team-member-header.tsx | 42 +- .../lib/features/team-members-card-view.tsx | 3 +- .../lib/features/team-members-kanban-view.tsx | 554 ++++----- .../lib/features/team-members-table-view.tsx | 1 + .../user-team-block-header.tsx | 408 +++---- .../features/team/user-team-card/index.tsx | 2 +- .../team/user-team-card/task-skeleton.tsx | 25 +- .../user-team-table-header.tsx | 3 +- apps/web/lib/features/unverified-email.tsx | 2 +- apps/web/lib/features/user-nav-menu.tsx | 8 +- apps/web/lib/features/user-profile-plans.tsx | 16 +- apps/web/lib/features/user-profile-tasks.tsx | 4 +- apps/web/lib/layout/AppContainer.tsx | 42 + apps/web/lib/layout/GlobalFooter.tsx | 42 + apps/web/lib/layout/GlobalHeader.tsx | 68 ++ apps/web/lib/layout/footer.tsx | 59 +- apps/web/lib/layout/main-layout.tsx | 205 ++-- apps/web/lib/utils.ts | 26 + apps/web/next.config.js | 11 +- apps/web/package.json | 2 +- apps/web/styles/globals.css | 98 +- yarn.lock | 34 +- 67 files changed, 3630 insertions(+), 3438 deletions(-) create mode 100644 apps/web/lib/layout/AppContainer.tsx create mode 100644 apps/web/lib/layout/GlobalFooter.tsx create mode 100644 apps/web/lib/layout/GlobalHeader.tsx diff --git a/apps/web/.env b/apps/web/.env index 41c6a1aa3..9180c30c7 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -133,3 +133,7 @@ NEXT_PUBLIC_CHATWOOT_API_KEY= # PostHog NEXT_PUBLIC_POSTHOG_KEY= NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + +# Warning: IF TRUE This allows production builds to successfully complete even if +# your project has ESLint errors. +NEXT_IGNORE_ESLINT_ERROR_ON_BUILD=true diff --git a/apps/web/.env.sample b/apps/web/.env.sample index 7a6cedce2..62ae03258 100644 --- a/apps/web/.env.sample +++ b/apps/web/.env.sample @@ -83,3 +83,7 @@ NEXT_PUBLIC_MEET_DOMAIN="meet.ever.team" # Private Variables (Meet) MEET_JWT_APP_ID=ever_teams MEET_JWT_APP_SECRET= + +# Warning: IF TRUE This allows production builds to successfully complete even if +# your project has ESLint errors. +NEXT_IGNORE_ESLINT_ERROR_ON_BUILD=true diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 71584b962..018561643 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -1,23 +1,16 @@ 'use client'; - import { KanbanTabs } from '@app/constants'; -import { - useAuthenticateUser, - useModal, - 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'; -import { Breadcrumb, Container, Divider } from 'lib/components'; +import { Breadcrumb, Container } from 'lib/components'; import { KanbanView } from 'lib/features/team-members-kanban-view'; -import { Footer, MainLayout } from 'lib/layout'; +import { MainLayout } from 'lib/layout'; import { useEffect, useMemo, useState } from 'react'; import { useTranslations } from 'next-intl'; import { useParams, useSearchParams } from 'next/navigation'; -import ImageComponent, { - ImageOverlapperProps -} from 'lib/components/image-overlapper'; +import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper'; import Separator from '@components/ui/separator'; import HeaderTabs from '@components/pages/main/header-tabs'; import { AddIcon, PeoplesIcon } from 'assets/svg'; @@ -25,288 +18,262 @@ import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; import KanbanSearch from '@components/pages/kanban/search-bar'; import { - EpicPropertiesDropdown, - StatusDropdown, - TStatusItem, - TaskLabelsDropdown, - TaskPropertiesDropdown, - TaskSizesDropdown, - taskIssues, - useStatusValue + EpicPropertiesDropdown, + StatusDropdown, + TStatusItem, + TaskLabelsDropdown, + TaskPropertiesDropdown, + TaskSizesDropdown, + taskIssues, + useStatusValue } from 'lib/features'; import { useAtomValue } from 'jotai'; import { fullWidthState } from '@app/stores/fullWidth'; import { CircleIcon } from 'lucide-react'; import { XMarkIcon } from '@heroicons/react/20/solid'; -import Head from 'next/head'; -import { clsxm } from '@app/utils'; const Kanban = () => { - const { - data, - setSearchTasks, - searchTasks, - isLoading, - setPriority, - setSizes, - setLabels, - setEpics, - setIssues, - issues - } = useKanban(); + const { + data, + setSearchTasks, + searchTasks, + isLoading, + setPriority, + setSizes, + setLabels, + setEpics, + setIssues, + issues + } = useKanban(); + + const { activeTeam, isTrackingEnabled } = useOrganizationTeams(); + const t = useTranslations(); + const params = useParams<{ locale: string }>(); + const fullWidth = useAtomValue(fullWidthState); + const currentLocale = params ? params.locale : null; + const [activeTab, setActiveTab] = useState(KanbanTabs.TODAY); + const employee = useSearchParams().get('employee'); + const breadcrumbPath = useMemo( + () => [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: activeTeam?.name || '', href: '/' }, + { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` } + ], + [activeTeam?.name, currentLocale, t] + ); - const { activeTeam, isTrackingEnabled } = useOrganizationTeams(); - const t = useTranslations(); - const params = useParams<{ locale: string }>(); - const fullWidth = useAtomValue(fullWidthState); - const currentLocale = params ? params.locale : null; - const [activeTab, setActiveTab] = useState(KanbanTabs.TODAY); - const employee = useSearchParams().get('employee'); - const breadcrumbPath = useMemo( - () => [ - { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, - { title: activeTeam?.name || '', href: '/' }, - { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` } - ], - [activeTeam?.name, currentLocale, t] - ); + const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; - const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; + const teamMembers: ImageOverlapperProps[] = []; - const teamMembers: ImageOverlapperProps[] = []; + activeTeamMembers.map((member: any) => { + teamMembers.push({ + id: member.employee.user.id, + url: member.employee.user.imageUrl, + alt: member.employee.user.firstName + }); + }); + const tabs = [ + { name: t('common.TODAY'), value: KanbanTabs.TODAY }, + { 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(); - activeTeamMembers.map((member: any) => { - teamMembers.push({ - id: member.employee.user.id, - url: member.employee.user.imageUrl, - alt: member.employee.user.firstName - }); - }); - const tabs = [ - { name: t('common.TODAY'), value: KanbanTabs.TODAY }, - { 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(); - const { items } = useStatusValue<'issueType'>({ - status: taskIssues, - value: issues as any, - onValueChange: setIssues as any - }); + const { items } = useStatusValue<'issueType'>({ + status: taskIssues, + value: issues as any, + onValueChange: setIssues as any + }); - useEffect(() => { - const lastPath = breadcrumbPath.slice(-1)[0]; - if (employee) { - if (lastPath.title == 'Kanban') { - breadcrumbPath.push({ - title: employee, - href: `/${currentLocale}/kanban?employee=${employee}` - }); - } else { - breadcrumbPath.pop(); - breadcrumbPath.push({ - title: employee, - href: `/${currentLocale}/kanban?employee=${employee}` - }); - } - } else { - if (lastPath.title !== 'Kanban') { - breadcrumbPath.pop(); - } - } - }, [breadcrumbPath, currentLocale, employee]); - return ( - <> - - - {t('common.KANBAN')} {t('common.BOARD')} - - - -
-
- -
-
- - -
-
- -
-
-
-

- {t('common.KANBAN')} {t('common.BOARD')} -

-
- - {`(`} - {timezone.split('(')[1]} - -
- -
- -
- -
+ useEffect(() => { + const lastPath = breadcrumbPath.slice(-1)[0]; + if (employee) { + if (lastPath.title == 'Kanban') { + breadcrumbPath.push({ + title: employee, + href: `/${currentLocale}/kanban?employee=${employee}` + }); + } else { + breadcrumbPath.pop(); + breadcrumbPath.push({ + title: employee, + href: `/${currentLocale}/kanban?employee=${employee}` + }); + } + } else if (lastPath.title !== 'Kanban') { + breadcrumbPath.pop(); + } + }, [breadcrumbPath, currentLocale, employee]); + return ( + <> + + +
+
+ + +
+
+ +
+
+
+

+ {t('common.KANBAN')} {t('common.BOARD')} +

+
+ + {`(`} + {timezone.split('(')[1]} + +
+ +
+ +
+ +
- -
-
-
-
- {tabs.map((tab) => ( -
setActiveTab(tab.value)} - className={`cursor-pointer pt-2.5 px-5 pb-[30px] text-base font-semibold ${ - activeTab === tab.value - ? 'border-b-[#3826A6] text-[#3826A6] dark:text-white dark:border-b-white' - : 'border-b-white dark:border-b-[#191A20] dark:text-white text-[#282048]' - }`} - style={{ - borderBottomWidth: '3px', - borderBottomStyle: 'solid' - }} - > - {tab.name} -
- ))} -
-
-
- setEpics(values || [])} - className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" - multiple={true} - /> -
- {/*
*/} -
-
- -
- {issues.icon ?? } -
-

{issues.name}

-
- {issues.value && ( -
- setIssues({ - name: 'Issues', - icon: null, - bgColor: '', - value: '' - }) - } - className="w-5 h-5 z-50 p-0.5 cursor-pointer" - > - -
- )} -
+ +
+
+
+
+ {tabs.map((tab) => ( +
setActiveTab(tab.value)} + className={`cursor-pointer pt-2.5 px-5 pb-[30px] text-base font-semibold ${ + activeTab === tab.value + ? 'border-b-[#3826A6] text-[#3826A6] dark:text-white dark:border-b-white' + : 'border-b-white dark:border-b-[#191A20] dark:text-white text-[#282048]' + }`} + style={{ + borderBottomWidth: '3px', + borderBottomStyle: 'solid' + }} + > + {tab.name} +
+ ))} +
+
+
+ setEpics(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
+ {/*
*/} +
+
+ +
+ {issues.icon ?? } +
+

{issues.name}

+
+ {issues.value && ( +
+ setIssues({ + name: 'Issues', + icon: null, + bgColor: '', + value: '' + }) + } + className="w-5 h-5 z-10 p-0.5 cursor-pointer" + > + +
+ )} +
- { - setIssues(items.find((v) => v.name == e) as TStatusItem); - }} - issueType="issue" - /> -
- {/*
*/} -
- setLabels(values || [])} - className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" - multiple={true} - /> -
-
- setPriority(values || [])} - className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" - multiple={true} - /> -
-
- setSizes(values || [])} - className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" - multiple={true} - /> -
-
- -
- -
-
- {/*
*/} - -
-
- {/** TODO:fetch teamtask based on days */} - {activeTab && ( // add filter for today, yesterday and tomorrow -
- {Object.keys(data).length > 0 ? ( - - ) : ( - - )} -
- )} -
- -
- -
-
- - - ); + { + setIssues(items.find((v) => v.name == e) as TStatusItem); + }} + issueType="issue" + /> +
+ {/*
*/} +
+ setLabels(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
+
+ setPriority(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
+
+ setSizes(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
+
+ +
+ +
+
+ {/*
*/} + + + } + > + {/** TODO:fetch teamtask based on days */} +
+ {activeTab && + (Object.keys(data).length > 0 ? ( + + ) : ( + // add filter for today, yesterday and tomorrow +
+ +
+ ))} +
+
+ + + ); }; export default withAuthentication(Kanban, { displayName: 'Kanban' }); diff --git a/apps/web/app/[locale]/layout.tsx b/apps/web/app/[locale]/layout.tsx index 8efdc3b8e..8dd5ce0ce 100644 --- a/apps/web/app/[locale]/layout.tsx +++ b/apps/web/app/[locale]/layout.tsx @@ -130,7 +130,11 @@ const LocaleLayout = ({ children, params: { locale }, pageProps }: PropsWithChil */} - + diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index febc94842..68f697eed 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -1,7 +1,6 @@ /* eslint-disable no-mixed-spaces-and-tabs */ 'use client'; - import React, { useEffect, useState } from 'react'; import { useOrganizationTeams } from '@app/hooks'; import { clsxm } from '@app/utils'; @@ -29,12 +28,11 @@ import { headerTabs } from '@app/stores/header-tabs'; import { usePathname } from 'next/navigation'; import { PeoplesIcon } from 'assets/svg'; import TeamMemberHeader from 'lib/features/team-member-header'; -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; import { TeamOutstandingNotifications } from 'lib/features/team/team-outstanding-notifications'; function MainPage() { const t = useTranslations(); - const [headerSize, setHeaderSize] = useState(10); + const [headerSize] = useState(10); const { isTeamMember, isTrackingEnabled, activeTeam } = useOrganizationTeams(); const [fullWidth, setFullWidth] = useAtom(fullWidthState); const [view, setView] = useAtom(headerTabs); @@ -67,63 +65,40 @@ function MainPage() { - - -
- - {/* */} - - setHeaderSize(size)} - > -
-
-
-
- - - -
- -
- -
-
+ mainHeaderSlot={ +
+
+
+
+ + + +
-
-
- +
+ +
+
- +
+
+ - -
+ - {isTeamMember ? ( - - ) : null} -
- +
-
- - - - {/* */} - - {isTeamMember ? : } - - -
+ {isTeamMember ? : null} +
+ +
+
+ } + footerClassName={clsxm('')} + > + +
{isTeamMember ? : }
@@ -131,19 +106,19 @@ function MainPage() { ); } -function TaskTimerSection({ isTrackingEnabled }: { isTrackingEnabled: boolean }) { +function TaskTimerSection({ isTrackingEnabled }: Readonly<{ isTrackingEnabled: boolean }>) { const [showInput, setShowInput] = React.useState(false); return ( {isTrackingEnabled ? ( -
+
) : null} diff --git a/apps/web/app/[locale]/profile/[memberId]/page.tsx b/apps/web/app/[locale]/profile/[memberId]/page.tsx index c3d41d65c..690ee558d 100644 --- a/apps/web/app/[locale]/profile/[memberId]/page.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/page.tsx @@ -17,7 +17,6 @@ import { ScreenshootTab } from 'lib/features/activity/screenshoots'; import { AppsTab } from 'lib/features/activity/apps'; import { VisitedSitesTab } from 'lib/features/activity/visited-sites'; import { activityTypeState } from '@app/stores/activity-type'; -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; import { UserProfileDetail } from './components/UserProfileDetail'; import { cn } from 'lib/utils'; // import { ActivityCalendar } from 'lib/features/activity/calendar'; @@ -26,7 +25,7 @@ export type FilterTab = 'Tasks' | 'Screenshots' | 'Apps' | 'Visited Sites'; const Profile = React.memo(function ProfilePage({ params }: { params: { memberId: string } }) { const profile = useUserProfilePage(); - const [headerSize, setHeaderSize] = useState(10); + const [headerSize] = useState(10); const { user } = useAuthenticateUser(); const { isTrackingEnabled, activeTeam, activeTeamManagers } = useOrganizationTeams(); const members = activeTeam?.members; @@ -115,79 +114,71 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId } return ( - - - setHeaderSize(size)} - > - -
- {/* Breadcrumb */} -
- - - - - -
+ +
+ {/* Breadcrumb */} +
+ + + + + +
- {/* User Profile Detail */} -
- - - {profileIsAuthUser && isTrackingEnabled && ( - - )} -
- {/* TaskFilter */} - + {/* User Profile Detail */} +
+ + + {profileIsAuthUser && isTrackingEnabled && ( + + )}
- - {/*
+ {/* TaskFilter */} + +
+ + } + > + {/*
*/} - - - - {hook.tab == 'worked' && canSeeActivity && ( - -
- {Object.keys(activityScreens).map((filter, i) => ( -
- {i !== 0 && } -
changeActivityFilter(filter as FilterTab)} - > - {filter} -
-
- ))} + {hook.tab == 'worked' && canSeeActivity && ( + +
+ {Object.keys(activityScreens).map((filter, i) => ( +
+ {i !== 0 && } +
changeActivityFilter(filter as FilterTab)} + > + {filter} +
- - )} - - - {hook.tab !== 'worked' || activityFilter == 'Tasks' ? ( - - ) : ( - activityScreens[activityFilter] ?? null - )} - - - + ))} +
+
+ )} + + + {hook.tab !== 'worked' || activityFilter == 'Tasks' ? ( + + ) : ( + activityScreens[activityFilter] ?? null + )} + ); }); diff --git a/apps/web/app/[locale]/settings/layout.tsx b/apps/web/app/[locale]/settings/layout.tsx index 232661884..7ef2e1df6 100644 --- a/apps/web/app/[locale]/settings/layout.tsx +++ b/apps/web/app/[locale]/settings/layout.tsx @@ -9,59 +9,57 @@ import { LeftSideSettingMenu } from 'lib/settings'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { useAtom, useAtomValue } from 'jotai'; -import { clsxm } from '@app/utils'; import { withAuthentication } from 'lib/app/authenticator'; import { usePathname } from 'next/navigation'; import { useOrganizationTeams } from '@app/hooks'; +import { cn } from '@/lib/utils'; const SettingsLayout = ({ children }: { children: JSX.Element }) => { - const { isTrackingEnabled } = useOrganizationTeams(); - const t = useTranslations(); - const [user] = useAtom(userState); - const fullWidth = useAtomValue(fullWidthState); - const pathName = usePathname(); - const getEndPath: any = pathName?.split('settings/')[1]; - const endWord: 'TEAM' | 'PERSONAL' = getEndPath?.toUpperCase(); - const breadcrumb = [ - { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, - { title: t('common.SETTINGS'), href: pathName as string }, - { title: t(`common.${endWord}`), href: pathName as string } - ]; + const { isTrackingEnabled } = useOrganizationTeams(); + const t = useTranslations(); + const [user] = useAtom(userState); + const fullWidth = useAtomValue(fullWidthState); + const pathName = usePathname(); + const getEndPath: any = pathName?.split('settings/')[1]; + const endWord: 'TEAM' | 'PERSONAL' = getEndPath?.toUpperCase(); + const breadcrumb = [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: t('common.SETTINGS'), href: pathName as string }, + { title: t(`common.${endWord}`), href: pathName as string } + ]; - if (!user) { - return ; - } else { - return ( - -
- - - - + if (!user) { + return ; + } else { + return ( + + + + + - - -
- -
- -
- {children} -
-
-
-
- ); - } + + +
+ } + > + +
+ +
{children}
+
+
+ + ); + } }; export default withAuthentication(SettingsLayout, { displayName: 'Settings' }); diff --git a/apps/web/app/[locale]/settings/personal/page.tsx b/apps/web/app/[locale]/settings/personal/page.tsx index 4a2585354..63bb4889d 100644 --- a/apps/web/app/[locale]/settings/personal/page.tsx +++ b/apps/web/app/[locale]/settings/personal/page.tsx @@ -11,15 +11,15 @@ const Personal = () => { const t = useTranslations(); return ( -
+
- {/* @@ -38,7 +38,6 @@ const Personal = () => { diff --git a/apps/web/app/[locale]/task/[id]/component.tsx b/apps/web/app/[locale]/task/[id]/component.tsx index 4883d3600..46cea2009 100644 --- a/apps/web/app/[locale]/task/[id]/component.tsx +++ b/apps/web/app/[locale]/task/[id]/component.tsx @@ -22,9 +22,9 @@ interface ITaskDetailsComponentProps { export function TaskDetailsComponent(props: ITaskDetailsComponentProps) { const { task } = props; return ( -
+
-
+
diff --git a/apps/web/app/[locale]/task/[id]/page.tsx b/apps/web/app/[locale]/task/[id]/page.tsx index 44335da0e..2b0651f14 100644 --- a/apps/web/app/[locale]/task/[id]/page.tsx +++ b/apps/web/app/[locale]/task/[id]/page.tsx @@ -1,10 +1,6 @@ 'use client'; -import { - useOrganizationTeams, - useTeamTasks, - useUserProfilePage -} from '@app/hooks'; +import { useOrganizationTeams, useTeamTasks, useUserProfilePage } from '@app/hooks'; import { withAuthentication } from 'lib/app/authenticator'; import { Breadcrumb, Container } from 'lib/components'; import { ArrowLeftIcon } from 'assets/svg'; @@ -18,72 +14,66 @@ import { useAtomValue } from 'jotai'; import { TaskDetailsComponent } from './component'; const TaskDetails = () => { - const profile = useUserProfilePage(); - const t = useTranslations(); - const router = useRouter(); - const params = useParams(); - const { isTrackingEnabled, activeTeam } = useOrganizationTeams(); - const { - getTaskById, - detailedTask: task, - getTasksByIdLoading - } = useTeamTasks(); - const fullWidth = useAtomValue(fullWidthState); + const profile = useUserProfilePage(); + const t = useTranslations(); + const router = useRouter(); + const params = useParams(); + const { isTrackingEnabled, activeTeam } = useOrganizationTeams(); + const { getTaskById, detailedTask: task, getTasksByIdLoading } = useTeamTasks(); + const fullWidth = useAtomValue(fullWidthState); - const id = params?.id; + const id = params?.id; - const breadcrumb = [ - { title: activeTeam?.name || '', href: '/' }, - { - title: JSON.parse(t('pages.taskDetails.BREADCRUMB')), - href: `/task/${id}` - } - ]; + const breadcrumb = [ + { title: activeTeam?.name || '', href: '/' }, + { + title: JSON.parse(t('pages.taskDetails.BREADCRUMB')), + href: `/task/${id}` + } + ]; - useEffect(() => { - if ( - router && - // If id is passed in query param - id && - // Either no task or task id doesn't match query id - (!task || (task && task.id !== id)) && - !getTasksByIdLoading - ) { - getTaskById(id as string); - } - }, [getTaskById, router, task, getTasksByIdLoading, id]); + useEffect(() => { + if ( + router && + // If id is passed in query param + id && + // Either no task or task id doesn't match query id + (!task || (task && task.id !== id)) && + !getTasksByIdLoading + ) { + getTaskById(id as string); + } + }, [getTaskById, router, task, getTasksByIdLoading, id]); - return ( - -
- -
- { - router.replace('/'); - }} - > - - + return ( + + +
+ { + router.replace('/'); + }} + > + + - -
-
-
- - - {task && } - {/* */} - - - ); + +
+ +
+ } + > + + {task && } + {/* */} + + + ); }; export default withAuthentication(TaskDetails, { displayName: 'TaskDetails' }); diff --git a/apps/web/app/[locale]/team/tasks/page.tsx b/apps/web/app/[locale]/team/tasks/page.tsx index db1844d68..33cb32f28 100644 --- a/apps/web/app/[locale]/team/tasks/page.tsx +++ b/apps/web/app/[locale]/team/tasks/page.tsx @@ -1,5 +1,5 @@ 'use client'; -import { Breadcrumb, Container } from 'lib/components'; +import { Breadcrumb, Container, Paginate } from 'lib/components'; import { MainLayout } from 'lib/layout'; import { useParams } from 'next/navigation'; import { useMemo } from 'react'; @@ -7,10 +7,21 @@ import { useTranslations } from 'next-intl'; import { useAtomValue } from 'jotai'; import { fullWidthState } from '@app/stores/fullWidth'; -import { TaskTable } from '@components/pages/team/tasks/TaskTable'; -import { useOrganizationTeams } from '@app/hooks'; +import { useOrganizationTeams, useTeamTasks } from '@app/hooks'; import { withAuthentication } from '@/lib/app/authenticator'; +import { ITeamTask } from '@/app/interfaces'; + +import { getCoreRowModel, getFilteredRowModel, useReactTable } from '@tanstack/react-table'; +import StatusBadge from '@components/pages/team/tasks/StatusBadge'; +import { getStatusColor } from '@/lib/utils'; +import FilterButton from '@components/pages/team/tasks/FilterButton'; +import { Input } from '@components/ui/input'; +import { Search } from 'lucide-react'; +import { Button } from '@components/ui/button'; +import { TaskTable } from '@components/pages/team/tasks/TaskTable'; +import { columns } from '@components/pages/team/tasks/columns'; +import { usePagination } from '@/app/hooks/features/usePagination'; const TeamTask = () => { const t = useTranslations(); const params = useParams<{ locale: string }>(); @@ -23,15 +34,113 @@ const TeamTask = () => { { title: activeTeam?.name || '', href: '/' }, { title: "Team's Task", href: `/${currentLocale}/team/task` } ], - [activeTeam?.name, currentLocale] + [activeTeam?.name, currentLocale, t] ); + const { tasks } = useTeamTasks(); + + const { total, onPageChange, itemsPerPage, itemOffset, endOffset, setItemsPerPage, currentItems } = + usePagination(tasks); + const table = useReactTable({ + data: currentItems, + columns, + + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel() + }); + return ( - - - - - + + + +
+
+

+ {t('sidebar.TEAMTASKS')} +

+ +
+
+ + } + childrenClassName="bg-white dark:bg-dark--theme" + > +
+ + + +
); }; diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx index 3dffdc1b5..1828d9a9b 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx @@ -1,231 +1,231 @@ -import { Button, Modal, statusColor } from "@/lib/components"; -import { IoMdArrowDropdown } from "react-icons/io"; -import { FaRegClock } from "react-icons/fa"; -import { DatePickerFilter } from "./TimesheetFilterDate"; -import { useState } from "react"; -import { useTranslations } from "next-intl"; -import { clsxm } from "@/app/utils"; -import { Item, ManageOrMemberComponent, getNestedValue } from "@/lib/features/manual-time/manage-member-component"; -import { useTeamTasks } from "@/app/hooks"; -import { CustomSelect } from "@/lib/features"; -import { statusTable } from "./TimesheetAction"; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Button, Modal, statusColor } from '@/lib/components'; +import { IoMdArrowDropdown } from 'react-icons/io'; +import { FaRegClock } from 'react-icons/fa'; +import { DatePickerFilter } from './TimesheetFilterDate'; +import { useState } from 'react'; +import { useTranslations } from 'next-intl'; +import { clsxm } from '@/app/utils'; +import { Item, ManageOrMemberComponent, getNestedValue } from '@/lib/features/manual-time/manage-member-component'; +import { useTeamTasks } from '@/app/hooks'; +import { CustomSelect } from '@/lib/features'; +import { statusTable } from './TimesheetAction'; export interface IEditTaskModalProps { - isOpen: boolean; - closeModal: () => void; - + isOpen: boolean; + closeModal: () => void; } export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) { - const { activeTeam } = useTeamTasks(); - const t = useTranslations(); - const [dateRange, setDateRange] = useState<{ from: Date | null }>({ - from: new Date(), - }); - const [endTime, setEndTime] = useState(''); - const [startTime, setStartTime] = useState(''); - const [isBillable, setIsBillable] = useState(false); - const [notes, setNotes] = useState(''); - const memberItemsLists = { - Project: activeTeam?.projects as [], - }; - const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => { - // Handle value changes - }; - const selectedValues = { - Teams: null, - }; - - const handleChange = (field: string, selectedItem: Item | null) => { - // Handle field changes - }; - - const fields = [ - { - label: 'Project', - placeholder: 'Select a project', - isRequired: true, - valueKey: 'id', - displayKey: 'name', - element: 'Project' - }, - ]; - - const handleFromChange = (fromDate: Date | null) => { - setDateRange((prev) => ({ ...prev, from: fromDate })); - }; - return ( - -
-
- #321 Spike for creating calendar views on mobile -
- for - Savannah Nguyen - -
-
-
-
- Task Time -
- - 08:10h -
-
-
-
- - setStartTime(e.target.value)} - className="w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" - required - /> -
+ const { activeTeam } = useTeamTasks(); + const t = useTranslations(); + const [dateRange, setDateRange] = useState<{ from: Date | null }>({ + from: new Date() + }); + const [endTime, setEndTime] = useState(''); + const [startTime, setStartTime] = useState(''); + const [isBillable, setIsBillable] = useState(false); + const [notes, setNotes] = useState(''); + const memberItemsLists = { + Project: activeTeam?.projects as [] + }; + const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => { + // Handle value changes + }; + const selectedValues = { + Teams: null + }; -
- + const handleChange = (field: string, selectedItem: Item | null) => { + // Handle field changes + }; - setEndTime(e.target.value)} - className="w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" - required - /> -
+ const fields = [ + { + label: 'Project', + placeholder: 'Select a project', + isRequired: true, + valueKey: 'id', + displayKey: 'name', + element: 'Project' + } + ]; -
-
- {t("manualTime.DATE")} - -
-
- getNestedValue(item, displayKey) || ''} - itemToValue={(item, valueKey) => getNestedValue(item, valueKey) || ''} - /> -
-
- -
- setIsBillable(!isBillable)} - label={t('pages.timesheet.BILLABLE.YES')} - /> - setIsBillable(!isBillable)} - label={t('pages.timesheet.BILLABLE.NO')} - /> -
-
-
- Notes -