diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetLoader.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetLoader.tsx new file mode 100644 index 000000000..326e9bdfb --- /dev/null +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetLoader.tsx @@ -0,0 +1,23 @@ +import { BackdropLoader } from "@/lib/components"; +import { useEffect, useState } from "react"; + +export function TimesheetLoader({ show = false }: { show?: boolean }) { + const [dots, setDots] = useState(""); + + useEffect(() => { + if (!show) { + setDots(""); // Reset the dots when loader is hidden + return; + } + + const interval = setInterval(() => { + setDots((prev) => (prev.length < 3 ? prev + "." : "")); + }, 1000); // Update dots every second + + return () => clearInterval(interval); // Cleanup interval on unmount or when `show` changes + }, [show]); + + return ( + + ); +} diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx index aed47e2c3..4fd852b36 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx @@ -2,23 +2,28 @@ import { GroupedTimesheet } from '@/app/hooks/features/useTimesheet'; import { DataTableTimeSheet } from 'lib/features/integrations/calendar'; import { useTranslations } from 'next-intl'; -export function TimesheetView({ data }: { data?: GroupedTimesheet[] }) { +export function TimesheetView({ data, loading }: { data?: GroupedTimesheet[]; loading?: boolean }) { const t = useTranslations(); + + if (loading || !data) { + return ( +
+

{t('pages.timesheet.LOADING')}

+
+ ); + } + + if (data.length === 0) { + return ( +
+

{t('pages.timesheet.NO_ENTRIES_FOUND')}

+
+ ); + } + return (
- {data ? ( - data.length > 0 ? ( - - ) : ( -
-

{t('pages.timesheet.NO_ENTRIES_FOUND')}

-
- ) - ) : ( -
-

{t('pages.timesheet.LOADING')}

-
- )} +
); } diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/index.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/index.tsx index 5c0dea0a2..06948073c 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/index.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/index.tsx @@ -9,3 +9,4 @@ export * from './TimeSheetFilterPopover' export * from './TimesheetAction'; export * from './RejectSelectedModal'; export * from './EditTaskModal'; +export * from './TimesheetLoader' diff --git a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx index 14218b4de..863759acc 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx @@ -43,7 +43,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb from: startOfDay(new Date()), to: endOfDay(new Date()) }); - const { timesheet, statusTimesheet } = useTimesheet({ + const { timesheet, statusTimesheet, loadingTimesheet } = useTimesheet({ startDate: dateRange.from ?? '', endDate: dateRange.to ?? '' }); @@ -195,7 +195,8 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb {/* */}
{timesheetNavigator === 'ListView' ? ( - + ) : ( )} diff --git a/apps/web/app/hooks/features/useTimelogFilterOptions.ts b/apps/web/app/hooks/features/useTimelogFilterOptions.ts index b37953048..a8fdc2fd8 100644 --- a/apps/web/app/hooks/features/useTimelogFilterOptions.ts +++ b/apps/web/app/hooks/features/useTimelogFilterOptions.ts @@ -10,6 +10,7 @@ export function useTimelogFilterOptions() { const [selectTimesheet, setSelectTimesheet] = useAtom(timesheetDeleteState); const [timesheetGroupByDays, setTimesheetGroupByDays] = useAtom(timesheetGroupByDayState); const [puTimesheetStatus, setPuTimesheetStatus] = useAtom(timesheetUpdateStatus) + const [selectedItems, setSelectedItems] = React.useState<{ status: string; date: string }[]>([]); const employee = employeeState; const project = projectState; @@ -26,6 +27,17 @@ export function useTimelogFilterOptions() { const handleSelectRowTimesheet = (items: string) => { setSelectTimesheet((prev) => prev.includes(items) ? prev.filter((filter) => filter !== items) : [...prev, items]) } + + const handleSelectRowByStatusAndDate = (status: string, date: string) => { + setSelectedItems((prev) => + prev.some((item) => item.status === status && item.date === date) + ? prev.filter((item) => !(item.status === status && item.date === date)) + : [...prev, { status, date }] + ); + } + + + React.useEffect(() => { return () => setSelectTimesheet([]); }, []); @@ -40,6 +52,8 @@ export function useTimelogFilterOptions() { setTaskState, setStatusState, handleSelectRowTimesheet, + handleSelectRowByStatusAndDate, + selectedItems, selectTimesheet, setSelectTimesheet, timesheetGroupByDays, 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 6b9112294..5326b964c 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -38,7 +38,9 @@ import { MdKeyboardDoubleArrowLeft, MdKeyboardDoubleArrowRight, MdKeyboardArrowLeft, - MdKeyboardArrowRight + MdKeyboardArrowRight, + MdKeyboardArrowUp, + MdKeyboardArrowDown } from 'react-icons/md'; import { ConfirmStatusChange, StatusBadge, statusOptions, dataSourceTimeSheet, TimeSheet } from '.'; import { useModal, useTimelogFilterOptions } from '@app/hooks'; @@ -153,7 +155,7 @@ export const columns: ColumnDef[] = [ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) { const { isOpen, openModal, closeModal } = useModal(); const { deleteTaskTimesheet, loadingDeleteTimesheet, getStatusTimesheet } = useTimesheet({}); - const { handleSelectRowTimesheet, selectTimesheet, setSelectTimesheet, timesheetGroupByDays } = useTimelogFilterOptions(); + const { handleSelectRowTimesheet, selectTimesheet, setSelectTimesheet, timesheetGroupByDays, handleSelectRowByStatusAndDate } = useTimelogFilterOptions(); const [isDialogOpen, setIsDialogOpen] = React.useState(false); const handleConfirm = () => { try { @@ -195,7 +197,9 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) { rowSelection } }); - + const handleSort = (key: string, order: SortOrder) => { + console.log(`Sorting ${key} in ${order} order`); + }; const handleButtonClick = (action: StatusAction) => { switch (action) { case 'Approved': @@ -211,7 +215,6 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) { console.error(`Unsupported action: ${action}`); } }; - return (
- {Object.entries(getStatusTimesheet(plan.tasks)).map(([status, rows]) => ( - { + return rows.length > 0 && status && {status === 'DENIED' ? 'REJECTED' : status} - ({rows?.length}) + ({rows.length})
- {rows?.map((task) => ( + handleSelectRowByStatusAndDate(status, plan.date)} + data={rows} + status={status} + onSort={handleSort} + date={plan.date} + /> + {rows.map((task) => (
- {task.timesheet.status} + {task.timesheet.status === 'DENIED' ? 'REJECTED' : task.timesheet.status}
- ))} + } + )} } - )}
@@ -388,7 +399,7 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
- + ); } @@ -555,3 +566,110 @@ const getBadgeColor = (timesheetStatus: TimesheetStatus | null) => { return 'bg-gray-100'; } }; + + +type SortOrder = "ASC" | "DESC"; + +const HeaderColumn = ({ + label, + onSort, + currentSort, +}: { + label: string; + onSort: () => void; + currentSort: SortOrder | null; +}) => ( +
+ {label} + +
+); + +const HeaderRow = ({ + status, + onSort, + data, + handleSelectRowByStatusAndDate, date +}: { + status: string; + onSort: (key: string, order: SortOrder) => void, + data: TimesheetLog[], + handleSelectRowByStatusAndDate: (status: string, date: string) => void, + date?: string +}) => { + + const { bg, bgOpacity } = statusColor(status); + const [sortState, setSortState] = React.useState<{ [key: string]: SortOrder | null }>({ + Task: null, + Project: null, + Employee: null, + Status: null, + }); + + const handleSort = (key: string) => { + const newOrder = sortState[key] === "ASC" ? "DESC" : "ASC"; + setSortState({ ...sortState, [key]: newOrder }); + onSort(key, newOrder); + }; + + return ( +
+ date && handleSelectRowByStatusAndDate(status, date)} + className="w-5 h-5" + disabled={!date} + /> +
+ handleSort("Task")} + currentSort={sortState["Task"]} + /> +
+
+ handleSort("Project")} + currentSort={sortState["Project"]} + /> +
+
+ handleSort("Employee")} + currentSort={sortState["Employee"]} + /> +
+
+ handleSort("Status")} + currentSort={sortState["Status"]} + /> +
+
+ Time +
+
+ ); +};