diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx new file mode 100644 index 000000000..0b8d6501c --- /dev/null +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -0,0 +1,31 @@ +import { clsxm } from "@app/utils"; +import { QueueListIcon } from "@heroicons/react/20/solid"; +import { Button } from "lib/components"; +import { LuCalendarDays } from "react-icons/lu"; + +export function HeadCalendar({ openModal }: { openModal?: () => void }) { + return ( +
+

CALENDAR

+
+ + + +
+
+ ); +} diff --git a/apps/web/app/[locale]/calendar/page.tsx b/apps/web/app/[locale]/calendar/page.tsx index e805038f3..f149a2b18 100644 --- a/apps/web/app/[locale]/calendar/page.tsx +++ b/apps/web/app/[locale]/calendar/page.tsx @@ -1,24 +1,31 @@ -"use client" -import { useOrganizationTeams } from '@app/hooks'; +"use client"; + +import { useModal, useOrganizationTeams } from '@app/hooks'; import { fullWidthState } from '@app/stores/fullWidth'; import { clsxm } from '@app/utils'; import HeaderTabs from '@components/pages/main/header-tabs'; import { PeoplesIcon } from 'assets/svg'; import { withAuthentication } from 'lib/app/authenticator'; -import { Breadcrumb, Button, Container, Divider } from 'lib/components'; -import { SetupFullCalendar } from 'lib/features' +import { Breadcrumb, Container, Divider } from 'lib/components'; +import { SetupFullCalendar } from 'lib/features'; import { Footer, MainLayout } from 'lib/layout'; import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; -import React, { useMemo } from 'react' +import React, { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { LuCalendarDays } from "react-icons/lu"; - +import { HeadCalendar } from './component'; +import { AddManualTimeModal } from 'lib/features/manual-time/add-manual-time-modal'; const CalendarPage = () => { const t = useTranslations(); const fullWidth = useRecoilValue(fullWidthState); const { activeTeam, isTrackingEnabled } = useOrganizationTeams(); + const { + isOpen: isManualTimeModalOpen, + openModal: openManualTimeModal, + closeModal: closeManualTimeModal + } = useModal(); + const params = useParams<{ locale: string }>(); const currentLocale = params ? params.locale : null; const breadcrumbPath = useMemo( @@ -29,19 +36,24 @@ const CalendarPage = () => { ], [activeTeam?.name, currentLocale, t] ); + return ( <> + -
+ + +
@@ -49,44 +61,25 @@ const CalendarPage = () => {
-
+
-
-

- CALENDAR -

-
- - - -
-
+
-
+
-
+
- ) -} + ); +}; -export default withAuthentication(CalendarPage, { displayName: 'Calender' }); +export default withAuthentication(CalendarPage, { displayName: 'Calendar' }); diff --git a/apps/web/app/hooks/useLocalStorageState.ts b/apps/web/app/hooks/useLocalStorageState.ts new file mode 100644 index 000000000..5330ac059 --- /dev/null +++ b/apps/web/app/hooks/useLocalStorageState.ts @@ -0,0 +1,30 @@ +"use client" +import { useState, useEffect } from 'react'; +/** + * Custom hook to manage state that is synchronized with `localStorage`. + * + * @template T - The type of the state value. + * @param {string} key - The key under which the value is stored in `localStorage`. + * @param {T} defaultValue - The default value to use if the key is not found in `localStorage`. + * + * @returns {[T, React.Dispatch>]} - Returns a stateful value and a function to update it. + * + * @example + * const [calendar, setCalendar] = useLocalStorageState('calendar-timesheet', 'Calendar'); + * + * - The state `calendar` will be initialized with the value from `localStorage` if it exists, or 'Calendar' if not. + * - Any updates to `calendar` will be reflected in `localStorage`. + */ + +export const useLocalStorageState = (key: string, defaultValue: T) => { + const [state, setState] = useState(() => + (typeof window !== 'undefined' && window.localStorage.getItem(key) as T) || defaultValue + ); + useEffect(() => { + if (typeof window !== 'undefined') { + window.localStorage.setItem(key, state as any); + } + }, [state, key]); + + return [state, setState] as const; +}; diff --git a/apps/web/lib/components/custom-select/index.tsx b/apps/web/lib/components/custom-select/index.tsx index 046fff4de..0e56528b7 100644 --- a/apps/web/lib/components/custom-select/index.tsx +++ b/apps/web/lib/components/custom-select/index.tsx @@ -53,8 +53,8 @@ export function SelectItems({ onClick={() => setPopoverOpen(!isPopoverOpen)} variant="outline" className={cn( - 'w-full justify-between text-left font-normal h-9 rounded-lg dark:bg-dark--theme-light', - !selectedItem && 'text-muted-foreground', + 'w-full justify-between text-left font-normal h-10 rounded-lg dark:bg-dark--theme-light', + // !selectedItem && 'text-muted-foreground', triggerClassName )} > @@ -64,7 +64,7 @@ export function SelectItems({ Select an item )} @@ -82,7 +82,7 @@ export function SelectItems({ onClick(item)} key={itemId(item)} - className="truncate hover:cursor-pointer hover:bg-slate-50 w-full text-[13px] hover:rounded-lg p-1 hover:font-bold dark:text-white dark:hover:bg-primary" + className="truncate hover:cursor-pointer hover:bg-slate-50 w-full text-[13px] hover:rounded-lg p-1 hover:font-normal dark:text-white dark:hover:bg-primary" style={{ textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden' }} > {itemToString(item)} diff --git a/apps/web/lib/components/time-picker/index.tsx b/apps/web/lib/components/time-picker/index.tsx index dba5217c5..8104aef8a 100644 --- a/apps/web/lib/components/time-picker/index.tsx +++ b/apps/web/lib/components/time-picker/index.tsx @@ -39,13 +39,13 @@ export function TimePicker({ onChange, defaultValue }: IPopoverTimePicker) { diff --git a/apps/web/lib/features/integrations/activity-calendar/index.tsx b/apps/web/lib/features/integrations/activity-calendar/index.tsx index 10f6e9289..2676e4a21 100644 --- a/apps/web/lib/features/integrations/activity-calendar/index.tsx +++ b/apps/web/lib/features/integrations/activity-calendar/index.tsx @@ -76,7 +76,20 @@ export function ActivityCalendar() { } ]} monthSpacing={20} - monthLegend={(_, __, d) => d.toLocaleString('en-US', { month: 'short' })} + monthLegend={(year, month) => { + return new Date(year, month).toLocaleString('en-US', { month: 'short' }); + }} + theme={{ + labels: { + text: { + fill: '#9ca3af', + fontSize: 16, + font: 'icon', + animation: 'ease', + border: '12', + } + } + }} />
diff --git a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx index cf8c1b4b9..8f220c52b 100644 --- a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx +++ b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx @@ -3,25 +3,37 @@ import '../../../styles/style.css'; import { useOrganizationTeams, useTeamTasks } from '@app/hooks'; import { clsxm } from '@app/utils'; import { DatePicker } from '@components/ui/DatePicker'; -import { PencilSquareIcon } from '@heroicons/react/20/solid'; import { format } from 'date-fns'; import { useState, useEffect, FormEvent, useCallback } from 'react'; import { Button, SelectItems, Modal } from 'lib/components'; -import { FaRegCalendarAlt } from 'react-icons/fa'; -import { HiMiniClock } from 'react-icons/hi2'; import { manualTimeReasons } from '@app/constants'; import { useTranslations } from 'next-intl'; import { IOrganizationTeamList } from '@app/interfaces'; import { useManualTime } from '@app/hooks/features/useManualTime'; import { IAddManualTimeRequest } from '@app/interfaces/timer/ITimerLogs'; +import { cn } from 'lib/utils'; +import { CalendarDays } from 'lucide-react'; +import { IoTime } from 'react-icons/io5'; +/** + * Interface for the properties of the `AddManualTimeModal` component. + * + * This interface defines the properties expected by the `AddManualTimeModal` component. + * + * @interface IAddManualTimeModalProps + * + * @property {boolean} isOpen - Indicates whether the modal is open or closed. + * @property {"AddManuelTime" | "AddTime"} params - Determines the context in which the modal is used, either "AddManuelTime" for the Add Manuel Time view or "AddTime" for the Add time. + * @property {() => void} closeModal - Callback function to be called to close the modal. + */ interface IAddManualTimeModalProps { isOpen: boolean; + params: "AddManuelTime" | "AddTime"; closeModal: () => void; } export function AddManualTimeModal(props: IAddManualTimeModalProps) { - const { closeModal, isOpen } = props; + const { closeModal, isOpen, params } = props; const t = useTranslations(); const [isBillable, setIsBillable] = useState(false); const [description, setDescription] = useState(''); @@ -93,15 +105,20 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { const endTotalMinutes = endHours * 60 + endMinutes; const diffMinutes = endTotalMinutes - startTotalMinutes; - if (diffMinutes < 0) { - return; - } + if (diffMinutes < 0) return; const hours = Math.floor(diffMinutes / 60); const minutes = diffMinutes % 60; - setTimeDifference(`${String(hours).padStart(2, '0')}h ${String(minutes).padStart(2, '0')}m`); + + const timeString = [ + hours > 0 ? `${String(hours).padStart(2, '0')}h` : '0h', + minutes > 0 ? `${String(minutes).padStart(2, '0')}m` : '' + ].filter(Boolean).join(' '); + + setTimeDifference(timeString); }, [endTime, startTime]); + useEffect(() => { calculateTimeDifference(); }, [calculateTimeDifference, endTime, startTime]); @@ -127,48 +144,38 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) {
-
+
-
- - {date ? ( -
- - {format(date, 'PPP')} -
- ) : ( - - )} -
- } - selected={date} - onSelect={(value) => { - value && setDate(value); - }} - mode={'single'} - /> -
+ + + + + } + selected={date} + onSelect={(value) => { + value && setDate(value); + }} + mode={'single'} + />
@@ -196,7 +203,7 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { type="time" value={startTime} onChange={(e) => setStartTime(e.target.value)} - className="w-full p-2 border font-bold border-slate-100 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" + className="w-full p-2 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" required />
@@ -210,19 +217,19 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { type="time" value={endTime} onChange={(e) => setEndTime(e.target.value)} - className="w-full p-2 border font-bold border-slate-100 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" + className="w-full p-2 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" required />
- -
-
- {`${params === 'AddManuelTime' ? 'Total hours' : 'Added hours'}`}: +
+
+
{timeDifference} @@ -239,65 +246,127 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { onValueChange={(team) => setTeam(team)} itemId={(team) => (team ? team.id : '')} itemToString={(team) => (team ? team.name : '')} - triggerClassName="border-slate-100 dark:border-slate-600" + triggerClassName="border-gray-300 dark:border-slate-600" />
-
- - setTaskId(task ? task.id : '')} - itemId={(task) => (task ? task.id : '')} - itemToString={(task) => (task ? task.title : '')} - triggerClassName="border-slate-100 dark:border-slate-600" - /> -
+ { + params === 'AddManuelTime' ? ( + <> -
- -