Skip to content

Commit

Permalink
add project dropdown in task creation process
Browse files Browse the repository at this point in the history
  • Loading branch information
CREDO23 committed Nov 25, 2024
1 parent b914188 commit 4d6b944
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 25 deletions.
27 changes: 26 additions & 1 deletion apps/web/app/hooks/features/useOrganizationProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import {
editOrganizationProjectSettingAPI,
editOrganizationProjectAPI,
getOrganizationProjectAPI,
getOrganizationProjectsAPI
getOrganizationProjectsAPI,
createOrganizationProjectAPI
} from '@app/services/client/api';
import { userState } from '@app/stores';
import { useCallback } from 'react';
import { useAtom } from 'jotai';
import { useQuery } from '../useQuery';
import { organizationProjectsState } from '@/app/stores/organization-projects';
import { getOrganizationIdCookie, getTenantIdCookie } from '@/app/helpers';

export function useOrganizationProjects() {
const [user] = useAtom(userState);
Expand All @@ -26,6 +28,9 @@ export function useOrganizationProjects() {
const { loading: getOrganizationProjectsLoading, queryCall: getOrganizationProjectsQueryCall } =
useQuery(getOrganizationProjectsAPI);

const { loading: createOrganizationProjectLoading, queryCall: createOrganizationProjectQueryCall } =
useQuery(createOrganizationProjectAPI);

const editOrganizationProjectSetting = useCallback(
(id: string, data: any) => {
if (user?.tenantId) {
Expand Down Expand Up @@ -69,6 +74,24 @@ export function useOrganizationProjects() {
}
}, [getOrganizationProjectsQueryCall, setOrganizationProjects]);

const createOrganizationProject = useCallback(
async (data: { name: string }) => {
try {
const organizationId = getOrganizationIdCookie();
const tenantId = getTenantIdCookie();

const res = await createOrganizationProjectQueryCall({ ...data, organizationId, tenantId });

setOrganizationProjects([...organizationProjects, res.data]);

return res.data;
} catch (error) {
console.error(error);
}
},
[createOrganizationProjectQueryCall, organizationProjects, setOrganizationProjects]
);

return {
editOrganizationProjectSetting,
editOrganizationProjectSettingLoading,
Expand All @@ -79,5 +102,7 @@ export function useOrganizationProjects() {
getOrganizationProjects,
getOrganizationProjectsLoading,
organizationProjects,
createOrganizationProject,
createOrganizationProjectLoading
};
}
11 changes: 3 additions & 8 deletions apps/web/app/services/client/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { IProject } from '@app/interfaces';
import { IProject, IProjectCreate } from '@app/interfaces';
import { post } from '../axios';

type Params = {
name: string;
tenantId: string;
organizationId: string;
};
export function createOrganizationProjectAPI(data: IProjectCreate) {

export function createOrganizationProjectAPI(params: Params) {
return post<IProject>(`/organization-projects`, params);
return post<IProject>(`/organization-projects`, data);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useModal, useOrganizationProjects, useTeamTasks } from '@app/hooks';
import { useModal, useOrganizationProjects, useOrganizationTeams, useTeamTasks } from '@app/hooks';
import { IProject, ITaskVersionCreate, ITeamTask } from '@app/interfaces';
import { detailedTaskState } from '@app/stores';
import { PlusIcon } from '@heroicons/react/20/solid';
Expand All @@ -8,7 +8,6 @@ import {
ActiveTaskSizesDropdown,
ActiveTaskStatusDropdown,
ActiveTaskVersionDropdown,
CreateTeamModal,
EpicPropertiesDropdown as TaskEpicDropdown,
TaskLabels,
TaskStatus,
Expand All @@ -28,6 +27,7 @@ import { clsxm } from '@/app/utils';
import { organizationProjectsState } from '@/app/stores/organization-projects';
import ProjectIcon from '@components/ui/svgs/project-icon';
import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar';
import { CreateProjectModal } from '@/lib/features/project/create-project-modal';

type StatusType = 'version' | 'epic' | 'status' | 'label' | 'size' | 'priority';

Expand Down Expand Up @@ -286,11 +286,12 @@ export function ProjectDropDown(props: ITaskProjectDropdownProps) {
const organizationProjects = useAtomValue(organizationProjectsState);
const { getOrganizationProjects } = useOrganizationProjects();
const { updateTask, updateLoading } = useTeamTasks();
const { teams } = useOrganizationTeams();
const t = useTranslations();

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

const [selected, setSelected] = useState<IProject>();

Expand Down Expand Up @@ -411,25 +412,25 @@ export function ProjectDropDown(props: ITaskProjectDropdownProps) {
</Listbox.Option>
);
})}
{!controlled && (
<div className="mt-2">
<div className="mt-2">
{!controlled && (
<Button
className=" px-2 py-1 w-full !justify-start !gap-2 !min-w-min h-[2rem] rounded-lg text-xs dark:text-white dark:border-white"
variant="outline"
onClick={handleRemoveProject}
>
<TrashIcon className="w-5 " /> {t('common.REMOVE')}
</Button>
<Button
className=" px-2 py-1 mt-2 w-full !justify-start !min-w-min h-[2rem] rounded-lg text-xs dark:text-white dark:border-white"
variant="outline"
onClick={openModal}
>
<AddIcon className="w-3 h-3 text-dark dark:text-white" />{' '}
<span className=" truncate">Create new</span>
</Button>
</div>
)}
)}
<Button
className=" px-2 py-1 mt-2 w-full !justify-start !min-w-min h-[2rem] rounded-lg text-xs dark:text-white dark:border-white"
variant="outline"
onClick={openModal}
>
<AddIcon className="w-3 h-3 text-dark dark:text-white" />{' '}
<span className=" truncate">Create new</span>
</Button>
</div>
</div>
<ScrollBar className="-pr-60" />
</ScrollArea>
Expand All @@ -441,7 +442,17 @@ export function ProjectDropDown(props: ITaskProjectDropdownProps) {
}}
</Listbox>
</div>
{<CreateTeamModal open={isOpen} closeModal={closeModal} />}
<CreateProjectModal
onSuccess={(project) => {
setSelected(project);
onChange?.(project);
if (!controlled) {
handleUpdateProject(project);
}
}}
open={isOpen}
closeModal={closeModal}
/>
</>
);
}
87 changes: 87 additions & 0 deletions apps/web/lib/features/project/create-project-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useOrganizationProjects } from '@/app/hooks';
import { IProject } from '@/app/interfaces';
import { Button, Card, InputField, Modal, Text } from 'lib/components';
import { useTranslations } from 'next-intl';
import { useCallback, useState } from 'react';

