Skip to content

Commit

Permalink
[Feat]: Added Bulk Deletion Functionality For Time Entries (#3402)
Browse files Browse the repository at this point in the history
* feat: added bulk deletion functionality for time entries

* fix: coderabbitai
  • Loading branch information
Innocent-Akim authored Dec 8, 2024
1 parent 87af1f5 commit 7cf00f0
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
isOpen={isOpen}
showCloseIcon
title={'Edit Task'}
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:min-w-[32rem] justify-start h-[auto]"
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[32rem] justify-start h-[auto]"
titleClass="font-bold flex justify-start w-full">
<form onSubmit={handleUpdateSubmit} className="flex flex-col w-full">
<div className="flex flex-col border-b border-b-slate-100 dark:border-b-gray-700">
Expand Down
7 changes: 5 additions & 2 deletions apps/web/app/hooks/features/useTimelogFilterOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function useTimelogFilterOptions() {
const [timesheetGroupByDays, setTimesheetGroupByDays] = useAtom(timesheetGroupByDayState);
const [puTimesheetStatus, setPuTimesheetStatus] = useAtom(timesheetUpdateStatus)
const [selectedItems, setSelectedItems] = React.useState<{ status: string; date: string }[]>([]);
const [selectTimesheetId, setSelectTimesheetId] = React.useState<string[]>([])

const employee = employeeState;
const project = projectState;
Expand All @@ -29,7 +30,7 @@ export function useTimelogFilterOptions() {
};

const handleSelectRowTimesheet = (items: string) => {
setSelectTimesheet((prev) => prev.includes(items) ? prev.filter((filter) => filter !== items) : [...prev, items])
setSelectTimesheetId((prev) => prev.includes(items) ? prev.filter((filter) => filter !== items) : [...prev, items])
}

const handleSelectRowByStatusAndDate = (status: string, date: string) => {
Expand All @@ -43,7 +44,7 @@ export function useTimelogFilterOptions() {


React.useEffect(() => {
return () => setSelectTimesheet([]);
return () => setSelectTimesheetId([]);
}, []);

return {
Expand All @@ -56,6 +57,8 @@ export function useTimelogFilterOptions() {
setTaskState,
setStatusState,
handleSelectRowTimesheet,
selectTimesheetId,
setSelectTimesheetId,
handleSelectRowByStatusAndDate,
selectedItems,
selectTimesheet,
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function useTimesheet({
}: TimesheetParams) {
const { user } = useAuthenticateUser();
const [timesheet, setTimesheet] = useAtom(timesheetRapportState);
const { employee, project, task, statusState, selectTimesheet: logIds, timesheetGroupByDays, puTimesheetStatus } = useTimelogFilterOptions();
const { employee, project, task, statusState, timesheetGroupByDays, puTimesheetStatus } = useTimelogFilterOptions();
const { loading: loadingTimesheet, queryCall: queryTimesheet } = useQuery(getTaskTimesheetLogsApi);
const { loading: loadingDeleteTimesheet, queryCall: queryDeleteTimesheet } = useQuery(deleteTaskTimesheetLogsApi);
const { loading: loadingUpdateTimesheetStatus, queryCall: queryUpdateTimesheetStatus } = useQuery(updateStatusTimesheetFromApi)
Expand Down Expand Up @@ -239,7 +239,7 @@ export function useTimesheet({
}


const deleteTaskTimesheet = useCallback(async () => {
const deleteTaskTimesheet = useCallback(async ({ logIds }: { logIds: string[] }) => {
if (!user) {
throw new Error('User not authenticated');
}
Expand All @@ -253,14 +253,14 @@ export function useTimesheet({
logIds
});
setTimesheet(prevTimesheet =>
prevTimesheet.filter(item => !logIds.includes(item.timesheet.id))
prevTimesheet.filter(item => !logIds.includes(item.id))
);

} catch (error) {
console.error('Failed to delete timesheets:', error);
throw error;
}
}, [user, logIds, queryDeleteTimesheet, setTimesheet]);
}, [user, queryDeleteTimesheet, setTimesheet]);


const timesheetElementGroup = useMemo(() => {
Expand Down
72 changes: 72 additions & 0 deletions apps/web/lib/components/alert-dialog-confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@components/ui/alert-dialog"
import { Button, Card, Modal, Text } from 'lib/components';
import { ReloadIcon } from "@radix-ui/react-icons";
import React from "react";

Expand Down Expand Up @@ -70,3 +71,74 @@ export function AlertDialogConfirmation({
</AlertDialog>
);
}



export const AlertConfirmationModal = ({
open,
close,
title,
description,
onAction,
loading,
confirmText = "Continue",
cancelText = "Cancel",
countID = 0
}: {
open: boolean;
close: () => void;
onAction: () => any;
title: string;
description: string;
loading: boolean;
confirmText?: string;
cancelText?: string;
countID?: number
}) => {
return (
<>
<Modal
isOpen={open}
closeModal={close}
showCloseIcon={false}>
<Card className="w-full md:min-w-[480px]" shadow="custom">
<div className="flex flex-col items-center justify-between">
<div className="flex flex-col">
<Text.Heading as="h3" className="text-2xl text-center">
{title}
</Text.Heading>
<div className="flex items-center gap-x-2">
<span className="text-center">
{description}
</span>
{countID > 0 && <span className=" h-7 w-7 flex items-center justify-center text-center text-xl rounded-full font-bold bg-primary dark:bg-primary-light text-white"> {countID}</span>}
</div>
</div>
<div className="flex items-center justify-end gap-x-5 w-full mt-4">
<Button
type="button"
onClick={close}
className={'px-4 py-2 text-sm font-medium text-gray-700 dark:text-red-600 border rounded-md bg-light--theme-light dark:!bg-dark--theme-light'}
>
{cancelText}
</Button>
<Button
variant="danger"
type="submit"
className="px-4 py-2 text-sm hover:bg-red-600 hover:text-white font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light"
disabled={loading}
loading={loading}
onClick={() => {
onAction()?.then(() => {
close();
});
}}>
{confirmText}
</Button>
</div>
</div>
</Card>
</Modal>
</>
);
};
92 changes: 65 additions & 27 deletions apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ 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 { AlertConfirmationModal, statusColor } from '@/lib/components';
import { Badge } from '@components/ui/badge';
import {
EditTaskModal,
Expand All @@ -64,6 +64,8 @@ import { formatDate } from '@/app/helpers';
import { GroupedTimesheet, useTimesheet } from '@/app/hooks/features/useTimesheet';
import { DisplayTimeForTimesheet, TaskNameInfoDisplay, TotalDurationByDate, TotalTimeDisplay } from '../../task/task-displays';
import { TimesheetLog, TimesheetStatus } from '@/app/interfaces';
import { toast } from '@components/ui/use-toast';
import { ToastAction } from '@components/ui/toast';

export const columns: ColumnDef<TimeSheet>[] = [
{
Expand Down Expand Up @@ -155,19 +157,22 @@ export const columns: ColumnDef<TimeSheet>[] = [
];

export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
const { isOpen, openModal, closeModal } = useModal();

const modal = useModal();
const alertConfirmationModal = useModal();
const { isOpen, openModal, closeModal } = modal;
const { isOpen: isOpenAlert, openModal: openAlertConfirmation, closeModal: closeAlertConfirmation } = alertConfirmationModal;

const { deleteTaskTimesheet, loadingDeleteTimesheet, getStatusTimesheet, updateTimesheetStatus } = useTimesheet({});
const { handleSelectRowTimesheet, selectTimesheet, setSelectTimesheet, timesheetGroupByDays, handleSelectRowByStatusAndDate } = useTimelogFilterOptions();
const { timesheetGroupByDays, handleSelectRowByStatusAndDate, handleSelectRowTimesheet, selectTimesheetId, setSelectTimesheetId } = useTimelogFilterOptions();



const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const handleConfirm = () => {
try {
deleteTaskTimesheet()
deleteTaskTimesheet({ logIds: selectTimesheetId })
.then(() => {
setSelectTimesheet([]);
setIsDialogOpen(false);
setSelectTimesheetId([]);
closeAlertConfirmation()
})
.catch((error) => {
console.error('Delete timesheet error:', error);
Expand All @@ -176,9 +181,7 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
console.error('Delete timesheet error:', error);
}
};
const handleCancel = () => {
setIsDialogOpen(false);
};

const t = useTranslations();
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
Expand Down Expand Up @@ -208,35 +211,35 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
const handleButtonClick = async (action: StatusAction) => {
switch (action) {
case 'Approved':
if (selectTimesheet.length > 0) {
if (selectTimesheetId.length > 0) {
await updateTimesheetStatus({
status: 'APPROVED',
ids: selectTimesheet
ids: selectTimesheetId
})
}
break;
case 'Denied':
openModal();
break;
case 'Deleted':
setIsDialogOpen(true);
openAlertConfirmation();
break;
default:
console.error(`Unsupported action: ${action}`);
}
};


return (
<div className="w-full dark:bg-dark--theme">
<AlertDialogConfirmation
title={t('common.DELETE_CONFIRMATION')}
<AlertConfirmationModal
description={t('common.IRREVERSIBLE_ACTION_WARNING')}
confirmText={t('common.DELETE')}
cancelText={t('common.CANCEL')}
isOpen={isDialogOpen}
onOpenChange={setIsDialogOpen}
onConfirm={handleConfirm}
onCancel={handleCancel}
close={closeAlertConfirmation}
loading={loadingDeleteTimesheet}
onAction={handleConfirm}
open={isOpenAlert}
title={t('common.DELETE_CONFIRMATION')}
countID={selectTimesheetId.length}
/>
<RejectSelectedModal
onReject={() => {
Expand Down Expand Up @@ -300,7 +303,7 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
</Badge>
</div>
<div className={clsxm('flex items-center gap-2 p-x-1 capitalize')}>
{getTimesheetButtons(status as StatusType, t, true, handleButtonClick)}
{getTimesheetButtons(status as StatusType, t, false, handleButtonClick)}
</div>
</div>
</AccordionTrigger>
Expand All @@ -327,7 +330,7 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
<Checkbox
className="w-5 h-5"
onCheckedChange={() => handleSelectRowTimesheet(task.id)}
checked={selectTimesheet.includes(task.id)}
checked={selectTimesheetId.includes(task.id)}
/>
<div className="flex-[2]">
<TaskNameInfoDisplay
Expand Down Expand Up @@ -468,15 +471,46 @@ export function SelectFilter({ selectedStatus }: { selectedStatus?: string }) {

const TaskActionMenu = ({ dataTimesheet }: { dataTimesheet: TimesheetLog }) => {
const { isOpen: isEditTask, openModal: isOpenModalEditTask, closeModal: isCloseModalEditTask } = useModal();
const { isOpen: isOpenAlert, openModal: openAlertConfirmation, closeModal: closeAlertConfirmation } = useModal();
const { deleteTaskTimesheet, loadingDeleteTimesheet } = useTimesheet({});

const t = useTranslations();
const handleDeleteTask = () => {
deleteTaskTimesheet({ logIds: [dataTimesheet.id] })
.then(() => {
toast({
title: 'Deletion Confirmed',
description: "The timesheet has been successfully deleted.",
variant: 'default',
className: 'bg-red-50 text-red-600 border-red-500',
action: <ToastAction altText="Restore timesheet">Undo</ToastAction>
});
})
.catch((error) => {
toast({
title: 'Error during deletion',
description: `An error occurred: ${error}.The timesheet could not be deleted.`,
variant: 'destructive',
className: 'bg-red-50 text-red-600 border-red-500'
});
});
};

return (
<>
{<EditTaskModal
<AlertConfirmationModal
description={t('common.IRREVERSIBLE_ACTION_WARNING')}
close={closeAlertConfirmation}
loading={loadingDeleteTimesheet}
onAction={handleDeleteTask}
open={isOpenAlert}
title={t('common.DELETE_CONFIRMATION')}
/>
<EditTaskModal
closeModal={isCloseModalEditTask}
isOpen={isEditTask}
dataTimesheet={dataTimesheet}
/>}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-8 h-8 p-0 text-sm sm:text-base">
Expand All @@ -490,7 +524,10 @@ const TaskActionMenu = ({ dataTimesheet }: { dataTimesheet: TimesheetLog }) => {
</DropdownMenuItem>
<DropdownMenuSeparator />
<StatusTask timesheet={dataTimesheet} />
<DropdownMenuItem className="text-red-600 hover:!text-red-600 cursor-pointer">
<DropdownMenuItem
onClick={openAlertConfirmation}
className="text-red-600 hover:!text-red-600 cursor-pointer"
>
{t('common.DELETE')}
</DropdownMenuItem>
</DropdownMenuContent>
Expand All @@ -499,6 +536,7 @@ const TaskActionMenu = ({ dataTimesheet }: { dataTimesheet: TimesheetLog }) => {
);
};


const TaskDetails = ({ description, name }: { description: string; name: string }) => {
return (
<div className="flex items-center w-40 gap-x-2 ">
Expand Down

0 comments on commit 7cf00f0

Please sign in to comment.