Skip to content

Commit

Permalink
feat: add component for timesheet creation and optimize task button
Browse files Browse the repository at this point in the history
  • Loading branch information
Innocent-Akim committed Nov 21, 2024
1 parent 681714b commit 97521cb
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 27 deletions.
156 changes: 156 additions & 0 deletions apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useTeamTasks } from '@/app/hooks';
import { ITaskIssue } from '@/app/interfaces';
import { clsxm } from '@/app/utils';
import { Modal } from '@/lib/components'
import { CustomSelect, TaskStatus, taskIssues } from '@/lib/features';
import { Item, ManageOrMemberComponent, getNestedValue } from '@/lib/features/manual-time/manage-member-component';
import { useTranslations } from 'next-intl';
import React from 'react'
import { ToggleButton } from './EditTaskModal';
export interface IAddTaskModalProps {
isOpen: boolean;
closeModal: () => void;
}
export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
const t = useTranslations();
const { activeTeam } = useTeamTasks();
const [notes, setNotes] = React.useState('');
const [task, setTasks] = React.useState('')
const [isBillable, setIsBillable] = React.useState<boolean>(true);

const projectItemsLists = {
Project: activeTeam?.projects as [],
};
const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => {
// Handle value changes
};
const selectedValues = {
Preject: null,

Check warning on line 28 in apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx

View workflow job for this annotation

GitHub Actions / Cspell

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

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

return (
<Modal
isOpen={isOpen}
closeModal={closeModal}
title={'ADD TASK'}
showCloseIcon
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 flex justify-start w-full">
<div className="flex flex-col w-full gap-4 justify-start">
<div className=" w-full mr-[4%]">
<label className="block text-[#282048] font-medium mb-1">
Task
<span className="text-[#de5505e1] ml-1">*</span>
</label>
<input
aria-label="Task"
aria-describedby="start-time-error"
type="Task"
value={task}
onChange={(e) => setTasks(e.target?.value)}
className="w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md"
required
/>
</div>
<div className=" w-full mr-[4%] flex items-center">
<label className="block text-[#282048] mb-1 px-2">
{t('common.ISSUE_TYPE')}
<span className="text-[#de5505e1] ml-1">*</span>:
</label>
<CustomSelect
className='w-auto'
options={Object.keys(taskIssues).flatMap((items) => items)}
renderOption={(option) => (
<div className="flex items-center gap-x-2">
<TaskStatus
{...taskIssues[option as ITaskIssue]}
showIssueLabels={false}
issueType="issue"
className={clsxm('rounded-md px-2 text-white bg-primary')}
/>
{option}
</div>
)}
/>
</div>
<div className=" flex flex-col items-center w-full">
<label className="text-[#282048] font-medium mr-12 capitalize">
{t('pages.timesheet.BILLABLE.BILLABLE').toLowerCase()
}</label>
<div className="flex items-center gap-3">
<ToggleButton
isActive={isBillable}
onClick={() => setIsBillable(true)}
label={t('pages.timesheet.BILLABLE.YES')}
/>
<ToggleButton
isActive={!isBillable}
onClick={() => setIsBillable(false)}
label={t('pages.timesheet.BILLABLE.NO')}
/>
</div>
</div>
<div className="w-full flex flex-col">
<ManageOrMemberComponent
fields={fields}
itemsLists={projectItemsLists}
selectedValues={selectedValues}
onSelectedValuesChange={handleSelectedValuesChange}
handleChange={handleChange}
itemToString={(item, displayKey) => getNestedValue(item, displayKey) || ''}
itemToValue={(item, valueKey) => getNestedValue(item, valueKey) || ''}
/>
</div>
<div className="w-full flex flex-col">
<span className="text-[#282048] font-medium">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 bg-[#FBB6500D]",
notes.trim().length === 0 && "border-red-500"
)}
maxLength={120}
minLength={0}
aria-label="Insert notes here"
required
/>
<div className="text-sm text-[#282048] font-medium text-right">
{notes.length}/{120}
</div>
</div>
<div className="flex items-center gap-x-2 justify-end w-full">
<button
type="button"
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
type="submit"
className={clsxm(
'bg-[#3826A6] h-[2.3rem] w-[5.5rem] justify-center font-normal flex items-center text-white px-2 rounded-lg',
)}>
{t('common.SAVE')}
</button>
</div>
</div>
</Modal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ interface ToggleButtonProps {
label: string;
}

const ToggleButton = ({ isActive, onClick, label }: ToggleButtonProps) => (
export const ToggleButton = ({ isActive, onClick, label }: ToggleButtonProps) => (
<div className="flex items-center gap-x-2">
<div
className="w-6 h-6 flex items-center bg-[#6c57f4b7] rounded-full p-1 cursor-pointer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,44 @@
import React, { HTMLAttributes } from 'react';
import { Button } from 'lib/components';
import { clsxm } from '@app/utils';
import { TimesheetLog, TimesheetStatus } from '@/app/interfaces';

export type FilterStatus = 'All Tasks' | 'Pending' | 'Approved' | 'Rejected';
export type FilterStatus = 'All Tasks' | 'Pending' | 'Approved' | 'In review' | 'Draft' | 'Rejected';
export function FilterWithStatus({
activeStatus,
onToggle,
className
className,
data
}: Readonly<{
activeStatus: FilterStatus;
data?: Record<TimesheetStatus, TimesheetLog[]>

onToggle: (status: FilterStatus) => void;
className?: HTMLAttributes<HTMLDivElement>;
}>) {
const buttonData: { label: FilterStatus; count: number; icon: React.ReactNode }[] = [
{ label: 'All Tasks', count: 46, icon: <i className="icon-all" /> },
{ label: 'Pending', count: 12, icon: <i className="icon-pending" /> },
{ label: 'Approved', count: 28, icon: <i className="icon-approved" /> },
{ label: 'Rejected', count: 6, icon: <i className="icon-rejected" /> }
];

const statusIcons: Record<FilterStatus, string> = {
'All Tasks': 'icon-all',
Pending: 'icon-pending',
Approved: 'icon-approved',
'In review': 'icon-rejected',
Draft: 'icon-approved',
Rejected: 'icon-rejected',
};

const buttonData = Object.entries({
'All Tasks': Object.values(data!).reduce((total, tasks) => total + tasks.length, 0),
Pending: data!.PENDING.length,
Approved: data!.APPROVED.length,
'In review': data!['IN REVIEW'].length,
Draft: data!.DRAFT.length,
Rejected: data!.DENIED.length,
}).map(([label, count]) => ({
label: label as FilterStatus,
count,
icon: <i className={statusIcons[label as FilterStatus]} />,
}));


return (
<div
Expand All @@ -34,7 +55,7 @@ export function FilterWithStatus({
'group flex items-center justify-start h-[2.2rem] rounded-xl w-full',
'dark:bg-gray-800 dark:border-primary-light bg-transparent text-[#71717A] w-[80px]',
activeStatus === label &&
'text-primary bg-white shadow-2xl dark:text-primary-light font-bold border'
'text-primary bg-white shadow-2xl dark:text-primary-light font-bold border'
)}
onClick={() => onToggle(label)}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FilterStatus, FilterWithStatus } from './FilterWithStatus';
import { FrequencySelect, TimeSheetFilterPopover, TimesheetFilterDate, TimesheetFilterDateProps } from '.';
import { Button } from 'lib/components';
import { AddManualTimeModal } from '@/lib/features/manual-time/add-manual-time-modal';
import { TranslationHooks } from 'next-intl';
import { AddTaskModal } from './AddTaskModal';
import { TimesheetLog, TimesheetStatus } from '@/app/interfaces';

interface ITimesheetFilter {
isOpen: boolean,
Expand All @@ -11,22 +12,23 @@ interface ITimesheetFilter {
t: TranslationHooks,
initDate?: Pick<TimesheetFilterDateProps, 'initialRange' | 'onChange' | 'maxDate' | 'minDate'>,
onChangeStatus?: (status: FilterStatus) => void;
filterStatus?: FilterStatus
filterStatus?: FilterStatus,
data?: Record<TimesheetStatus, TimesheetLog[]>

}

export function TimesheetFilter({ closeModal, isOpen, openModal, t, initDate, filterStatus, onChangeStatus }: ITimesheetFilter,) {
export function TimesheetFilter({ closeModal, isOpen, openModal, t, initDate, filterStatus, onChangeStatus, data }: ITimesheetFilter,) {
return (
<>
{
isOpen && <AddManualTimeModal
isOpen && <AddTaskModal
closeModal={closeModal}
isOpen={isOpen}
params="AddManuelTime"
timeSheetStatus="ManagerTimesheet"
/>}
<div className="flex w-full justify-between items-center">
<div>
<FilterWithStatus
data={data}
activeStatus={filterStatus || "All Tasks"}
onToggle={(label) => onChangeStatus?.(label)}
/>
Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/[locale]/timesheet/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
to: endOfDay(new Date())
});

const { timesheet } = useTimesheet({
const { timesheet, statusTimesheet } = useTimesheet({
startDate: dateRange.from ?? '',
endDate: dateRange.to ?? ''
});
Expand Down Expand Up @@ -168,6 +168,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
</div>

<TimesheetFilter
data={statusTimesheet}
onChangeStatus={setFilterStatus}
filterStatus={filterStatus}
initDate={{
Expand Down Expand Up @@ -210,7 +211,7 @@ const ViewToggleButton: React.FC<ViewToggleButtonProps> = ({ mode, active, icon,
className={clsxm(
'text-[#7E7991] font-medium w-[191px] h-[40px] flex items-center gap-x-4 text-[14px] px-2 rounded',
active &&
'border-b-primary text-primary border-b-2 dark:text-primary-light dark:border-b-primary-light bg-[#F1F5F9] dark:bg-gray-800 font-bold'
'border-b-primary text-primary border-b-2 dark:text-primary-light dark:border-b-primary-light bg-[#F1F5F9] dark:bg-gray-800 font-bold'
)}
>
{icon}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export function useTimesheet({
getTaskTimesheet,
loadingDeleteTimesheet,
deleteTaskTimesheet,
getStatusTimesheet
getStatusTimesheet,
statusTimesheet: getStatusTimesheet(timesheet.flatMap((data) => data))
};
}
20 changes: 12 additions & 8 deletions apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import {
RejectSelectedModal,
StatusAction,
StatusType,
getTimesheetButtons
getTimesheetButtons,
statusTable
} from '@/app/[locale]/timesheet/[memberId]/components';
import { useTranslations } from 'next-intl';
import { formatDate } from '@/app/helpers';
Expand Down Expand Up @@ -395,6 +396,7 @@ export function SelectFilter({ selectedStatus }: { selectedStatus?: string }) {
const { isOpen, closeModal, openModal } = useModal();
const [selected] = React.useState(selectedStatus);
const [newStatus, setNewStatus] = React.useState('');
const t = useTranslations();

const getColorClass = () => {
switch (selected) {
Expand Down Expand Up @@ -426,7 +428,7 @@ export function SelectFilter({ selectedStatus }: { selectedStatus?: string }) {
</SelectTrigger>
<SelectContent>
<SelectGroup className="rounded">
<SelectLabel>Status</SelectLabel>
<SelectLabel>{t('common.STATUS')}</SelectLabel>
{statusOptions.map((option, index) => (
<div key={option.value}>
<SelectItem value={option.value}>{option.label}</SelectItem>
Expand All @@ -444,6 +446,8 @@ export function SelectFilter({ selectedStatus }: { selectedStatus?: string }) {

const TaskActionMenu = ({ idTasks }: { idTasks: string | number }) => {
const { isOpen: isEditTask, openModal: isOpenModalEditTask, closeModal: isCloseModalEditTask } = useModal();
const t = useTranslations();

return (
<>
{<EditTaskModal closeModal={isCloseModalEditTask} isOpen={isEditTask} />}
Expand All @@ -456,12 +460,12 @@ const TaskActionMenu = ({ idTasks }: { idTasks: string | number }) => {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem className="cursor-pointer" onClick={isOpenModalEditTask}>
Edit
{t('common.EDIT')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<StatusTask />
<DropdownMenuItem className="text-red-600 hover:!text-red-600 cursor-pointer">
Delete
{t('common.DELETE')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down Expand Up @@ -493,10 +497,10 @@ export const StatusTask = () => {
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
{statusOptions?.map((status, index) => (
<DropdownMenuItem key={index} textValue={status.value} className="cursor-pointer">
{statusTable?.map((status, index) => (
<DropdownMenuItem key={index} textValue={status.label} className="cursor-pointer">
<div className="flex items-center gap-3">
<div className={clsxm('h-2 w-2 rounded-full', statusColor(status.value).bg)}></div>
<div className={clsxm('h-2 w-2 rounded-full', statusColor(status.label).bg)}></div>
<span>{status.label}</span>
</div>
</DropdownMenuItem>
Expand All @@ -506,7 +510,7 @@ export const StatusTask = () => {
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span>Billable</span>
<span>{t('pages.timesheet.BILLABLE.BILLABLE')}</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
Expand Down

0 comments on commit 97521cb

Please sign in to comment.