Skip to content

Commit

Permalink
[Feat]: Improve Modal For Updating Tasks and Filter (#3358)
Browse files Browse the repository at this point in the history
* feat: improve modal for updating tasks

* fix: cspell

* fix: deepScan

* fix: deepScan

* fix: deepScan

* fix: conflit
  • Loading branch information
Innocent-Akim authored Nov 22, 2024
1 parent dc1b07b commit bfde5c7
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 110 deletions.
188 changes: 108 additions & 80 deletions apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,69 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Button, Modal, statusColor } from '@/lib/components';
import { IoMdArrowDropdown } from 'react-icons/io';
import { FaRegClock } from 'react-icons/fa';
import { DatePickerFilter } from './TimesheetFilterDate';
import { useState } from 'react';
import { useTranslations } from 'next-intl';
import { clsxm } from '@/app/utils';
import { Item, ManageOrMemberComponent, getNestedValue } from '@/lib/features/manual-time/manage-member-component';
import { useTeamTasks } from '@/app/hooks';
import { CustomSelect } from '@/lib/features';
import { statusTable } from './TimesheetAction';
import { Modal, statusColor } from "@/lib/components";
import { IoMdArrowDropdown } from "react-icons/io";
import { FaRegClock } from "react-icons/fa";
import { DatePickerFilter } from "./TimesheetFilterDate";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { clsxm } from "@/app/utils";
import { Item, ManageOrMemberComponent, getNestedValue } from "@/lib/features/manual-time/manage-member-component";
import { useTeamTasks } from "@/app/hooks";
import { CustomSelect, TaskNameInfoDisplay } from "@/lib/features";
import { statusTable } from "./TimesheetAction";
import { TimesheetLog } from "@/app/interfaces";

export interface IEditTaskModalProps {
isOpen: boolean;
closeModal: () => void;
dataTimesheet: TimesheetLog
}
export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) {
export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskModalProps) {
const { activeTeam } = useTeamTasks();
const t = useTranslations();
// const [dateRange, setDateRange] = useState<{ from: Date | null }>({
// from: new Date(),
// });
// const [endTime, setEndTime] = useState<string>('');
// const [startTime, setStartTime] = useState<string>('');
// const [isBillable, setIsBillable] = useState<boolean>(dataTimesheet.isBillable);
// const [notes, setNotes] = useState('');

const [dateRange, setDateRange] = useState<{ from: Date | null }>({
from: new Date()
from: dataTimesheet.timesheet?.startedAt ? new Date(dataTimesheet.timesheet.startedAt) : new Date(),
});
const [endTime, setEndTime] = useState<string>('');
const [startTime, setStartTime] = useState<string>('');
const [isBillable, setIsBillable] = useState<boolean>(false);
const [notes, setNotes] = useState('');
const [endTime, setEndTime] = useState<string>(
dataTimesheet.timesheet?.stoppedAt
? new Date(dataTimesheet.timesheet.stoppedAt).toLocaleTimeString('en-US', { hour12: false }).slice(0, 5)
: ''
);
const [startTime, setStartTime] = useState<string>(
dataTimesheet.timesheet?.startedAt
? new Date(dataTimesheet.timesheet.startedAt).toLocaleTimeString('en-US', { hour12: false }).slice(0, 5)
: ''
);
const [isBillable, setIsBillable] = useState<boolean>(dataTimesheet.isBillable);
const [notes, setNotes] = useState<string>('');
const memberItemsLists = {
Project: activeTeam?.projects as []
Project: activeTeam?.projects as [],
};
const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => {
// Handle value changes
};
const selectedValues = {
Teams: null
Teams: null,
};

const handleChange = (field: string, selectedItem: Item | null) => {
// Handle field changes
};

const fields = [
{
label: 'Project',
label: t('sidebar.PROJECTS'),
placeholder: 'Select a project',
isRequired: true,
valueKey: 'id',
displayKey: 'name',
element: 'Project'
}
},
];

