Skip to content

Commit

Permalink
[Feat]: Create RejectSelectedModal Component (#3233)
Browse files Browse the repository at this point in the history
* feat: Create RejectSelectedModal component for rejecting selected entries with a reason

* fix:Cspell

* refact: coderabbitai
  • Loading branch information
Innocent-Akim authored Nov 6, 2024
1 parent e64da11 commit c3afbd1
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { clsxm } from "@/app/utils";
import { Modal } from "@/lib/components";
import { useState } from "react";
export interface IRejectSelectedModalProps {
isOpen: boolean;
closeModal: () => void;
onReject: (reason: string) => void;
minReasonLength?: number;
maxReasonLength?: number;
}
export function RejectSelectedModal({ isOpen, closeModal, maxReasonLength, onReject, minReasonLength }: IRejectSelectedModalProps) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [reason, setReason] = useState('');

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
try {
await onReject(reason);
closeModal();
} finally {
setIsSubmitting(false);
}
};
return (
<Modal
isOpen={isOpen}
showCloseIcon={false}
closeModal={closeModal}
title={'Reject Selected'}
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[24rem] justify-start"
titleClass="font-bold">
<form onSubmit={handleSubmit}>
<div className="flex flex-col gap-4">
<span className="text-[#71717A] text-center">
You are about to reject the selected entry, would you like to proceed?
</span>
<textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Add reason here..."
className={clsxm(
"bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent",
"placeholder-gray-300 placeholder:font-normal resize-none p-2 grow",
"border border-gray-200 dark:border-slate-600 dark:bg-dark--theme-light rounded-md h-40",
reason.length < minReasonLength! && "border-red-500"
)}
maxLength={120}
minLength={0}
aria-label="Rejection reason"
required
/>
<div className="text-sm text-gray-500 text-right">
{reason.length}/{maxReasonLength}
</div>
<div className="flex items-center gap-x-4 justify-end">
<button
onClick={closeModal}
type="button"
disabled={isSubmitting}
aria-label="Cancel rejection"
className="dark:text-primary border-[#E2E8F0] dark:border-slate-600 font-normal dark:bg-dark--theme-light h-[2.2rem] text-gray-700 border px-2 rounded-lg"
>
Cancel
</button>
<button
type="submit"
disabled={isSubmitting || reason.length < minReasonLength!}
aria-label="Confirm rejection"
className={clsxm(
'bg-red-600 h-[2.2rem] font-normal flex items-center text-white px-2 rounded-lg',
'disabled:opacity-50 disabled:cursor-not-allowed'
)} >
{isSubmitting ? 'Rejecting...' : 'Reject Entry'}
</button>
</div>

</div>
</form>
</Modal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,30 @@ export const TimesheetButton = ({ className, icon, onClick, title }: ITimesheetB
export type StatusType = "Pending" | "Approved" | "Rejected";

// eslint-disable-next-line @typescript-eslint/no-empty-function
export const getTimesheetButtons = (status: StatusType, t: TranslationHooks) => {
export const getTimesheetButtons = (status: StatusType, t: TranslationHooks, onClick: (action: StatusType) => void) => {

const buttonsConfig: Record<StatusType, { icon: JSX.Element; title: string }[]> = {
const buttonsConfig: Record<StatusType, { icon: JSX.Element; title: string; action: StatusType }[]> = {
Pending: [
{ icon: <FaClipboardCheck className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_APPROVE_SELECTED') },
{ icon: <IoClose className="!bg-[#2932417c] dark:!bg-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_REJECT_SELECTED') },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED') }
{ icon: <FaClipboardCheck className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_APPROVE_SELECTED'), action: "Approved" },
{ icon: <IoClose className="!bg-[#2932417c] dark:!bg-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_REJECT_SELECTED'), action: "Rejected" },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED'), action: "Pending" }
],
Approved: [
{ icon: <IoClose className="!bg-[#2932417c] rounded dark:!bg-gray-400" />, title: t('pages.timesheet.TIMESHEET_ACTION_REJECT_SELECTED') },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] rounded dark:!text-gray-400" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED') }
{ icon: <IoClose className="!bg-[#2932417c] dark:!bg-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_REJECT_SELECTED'), action: "Rejected" },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED'), action: "Pending" }
],
Rejected: [
{ icon: <FaClipboardCheck className="!text-[#2932417c] rounded dark:!text-gray-400" />, title: t('pages.timesheet.TIMESHEET_ACTION_APPROVE_SELECTED') },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] rounded dark:!text-gray-400" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED') }
{ icon: <FaClipboardCheck className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_APPROVE_SELECTED'), action: "Approved" },
{ icon: <RiDeleteBin6Fill className="!text-[#2932417c] dark:!text-gray-400 rounded" />, title: t('pages.timesheet.TIMESHEET_ACTION_DELETE_SELECTED'), action: "Pending" }
]
};

return (buttonsConfig[status] || buttonsConfig.Rejected).map((button, index) => (
<TimesheetButton
className="hover:underline"
key={index}
icon={button.icon}
onClick={() => {
// TODO: Implement the onClick functionality
}}
onClick={() => onClick(button.action)}
title={button.title}
/>
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function TimesheetFilterDate({
}
};

const actionButtonClass = "h-4 border-none dark:bg-dark-theme-light text-primary hover:bg-transparent hover:underline"
const actionButtonClass = "h-4 border-none dark:bg-dark--theme-light text-primary hover:bg-transparent hover:underline"

return (<>
<Popover>
Expand All @@ -93,7 +93,7 @@ export function TimesheetFilterDate({
aria-label="Select date range"
aria-expanded="false"
className={cn(
"w-44 justify-start dark:bg-dark-theme h-[2.2rem] items-center gap-x-2 text-left font-normal overflow-hidden text-clip dark:bg-dark-theme-light",
"w-44 justify-start dark:bg-dark--theme-light dark:text-gray-300 h-[2.2rem] items-center gap-x-2 text-left font-normal overflow-hidden text-clip",
!dateRange.from && "text-muted-foreground"
)}>
<CalendarIcon />
Expand All @@ -110,7 +110,7 @@ export function TimesheetFilterDate({
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0 flex dark:bg-dark-theme-light">
<PopoverContent className="w-auto p-0 flex dark:bg-dark--theme-light">
{isVisible && (
<div className="flex flex-col p-2 gap-2 translate-x-0 justify-between">
<div className="flex flex-col gap-2">
Expand All @@ -127,8 +127,24 @@ export function TimesheetFilterDate({
/>
</div>
<div className="flex w-full justify-end items-end">
<Button variant={'outline'} className={`${actionButtonClass} hover:text-gray-500`}>Cancel</Button>
<Button variant={'outline'} className={`${actionButtonClass} hover:text-primary-dark`}>Apply</Button>
<Button
variant={'outline'}
className={actionButtonClass}
onClick={() => {
setDateRange(initialRange ?? { from: new Date(), to: new Date() });
setIsVisible(false);
}}>
Cancel
</Button>
<Button
variant={'outline'}
className={actionButtonClass}
onClick={() => {
onChange?.(dateRange);
setIsVisible(false);
}} >
Apply
</Button>
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from './FrequencySelect';
export * from './FilterWithStatus';
export * from './TimesheetFilterDate';
export * from './TimeSheetFilterPopover'
export * from './TimesheetAction'
export * from './TimesheetAction';
export * from './RejectSelectedModal'
7 changes: 4 additions & 3 deletions apps/web/app/[locale]/timesheet/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
className="items-start pb-1 !overflow-hidden w-full"
childrenClassName="h-[calc(100vh-_300px)] !overflow-hidden w-full"
>
<div className="top-14 fixed flex flex-col border-b-[1px] dark:border-gray-800 z-10 mx-0 w-full bg-white dark:bg-dark-high shadow-2xl shadow-transparent dark:shadow-transparent ">
<Container fullWidth={fullWidth}>
<div className="top-14 fixed flex flex-col border-b-[1px] dark:border-gray-800 z-10 mx-0 w-full bg-white dark:bg-dark-high shadow-2xl shadow-transparent dark:shadow-transparent">
<Container fullWidth={fullWidth} className='bg-white'>
<div className="flex flex-row items-start justify-between mt-12 bg-white dark:bg-dark-high">
<div className="flex items-center justify-center h-10 gap-8">
<ArrowLeftIcon className="text-dark dark:text-[#6b7280] h-6 w-6" />
Expand Down Expand Up @@ -138,7 +138,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
openModal={openManualTimeModal}
isOpen={isManualTimeModalOpen}
/>
<div className='h-[calc(100vh-_291px)] mt-3 p-10 overflow-y-auto'>
<div className='h-[calc(100vh-_291px)] mt-3 overflow-y-auto border border-gray-200 rounded-lg dark:border-gray-800'>
{timesheetNavigator === 'ListView' ?
<TimesheetView data={sortedPlans} />
: <CalendarView />
Expand Down Expand Up @@ -172,3 +172,4 @@ const ViewToggleButton: React.FC<ViewToggleButtonProps> = ({
<span>{mode === 'ListView' ? t('pages.timesheet.VIEWS.LIST') : t('pages.timesheet.VIEWS.CALENDAR')}</span>
</button>
);
// https://demo.gauzy.co/#/pages/employees/timesheets/daily?organizationId=e4a6eeb6-3f13-4712-b880-34aa9ef1dd2f&date=2024-11-02&date_end=2024-11-02&unit_of_time=day&is_custom_date=false
43 changes: 36 additions & 7 deletions apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { clsxm } from "@/app/utils"
import { statusColor } from "@/lib/components"
import { Badge } from '@components/ui/badge'
import { IDailyPlan } from "@/app/interfaces"
import { StatusType, getTimesheetButtons } from "@/app/[locale]/timesheet/[memberId]/components"
import { RejectSelectedModal, StatusType, getTimesheetButtons } from "@/app/[locale]/timesheet/[memberId]/components"
import { useTranslations } from "next-intl"
import { formatDate } from "@/app/helpers"
import { TaskNameInfoDisplay } from "../../task/task-displays"
Expand Down Expand Up @@ -174,6 +174,12 @@ export const columns: ColumnDef<TimeSheet>[] = [


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

const t = useTranslations();
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
Expand Down Expand Up @@ -204,16 +210,39 @@ export function DataTableTimeSheet({ data }: { data?: IDailyPlan[] }) {
Rejected: table.getRowModel().rows.filter(row => row.original.status === "Rejected")
};

const handleButtonClick = (action: StatusType) => {
switch (action) {
case 'Approved':
// TODO: Implement approval logic
break;
case 'Rejected':
openModal()
break;
case 'Pending':
// TODO: Implement pending logic
break;
default:
console.error(`Unsupported action: ${action}`);
}
};


return (
<div className="w-full dark:bg-dark--theme">
{<RejectSelectedModal
onReject={() => {
// Pending implementation
}}
maxReasonLength={120}
minReasonLength={0}
closeModal={closeModal}
isOpen={isOpen} />}
<div className="rounded-md">
<Table className="order rounded-md dark:bg-dark--theme-light">
<TableBody className="w-full rounded-md h-[400px] overflow-y-auto dark:bg-dark--theme">
{data?.map((plan, index) => (
<div key={index}>
<div className="h-10 flex justify-between items-center w-full bg-[#eeeef1cc] dark:bg-dark--theme rounded-md border-1 border-gray-400 px-5 text-[#71717A] font-medium">
<div className="h-[48px] flex justify-between items-center w-full bg-[#ffffffcc] dark:bg-dark--theme rounded-md border-1 border-gray-400 px-5 text-[#71717A] font-medium">
<span>{formatDate(plan?.date)}</span>
<span>64:30h</span>
</div>
Expand All @@ -225,7 +254,7 @@ export function DataTableTimeSheet({ data }: { data?: IDailyPlan[] }) {
style={{ backgroundColor: statusColor(status).bgOpacity }}
type="button"
className={clsxm(
"flex flex-row-reverse justify-end items-center w-full h-9 rounded-sm gap-x-2 hover:no-underline px-2",
"flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2",
statusColor(status).text,
)}
>
Expand All @@ -238,22 +267,22 @@ export function DataTableTimeSheet({ data }: { data?: IDailyPlan[] }) {
</span>
<span className="text-gray-400 text-[14px]">({rows.length})</span>
</div>
<Badge variant={'outline'} className="flex items-center gap-x-2 rounded-md bg-[#E4E4E7] dark:bg-gray-800">
<Badge variant={'outline'} className="flex items-center gap-x-2 h-[25px] rounded-md bg-[#E4E4E7] dark:bg-gray-800">
<span className="text-[#5f5f61]">Total</span>
<span className="text-[#868688]">24:30h</span>
</Badge>
</div>
<div className="flex items-center gap-2 p-x-1">
{getTimesheetButtons(status as StatusType, t)}
{getTimesheetButtons(status as StatusType, t, handleButtonClick)}
</div>
</div>
</AccordionTrigger>
<AccordionContent className="flex flex-col w-full">
{plan.tasks?.map((task) => (
<div
key={task.id}
style={{ backgroundColor: statusColor(status).bgOpacity }}
className="flex items-center border-b border-b-gray-200 dark:border-b-gray-600 space-x-4 p-1"
style={{ backgroundColor: statusColor(status).bgOpacity, borderBottomColor: statusColor(status).bg }}
className={clsxm("flex items-center border-b border-b-gray-200 dark:border-b-gray-600 space-x-4 p-1 h-[60px]")}
>
<Checkbox className="h-5 w-5" />
<div className="flex-[2]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export function AddManualTimeModal(props: Readonly<IAddManualTimeModalProps>) {
isOpen={isOpen}
closeModal={closeModal}
title={'Add Time'}
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[24rem] h-[auto] justify-start shadow-xl"
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[24rem] h-[auto] justify-start"
titleClass="font-bold"
>
<form onSubmit={handleSubmit} className="text-sm w-[90%] md:w-full flex flex-col justify-between gap-4">
Expand Down

0 comments on commit c3afbd1

Please sign in to comment.