From ae5afd606f345518ebb96377a297bf05fee87929 Mon Sep 17 00:00:00 2001 From: "Thierry CH." Date: Thu, 15 Aug 2024 09:11:01 +0200 Subject: [PATCH 01/15] refactor: improve the workspaces list on login/workspace screen (#2899) * refactor: improve the workspaces list on login/workspace screen * fix: fix spell typo --------- Co-authored-by: Gloire Mutaliko (Salva) <86450367+GloireMutaliko21@users.noreply.github.com> --- .../app/[locale]/auth/passcode/component.tsx | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/apps/web/app/[locale]/auth/passcode/component.tsx b/apps/web/app/[locale]/auth/passcode/component.tsx index 7ebb81c22..2414393d4 100644 --- a/apps/web/app/[locale]/auth/passcode/component.tsx +++ b/apps/web/app/[locale]/auth/passcode/component.tsx @@ -27,6 +27,8 @@ import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar'; import SocialLogins from '../social-logins-buttons'; import { useSession } from 'next-auth/react'; import { LAST_WORSPACE_AND_TEAM, USER_SAW_OUTSTANDING_NOTIFICATION } from '@app/constants'; +import { MdOutlineKeyboardArrowDown } from 'react-icons/md'; +import { cn } from 'lib/utils'; function AuthPasscode() { const form = useAuthenticationPasscode(); @@ -78,7 +80,8 @@ function AuthPasscode() { {form.authScreen.screen === 'workspace' && ( )} - + + {/* Social logins */} @@ -412,6 +415,12 @@ type IWorkSpace = { export function WorkSpaceComponent(props: IWorkSpace) { const t = useTranslations(); + const [expandedWorkspace, setExpandedWorkspace] = useState(props.selectedWorkspace); + + useEffect(() => { + setExpandedWorkspace(props.selectedWorkspace); + }, [props.selectedWorkspace]); + return (
(
- {worksace.user.tenant.name} +
setExpandedWorkspace(index)} + className="flex cursor-pointer items-center justify-center gap-1" + > + {worksace.user.tenant.name} + + + +
{ @@ -456,7 +480,9 @@ export function WorkSpaceComponent(props: IWorkSpace) { )}
- + {/*
*/}
{worksace.current_teams?.map((team) => ( From bd175a41ff08d872a2e756470cd3c1ff7d891631 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 15 Aug 2024 22:19:49 +0200 Subject: [PATCH 02/15] refactor: focus should be on the first input which has a digit when user click on edit | estimate task --- apps/web/lib/features/task/task-estimate.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/web/lib/features/task/task-estimate.tsx b/apps/web/lib/features/task/task-estimate.tsx index 5a0c5b864..4cef5407b 100644 --- a/apps/web/lib/features/task/task-estimate.tsx +++ b/apps/web/lib/features/task/task-estimate.tsx @@ -5,7 +5,7 @@ import { ITeamTask, Nullable } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { EditPenBoxIcon, CheckCircleTickIcon as TickSaveIcon, LoadingIcon } from 'assets/svg'; import { TimeInputField } from 'lib/components'; -import { MutableRefObject, useEffect } from 'react'; +import { MutableRefObject, useEffect, useRef } from 'react'; type Props = { _task?: Nullable; @@ -42,9 +42,22 @@ export function TaskEstimate({ } = useTaskEstimation(_task); const onCloseEditionRef = useCallbackRef(onCloseEdition); const closeable_fcRef = useCallbackRef(closeable_fc); + const hourRef = useRef(null); + const minRef = useRef(null); useEffect(() => { !editableMode && onCloseEditionRef.current && onCloseEditionRef.current(); + + if (editableMode) { + if (value['hours']) { + hourRef.current?.focus(); + } else if (value['minutes']) { + minRef.current?.focus(); + } else { + hourRef.current?.focus(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [editableMode, onCloseEditionRef]); useEffect(() => { @@ -60,6 +73,7 @@ export function TaskEstimate({ return (
{ @@ -91,6 +105,7 @@ export function TaskEstimate({ ) : null ) : null} { From 39731f8a538cbe20c36bfe7744564b0d68a5f18f Mon Sep 17 00:00:00 2001 From: AKILIMAILI CIZUNGU Innocent <51681130+Innocent-Akim@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:47:06 +0200 Subject: [PATCH 03/15] [Improvement]: Timer | Implement hide/show functionality across multiple pages (#2904) * feat: implemented logic to hide or show the timer component depending on the pages * fix: cspell * fix: cspell * update: Conditional Rendering | Add logic for header size, tracking, and profile authentication * fix: Typo with Scroll Word * refact: show small Timer in All Team Page --------- Co-authored-by: Cedric Karungu --- apps/web/app/[locale]/all-teams/component.tsx | 4 +++- apps/web/app/[locale]/kanban/page.tsx | 4 ++-- apps/web/app/[locale]/page-component.tsx | 6 ++++- .../app/[locale]/profile/[memberId]/page.tsx | 4 +--- apps/web/app/[locale]/settings/layout.tsx | 9 +++++--- apps/web/app/hooks/index.ts | 2 ++ apps/web/app/hooks/useScrollListener.ts | 22 +++++++++++++++++++ 7 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 apps/web/app/hooks/useScrollListener.ts diff --git a/apps/web/app/[locale]/all-teams/component.tsx b/apps/web/app/[locale]/all-teams/component.tsx index 3e572b9cf..2f0f0a54e 100644 --- a/apps/web/app/[locale]/all-teams/component.tsx +++ b/apps/web/app/[locale]/all-teams/component.tsx @@ -15,12 +15,14 @@ import { HeaderTabs } from '@components/pages/all-teams/header-tabs'; import { allTeamsHeaderTabs } from '@app/stores/header-tabs'; import AllTeamsMembers from 'lib/features/all-teams-members'; import { MemberFilter } from 'lib/features/all-teams/all-team-members-filter'; +import { useOrganizationTeams } from '@app/hooks'; function AllTeamsPage() { const t = useTranslations(); const fullWidth = useRecoilValue(fullWidthState); const view = useRecoilValue(allTeamsHeaderTabs); const { filteredTeams, userManagedTeams } = useOrganizationAndTeamManagers(); + const { isTrackingEnabled } = useOrganizationTeams(); const breadcrumb = [ { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, @@ -33,7 +35,7 @@ function AllTeamsPage() { if (userManagedTeams.length < 2) return ; return ( - + {/* Breadcrumb */}
diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 490d1b61f..c7dff39c2 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -167,8 +167,8 @@ const Kanban = () => { key={tab.name} onClick={() => 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]' + ? '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', diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 2c6c76b80..f2afa3ea3 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -64,7 +64,11 @@ function MainPage() { <>
{/*
*/} - +
diff --git a/apps/web/app/[locale]/profile/[memberId]/page.tsx b/apps/web/app/[locale]/profile/[memberId]/page.tsx index 126c7be80..69454ee52 100644 --- a/apps/web/app/[locale]/profile/[memberId]/page.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/page.tsx @@ -29,9 +29,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 { user } = useAuthenticateUser(); const { isTrackingEnabled, activeTeam, activeTeamManagers } = useOrganizationTeams(); const members = activeTeam?.members; @@ -115,7 +113,7 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId
) : ( - + { + const { isTrackingEnabled, } = useOrganizationTeams(); const t = useTranslations(); const [user] = useRecoilState(userState); const fullWidth = useRecoilValue(fullWidthState); @@ -26,12 +28,13 @@ const SettingsLayout = ({ children }: { children: JSX.Element }) => { { title: t('common.SETTINGS'), href: pathName as string }, { title: t(`common.${endWord}`), href: pathName as string } ]; - + if (!user) { return ; } else { return ( @@ -39,12 +42,12 @@ const SettingsLayout = ({ children }: { children: JSX.Element }) => { + > - +
diff --git a/apps/web/app/hooks/index.ts b/apps/web/app/hooks/index.ts index 8e1f27f7b..e6690d22c 100644 --- a/apps/web/app/hooks/index.ts +++ b/apps/web/app/hooks/index.ts @@ -69,3 +69,5 @@ export * from './integrations/useGitHubIntegration'; export * from './integrations/useIntegration'; export * from './integrations/useIntegrationTenant'; export * from './integrations/useIntegrationTypes'; + +export * from './useScrollListener'; diff --git a/apps/web/app/hooks/useScrollListener.ts b/apps/web/app/hooks/useScrollListener.ts new file mode 100644 index 000000000..eb0ef9c4b --- /dev/null +++ b/apps/web/app/hooks/useScrollListener.ts @@ -0,0 +1,22 @@ +'use client'; +import React from 'react'; + +export function useScrollListener() { + const [scrolling, setScrolling] = React.useState(false); + React.useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 100) { + setScrolling(true); + } else { + setScrolling(false); + } + }; + console.log(window.scrollY); + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + return { scrolling }; +} From 7a66d6e2a3e60b18c8dba4e4851c2f372eeee8a5 Mon Sep 17 00:00:00 2001 From: "Gloire Mutaliko (Salva)" <86450367+GloireMutaliko21@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:48:27 +0200 Subject: [PATCH 04/15] Settings | Team - Create Issue Statuses (#2905) * feat: add standard task statuses for creation status * fix: custom fields comments * fix: build issues --- apps/web/app/interfaces/ITask.ts | 10 +++- apps/web/app/interfaces/ITaskStatus.ts | 39 ++++++++++----- .../lib/features/task/task-default-status.tsx | 18 ++++++- .../lib/features/task/task-status-modal.tsx | 1 + apps/web/lib/features/task/task-status.tsx | 50 +++++++++++++++++-- apps/web/lib/settings/task-statuses-form.tsx | 14 ++++-- 6 files changed, 108 insertions(+), 24 deletions(-) diff --git a/apps/web/app/interfaces/ITask.ts b/apps/web/app/interfaces/ITask.ts index 3c7091549..adf59e95a 100644 --- a/apps/web/app/interfaces/ITask.ts +++ b/apps/web/app/interfaces/ITask.ts @@ -109,7 +109,11 @@ export type ITaskStatus = | 'completed' | 'closed' | 'in review' - | 'open'; + | 'open' + | 'custom' + | 'ready-for-review' + | 'in-review' + | 'done'; export type ITaskIssue = 'Bug' | 'Task' | 'Story' | 'Epic'; @@ -130,7 +134,8 @@ export type ITaskStatusField = | 'epic' | 'project' | 'team' - | 'tags'; + | 'tags' + | 'status type'; export type ITaskStatusStack = { status: ITaskStatus; @@ -140,6 +145,7 @@ export type ITaskStatusStack = { issueType: ITaskIssue; version: IVersionProperty; epic: IEpicProperty; + 'status type': any; project: string; //TODO: these types are not strings, but rather objects for team and project. To reimplement team: string; //TODO: these types are not strings, but rather objects for team and project. To reimplement tags: any; //TODO: these types are not strings, but rather array of objects for tags. To reimplement diff --git a/apps/web/app/interfaces/ITaskStatus.ts b/apps/web/app/interfaces/ITaskStatus.ts index 3769e6163..1a5cdb91c 100644 --- a/apps/web/app/interfaces/ITaskStatus.ts +++ b/apps/web/app/interfaces/ITaskStatus.ts @@ -1,4 +1,4 @@ -export interface ITaskStatusItemList { +export interface ITaskStatusItemList extends TaskStatusWorkFlow { id: string; createdAt: string; updatedAt: string; @@ -14,19 +14,32 @@ export interface ITaskStatusItemList { isSystem?: boolean; projectId?: string; isCollapsed?: boolean; + organizationTeamId?: string | undefined | null; order?: number; + template?: TaskStatusEnum; } -export interface ITaskStatusCreate { - name?: string; - description?: string; - icon?: string; - value?: string; - color?: string; - projectId?: string; - organizationId?: string; - tenantId?: string | undefined | null; - organizationTeamId?: string | undefined | null; - isCollapsed?: boolean; - order?: number; +export interface ITaskStatusCreate + extends Partial>, + Partial> {} + +/** + * Default task statuses + */ +export enum TaskStatusEnum { + BACKLOG = 'backlog', + OPEN = 'open', + IN_PROGRESS = 'in-progress', + READY_FOR_REVIEW = 'ready-for-review', + IN_REVIEW = 'in-review', + BLOCKED = 'blocked', + DONE = 'done', + COMPLETED = 'completed', + CUSTOM = 'custom' +} + +export interface TaskStatusWorkFlow { + isTodo?: boolean; + isInProgress?: boolean; + isDone?: boolean; } diff --git a/apps/web/lib/features/task/task-default-status.tsx b/apps/web/lib/features/task/task-default-status.tsx index 42ed76228..4008dd361 100644 --- a/apps/web/lib/features/task/task-default-status.tsx +++ b/apps/web/lib/features/task/task-default-status.tsx @@ -52,7 +52,23 @@ export const taskStatus: TStatus = { icon: , bgColor: '#eaeaea' }, - open: {} + open: { + icon: , + bgColor: '#D6E4F9' + }, + 'in-review': { + icon: , + bgColor: ' #F3D8B0' + }, + 'ready-for-review': { + icon: , + bgColor: '#F5F1CB' + }, + done: { + icon: , + bgColor: '#D4EFDF' + }, + custom: {} }; export const taskPriorities: TStatus = { diff --git a/apps/web/lib/features/task/task-status-modal.tsx b/apps/web/lib/features/task/task-status-modal.tsx index 7b372391c..792dbe291 100644 --- a/apps/web/lib/features/task/task-status-modal.tsx +++ b/apps/web/lib/features/task/task-status-modal.tsx @@ -48,6 +48,7 @@ export function TaskStatusModal({ size: taskSizesValue, label: taskLabels, issueType: taskIssues, + 'status type': {}, project: {}, epic: {}, team: {}, diff --git a/apps/web/lib/features/task/task-status.tsx b/apps/web/lib/features/task/task-status.tsx index df23da5ab..b2c242770 100644 --- a/apps/web/lib/features/task/task-status.tsx +++ b/apps/web/lib/features/task/task-status.tsx @@ -8,7 +8,8 @@ import { ITaskStatusStack, ITeamTask, Nullable, - Tag + Tag, + TaskStatusEnum } from '@app/interfaces'; import { Queue, clsxm } from '@app/utils'; import { Listbox, Transition } from '@headlessui/react'; @@ -307,6 +308,49 @@ export function TaskStatusDropdown({ ); } +export function StandardTaskStatusDropDown({ + className, + defaultValue, + onValueChange, + forDetails, + multiple, + sidebarUI = false, + children, + largerWidth +}: TTaskStatusesDropdown<'status type'>) { + const taskStatusValues = useTaskStatusValue(); + + const { item, items, onChange, values } = useStatusValue<'status type'>({ + status: taskStatusValues, + value: defaultValue, + onValueChange, + multiple + }); + + const standardStatuses = useMemo( + () => items.filter((status) => Object.values(TaskStatusEnum).includes(status.value as TaskStatusEnum)), + [items] + ); + return ( + + {children} + + ); +} + /** * If no task hasn't been passed then the auth active task will used * @@ -972,7 +1016,7 @@ export function StatusDropdown({ as="div" className={clsxm( !forDetails && 'w-full max-w-[190px]', - 'cursor-pointer outline-none' + 'cursor-pointer outline-none h-full' )} style={{ width: largerWidth ? '160px' : '' @@ -992,7 +1036,7 @@ export function StatusDropdown({ forDetails={forDetails} sidebarUI={sidebarUI} className={clsxm( - 'justify-between w-full capitalize', + 'justify-between w-full capitalize h-full', sidebarUI && ['text-xs'], 'text-dark dark:text-white bg-[#F2F2F2] dark:bg-dark--theme-light', forDetails && diff --git a/apps/web/lib/settings/task-statuses-form.tsx b/apps/web/lib/settings/task-statuses-form.tsx index 415fceee0..caefbe724 100644 --- a/apps/web/lib/settings/task-statuses-form.tsx +++ b/apps/web/lib/settings/task-statuses-form.tsx @@ -14,6 +14,7 @@ import { generateIconList } from './icon-items'; import IconPopover from './icon-popover'; import { StatusesListCard } from './list-card'; import SortTasksStatusSettings from '@components/pages/kanban/sort-tasks-status-settings'; +import { StandardTaskStatusDropDown } from 'lib/features'; type StatusForm = { formOnly?: boolean; @@ -86,8 +87,9 @@ export const TaskStatusesForm = ({ formOnly = false, onCreated }: StatusForm) => color: values.color, // description: '', organizationId: user?.employee?.organizationId, - tenantId: user?.tenantId, - icon: values.icon + tenantId: user?.tenantId ?? '', + icon: values.icon, + template: values.template // projectId: '', })?.then(() => { !formOnly && setCreateNew(false); @@ -163,7 +165,7 @@ export const TaskStatusesForm = ({ formOnly = false, onCreated }: StatusForm) =>
@@ -174,7 +176,10 @@ export const TaskStatusesForm = ({ formOnly = false, onCreated }: StatusForm) => wrapperClassName="mb-0 rounded-lg" {...register('name')} /> - + setValue('template', status)} + className="h-[53px] w-[265px]" + /> : null } /> - setValue('color', color)} From bc19240cc10f4cb41716a3a244cab28f3427559f Mon Sep 17 00:00:00 2001 From: AKILIMAILI CIZUNGU Innocent <51681130+Innocent-Akim@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:50:18 +0200 Subject: [PATCH 05/15] =?UTF-8?q?[Feat]:=20DataTableTimeSheet=20component?= =?UTF-8?q?=20for=20improved=20readability=20and=20re=E2=80=A6=20(#2888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor DataTableTimeSheet component for improved readability and responsiveness * fix: cspell * Refactor: Optimize export statements for better clarity and maintainability * fix: mode sombre on date picker * feat: feat: ConfirmStatusChange modal component * fix: cspell * feat: add filter functionality with team, employee, status, and task selection * fix: error --------- Co-authored-by: Ruslan Konviser --- .cspell.json | 1 + apps/web/app/[locale]/calendar/component.tsx | 133 +++++- apps/web/app/[locale]/calendar/page.tsx | 39 +- apps/web/app/constants.ts | 6 + apps/web/app/hooks/index.ts | 1 + apps/web/components/ui/checkbox.tsx | 28 ++ apps/web/components/ui/input.tsx | 26 ++ .../calendar/calendar-component.tsx | 2 +- .../calendar/confirm-change-status.tsx | 55 +++ .../integrations/calendar/helper-calendar.ts | 1 + .../features/integrations/calendar/index.ts | 8 + .../calendar/setup-full-calendar.tsx | 8 +- .../calendar/setup-time-sheet.tsx | 16 + .../calendar/table-time-sheet.tsx | 423 ++++++++++++++++++ .../calendar/time-sheet-filter-popover.tsx | 101 +++++ .../manual-time/add-manual-time-modal.tsx | 4 +- apps/web/package.json | 1 + yarn.lock | 22 + 18 files changed, 853 insertions(+), 22 deletions(-) create mode 100644 apps/web/components/ui/checkbox.tsx create mode 100644 apps/web/components/ui/input.tsx create mode 100644 apps/web/lib/features/integrations/calendar/confirm-change-status.tsx create mode 100644 apps/web/lib/features/integrations/calendar/helper-calendar.ts create mode 100644 apps/web/lib/features/integrations/calendar/index.ts create mode 100644 apps/web/lib/features/integrations/calendar/setup-time-sheet.tsx create mode 100644 apps/web/lib/features/integrations/calendar/table-time-sheet.tsx create mode 100644 apps/web/lib/features/integrations/calendar/time-sheet-filter-popover.tsx diff --git a/.cspell.json b/.cspell.json index b662c7478..0da70ed82 100644 --- a/.cspell.json +++ b/.cspell.json @@ -32,6 +32,7 @@ "borde", "Bowser", "Btns", + "Konviser", "Bugsnag", "buildjet", "cacheable", diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx index 0b8d6501c..3a44ef27e 100644 --- a/apps/web/app/[locale]/calendar/component.tsx +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -1,31 +1,150 @@ import { clsxm } from "@app/utils"; +import { DatePicker } from "@components/ui/DatePicker"; import { QueueListIcon } from "@heroicons/react/20/solid"; +import { addDays, format } from "date-fns"; import { Button } from "lib/components"; +import { TimeSheetFilter, timesheetCalendar } from "lib/features/integrations/calendar"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@components/ui/select" +import { cn } from "lib/utils"; +import { CalendarDays } from "lucide-react"; +import React from "react"; +import { DateRange } from "react-day-picker"; import { LuCalendarDays } from "react-icons/lu"; -export function HeadCalendar({ openModal }: { openModal?: () => void }) { +export function HeadCalendar({ + openModal, + timesheet, + setCalendarTimeSheet +}: { + openModal?: () => void, + timesheet: timesheetCalendar, + setCalendarTimeSheet: React.Dispatch> +}) { + return (
-

CALENDAR

+

{timesheet === 'Calendar' ? "Calendar" : "Timesheet"}

); } + + +export function HeadTimeSheet({ timesheet }: { timesheet?: timesheetCalendar }) { + + const [date, setDate] = React.useState({ + from: new Date(2022, 0, 20), + to: addDays(new Date(2022, 0, 20), 20) + }); + console.log(timesheet); + return ( +
+
+ {timesheet === 'TimeSheet' && ( +
+
+ +
+ + + + + } + mode={'range'} + numberOfMonths={2} + initialFocus + defaultMonth={date?.from} + selected={date} + onSelect={(value) => { + value && setDate(value); + }} + /> +
+ +
+
+ )} +
+
+ ) +} + + + +export function CustomSelect() { + const [selectedValue, setSelectedValue] = React.useState(undefined); + + const handleSelectChange = (value: string) => { + setSelectedValue(value); + }; + + return ( + + ); +} diff --git a/apps/web/app/[locale]/calendar/page.tsx b/apps/web/app/[locale]/calendar/page.tsx index f149a2b18..1d1472623 100644 --- a/apps/web/app/[locale]/calendar/page.tsx +++ b/apps/web/app/[locale]/calendar/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useModal, useOrganizationTeams } from '@app/hooks'; +import { useLocalStorageState, useModal, useOrganizationTeams } from '@app/hooks'; import { fullWidthState } from '@app/stores/fullWidth'; import { clsxm } from '@app/utils'; import HeaderTabs from '@components/pages/main/header-tabs'; @@ -13,13 +13,15 @@ import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; import React, { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { HeadCalendar } from './component'; +import { HeadCalendar, HeadTimeSheet } from './component'; import { AddManualTimeModal } from 'lib/features/manual-time/add-manual-time-modal'; +import { SetupTimeSheet, timesheetCalendar } from 'lib/features/integrations/calendar'; const CalendarPage = () => { const t = useTranslations(); const fullWidth = useRecoilValue(fullWidthState); const { activeTeam, isTrackingEnabled } = useOrganizationTeams(); + const [calendarTimeSheet, setCalendarTimeSheet] = useLocalStorageState('calendar-timesheet', 'Calendar') const { isOpen: isManualTimeModalOpen, openModal: openManualTimeModal, @@ -37,13 +39,23 @@ const CalendarPage = () => { [activeTeam?.name, currentLocale, t] ); + const renderComponent = () => { + switch (calendarTimeSheet) { + case 'Calendar': + return ; + case 'TimeSheet': + return ; + default: + return null; + } + }; return ( <> { params='AddManuelTime' /> -
+
@@ -65,11 +77,22 @@ const CalendarPage = () => {
- +
+ +
+ + +
-
- + +
+ + {renderComponent()} +
diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index 83ac77069..5ecc3f76d 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -313,3 +313,9 @@ export const manualTimeReasons: ManualTimeReasons[] = [ 'UNPLANNED_WORK', 'TESTED_TIMER' ]; + +export const statusOptions = [ + { value: "Approved", label: "Approved" }, + { value: "Pending", label: "Pending" }, + { value: "Rejected", label: "Rejected" }, +]; diff --git a/apps/web/app/hooks/index.ts b/apps/web/app/hooks/index.ts index e6690d22c..93610f55d 100644 --- a/apps/web/app/hooks/index.ts +++ b/apps/web/app/hooks/index.ts @@ -70,4 +70,5 @@ export * from './integrations/useIntegration'; export * from './integrations/useIntegrationTenant'; export * from './integrations/useIntegrationTypes'; +export * from './useLocalStorageState'; export * from './useScrollListener'; diff --git a/apps/web/components/ui/checkbox.tsx b/apps/web/components/ui/checkbox.tsx new file mode 100644 index 000000000..e39edefa4 --- /dev/null +++ b/apps/web/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/apps/web/components/ui/input.tsx b/apps/web/components/ui/input.tsx new file mode 100644 index 000000000..4f3a2820f --- /dev/null +++ b/apps/web/components/ui/input.tsx @@ -0,0 +1,26 @@ +import * as React from "react" + +import { cn } from "lib/utils" + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface InputProps + extends React.InputHTMLAttributes { } + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/apps/web/lib/features/integrations/calendar/calendar-component.tsx b/apps/web/lib/features/integrations/calendar/calendar-component.tsx index ff7c603db..09ae54355 100644 --- a/apps/web/lib/features/integrations/calendar/calendar-component.tsx +++ b/apps/web/lib/features/integrations/calendar/calendar-component.tsx @@ -28,7 +28,7 @@ const CalendarComponent: React.FC = ({ return ( void; +} +export function ConfirmStatusChange({ closeModal, isOpen, newStatus, oldStatus }: ConfirmStatusChangeProps) { + const getStatusClasses = (status?: string) => { + const classes: Record = { + Rejected: "text-red-500 border-red-500", + Approved: "text-green-500 border-green-500", + Pending: "text-orange-500 border-orange-500", + }; + + return status ? classes[status] || "text-gray-500 border-gray-200" : "text-gray-500 border-gray-200"; + }; + const newStatusClass = getStatusClasses(newStatus); + const oldStatusClass = getStatusClasses(oldStatus); + + return ( + +
+ Time entry will be changed from +
+ {oldStatus} + to + {newStatus} +
+ Add a comment for this change that the employee will see + +
+ +