Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix]: Update Timesheet Functionality and Load All Projects #3413

Merged
merged 2 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ?? [],
};
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved

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" }:
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
{ 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()
});
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
}, [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
Loading