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]/timesheet/[memberId]/components/TimesheetView.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx index c20e08c90..aed47e2c3 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx @@ -3,22 +3,22 @@ import { DataTableTimeSheet } from 'lib/features/integrations/calendar'; import { useTranslations } from 'next-intl'; export function TimesheetView({ data }: { data?: GroupedTimesheet[] }) { - const t = useTranslations(); - return ( -
- {data ? ( - data.length > 0 ? ( - - ) : ( -
-

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

-
- ) - ) : ( -
-

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

-
- )} -
- ) + const t = useTranslations(); + return ( +
+ {data ? ( + data.length > 0 ? ( + + ) : ( +
+

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

+
+ ) + ) : ( +
+

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

+
+ )} +
+ ); } diff --git a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx index 6fe380541..f3b7df739 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx @@ -93,28 +93,22 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb - -
+
+ +
-
-
- } - > -
- -
+

{getGreeting(t)}, {username} !

- + {t('pages.timesheet.HEADING_DESCRIPTION')}
@@ -141,40 +135,38 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb classNameIcon="bg-[#30B366] shadow-[#30b3678f]" />
-
-
-
- } - mode="ListView" - active={timesheetNavigator === 'ListView'} - onClick={() => setTimesheetNavigator('ListView')} - t={t} - /> - } - mode="CalendarView" - active={timesheetNavigator === 'CalendarView'} - onClick={() => setTimesheetNavigator('CalendarView')} - t={t} - /> -
-
- - setSearch(v.target.value)} - role="searchbox" - aria-label="Search timesheet" - type="search" - name="timesheet-search" - id="timesheet-search" - className="!h-[2.2rem] w-full bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent placeholder-gray-500 placeholder:font-medium shadow-sm outline-none" - placeholder={t('common.SEARCH')} - /> +
+
+ } + mode="ListView" + active={timesheetNavigator === 'ListView'} + onClick={() => setTimesheetNavigator('ListView')} + t={t} + /> + } + mode="CalendarView" + active={timesheetNavigator === 'CalendarView'} + onClick={() => setTimesheetNavigator('CalendarView')} + t={t} + /> +
+
+ + setSearch(v.target.value)} + role="searchbox" + aria-label="Search timesheet" + type="search" + name="timesheet-search" + id="timesheet-search" + className="!h-[2.2rem] w-full bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent placeholder-gray-500 placeholder:font-medium shadow-sm outline-none" + placeholder={t('common.SEARCH')} + /> +
-
- {/* */} -
+ -
- {timesheetNavigator === 'ListView' ? ( - - ) : ( - - )} -
+ +
+ } + > +
+ + {/* */} +
+ {timesheetNavigator === 'ListView' ? ( + + ) : ( + + )}
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 01bc391b0..52fb005b5 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -1,573 +1,545 @@ -"use client" +'use client'; -import * as React from "react" +import * as React from 'react'; import { - ColumnDef, - ColumnFiltersState, - SortingState, - VisibilityState, getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable -} from "@tanstack/react-table" -import { ArrowUpDownIcon, MoreHorizontal } from "lucide-react" -import { Button } from "@components/ui/button" + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable +} from '@tanstack/react-table'; +import { ArrowUpDownIcon, MoreHorizontal } from 'lucide-react'; +import { Button } from '@components/ui/button'; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuPortal, - DropdownMenuSeparator, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, -} from "@components/ui/dropdown-menu" + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuPortal, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger +} from '@components/ui/dropdown-menu'; import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@components/ui/select" + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue +} from '@components/ui/select'; import { - MdKeyboardDoubleArrowLeft, - MdKeyboardDoubleArrowRight, - MdKeyboardArrowLeft, - MdKeyboardArrowRight -} from "react-icons/md" -import { ConfirmStatusChange, StatusBadge, statusOptions, dataSourceTimeSheet, TimeSheet } from "." -import { useModal, useTimelogFilterOptions } from "@app/hooks" -import { Checkbox } from "@components/ui/checkbox" + MdKeyboardDoubleArrowLeft, + MdKeyboardDoubleArrowRight, + MdKeyboardArrowLeft, + MdKeyboardArrowRight +} from 'react-icons/md'; +import { ConfirmStatusChange, StatusBadge, statusOptions, dataSourceTimeSheet, TimeSheet } from '.'; +import { useModal, useTimelogFilterOptions } from '@app/hooks'; +import { Checkbox } from '@components/ui/checkbox'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@components/ui/accordion'; +import { clsxm } from '@/app/utils'; +import { AlertDialogConfirmation, statusColor } from '@/lib/components'; +import { Badge } from '@components/ui/badge'; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@components/ui/accordion" -import { clsxm } from "@/app/utils" -import { AlertDialogConfirmation, statusColor } from "@/lib/components" -import { Badge } from '@components/ui/badge' -import { EditTaskModal, RejectSelectedModal, StatusAction, StatusType, getTimesheetButtons } from "@/app/[locale]/timesheet/[memberId]/components" -import { useTranslations } from "next-intl" -import { formatDate } from "@/app/helpers" -import { GroupedTimesheet, useTimesheet } from "@/app/hooks/features/useTimesheet" -import { TaskNameInfoDisplay } from "../../task/task-displays" -import { TimesheetStatus } from "@/app/interfaces" + EditTaskModal, + RejectSelectedModal, + StatusAction, + StatusType, + getTimesheetButtons +} from '@/app/[locale]/timesheet/[memberId]/components'; +import { useTranslations } from 'next-intl'; +import { formatDate } from '@/app/helpers'; +import { GroupedTimesheet, useTimesheet } from '@/app/hooks/features/useTimesheet'; +import { TaskNameInfoDisplay } from '../../task/task-displays'; +import { TimesheetStatus } from '@/app/interfaces'; import dayjs from 'dayjs'; - export const columns: ColumnDef[] = [ - { - enableHiding: false, - id: "select", - size: 50, - header: ({ table }) => ( -
- table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - Task -
- ), - cell: ({ row }) => ( - -
- row.toggleSelected(!!value)} - aria-label="Select row" - /> - {row.original.task} -
- ), - }, - { - accessorKey: "name", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - - ), - }, - { - accessorKey: "employee", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.employee} -
- ), - }, - { - accessorKey: "status", - header: ({ column }) => ( - - - ), - cell: ({ row }) => { - return - } - }, - { - accessorKey: "time", - header: () => ( -
Time
- ), - cell: ({ row }) => ( -
- {row.original.time} -
- ), - }, - { - id: "actions", - enableHiding: false, - cell: ({ row }) => { - - return ( - - ); - }, - }, + { + enableHiding: false, + id: 'select', + size: 50, + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + Task +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label="Select row" + /> + + {row.original.task} + +
+ ) + }, + { + accessorKey: 'name', + header: ({ column }) => ( + + ), + cell: ({ row }) => + }, + { + accessorKey: 'employee', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.original.employee} +
+ ) + }, + { + accessorKey: 'status', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + } + }, + { + accessorKey: 'time', + header: () =>
Time
, + cell: ({ row }) => ( +
+ {row.original.time} +
+ ) + }, + { + id: 'actions', + enableHiding: false, + cell: ({ row }) => { + return ; + } + } ]; - - export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) { - const { - isOpen, - openModal, - closeModal - } = useModal(); - const { deleteTaskTimesheet, loadingDeleteTimesheet, getStatusTimesheet } = useTimesheet({}) - const { handleSelectRowTimesheet, selectTimesheet, setSelectTimesheet } = useTimelogFilterOptions() - const [isDialogOpen, setIsDialogOpen] = React.useState(false); - const handleConfirm = () => { - try { - deleteTaskTimesheet() - .then(() => { - setSelectTimesheet([]) - setIsDialogOpen(false); - }) - .catch((error) => { - console.error('Delete timesheet error:', error); - }); - } catch (error) { - console.error('Delete timesheet error:', error); - } - }; - const handleCancel = () => { - setIsDialogOpen(false); - }; - const t = useTranslations(); - const [sorting, setSorting] = React.useState([]) - const [columnFilters, setColumnFilters] = React.useState([]) - const [columnVisibility, setColumnVisibility] = React.useState({}) - const [rowSelection, setRowSelection] = React.useState({}) - const table = useReactTable({ - data: dataSourceTimeSheet, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: setRowSelection, - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, - }) - - - const handleButtonClick = (action: StatusAction) => { - switch (action) { - case 'Approved': - // TODO: Implement approval logic - break; - case 'Denied': - openModal() - break; - case 'Deleted': - setIsDialogOpen(true) - break; - default: - console.error(`Unsupported action: ${action}`); - } - }; - - return ( -
- - { - // Pending implementation - }} - maxReasonLength={120} - minReasonLength={0} - closeModal={closeModal} - isOpen={isOpen} - /> -
- {data?.map((plan, index) => ( -
-
- {formatDate(plan.date)} - 64:30h -
- - - {Object.entries(getStatusTimesheet(plan.tasks)).map(([status, rows]) => ( - - -
-
-
-
- - {status === 'DENIED' ? "REJECTED" : status} - - ({rows?.length}) -
- - Total - 24:30h - -
-
- {getTimesheetButtons(status as StatusType, t, true, handleButtonClick)} -
-
-
- - {rows?.map((task) => ( -
- handleSelectRowTimesheet(task.id)} - checked={selectTimesheet.includes(task.id)} - /> -
- -
- {task.project && task.project.name} -
- - {task.employee.fullName} -
-
- - {task.timesheet.status} - - -
- - {dayjs(task.timesheet.createdAt).format("HH:mm:ss")} - - -
- ))} -
-
- ))} -
-
- ))} -
-
-
- {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. -
-
- - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} - - - - - -
-
-
- - - ) + const { isOpen, openModal, closeModal } = useModal(); + const { deleteTaskTimesheet, loadingDeleteTimesheet, getStatusTimesheet } = useTimesheet({}); + const { handleSelectRowTimesheet, selectTimesheet, setSelectTimesheet } = useTimelogFilterOptions(); + const [isDialogOpen, setIsDialogOpen] = React.useState(false); + const handleConfirm = () => { + try { + deleteTaskTimesheet() + .then(() => { + setSelectTimesheet([]); + setIsDialogOpen(false); + }) + .catch((error) => { + console.error('Delete timesheet error:', error); + }); + } catch (error) { + console.error('Delete timesheet error:', error); + } + }; + const handleCancel = () => { + setIsDialogOpen(false); + }; + const t = useTranslations(); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState([]); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const table = useReactTable({ + data: dataSourceTimeSheet, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection + } + }); + + const handleButtonClick = (action: StatusAction) => { + switch (action) { + case 'Approved': + // TODO: Implement approval logic + break; + case 'Denied': + openModal(); + break; + case 'Deleted': + setIsDialogOpen(true); + break; + default: + console.error(`Unsupported action: ${action}`); + } + }; + + return ( +
+ + { + // Pending implementation + }} + maxReasonLength={120} + minReasonLength={0} + closeModal={closeModal} + isOpen={isOpen} + /> +
+ {data?.map((plan, index) => ( +
+
+ {formatDate(plan.date)} + 64:30h +
+ + + {Object.entries(getStatusTimesheet(plan.tasks)).map(([status, rows]) => ( + + +
+
+
+
+ + {status === 'DENIED' ? 'REJECTED' : status} + + ({rows?.length}) +
+ + Total + 24:30h + +
+
+ {getTimesheetButtons(status as StatusType, t, true, handleButtonClick)} +
+
+
+ + {rows?.map((task) => ( +
+ handleSelectRowTimesheet(task.id)} + checked={selectTimesheet.includes(task.id)} + /> +
+ +
+ {task.project && task.project.name} +
+ + {task.employee.fullName} +
+
+ + {task.timesheet.status} + +
+ + {dayjs(task.timesheet.createdAt).format('HH:mm:ss')} + + +
+ ))} +
+
+ ))} +
+
+ ))} +
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length}{' '} + row(s) selected. +
+
+ + Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + + + + + +
+
+
+ ); } - - - export function SelectFilter({ selectedStatus }: { selectedStatus?: string }) { - - const { isOpen, closeModal, openModal } = useModal(); - const [selected] = React.useState(selectedStatus); - const [newStatus, setNewStatus] = React.useState(''); - - const getColorClass = () => { - switch (selected) { - case "Rejected": - return "text-red-500 border-gray-200"; - case "Approved": - return "text-green-500 border-gray-200"; - case "Pending": - return "text-orange-500 border-gray-200"; - default: - return "text-gray-500 border-gray-200"; - } - }; - - - const onValueChanges = (value: string) => { - setNewStatus(value); - openModal() - } - - return ( - <> - - - - - - ); + const { isOpen, closeModal, openModal } = useModal(); + const [selected] = React.useState(selectedStatus); + const [newStatus, setNewStatus] = React.useState(''); + + const getColorClass = () => { + switch (selected) { + case 'Rejected': + return 'text-red-500 border-gray-200'; + case 'Approved': + return 'text-green-500 border-gray-200'; + case 'Pending': + return 'text-orange-500 border-gray-200'; + default: + return 'text-gray-500 border-gray-200'; + } + }; + + const onValueChanges = (value: string) => { + setNewStatus(value); + openModal(); + }; + + return ( + <> + + + + + ); } const TaskActionMenu = ({ idTasks }: { idTasks: string | number }) => { - const { - isOpen: isEditTask, - openModal: isOpenModalEditTask, - closeModal: isCloseModalEditTask - } = useModal(); - return ( - <> - { - - } - - - - - - - Edit - - - - Delete - - - - - - ); + const { isOpen: isEditTask, openModal: isOpenModalEditTask, closeModal: isCloseModalEditTask } = useModal(); + return ( + <> + {} + + + + + + + Edit + + + + + Delete + + + + + ); }; const TaskDetails = ({ description, name }: { description: string; name: string }) => { - return ( -
-
- ever -
- - {name} - -
{description}
-
- ); + return ( +
+
+ ever +
+ + {name} + +
{description}
+
+ ); }; export const StatusTask = () => { - const t = useTranslations(); - return ( - <> - - - Change status - - - - {statusOptions?.map((status, index) => ( - -
-
- {status.label} -
-
- ))} -
-
-
- - - Billable - - - - -
- {t('pages.timesheet.BILLABLE.YES')} -
-
- -
- {t('pages.timesheet.BILLABLE.NO')} -
-
-
-
-
- - ) -} - + const t = useTranslations(); + return ( + <> + + + Change status + + + + {statusOptions?.map((status, index) => ( + +
+
+ {status.label} +
+
+ ))} +
+
+
+ + + Billable + + + + +
+ {t('pages.timesheet.BILLABLE.YES')} +
+
+ +
+ {t('pages.timesheet.BILLABLE.NO')} +
+
+
+
+
+ + ); +}; const getBadgeColor = (timesheetStatus: TimesheetStatus | null) => { - switch (timesheetStatus) { - case 'DRAFT': - return 'bg-gray-300'; - case 'PENDING': - return 'bg-yellow-400'; - case 'IN REVIEW': - return 'bg-blue-500'; - case 'DENIED': - return 'bg-red-500'; - case 'APPROVED': - return 'bg-green-500'; - default: - return 'bg-gray-100'; - } + switch (timesheetStatus) { + case 'DRAFT': + return 'bg-gray-300'; + case 'PENDING': + return 'bg-yellow-400'; + case 'IN REVIEW': + return 'bg-blue-500'; + case 'DENIED': + return 'bg-red-500'; + case 'APPROVED': + return 'bg-green-500'; + default: + return 'bg-gray-100'; + } };