const handleFromChange = (fromDate: Date | null) => {
Expand All @@ -60,28 +76,34 @@ export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) {
showCloseIcon
title={'Edit Task'}
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[30rem] justify-start h-[auto]"
titleClass="font-bold"
>
titleClass="font-bold flex justify-start w-full">
<div className="flex flex-col w-full">
<div className="flex flex-col border-b border-b-slate-100 dark:border-b-gray-700">
<span> #321 Spike for creating calendar views on mobile</span>
<TaskNameInfoDisplay
task={dataTimesheet.task}
className={clsxm('shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent')}
taskTitleClassName={clsxm('text-sm text-ellipsis overflow-hidden ')}
showSize={true}
dash
taskNumberClassName="text-sm"
/>
<div className="flex items-center gap-x-1 ">
<span className="text-gray-400">for</span>
<span className="text-primary dark:text-primary-light">Savannah Nguyen </span>
<span className="text-primary dark:text-primary-light">{dataTimesheet.employee?.fullName ?? ""}</span>
<IoMdArrowDropdown className="cursor-pointer" />
</div>
</div>
<div className="flex items-start flex-col justify-center gap-4">
<div>
<span>Task Time</span>
<span className="text-[#282048] dark:text-gray-500 ">Task Time</span>
<div className="flex items-center gap-x-2 ">
<FaRegClock className="text-[#30B366]" />
<span>08:10h</span>
</div>
</div>
<div className="flex items-center w-full">
<div className=" w-[48%] mr-[4%]">
<label className="block text-gray-500 mb-1">
<label className="block text-[#282048] dark:text-gray-500 mb-1">
{t('manualTime.START_TIME')}
<span className="text-[#de5505e1] ml-1">*</span>
</label>
Expand All @@ -97,7 +119,7 @@ export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) {
</div>

<div className=" w-[48%]">
<label className="block text-gray-500 mb-1">
<label className="block text-[#282048] dark:text-gray-500 mb-1">
{t('manualTime.END_TIME')}
<span className="text-[#de5505e1] ml-1">*</span>
</label>
Expand All @@ -112,13 +134,19 @@ export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) {
required
/>
</div>

</div>
<div>
<span className="block text-gray-500 mr-2">{t('manualTime.DATE')}</span>
<DatePickerFilter date={dateRange.from} setDate={handleFromChange} label="Oct 01 2024" />
<span className="block text-[#282048] dark:text-gray-500 mr-2">{t("manualTime.DATE")}</span>
<DatePickerFilter
date={dateRange.from}
setDate={handleFromChange}
label="Oct 01 2024"
/>
</div>
<div className="w-full flex flex-col">
<ManageOrMemberComponent
classNameTitle={'text-[#282048] dark:text-gray-500 '}
fields={fields}
itemsLists={memberItemsLists}
selectedValues={selectedValues}
Expand All @@ -129,79 +157,77 @@ export function EditTaskModal({ isOpen, closeModal }: IEditTaskModalProps) {
/>
</div>
<div className=" flex flex-col items-center">
<label className="text-gray-500 mr-6 capitalize">
{t('pages.timesheet.BILLABLE.BILLABLE')}
</label>
<label className="text-[#282048] dark:text-gray-500 mr-12 capitalize">{t('pages.timesheet.BILLABLE.BILLABLE').toLowerCase()}</label>
<div className="flex items-center gap-3">
<ToggleButton
isActive={isBillable}
onClick={() => setIsBillable(!isBillable)}
onClick={() => setIsBillable(true)}
label={t('pages.timesheet.BILLABLE.YES')}
/>
<ToggleButton
isActive={!isBillable}
onClick={() => setIsBillable(!isBillable)}
onClick={() => setIsBillable(false)}
label={t('pages.timesheet.BILLABLE.NO')}
/>
</div>
</div>
<div className="w-full flex flex-col">
<span>Notes</span>
<span className="text-[#282048] dark:text-gray-500 ">Notes</span>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Insert notes 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 w-full',
'border border-gray-200 dark:border-slate-600 dark:bg-dark--theme-light rounded-md h-40',
notes.trim().length === 0 && 'border-red-500'
"bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent",
"placeholder-gray-300 placeholder:font-normal resize-none p-2 grow w-full",
"border border-gray-200 dark:border-slate-600 dark:bg-dark--theme-light rounded-md h-40 bg-[#FBB6500D]",
notes.trim().length === 0 && "border-red-500"
)}
maxLength={120}
minLength={0}
aria-label="Insert notes here"
required
/>
<div className="text-sm text-gray-500 text-right">
<div className="text-sm text-[#282048] dark:text-gray-500 text-right">
{notes.length}/{120}
</div>
</div>
<div className="border-t border-t-gray-200 dark:border-t-gray-700 w-full"></div>
<div className="flex items-center justify-between gap-2 ">
<div className="flex flex-col items-start justify-center">
<div className="!flex items-center justify-between gap-2 w-full">
<div className="flex flex-col items-start justify-center ">
<CustomSelect
defaultValue={dataTimesheet.timesheet?.status ?? ""}
ariaLabel={dataTimesheet.timesheet?.status ?? ""}
className="ring-offset-sidebar-primary-foreground w-[150px]"
options={statusTable?.flatMap((status) => status.label)}
renderOption={(option) => (
<div className="flex items-center gap-x-2">
<div className={clsxm('p-2 rounded-full', statusColor(option).bg)}></div>
<span className={clsxm('ml-1', statusColor(option).text)}>{option}</span>
<div className="flex items-center gap-x-2 cursor-pointer">
<div className={clsxm("p-2 rounded-full", statusColor(option).bg)}></div>
<span className={clsxm("ml-1", statusColor(option).text,)}>{option}</span>
</div>
)}
/>
</div>
<div className="flex items-center gap-x-2">
<Button
variant="outline"
<div className="flex items-center gap-x-2 justify-end w-full">
<button
type="button"
className={clsxm(
'dark:text-primary h-[2.3rem] border-gray-300 dark:border-slate-600 font-normal dark:bg-dark--theme-light'
)}
>
className={clsxm("dark:text-primary h-[2.3rem] w-[5.5rem] border px-2 rounded-lg border-gray-300 dark:border-slate-600 font-normal dark:bg-dark--theme-light")}>
{t('common.CANCEL')}
</Button>

