Skip to content

Commit

Permalink
[Fix]: Update Timesheet Functionality and Load All Projects (#3413)
Browse files Browse the repository at this point in the history
* fix: update timesheet functionality and load all projects

* fix: cspell
  • Loading branch information
Innocent-Akim authored Dec 9, 2024
1 parent 34fe05d commit 31507e2
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { useTeamTasks, useTimelogFilterOptions } from '@/app/hooks';
import { useOrganizationProjects, useTimelogFilterOptions } from '@/app/hooks';
import { ITaskIssue } from '@/app/interfaces';
import { clsxm } from '@/app/utils';
import { Modal } from '@/lib/components'
Expand All @@ -17,20 +17,22 @@ export interface IAddTaskModalProps {
}
export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
const { generateTimeOptions } = useTimelogFilterOptions();
const { organizationProjects } = useOrganizationProjects();

const timeOptions = generateTimeOptions(15);
const t = useTranslations();
const { activeTeam } = useTeamTasks();
const [notes, setNotes] = React.useState('');
const [task, setTasks] = React.useState('')
const [isBillable, setIsBillable] = React.useState<boolean>(true);
const [dateRange, setDateRange] = React.useState<{ from: Date | null }>({
from: new Date(),
});

const handleFromChange = (fromDate: Date | null) => {
setDateRange((prev) => ({ ...prev, from: fromDate }));
};
const projectItemsLists = {
Project: activeTeam?.projects ?? [],
Project: organizationProjects ?? [],
};

const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati
<div className="flex items-center w-full gap-2">
<div className={cn('p-2 rounded', statusColor(status).bg)}></div>
<div className="flex items-center gap-x-1">
<span className="text-base font-normal text-gray-400 uppercase !text-[12px]">
<span className="text-base font-normal text-gray-400 uppercase !text-[14px]">
{status === 'DENIED' ? 'REJECTED' : status}
</span>
<span className="text-gray-400 text-[14px]">({rows.length})</span>
Expand Down Expand Up @@ -141,7 +141,7 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati
<TaskNameInfoDisplay
task={task.task}
className={cn(
'shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
'rounded-sm h-auto !px-[0.3312rem] py-[0.2875rem] shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
)}
taskTitleClassName={cn(
'text-sm text-ellipsis overflow-hidden !text-[#293241] dark:!text-white '
Expand Down Expand Up @@ -235,7 +235,7 @@ const BaseCalendarDataView = ({ data, daysLabels, t, CalendarComponent }: BaseCa
<TaskNameInfoDisplay
task={task.task}
className={cn(
'shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
'rounded-sm h-auto !px-[0.3312rem] py-[0.2875rem] shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
)}
taskTitleClassName={cn(
'text-sm !text-ellipsis !overflow-hidden !truncate !text-[#293241] dark:!text-white '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const LoadingSpinner = ({ className }: { className?: string }) => (
</svg>
);

const ImageWithLoader = ({ imageUrl, alt, className = "w-6 h-6 rounded-full" }:
const ImageWithLoader = ({ imageUrl, alt, className = "w-6 h-6 rounded-full font-bold" }:
{ imageUrl: string; alt: string; className?: string }) => {
const [isLoading, setIsLoading] = React.useState(true);
return (
<div className="relative w-6 h-6">
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-200 rounded-full">
<LoadingSpinner className="w-4 h-4" />
<LoadingSpinner className="w-5 h-5" />
</div>
)}
<img
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import { FormEvent, useCallback, 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 { useOrganizationProjects } from "@/app/hooks";
import { CustomSelect, TaskNameInfoDisplay } from "@/lib/features";
import { statusTable } from "./TimesheetAction";
import { TimesheetLog } from "@/app/interfaces";
import { secondsToTime } from "@/app/helpers";
import { useTimesheet } from "@/app/hooks/features/useTimesheet";
import { toast } from "@components/ui/use-toast";
import { ToastAction } from "@components/ui/toast";
import { ReloadIcon } from "@radix-ui/react-icons";

export interface IEditTaskModalProps {
isOpen: boolean;
closeModal: () => void;
dataTimesheet: TimesheetLog
}
export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskModalProps) {
const { activeTeam } = useTeamTasks();
const { organizationProjects } = useOrganizationProjects();
const t = useTranslations();
const { updateTimesheet } = useTimesheet({})
const { updateTimesheet, loadingUpdateTimesheet } = useTimesheet({})

const [dateRange, setDateRange] = useState<{ date: Date | null }>({
date: dataTimesheet.timesheet?.startedAt ? new Date(dataTimesheet.timesheet.startedAt) : new Date(),
Expand All @@ -45,13 +48,12 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
}));
};
const [timesheetData, setTimesheetData] = useState({
isBillable: dataTimesheet.isBillable,
isBillable: dataTimesheet.isBillable ?? true,
projectId: dataTimesheet.project?.id || '',
notes: dataTimesheet.description || '',
});

const memberItemsLists = {
Project: activeTeam?.projects as [],
Project: organizationProjects,
};
const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => {
setTimesheetData((prev) => ({
Expand All @@ -60,7 +62,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
}));
};
const selectedValues = {
Teams: null,
Project: dataTimesheet.project,
};
const handleChange = (field: string, selectedItem: Item | null) => {
// Handle field changes
Expand Down Expand Up @@ -94,19 +96,40 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
...timeRange.endTime.split(':').map(Number)
)
);
await updateTimesheet({
id: dataTimesheet.timesheetId,
const payload = {
id: dataTimesheet.id,
isBillable: timesheetData.isBillable,
employeeId: dataTimesheet.employeeId,
logType: dataTimesheet.logType,
source: dataTimesheet.source,
startedAt: startedAt,
stoppedAt: stoppedAt,
startedAt,
stoppedAt,
tenantId: dataTimesheet.tenantId,
organizationId: dataTimesheet.organizationId,
description: timesheetData.notes,
projectId: timesheetData.projectId,
});
organizationTeamId: dataTimesheet.organizationTeamId ?? null,
organizationContactId: dataTimesheet.organizationContactId ?? null,
}
updateTimesheet({ ...payload })
.then(() => {
toast({
title: 'Modification Confirmed',
description: "The timesheet has been successfully modified.",
variant: 'default',
className: 'bg-green-50 text-green-600 border-green-500 z-[10000px]',
action: <ToastAction altText="Undo changes">Undo</ToastAction>
});
closeModal()
}).catch((error) => {
toast({
title: 'Error during modification',
description: `An error occurred: ${error}. The timesheet could not be modified.`,
variant: 'destructive',
className: 'bg-red-50 text-red-600 border-red-500 z-[10000px]'
});
closeModal()
});
}, [dateRange, timeRange, timesheetData, dataTimesheet, updateTimesheet]);

const fields = [
Expand Down Expand Up @@ -137,8 +160,8 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
<div className="flex flex-col border-b border-b-slate-100 dark:border-b-gray-700">
<TaskNameInfoDisplay
task={dataTimesheet.task}
className={clsxm('shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent')}
taskTitleClassName={clsxm('text-sm text-ellipsis overflow-hidden ')}
className={clsxm('rounded-sm h-auto !px-[0.3312rem] py-[0.2875rem] shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent')}
taskTitleClassName={clsxm('text-sm text-ellipsis overflow-hidden')}
showSize={true}
dash
taskNumberClassName="text-sm"
Expand Down Expand Up @@ -182,7 +205,6 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
{t('manualTime.END_TIME')}
<span className="text-[#de5505e1] ml-1">*</span>
</label>

<input
aria-label="End time"
aria-describedby="end-time-error"
Expand All @@ -207,6 +229,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
<ManageOrMemberComponent
classNameTitle={'text-[#282048] dark:text-gray-500 '}
fields={fields}

itemsLists={memberItemsLists}
selectedValues={selectedValues}
onSelectedValuesChange={handleSelectedValuesChange}
Expand Down Expand Up @@ -278,16 +301,21 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
</div>
<div className="flex items-center gap-x-2 justify-end w-full">
<button
onClick={closeModal}
type="button"
className={clsxm("dark:text-primary-light 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
disabled={loadingUpdateTimesheet}
type="submit"
className={clsxm(
'bg-primary dark:bg-primary-light h-[2.3rem] w-[5.5rem] justify-center font-normal flex items-center text-white px-2 rounded-lg',
)}>
{t('common.SAVE')}
{loadingUpdateTimesheet && (
<ReloadIcon className="w-4 h-4 mr-2 animate-spin" />
)}
{loadingUpdateTimesheet ? "Processing..." : t('common.SAVE')}
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ export function FilterWithStatus({
className={clsxm(
'flex flex-nowrap h-[2.2rem] items-center bg-[#e2e8f0aa] dark:bg-gray-800 rounded-xl ',
className
)}
>
)}>
{buttonData.map(({ label, count, icon }, index) => (
<Button
key={index}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { useOrganizationTeams, useTeamTasks } from '@app/hooks';
import { useOrganizationProjects, useOrganizationTeams, useTeamTasks } from '@app/hooks';
import { Button } from '@components/ui/button';
import { statusOptions } from '@app/constants';
import { MultiSelect } from 'lib/components/custom-select';
Expand All @@ -13,6 +13,8 @@ import { cn } from '@/lib/utils';
export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover() {
const [shouldRemoveItems, setShouldRemoveItems] = React.useState(false);
const { activeTeam } = useOrganizationTeams();
const { organizationProjects } = useOrganizationProjects();

const { tasks } = useTeamTasks();
const t = useTranslations();
const { setEmployeeState, setProjectState, setStatusState, setTaskState, employee, project, statusState, task } =
Expand Down Expand Up @@ -95,9 +97,9 @@ export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover
<MultiSelect
localStorageKey="timesheet-select-filter-projects"
removeItems={shouldRemoveItems}
items={activeTeam?.projects ?? []}
items={organizationProjects ?? []}
itemToString={(project) =>
(activeTeam?.projects && project ? project.name : '') || ''
(organizationProjects && project ? project.name : '') || ''
}
itemId={(item) => item.id}
onValueChange={(selectedItems) => setProjectState(selectedItems as any)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export const TimesheetCardDetail = ({ data }: { data?: Record<TimesheetStatus, T
<TaskNameInfoDisplay
task={task.task}
className={cn(
'shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
'rounded-sm h-auto !px-[0.3312rem] py-[0.2875rem] shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
)}
taskTitleClassName={cn(
'text-sm !text-ellipsis !overflow-hidden text-sm'
Expand Down
8 changes: 6 additions & 2 deletions apps/web/app/[locale]/timesheet/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { withAuthentication } from 'lib/app/authenticator';
import { Breadcrumb, Container } from 'lib/components';
import { MainLayout } from 'lib/layout';

import { useAuthenticateUser, useLocalStorageState, useModal, useOrganizationTeams } from '@app/hooks';
import { useAuthenticateUser, useLocalStorageState, useModal, useOrganizationProjects, useOrganizationTeams } from '@app/hooks';
import { clsxm } from '@app/utils';
import { fullWidthState } from '@app/stores/fullWidth';
import { useAtomValue } from 'jotai';
Expand Down Expand Up @@ -37,6 +37,8 @@ type ViewToggleButtonProps = {
const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memberId: string } }) {
const t = useTranslations();
const { user } = useAuthenticateUser();
const { getOrganizationProjects } = useOrganizationProjects();

const { isTrackingEnabled, activeTeam } = useOrganizationTeams();
const [search, setSearch] = useState<string>('');
const [filterStatus, setFilterStatus] = useLocalStorageState<FilterStatus>('timesheet-filter-status', 'All Tasks');
Expand All @@ -55,7 +57,9 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
timesheetViewMode: timesheetNavigator
});


React.useEffect(() => {
getOrganizationProjects();
}, [getOrganizationProjects])

const lowerCaseSearch = useMemo(() => search?.toLowerCase() ?? '', [search]);
const filterDataTimesheet = useMemo(() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function useTimesheet({
const response = await queryUpdateTimesheet(timesheet);
setTimesheet(prevTimesheet =>
prevTimesheet.map(item =>
item.timesheet.id === response.data.timesheet.id
item.id === response.data.id
? response.data
: item
)
Expand Down
8 changes: 4 additions & 4 deletions apps/web/lib/components/Kanban.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export const EmptyKanbanDroppable = ({
items: ITeamTask[];
}) => {
const [enabled, setEnabled] = useState(false);

const t = useTranslations()
const { toggleColumn } = useKanban();

useEffect(() => {
Expand Down Expand Up @@ -293,7 +293,7 @@ export const EmptyKanbanDroppable = ({
className="hover:font-medium p-1.5 text-sm cursor-pointer"
onClick={() => openModal()}
>
Create Task
{t('hotkeys.CREATE_TASK')}
</div>
<div
className="hover:font-medium p-1.5 text-sm cursor-pointer"
Expand Down Expand Up @@ -377,7 +377,7 @@ const KanbanDraggableHeader = ({
}) => {
const { toggleColumn } = useKanban();
const { isOpen, closeModal, openModal } = useModal();

const t = useTranslations()
return (
<>
{title && (
Expand Down Expand Up @@ -417,7 +417,7 @@ const KanbanDraggableHeader = ({
className="hover:font-medium p-1.5 text-sm cursor-pointer"
onClick={() => createTask()}
>
Create Task
{t('common.CREATE_TASK')}
</div>
<div
className="hover:font-medium p-1.5 text-sm cursor-pointer"
Expand Down

0 comments on commit 31507e2

Please sign in to comment.