interface ICreateProjectModalProps {
open: boolean;
closeModal: () => void;
onSuccess?: (project: IProject) => void;
}
/**
* A modal that allow to create a new project
*
* @param {Object} props - The props Object
* @param {boolean} props.open - If true open the modal otherwise close the modal
* @param {() => void} props.closeModal - A function to close the modal
*
* @returns {JSX.Element} The modal element
*/
export function CreateProjectModal(props: ICreateProjectModalProps) {
const t = useTranslations();
const { open, closeModal, onSuccess } = props;
const { createOrganizationProject, createOrganizationProjectLoading } = useOrganizationProjects();
const [name, setName] = useState('');

const handleCreateProject = useCallback(async () => {
try {
const data = await createOrganizationProject({ name });

if (data) {
onSuccess?.(data);
}

setName('');
closeModal();
} catch (error) {
console.error(error);
}
}, [closeModal, createOrganizationProject, name, onSuccess]);

return (
<Modal isOpen={open} closeModal={closeModal} alignCloseIcon>
<Card className=" sm:w-[33rem] w-[20rem]" shadow="custom">
<div className="flex flex-col items-center justify-between gap-8">
<Text.Heading as="h3" className="text-center">
{t('common.CREATE_PROJECT')}
</Text.Heading>

<div className="w-full">
<InputField
name="name"
autoCustomFocus
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={'Please enter the project name'}
required
className="w-full"
wrapperClassName=" h-full border border-blue-500"
noWrapper
/>
</div>

<div className="flex items-center justify-between w-full">
<Button
disabled={createOrganizationProjectLoading}
onClick={closeModal}
className="h-[2.75rem]"
variant="outline"
>
{t('common.CANCEL')}
</Button>
<Button
disabled={createOrganizationProjectLoading}
loading={createOrganizationProjectLoading}
className="h-[2.75rem]"
type="submit"
onClick={handleCreateProject}
>
{t('common.CREATE')}
</Button>
</div>
</div>
</Card>
</Modal>
);
}

0 comments on commit 4d6b944

Please sign in to comment.