<Button
</button>
<button
type="submit"
className={clsxm('bg-[#3826A6] h-[2.3rem] font-normal flex items-center text-white')}
>
className={clsxm(
'bg-[#4435a1] h-[2.3rem] w-[5.5rem] justify-center font-normal flex items-center text-white px-2 rounded-lg',
)}>
{t('common.SAVE')}
</Button>
</button>
</div>
</div>
</div>
</div>

</Modal>
);
)
}

interface ToggleButtonProps {
Expand All @@ -210,22 +236,24 @@ interface ToggleButtonProps {
label: string;
}

const ToggleButton = ({ isActive, onClick, label }: ToggleButtonProps) => (
<div className="flex items-center gap-x-2">
export const ToggleButton = ({ isActive, onClick, label }: ToggleButtonProps) => (
<button
type="button"
onClick={onClick}
aria-pressed={isActive}
className={clsxm(
'flex items-center gap-x-2 p-2 rounded focus:outline-none focus:ring-2 focus:ring-primary/50',
'transition-colors duration-200 ease-in-out'
)}
>
<div
className="w-6 h-6 flex items-center bg-[#6c57f4b7] rounded-full p-1 cursor-pointer"
onClick={onClick}
style={{
background: isActive ? 'linear-gradient(to right, #9d91efb7, #8a7bedb7)' : '#1f2937'
}}
>
<div
className={clsxm(
'bg-[#3826A6] w-4 h-4 rounded-full shadow-md transform transition-transform translate-x-0',
isActive && 'bg-white'
)}
/>
</div>
<span>{label}</span>
</div>
);
className={clsxm(
'w-4 h-4 rounded-full transition-colors duration-200 ease-in-out',
isActive ? 'bg-primary' : 'bg-gray-200'
)}
/>
<span className={clsxm('', isActive && 'text-primary')}>
{label}
</span>
</button>
)
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover
items={statusOptions}
itemToString={(status) => (status ? status.value : '')}
itemId={(item) => item.value}
onValueChange={(selectedItems) => setStatusState(selectedItems)}
onValueChange={(selectedItems) => setStatusState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TimesheetStatus } from "@/app/interfaces";
import { clsxm } from "@/app/utils";
import { TranslationHooks } from "next-intl";
import { ReactNode } from "react";
Expand Down Expand Up @@ -61,8 +62,10 @@ export const getTimesheetButtons = (status: StatusType, t: TranslationHooks, dis
));
};

export const statusTable: { label: StatusType; description: string }[] = [
{ label: "Pending", description: "Awaiting approval or review" },
{ label: "Approved", description: "The item has been approved" },
{ label: "Denied", description: "The item has been rejected" },
export const statusTable: { label: TimesheetStatus; description: string }[] = [
{ label: "PENDING", description: "Awaiting approval or review" },
{ label: "IN REVIEW", description: "The item is being reviewed" },
{ label: "APPROVED", description: "The item has been approved" },
{ label: "DRAFT", description: "The item is saved as draft" },
{ label: "DENIED", description: "The item has been rejected" },
];
10 changes: 7 additions & 3 deletions apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function useTimesheet({
}: TimesheetParams) {
const { user } = useAuthenticateUser();
const [timesheet, setTimesheet] = useAtom(timesheetRapportState);
const { employee, project, selectTimesheet: logIds } = useTimelogFilterOptions();
const { employee, project, task, statusState, selectTimesheet: logIds } = useTimelogFilterOptions();
const { loading: loadingTimesheet, queryCall: queryTimesheet } = useQuery(getTaskTimesheetLogsApi);
const { loading: loadingDeleteTimesheet, queryCall: queryDeleteTimesheet } = useQuery(deleteTaskTimesheetLogsApi)

Expand All @@ -77,7 +77,9 @@ export function useTimesheet({
tenantId: user.tenantId ?? '',
timeZone: user.timeZone?.split('(')[0].trim(),
employeeIds: employee?.map((member) => member.employee.id).filter((id) => id !== undefined),
projectIds: project?.map((project) => project.id).filter((id) => id !== undefined)
projectIds: project?.map((project) => project.id).filter((id) => id !== undefined),
taskIds: task?.map((task) => task.id).filter((id) => id !== undefined),
status: statusState?.map((status) => status.value).filter((value) => value !== undefined)
}).then((response) => {
setTimesheet(response.data);
}).catch((error) => {
Expand All @@ -89,7 +91,9 @@ export function useTimesheet({
queryTimesheet,
setTimesheet,
employee,
project
project,
task,
statusState
]
);

Expand Down
Loading

0 comments on commit bfde5c7

Please sign in to comment.