diff --git a/.deploy/k8s/k8s-manifest-api.prod.yaml b/.deploy/k8s/k8s-manifest-api.prod.yaml index 11332f9d2..3a82c54ae 100644 --- a/.deploy/k8s/k8s-manifest-api.prod.yaml +++ b/.deploy/k8s/k8s-manifest-api.prod.yaml @@ -26,7 +26,7 @@ apiVersion: apps/v1 metadata: name: ever-teams-prod-api spec: - replicas: 6 + replicas: 3 selector: matchLabels: app: ever-teams-prod-api diff --git a/.deploy/k8s/k8s-manifest-api.stage.yaml b/.deploy/k8s/k8s-manifest-api.stage.yaml index cb08088fc..5bf790040 100644 --- a/.deploy/k8s/k8s-manifest-api.stage.yaml +++ b/.deploy/k8s/k8s-manifest-api.stage.yaml @@ -26,7 +26,7 @@ apiVersion: apps/v1 metadata: name: ever-teams-stage-api spec: - replicas: 3 + replicas: 2 selector: matchLabels: app: ever-teams-stage-api diff --git a/.deploy/k8s/k8s-manifest.prod.yaml b/.deploy/k8s/k8s-manifest.prod.yaml index 37275c528..86e05a9a7 100644 --- a/.deploy/k8s/k8s-manifest.prod.yaml +++ b/.deploy/k8s/k8s-manifest.prod.yaml @@ -27,7 +27,7 @@ apiVersion: apps/v1 metadata: name: ever-teams-prod-webapp spec: - replicas: 8 + replicas: 4 selector: matchLabels: app: ever-teams-prod-webapp diff --git a/apps/mobile/app/services/client/requests/auth.ts b/apps/mobile/app/services/client/requests/auth.ts index e4e267c88..897435e88 100644 --- a/apps/mobile/app/services/client/requests/auth.ts +++ b/apps/mobile/app/services/client/requests/auth.ts @@ -54,20 +54,30 @@ type IUEmployeeParam = { relations?: string[]; }; +/** + * Fetches details of the currently authenticated user. + * + * @param {IUEmployeeParam} The employee parameters, including bearer token and optional relations. + * @returns A Promise resolving to the IUser object with the desired relations and employee details. + */ export const currentAuthenticatedUserRequest = ({ bearer_token, - relations = ['employee', 'role', 'tenant'] + relations = ['role', 'tenant'] }: IUEmployeeParam) => { - const params = {} as { [x: string]: string }; + // Create a new instance of URLSearchParams for query string construction + const query = new URLSearchParams(); - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; + // Append each relation to the query string + relations.forEach((relation, index) => { + query.append(`relations[${index}]`, relation); }); - const query = new URLSearchParams(params); + // Append includeEmployee parameter set to true + query.append('includeEmployee', 'true'); + // Construct the fetch request with the query string return serverFetch({ - path: `/user/me?${query.toString()}`, + path: `/user/me?${query}`, method: 'GET', bearer_token }); diff --git a/apps/mobile/app/services/client/requests/organization-team.ts b/apps/mobile/app/services/client/requests/organization-team.ts index fa36743ed..946a0f320 100644 --- a/apps/mobile/app/services/client/requests/organization-team.ts +++ b/apps/mobile/app/services/client/requests/organization-team.ts @@ -36,6 +36,13 @@ export function updateOrganizationTeamRequest({ }); } +/** + * Fetches detailed information for a specific team within an organization. + * + * @param {TeamRequestParams & { teamId: string }} params Parameters including organizationId, tenantId, teamId, and optional relations. + * @param {string} bearer_token Authentication token for the request. + * @returns A Promise resolving to the detailed information of the organization team with member status. + */ export function getOrganizationTeamRequest( { organizationId, @@ -47,29 +54,28 @@ export function getOrganizationTeamRequest( 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects' ] }: TeamRequestParams & { teamId: string }, bearer_token: string ) { - const params = { + // Define query parameters + const queryParameters = { organizationId, tenantId, - // source: "BROWSER", - withLaskWorkedTask: 'true', + withLastWorkedTask: 'true', // Corrected the typo here startDate: moment().startOf('day').toISOString(), endDate: moment().endOf('day').toISOString(), - includeOrganizationTeamId: 'false' - } as { [x: string]: string }; + includeOrganizationTeamId: 'false', + ...Object.fromEntries(relations.map((relation, index) => [`relations[${index}]`, relation])) + }; - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); + // Construct the query string + const query = new URLSearchParams(queryParameters); - const queries = new URLSearchParams(params); + // Fetch and return the team data return serverFetch({ - path: `/organization-team/${teamId}?${queries.toString()}`, + path: `/organization-team/${teamId}?${query.toString()}`, method: 'GET', bearer_token, tenantId @@ -82,6 +88,13 @@ type TeamRequestParams = { relations?: string[]; }; +/** + * Fetches a list of all teams within an organization, including specified relation data. + * + * @param {TeamRequestParams} params Contains organizationId, tenantId, and optional relation specifications. + * @param {string} bearer_token Token for request authentication. + * @returns A Promise resolving to a paginated response of organization team lists. + */ export function getAllOrganizationTeamRequest( { organizationId, @@ -91,24 +104,21 @@ export function getAllOrganizationTeamRequest( 'members.role', 'members.employee', 'members.employee.user', - 'createdBy', - 'createdBy.employee' + 'createdBy' ] }: TeamRequestParams, bearer_token: string ) { - const params = { + // Define query parameters + const queryParameters = { 'where[organizationId]': organizationId, 'where[tenantId]': tenantId, source: 'BROWSER', - withLaskWorkedTask: 'true' - } as { [x: string]: string }; - - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); + withLaskWorkedTask: 'true', + ...Object.fromEntries(relations.map((relation, index) => [`relations[${index}]`, relation])) + }; - const query = new URLSearchParams(params); + const query = new URLSearchParams(queryParameters); return serverFetch>({ path: `/organization-team?${query.toString()}`, diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 5dc66460f..7aa669af1 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -229,7 +229,7 @@ const Kanban = () => { {/*
*/} -
+
{/** TODO:fetch teamtask based on days */} {activeTab && ( // add filter for today, yesterday and tomorrow
diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index 852e1a348..42596ba2e 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -80,11 +80,11 @@ export function useKanban() { }; const isColumnCollapse = (column: string) => { - const columnData = taskStatusHook.taskStatus.filter((taskStatus: ITaskStatusItemList) => { + const columnData = taskStatusHook.taskStatus.find((taskStatus: ITaskStatusItemList) => { return taskStatus.name === column; }); - return columnData[0].isCollapsed; + return columnData?.isCollapsed; }; const reorderStatus = (itemStatus: string, index: number) => { diff --git a/apps/web/app/services/client/api/auth.ts b/apps/web/app/services/client/api/auth.ts index 22d6d6a44..d6cec2346 100644 --- a/apps/web/app/services/client/api/auth.ts +++ b/apps/web/app/services/client/api/auth.ts @@ -14,16 +14,22 @@ import { import qs from 'qs'; import { signInEmailConfirmGauzy, signInWorkspaceGauzy } from './auth/invite-accept'; +/** + * Fetches data of the authenticated user with specified relations and the option to include employee details. + * + * @returns A Promise resolving to the IUser object. + */ export const getAuthenticatedUserDataAPI = () => { - const params = {} as { [x: string]: string }; - const relations = ['employee', 'role', 'tenant']; + // Define the relations to be included in the request + const relations = ['role', 'tenant']; - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; + // Construct the query string with 'qs', including the includeEmployee parameter + const query = qs.stringify({ + relations: relations, + includeEmployee: true // Append includeEmployee parameter set to true }); - const query = qs.stringify(params); - + // Execute the GET request to fetch the user data return get(`/user/me?${query}`); }; diff --git a/apps/web/app/services/client/api/auth/invite-accept.ts b/apps/web/app/services/client/api/auth/invite-accept.ts index 05f1aab58..1e84798a6 100644 --- a/apps/web/app/services/client/api/auth/invite-accept.ts +++ b/apps/web/app/services/client/api/auth/invite-accept.ts @@ -42,6 +42,13 @@ export function getUserOrganizationsRequest(params: { tenantId: string; userId: }); } +/** + * Fetches a list of all teams within an organization, including specified relation data. + * + * @param {ITeamRequestParams} params Parameters for the team request, including organization and tenant IDs, and optional relations. + * @param {string} bearer_token The bearer token for authentication. + * @returns A Promise resolving to the pagination response of organization teams. + */ export function getAllOrganizationTeamAPI(params: ITeamRequestParams, bearer_token: string) { const relations = params.relations || [ 'members', @@ -49,25 +56,24 @@ export function getAllOrganizationTeamAPI(params: ITeamRequestParams, bearer_tok 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects', 'projects.repository' ]; - const searchQueries = { + // Construct search queries + const queryParams = { 'where[organizationId]': params.organizationId, 'where[tenantId]': params.tenantId, source: TimerSource.TEAMS, - withLaskWorkedTask: 'true' - } as { [x: string]: string }; - - relations.forEach((rl, i) => { - searchQueries[`relations[${i}]`] = rl; - }); + withLastWorkedTask: 'true', // Corrected the typo here + ...Object.fromEntries(relations.map((relation, index) => [`relations[${index}]`, relation])) + }; - const query = qs.stringify(params); + // Serialize search queries into a query string + const queryString = qs.stringify(queryParams, { arrayFormat: 'brackets' }); - return get>(`/organization-team?${query}`, { + // Construct and execute the request + return get>(`/organization-team?${queryString}`, { tenantId: params.tenantId, headers: { Authorization: `Bearer ${bearer_token}` diff --git a/apps/web/app/services/client/api/organization-team.ts b/apps/web/app/services/client/api/organization-team.ts index 2769cab13..75e737fd9 100644 --- a/apps/web/app/services/client/api/organization-team.ts +++ b/apps/web/app/services/client/api/organization-team.ts @@ -16,6 +16,13 @@ import { getAccessTokenCookie, getOrganizationIdCookie, getTenantIdCookie } from import { createOrganizationProjectAPI } from './projects'; import qs from 'qs'; +/** + * Fetches a list of teams for a specified organization. + * + * @param {string} organizationId The unique identifier for the organization. + * @param {string} tenantId The tenant identifier. + * @returns A Promise resolving to a paginated response containing the list of organization teams. + */ export async function getOrganizationTeamsAPI(organizationId: string, tenantId: string) { const relations = [ 'members', @@ -23,22 +30,21 @@ export async function getOrganizationTeamsAPI(organizationId: string, tenantId: 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects', 'projects.repository' ]; - - const params = { + // Construct the query parameters including relations + const queryParameters = { 'where[organizationId]': organizationId, 'where[tenantId]': tenantId, source: TimerSource.TEAMS, - withLaskWorkedTask: 'true' - } as { [x: string]: string }; + withLastWorkedTask: 'true', // Corrected the typo here + relations + }; + + // Serialize parameters into a query string + const query = qs.stringify(queryParameters, { arrayFormat: 'brackets' }); - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); - const query = qs.stringify(params); const endpoint = `/organization-team?${query}`; return get>(endpoint, { tenantId }); @@ -84,36 +90,43 @@ export async function createOrganizationTeamAPI(name: string, user: IUser) { return api.post>('/organization-team', { name }); } +/** + * Fetches details of a specific team within an organization. + * + * @param {string} teamId The unique identifier of the team. + * @param {string} organizationId The unique identifier of the organization. + * @param {string} tenantId The tenant identifier. + * @returns A Promise resolving to the details of the specified organization team. + */ export async function getOrganizationTeamAPI(teamId: string, organizationId: string, tenantId: string) { - const params = { - organizationId: organizationId, - tenantId: tenantId, - // source: TimerSource.TEAMS, - withLaskWorkedTask: 'true', - startDate: moment().startOf('day').toISOString(), - endDate: moment().endOf('day').toISOString(), - includeOrganizationTeamId: 'false' - } as { [x: string]: string }; - const relations = [ 'members', 'members.role', 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects', 'projects.repository' ]; - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); + // Define base parameters including organization and tenant IDs, and date range + const queryParams = { + organizationId, + tenantId, + withLastWorkedTask: 'true', // Corrected the typo here + startDate: moment().startOf('day').toISOString(), + endDate: moment().endOf('day').toISOString(), + includeOrganizationTeamId: 'false', + relations + }; - const queries = qs.stringify(params); + // Serialize parameters into a query string using 'qs' + const queryString = qs.stringify(queryParams, { arrayFormat: 'brackets' }); - const endpoint = `/organization-team/${teamId}?${queries}`; + // Construct the endpoint URL + const endpoint = `/organization-team/${teamId}?${queryString}`; + // Fetch and return the team details return get(endpoint); } diff --git a/apps/web/app/services/server/requests/auth.ts b/apps/web/app/services/server/requests/auth.ts index de2abfa06..01a876f53 100644 --- a/apps/web/app/services/server/requests/auth.ts +++ b/apps/web/app/services/server/requests/auth.ts @@ -100,18 +100,23 @@ type IUEmployeeParam = { relations?: string[]; }; +/** + * Fetches details of the currently authenticated user, including specified relations. + * + * @param {IUEmployeeParam} employeeParams - The employee parameters, including bearer token and optional relations. + * @returns A Promise resolving to the IUser object with the desired relations. + */ export const currentAuthenticatedUserRequest = ({ bearer_token, - relations = ['employee', 'role', 'tenant'] + relations = ['role', 'tenant'] }: IUEmployeeParam) => { - const params = {} as { [x: string]: string }; - - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; + // Construct the query string with 'qs', including the includeEmployee parameter + const query = qs.stringify({ + relations: relations, + includeEmployee: true // Append includeEmployee parameter set to true }); - const query = qs.stringify(params); - + // Construct and return the server fetch request return serverFetch({ path: `/user/me?${query}`, method: 'GET', diff --git a/apps/web/app/services/server/requests/organization-team.ts b/apps/web/app/services/server/requests/organization-team.ts index b4040e9ff..a62c2c6d9 100644 --- a/apps/web/app/services/server/requests/organization-team.ts +++ b/apps/web/app/services/server/requests/organization-team.ts @@ -73,6 +73,13 @@ export function deleteOrganizationTeamRequest({ }); } +/** + * Fetches detailed information for a specific team within an organization. + * + * @param {ITeamRequestParams & { teamId: string }} params Contains team, organization, tenant IDs, and optional relations. + * @param {string} bearer_token Token for authenticating the request. + * @returns A Promise resolving to the detailed information of the organization team with additional status. + */ export function getOrganizationTeamRequest( { organizationId, @@ -84,37 +91,43 @@ export function getOrganizationTeamRequest( 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects', 'projects.repository' ] }: ITeamRequestParams & { teamId: string }, bearer_token: string ) { - const params = { - organizationId: organizationId, - tenantId: tenantId, + // Define base query parameters + const queryParams = { + organizationId, + tenantId, // source: TimerSource.TEAMS, - withLaskWorkedTask: 'true', + withLastWorkedTask: 'true', // Corrected typo startDate: moment().startOf('day').toISOString(), endDate: moment().endOf('day').toISOString(), - includeOrganizationTeamId: 'false' - } as { [x: string]: string }; + includeOrganizationTeamId: 'false', + ...Object.fromEntries(relations.map((relation, index) => [`relations[${index}]`, relation])) + }; - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); - - const queries = qs.stringify(params); + // Serialize parameters into a query string + const queryString = qs.stringify(queryParams, { arrayFormat: 'brackets' }); + // Fetch and return team details return serverFetch({ - path: `/organization-team/${teamId}?${queries.toString()}`, + path: `/organization-team/${teamId}?${queryString}`, method: 'GET', bearer_token, tenantId }); } +/** + * Fetches team details for a specified organization from the server. + * + * @param {TeamRequestParams} params Contains organizationId, tenantId, and optional relationship specifications. + * @param {string} bearer_token Token for request authentication. + * @returns A Promise resolving to a paginated list of organization team data. + */ export function getAllOrganizationTeamRequest( { organizationId, @@ -125,26 +138,25 @@ export function getAllOrganizationTeamRequest( 'members.employee', 'members.employee.user', 'createdBy', - 'createdBy.employee', 'projects', 'projects.repository' ] }: ITeamRequestParams, bearer_token: string ) { + // Consolidate all parameters into a single object const params = { 'where[organizationId]': organizationId, 'where[tenantId]': tenantId, source: TimerSource.TEAMS, - withLaskWorkedTask: 'true' - } as { [x: string]: string }; - - relations.forEach((rl, i) => { - params[`relations[${i}]`] = rl; - }); + withLastWorkedTask: 'true', + relations + }; - const query = qs.stringify(params); + // Serialize parameters into a query string + const query = qs.stringify(params, { arrayFormat: 'brackets' }); + // Construct and return the server fetch request return serverFetch>({ path: `/organization-team?${query}`, method: 'GET', diff --git a/apps/web/components/pages/kanban/edit-status-modal.tsx b/apps/web/components/pages/kanban/edit-status-modal.tsx new file mode 100644 index 000000000..fa0f9187d --- /dev/null +++ b/apps/web/components/pages/kanban/edit-status-modal.tsx @@ -0,0 +1,116 @@ +import React, { useState } from 'react'; +import { useTaskStatus } from '@app/hooks'; +import { Button, Text, InputField, ColorPicker, Card } from 'lib/components'; +import { useForm } from 'react-hook-form'; +import { IIcon } from '@app/interfaces'; +import { generateIconList } from 'lib/settings/icon-items'; +import { useTranslations } from 'next-intl'; +import IconPopover from 'lib/settings/icon-popover'; +import { Loader } from 'lucide-react'; + +type EditSet = { + name: string; + color: string; + icon: string; +}; +const EditStatusModal = ({ status, onClose, setColumn }: { status: any; onClose: any; setColumn: any }) => { + const { editTaskStatus, loading,editTaskStatusLoading } = useTaskStatus(); + const editStatus: any = editTaskStatus; + const [createNew] = useState(status); + const t = useTranslations(); + const { register, handleSubmit, setValue, getValues } = useForm({ + defaultValues: { + name: status.name || '', + color: status.color || '', + icon: status.icon || '' + } + }); + const renameProperty = (newProp: string, icon: string) => { + setColumn((prev: any) => { + const newColumn = prev.map((column: any) => { + if (column.id === status.id) { + return { + ...column, + name: newProp, + icon: !icon.includes('https') ? `https://api.ever.team/public/${icon}` : icon + }; + } + return column; + }); + return newColumn; + }); + }; + + const onSubmit = async (values: EditSet) => { + if (status && values) { + await editStatus(status.id, { ...values, color: !values.color ? status.color : values.color }).then(() => { + renameProperty(values.name, values.icon); + + // Call this function with 'Open1' and 'Open2' when you need to change the property name. + onClose(); + }); + } + }; + const taskStatusIconList: IIcon[] = generateIconList('task-statuses', [ + 'open', + 'in-progress', + 'ready', + 'in-review', + 'blocked', + 'completed' + ]); + const taskSizesIconList: IIcon[] = generateIconList('task-sizes', ['x-large', 'large', 'medium', 'small', 'tiny']); + const taskPrioritiesIconList: IIcon[] = generateIconList('task-priorities', ['urgent', 'high', 'medium', 'low']); + + const iconList: IIcon[] = [...taskStatusIconList, ...taskSizesIconList, ...taskPrioritiesIconList]; + + return ( +
+ +
+ + {createNew ? t('common.NEW') : t('common.EDIT')} {t('common.ISSUE_TYPE')} + +
+ + + v.fullUrl === getValues('icon'))} + /> + setValue('color', e)} /> +
+
+ + +
+
+
+
+ ); +}; + +export default EditStatusModal; diff --git a/apps/web/lib/components/Kanban.tsx b/apps/web/lib/components/Kanban.tsx index e38578d92..c1be067b5 100644 --- a/apps/web/lib/components/Kanban.tsx +++ b/apps/web/lib/components/Kanban.tsx @@ -23,6 +23,7 @@ import { useModal } from '@app/hooks'; import { Modal } from './modal'; import CreateTaskModal from '@components/pages/kanban/create-task-modal'; import Image from 'next/image'; +import EditStatusModal from '@components/pages/kanban/edit-status-modal'; const grid = 8; @@ -70,29 +71,31 @@ function InnerItemList({ items, title }: { title: string; items: ITeamTask[]; dr return ( <>
- {items.map((item: ITeamTask, index: number) => ( - - {(dragProvided: DraggableProvided, dragSnapshot: DraggableStateSnapshot) => ( - 0 && + items.map((item: ITeamTask, index: number) => ( + + {(dragProvided: DraggableProvided, dragSnapshot: DraggableStateSnapshot) => ( + - )} - - ))} - {items.length == 0 && ( + /> + )} + + ))} + {Array.isArray(items) && items?.length == 0 && (
not found! @@ -181,11 +184,15 @@ export const KanbanDroppable = ({ export const EmptyKanbanDroppable = ({ index, title, + status, + setColumn, items, backgroundColor }: { index: number; title: string; + status: any; + setColumn: any; backgroundColor: any; items: ITeamTask[]; }) => { @@ -203,6 +210,7 @@ export const EmptyKanbanDroppable = ({ }, []); const { isOpen, closeModal, openModal } = useModal(); + const { isOpen:editIsOpen, closeModal:editCloseModal, openModal:editOpenModal } = useModal(); if (!enabled) return null; @@ -258,9 +266,22 @@ export const EmptyKanbanDroppable = ({ > Collapse Column
+
+ Edit Status +
+ + +
+ + + ); }; @@ -298,6 +322,8 @@ export const EmptyKanbanDroppable = ({ const KanbanDraggableHeader = ({ title, icon, + setColumn, + status, items, snapshot, createTask, @@ -306,6 +332,8 @@ const KanbanDraggableHeader = ({ }: { title: string; items: any; + setColumn: any; + status: any; icon: string; createTask: () => void; snapshot: DraggableStateSnapshot; @@ -313,6 +341,8 @@ const KanbanDraggableHeader = ({ provided: DraggableProvided; }) => { const { toggleColumn } = useKanban(); + const { isOpen, closeModal, openModal } = useModal(); + return ( <> {title && ( @@ -334,7 +364,7 @@ const KanbanDraggableHeader = ({ flex flex-col items-center justify-center px-2.5 text-xs py-1 text-black bg-transparentWhite rounded-xl" > - {items.length} + {items?.length ?? '0'}
@@ -360,12 +390,9 @@ const KanbanDraggableHeader = ({ > Collapse Column
- {/*
toggleColumn(title, true)} - > +
Edit Status -
*/} +