diff --git a/.cspell.json b/.cspell.json
index beb86e6fa..be2df850e 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -355,6 +355,7 @@
"tailess",
"Tailess",
"tailwindcss",
+ "timesheet-viewMode",
"tanstack",
"taskid",
"taskstatus",
diff --git a/apps/web/app/[locale]/timesheet/components/CalendarView.tsx b/apps/web/app/[locale]/timesheet/components/CalendarView.tsx
new file mode 100644
index 000000000..b7a4fd146
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/CalendarView.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export function CalendarView() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx b/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx
new file mode 100644
index 000000000..ed38c2d23
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx
@@ -0,0 +1,35 @@
+import React, { HTMLAttributes } from 'react';
+import { Button } from 'lib/components';
+import { clsxm } from '@app/utils';
+
+export type FilterStatus = "All Tasks" | "Pending" | "Approved" | "Rejected";
+export function FilterWithStatus({
+ activeStatus,
+ onToggle,
+ className
+}: {
+ activeStatus: FilterStatus;
+ onToggle: (status: FilterStatus) => void;
+ className?: HTMLAttributes
+}) {
+ const buttonData: { label: FilterStatus; count: number; icon: React.ReactNode }[] = [
+ { label: 'All Tasks', count: 46, icon: },
+ { label: 'Pending', count: 12, icon: },
+ { label: 'Approved', count: 28, icon: },
+ { label: 'Rejected', count: 6, icon: },
+ ];
+
+ return (
+
+ {buttonData.map(({ label, count, icon }, index) => (
+
+ ))}
+
+ );
+}
diff --git a/apps/web/app/[locale]/timesheet/components/FrequencySelect.tsx b/apps/web/app/[locale]/timesheet/components/FrequencySelect.tsx
new file mode 100644
index 000000000..656e63aa0
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/FrequencySelect.tsx
@@ -0,0 +1,99 @@
+import React from "react";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@components/ui/select"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@components/ui/dropdown-menu"
+import { Button } from "lib/components/button";
+
+export function FrequencySelect() {
+ const [selectedValue, setSelectedValue] = React.useState(undefined);
+
+ const handleSelectChange = (value: string) => {
+ setSelectedValue(value);
+ };
+
+ return (
+
+ );
+}
+
+
+
+
+export const FilterTaskActionMenu = () => {
+ // const handleCopyPaymentId = () => navigator.clipboard.writeText(idTasks);
+ return (
+
+
+
+
+
+
+ Today
+
+
+ Last 7 days
+
+
+ Last 30 days
+
+
+ This year (2024)
+ {/* ({new Date().getFullYear()}) */}
+
+ {/* */}
+
+
+
+ );
+};
+
+export const CustomDateRange = () => {
+ return (
+
+
+ Custom Date Range
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/web/app/[locale]/timesheet/components/TimesheetCard.tsx b/apps/web/app/[locale]/timesheet/components/TimesheetCard.tsx
new file mode 100644
index 000000000..687473020
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/TimesheetCard.tsx
@@ -0,0 +1,65 @@
+
+import { clsxm } from '@app/utils';
+import { ArrowRightIcon } from 'assets/svg';
+import { Button, Card } from 'lib/components';
+import React, { ReactNode } from 'react'
+
+interface ITimesheetCard {
+ title?: string;
+ date?: string
+ description?: string;
+ hours?: string;
+ count?: number;
+ color?: string;
+ icon?: ReactNode;
+ classNameIcon?: string
+ onClick?: () => void;
+}
+
+
+export function TimesheetCard({ ...props }: ITimesheetCard) {
+ const { icon, title, date, description, hours, count, onClick, classNameIcon } = props;
+ return (
+
+
+
+
{hours ?? count}
+ {title}
+ {date ?? description}
+
+
+
+
+ {icon}
+
+
+ )
+}
diff --git a/apps/web/app/[locale]/timesheet/components/TimesheetFilter.tsx b/apps/web/app/[locale]/timesheet/components/TimesheetFilter.tsx
new file mode 100644
index 000000000..179d5ccc8
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/TimesheetFilter.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import { FilterWithStatus } from './FilterWithStatus'
+import { FrequencySelect } from '.';
+import { Button } from 'lib/components';
+import { SettingFilterIcon } from 'assets/svg';
+
+export function TimesheetFilter() {
+ return (
+
+
+ {
+ console.log(label)
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/web/app/[locale]/timesheet/components/TimesheetView.tsx b/apps/web/app/[locale]/timesheet/components/TimesheetView.tsx
new file mode 100644
index 000000000..91b1d73d5
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/TimesheetView.tsx
@@ -0,0 +1,10 @@
+import { DataTableTimeSheet } from 'lib/features/integrations/calendar'
+import React from 'react'
+
+export function TimesheetView() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/web/app/[locale]/timesheet/components/index.tsx b/apps/web/app/[locale]/timesheet/components/index.tsx
new file mode 100644
index 000000000..12cf616a7
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/components/index.tsx
@@ -0,0 +1,6 @@
+export * from './TimesheetCard';
+export * from './TimesheetView';
+export * from './CalendarView';
+export * from './TimesheetFilter';
+export * from './FrequencySelect';
+export * from './FilterWithStatus';
diff --git a/apps/web/app/[locale]/timesheet/page.tsx b/apps/web/app/[locale]/timesheet/page.tsx
new file mode 100644
index 000000000..c278f7fa5
--- /dev/null
+++ b/apps/web/app/[locale]/timesheet/page.tsx
@@ -0,0 +1,162 @@
+"use client"
+import React, { useMemo } from 'react';
+import { useParams } from 'next/navigation';
+import { useTranslations } from 'next-intl';
+
+import { withAuthentication } from 'lib/app/authenticator';
+import { Breadcrumb, Container, Divider } from 'lib/components';
+import { Footer, MainLayout } from 'lib/layout';
+
+import { useLocalStorageState, useOrganizationTeams } from '@app/hooks';
+import { clsxm } from '@app/utils';
+import { fullWidthState } from '@app/stores/fullWidth';
+import { useAtomValue } from 'jotai';
+import { ArrowLeftIcon } from 'assets/svg';
+import { CalendarView, TimesheetCard, TimesheetFilter, TimesheetView } from './components';
+import { CalendarDaysIcon, Clock, User2 } from 'lucide-react';
+import { GrTask } from "react-icons/gr";
+import { GoSearch } from "react-icons/go";
+
+type TimesheetViewMode = "ListView" | "CalendarView"
+type ViewToggleButtonProps = {
+ mode: TimesheetViewMode;
+ active: boolean;
+ icon: React.ReactNode;
+ onClick: () => void;
+};
+
+function TimeSheetPage() {
+ const t = useTranslations();
+ const [timesheetNavigator, setTimesheetNavigator] = useLocalStorageState('timesheet-viewMode', 'ListView');
+
+ const fullWidth = useAtomValue(fullWidthState);
+ const { isTrackingEnabled, activeTeam } = useOrganizationTeams();
+
+ const params = useParams<{ locale: string }>();
+ const currentLocale = params ? params.locale : null;
+ const breadcrumbPath = useMemo(
+ () => [
+ { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' },
+ { title: activeTeam?.name || '', href: '/' },
+ { title: 'Timesheet', href: `/${currentLocale}/timesheet` }
+ ],
+ [activeTeam?.name, currentLocale, t]
+ );
+ return (
+ < >
+
+
+
+
+
+
+
Good morning, Ruslan !
+ This is your personal timesheet dashboard, showing you what needs your attention now.
+
+
+ }
+ classNameIcon='bg-[#FBB650] shadow-[#fbb75095]'
+ />
+ }
+ classNameIcon='bg-[#3D5A80] shadow-[#3d5a809c] '
+ />
+ }
+ classNameIcon='bg-[#30B366] shadow-[#30b3678f]'
+ />
+
+
+
+
+ }
+ mode='ListView'
+ active={timesheetNavigator === 'ListView'}
+ onClick={() => setTimesheetNavigator('ListView')}
+ />
+ }
+ mode='CalendarView'
+ active={timesheetNavigator === 'CalendarView'}
+ onClick={() => setTimesheetNavigator('CalendarView')}
+ />
+
+
+
+
+
+
+ {/* */}
+
+
+
+ {timesheetNavigator === 'ListView' ?
+
+ :
+ }
+
+
+
+
+
+
+ >
+ )
+}
+
+export default withAuthentication(TimeSheetPage, { displayName: 'TimeSheet' });
+
+
+const FooterTimeSheet = ({ fullWidth }: { fullWidth: boolean }) => {
+ return (
+
+ )
+}
+const ViewToggleButton: React.FC = ({
+ mode,
+ active,
+ icon,
+ onClick
+}) => (
+
+);
diff --git a/apps/web/app/stores/user.ts b/apps/web/app/stores/user.ts
index e09c7752e..7d5285e7e 100644
--- a/apps/web/app/stores/user.ts
+++ b/apps/web/app/stores/user.ts
@@ -3,3 +3,4 @@ import { atom } from 'jotai';
export const userState = atom(null);
export const userDetailAccordion = atom('');
+export const stayOpen = atom(false)
diff --git a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx
index d5ff98662..e6c9789a3 100644
--- a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx
+++ b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx
@@ -47,8 +47,6 @@ export function SetupFullCalendar() {
extendedProps: {
icon: ,
},
-
-
},
{
id: '13',
diff --git a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
index f18ae4c5e..7f479781d 100644
--- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
+++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
@@ -256,8 +256,7 @@ export function DataTableTimeSheet() {
+ className="h-24 text-center">
No results.
@@ -417,7 +416,7 @@ const TaskDetails = ({ description, name }: { description: string; name: string
{name}
- {description}
+ {description}
);
};
diff --git a/apps/web/lib/features/task/task-filters.tsx b/apps/web/lib/features/task/task-filters.tsx
index 11a53ea4c..4bb960e9f 100644
--- a/apps/web/lib/features/task/task-filters.tsx
+++ b/apps/web/lib/features/task/task-filters.tsx
@@ -226,9 +226,9 @@ export function useTaskFilter(profile: I_UserProfilePage) {
.every((k) => {
return k === 'label'
? intersection(
- statusFilters[k],
- task['tags'].map((item) => item.name)
- ).length === statusFilters[k].length
+ statusFilters[k],
+ task['tags'].map((item) => item.name)
+ ).length === statusFilters[k].length
: statusFilters[k].includes(task[k]);
});
});
diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx
index c25ea6de7..59000339f 100644
--- a/apps/web/lib/features/task/task-input-kanban.tsx
+++ b/apps/web/lib/features/task/task-input-kanban.tsx
@@ -169,9 +169,9 @@ export function TaskInputKanban(props: Props) {
}, [editMode, setFilter]);
/*
- If task is passed then we don't want to set the active task for the authenticated user.
- after task creation
- */
+ If task is passed then we don't want to set the active task for the authenticated user.
+ after task creation
+ */
const handleTaskCreation = useCallback(async () => {
/* Checking if the `handleTaskCreation` is available and if the `hasCreateForm` is true. */
datas &&
diff --git a/apps/web/lib/features/task/task-input.tsx b/apps/web/lib/features/task/task-input.tsx
index 7ca9cc253..213990f05 100644
--- a/apps/web/lib/features/task/task-input.tsx
+++ b/apps/web/lib/features/task/task-input.tsx
@@ -234,10 +234,10 @@ export function TaskInput(props: Props) {
}, [editMode, setFilter]);
/*
- If task is passed then we don't want to set the active task for the authenticated user.
- after task creation
- */
- const autoActiveTask = props.task !== undefined ? false : true;
+ If task is passed then we don't want to set the active task for the authenticated user.
+ after task creation
+ */
+ const autoActiveTask: boolean = props.task === undefined;
const handleTaskCreation = useCallback(() => {
/* Checking if the `handleTaskCreation` is available and if the `hasCreateForm` is true. */
datas &&
@@ -322,6 +322,14 @@ export function TaskInput(props: Props) {
inputTaskTitle,
setEditMode
]);
+ const [openPopoverId, setOpenPopoverId] = useState(null);
+ const handlePopoverToggle = useCallback((popoverId: string) => {
+ if (openPopoverId === popoverId) {
+ setOpenPopoverId(null);
+ } else {
+ setOpenPopoverId(popoverId);
+ }
+ }, [openPopoverId]);
// Handling Hotkeys
const handleCommandKeySequence = useCallback(() => {
@@ -424,7 +432,7 @@ export function TaskInput(props: Props) {
defaultIssueType
? defaultIssueType.name
: (localStorage.getItem('lastTaskIssue') as ITaskIssue) ||
- null
+ null
}
/>
)}
@@ -461,7 +469,9 @@ export function TaskInput(props: Props) {
return viewType === 'one-view' ? (
taskCard
) : (
-
+ handlePopoverToggle('popover1')}
+ className="relative z-20 w-full" ref={inputRef}>
{
return (
<>