diff --git a/apps/web/app/api/task-versions/[id]/route.ts b/apps/web/app/api/task-versions/[id]/route.ts index fb78de3c8..aad09ccbf 100644 --- a/apps/web/app/api/task-versions/[id]/route.ts +++ b/apps/web/app/api/task-versions/[id]/route.ts @@ -3,7 +3,7 @@ import { authenticatedGuard } from '@app/services/server/guards/authenticated-gu import { deleteTaskVersionRequest, editTaskVersionRequest } from '@app/services/server/requests/task-version'; import { NextResponse } from 'next/server'; -export async function PUT(req: Request, { params }: { params: { id: string } }) { +export async function PUT(req: Request, { params }: { params: { id: string } }) { const res = new NextResponse(); const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); @@ -12,17 +12,18 @@ export async function PUT(req: Request, { params }: { params: { id: string } }) const { id } = params; const datas = (await req.json()) as unknown as ITaskVersionCreate; - return $res( - await editTaskVersionRequest({ - id, - datas, - bearer_token: access_token, - tenantId - }) - ); + + const response = await editTaskVersionRequest({ + id, + datas, + bearer_token: access_token, + tenantId + }); + + return $res(response.data); } -export async function DELETE(req: Request, { params }: { params: { id: string } }) { +export async function DELETE(req: Request, { params }: { params: { id: string } }) { const res = new NextResponse(); const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); @@ -30,11 +31,11 @@ export async function DELETE(req: Request, { params }: { params: { id: string } const { id } = params; - return $res( - await deleteTaskVersionRequest({ - id, - bearer_token: access_token, - tenantId - }) - ); + const response = await deleteTaskVersionRequest({ + id, + bearer_token: access_token, + tenantId + }); + + return $res(response.data); } diff --git a/apps/web/app/api/task-versions/route.ts b/apps/web/app/api/task-versions/route.ts index 01325d220..cb3860127 100644 --- a/apps/web/app/api/task-versions/route.ts +++ b/apps/web/app/api/task-versions/route.ts @@ -32,5 +32,7 @@ export async function POST(req: Request) { const body = (await req.json()) as unknown as ITaskVersionCreate; - return $res(await createVersionRequest(body, access_token, body?.tenantId)); + const response = await createVersionRequest(body, access_token, body?.tenantId); + + return $res(response.data); } diff --git a/apps/web/app/api/tasks/[id]/route.ts b/apps/web/app/api/tasks/[id]/route.ts index 6c62f0a9b..84f6721fb 100644 --- a/apps/web/app/api/tasks/[id]/route.ts +++ b/apps/web/app/api/tasks/[id]/route.ts @@ -10,14 +10,14 @@ export async function GET(req: Request, { params }: { params: { id: string } }) const { id: taskId } = params; - return $res( - await getTaskByIdRequest({ - taskId: taskId as string, - tenantId, - organizationId, - bearer_token: access_token - }) - ); + const response = await getTaskByIdRequest({ + taskId: taskId as string, + tenantId, + organizationId, + bearer_token: access_token + }); + + return $res(response.data); } export async function PUT(req: Request, { params }: { params: { id: string } }) { @@ -52,4 +52,3 @@ export async function PUT(req: Request, { params }: { params: { id: string } }) return $res(tasks); } -// Unauthorized; diff --git a/apps/web/app/api/user/[id]/route.ts b/apps/web/app/api/user/[id]/route.ts index 456ab9820..93e85f7c9 100644 --- a/apps/web/app/api/user/[id]/route.ts +++ b/apps/web/app/api/user/[id]/route.ts @@ -28,16 +28,16 @@ export async function PUT(req: Request) { const body = (await req.json()) as unknown as IUser; - return $res( - await updateUserAvatarRequest( - { - data: body, - id: user.id as string, - tenantId - }, - access_token - ) + const response = await updateUserAvatarRequest( + { + data: body, + id: user.id as string, + tenantId + }, + access_token ); + + return $res(response.data); } export async function DELETE(req: Request) { @@ -46,11 +46,11 @@ export async function DELETE(req: Request) { const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); if (!user) return $res('Unauthorized'); - return $res( - await deleteUserRequest({ - id: user.id, - bearer_token: access_token, - tenantId - }) - ); + const response = await deleteUserRequest({ + id: user.id, + bearer_token: access_token, + tenantId + }); + + return $res(response.data); } diff --git a/apps/web/app/api/user/reset/route.ts b/apps/web/app/api/user/reset/route.ts index fc6c55b49..e905e393d 100644 --- a/apps/web/app/api/user/reset/route.ts +++ b/apps/web/app/api/user/reset/route.ts @@ -8,10 +8,10 @@ export async function DELETE(req: Request) { const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); if (!user) return $res('Unauthorized'); - return $res( - await resetUserRequest({ - bearer_token: access_token, - tenantId - }) - ); + const response = await resetUserRequest({ + bearer_token: access_token, + tenantId + }); + + return $res(response.data); } diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index cf6fed18b..c9ea813f1 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -1,85 +1,86 @@ -import { kanbanBoardState } from "@app/stores/kanban"; -import { useTaskStatus } from "./useTaskStatus"; -import { useRecoilState } from "recoil"; -import { useEffect, useState } from "react"; -import { ITaskStatusItemList, ITeamTask } from "@app/interfaces"; -import { useTeamTasks } from "./useTeamTasks"; +import { kanbanBoardState } from '@app/stores/kanban'; +import { useTaskStatus } from './useTaskStatus'; +import { useRecoilState } from 'recoil'; +import { useEffect, useState } from 'react'; +import { ITaskStatusItemList, ITeamTask } from '@app/interfaces'; +import { useTeamTasks } from './useTeamTasks'; export function useKanban() { + const [loading, setLoading] = useState(true); - const [loading, setLoading] = useState(true); - - const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); + const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); - const taskStatusHook = useTaskStatus(); + const taskStatusHook = useTaskStatus(); - const { tasks, tasksFetching, updateTask } = useTeamTasks(); + const { tasks, tasksFetching, updateTask } = useTeamTasks(); - /** - * format data for kanban board - */ - useEffect(()=> { - if(!taskStatusHook.loading && !tasksFetching) { - let kanban = {}; + /** + * format data for kanban board + */ + useEffect(() => { + if (!taskStatusHook.loading && !tasksFetching) { + let kanban = {}; - const getTasksByStatus = (status: string | undefined) => { - return tasks.filter((task: ITeamTask)=> { - return task.status === status - }) - } + const getTasksByStatus = (status: string | undefined) => { + return tasks.filter((task: ITeamTask) => { + return task.status === status; + }); + }; - taskStatusHook.taskStatus.map((taskStatus: ITaskStatusItemList,)=> { - kanban = { - ...kanban, - [taskStatus.name ? taskStatus.name : ''] : getTasksByStatus(taskStatus.name) - } - }); - setKanbanBoard(kanban) - setLoading(false) - } - },[taskStatusHook.loading, tasksFetching]) + taskStatusHook.taskStatus.map((taskStatus: ITaskStatusItemList) => { + kanban = { + ...kanban, + [taskStatus.name ? taskStatus.name : '']: getTasksByStatus(taskStatus.name) + }; + }); + setKanbanBoard(kanban); + setLoading(false); + } + }, [taskStatusHook.loading, tasksFetching]); - /** - * collapse or show kanban column - */ - const toggleColumn = (column: string, status: boolean) => { - const columnData = taskStatusHook.taskStatus.filter((taskStatus: ITaskStatusItemList,)=> { - return taskStatus.name === column - }); + /** + * collapse or show kanban column + */ + const toggleColumn = (column: string, status: boolean) => { + const columnData = taskStatusHook.taskStatus.filter((taskStatus: ITaskStatusItemList) => { + return taskStatus.name === column; + }); - const columnId = columnData[0].id; + const columnId = columnData[0].id; taskStatusHook.editTaskStatus(columnId, { isCollapsed: status }); - } + }; - const isColumnCollapse = (column: string) => { - const columnData = taskStatusHook.taskStatus.filter((taskStatus: ITaskStatusItemList,)=> { - return taskStatus.name === column - }); + const isColumnCollapse = (column: string) => { + const columnData = taskStatusHook.taskStatus.filter((taskStatus: ITaskStatusItemList) => { + return taskStatus.name === column; + }); - return columnData[0].isCollapsed - } + return columnData[0].isCollapsed; + }; - const reorderStatus = (itemStatus: string, index: number) => { - taskStatusHook.taskStatus.filter((status: ITaskStatusItemList)=> { - return status.name === itemStatus - }).map((status: ITaskStatusItemList)=> { - taskStatusHook.editTaskStatus(status.id, { - order: index - }); - }) - } + const reorderStatus = (itemStatus: string, index: number) => { + taskStatusHook.taskStatus + .filter((status: ITaskStatusItemList) => { + return status.name === itemStatus; + }) + .map((status: ITaskStatusItemList) => { + taskStatusHook.editTaskStatus(status.id, { + order: index + }); + }); + }; - return { - data: kanbanBoard, - isLoading: loading, - columns: taskStatusHook.taskStatus, - updateKanbanBoard: setKanbanBoard, - updateTaskStatus: updateTask, - toggleColumn, - isColumnCollapse, - reorderStatus - } -} \ No newline at end of file + return { + data: kanbanBoard, + isLoading: loading, + columns: taskStatusHook.taskStatus, + updateKanbanBoard: setKanbanBoard, + updateTaskStatus: updateTask, + toggleColumn, + isColumnCollapse, + reorderStatus + }; +} diff --git a/apps/web/app/hooks/features/usePublicOrganizationTeams.ts b/apps/web/app/hooks/features/usePublicOrganizationTeams.ts index 7467f68a6..00707b079 100644 --- a/apps/web/app/hooks/features/usePublicOrganizationTeams.ts +++ b/apps/web/app/hooks/features/usePublicOrganizationTeams.ts @@ -86,13 +86,14 @@ export function usePublicOrganizationTeams() { return res; }); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [queryCall, setTeams, setAllTasks, setPublicTeam, teams, publicTeam] ); const loadPublicTeamMiscData = useCallback( (profileLink: string, teamId: string) => { return queryCallMiscData(profileLink, teamId).then((res) => { - if (res.data.status === 404) { + if (res.data?.status === 404) { setTeams([]); return res; } diff --git a/apps/web/app/hooks/features/useTeamTasks.ts b/apps/web/app/hooks/features/useTeamTasks.ts index b1eb8c86d..aaf6da940 100644 --- a/apps/web/app/hooks/features/useTeamTasks.ts +++ b/apps/web/app/hooks/features/useTeamTasks.ts @@ -74,7 +74,7 @@ export function useTeamTasks() { const getTaskById = useCallback( (taskId: string) => { return getTasksByIdQueryCall(taskId).then((res) => { - setDetailedTask(res?.data?.data || null); + setDetailedTask(res?.data || null); return res; }); }, diff --git a/apps/web/app/hooks/useInfinityFetch.ts b/apps/web/app/hooks/useInfinityFetch.ts new file mode 100644 index 000000000..e85ace888 --- /dev/null +++ b/apps/web/app/hooks/useInfinityFetch.ts @@ -0,0 +1,37 @@ +'use client'; + +import React from 'react'; + +export const getPartData = ({ offset = 0, limit = 10, arr = [] }: { offset?: number; limit?: number; arr: any[] }) => + arr.slice(0, offset * limit + limit); + +export const useInfinityScrolling = (arr: any) => { + const [offset, setOffset] = React.useState(0); + const [data, setData] = React.useState(arr); + + const getSomeTasks = React.useCallback( + (offset: number) => { + setData(getPartData({ arr, limit: 10, offset })); + }, + [arr] + ); + + const nextOffset = React.useCallback(() => { + setOffset((prev) => prev + 1); + setData((prev) => getPartData({ arr: prev, limit: 10, offset })); + }, [offset]); + + React.useEffect(() => { + console.log({ offset }); + getSomeTasks(offset); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [offset]); + + return { + offset, + setOffset, + getSomeTasks, + nextOffset, + data + }; +}; diff --git a/apps/web/app/services/client/api/settings.ts b/apps/web/app/services/client/api/settings.ts index de4f16a07..52ef081b2 100644 --- a/apps/web/app/services/client/api/settings.ts +++ b/apps/web/app/services/client/api/settings.ts @@ -1,11 +1,11 @@ import { IUser } from '@app/interfaces'; -import api from '../axios'; +import { post, put } from '../axios'; export function savePersonalSettingsAPI(id: string, data: any) { - return api.post(`/user/${id}`, { ...data }); + return post(`/user/${id}`, { ...data }); } // update/delete profile avatar for user setting export function updateUserAvatarAPI(id: string, body: Partial) { - return api.put(`/user/${id}`, body); + return put(`/user/${id}`, body); } diff --git a/apps/web/app/services/client/api/task-version.ts b/apps/web/app/services/client/api/task-version.ts index cf4cb6b36..d3e3148db 100644 --- a/apps/web/app/services/client/api/task-version.ts +++ b/apps/web/app/services/client/api/task-version.ts @@ -1,30 +1,20 @@ -import { - CreateResponse, - DeleteResponse, - ITaskVersionCreate, - ITaskVersionItemList, - PaginationResponse -} from '@app/interfaces'; -import api, { get } from '../axios'; +import { DeleteResponse, ITaskVersionCreate, ITaskVersionItemList, PaginationResponse } from '@app/interfaces'; +import { deleteApi, get, post, put } from '../axios'; export function createTaskVersionAPI(data: ITaskVersionCreate, tenantId?: string) { - return api.post>('/task-versions', data, { - headers: { - 'Tenant-Id': tenantId - } + return post('/task-versions', data, { + tenantId }); } export function editTaskVersionAPI(id: string, data: ITaskVersionCreate, tenantId?: string) { - return api.put>(`/task-versions/${id}`, data, { - headers: { - 'Tenant-Id': tenantId - } + return put(`/task-versions/${id}`, data, { + tenantId }); } export function deleteTaskVersionAPI(id: string) { - return api.delete(`/task-versions/${id}`); + return deleteApi(`/task-versions/${id}`); } export async function getTaskVersionList(tenantId: string, organizationId: string, organizationTeamId: string | null) { diff --git a/apps/web/app/services/client/api/tasks.ts b/apps/web/app/services/client/api/tasks.ts index 1698725df..20182fe83 100644 --- a/apps/web/app/services/client/api/tasks.ts +++ b/apps/web/app/services/client/api/tasks.ts @@ -1,12 +1,51 @@ /* eslint-disable no-mixed-spaces-and-tabs */ -import { CreateResponse, DeleteResponse, PaginationResponse } from '@app/interfaces/IDataResponse'; +import { DeleteResponse, PaginationResponse } from '@app/interfaces/IDataResponse'; import { ICreateTask, ITeamTask } from '@app/interfaces/ITask'; import { ITasksTimesheet } from '@app/interfaces/ITimer'; -import api, { get } from '../axios'; +import api, { deleteApi, get, put } from '../axios'; import { GAUZY_API_BASE_SERVER_URL } from '@app/constants'; +import { + getActiveProjectIdCookie, + getActiveTeamIdCookie, + getOrganizationIdCookie, + getTenantIdCookie +} from '@app/helpers'; export function getTasksByIdAPI(taskId: string) { - return api.get>(`/tasks/${taskId}`); + const organizationId = getOrganizationIdCookie(); + const tenantId = getTenantIdCookie(); + + const relations = [ + 'tags', + 'teams', + 'members', + 'members.user', + 'creator', + 'linkedIssues', + 'linkedIssues.taskTo', + 'linkedIssues.taskFrom', + 'parent', + 'children' + ]; + + const obj = { + 'where[organizationId]': organizationId, + 'where[tenantId]': tenantId, + 'join[alias]': 'task', + 'join[leftJoinAndSelect][members]': 'task.members', + 'join[leftJoinAndSelect][user]': 'members.user', + includeRootEpic: 'true' + } as Record; + + relations.forEach((rl, i) => { + obj[`relations[${i}]`] = rl; + }); + + const query = new URLSearchParams(obj); + + const endpoint = GAUZY_API_BASE_SERVER_URL.value ? `/tasks/${taskId}?${query.toString()}` : `/tasks/${taskId}`; + + return get(endpoint); } export async function getTeamTasksAPI(organizationId: string, tenantId: string, projectId: string, teamId: string) { @@ -44,11 +83,26 @@ export async function getTeamTasksAPI(organizationId: string, tenantId: string, } export function deleteTaskAPI(taskId: string) { - return api.delete(`/tasks/${taskId}`); + return deleteApi(`/tasks/${taskId}`); } -export function updateTaskAPI(taskId: string, body: Partial) { - return api.put>(`/tasks/${taskId}`, body); +export async function updateTaskAPI(taskId: string, body: Partial) { + if (GAUZY_API_BASE_SERVER_URL.value) { + const tenantId = getTenantIdCookie(); + const organizationId = getOrganizationIdCookie(); + const teamId = getActiveTeamIdCookie(); + const projectId = getActiveProjectIdCookie(); + + const nBody = { ...body }; + delete nBody.selectedTeam; + delete nBody.rootEpic; + + await put(`/tasks/${taskId}`, nBody); + + return getTeamTasksAPI(organizationId, tenantId, projectId, teamId); + } + + return put>(`/tasks/${taskId}`, body); } export function createTeamTaskAPI(body: Partial & { title: string }) { diff --git a/apps/web/app/services/client/api/user.ts b/apps/web/app/services/client/api/user.ts index b878f67ba..d3b32d727 100644 --- a/apps/web/app/services/client/api/user.ts +++ b/apps/web/app/services/client/api/user.ts @@ -1,10 +1,10 @@ import { DeleteResponse } from '@app/interfaces'; -import api from '../axios'; +import { deleteApi } from '../axios'; export function deleteUserAPI(id: string) { - return api.delete(`/user/${id}`); + return deleteApi(`/user/${id}`); } export function resetUserAPI() { - return api.delete(`/user/reset`); + return deleteApi(`/user/reset`); } diff --git a/apps/web/app/services/server/requests/tasks.ts b/apps/web/app/services/server/requests/tasks.ts index 076c7fa1a..ef7407a95 100644 --- a/apps/web/app/services/server/requests/tasks.ts +++ b/apps/web/app/services/server/requests/tasks.ts @@ -1,4 +1,4 @@ -import { CreateResponse, DeleteResponse, PaginationResponse, SingleDataResponse } from '@app/interfaces'; +import { DeleteResponse, PaginationResponse, SingleDataResponse } from '@app/interfaces'; import { ICreateTask, ITeamTask } from '@app/interfaces/ITask'; import { serverFetch } from '../fetch'; import { IUser } from '@app/interfaces'; @@ -92,7 +92,7 @@ export function getTaskByIdRequest({ const query = new URLSearchParams(obj); - return serverFetch>({ + return serverFetch({ path: `/tasks/${taskId}?${query.toString()}`, method: 'GET', bearer_token, diff --git a/apps/web/app/utils/scroll-to-element.ts b/apps/web/app/utils/scroll-to-element.ts index 5aedc9fc8..a233b8d43 100644 --- a/apps/web/app/utils/scroll-to-element.ts +++ b/apps/web/app/utils/scroll-to-element.ts @@ -3,4 +3,4 @@ export function scrollToElement(rect: DOMRect, diff = 150) { top: rect.y > 0 ? rect.y + window.scrollY - diff : window.scrollY - Math.abs(rect.y) - diff, behavior: 'smooth' }); -} +} \ No newline at end of file diff --git a/apps/web/components/shared/Observer/index.tsx b/apps/web/components/shared/Observer/index.tsx new file mode 100644 index 000000000..7f6f10d0d --- /dev/null +++ b/apps/web/components/shared/Observer/index.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const ObserverComponent = ({ isLast, getNextData }: { isLast: boolean; getNextData: () => any }) => { + const cardRef = React.useRef(); + + React.useEffect(() => { + if (!cardRef?.current) return; + + const observer = new IntersectionObserver(([entry]) => { + if (isLast && entry.isIntersecting) { + // fetch with new Entry + console.log('IN OBSERVER'); + getNextData(); + observer.unobserve(entry.target); + } + }); + + observer.observe(cardRef.current); + }, [isLast, getNextData]); + // @ts-expect-error + return
; +}; diff --git a/apps/web/lib/features/user-profile-tasks.tsx b/apps/web/lib/features/user-profile-tasks.tsx index f87637377..7952dbd7b 100644 --- a/apps/web/lib/features/user-profile-tasks.tsx +++ b/apps/web/lib/features/user-profile-tasks.tsx @@ -3,6 +3,8 @@ import { Divider, Text } from 'lib/components'; import { TaskCard } from './task/task-card'; import { I_TaskFilter } from './task/task-filters'; import { useTranslations } from 'next-intl'; +import { ObserverComponent } from '@components/shared/Observer'; +import { useInfinityScrolling } from '@app/hooks/useInfinityFetch'; type Props = { tabFiltered: I_TaskFilter; @@ -28,6 +30,9 @@ export function UserProfileTask({ profile, tabFiltered }: Props) { const otherTasks = tasks.filter((t) => profile.member?.running == true ? t.id !== profile.activeUserTeamTask?.id : t ); + const { nextOffset, data } = useInfinityScrolling(otherTasks); + // const { total, onPageChange, itemsPerPage, itemOffset, endOffset, setItemsPerPage, currentItems } = + // usePagination(otherTasks); return (
@@ -79,9 +84,10 @@ export function UserProfileTask({ profile, tabFiltered }: Props) { )}
    - {otherTasks.map((task) => { + {data.map((task, index) => { return (
  • + { updateAvatar({ firstName: editMember?.employee?.user?.firstName || '', lastName: editMember?.employee?.user?.lastName || '', - id: editMember.employee.userId + id: editMember?.employee?.userId }).then(() => { const teamIndex = organizationTeams.findIndex((team) => team.id === activeTeamId); const tempOrganizationTeams = cloneDeep(organizationTeams);